QqqTurnOfMonthSeasonalityLS
Hypotheses
US Equity Turn-of-Month Seasonality — Long/Short Index Timing on QQQ: Capture the Documented Turn-of-Month Return Cluster (Long), Short the Post-ToM Mid-Month Window ONLY in a Bearish Regime, Calendar-Driven with Near-Zero Fitted Parameters (QQQ.USEQ, Daily Bars, 2-Parameter)
Hypotheses
A LONG-SHORT, SINGLE-INSTRUMENT calendar-seasonality index-timing strategy on QQQ.USEQ (Nasdaq-100 ETF), a family entirely absent from the factory's book and structurally immune to the two biggest killers here (overfitting and sub-fee economics). The signal is the CALENDAR, not a fitted price threshold: the turn-of-month (ToM) effect — that a disproportionate share of equity index returns accrues in the window spanning the last trading day of a month through the first ~3 trading days of the next — is one of the most robust, out-of-sample-persistent anomalies in equities (Ariel 1987; McConnell & Xu 2008, confirmed across decades and dozens of markets). Because the entry/exit dates are DETERMINED BY THE CALENDAR rather than optimized on price, the overfitting surface that sank 338 prior hypotheses barely exists — there are only 2 genuinely free parameters (a trend-regime SMA length and a short-enable flag), and the ToM window bounds come from published research, not a parameter sweep. It runs on QQQ daily bars with 25+ years of history (QQQ since 1999) for a genuinely robust multi-window walk-forward and real holdout. Direction is LONG-SHORT (filling the under-represented long_short bucket) and it is NOT market-neutral (0.7% survival) nor cross-sectional rotation (0/125, L68) nor trend-following momentum — it is directional calendar timing of a single liquid ETF. Why USEQ and not crypto: the ToM capture is a modest ~0.3-0.6% per window; commission-free USEQ execution (~0.02-0.05% round-trip, spread+impact only) leaves nearly all of it, whereas crypto's 0.10-0.20% fees plus 24/7 tapes (no monthly settlement/rebalance flows that drive the ToM effect) would erase it. FEE ARITHMETIC: ToM long window historically averages ~0.3-0.6% on QQQ over ~4 trading days; net of ~0.04% round-trip that is ~0.3-0.55% captured, roughly 8-14x the effective cost floor and above the 0.15% viability bar. ~12 long trades/year (one per month) plus the conditional short leg → adequate sample for significance over 25 years without any high-frequency fee drag.
Hypotheses
Iteration 2 - single targeted fix for the Layer-2 failure, no other changes. The crash was TypeError: FactoryStrategy._bar_ts() takes 1 positional argument but 2 were given: the Layer-2 proxy copies every callable onto a plain wrapper via types.MethodType, which turns a @staticmethod into a BOUND method, so the instance is passed as the first positional argument. Fix: (1) the strategy no longer calls the base static helper self._bar_ts(bar) - it reads the timestamp through its own instance method _ts_of(bar) (ts_event with ts_init fallback, identical semantics); (2) the three calendar helpers (_days_from_ns, _civil_from_days, _days_in_month) were converted from @staticmethod to plain instance methods for the same reason, since they would have hit the identical mis-binding on the very next call. Nothing else was touched: imports are unchanged (still only math, collections, decimal, nautilus_trader, src - no banned modules, so Layer 1 stays green), and the signal logic, calendar windows, entry/exit rules, sizing and config are byte-identical to the previous iteration. The underlying design is unchanged from iteration 1: the trigger is the calendar rather than a fitted price level (2 free parameters, regime_sma_len declared in _param_bounds because the code clamps it, plus the short_enabled flag); the session date comes from the bar timestamp via pure integer civil-date arithmetic shifted 5h so it lands on the New York calendar day under both EST and EDT; the trading-day index within the month is counted from observed bars so holidays and half-days need no calendar table; and nothing is anchored to a process-relative bar counter, so the schedule is identical across restarts, replays and backtests.
Hypotheses
Structurally low-Sharpe USEQ calendar anomaly with no robust edge to optimize. Base Sharpe is 0.237 with a bootstrap CI [-0.11, 0.60] that straddles zero — statistically indistinguishable from no-skill before any optimization selection bias, and deflation/holdout only lower it. profit_factor 1.195 is below the 1.2 meaningful-edge line; on near-frictionless USEQ that means the turn-of-month effect is barely present net of spread, not fee-suppressed (L75). Over 27 years it returns only ~116% (~2.9%/yr) with a NEGATIVE information ratio (-0.296), so it fails to beat buy-and-hold, and the conditional short leg is a drag (short_win_rate 0.457) that still absorbs large directional losses (2002 -33.5%, 2001 -14.5%) — the drawdown-immunity premise does not hold (max DD 43.7%, CI to 87.8%). With only 2 free parameters there is no path to lift a 0.24 Sharpe to a promotable, robust level. This is the L75 USEQ turn-of-month zero-survivor pattern; abandon at BACKTEST_REVIEW rather than iterate.
Implementation
Calendar-seasonality index timing on QQQ.USEQ daily bars, long and short. LONG leg: buy at the close of the last trading session of each month (or on the first two sessions of the new month if that close was missed) and exit at the close of trading day 3 of the new month, capturing the documented turn-of-month return cluster. SHORT leg (optional, regime-conditional): short the post-ToM mid-month lull, trading days 7-15 of the month, only while QQQ sits meaningfully below its 200-session regime SMA; the short covers when the window ends or the bearish regime disappears. calculate_signal returns a continuous calendar-proximity score every bar - an exponential lobe peaking inside the ToM window minus a mid-month lobe scaled by how bearish the regime is - and should_enter thresholds that score. Sizing is capital-relative whole shares (95% of equity long, 50% short, leverage 1.0), with a 5% catastrophe stop as the only price-based rule. Roughly 12 long trades per year plus the conditional shorts.
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
Optimize short_enabled True vs False as a first-class fork; ship long-only ToM if the short leg doesn't improve OOS Sharpe net of its tail.
Verification Results
The regime-conditional short leg (19 sandbox trades) is the main tail-risk source and the part least supported by the published anomaly (ToM is a long-side effect; the bearish mid-month short is a developer overlay). Not a defect, but it widens the drawdown CI (max_dd_ci_high 47%) and its edge is unproven at this sample. Not blocking.
Backtest Review
avg_trade_return_pct 0.203% clears the USEQ cost floor (not a fee failure)
Backtest Review
460 trades over 27 years — adequate sample, calendar-driven so low overfitting surface
Backtest Review
low market beta (0.087) — the return is not just index exposure
Backtest Review
Sharpe 0.237 with bootstrap CI [-0.11, 0.60] straddles zero — no statistically robust risk-adjusted edge before any optimization
Backtest Review
profit_factor 1.195 below the 1.2 meaningful-edge threshold (L75: near-frictionless USEQ + PF<1.2 means the anomaly is absent, not fee-suppressed)
Backtest Review
information_ratio -0.296: underperforms buy-and-hold on a risk-adjusted active basis; total_return only ~116% over 27 years (~2.9%/yr)
Backtest Review
conditional short leg is a net drag (short_win_rate 0.457) and the strategy still takes large directional hits (2002 -33.5%, 2001 -14.5%), contradicting the drawdown-immunity claim
Backtest Review
max_drawdown 43.7% (CI to 87.8%) against a sub-3%/yr return
Backtest Review
matches the L75 USEQ turn-of-month zero-survivor pattern
Iteration History
Verification failed (Layer 2 — synthetic scenarios):
Parameters used: ['decay', 'bear_ref', 'tom_days', 'short_end', 'long_thresh', 'short_start', 'warmup_bars', 'min_notional', 'short_thresh', '_param_bounds', 'long_exposure', 'short_enabled', 'stop_loss_pct', 'regime_sma_len', '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)
Backtest and paper results are hypothetical. Trading involves risk of loss.