Sector Flow & Momentum
Abstract
This signal aggregates 4-week speculative flows across futures markets into sector-level flow and momentum metrics. By combining sign and magnitude of flows with crowding information, it distinguishes between trend-confirming flows, contrarian flows, and the evolution of flow momentum over time. The output provides an economic-cycle proxy for sectors such as industrial metals, energy, and agriculture, highlighting phases of expansion, slowdown, and potential turning points.
1. Inputs
- df_cot_signals — unified enriched CoT dataset (market-level).
- Flow_4w — 4-week net speculative flow per market.
- crowded_long / crowded_short — crowding flags indicating extreme positioning.
- Commodity_Class, Classification, Sub-Classification, Commodity_SubClass — used for sector mapping where available.
- Market_and_Exchange_Names — fallback for heuristic sector classification.
2. Sector Classification
Markets are first mapped into macro sectors, preferring explicit metadata fields and falling back to name-based heuristics when necessary. The sectors are:
- Energy
- Metals (Precious & Base)
- Ags (Grains, Softs, Livestock)
- FX
- Rates
- Other (uncategorised or mixed)
This mirrors the sector logic used in other CoT sector signals, ensuring consistency across positioning and flow overlays.
3. Per-Market Flow-Based Flags
Two key tactical flags are defined at the market level based on how flows interact with crowding:
Trend-confirming flows reinforce existing crowded positions, while contrarian flows point to potential position unwinds or regime shifts.
4. Sector-Level Flow & Momentum
For each sector and report date, the model aggregates flow and positioning metrics:
- avg_flow_4w — average 4-week flow across markets in the sector.
- pos_flow_share — fraction of markets with positive flows.
- neg_flow_share — fraction of markets with negative flows.
- trend_confirm_share — share of markets with trend-confirming flows.
- contrarian_flow_share — share of markets with contrarian flows.
- crowded_long_share / crowded_short_share — positioning context.
Two additional derived metrics capture flow momentum and smoothing:
Together, these form a sector-level view of whether flows are broadly supportive, deteriorating, or reversing.
5. Implementation (Python)
df_flow = df_cot_signals.copy()
df_flow["Report_Date"] = pd.to_datetime(df_flow["Report_Date"], errors="coerce")
# Ensure core columns
if "Flow_4w" not in df_flow.columns:
raise KeyError("Expected column 'Flow_4w' not found in df_cot_signals.")
for flag in ["crowded_long", "crowded_short"]:
if flag not in df_flow.columns:
df_flow[flag] = False
# Numeric flow
df_flow["Flow_4w"] = pd.to_numeric(df_flow["Flow_4w"], errors="coerce")
# Sector classification
if "Sector" not in df_flow.columns:
df_flow["Sector"] = df_flow.apply(_classify_sector_for_flow, axis=1)
# Per-market flow flags
flow = df_flow["Flow_4w"]
df_flow["trend_confirm_flow"] = (
(df_flow["crowded_long"] & (flow > 0)) |
(df_flow["crowded_short"] & (flow < 0))
)
df_flow["contrarian_flow"] = (
(df_flow["crowded_long"] & (flow < 0)) |
(df_flow["crowded_short"] & (flow > 0))
)
# Sector-level aggregation
group_cols = ["Report_Date", "Sector"]
df_sector_flow = (
df_flow
.groupby(group_cols)
.agg(
n_markets=("Market_and_Exchange_Names", "nunique"),
avg_flow_4w=("Flow_4w", "mean"),
pos_flow_share=("Flow_4w", lambda x: (x > 0).mean()),
neg_flow_share=("Flow_4w", lambda x: (x < 0).mean()),
trend_confirm_share=("trend_confirm_flow", "mean"),
contrarian_flow_share=("contrarian_flow", "mean"),
crowded_long_share=("crowded_long", "mean"),
crowded_short_share=("crowded_short", "mean"),
)
.reset_index()
)
for col in [
"pos_flow_share",
"neg_flow_share",
"trend_confirm_share",
"contrarian_flow_share",
"crowded_long_share",
"crowded_short_share",
]:
df_sector_flow[col] = df_sector_flow[col].astype(float)
# Sort and momentum
df_sector_flow = df_sector_flow.sort_values(["Sector", "Report_Date"]).reset_index(drop=True)
df_sector_flow["avg_flow_4w_mom"] = (
df_sector_flow
.groupby("Sector")["avg_flow_4w"]
.diff()
)
df_sector_flow["avg_flow_4w_3w_ma"] = (
df_sector_flow
.groupby("Sector")["avg_flow_4w"]
.transform(lambda s: s.rolling(3, min_periods=1).mean())
)
6. Interpretation
- High pos_flow_share with high trend_confirm_share — flows are reinforcing existing sector trends (pro-cyclical regime).
- Rising contrarian_flow_share — flows increasingly oppose existing crowding, signalling risk of reversals or de-risking.
- Positive avg_flow_4w_mom — flow momentum is improving, often consistent with early-cycle or acceleration phases.
- Negative avg_flow_4w_mom — deteriorating flows that may precede slowdowns or topping patterns.
7. Limitations
- Flow measures depend on upstream construction of Flow_4w and may embed noise from roll activity or contract changes.
- Sector classification via heuristics can mislabel niche contracts.
- No explicit z-score normalisation is applied here; optional normalisation may be added for cross-signal comparability.
8. Practical Use Cases
- Track industrial Metals and Energy flow momentum as proxies for global manufacturing and transport demand.
- Combine with Sector Positioning and Hedger Pressure signals to distinguish between flow-driven and fundamentally-driven moves.
- Use contrarian_flow_share spikes as alerts for potential tactical turning points in crowded sectors.