Skip to content

View translation

QqqPreFomcDriftEventLS

Hypotheses

US Equity Pre-FOMC Announcement Drift — Long-Short Scheduled-Event Timing on QQQ: Hold the Nasdaq-100 Into Scheduled FOMC Decisions to Capture the Documented Pre-Announcement Equity Drift (~50bps/Event), Flip to SHORT the Window Only in Confirmed Risk-OFF Regimes, Event-Calendar-Driven with Near-Zero Fitted Parameters (QQQ.USEQ, Daily Bars, 2-Parameter)

Hypotheses

A LONG-SHORT, event-driven scheduled-macro-event strategy on QQQ.USEQ built on the pre-FOMC announcement drift — one of the largest and most robust anomalies in equities (Lucca & Moench 2015, replicated widely): US equities earn a strikingly disproportionate share of their total excess return in the ~24-48 hours BEFORE scheduled FOMC policy announcements (~50bps average per pre-FOMC window; historically the bulk of the entire equity risk premium accrued in these ~8 windows/year, with near-zero average return the rest of the time). This is a DIFFERENT FAMILY from every equity strategy already in the pipeline (KO/PEP cointegration, QQQ turn-of-month seasonality, XLK/XLU beta-regime rotation, QQQ vol-regime managed exposure) and is explicitly the kind of REAL, high-magnitude, structurally-motivated edge the XLF no-edge death demanded — not a thin fade of daily noise, but a documented risk-compensation/uncertainty-resolution premium tied to a FIXED, known event calendar. Because the entry/exit are determined by the published FOMC schedule (known history — no news data, no supplementary feed required, fully backtestable per the researcher mandate), the overfitting surface that killed 338 prior hypotheses barely exists: only 2 free parameters (the regime SMA length and the pre-event window length), and the event dates themselves are not fitted. Direction is LONG-SHORT (filling the under-represented long_short bucket): LONG the pre-FOMC window in normal/risk-on regimes where the drift is documented to concentrate, and SHORT the window when the market is in a confirmed downtrend (the drift is empirically absent-to-negative around hawkish-surprise/risk-off regimes, where pre-meeting positioning de-risks). It runs on QQQ daily bars with 25+ years of history (since 1999) across ~200 FOMC events spanning easing and tightening cycles for a robust walk-forward. Commission-free USEQ execution (~0.02-0.05% RT) leaves nearly all of the ~50bps capture intact. FEE ARITHMETIC: pre-FOMC window historically averages ~+0.3-0.6% on broad equities (larger on higher-beta QQQ) over a ~2-day hold; net of ~0.04% round-trip that is ~0.3-0.55% captured per event, ~8-14x the effective cost floor and well above the 0.15% viability bar. ~8 events/year × 25 years ≈ 200 trades — an adequate sample for significance without any high-frequency fee drag.

Hypotheses

Iteration 3 fix, minimal and surgical. Layer 2 failed with 'TypeError: _bar_epoch_day() takes 1 positional argument but 2 were given' on all six synthetic scenarios. Root cause confirmed by reading src/verification/strategy_verifier.py:126-143: the _StrategyProxy copies every callable attribute onto a plain wrapper via types.MethodType(val.__func__ or val, wrapper), which re-binds @staticmethod/@classmethod helpers as instance methods and injects a spurious self argument. The only change made: the three pure calendar helpers (_days_from_epoch, _epoch_day_from_yyyymmdd, _bar_epoch_day) were moved from staticmethod/classmethod on the class to module-level functions, exactly like the already-working _ts_of helper, and their two call sites updated (self._epoch_day_from_yyyymmdd -> _epoch_day_from_yyyymmdd in __init__, self._bar_epoch_day -> _bar_epoch_day in calculate_signal). No imports were added or removed, no signal logic, thresholds, sizing, or parameter set changed, so Layer 1 stays green. Signal remains continuous every bar (event-proximity lobe x graded regime direction) and the schedule is still anchored to the bar's own calendar timestamp, not a bar counter, so it is restart- and replay-identical.

Hypotheses

Structurally low, decayed risk-adjusted edge with no realistic path to a promotable Sharpe. Base Sharpe 0.129 with 95% CI [-0.226, 0.519] straddling zero and information_ratio -0.394 means no measurable risk-adjusted edge even before optimization selection bias; ~1.3%/yr CAGR and only +0.76%/yr alpha. The pre-FOMC drift is concentrated pre-2005 (rolling Sharpe ~8 in 2002-03, ~0 since) and 2019-2026 is mostly negative — the anomaly has decayed on modern QQQ. The distinguishing short overlay is broken (short_win_rate 0.37, negative contribution). Not a fee failure (avg_trade_return_pct 0.143% clears the USEQ floor) — the edge is simply too small and too decayed. Only 2 free params, so optimization can only curve-fit the early window and would die at deflated-Sharpe/holdout. Matches the USEQ commission-free zero-survivor pattern (this pre-FOMC-drift hypothesis previously burned 3 iterations to risk_reject) and the base-Sharpe<1.0 always-in-event abandon pattern. Failure pattern: no_edge / structurally-low-Sharpe USEQ event strategy.

