CoT Market Tone
Abstract
The CoT Market Tone signal aggregates daily positioning across all markets to determine whether speculative futures activity is broadly Risk-On or Risk-Off. By comparing crowded-long vs crowded-short breadth, constructing a net tone index, and applying robust z-score scaling, the model classifies market tone into regimes ranging from Strong Risk-On to Strong Risk-Off.
1. Inputs
- df_cot_signals — unified enriched CoT dataset with market-level signals.
- crowded_long / crowded_short flags — indicate extreme speculative positioning.
- Market_and_Exchange_Names — used to count unique markets per report date.
2. Daily Aggregation: Breadth Metrics
For each CoT reporting date, the model aggregates market-level information to compute Risk-On vs Risk-Off breadth:
- crowded_long_count: number of markets flagged crowded long.
- crowded_short_count: number of markets flagged crowded short.
- n_markets: number of unique markets in the dataset.
These are used to compute:
3. Robust Z-Score Normalisation
A robust z-score is applied to the daily net tone to stabilise the signal and reduce sensitivity to outliers:
This transformation ensures that extreme values, skewed distributions, and structural shifts do not distort the market tone classification.
4. Regime Classification
The robust z-score is mapped to qualitative market regimes:
5. Implementation (Python)
df_tone = df_cot_signals.copy()
df_tone["Report_Date"] = pd.to_datetime(df_tone["Report_Date"], errors="coerce")
# Aggregation
df_cot_tone_daily = (
df_tone.groupby("Report_Date").agg(
n_markets=("Market_and_Exchange_Names", "nunique"),
crowded_long_count=("crowded_long", "sum"),
crowded_short_count=("crowded_short", "sum"),
).reset_index()
)
df_cot_tone_daily["crowded_long_share"] = df_cot_tone_daily["crowded_long_count"] / df_cot_tone_daily["n_markets"]
df_cot_tone_daily["crowded_short_share"] = df_cot_tone_daily["crowded_short_count"] / df_cot_tone_daily["n_markets"]
df_cot_tone_daily["net_tone"] = df_cot_tone_daily["crowded_long_share"] - df_cot_tone_daily["crowded_short_share"]
# Robust z-score
med = df_cot_tone_daily["net_tone"].median()
mad = (df_cot_tone_daily["net_tone"] - med).abs().median()
df_cot_tone_daily["CoT_Market_Tone_z"] = (df_cot_tone_daily["net_tone"] - med) / (1.4826 * mad)
# Regimes
def _tone_label(z):
if pd.isna(z): return "Unknown"
if z >= 1.0: return "Strong Risk-On"
if z >= 0.5: return "Risk-On"
if z > -0.5: return "Neutral"
if z > -1.0: return "Risk-Off"
return "Strong Risk-Off"
df_cot_tone_daily["CoT_Market_Tone_Regime"] = df_cot_tone_daily["CoT_Market_Tone_z"].apply(_tone_label)
6. Interpretation
- Risk-On tone indicates speculative appetite and broad directional alignment in long positioning.
- Risk-Off tone reflects defensive or uncertain market behaviour with increased short positioning breadth.
- Neutral tone signals a balanced speculative environment lacking strong macro conviction.
7. Limitations
- Breadth measures may mask sector‑specific divergences.
- Some markets have thin liquidity, which can distort crowding flags.
- Static regime thresholds may not reflect structural shifts in futures participation.
8. Practical Use Cases
- Detect broad speculative sentiment shifts for macro allocation.
- Combine with volatility or yield‑curve signals to confirm risk regime transitions.
- Use as an input to multi‑factor macro composites or tactical models.