Macro at the Desk: COT Positioning, Real Rates, and a Three-Factor FX Model¶
Strategy Compendium · No. 13 · Category
commodity_currency(21 strategies) · 2026-09-02
Why does the Australian dollar track iron ore? Why does gold fear rate hikes? Both answers live on one macro chain: rates decide carry, carry decides flows, flows decide prices. Rising real rates make holding yieldless gold expensive; returning risk appetite lifts high-beta commodity currencies. That chain hands macro strategies a shared fate — you must watch variables the chart does not show.
The 21 backtests in tests/functional/strategies/commodity_currency/ orbit that chain: CFTC positioning, real-rate proxies, equity and bond momentum factors, cross-sectional skewness and inventory. Each is a self-contained regression. We deep-dive three.
Category at a Glance¶
Strategy |
Data |
Core idea |
Source |
|---|---|---|---|
Change-point trading |
XAUUSD daily 2008-2025 |
Rolling mean/vol ratio detects regime shifts |
|
Walk-forward |
XAUUSD daily 2008-2025 |
Optimize in-window, trade out-of-window |
|
Factor timing |
XAUUSD/IVV/GTIP monthly |
Value + momentum factors set gold exposure |
|
Gold COT |
XAUUSD weekly + CFTC reports |
Follow commercials at z-score extremes |
|
Currency prediction |
XAUUSD/DXY/EURUSD/USDJPY |
Rolling regression on FX returns |
|
Commodity trend |
XAUUSD daily 2008-2025 |
Classic fast/slow MA trend system |
|
Quantpedia combo |
XAUUSD daily 2008-2025 |
Long-only blend of three gold anomalies |
|
Strategy lifecycle |
XAUUSD daily 2010-2025 |
Sharpe decay and drawdown health of SMA200 |
|
ETF ranking |
GLD/IAU/GDX/GDXJ/BAR |
Risk-adjusted momentum rotation across five ETFs |
|
Real-rate signal |
XAUUSD/IEF/GTIP daily |
ETF log-ratio proxies real rates |
|
Dow-gold ratio |
XAUUSD/DJIA daily |
Mean reversion of the gold/DJIA ratio |
|
GDX overnight |
GDX daily |
Overnight session effect + 50-day trend filter |
|
ARIMA-GARCH |
XAUUSD daily |
ARIMA for direction, GARCH for size |
|
Multi-signal timing |
XAUUSD daily |
SMA/momentum/vol regime/RSI weighted ladder |
|
Commodity skewness |
XAU/XAG/XPT/XPD/DBC |
Long-short precious-metal skewness factor |
|
Macro FX |
4 FX pairs + IVV/IEF |
Growth/rates/trend z-scores scaled by beta |
|
Metal inventory |
XAU/XAG/XPT/XPD daily |
Inventory-driven allocation across four metals |
|
FX regression learning |
EURUSD daily 2022-2025 |
Carry/momentum/value/vol rolling regression |
|
KA Gold Bot |
XAUUSD M5 2025-12 |
MT5 minute-level gold bot with spread filter |
|
SilverTrend v3 |
XAUUSD M15 2025-2026 |
SilverTrend indicator EA port |
|
SilverTrend dual-TF |
XAUUSD M15 + H1 |
H1 signals, M15 execution |
|
Deep Dive 1: Macro FX — Three-Factor Z-Scores, Scaled by Beta¶
test_0016_macro_fx_strategy.py trades EURUSD, AUDUSD, NZDUSD, and GBPUSD — but every signal comes from two instruments it never trades: IVV (S&P 500 ETF, growth proxy) and IEF (Treasuries, rates proxy):
growth_factor = _zscore(ivv['close'].pct_change(macro_lookback), zscore_lookback)
rates_factor = _zscore(-ief['close'].pct_change(macro_lookback), zscore_lookback)
...
raw_signal = (
float(factor_weights.get('growth', 0.4)) * growth_factor * beta +
float(factor_weights.get('rates', 0.35)) * rates_factor * beta +
float(factor_weights.get('trend', 0.25)) * pair_trend
)
target_percent = raw_signal.clip(lower=-signal_threshold, upper=signal_threshold) / max(signal_threshold, 1e-6) * max_pair_weight
Two design choices deserve chewing. The rates factor is negated: bonds up (yields down) → positive rates factor → bigger commodity-currency longs — the macro chain as one line of code. And beta scaling: AUDUSD and NZDUSD, the textbook commodity currencies, get beta 1.0; GBPUSD 0.8, EURUSD 0.6. The composite is clipped at ±0.5, mapped to a ±25% per-pair cap, rebalanced every 21 trading days. Baseline: 4,331 daily bars over 2008-2025, 259 trades, final value 1,040,485.14 (+4.05%), profit factor 1.037, max drawdown 34.82% — an equity curve as flat as a currency portfolio should be.
Deep Dive 2: Gold COT — Following the “Smart Money”¶
Every Friday the CFTC publishes the Commitments of Traders report, splitting positions into commercials (hedgers) and non-commercials (speculators). The classic hypothesis: commercials are the smart money, speculators are the crowd. test_0004_gold_cot.py encodes it as 156-week (three-year) rolling z-scores:
out['commercial_z'] = (cot_weekly['commercial_net'] - commercial_mean) / commercial_std
out['speculator_z'] = (cot_weekly['speculator_net'] - spec_mean) / spec_std
long_entry = (out['commercial_z'] >= extreme_threshold) & (out['speculator_z'] <= -extreme_threshold)
long_exit = (out['commercial_z'] < exit_threshold) & (out['speculator_z'] > -exit_threshold)
Enter when commercials are extremely long (z ≥ +2.0) while speculators are extremely short (z ≤ −2.0); exit as both revert toward neutral (±1.0). Size scales with extremity — 3% base, 5% cap — plus a 3% stop and a “three consecutive losses, pause four weeks” cooldown. The engineering is serious too: daily XAUUSD is resampled to W-FRI weeks and aligned with COT releases (888 usable bars), the CFTC archive auto-downloaded when the local cache is missing. The result is honest: 22 trades, 36.36% win rate, final value 997,205.05 (−0.28%), profit factor 0.749. The smart-money hypothesis did not pay on twenty years of gold — and the baseline records exactly that.
Deep Dive 3: Real-Rate Signal — An ETF Log-Ratio Proxy¶
Real rates (nominal minus inflation expectations) are the first-order variable in gold pricing. The trick in test_0010_gold_real_rate_signal.py: skip the macro database — a ratio of two ETFs approximates the level:
ratio = nominal['close'] / inflation['close'] # IEF / GTIP
signal_df['real_rate_proxy'] = np.log(ratio)
signal_df['real_rate_change'] = signal_df['real_rate_proxy'] - signal_df['real_rate_proxy'].shift(signal_window)
signal_df['real_rate_trend'] = signal_df['real_rate_proxy'] - signal_df['real_rate_proxy'].rolling(trend_window).mean()
...
active = rr_change < entry_threshold and rr_trend < 0 and drawdown > -stop_loss_pct
When the proxy is falling over 63 days and below its 126-day trend — gold-supportive — and gold itself is not in a deep (>8%) drawdown, exposure scales from 50% to 100% by signal strength; annualized volatility above 25% halves the target; rebalancing is monthly. Baseline over 2011-2025: 2,748 daily bars, only 10 trades, final value 1,064,691.53 (+6.47%), profit factor 1.284, max drawdown 25.10%, Sharpe 0.135. Low frequency, low turnover, transparent logic — the typical physique of a macro signal strategy.
The Rest of the Bench¶
Change-point / Walk-forward (
test_0001/0002): one hunts regime shifts, the other fights overfitting with rolling re-optimization — methodology more than money.Factor timing / Quantpedia combo / Multi-signal timing (
test_0003/0007/0014): a gold factor zoo — value, momentum, volatility regimes, RSI.Currency prediction / FX regression learning (
test_0005/0018): rolling-regression siblings — one predicts gold from FX, one autoregresses EURUSD.Dow-gold ratio / GDX overnight (
test_0011/0012): classic ratio timing and a miner-equity session effect.ARIMA-GARCH (
test_0013): forecast direction with ARIMA, size the position with GARCH.Skewness / Inventory (
test_0015/0017): cross-sectional metal factors betting on distribution shape and physical inventories.KA Gold Bot / SilverTrend ×2 (
test_0019/0020/0021): minute-level EA ports giving the macro shelf some intraday fireworks.
Run It Yourself¶
# The whole category (21 strategies)
pytest tests/functional/strategies/commodity_currency/ -v
# Just Macro FX
pytest tests/functional/strategies/commodity_currency/test_0016_macro_fx_strategy.py -v
# Just Gold COT (first run may download the CFTC historical archive)
pytest tests/functional/strategies/commodity_currency/test_0004_gold_cot.py -v
Why Study Macro Strategies Here¶
The natural enemies of macro strategies are sluggish pipelines and silent result drift: multi-series alignment, resampling, external data — any wobble rewrites the conclusion. cloudQuant/backtrader pins those down with 1,152 strategy regression tests and per-strategy asserted metric baselines — every number above must reproduce on every rerun. The pure Python engine runs 46% faster than the original, so multi-factor experiments finish same-day; the C++ backend (pip install back-trader-cpp) delivers a median 128x speedup; runonce/runnext dual-mode parity keeps both execution paths on the same page.
Find it useful? Star the project on GitHub. Start from the series overview for the full map. A deeper (Chinese) treatment lives here.
Risk disclaimer: for education and research only. Backtests use historical data and do not constitute investment advice; algorithmic trading carries substantial risk of loss.