Case study · Backtest
🗓 Backtest period: 2020-01-01..2026-11-01
Spec ID: spec-sector-momentum-rotation-etf-top50-1767027941_v27 · Generated: 2026-11-05 10:44 UTC
Cluster: Momentum · Sub Cluster: Multi-Horizon Sector Relative Strength Rotation
Ranks eligible sector ETFs by composite 63-, 126-, and 252-day relative strength versus SPY and targets the top three at equal weights, rebalanced monthly. The intended portfolio is long-only with 1.0× gross exposure, subject to position and sector caps.
Sector leadership can persist as investors gradually incorporate changes in earnings prospects, economic conditions, and capital flows. Ranking relative strength across several horizons seeks to capture persistent leadership rather than a single short-lived price move.
The strategy expresses this hypothesis through liquid ETFs, reducing individual-stock exposure. Relative ranking does not provide an absolute defensive signal: selected sectors can still fall together, and leadership reversals can produce whipsaw losses. This rationale is a hypothesis, not a causal conclusion established by the supplied backtest.
Specification-level logic; the truncated source does not establish the full implemented selection and order-generation path.
[code omitted from public view]
The specification requests a one-day signal lag and next-open trading; the applied execution convention is daily-bar MOC at the official closing auction print. These are distinct settings and the actual timing requires blotter reconciliation. The minimum-three-position setting, disabled cash fallback, and caps do not fully specify a feasible allocation when too few eligible sectors exist. The return-ratio definition also requires verification of whether total_return means a gross return factor or a signed cumulative return.
| Param | Value | Notes |
|---|---|---|
| Strategy / rule | Sector Relative Strength Rotation (3/6/12m vs SPY) / SECTOR_MOMENTUM | Specification-level description |
| Universe | ETF; top_n = 50; sort_by = capitalization | Supplied 50-symbol snapshot; visible code describes annual refreshes |
| Universe symbols | AGG, BIL, BIV, BND, BNDX, BSV, DIA, EFA, GLD, IEFA, IEMG, IJR, ITOT, IVE, IVV, IWD, IWF, IWM, IXUS, JEPI, LQD, MBB, MUB, QQQ, QUAL, RSP, SCHD, SCHF, SPY, VB, VCIT, VCSH, VEA, VGIT, VGT, VIG, VNQ, VO, VOO, VT, VTEB, VTI, VTIP, VTV, VUG, VWO, VXUS, VYM, XLK, XLV | All supplied keys have suffix &US&ETF; not all are eligible sector ETFs |
| Sector filter | sector_etfs_only; use_provider_metadata_or_name_match; GICS_11 | Keywords: Select Sector, Sector, SPDR, XL; fallback category: Sector Equity |
| Benchmark | SPY&US&ETF | Relative-strength denominator |
| Lookbacks | 63, 126, 252 trading days | Approximately 3, 6, 12 months |
| Relative strength | total_return(etf,LB)/total_return(benchmark,LB) | Return-factor versus signed-return semantics require verification |
| Composite | mean_zscore_across_lookbacks | Mean cross-sectional standardized relative strength |
| History / missing data | require_full_price_history_days = 252; drop_if_missing_any_lookback = true | Exclude incomplete candidates |
| Liquidity | volume_lookback_days = 63; min_avg_daily_volume_shares = 250000 | Rule-level filter; plan-level liquidity_filter = null |
| Rebalance | monthly; first_trading_day | Monthly ranking and portfolio reset |
| Selection / sizing | selection_count = 3; position_sizing = equal_weight; gross_leverage = 1.0 | Intended three-position, long-only allocation |
| Risk overrides | min_positions = 3; max_weight_per_position = 0.4; cash_if_fewer_candidates = false | Insufficient-candidate behavior is not established |
| Sector cap | max_sector_allocation = 0.4 | Aggregate sector constraint can bind across overlapping ETFs |
| Other risk controls | stop_loss_pct = null; volatility_targeting = false | No configured stop or volatility target |
| Requested execution | trade_on = next_open; signal_lag_days = 1; skip_if_turnover_below = 0.0 | Differs from recorded applied convention |
| Applied execution | daily bars, MOC (market-on-close) by default | From costs_applied.execution |
| Commissions | $0.0040 per share, minimum $1.00 per order, capped at 1.00% of trade value | Charged per fill by the results module; all metrics are net of them |
| Slippage | 0 bps — not applied | Not an omission — MOC (market-on-close) fills at the auction print the backtest uses |
| Costs not modelled | short borrow fees / rebate, margin financing on leverage, market impact, exchange/regulatory/clearing pass-through fees, taxes | Excluded deliberately, not unknown |
| Backtest window / bars | 2020-01-01..2026-11-01; 1d | Requested interval; actual end of evaluated returns is not established |
| Supplied data period | 2018-01-01..2023-01-01 | Does not cover the full requested interval |
| Annualized measurement start | 2020-01-03 | Reported annualized_metrics_measured_from |
| Evaluation settings | performance_metrics = annualized_return, sharpe, max_drawdown, turnover; wfa = null; position_limits = null | Plan-level settings; rule-level caps remain specified |
| Initial sizing capital | 1000000.0 fallback | Visible code accepts configuration/environment overrides; actual run capital is not confirmed |
| # | Concern | Status |
|---|---|---|
| 1 | Point-in-time universe and sector classifications | ⚠ Annual refresh is described, but historical capitalization ranks, membership, and metadata timestamps are not verified. |
| 2 | Signal and execution alignment | ⚠ Applied convention is MOC at the closing auction print; reconcile with the requested one-day lag/next-open setting. Same-day open fills using that day's closing data would violate causality. |
| 3 | Return and volume inputs | ⚠ Verify dividend-adjusted total returns, trailing-only liquidity windows, and complete benchmark/ETF history at every signal date. |
| 4 | Data coverage | ⚠ Supplied data ends 2023-01-01 while the requested backtest ends 2026-11-01; actual evaluated end date requires confirmation. |
| 5 | Ranking and constraint implementation | ⚠ Truncated code does not verify z-score handling, return-ratio semantics, insufficient-candidate behavior, or sector-cap enforcement. |
| 6 | Charged costs | ✓ commissions $0.0040/share (min $1.00/order); slippage 0 bps — not applied |
The supplied net-of-charged-commissions results show a 106.15% total return, a 0.96 Sharpe ratio, and a -22.45% maximum drawdown across 68 reported trades. Reported volatility is 16.22%, with a 70.59% win rate and a 5.96 profit factor. These figures indicate positive historical performance with material downside, but do not establish outperformance versus SPY. The unresolved data-window, eligibility, and execution discrepancies limit confidence that these results describe the intended three-sector rotation strategy.
Recomputed here from the run's stored daily returns, because every annualized figure in the table above is derived from the LENGTH of that array from its first active row (years = n / 252, counted from the first trade). Only total return and max drawdown do not depend on the row count, so when the recomputed and stored values differ by a common factor, those two are the numbers to trust.
| Check | From the run's own returns | Note |
|---|---|---|
| Daily-return rows | 1,717 | 1.00x the 1,722 trading days in 2020-01-01..2026-11-01 |
| Annualized from | 2020-01-03 | 1 leading flat row(s) before the first trade are excluded from Sharpe, volatility and CAGR (1,716 rows count) |
| Distinct dates | 1,717 | one row per date |
| Date span | 2020-01-02 .. 2026-10-30 | |
| Sum of daily returns | 106.15% | matches the reported total return |
| Sharpe from these rows | 0.96 | stored 0.96 |
| Volatility from these rows | 16.22% | stored 16.22% |
| Max drawdown from these rows | -22.45% | stored -22.45% |
| Lowest the book stood | -16.16% | low-water mark, in units of starting capital |
| CAGR from these rows | 11.21% | stored 11.21% |
| Metric | Value |
|---|---|
| Total Return | 106.15% |
| Sharpe | 0.96 |
| Sortino | 1.23 |
| Calmar | 0.50 |
| Max Drawdown | -22.45% |
| Volatility | 16.22% |
| Beta vs SPY | 0.69 |
| Annualized from | 2020-01-03 |
| Win Rate | 70.59% |
| Profit Factor | 5.96 |
| Total Trades | 68 |
| Symbols | 50 (AGG, BIL, BIV, BND, BNDX, BSV, DIA, EFA, GLD, IEFA, +40 more) |