Skip to content
All strategies

Strategies

Pension Month-End Rebalancing Pressure on SPY: Fade the Month-to-Date SPY-minus-TLT Gap Over the Last Session of the Month, Then Reverse It Over the First 2 Sessions of the Next (SPY.USEQ 1-DAY, long-short, TLT signal-only, flat ~86%)

Outcome: Abandoned

PensionMonthEndRebalanceSpyTlt

Outcome Summary

PensionMonthEndRebalanceSpyTlt tried to trade against the month-end equity/bond rebalancing flow of balanced funds using the month-to-date SPY-minus-TLT gap. It passed backtest review with 514 trades and 0.162% per trade, and its full-period trade ledger reproduced the pre-study's Sharpe of about 0.55. After optimization it failed two hard gates (walk-forward overfit, CPCV OOS Sharpe 0.26 < 0.5), and holdout-window trades averaged -0.014%, so the analyst abandoned it after 2 iterations as no_edge. The analyst also flagged a suspected USEQ engine artifact for separate review: paired MTM swings on days with no open position, which distort Sharpe and drawdown.

Hypothesis

Single traded leg: SPY.USEQ on daily bars. TLT.USEQ is a SIGNAL-ONLY extra instrument and is never traded. Economic cause: balanced mandates (pensions, target-date and 60/40 funds) rebalance toward fixed equity/bond weights at month-end. When equities have outperformed bonds month-to-date they must SELL equities into the close of the month, and when equities have lagged they must BUY. This is a known, mechanical, price-insensitive flow, and it is documented in Harvey, Mazzoleni & Melone (2025, 'The Unintended……Show moreShow less

Single traded leg: SPY.USEQ on daily bars. TLT.USEQ is a SIGNAL-ONLY extra instrument and is never traded. Economic cause: balanced mandates (pensions, target-date and 60/40 funds) rebalance toward fixed equity/bond weights at month-end. When equities have outperformed bonds month-to-date they must SELL equities into the close of the month, and when equities have lagged they must BUY. This is a known, mechanical, price-insensitive flow, and it is documented in Harvey, Mazzoleni & Melone (2025, 'The Unintended Consequences of Rebalancing', NBER w33554): SPY returns over the last days of the month are negative after equities outperform bonds, and they partially reverse at the start of the next month once the flow is gone. The payer is the rebalancing fund, which trades at a known time regardless of price. Point-in-time rule, no fitted threshold: at the close of the SECOND-TO-LAST NYSE session of month m (computed from the XNYS calendar), compute G = SPY_close/SPY_close(last session of m-1) - TLT_close/TLT_close(last session of m-1). Leg A (month-end pressure): position = -sign(G) SPY at that close, held to the close of the LAST session of month m. Leg B (post-flow reversal): at the last close of month m, flip to +sign(G) and hold for 2 sessions, to the close of the 2nd session of month m+1. Then flat until the next month's signal. Pre-study, run by me point-in-time on catalog SPY/TLT 1-DAY bars from 2002-08 to 2026-08 (289 month-ends), 3bp per unit of turnover. Leg A: mean +0.236% per trade, t = 4.19. Leg B: mean +0.216% per trade, t = 2.35. Combined book at 1x: calendar Sharpe 0.58, 3.9%/yr, max DD 18.4%, 72% of years positive, in the market 14% of sessions. The first 80% of the sample (IS) has Sharpe 0.49 and the last 20% (2021-11 to 2026-08) has Sharpe 1.02, so the effect has not decayed. Attribution control: the same rule with the bond leg removed (SPY month-to-date return only, i.e. plain short-term reversal) drops to Sharpe 0.24, with Leg A t = 0.74 and last-20% Sharpe -0.67. The information is therefore in the equity-vs-bond gap, not in SPY's own reversal. Robustness: the result holds with IEF or AGG as the bond leg (Leg A t = 3.0 and 3.2), and with signal day T-3 instead of T-2 (t = 3.08). Distinct from the abandoned IEF month-end duration-extension trade, which was an unconditional long-only calendar hold with no cross-asset signal, and from the abandoned SPY weekly majority/minority selector, which is a pure own-price scheme. Mechanism stripped of instrument and timeframe: 'trade against the forced month-end flow implied by a balanced fund's equity-vs-bond drift, then against its unwind'. That sentence has not been run in the corpus. Deliberately ignored library excerpts: none of the shown library documents apply.

Iteration 2 fixes the one-session fill lag QA found. On USEQ a market order fills at the NEXT daily bar's close, so every decision now happens one session before its target fill date. G is computed and Leg A submitted at session n-2, so it fills at close(n-1). That is the hypothesis's own T-3 robustness variant, which QA replicated on catalog data at Leg A +0.211%/trade (t=3.72) and Leg B +0.204% (t=2.21). The flip is submitted at n-1 and fills at close(n). The Leg B exit is submitted at session leg_b_sessions-1 of m+1 (or at session n when leg_b_sessions=1) and fills at close(session leg_b_sessions). signal_offset now means the decision session counted from the month end (k = n - offset). Its bounds are unchanged at [2,4], because 1 would leave Leg A with zero length. Only the schedule block and the docstring changed. The signal, sizing, imports and structure are untouched. The same-bar close-then-enter flip is still used; it is correct in the backtest, but paper execution may want a single net order.

Failure code: no_edge. The edge decays out of sample; this is not fee_edge. On the first measured report the full-period per-trade return is 0.16%, above the USEQ floor of 0.05%. On the realized trade ledger, trades in the holdout window (2017-10 to 2023-12, n=150) average -0.014% per trade (t=-0.13), with 2018, 2019 and 2020 all negative. Trades before the holdout average +0.198% (t=2.88), concentrated in 2003-2011. Two HARD gates also fail: walk-forward is flagged overfit, and CPCV OOS Sharpe is 0.26, below……Show moreShow less

Failure code: no_edge. The edge decays out of sample; this is not fee_edge. On the first measured report the full-period per-trade return is 0.16%, above the USEQ floor of 0.05%. On the realized trade ledger, trades in the holdout window (2017-10 to 2023-12, n=150) average -0.014% per trade (t=-0.13), with 2018, 2019 and 2020 all negative. Trades before the holdout average +0.198% (t=2.88), concentrated in 2003-2011. Two HARD gates also fail: walk-forward is flagged overfit, and CPCV OOS Sharpe is 0.26, below 0.5. Neither gate can be waived. The rule has no real tunable signal parameters (the sensitivity sweep never varied signal_offset, leg_b_sessions or min_abs_gap), so iteration cannot help and a second optimization would only re-fit leverage. SEPARATE ENGINE ISSUE, for review and not verified: the MTM daily-return series has recurring paired -10% to -19% / +10% to +25% swings on days with no open position (for example 2023-01-26/27), plus a -16% final-bar print on a zero-PnL phantom short. This is the same signature as the SPY majority/minority selector. It contaminates Sharpe, drawdown and DSR for USEQ runs and needs engine review (runner impact/MTM folding on USEQ), whatever this verdict is. Note for the Research Lead: the month-end balanced-fund rebalancing mechanism is plausible and did appear in-sample. On SPY/TLT at daily granularity it has not held since 2017 in the observable data. A version that the post-2017 data supports would need a different form, for example a futures or intraday-close execution that captures the MOC flow, or a multi-asset panel. It should not be re-proposed as this same single-SPY calendar rule.

Implementation

SPY.USEQ daily, TLT signal-only. G = month-to-date SPY return minus TLT return (vs. last close of the prior month), measured at the T-3 close (session n-2). Leg A: short SPY if G>0, long if G<0, filled at close(n-1) and held to close(n). Leg B: flip to +sign(G) at close(n) and hold to the close of session 2 of the next month. Flat otherwise, about 86% of sessions. Sized at 95% of equity in whole shares.

Verification Results

For paper, submit one net order of 2x size (or target-position logic) at the flip. No change is needed for the backtest.

The Leg A to Leg B flip is done inside should_exit() as _submit_exit() followed by _submit_entry() on the same bar. In the backtest both fill at close(n) and are booked as separate trades, which I verified in the ledger. In a paper or live NETTING account, however, a reduce-only close and an opposite-side entry are in flight at the same time. If the close is rejected or fills only partly, the entry nets against it.

Optionally derive month sessions from src.data.market_calendar if it is importable within ALLOWED_IMPORT_ROOTS.

The strategy embeds its own XNYS holiday calendar instead of using src/data/market_calendar.py, which the project treats as the single source of session truth. I checked the observed-holiday rules, Good Friday, Juneteenth (from 2022), Carter 2025-01-09, Bush 2018-12-05 and Sandy 2012. They are correct, and the 2020-2026 fill dates in the engine land exactly on sessions n-1, n and session 2 of m+1, including 2023-07-05 after the July 4 holiday. But any unlisted special closure would shift n for that month.

Acceptable as pre-registered. The analyst should check worst-leg loss and drawdown against the hypothesis's 18.4% DD in the full backtest.

There is no stop and sizing is 95% of equity per leg (lev 1.0, whole shares, no dynamic scalar). Worst-case gross is 0.95 x equity, below the 2x Reg-T limit. Holds are 1-2 sessions with a flat state about 85% of the time, so this is not the always-in compounding pattern of L148. Still, the worst loss per trade equals one to two days of SPY moves: about 9% of equity on a March-2020-type day (2020-03/05 legs of -1.5% and -2.4%). That is above a 1-2% per-trade risk guideline.

Engine review of the USEQ impact/capacity model on SPY. Use the full-backtest ledger for per-trade economics.

The sandbox ledger in the brief is internally inconsistent: avg_trade_return_pct is +0.024% while avg_trade_pnl_usd is -6.57 and total_return is -1.02%. It also reports impact_cost_pct 148% and capacity_usd $45.6k on SPY, the most liquid ETF in the universe. None of these numbers can be reproduced from the current code (re-run: +0.334% per trade, +$58k). This looks like an engine or metrics artifact, or a stale run, not a strategy defect.

The Layer-3 sandbox metrics in the brief (total_return -1.02%, avg_trade 0.024%, PF 0.98) do not match the submitted code. I re-ran this exact code and config through BacktestRunner on USEQ from 2020-01-01 to 2026-09-26. Result: 158 trades, mean +0.334% per trade (Leg A +0.314%, Leg B +0.354%), net PnL about +$58k. That matches my offline catalog replication of the T-3 variant (2020 onward: A +0.29%, B +0.32%; full 2002-2026: A +0.211% t=3.72, B +0.204% t=2.21). The sandbox figure is probably stale (iteration-1 code) or from a different window. The analyst should judge on the full backtest, not the brief. Remaining doubts: 2020 is flat or negative (-0.04% per trade over 22 trades), and the result rests on about 290 monthly observations, i.e. 2 legs x 289 month-ends.

Backtest Review

Sharpe
0.19
Total return
91.42%
Max drawdown
20.32%
Trades
514
Win rate
55.8%
Profit factor
1.27

Sample is adequate: 514 trades from 2002-08 to 2023-12 (257 long, 257 short, balanced as expected for a -sign(G)/+sign(G) two-leg book). The holdout will hold about 100 trades.

The trades implement the hypothesis. I spot-checked Dec-2018/Jan-2019 and Nov/Dec-2023: a 1-session Leg A into the month-end close, flipped at the last close into a Leg B held 1-2 sessions into the next month, and flat mid-month. Leg A (217 one-day legs) averages +0.15% per trade.

Economics clear the USEQ floor. avg_trade_return_pct is 0.162% (0.138% on the realized trade ledger), against a floor of 0.05% and a preferred level of 0.10%. PF is 1.27 and win rate 0.56. Commission is zero by venue design; impact takes 17.7% of gross.

The realized trade ledger reproduces the pre-registered study. Per-trade t is 2.37, and the Sharpe of monthly returns built from trades is about 0.55, against the hypothesis's 0.58. 14 of 22 years are positive (64%, above the 60% floor). QA's re-run (2020-2026, +0.33% per trade) is consistent with this.

L170 beta screen: benchmark_meaningful is false, the book is long/short sign-alternating, time in market is 15.6%, and max DD of 20% is well below SPY's buy-and-hold DD (~55% in 2008). This is not beta.

SUSPECTED ENGINE ARTIFACT in the MTM equity curve (mechanism not verified). Every month there is a paired swing of -X% then +X% on consecutive mid-month sessions while the book is flat. Examples: 2019-01-18 -7.29% / 2019-01-22 +7.86%, 2020-03-02 -13.6% / 2020-03-03 +14.0%. No trade is open on those dates, which I checked in the trade ledger. There is also a -10.5% final bar on 2023-12-29. Each pair nets to about 0 and the magnitude grows steadily over time (0.07% in 2002 to about 10% in 2023). As a result, annualized_volatility (30%), max_drawdown (20.3%), the reported Sharpe (0.19, with a CI straddling 0) and PSR (0.67) are all contaminated. A 14%-exposure SPY book should run about 6% vol. The same symptom was flagged on the SPY weekly selector run. Needs engine review: MTM/impact folding for USEQ with a signal-only extra leg (TLT).

The backtest window ends 2023-12-29. The hypothesis's pre-study and QA's re-run extend to 2026-08/09, and that stretch is the period the hypothesis reports as strongest (Sharpe 1.02 in the last 20%). The holdout will therefore be roughly 2019-06 to 2023-12, which includes the weak 2018-2020 run (-4.8%, -6.5%, -4.9%).

QA concern partly confirmed: 2018-2020 were three consecutive losing years, and the edge is thin per unit of risk. QA's doubt that the brief's sandbox figures reflect this code is borne out: this full run matches QA's re-run, not the stale sandbox.

artifact-deflated; trade-ledger monthly Sharpe ~0.55

Analysis

Mechanism is correctly implemented. Trade records show a one-session Leg A (-sign(G)) filled at close(n-1) and held to close(n), then a Leg B flip held 2 sessions. Direction is balanced (257 long / 257 short), which matches the hypothesis.

Artifact-free trade ledger over the full 2002-2023 span: 514 trades, mean 0.136%/trade, t=2.34, monthly-aggregated Sharpe about 0.54. This reproduces the Research Lead's pre-study (about 0.58), so the in-sample effect is real.

The per-trade return of 0.14-0.16% clears the USEQ floor of 0.05%. PBO is 0.18. There are no parameter cliffs.

HARD failures (not waivable): walk-forward is flagged overfitted, and CPCV OOS Sharpe is 0.26, below the 0.5 validity floor.

The forward period does not hold up. Trades exiting in the holdout window (2017-10-23 to 2023-12-29, n=150) average -0.014% per trade, t=-0.13, which is zero edge on an adequate sample. 2018, 2019 and 2020 all lose (-4.8%, -6.6%, -4.9% summed trade return). Before the holdout (n=364) the mean is +0.198% with t=2.88. The effect is concentrated in 2003-2011 (2007-2009 alone gives about +41% of the roughly +70% summed). The hypothesis's claim of last-20% Sharpe 1.02 depends on 2024-2026 data that sits behind the locked vault. The 2017-2023 slice that could be observed does not show it.

SUSPECTED ENGINE ARTIFACT, mechanism not verified: the daily MTM series has large paired -X%/+X% swings on dates with no open trade. Examples are 2023-01-26 at -15.75% and 2023-01-27 at +18.70% (the trade ledger shows no position between the 01-04 exit and the 01-30 entry), plus about one such pair per month throughout. There is also a -16.2% print on the final bar, against a zero-PnL phantom short opened and closed on 2023-12-29. This is the same signature as the SPY majority/minority selector run earlier (paired MTM swings while flat, a final-bar loss while flat). These phantom days inflate annualized volatility to 47% and corrupt the Sharpe, OOS, holdout, DSR, drawdown (30.3%) and Calmar figures. The Sharpe rising with equity_fraction (0.16 to 0.24), which a real edge does not do, fits a roughly fixed-dollar noise term. My verdict does NOT rest on these contaminated numbers. It rests on the realized trade ledger above, where the forward period is flat.

The sensitivity sweep is degenerate. signal_offset, leg_b_sessions and min_abs_gap were never varied (all 5 variations are identical), so the only real free parameter was equity_fraction, which is sizing and not signal. The optimizer 'improved' Sharpe only by levering up to 1.42x.

Pre-registered predictions: 3 of 6 missed (min_oos_sharpe 0.55 vs 0.26, max_dd 22% vs 30.3%, PF 1.25 vs 1.24). The PF and drawdown misses may be artifact-driven. The forward-period trade mean of about 0 is not.

Benjamini-Hochberg at q=0.10 over 338 programme candidates keeps 8. A candidate that does not survive here is not distinguishable from the programme's own noise, however good its individual statistics look.

Iteration History

Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] Once the timing is fixed, the edge looks real on this catalog. Replicating the rule offline on catalog SPY/TLT 1-DAY bars reproduces the pre-study exactly: Leg A +0.236%/trade (t=4.19) and Leg B +0.216% (t=2.35) over 289 month-ends. The T-3 variant needed for the fix gives A +0.211% (t=3.72) and B +0.204% (t=2.21). Both clear the USEQ spread-and-impact floor (~0.05%). Leg B is weaker in the recent window (t~1.5-1.9 since 2020), so……Show moreShow less

Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] Once the timing is fixed, the edge looks real on this catalog. Replicating the rule offline on catalog SPY/TLT 1-DAY bars reproduces the pre-study exactly: Leg A +0.236%/trade (t=4.19) and Leg B +0.216% (t=2.35) over 289 month-ends. The T-3 variant needed for the fix gives A +0.211% (t=3.72) and B +0.204% (t=2.21). Both clear the USEQ spread-and-impact floor (~0.05%). Leg B is weaker in the recent window (t~1.5-1.9 since 2020), so check its contribution separately in the full backtest. Worst single-leg loss is large at 95% notional with no stop (sandbox -$6.0k, about 6% of equity, in the March-2020 turn-of-month). - [critical] The code trades every leg one session late, so it measures a different holding window from the one the hypothesis names. USEQ runs with latency_ms=100 (venue_configs.py:392, runner.py:501-512). A market order submitted when a close-stamped daily bar is processed therefore fills at the NEXT daily bar's close, not the signal bar's close. I reran the sandbox config (2020-01 to 2026-09) and the trade ledger shows the shift directly. Feb 2020: G is computed on 02-27 (T-2) and Leg A should run 02-27 to 02-28 close, but the engine enters LONG on 02-28 at 296.26 and exits on 03-02. Leg B, which should run 02-28 to 03-03, is booked SHORT from 03-02 to 03-04. Every month repeats the pattern. The effective Leg A is therefore close(last session of m) to close(1st session of m+1), which is exactly the turn-of-month day, where SPY drifts up regardless of G. Leg B becomes sessions 1 to 3. I checked this offline on catalog data over the full 289 month-ends. The shifted schedule gives Leg A -0.133%/trade (t=-1.73) and Leg B +0.061% (t=0.67). The intended schedule gives A +0.236% (t=4.19) and B +0.216% (t=2.35). The shift explains the sandbox PF of 0.76, the -17% return and the 0.38 short win rate. It is not a parameterization or edge outcome: the hypothesis's windows are simply never traded. (line 186) — fix: Make each decision one session earlier, so that fills land on the hypothesis's dates under the engine's next-bar fill. (1) Compute G and submit the Leg A entry at k = n-2, which is the close of T-3. That is the hypothesis's own robustness variant ('signal day T-3, t=3.08'), and it replicates on catalog data at A +0.211% (t=3.72) and B +0.204% (t=2.21). (2) Submit the flip at k = n-1, so Leg A fills at close(n-1) and runs to close(n), and Leg B fills at close(n). (3) Submit the Leg B exit on session leg_b_sessions-1 of m+1, so the default fills at close(session 2). Handle leg_b_sessions=1 by ex - [warning] The Leg A to Leg B flip is submitted inside should_exit(): a close_position() and then a new market entry on the same bar. In the backtest both fill in the same event (the ledger books them as separate trades, which is correct). In a live or paper NETTING account, however, the reduce-only close and the opposite entry are in flight together. If the close is rejected or fills only partly, the entry nets against it. The base _close_pending guard does not cover the entry. (line 203) — fix: Acceptable for backtest. For paper, consider submitting a single order of size |old| + |new| in the flip direction, or check that the close has been accepted before entering. - [warning] Sizing is 95% of equity per leg at 1x leverage, in whole shares. There is no dynamic scalar and only one position at a time, so worst-case gross is 0.95x equity, well within the Reg-T 2x limit (L156 is satisfied). There is no per-trade stop, as the hypothesis specifies (1-2 session holds). The worst single-leg loss is therefore an unbounded gap move of about 5-10% of equity (sandbox largest loss -$6.0k on the March-2020 turn). This is not the compounding always-in shape of L148, because the book is flat about 84% of the time. (line 232) — fix: No change is required by the hypothesis. The analyst should read the worst-leg loss and the drawdown against the pre-registered 18.4%.

Outcome Summary

The month-end rebalancing effect showed up in-sample, mostly in 2003-2011, but a single-SPY daily calendar rule has not held since 2017; a new attempt needs a different form, such as MOC or intraday-close execution, futures, or a multi-asset panel.

The analyst abandoned it with failure code no_edge. Two gates that cannot be waived failed: walk-forward was flagged as overfit, and CPCV OOS Sharpe of 0.26 was below the 0.5 floor. The edge was also flat in the forward period, and 2018, 2019 and 2020 all lost money.

Trade SPY on daily bars (TLT used only as a signal) against the month-end rebalancing flow of balanced funds: short SPY into the last session of the month if SPY had outperformed TLT month-to-date (long if it had lagged), then take the opposite side for the first 2 sessions of the next month.

The initial backtest (1993-2023, 514 trades) returned 91.4% with an average trade return of 0.162%, a profit factor of 1.27 and an MTM Sharpe of 0.19. After optimization, CPCV OOS Sharpe was 0.26 and holdout Sharpe 0.12. On the realized trade ledger, trades in the holdout window (2017-10 to 2023-12, n=150) averaged -0.014% (t=-0.13), compared with +0.198% (t=2.88) before it.

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