02 — CARRY Strategy Family (Cross-Asset Carry)
Scope: Commodity roll/term-structure carry, rates (yield-curve) carry, FX (rate-differential) carry — designed for direct implementation in /Users/admin/qgtmai/trading (Python 3.12, Alpaca PAPER, ETF-only execution).
Status: Research + specification. No production code, no repo edits. This document is the build contract for an implementing agent.
Author stance: Institutional quant researcher. Every claim is either (a) cited to the literature, (b) grounded in an inspected source file in this repo, or (c) flagged as an explicit assumption / capability gap. Carry is the one factor where the honest answer is "the signal is clean, but the tradeable ETF expression ranges from excellent (rates) to marginal (FX)." That honesty is the whole point of this spec.
0. TL;DR and prioritized shortlist
Carry — the return you earn if prices don't move (Koijen, Moskowitz, Pedersen, Vrugt 2018, hereafter KMPV) — is one of the most robust, model-free return predictors known: a long-high/short-low carry trade earns an annualized Sharpe of ~0.8 per asset class, and a diversified cross-asset carry factor earns ~1.1–1.2 gross (KMPV 2018, JFE 127(2):197–225). The diversification across asset classes is the alpha; the single-sleeve Sharpes are good-not-great and all carry sleeves crash together in global recessions (KMPV's three worst drawdowns are 1972–73, 2008–09, and recession episodes).
The catch for this system: we trade ETFs on Alpaca, not futures or FX forwards. The cleanliness of the ETF expression varies enormously:
| Sleeve | Signal data wired? | Clean ETF expression? | First-cut verdict |
|---|---|---|---|
| Rates / curve carry | ~80% (need short + long curve points) | Yes — TLT/IEF/SHY/BIL are liquid, and a bond ETF literally earns yield+rolldown | Ship first. Highest capacity, lowest implementation risk. |
| Commodity term-structure / roll | Partial (only GC/SI futures curves wired) | Medium — true roll yield needs futures curves we must add; ETF-momentum proxy is weak | Ship second, with a small futures_chain.py extension (CL/NG/HG). Reuses existing gc_term_structure machinery. |
| FX / rate-differential carry | No (no foreign rates, no FX spot) | Weak — DBV (the FX-carry ETF) was liquidated ~2023; only thin CurrencyShares single-currency ETFs remain; no liquid NZD | Research / paper-trade only first. Flag as capability-limited; trade tiny if at all. |
Recommended first-implementation order:
rates_curve_carry— bond carry (yield + roll-down) across SHY/IEF/TLT, time-series + cross-sectional. Cleanest, biggest, do it first.commodity_curve_carry_xs— cross-sectional commodity carry across the complex (the canonical KMPV/Gorton–Rouwenhorst commodity factor), expressed in USO/UNG/CPER/GLD/SLV/DBA. Requires the futures-curve data extension (below).commodity_curve_carry_ts— per-commodity time-series roll-yield timing (generalizesgc_term_structureto energy + base metals). Same data dependency; ship alongside #2.fx_carry_g10— FX carry via CurrencyShares ETFs + UUP. Paper-first; smallest sizing; capability gap flagged.
Plus a shared carry_crash_guard overlay (vol/funding/regime-conditioned de-grossing + carry×trend blending) applied to all four, because every carry sleeve is structurally short-volatility and negatively skewed.
Already-built strategies that belong to this family and should be folded in, not duplicated: gc_term_structure, si_term_structure, cross_carry, backwardation_stress, and the COT-driven hedging_pressure overlay.
1. What carry is, and why it persists
1.1 Unifying definition (KMPV)
For any asset, decompose expected return into carry (mechanical, observable today, assuming prices/curves stay constant) and expected price appreciation (requires a forecast):
KMPV's empirical result: carry alone is a strong positive predictor of returns in equities, bonds, currencies, commodities, Treasuries, credit, and options — in both the cross-section and the time series — and is not subsumed by value, momentum, or time-series momentum. The carry factor "rejects a generalized UIP / expectations hypothesis in favor of time-varying risk premia."
Per-asset-class carry definitions used in this spec:
- Commodity: \(\text{Carry} = \dfrac{S - F}{F}\) annualized = the roll yield. Positive when the curve is backwardated (front > deferred), negative in contango. (Gorton & Rouwenhorst 2006; Erb & Harvey 2006; Szymanowska et al. 2014.)
- Bond: \(\text{Carry} \approx (y_{\text{long}} - r_{\text{financing}}) + \text{roll-down}\), i.e. yield spread over funding plus the capital gain from a bond "rolling down" a normally upward-sloping curve. KMPV show bond carry ≈ closely related to the slope of the yield curve (their slope-carry signal is 0.91-correlated with the level-carry signal). (LSEG/FTSE "The Carry Concept"; Vontobel "roll-down"; KMPV §slope-carry.)
- FX: \(\text{Carry} = i_{\text{foreign}} - i_{\text{domestic}}\) = the interest-rate differential = forward discount. (Lustig–Roussanov–Verdelhan 2011; Menkhoff–Sarno–Schmeling–Schrimpf 2012.)
1.2 Why it persists (the risk-premium / limits-to-arbitrage story)
Carry is not an arbitrage; it is compensation for bearing a specific risk that shows up exactly when investors least want it:
- Crash risk / negative skew (FX, the canonical case). Brunnermeier, Nagel & Pedersen (2008) — "currencies go up by the stairs and down by the elevator." High-rate currencies have negative conditional skewness; carry crashes when risk appetite and funding liquidity dry up and speculators unwind crowded positions simultaneously. Funding-liquidity measures predict the crashes.
- Recession / business-cycle risk (all sleeves). KMPV: although carry sleeves have low unconditional cross-asset correlation, "there are times when carry strategies across all asset classes do poorly, and these episodes coincide with global recessions." The diversification is fair-weather.
- Convenience yield / storage economics (commodities). Backwardation signals physical tightness (Kaldor 1939; Working 1949; Fama–French 1987); producers pay to hedge (hedging-pressure hypothesis: Bessembinder 1992; De Roon, Nijman, Veld 2000) → a roll-yield risk premium accrues to the long.
- Term premium (bonds). Investors demand compensation for duration/inflation risk; the curve is upward-sloping "on average," so roll-down + term spread is harvestable (GPFG 2018; KMPV).
Because the payoff is "small gains most of the time, rare large losses," arbitrage is limited — carry is a short-volatility, picking-up-pennies premium, and that is precisely why it does not get competed away.
1.3 Evidence table (gross, from the literature)
| Factor | Source | Reported gross Sharpe | Key caveat |
|---|---|---|---|
| Carry, avg per asset class | KMPV 2018 | ~0.8 | Single-sleeve; crash risk |
| Global diversified carry factor (GCF) | KMPV 2018 | ~1.1–1.2 | Diversification is the alpha; fails in recessions |
| Carry timing (TS), per class / global | KMPV 2018 | ~0.6 / ~0.9 | Long-biased version |
| "carry1-12" (12-mo avg signal) global | KMPV 2018 | ~1.1 | Cuts turnover ~50% for ~same Sharpe |
| Commodity term-structure / roll factor | Gorton–Rouwenhorst 2006; Erb–Harvey 2006 | ~0.5–0.8 | Erb–Harvey: "income return" can be negative for ~10yr (energy contango post-2008) |
| FX G10 carry | Lustig et al. 2011; Menkhoff et al. 2012 | ~0.6–0.9 pre-GFC | Post-GFC ≈ 0–0.3, brutal 2008/2020 crashes; negative skew |
| Bond carry (level + slope) | KMPV; LSEG; GPFG 2018 | ~0.5–0.8 | Mostly the yield-spread component; long-bond cross-country carry ≈ 0 (Lustig et al. 2019) |
Honest translation to our ETF-net world: subtract ETF expense ratios (0.15%–1.06% here), roll/tracking drag (already embedded if we backtest the ETF price series — see §8), turnover costs, and post-2008 FX-carry decay. Realistic net sleeve Sharpes: rates ~0.3–0.5, commodity ~0.2–0.4, FX ~ −0.1 to +0.2. The combined, crash-guarded, diversified sleeve targets net ~0.4–0.7 — if rates and commodity carry carry the load and FX is kept tiny.
2. System integration map (how a carry strategy plugs in)
Grounded in inspected files. An implementing agent should match these patterns exactly.
2.1 Strategy interface (qgtm_strategies/base.py)
- Subclass
Strategy; implementgenerate_signals(self, features: pl.DataFrame, universe: list[str], timestamp: datetime) -> list[Signal]. - Declare class attributes:
strategy_id,description,asset_classes,rebalance_frequency("daily"|"weekly"|"monthly"),regime_tags(list ofRegime),factor_exposures(FactorExposure(...)— acarryfield already exists, value in ~[0,1]), andcapacity(CapacityModel(max_capacity_usd=..., half_life_days=...)). - Helpers provided by the base class:
self.macro_series(features, col)→ dedupes a macro column bytimestampand returns a clean ascendingnp.ndarray(long-format features repeat macro values per symbol; always use this for macro/curve columns).self._empty("snake_case_reason")→ records a diagnosable empty reason (surfaced in daemonschedule_diagnostics); use instead of barereturn [].self.validate_signals(signals)→ final NaN/bounds guard; always return through it.self.should_trade(current_regime)→ honored by the daemon forregime_tagsgating.Signal(qgtm_core/types.py):weight ∈ [-1, 1],confidence ∈ [0, 1],side ∈ {Side.LONG, Side.SHORT}, plusrationale,timestamp,metadatadict. The risk engine vol-targets and caps downstream (RiskLimits:vol_target=0.12, half-Kelly,max_single_name_pct,per_symbol_caps,cvar_limit_95), so strategy weights are relative intents — but carry strategies must still pre-scale by inverse vol and self-cap in crisis (§4).
2.2 Data that is actually wired into features at runtime
From qgtm_live/daemon.py (_fetch_fred_history v3 + _enrich_features_external, join-asof backward by date → PIT-safe):
FRED macro history (~900 calendar days, ≥252 trading days for z-scores):
real_10y (DFII10), breakeven_10y (T10YIE), dxy (DTWEXBGS), vix (VIXCLS), fed_funds (FEDFUNDS), walcl (WALCL), dgs2 (DGS2), dgs10 (DGS10), t10y2y (T10Y2Y), gvz (GVZCLS), vxslv (VXSLVCLS). (Aliases also added: dfii10, t10yie, dtwexbgs, etc.)
Futures term structure (qgtm_data/futures_chain.py, real Yahoo COMEX .CMX closes, ~900d, gold has a synthetic cost-of-carry fallback): gc_front, gc_back, gc_dte, si_front, si_back. Only gold and silver.
Per-symbol price features (qgtm_features/store.py): ret_{1,5,10,21,63,126,252}d, vol_{21,63,126}d (annualized realized), tsmom_signal_*, vol_adj_mom_*, zscore_*, skew_21d/63d, GARCH vol helper, and a weak ETF carry proxy (carry_proxy_21d/63d, backwardation_indicator, roll_yield_est) derived from short-minus-long ETF momentum — not a true roll yield (see §3.2).
COT / CFTC (qgtm_data/cot_reports.py, cftc.py): real Commitments-of-Traders + apply_cot_symbol_aliases projecting commercial_net/open_interest per commodity (consumed by hedging_pressure). Plus a price/volume COT proxy in the feature store.
2.3 Tradeable universe (qgtm_core/universe.py)
Entirely commodity ETFs + miners: energy (USO, UCO, SCO, BNO, UNG, BOIL, KOLD, UGA), precious (GLD, IAU, SGOL, SLV, SIVR, PPLT, PALL), base/industrial (CPER, COPX, JJC, JJN, REMX, LIT, SLX), ags (DBA, CORN, WEAT, SOYB, CANE, JO, NIB, COW, BAL), broad baskets (DBC, GSG, PDBC, COMT, FTGC, BCI, CMDY), miners (GDX, GDXJ, SIL, SILJ, URA, URNM).
qgtm_core/pm_universe.py additionally lists signal-only "cross-asset inputs": UUP, TIP, IEF, IBIT, CPER, USO, VIXY, UVXY, SPY (not in the default tradeable commodity universe).
There are NO rates ETFs (TLT/SHY/BIL/SHV) and NO FX ETFs (FXE/FXY/FXB/FXA/FXC/FXF) tradeable today. IEF, TIP, UUP exist only as PM signal inputs. This is the single biggest dependency for the carry family (see §3).
2.4 Validation harness (qgtm_backtest/validation.py)
run_full_validation(returns, strategy_id, regime_labels=..., n_strategies_tested=..., ...) returns a ValidationReport aggregating: CPCV + PBO (pass pbo<0.5), Deflated Sharpe (pass dsr>0.95, penalized by num_trials), Harvey–Liu–Zhu multiple-testing threshold, walk-forward anchored + rolling (default train=504, test=63, embargo=5), regime-stratified Sharpe (pass if positive in ≥2 regimes), Monte-Carlo block bootstrap (pass if Sharpe CI-lower > 0), and Almgren–Chriss capacity. This is exactly the OOS gate the carry sleeve must clear (§8).
3. Capability gaps & required data wiring (read this before building)
The mandate said to be explicit about where the ETF expression is weak or where futures would be needed. Here is the full dependency list, ordered by effort. Each is a prerequisite for the sleeve it unlocks.
| # | Gap | Unlocks | Effort | Concrete change |
|---|---|---|---|---|
| G1 | Rates ETFs not tradeable | rates_curve_carry |
Trivial | Add SHY, IEF, TLT (+ BIL/SHV, optional IEI,TLH) to qgtm_core/universe.py (new AssetClass, e.g. RATES) and to the daemon's tradeable set. |
| G2 | Short/long curve points missing | accurate bond carry + roll-down | Trivial | Add DGS3MO, DGS1, DGS5, DGS7, DGS20, DGS30 to _fetch_fred_history series_map (+ fred.py FRED_SERIES). Currently only DGS2/DGS10/T10Y2Y. |
| G3 | MOVE not in daemon history |
rates carry-crash gating | Trivial | MOVE is defined in fred.py but not loaded by _fetch_fred_history v3. Add it. (Also: TEDRATE is discontinued since 2022 → replace funding-stress proxy with SOFR−IORB or a CP-bill spread.) |
| G4 | Commodity futures curves only for GC/SI | true commodity roll yield (energy, base metals) | Small | Extend qgtm_data/futures_chain.py (same Yahoo pattern) to CL (CL*.NYM), NG (NG*.NYM), HG (HG*.CMX) front/back; emit cl_front/back, ng_front/back, hg_front/back + *_dte. This is the cleanest path and mirrors existing code. |
| G5 | Roll-method ETF siblings missing | clean single-name roll harvest / curve proxy without futures | Trivial | Add USL, DBO, OILK (crude), and use existing DBC/PDBC/COMT/CMDY (optimum/dynamic roll) vs GSG (naive front-month) as an aggregate backwardation proxy. |
| G6 | FX ETFs not tradeable | fx_carry_g10 |
Small | Add FXE, FXY, FXB, FXA, FXC, FXF and UUP/UDN. Honesty: DBV (the purpose-built G10 carry ETF) was liquidated ~2023; FXA/FXC/FXF are thin; no liquid NZD ETF (NZD is a classic high-carry leg → permanent gap). FOXY (Simplify, 2025, active) and CEW (WisdomTree EM) exist but are new/small. |
| G7 | Foreign rates + FX spot not wired | FX carry signal | Medium | Add foreign short rates (ECB/€STR, BOJ, BOE, RBA, BOC, SNB — FRED OECD IR3TIB01* 3-mo rates or policy series) and spot (DEXUSEU, DEXJPUS, DEXUSUK, DEXUSAL, DEXCAUS, DEXSZUS). Without this, FX carry has no clean signal — or exploit that CurrencyShares ETFs already accrue the foreign deposit rate (the carry is partly embedded in the ETF total return). |
Design rule: every strategy below degrades gracefully via self._empty("reason") when its required columns/instruments are absent, so it can ship before its data dependency lands (it simply stays silent) — matching how gc_term_structure already handles missing gc_front/gc_back.
3.1 Why ETFs can express carry (the good news)
- Bond ETFs are near-perfect carry vehicles. Holding IEF/TLT literally earns the coupon yield and the roll-down as the index rebalances; SHY/BIL are the funding leg. The curve points needed to measure the carry are FRED-wired. This is why rates is #1.
- CurrencyShares grantor trusts (FXE, FXY, FXB, FXA, FXC, FXF) hold actual currency at JPMorgan and accrue the foreign deposit rate, distributed monthly (e.g., FXE ~0.8% trailing yield, 0.40% ER). So holding the high-rate-currency ETF and shorting the low-rate-currency ETF mechanically captures the rate differential — a genuine (if thin, ordinary-income-taxed) FX-carry expression.
- Commodity roll yield is observable from ETF pairs. USO (front-month) vs USL (12-month ladder) vs DBO (optimum-yield) diverge exactly by the realized roll cost: USO is down ~90% since 2006 while USL is down ~31% — the spread is the contango drag. So even without futures data,
USO − DBO(orGSG − PDBCat the index level) is a usable curve-slope proxy.
3.2 Why the current ETF carry proxy is insufficient (the bad news)
FeatureStore.compute_carry_features proxies carry as short-horizon ETF return minus long-horizon ETF return (ret_5d − ret_63d, etc.). This is momentum in disguise, not roll yield — it cannot distinguish "price rose" from "curve is backwardated." Do not use it as the carry signal for commodities; it is acceptable only as a fallback feature when no futures curve exists, and it should be labeled as such. The real signal is the front/back basis (G4).
4. Shared design components (used by all sleeves)
4.1 Signal → carry score → weight pipeline
- Carry estimate \(C_{i,t}\): per asset, per the asset-class formula (§5). Annualize consistently (×252/Δdays for futures basis; per-annum yields for bonds/FX).
- Standardize. Cross-sectional: demeaned rank or z-score across the basket at time \(t\). Time-series: rolling z-score \(z_{i,t} = (C_{i,t}-\bar C_{i})/\sigma_{C_i}\) over a 252-day window (reuse the existing
_rolling_zscorepattern). - carry1-12 smoothing (turnover control). Use the trailing 12-month average of the carry signal, not the spot value: KMPV show this cuts turnover ~50% for ~the same Sharpe. Implement as a 252-day mean of \(C_{i,t}\) before standardizing.
- Inverse-vol scaling (risk parity within sleeve). \(w_{i,t} \propto \dfrac{\text{score}_{i,t}}{\sigma_{i,t}}\), using
vol_63d(already a feature) or the GARCH helper. Then normalize so the sleeve targets a conservative ~6–8% ex-ante vol (below the book's 12%vol_target, because carry is negatively skewed — see §4.3). - Cap & emit. Clip per-name to
max_weight; emitSignalthroughvalidate_signals. Put the raw carry, z-score, and curve state inmetadatafor attribution.
4.2 Cross-sectional vs time-series construction
- Cross-sectional (XS): dollar-neutral long top-quantile / short bottom-quantile of the carry ranking. Strips out the asset-class beta; isolates the carry premium (KMPV's main spec). Best for the commodity complex and rates segments.
- Time-series (TS): long if carry > 0 (or > its history), short/flat if carry < 0. Long-biased; captures the term/risk premium directly. Lower Sharpe but simpler, and the natural form for a single curve (e.g. one commodity's term structure, à la
gc_term_structure). - Run both where the universe supports it; they are ~complementary (KMPV).
4.3 carry_crash_guard — mandatory shared overlay
Because every carry sleeve is implicitly short-vol and negatively skewed (Brunnermeier–Nagel–Pedersen 2008; KMPV recession clustering), apply a single, shared de-grossing overlay to all carry weights before they leave the strategy. Implement as a mixin/util apply_carry_crash_guard(weights, features, timestamp) -> weights:
- \(g^{\text{vol}}_t\) (volatility brake): multiplicatively shrink as
vix(andMOVEfor rates) rise above rolling norms or spike fast. E.g. \(g^{\text{vol}} = \text{clip}(1 - \max(0, z_{\text{VIX},t})/k,\ g_{\min}, 1)\). Carry crashes are vol spikes. - \(g^{\text{funding}}_t\) (funding-liquidity brake): shrink when funding stress rises (SOFR−IORB / CP−bill proxy;
ted_spreadis discontinued — flag G3). BNP "carry unwinds when funding dries up." - \(g^{\text{trend}}_{i,t}\) (carry×trend confirmation): reduce (or veto) carry positions that the trend/momentum sleeve strongly contradicts (use existing
tsmom/xsmomsignals). KMPV & AQR: carry and momentum are complementary; the blend materially cuts the left tail. - \(g^{\text{regime}}_t\) (regime gate): via
regime_tags/should_trade— halve gross inRegime.RISK_OFF, cut hard inRegime.CRISIS.
This overlay is the difference between "harvest pennies" and "harvest pennies in front of a steamroller."
4.4 No-look-ahead rules (carry-specific)
- FRED publication lag: DGS/real-yield series post next business day; use the T-1 value to decide the T trade. The daemon's join-asof-backward already enforces as-of semantics; do not read same-day un-asof'd values.
- Futures curve: daily settle; decide on T close, execute T+1 open.
- Backtest the ETF price series itself (not the underlying spot/index): this is essential so the contango drag / roll cost / expense ratio are honestly embedded in returns (USO's −90% is the cautionary tale).
- Overlapping carry signals (esp. carry1-12) create autocorrelated labels → use embargo ≥ holding period (≥21 trading days) in CPCV/walk-forward to prevent leakage (§8).
5. Strategy specifications
Each spec follows the mandated template: rationale + persistence; exact formulas; data + wired?; entry/exit/sizing; expected gross/net Sharpe, turnover, capacity; correlations; failure modes + decay; OOS plan.
S1 — rates_curve_carry (rates / yield-curve carry) ⭐ PRIORITY 1
Asset classes: rates (Treasuries via ETFs). Rebalance: monthly (carry1-12). Factor exposures: carry≈0.8, real_rates≈0.5, dollar≈0.
Economic rationale + persistence
A bond's carry = yield over funding + roll-down the (normally upward-sloping) curve (KMPV; LSEG "Carry Concept"; Vontobel). It is compensation for the term premium — duration/inflation risk that investors demand to be paid for (GPFG 2018). It persists because the curve is upward-sloping "on average" and the expectations hypothesis is empirically violated (KMPV: predictive coefficient > 1 for global bond level & slope). The "sweet spot" for roll-down is typically the 3–7y belly (Vontobel), which biases the cross-sectional version toward IEF over TLT per unit of risk.
Exact formulas
Per ETF segment \(k\) with approximate duration \(D_k\) and representative maturity \(m_k\):
with \(r_{\text{fund}} = y(\text{3M})\) (BIL/SHV proxy), \(h\) = 1y, and the curve \(y(\cdot)\) interpolated from FRED points. Mapping (using _ADV/duration conventions):
- SHY (1–3y, \(D\approx1.9\), \(m\approx2\)): \(\text{Carry}_{\text{SHY}} = (y_{2} - y_{3m}) + 1.9\,(y_{2}-y_{1})\)
- IEF (7–10y, \(D\approx7.5\), \(m\approx8.5\)): \(\text{Carry}_{\text{IEF}} = (y_{10} - y_{3m}) + 7.5\,(y_{10}-y_{7})/\,\text{(per yr)}\)
- TLT (20–30y, \(D\approx17\), \(m\approx25\)): \(\text{Carry}_{\text{TLT}} = (y_{30} - y_{3m}) + 17\,(y_{30}-y_{20})\)
Slope-carry (KMPV eq. 13) as a robust alternative signal: \(C^{\text{slope}} = C_{10Y} - C_{2Y}\) — long duration when the curve is steep, flat/short when flat/inverted. This needs only currently-wired dgs2, dgs10, t10y2y (ships before G2!).
TS rule: long IEF/TLT when \(C^{\text{slope}}>0\) and rising; flat/short (or rotate to SHY/BIL) when the curve is inverted (\(t10y2y<0\)) — inversion = negative carry + historically a duration-unfriendly, rate-shock-prone regime. XS rule: rank {SHY, IEF, TLT} by carry per unit duration \(C_k/D_k\); long the best, short the worst, duration-balanced (so it's a curve-shape bet, not a directional rate bet).
Data needed + wired?
- Curve:
dgs2,dgs10,t10y2y✅ wired → slope-carry ships now. Full level+rolldown needsdgs3mo, dgs1, dgs5, dgs7, dgs20, dgs30→ G2 (trivial). - Instruments:
SHY, IEF, TLT(+BIL/SHV) → G1 (trivial). - Crash gate:
vix✅,MOVE→ G3 (trivial).
Entry / exit / sizing
- Entry: monthly rebalance on carry1-12 of \(C^{\text{slope}}\) (TS) and \(C_k/D_k\) ranking (XS). Inverse-vol scale by
vol_63d; sleeve vol target ~7%. - Exit/flip: when slope-carry crosses zero (TS) or ranking inverts (XS); hard de-risk if
t10y2yinverts orMOVEz-spikes (rate-shock guard). - Sizing:
max_weightper name ~0.4;carry_crash_guardapplied; respectper_symbol_caps.
Expected gross/net Sharpe, turnover, capacity
- Gross Sharpe 0.5–0.8 (KMPV bond carry; GPFG "carry gives the highest FI-factor Sharpe"). Net ~0.3–0.5 (TLT 0.15% / IEF 0.15% / SHY 0.15% ER; low turnover).
- Turnover: low, ~1–3×/yr (monthly carry1-12).
- Capacity: high, $100M+ (TLT/IEF/SHY ADV in the millions of shares; Almgren–Chriss
capacity_calibrationwill clear easily).
Correlations to other sleeves
- Trend/momentum: low; bond-TSMOM overlaps with TS slope-carry in trending-rate regimes → keep them de-correlated via the carry×trend blend.
- Vol sleeve: low-to-moderate (rates carry is short rate vol, not equity vol).
- Macro sleeve: overlaps with
real_rate_gold,gold_yield_curve_steepener(duration/real-rate exposure) → net duration must be managed at the portfolio level (the declaredcarry/real_ratesfactor loadings letfactor_model.pynet it out).
Failure modes + decay
- Bear-flattener / rate shock (2013 taper tantrum, 2022): long duration when carry is positive gets hit hard if the curve sells off; the
MOVE/inversion guard and carry×trend blend are the mitigants. 2022 is the key stress (positive slope-carry early, then violent repricing). - Inverted-curve regimes: slope-carry goes negative/zero → strategy correctly de-risks; verify it doesn't whipsaw around zero (hysteresis band).
- Decay: structurally durable (term premium is fundamental), but QE/QT regimes compress/expand it; monitor
decay.is_decayed(6m/12m Sharpe vs IS).
OOS validation plan
run_full_validation with stress windows: 2013 taper tantrum, 2015 bund tantrum, 2020 COVID dash-for-cash, 2022 hiking cycle. Require PBO<0.5, DSR>0.95 (n_strategies_tested set to the real count of variants tried, ~10–20), regime-positive in ≥2 regimes, bootstrap CI-lower>0. Embargo ≥21d. ETF total-return series (dividends reinvested) so coupon carry is captured.
S2 — commodity_curve_carry_xs (cross-sectional commodity carry) ⭐ PRIORITY 2
Asset classes: commodities (ETFs). Rebalance: monthly. Factor exposures: carry≈0.9, energy/metals/agriculture per leg, momentum≈0.1.
Economic rationale + persistence
The canonical commodity carry / term-structure factor: long backwardated commodities, short contangoed ones (Gorton–Rouwenhorst 2006; Erb–Harvey 2006; Szymanowska et al. 2014; KMPV). Backwardation reflects physical tightness / convenience yield / hedging pressure (Kaldor 1939; Working 1949; Bessembinder 1992; De Roon et al. 2000); the long earns the roll yield as a risk premium. Persistence caveat (Erb–Harvey, honest): the aggregate "income return" can be negative for ~decade-long stretches (energy moved to structural contango post-2008), so the cross-sectional (relative) version is far more reliable than long-only, because it does not depend on the average commodity being backwardated.
Exact formulas
Per commodity \(i\) with front/back futures \(F^{\text{front}}_i, F^{\text{back}}_i\), days-apart \(\Delta_i\):
(Identical to the formula already in cross_carry.py / si_term_structure.py.) Then:
Long top-2 / short bottom-2 (or top/bottom tercile) of the complex, dollar-neutral, inverse-vol weighted. Express via the most liquid, lowest-fee ETF per commodity: WTI→USO (or DBO for less roll drag), NatGas→UNG, Copper→CPER, Gold→GLD, Silver→SLV, Ags→DBA/CORN/WEAT/SOYB. For shorts where borrow is hard, use inverse ETFs (SCO, KOLD) or simply express the basket short via the broad DBC/GSG leg.
Data needed + wired?
- Per-commodity curves: GC/SI ✅ wired; CL, NG, HG → G4 (small
futures_chain.pyextension). Ags curves are not on Yahoo cleanly → use the roll-ETF-pair proxy (GSGnaive vsPDBCoptimum-yield) for an aggregate ag/curve slope, or omit ags v1. - Instruments: USO/UNG/CPER/GLD/SLV/DBA ✅ in universe; add
DBO/USL(G5) for cleaner energy roll.
Entry / exit / sizing
- Monthly rebalance on carry1-12 ranking; inverse-vol; sleeve vol ~7–8%; per-name
max_weight~0.3. - Exit/flip on ranking changes;
carry_crash_guardapplied (commodity carry also crashes in risk-off, though less violently than FX). - Minimum 3 commodities with valid curves required, else
self._empty("insufficient_commodity_curves").
Expected gross/net Sharpe, turnover, capacity
- Gross Sharpe 0.5–0.8 (Gorton–Rouwenhorst / KMPV commodity carry). Net ~0.2–0.4 — ETF fees are heavy (UNG 1.06%, DBC 0.85%, USO 0.60%) and roll drag is real; XS construction partly self-hedges fees.
- Turnover: moderate, ~3–6×/yr.
- Capacity: medium, $20–60M — constrained by thinner legs (CPER ~200k ADV, ags).
gc_term_structureis already calibrated at $35M; treat that as a per-leg anchor.
Correlations
- Trend/momentum: low-to-moderate; commodity carry and commodity TSMOM are complementary (combine).
- Vol sleeve: positive (short-vol nature) — do not stack with short-vol VRP at full size.
- Existing PM book: shares GLD/SLV legs with
gc_term_structure/cross_carry/backwardation_stress→ net at the portfolio level to avoid double-counting the gold/silver curve bet (these existing strategies should be subsumed as the precious-metals legs of this XS factor).
Failure modes + decay
- Structural contango (energy, 2009–2020): long-only roll bleeds; XS construction is the mitigant, but a whole-complex contango still hurts the long legs.
- Curve regime flips (backwardation↔contango) cause whipsaw → carry1-12 smoothing + hysteresis.
- 2014–16 oil crash / 2020 COVID negative WTI: extreme curve dislocations; cap energy leg weight and use DBO/USL to avoid front-month blowups (USO's 2020 roll trauma).
- Decay: financialization post-2004 compressed the premium (Erb–Harvey); monitor.
OOS validation plan
run_full_validation + stress: 2008, 2011, 2014–16 oil, Aug-2015 CNY, 2020 (incl. negative WTI), 2022 energy backwardation. Regime-stratified must be positive in ≥2 regimes. Backtest on ETF series so roll drag is embedded. Embargo ≥21d. Set n_strategies_tested honestly (curve-slope vs basis vs roll variants × lookbacks → ~30–50) to keep DSR honest.
S3 — commodity_curve_carry_ts (per-commodity time-series roll timing) — PRIORITY 2b
Asset classes: commodities. Rebalance: weekly/daily. Factor exposures: carry≈0.8, momentum≈0.2.
Generalizes the existing gc_term_structure (gold) to the whole complex: for each commodity ETF, go long when its own curve is backwardated (C_i>0, z-score > entry band), flat/short when steeply contangoed. Same \(C_i\) formula as S2; same z-score/entry-band machinery as gc_term_structure.py (entry_z≈1.5, max_weight≈0.5).
- Data: identical to S2 (G4). Ships incrementally: gold/silver legs work today (curves wired); energy/base metals activate when G4 lands.
- Gross Sharpe 0.4–0.7; net ~0.2–0.35; turnover higher (weekly); capacity medium.
- Why both S2 and S3: XS isolates the premium (better Sharpe, dollar-neutral); TS is long-biased and directional (captures the term premium + acts as an inflation hedge). Low mutual correlation; KMPV run both.
- Failure modes / OOS: same family as S2. Note S3 is more exposed to whole-complex contango than S2 (no short hedge), so the
carry_crash_guard+ regime gate matter more.
S4 — fx_carry_g10 (FX rate-differential carry) — PRIORITY 4 (paper-first, capability-limited)
Asset classes: FX (via ETFs). Rebalance: monthly. Factor exposures: carry≈0.9, dollar≈0.5.
Economic rationale + persistence
The original carry trade: long high-rate currencies, short low-rate currencies (Lustig–Roussanov–Verdelhan 2011; Menkhoff et al. 2012). UIP is violated — high-rate currencies do not depreciate enough to offset the rate differential, so the differential is earned on average (KMPV: FX predictive coefficient ≈ 1). It persists as compensation for crash risk: high-rate currencies have negative skew and crash in funding-liquidity / risk-off episodes (Brunnermeier–Nagel–Pedersen 2008; Lustig–Verdelhan consumption-risk loading).
Exact formulas
[ C_j = i_j - i_{\text{USD}} \quad (\text{short-rate differential, } j \in \text{G10}) ] Rank G10 by carry1-12 of \(C_j\); long top, short bottom, vol-scaled, heavily crash-guarded (FX carry is the most negatively skewed sleeve).
ETF expression (be explicit about its weakness): the CurrencyShares trusts (FXE/EUR, FXY/JPY, FXB/GBP, FXA/AUD, FXC/CAD, FXF/CHF) hold the actual currency and accrue its deposit rate — so the carry is partly embedded in the ETF total return. Practical trade: long the high-rate-currency ETF(s), short the low-rate-currency ETF(s); or express the USD leg via short UUP / long UDN. If foreign rates aren't wired (G7), a carry proxy is the ETF's own trailing distribution yield (each trust distributes its accrued foreign interest monthly).
Data needed + wired?
- Signal: foreign short rates + FX spot → NOT wired (G7, medium effort). Interim proxy: ETF trailing distribution yield (observable from price/dividend data).
- Instruments: FXE/FXY/FXB/FXA/FXC/FXF, UUP/UDN → NOT in universe (G6).
- Permanent gaps: DBV (purpose-built G10 carry ETF) liquidated ~2023; no liquid NZD ETF (a top carry leg); FXA/FXC/FXF are thin; CurrencyShares distributions taxed as ordinary income (irrelevant for PAPER but relevant for any live capacity claim).
Entry / exit / sizing
- Monthly; tiny gross (sleeve vol target ≤3%, per-name
max_weight≤0.15);carry_crash_guardwith an aggressive vol/funding brake and skew cap; veto longs that trend strongly contradicts. - Liquidity gate: skip any ETF whose ADV is below a floor (FXA/FXC often fail) →
self._empty("fx_etf_too_thin").
Expected gross/net Sharpe, turnover, capacity
- Gross Sharpe 0.5–0.9 pre-GFC, but ~0–0.3 post-2008 (the factor has decayed badly and crashed in 2008/2020). Net via thin ETFs: −0.1 to +0.2 — realistically marginal.
- Turnover: low–moderate.
- Capacity: LOW, ~$2–10M (thin ETFs) —
capacity_calibrationwill likely fail the defaultmin_capacity_usd=$1Monly for the thinnest legs, but the sleeve as a whole is small-capacity. This is why it is paper-first and last in priority.
Correlations, failure modes, decay
- Correlation to other carry sleeves spikes to +1 in crises (KMPV) — FX carry crashing is often the leading indicator of a cross-asset carry drawdown.
- Failure modes: 2008 GFC, Aug-2015 CNY devaluation, Jan-2015 CHF de-peg (a CurrencyShares FXF single-day ~15%+ gap — a brutal short-funding-currency tail), Mar-2020. The CHF de-peg is the textbook reason to cap funding-currency shorts and respect skew.
- Decay: pronounced post-GFC (rate differentials compressed to ~zero across G10 for years). Treat as a research sleeve unless EM access (e.g., CEW) is added later.
OOS validation plan
run_full_validation + stress: 2008, Jan-2015 CHF, Aug-2015 CNY, 2018, Mar-2020. Expect (and require the report to show) large negative skew and a deep 2008/2020 drawdown — if the crash-guard can't keep bootstrap CI-lower ≥ ~0 and PBO<0.5, do not promote to live.
Reuse / fold-in (already implemented — do not duplicate)
| Existing strategy | Role in the carry family | Action |
|---|---|---|
gc_term_structure |
Gold leg of S3 (TS commodity carry) | Generalize into S3; keep as the gold specialization |
si_term_structure |
Silver leg of S3 | Same |
cross_carry (GLD vs SLV carry diff) |
A 2-asset XS commodity carry | Subsume as the precious-metals pair within S2 |
backwardation_stress |
Persistent-backwardation event overlay | Keep as a high-conviction TS overlay feeding S3 |
hedging_pressure (COT) |
Hedging-pressure risk-premium signal (Bessembinder; De Roon) | Use as an orthogonal confirm for S2/S3 carry legs |
gold_yield_curve_steepener |
Rates-regime → gold (not bond carry) | Distinct; net duration vs S1 at portfolio level |
6. Portfolio construction & correlation management
- Equal-risk across sleeves, scaled to 10% then averaged (KMPV's GCF method): build each sleeve to its own ~7–8% vol, then combine with equal risk weights — this is what produces the diversification uplift (single-sleeve ~0.8 → diversified ~1.1–1.2 gross). Down-weight FX to a fractional risk slot given its decay/thinness.
- Crowding / short-vol concentration: carry sleeves + the existing
vol_risk_premium_pm(short-vol) all sell volatility → cap the aggregate short-vol risk budget; pair withtail_hedge_pm(long-vol) as the structural hedge. - Factor netting: declare
FactorExposure(carry=...)honestly soqgtm_risk/factor_model.pycan net carry exposure and prevent the book from becoming "secretly all carry." Net duration (S1 vsreal_rate_gold/gold_yield_curve_steepener) and net commodity beta (S2/S3 vs directional commodity strategies). - Crash co-movement is the master risk: size the joint carry drawdown (all sleeves → correlation ~1 in recessions), not the diversified fair-weather vol. The
carry_crash_guardis portfolio-level insurance, not per-strategy decoration.
7. Carry-crash tail risk & decay (consolidated)
- Negative skew is structural, not a bug (BNP 2008). Manage it; don't expect to remove it. Vol-target below the book default, cap leverage on the most-skewed legs (FX funding shorts), and keep dry powder for the post-crash mean-reversion (carry's best returns often follow its worst drawdowns — KMPV).
- Decay watch: FX carry has decayed most (post-GFC rate compression); commodity carry decayed with financialization (Erb–Harvey); rates carry is most durable (fundamental term premium). Wire each into the existing
DecayMetrics(sharpe_6m/12m vs in_sample,is_decayedat <0.5 ratio) and the daemon's kill-list workflow. - Regime dependence: all sleeves want
regime_tagsthat down-weightRISK_OFF/CRISIS. The repo already has aregime_detector/Regimeenum — use it.
8. OOS validation & stress plan (shared)
Use qgtm_backtest/validation.py::run_full_validation per sleeve and for the combined carry book. Pass bar:
| Check | Gate | Carry-specific note |
|---|---|---|
| CPCV + PBO | pbo < 0.5 |
Use embargo_pct sized to ≥1 month (carry1-12 labels overlap). |
| Deflated Sharpe | dsr > 0.95 |
Set num_trials to the honest count of variants tried (lookbacks × XS/TS × signal forms → 30–50); don't undercount or DSR lies. |
| Harvey–Liu–Zhu | observed ≥ threshold |
Carry is one factor family among many already tested in this repo — budget the family-wise error. |
| Walk-forward (anchored+rolling) | agg_sharpe>0, ≥2 windows |
train=504, test=63, embargo≥21. |
| Regime-stratified | positive in ≥2 regimes | The crucial carry test — must not be a single-regime artifact. |
| Block bootstrap | Sharpe CI-lower > 0 | block_size≈21 to preserve the autocorrelation/skew. |
| Capacity (Almgren–Chriss) | ≥ min_capacity_usd |
FX legs will be tight; report per-leg. |
Targeted stress windows (event-study each sleeve; report per-window Sharpe & maxDD): 2008 GFC (all carry — expect deep DD), 2011 EU crisis / silver crash (commodity, FX), 2013 taper tantrum (rates), 2014–16 oil crash + structural contango (commodity), Jan-2015 CHF de-peg (FX tail), Aug-2015 CNY deval / vol spike (FX, commodity), Q4-2018 risk-off (all), Mar-2020 COVID + dash-for-cash (all + funding stress), 2022 hiking cycle / energy backwardation (rates down, commodity up).
Acceptance: a sleeve promotes to live PAPER only if it (a) clears ValidationReport.passed, (b) shows the expected crash signature in 2008/2020 but recovers, and (c) the carry_crash_guard demonstrably reduces the stress-window maxDD vs the un-guarded variant. ETF total-return series only (roll/fee drag embedded). FRED values used as-of T-1.
9. Prioritized implementation roadmap
rates_curve_carry(slope-carry variant) — ships with currently wireddgs2/dgs10/t10y2y; only needs G1 (add SHY/IEF/TLT). Fastest path to a clean, high-capacity carry sleeve. (Then G2/G3 to upgrade to full level+rolldown + MOVE guard.)- G4 data extension (
futures_chain.py→ CL/NG/HG) — unblocks real commodity carry. commodity_curve_carry_xs+commodity_curve_carry_ts— fold ingc/si_term_structure,cross_carry,backwardation_stressas legs.carry_crash_guardoverlay + carry1-12 smoothing — wire into 1–3 before live promotion.fx_carry_g10— G6/G7 first; paper/research only; smallest risk slot; promote to live only if it survives the CHF/CNY/2008/2020 stress with the guard on.
Each new strategy: subclass Strategy, declare FactorExposure(carry=...) + CapacityModel, return via validate_signals, and use self._empty(reason) so it ships silent before its data lands.
10. References
- Koijen, Moskowitz, Pedersen, Vrugt (2018). "Carry." Journal of Financial Economics 127(2):197–225. (Cross-asset carry; per-class SR ~0.8, GCF ~1.1–1.2; carry1-12 turnover reduction; recession co-movement; bond slope-carry.)
- Brunnermeier, Nagel, Pedersen (2008). "Carry Trades and Currency Crashes." NBER Macro Annual. (Negative skew, funding-liquidity unwinds.)
- Lustig, Roussanov, Verdelhan (2011). "Common Risk Factors in Currency Markets." RFS. (HML-FX carry factor.)
- Menkhoff, Sarno, Schmeling, Schrimpf (2012). "Carry Trades and Global FX Volatility." Journal of Finance. (Vol as the carry risk factor.)
- Lustig, Stathopoulos, Verdelhan (2019). "The Term Structure of Currency Carry Trade Risk Premia." AER. (Carry premium declines with bond maturity.)
- Gorton, Rouwenhorst (2006). "Facts and Fantasies about Commodity Futures." FAJ.
- Gorton, Hayashi, Rouwenhorst (2013). "The Fundamentals of Commodity Futures Returns." Review of Finance.
- Erb, Harvey (2006). "The Strategic and Tactical Value of Commodity Futures." FAJ. (Roll/income return can be negative for ~decade.)
- Szymanowska, de Roon, Nijman, van den Goorbergh (2014). "An Anatomy of Commodity Futures Risk Premia." Journal of Finance.
- Bessembinder (1992); De Roon, Nijman, Veld (2000). Hedging-pressure risk premia in futures.
- Kaldor (1939); Working (1949); Fama–French (1987). Theory of storage / convenience yield.
- LSEG/FTSE Russell, "The Carry Concept"; Vontobel, "Fixed Income 101: Roll-down"; Norway GPFG (2018) FI benchmarking. (Bond carry = yield-spread + roll-down; term premium.)
- AQR, "Carry." Practitioner summary of KMPV.
- AlphaArchitect / SLCG / ETF.com / Precidian CurrencyShares (2021–2026): commodity ETF roll mechanics (USO vs USL vs DBO), DBV liquidation (~2023), CurrencyShares accrue foreign deposit rates.
Repo files inspected for integration: qgtm_strategies/base.py, gc_term_structure.py, si_term_structure.py, cross_carry.py, backwardation_stress.py, gold_yield_curve_steepener.py; qgtm_core/types.py, universe.py, pm_universe.py; qgtm_data/fred.py, futures_chain.py, cot_reports.py; qgtm_features/store.py; qgtm_backtest/validation.py; qgtm_live/daemon.py (FRED v3 history + feature enrichment); docs/strategies/curve_carry.md.