How PositionFlow computes positioning signals for ES futures. Every rule is explicit, every threshold is documented, and the full logic is open for audit.
PositionFlow uses the Traders in Financial Futures (TFF) report published weekly by the U.S. Commodity Futures Trading Commission (CFTC). Specifically:
| Report | TFF — Futures Only |
| Market | S&P 500 Consolidated (13874+) |
| Covers | E-mini + classic S&P 500 futures combined |
| Published | Every Friday at 3:30 PM ET |
| As-of date | Positions as of Tuesday close |
| History | 2011 to present (~793 weekly reports) |
The CFTC data is public domain. PositionFlow ingests it automatically every Friday and runs the signal engine within minutes of publication.
Most COT analysis uses the Legacy report. For ES futures, Legacy COT has a critical flaw: it groups hedge funds and pension funds together under "Non-Commercial." These two groups trade with completely different horizons and motivations.
Non-Commercial = hedge funds + pension funds + CTAs, all in one bucket. Long-term structural positions dilute the crowding signal from fast money.
Separates Leveraged Funds (hedge funds, CTAs) from Asset Managers (pension funds, endowments). The separation is the signal.
| Category | Who | Role in signal |
|---|---|---|
| Leveraged Funds | Hedge funds, CTAs, managed money | Primary signal. Fast money — crowded positioning precedes mean reversion. |
| Asset Managers | Pension funds, endowments, insurance | Context only. Structural, slow-moving. Confirms or opposes the LF extreme. |
| Dealer/Intermediary | Banks, swap dealers | Not used. Primarily hedging client flow. |
| Other Reportables | Everyone else above reporting threshold | Not used. Too heterogeneous for signal extraction. |
This is a contrarian signal. When hedge funds and CTAs are positioned at extreme levels relative to the last 5 years, the trade is "crowded." Crowded trades unwind — sometimes gradually, sometimes violently.
The signal does not predict timing. It identifies a positioning environment where the probability of mean reversion is elevated. It's one input among many, not a standalone trading system.
Raw position counts are meaningless without context. 500,000 Leveraged Funds net short contracts might be extreme in 2015 and normal in 2026 as open interest grows. Percentile rank normalizes across time.
For each weekly report, PositionFlow computes the percentile rank of the current Leveraged Funds net position relative to two lookback windows:
| Window | Lookback | Purpose |
|---|---|---|
| 52-week | ~52 reports | Recent context. Is positioning extreme relative to the past year? |
| 5-year | ~260 reports | Primary signal window. Is positioning extreme relative to a full market cycle? |
percentile = (count of historical values ≤ current value) / (total historical values) × 100
The same percentile computation is applied to Asset Manager net positioning over 52 weeks, used for the institutional context layer.
The signal engine evaluates a fixed set of rules in order. Every rule that fires is included in the rules_triggered array so you can audit exactly what produced the signal.
| Condition | Result | Rule tag |
|---|---|---|
| LF net percentile (5yr) ≥ 80 | bearish_lean | lev_funds_net_above_80th_percentile_5yr |
| LF net percentile (5yr) ≤ 20 | bullish_lean | lev_funds_net_below_20th_percentile_5yr |
| Between 20 and 80 | neutral | No rules triggered |
The 5-year window is the primary trigger. If positioning is within the 20th–80th percentile range, no signal is generated regardless of other factors.
If a 5-year extreme is detected, the engine checks whether the 52-week percentile confirms it:
| Condition | Rule tag |
|---|---|
| Bearish lean + LF 52wk percentile ≥ 80 | lev_funds_net_above_80th_percentile_52wk |
| Bullish lean + LF 52wk percentile ≤ 20 | lev_funds_net_below_20th_percentile_52wk |
52-week alignment means the extreme isn't just unusual over 5 years — it's also unusual in the recent context. This increases confidence.
Asset Manager positioning is evaluated on a 52-week percentile basis to determine whether institutional money confirms or opposes the Leveraged Funds extreme:
| AM 52wk percentile | During bearish_lean | During bullish_lean |
|---|---|---|
| < 35 (AM positioned short) | Opposing (strengthens reversal) | Supportive |
| 35 – 65 | Neutral — no context signal | |
| > 65 (AM positioned long) | Supportive | Opposing (strengthens reversal) |
Signal strength is determined by how many confirming factors align. It escalates in three levels:
| Strength | Requirements |
|---|---|
| Low | 5-year extreme detected only. No 52-week alignment, no Asset Manager context. |
| Medium | 5-year extreme + Asset Manager opposition (or support). |
| High | 5-year extreme + 52-week alignment + Asset Manager opposition (or support). All three factors firing together. |
When positioning is in the neutral zone (20th–80th percentile), signal strength is none and no bias is assigned.
Every API response includes these signal fields. Nothing is hidden — what fires is what you see.
| Field | Values | Description |
|---|---|---|
bias |
neutral, bullish_lean, bearish_lean |
Directional lean from LF positioning extreme |
signal_strength |
none, low, medium, high |
How many confirming factors align |
signal_type |
no_signal, leveraged_fund_extreme |
What generated the signal |
institutional_context |
asset_managers_neutral, ..._opposing, ..._supportive |
Asset Manager positioning relative to LF extreme |
rules_triggered |
Array of rule tags | Every rule that fired, for full auditability |
extreme_flag |
true / false |
Whether a 5yr extreme was detected |
extreme_side |
null, leveraged_funds_long, leveraged_funds_short |
Which side is crowded |
signal_version |
v1 |
Bumped when thresholds or logic change |
All thresholds used by the v1 signal engine, in one place:
| Parameter | Value | Used for |
|---|---|---|
| LF extreme high | 80th percentile | Crowded long detection (bearish_lean) |
| LF extreme low | 20th percentile | Crowded short detection (bullish_lean) |
| AM neutral low | 35th percentile | Below this, AM context is non-neutral |
| AM neutral high | 65th percentile | Above this, AM context is non-neutral |
| 52-week window | 52 reports | Recent percentile context |
| 5-year window | 260 reports | Primary signal percentile window |
| Minimum data points | 10 | Below this, percentile defaults to 50 |
When any of these thresholds change, signal_version is bumped so consumers can detect the change.