--- name: hfmd-quick-backtest description: Backtest a moving-average crossover (or adapt the template to another signal) on HF Market Data bars with an honest walk-forward (parameters chosen on a training window, evaluated on the next out-of-sample window), transaction costs, and a full metrics report. Use when the user wants to "test a strategy", "backtest", or compare signal parameters on a symbol. --- # hfmd-quick-backtest A small, honest backtest template on top of v1 bars. It exists to give a *defensible* first answer (walk-forward, costs, benchmark, no look-ahead) — not to be a full trading framework. ## When to use - "Backtest a 50/200 MA crossover on SPY", "does a 20/100 cross work on CL?", "which MA pair worked best on BTC — and did it hold out of sample?" - Any quick strategy sanity check on daily or intraday bars ## Method (what the script does) 1. Fetch bars (`/v1/bars/{asset}/{ticker}`), keep `close`, compute daily/bar returns. 2. **Walk-forward**: split time into consecutive blocks (`--test-years`, default 1). For each test block, select the (fast, slow) pair with the best Sharpe over the preceding `--train-years` (default 3) of data from the `--grid`; apply *that* pair to the test block. Concatenate the out-of-sample test blocks → the reported equity curve. 3. Signal: long when `MA_fast > MA_slow`, flat otherwise (`--allow-short` for ±1). Position is applied to the **next bar** (no look-ahead). 4. Costs: `--cost-bps` per side per change of position (default 5 bps). 5. Report: CAGR, annualised volatility, Sharpe, max drawdown, exposure, number of trades, turnover, vs buy-and-hold on the same out-of-sample span; per-block table of the chosen parameters and the block's Sharpe (this is where you see whether the choice was stable). 6. Optional chart: equity vs benchmark + drawdown + the chosen pair over time. ## Steps ```bash python3 scripts/ma_crossover.py --asset etf --ticker SPY --start 2010-01-01 \ --grid 10,20,50 --grid-slow 100,150,200 --train-years 3 --test-years 1 --cost-bps 5 --plot spy_wf.png ``` Then explain: (a) the out-of-sample numbers *only*, (b) the stability of the selected parameters across blocks, (c) the in-sample vs out-of-sample gap (`--show-insample` prints the best in-sample pair on the full history for contrast), (d) what is not modelled (slippage beyond bps, borrow, dividends if UNADJUSTED, intraday fills). ## Examples ```bash # Crude oil vendor continuous (ratio-adjusted so returns are meaningful across rolls) python3 scripts/ma_crossover.py --asset futures --ticker CL --adjustment contin_adj_ratio --start 2012-01-01 --allow-short # Bitcoin, hourly bars, 6-month train / 2-month test python3 scripts/ma_crossover.py --asset crypto --ticker BTCUSD --timeframe 1hour --start 2024-01-01 --train-years 0.5 --test-years 0.17 --grid 12,24,48 --grid-slow 96,168,336 # Single fixed pair, no optimisation (pure evaluation) python3 scripts/ma_crossover.py --asset stock --ticker AAPL --fixed 50,200 --start 2015-01-01 ``` ## Adapting the template `signal_ma_cross(close, fast, slow)` returns a position series in {0,1} (or {-1,0,1}). Replace it with any function of past data only; keep the `.shift(1)` when applying positions and keep the walk-forward loop untouched. ## Gotchas - For futures use `contin_adj_ratio` (multiplicative back-adjustment): `contin_UNadj` has roll jumps that fake returns; `contin_adj_absolute` can go negative in long histories. - Intraday bars are US/Eastern and include only the sessions the vendor covers; annualisation uses bars/year inferred from the median bar spacing — check the printed `periods_per_year`. - Keyless mode (low hourly limits, 30 req/h) is enough for daily history; hourly since 2010 needs a free API key (`HFMD_API_KEY`). - A Sharpe above ~1.5 out-of-sample on a plain MA cross is a red flag for a bug or a tiny sample, not a discovery.