Squeeze & Exhaustion Breadth
Abstract
This signal measures how broadly squeeze and exhaustion conditions appear across futures markets in the CoT dataset. For each report date, it aggregates market-level tactical flags (squeeze_risk, exhaustion_risk) into breadth measures, normalises them with a robust z-score, and maps them into simple regimes such as High Squeeze Risk or High Exhaustion Risk. The result is a compact indicator of whether the futures complex is vulnerable to forced covering or trend fatigue.
1. Inputs
- df_cot_signals — unified enriched CoT dataset with market-level positioning and tactical flags.
- squeeze_risk — boolean flag for markets at risk of short- or long-squeeze events.
- exhaustion_risk — boolean flag for markets showing late-trend exhaustion characteristics.
- crowded_long / crowded_short — crowding context (stored but not directly used in the z-signal here).
- Market_and_Exchange_Names — used to count distinct markets per report date.
2. Daily Aggregation
For each CoT report date, the model aggregates tactical flags into cross-market breadth metrics:
- n_markets — number of unique markets.
- squeeze_count — number of markets with squeeze_risk = True.
- exhaustion_count — number of markets with exhaustion_risk = True.
These raw counts are then converted into shares to make the signal comparable over time:
If n_markets = 0 on any date, it is replaced with NaN to avoid division by zero.
3. Normalised Signal Construction
To stabilise the breadth measures and place them on a comparable scale, a robust z-score transformation is applied separately to squeeze_share and exhaustion_share:
Here, MAD(X) is the median absolute deviation. Using median/MAD instead of mean/standard deviation makes the signal more resilient to outliers and structural breaks in the CoT sample.
4. Regime Labelling
The normalised values are mapped into intuitive risk regimes using symmetric thresholds:
The same thresholds are applied to the exhaustion z-score to define High Exhaustion Risk, Moderate Exhaustion Risk, and Normal exhaustion conditions.
5. Implementation (Python)
df_se = df_cot_signals.copy()
df_se["Report_Date"] = pd.to_datetime(df_se["Report_Date"], errors="coerce")
# Ensure flags exist
for flag in ["squeeze_risk", "exhaustion_risk", "crowded_long", "crowded_short"]:
if flag not in df_se.columns:
df_se[flag] = False
# Aggregation
df_squeeze_exhaust = (
df_se.groupby("Report_Date").agg(
n_markets=("Market_and_Exchange_Names", "nunique"),
squeeze_count=("squeeze_risk", "sum"),
exhaustion_count=("exhaustion_risk", "sum"),
crowded_long_count=("crowded_long", "sum"),
crowded_short_count=("crowded_short", "sum"),
).reset_index()
)
df_squeeze_exhaust["n_markets"] = df_squeeze_exhaust["n_markets"].replace(0, np.nan)
df_squeeze_exhaust["squeeze_share"] = (
df_squeeze_exhaust["squeeze_count"] / df_squeeze_exhaust["n_markets"]
)
df_squeeze_exhaust["exhaustion_share"] = (
df_squeeze_exhaust["exhaustion_count"] / df_squeeze_exhaust["n_markets"]
)
# Robust z-score helper
def _robust_z(series: pd.Series) -> pd.Series:
x = pd.to_numeric(series, errors="coerce").astype(float)
med = x.median()
mad = (x - med).abs().median()
if mad == 0 or np.isnan(mad):
return pd.Series(np.nan, index=series.index)
return (x - med) / (1.4826 * mad)
# Normalised signals
df_squeeze_exhaust["Squeeze_z"] = _robust_z(df_squeeze_exhaust["squeeze_share"])
df_squeeze_exhaust["Exhaustion_z"] = _robust_z(df_squeeze_exhaust["exhaustion_share"])
# Regime labels
def _squeeze_label(z: float) -> str:
if pd.isna(z):
return "Unknown"
if z >= 1.0:
return "High Squeeze Risk"
if 0.5 <= z < 1.0:
return "Moderate Squeeze Risk"
return "Normal"
def _exhaust_label(z: float) -> str:
if pd.isna(z):
return "Unknown"
if z >= 1.0:
return "High Exhaustion Risk"
if 0.5 <= z < 1.0:
return "Moderate Exhaustion Risk"
return "Normal"
df_squeeze_exhaust["Squeeze_Regime"] = df_squeeze_exhaust["Squeeze_z"].apply(_squeeze_label)
df_squeeze_exhaust["Exhaustion_Regime"] = df_squeeze_exhaust["Exhaustion_z"].apply(_exhaust_label)
6. Interpretation
- High Squeeze Risk — squeeze flags are elevated across a wide set of markets, indicating vulnerability to sharp, flow-driven moves.
- High Exhaustion Risk — exhaustion flags are widespread, suggesting that prevailing trends may be losing momentum.
- Normal — breadth of squeeze or exhaustion conditions is close to its historical median, signalling typical tactical risk.
7. Limitations
- Breadth measures ignore sector composition; concentrated stress in a key sector may be downplayed.
- Flag quality depends on upstream definitions of squeeze_risk and exhaustion_risk.
- Static z-thresholds may need recalibration as the number and mix of markets in df_cot_signals evolve.
8. Practical Use Cases
- Gauge whether the futures complex is in a fragile state prone to squeezes.
- Flag periods where widespread exhaustion risk suggests caution on trend-following strategies.
- Use as a tactical overlay in macro risk management alongside volatility, liquidity, and positioning composites.