Implementation

Event-driven long/short pre-FOMC announcement drift on QQQ.USEQ daily bars. Positions into the ~1-2 sessions before each scheduled FOMC policy announcement (published Fed calendar, 1999-2027, hard-coded as data) and exits at the close of the announcement day. Direction is regime-graded: LONG when QQQ trades at or above its trend SMA, SHORT when it is confirmed below it by more than bear_ref, flat when the regime is ambiguous. Zero-commission USEQ execution; whole-share capital-relative sizing with a 5% catastrophe stop.

Verification Results

CLEAN RESTART 2026-09-04 — this run's verdict history and learning records were removed and it was restarted from verification. Its previous abandonment came from the pipeline, not from the market: the Layer-2 harness mis-bound @staticmethod helpers (fixed), QA issued terminal performance verdicts on an unoptimized smoke test (removed — QA now judges correctness only), and sandbox timeouts came from backtest-slot starvation (fixed). The hypothesis and the strategy code are unchanged. Verify the code on its merits; performance is decided later by the full backtest and the optimizer.

Verification Results

No code change; ensure the full 1999-present window is used.

Verification Results

Sandbox trade count (50 over ~1650 days) is fine to run but too small to confirm a ~50bps/event anomaly — NOT a measurability blocker, since the full backtest on QQQ since 1999 gives ~200 FOMC events. Flagged for per-window WF/holdout weighting.

Verification Results

Unavoidable at daily resolution; judge realized capture against a <50bps expectation.

Verification Results

Exit fires at the announcement-day close, folding in the post-decision reaction; the pure pre-FOMC drift ends at the ~2pm announcement. Matches the hypothesis's own stated rule and is the best daily-resolution behaviour, so not a defect, but it dilutes the expected per-event capture.

Backtest Review

Mechanism fires correctly and matches the hypothesis: 214 event-timed trades (168 long / 46 short) entered into the published FOMC calendar over 25.5 years — no data-snooping, fully backtestable.

Backtest Review

Per-trade edge (avg_trade_return_pct 0.143%) clears the near-zero USEQ cost floor, so this is not a fee failure.

Backtest Review

Low drawdown (14.4%), PF 1.32 — the long pre-FOMC leg has a genuine, documented micro-edge.

Backtest Review

Sharpe 0.129 with bootstrap CI [-0.226, 0.519] straddling zero — no statistically measurable risk-adjusted edge BEFORE any optimization selection; deflation only lowers it, far below the promotion Sharpe floor.

Backtest Review

~1.3%/yr CAGR and information_ratio -0.394: it underperforms buy-and-hold QQQ on a risk-adjusted basis; alpha is only +0.76%/yr.

Backtest Review

The distinguishing SHORT overlay (the hypothesis's differentiator vs a plain long-window trade) is broken: short_win_rate 0.37, dragging book P&L in risk-off years.

Backtest Review

Edge is concentrated in the pre-2005 window (rolling Sharpe ~8 in 2002-03, ~0 since); 2019-2026 annual returns are mostly negative — the anomaly has decayed on modern QQQ.

Backtest Review

Only 2 free parameters leave no room for optimization to lift a 0.13 Sharpe to a robust, promotable level; it would curve-fit the early window and fail deflated-Sharpe/holdout.

Backtest Review

Matches the USEQ commission-free zero-survivor cohort (this exact pre-FOMC-drift idea previously burned 3 iterations to risk_reject) and the base-Sharpe<1.0 abandon pattern.

Iteration History

Verification failed (Layer 2 — synthetic scenarios): Parameters used: ['decay', 'bear_ref', 'trend_len', 'pre_window', 'warmup_bars', 'entry_thresh', 'min_notional', '_param_bounds', 'long_exposure', 'max_scan_days', 'stop_loss_pct', 'short_exposure'] Check that __init__ sets all attributes from self.parameters.get(). - steady_uptrend: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - steady_downtrend: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - flat_ranging: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - volatility_spike: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - zero_volume: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - price_gap: TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000)

Iteration History

Verification failed (Layer 2 — synthetic scenarios): Parameters used: ['decay', 'bear_ref', 'trend_len', 'pre_window', 'warmup_bars', 'entry_thresh', 'min_notional', '_param_bounds', 'long_exposure', 'max_scan_days', 'stop_loss_pct', 'short_exposure'] Check that __init__ sets all attributes from self.parameters.get(). - steady_uptrend: TypeError: QqqPreFomcDriftEventLS._bar_epoch_day() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - steady_downtrend: TypeError: QqqPreFomcDriftEventLS._bar_epoch_day() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - flat_ranging: TypeError: QqqPreFomcDriftEventLS._bar_epoch_day() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - volatility_spike: TypeError: QqqPreFomcDriftEventLS._bar_epoch_day() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - zero_volume: TypeError: QqqPreFomcDriftEventLS._bar_epoch_day() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000) - price_gap: TypeError: QqqPreFomcDriftEventLS._bar_epoch_day() takes 1 positional argument but 2 were given (bar timestamp: 1735691340000)
Strategy report

Backtest and paper results are hypothetical. Trading involves risk of loss.