Case study · Backtest
🗓 Backtest period: 2020-01-01..2026-11-01
Spec ID: spec-hybrid-overnight-intraday-us-stocks-1782341488_v10 · Generated: 2026-11-05 10:02 UTC
Cluster: Momentum · Sub Cluster: Overnight Momentum With Intraday Extension Fade
Daily long-short equity strategy that buys persistent overnight winners without excessive intraday extension and shorts overnight losers after strong intraday rallies. Positions are volatility-adjusted and held close-to-close, with daily MOC rebalancing.
The hypothesis is that overnight returns contain persistent information or recurring demand that is distinct from daytime price pressure. Combining overnight momentum with an intraday extension penalty seeks to avoid buying temporarily stretched stocks and to short weak overnight names after daytime rallies. The overnight continuation and intraday reversal mechanisms are hypotheses, not established causal explanations of this backtest.
The following describes the supplied rules; the truncated code does not establish every implementation detail.
[code omitted from public view]
Rank scale/direction, rolling-return aggregation, volatility normalization, and entry/exit precedence are not established by the supplied excerpt.
| Param | Value | Notes |
|---|---|---|
| Rule type / data | HYBRID_OVERNIGHT_INTRADAY / daily_prices | Daily equity bars |
| Universe | 100 supplied US STOCK symbols; top_n = 100 | Selection key: volume_1y_in_dollars; point-in-time membership is unverified |
| Backtest window | 2020-01-01..2026-11-01 | Requested interval; annualized metrics measured from 2020-01-02 |
| Declared data period | 2018-01-01..2023-01-01 | Conflicts with the requested backtest end; actual evaluated end is unverified |
| Validation plan | wfa = null; position_limits = null; liquidity_filter = null | Rule-level sizing and liquidity controls are separately specified |
| Trading schedule | rebalance_frequency = daily; rebalance_time = close; holding_period = close_to_close | Daily MOC rebalancing |
| Return definitions | overnight_return = open_t / close_t_minus_1 - 1; intraday_return = close_t / open_t - 1; close_to_close_return = close_t / close_t_minus_1 - 1 | Supplied formulas |
| Feature windows | rolling_overnight_return_short_days = 5; rolling_overnight_return_long_days = 20; overnight_hit_rate_days = 20; intraday_zscore_days = 20; realized_volatility_days = 20 | Trading-day lookbacks |
| Composite score | 0.35zscore(rolling_overnight_return_5d) + 0.45zscore(rolling_overnight_return_20d) + 0.20zscore(overnight_hit_rate_20d) - 0.50max(intraday_return_zscore_20d, 0) | cross_sectional_ranking = true |
| Overnight score weights | 5-day return = 0.35; 20-day return = 0.45; hit rate = 0.20 | Matches composite-score coefficients |
| Extension penalty | metric = intraday_return_zscore_20d; penalty_weight = 0.5; excessive_extension_zscore = 1.25; penalize_positive_extension_only = true | Composite penalizes all positive z-scores, not only values above 1.25 |
| Long entry | min_rolling_overnight_return_20d = 0.0; min_overnight_hit_rate_20d = 0.55; max_intraday_return_zscore_20d = 1.25; select_top_n_by_composite_score = 20 | All entry filters apply |
| Short entry | enable_shorts = true; max_rolling_overnight_return_20d = 0.0; max_overnight_hit_rate_20d = 0.45; min_intraday_return_zscore_20d = 1.5; select_bottom_n_by_composite_score = 20 | All entry filters apply |
| Long exits | long_exit_if_rank_below = 40; long_exit_if_intraday_return_zscore_20d_gte = 2.0; long_exit_if_rolling_overnight_return_20d_lte = 0.0 | Exit on any condition; rank convention unverified |
| Short exits | short_exit_if_rank_above = 60; short_exit_if_intraday_return_zscore_20d_lte = 0.25; short_exit_if_rolling_overnight_return_20d_gte = 0.0 | Exit on any condition; rank convention unverified |
| Position sizing | equal_weight_with_volatility_adjustment; target_long_gross_exposure_pct = 0.5; target_short_gross_exposure_pct = 0.5; max_position_size_pct = 0.1 | Nominal gross target is 100%; maximum position is 10% |
| Leverage / volatility | max_leverage = 4.0; max_realized_volatility_20d = 0.6; volatility_adjust_positions = true; volatility_target_window_days = 20 | Volatility annualization and scaling formula are unverified |
| Liquidity | lookback_days = 20; dollar_volume_field = volume_in_dollars; min_avg_dollar_volume = 10000000 | skip_trade_if_liquidity_filter_fails = true |
| Missing prices | skip_trade_if_missing_open_close_or_prior_close = true | Required inputs must be available |
| 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 |
| Execution | daily bars, MOC (market-on-close) | Official auction print |
| Minimum notional order | 0.0 USD | min_notional_order_limit_usd = 0.0 |
| # | Concern | Status |
|---|---|---|
| 1 | Signal and execution timing | ✓ Specified close signals and same-close MOC execution follow the platform convention; no same-day open execution is specified. |
| 2 | Rolling features and cross-sectional scores | Verify trailing-only windows, historical cross-sections and absence of future-filled observations; truncated code does not establish these. |
| 3 | Universe selection | Verify that annual dollar-volume selection and membership are point-in-time; the supplied fixed symbol list does not establish this. |
| 4 | Coverage and corporate actions | Unresolved: requested end is 2026-11-01 versus declared data end 2023-01-01; verify actual coverage, split/dividend treatment and delisted-name handling. |
| 5 | Rank and portfolio implementation | Verify rank direction/scale, entry-exit precedence, volatility scaling and realized exposure limits against the complete implementation. |
| 6 | Charged transaction costs | ✓ commissions $0.0040/share (min $1.00/order); slippage 0 bps — not applied |
Reported commission-net total return is 33.38%, with Sharpe 0.52, Sortino 0.72 and maximum drawdown of 15.70%. A 52.48% win rate and 1.08 profit factor indicate a modest trading edge across 39,202 trades rather than a wide margin of profitability. Annualized volatility is 9.36%. The unresolved coverage mismatch and lack of out-of-sample evidence limit conclusions about persistence.
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 |
| Distinct dates | 1,717 | one row per date |
| Date span | 2020-01-02 .. 2026-10-30 | |
| Sum of daily returns | 33.38% | matches the reported total return |
| Sharpe from these rows | 0.52 | stored 0.52 |
| Volatility from these rows | 9.36% | stored 9.36% |
| Max drawdown from these rows | -15.70% | stored -15.70% |
| Lowest the book stood | -3.40% | low-water mark, in units of starting capital |
| CAGR from these rows | 4.32% | stored 4.32% |
| Metric | Value |
|---|---|
| Total Return | 33.38% |
| Sharpe | 0.52 |
| Sortino | 0.72 |
| Calmar | 0.27 |
| Max Drawdown | -15.70% |
| Volatility | 9.36% |
| Beta vs SPY | 0.16 |
| Annualized from | 2020-01-02 |
| Win Rate | 52.48% |
| Profit Factor | 1.08 |
| Total Trades | 39202 |
| Symbols | 100 (AAPL, ABBV, ABT, ACN, ADBE, ADI, AMAT, AMD, AMGN, AMZN, +90 more) |