Holds SPY through the seasonally strong month-boundary window — the 5th-last trading day of the month to the 3rd trading day of the next.
Real internal-backtest statistics from the registry — Internal backtest · SPY daily bars (Yahoo Finance) · 401 monthly windows · 1993–2026.
CAGR and exposure are not published for the library yet — we show “—” rather than invent them. Max drawdown, best year and worst year are undefined for the pooled Momentum universe backtest. Win rate is a share of trades, average return is per trade, and holding period is in trading days.
Turn of the Month — average cumulative return across the held window
Trading days relative to month-end (ME = last trading day; entry T−4, exit T+3) · Average cumulative return (%). Empirical average path: the mean cumulative return at each held session, computed across all completed backtest windows on real SPY daily closes — measured from history, NOT illustrative and NOT externally published. Anchored at entry (T−4 = the 5th-last trading day); each point averages the windows that reached it (T−4…T+1 all 401, T+2 400, T+3 398 — three windows were holiday-shortened). It is labelled basis:"derived" only because the frozen v1 enum offers no "empirical" value, and "derived" (our own computation) is more honest here than "published" (which would imply an external source).
Decades of academic and practitioner research have found that a large fraction of the equity market's long-run gains has historically accrued in a narrow window around month-end — the last few trading days of one month plus the first few of the next — rather than being spread evenly across all sessions.
This strategy monetizes that window with one fully mechanical trade per month:
It is a market-timing strategy: entry and exit are fixed trading dates, not price levels. It recommends no individual stocks and defines no stop or target — its only risk control is the short, fixed holding period (about seven trading sessions) and being flat otherwise.
Recurring, calendar-clustered flow, not news or fundamentals: month-end institutional rebalancing, payroll-driven retirement contributions, dividend reinvestment, and fund "window dressing."
| Driver | Mechanism |
|---|---|
| Payroll & retirement inflows | 401(k)/index-fund contributions land around month-end/month-start and are mechanically invested, lifting the broad index. |
| Institutional rebalancing | Funds rebalance to target weights at month-end, concentrating buy pressure into a few sessions. |
| Dividend reinvestment | A large share of dividends is paid and reinvested near month boundaries. |
| Window dressing | Managers buy winners into the reporting date to improve month-end statements. |
Documented by Lakonishok & Smidt (1988) and revisited by McConnell & Xu (2008), who found that historically the entire market risk premium was earned in the turn-of-month window. It is a tendency, not a guarantee, and has weakened as it became widely known.
The rules are unchanged from the original strategy; a pure, date-driven core (turnOfMonthSignalFromBars) now re-expresses the SAME calendar anchors as an as-of signal so the backtest and the live strategy can never drift.
The 5th-to-last trading day of the month —
nthLastTradingDay(year, month, 5).
Implementation: at the latest bar, entry fires on the first session on/after that anchor (previous bar before it, current bar on/after it). Because it keys off the trading calendar (weekends + NYSE holidays skipped), it lands on the correct session every month and fires exactly once. If the anchor day were an unscheduled closure, the next open session fires instead.
The 3rd trading day of the following month —
nthTradingDay(nextYear, nextMonth, 3).
Implementation: the exit anchor is "the 3rd trading day of the month," detected the same way. A position opened on the prior month's 5th-last day is held until that session; the engine turns the standalone exit signal into a round-trip.
entryPrice/stopLoss/firstTarget/riskPercent/rewardRiskRatio are reported as null (never invented).Deterministic, identical in construction to Santa Claus Rally and Russell Rebalancing — it uses the shared seasonalConfidence model. confidenceScore is a setup-quality measure (0–100), not a probability of profit.
| Component | Max | Awarded |
|---|---|---|
seasonalWindow | 60 | 60 when the window is Active (today is inside it), else 0. |
windowProximity | 40 | Ramps linearly over the 10 calendar days before entry: 40 × (10 − daysUntilEntry) / 10, clamped to [0, 40]. 0 when far away or already finished. |
Bands (library-wide): <70 Weak · 70–79 Good · 80–89 Strong · 90–100 Exceptional.
The API returns ["Turn-of-month seasonal window is active"] while the window is Active, else []. Operationally:
| Dimension | Ideal |
|---|---|
| Market | Broad US equities (SPY), normal-to-rising tape where systematic month-end/month-start inflows dominate short-term pricing. |
| Trader | Passive, rules-driven, systematic — someone who will take every monthly window without second-guessing and treat it as a small, repeatable overlay. |
| Timeframe | A recurring ~7-session window each month; a calendar overlay, not a standalone book. |
| Avoid when | A major macro event lands in the window (FOMC, crisis, liquidity shock); in fast bear markets where flows are swamped by news; or when trading costs/slippage on a monthly cadence erode the ~0.6% edge. |
basis enum gap.** The historicalPattern.basis field is "published" or "derived"; there is no "empirical" value in the frozen v1 enum. The pattern is genuinely empirical (computed from our own completed backtest windows), so it is labelled "derived" with the real provenance spelled out in the note.historical.* and the pattern are baked as of the 2026 windows; they drift as future months complete. The live /api/backtest always recomputes from current data.Real responses from the live endpoints for this strategy.
GET /api/strategies?id=turn_of_month{
"strategies": [
{
"id": "turn_of_month",
"name": "Turn of the Month",
"category": "Seasonality",
"asset": "SPY",
"description": "WHAT IT IS: The Turn of the Month (TOM) effect is a long-standing seasonal tendency…",
"historical": {
"yearsTested": 34,
"winRate": 0.6259,
"averageReturn": 0.61,
"bestYear": 27.38,
"worstYear": -11.34,
"maxDrawdown": 20.32,
"averageHoldingPeriod": 7,
"profitFactor": 1.85,
"totalTrades": 401,
"source": "Internal backtest · SPY daily bars (Yahoo Finance) · 401 monthly windows · 1993–2026"
},
"historicalPattern": {
"title": "Turn of the Month — average cumulative return across the held window",
"xLabel": "Trading days relative to month-end (ME = last trading day; entry T−4, exit T+3)",
"yLabel": "Average cumulative return (%)",
"basis": "derived",
"source": "Internal backtest — 401 completed SPY windows (Yahoo Finance daily closes), 1993–2026",
"note": "Empirical average path computed across all completed backtest windows on real SPY daily closes… labelled basis:\"derived\" only because the frozen v1 enum offers no \"empirical\" value.",
"series": [
{ "day": -4, "label": "T−4 (Entry)", "cumulativeReturn": 0.0 },
{ "day": -3, "label": "T−3", "cumulativeReturn": 0.14 },
{ "day": -2, "label": "T−2", "cumulativeReturn": 0.21 },
{ "day": -1, "label": "T−1", "cumulativeReturn": 0.31 },
{ "day": 0, "label": "ME", "cumulativeReturn": 0.27 },
{ "day": 1, "label": "T+1", "cumulativeReturn": 0.47 },
{ "day": 2, "label": "T+2", "cumulativeReturn": 0.55 },
{ "day": 3, "label": "T+3 (Exit)", "cumulativeReturn": 0.61 }
]
},
"status": "Active",
"entryDate": "2024-01-25",
"exitDate": "2024-02-05",
"daysUntilEntry": 0,
"entryPrice": null,
"stopLoss": null,
"firstTarget": null,
"riskPercent": null,
"rewardRiskRatio": null,
"confidenceScore": 100,
"confidenceBreakdown": { "seasonalWindow": 60, "windowProximity": 40 },
"explanation": "SPY is inside its Turn of the Month window (entered 2024-01-25, exits 2024-02-05) — the seasonally strong month-boundary period, rating 100/100 as a setup-quality measure (not a probability of profit). Entry is at the close of 2024-01-25; the strategy sets no price stop or target and exits on the fixed date 2024-02-05.",
"checklist": ["Turn-of-month seasonal window is active"],
"score": 70,
"recommended": true,
"weight": "medium"
}
]
}GET /api/scan?strategy=turn_of_monthThis is a market-wide calendar strategy, not a per-symbol screen, so /api/scan?strategy=turn_of_month returns { "supported": false } with a clear message.
GET /api/backtest?strategy=turn_of_month{
"strategy": "turn_of_month",
"symbol": "SPY",
"range": { "requestedFrom": null, "requestedTo": null, "testedFrom": "1993-01-29", "testedTo": "2026-08-04", "barsTested": 8435 },
"metrics": {
"totalTrades": 401,
"winningTrades": 251,
"losingTrades": 149,
"winRate": 0.6259,
"averageReturn": 0.0061,
"medianReturn": 0.0079,
"bestTrade": 0.1458,
"worstTrade": -0.0999,
"averageHoldingPeriod": 6.99,
"profitFactor": 1.8503,
"totalReturn": 8.7385,
"maxDrawdown": 0.2032
},
"sampleTrades": [
{ "entryDate": "2026-06-24", "exitDate": "2026-07-06", "entryPrice": 296.69, "exitPrice": 303.99, "return": 0.0246, "holdingDays": 7 }
],
"openTradeEntryDate": null
}lib/market-data/prices.ts).*Past behaviour does not ensure future results. confidenceScore measures setup quality, not the probability of profit.*