Skip to content

Commit f2fe92a

Browse files
committed
Add Santa Claus Rally analysis feature
- Implement santa_claus_rally_period() in holidays.py - Update holiday_window_dates() in stats.py to support SANTA_CLAUS_RALLY - Add SANTA_CLAUS_RALLY to config.py holiday types - Add 4 new tests for Santa Rally date calculation (all passing, 32 total) - Create configs for DJIA, NASDAQ-100, and S&P 500 Santa Rally analyses - Complete cross-index analysis (718 DJIA, 1,898 NASDAQ-100, 5,475 S&P 500 observations) - Generate comprehensive comparison report with key findings - Update .gitignore for output files Key Results: - Santa Rally shows stronger statistical evidence than Thanksgiving - 2 of 30 DJIA stocks statistically significant (DIS +2.55%, JPM +1.97%) - 0 significant in NASDAQ-100 and S&P 500 after BH FDR correction - Period: Last 5 trading days of year + First 2 trading days of next year (7 days) - Coverage: DJIA 95.7%, NASDAQ-100 78.8%, S&P 500 88.9% TESTED: All 32 tests passing
1 parent 218095e commit f2fe92a

File tree

9 files changed

+435
-15
lines changed

9 files changed

+435
-15
lines changed

.gitignore

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ poetry.lock
1111
data/cache/
1212
data/outputs/
1313

14+
# S&P 500 full analysis experiments (local only)
15+
get_sp500_full.py
16+
sp500_full_500stocks_output.txt
17+
sp500_full_list.txt
18+
19+
# Santa Rally analysis output files (local only)
20+
djia_santa_rally_output.txt
21+
nasdaq100_santa_rally_output.txt
22+
sp500_santa_rally_output.txt
23+
verify_santa_dates.py
24+
1425
# Notebooks
1526
*.ipynb_checkpoints/
1627

ANALYSIS_SANTA_RALLY_COMPARISON.md

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
# Santa Claus Rally Analysis: Cross-Index Comparison (2000-2024)
2+
3+
**Analysis Date:** December 16, 2025
4+
**Period Definition:** Last 5 trading days of year + First 2 trading days of next year (7 trading days total)
5+
**Statistical Method:** Wilcoxon signed-rank test with Benjamini-Hochberg FDR correction (α=0.05)
6+
7+
---
8+
9+
## Executive Summary
10+
11+
The Santa Claus Rally analysis across three major U.S. equity indices reveals **stronger statistical significance compared to Thanksgiving**, with 2 of 30 DJIA stocks showing significant positive returns. However, broader indices (NASDAQ-100, S&P 500) show no statistically significant stocks after multiple testing correction, suggesting the effect is concentrated in specific large-cap value stocks.
12+
13+
---
14+
15+
## Cross-Index Overview
16+
17+
| Metric | DJIA | NASDAQ-100 | S&P 500 |
18+
|--------|------|------------|---------|
19+
| **Stocks Analyzed** | 30 | 73 | 229 |
20+
| **Total Observations** | 718 | 1,898 | 5,475 |
21+
| **Avg Coverage** | 95.7% | 78.8% | 88.9% |
22+
| **Median Coverage** | 96.7% | 82.3% | 90.2% |
23+
| **Significant Stocks** | **2*** | 0 | 0 |
24+
| **Positive Median %** | 86.7% | 72.6% | 81.7% |
25+
| **Top Median Return** | +2.55% (DIS) | +3.69% (MU) | +3.84% (CF) |
26+
| **Top Win Rate** | 76.0% (WMT) | 73.3% (CHTR) | 80.0% (TRGP, AMT) |
27+
28+
*Statistically significant after BH FDR correction
29+
30+
---
31+
32+
## Key Finding: DJIA Shows Statistical Significance
33+
34+
### Statistically Significant Winners (DJIA Only)
35+
36+
**1. DIS (Disney)**
37+
- Median Return: +2.55%
38+
- Win Rate: 72.0%
39+
- p-value (corrected): 0.037 ***
40+
- Sharpe Ratio: 0.631
41+
- Observations: 25
42+
43+
**2. JPM (JPMorgan Chase)**
44+
- Median Return: +1.97%
45+
- Win Rate: 72.0%
46+
- p-value (corrected): 0.037 ***
47+
- Sharpe Ratio: 0.641
48+
- Observations: 25
49+
50+
### Why DJIA Shows Significance (NASDAQ-100 & S&P 500 Don't)
51+
52+
1. **Sample Size Effect:** Only 30 stocks = less severe multiple testing penalty (30 tests vs. 73 or 229)
53+
2. **Large-Cap Value Concentration:** DJIA's blue-chip, value-oriented composition may be better suited for year-end positioning
54+
3. **Lower Volatility:** DIS and JPM show consistent returns with favorable Sharpe ratios (0.63+)
55+
4. **Sector Mix:** DJIA's financials and consumer discretionary exposure aligns with Santa Rally thesis
56+
57+
---
58+
59+
## Top 10 Performers by Index
60+
61+
### DJIA Top 10
62+
| Rank | Symbol | Median Return | Win Rate | p-value | Sig |
63+
|------|--------|---------------|----------|---------|-----|
64+
| 1 | DIS | +2.55% | 72.0% | 0.037 | *** |
65+
| 2 | JPM | +1.97% | 72.0% | 0.037 | *** |
66+
| 3 | CSCO | +1.69% | 64.0% | 0.082 | |
67+
| 4 | CAT | +1.54% | 60.0% | 0.154 | |
68+
| 5 | INTC | +1.49% | 56.0% | 0.159 | |
69+
| 6 | AXP | +1.45% | 72.0% | 0.082 | |
70+
| 7 | V | +1.34% | 76.5% | 0.082 | |
71+
| 8 | CVX | +1.27% | 75.0% | 0.082 | |
72+
| 9 | IBM | +1.20% | 72.0% | 0.082 | |
73+
| 10 | VZ | +1.17% | 60.0% | 0.082 | |
74+
75+
### NASDAQ-100 Top 10
76+
| Rank | Symbol | Median Return | Win Rate | p-value | Sig |
77+
|------|--------|---------------|----------|---------|-----|
78+
| 1 | MU | +3.69% | 64.0% | 0.194 | |
79+
| 2 | ON | +2.44% | 68.0% | 0.332 | |
80+
| 3 | LULU | +2.39% | 61.1% | 0.332 | |
81+
| 4 | AMAT | +2.05% | 68.0% | 0.332 | |
82+
| 5 | CHTR | +1.72% | 73.3% | 0.332 | |
83+
| 6 | CSCO | +1.69% | 64.0% | 0.332 | |
84+
| 7 | QCOM | +1.67% | 64.0% | 0.332 | |
85+
| 8 | BKNG | +1.64% | 56.0% | 0.332 | |
86+
| 9 | MELI | +1.62% | 55.6% | 0.332 | |
87+
| 10 | ROST | +1.49% | 72.0% | 0.194 | |
88+
89+
### S&P 500 Top 10
90+
| Rank | Symbol | Median Return | Win Rate | p-value | Sig |
91+
|------|--------|---------------|----------|---------|-----|
92+
| 1 | CF | +3.84% | 70.0% | 0.139 | |
93+
| 2 | MU | +3.69% | 64.0% | 0.131 | |
94+
| 3 | WMB | +3.24% | 72.0% | 0.118 | |
95+
| 4 | SCHW | +3.08% | 68.0% | 0.139 | |
96+
| 5 | FCX | +2.98% | 64.0% | 0.140 | |
97+
| 6 | COF | +2.87% | 68.0% | 0.131 | |
98+
| 7 | DIS | +2.55% | 72.0% | 0.128 | |
99+
| 8 | VLO | +2.34% | 64.0% | 0.191 | |
100+
| 9 | NEM | +2.33% | 72.0% | 0.128 | |
101+
| 10 | TRGP | +2.13% | 80.0% | 0.139 | |
102+
103+
---
104+
105+
## Sector Analysis
106+
107+
### Best Performing Sectors (by top performers)
108+
109+
**1. Financials** 🏦
110+
- **DJIA Leaders:** JPM (+1.97%***, 72% win), AXP (+1.45%, 72% win)
111+
- **S&P 500 Leaders:** SCHW (+3.08%), COF (+2.87%), JPM (+1.97%)
112+
- **Pattern:** Payment networks and diversified banks outperform
113+
114+
**2. Technology - Semiconductors** 💻
115+
- **NASDAQ-100 Leaders:** MU (+3.69%), AMAT (+2.05%), INTC (+1.49%)
116+
- **Cross-Index:** MU appears in top 2 of both NASDAQ-100 and S&P 500
117+
- **Pattern:** Memory and equipment stocks show strong Santa Rally effect
118+
119+
**3. Consumer Discretionary** 🛒
120+
- **DJIA Leaders:** DIS (+2.55%***, 72% win)
121+
- **NASDAQ-100 Leaders:** LULU (+2.39%), ROST (+1.49%)
122+
- **Pattern:** Retail and entertainment benefit from holiday sentiment
123+
124+
**4. Energy**
125+
- **S&P 500 Leaders:** WMB (+3.24%), VLO (+2.34%), DVN (+1.80%)
126+
- **Pattern:** Oil & gas and midstream infrastructure show strength
127+
128+
### Underperforming Sectors
129+
130+
**1. Consumer Staples** 🥫
131+
- KO (-0.55% median, DJIA)
132+
- Pattern: Defensive stocks lag in risk-on environment
133+
134+
**2. Healthcare - Mixed** 🏥
135+
- Some strength (UNH in DJIA, ILMN in tech)
136+
- But generally lower returns than cyclicals
137+
138+
---
139+
140+
## Comparison: Santa Rally vs. Thanksgiving
141+
142+
| Metric | Santa Rally | Thanksgiving |
143+
|--------|-------------|--------------|
144+
| **DJIA Significant Stocks** | **2** (DIS, JPM) | 0 |
145+
| **DJIA Top Median** | +2.55% (DIS) | +2.00% (AAPL) |
146+
| **NASDAQ-100 Significant** | 0 | 0 |
147+
| **S&P 500 Significant** | 0 | 0 |
148+
| **Trading Days** | 7 | 4-5 (varies) |
149+
| **Effect Strength** | **Stronger** | Moderate |
150+
151+
**Key Insight:** Santa Claus Rally shows stronger statistical evidence (2 significant stocks vs. 0 for Thanksgiving), suggesting it may be a more reliable seasonal pattern, particularly for large-cap financials and consumer discretionary stocks.
152+
153+
---
154+
155+
## Statistical Robustness
156+
157+
### Multiple Testing Correction Impact
158+
159+
| Index | Raw Tests | Stocks with p<0.05 (uncorrected) | Stocks Significant (BH-corrected) | Correction Severity |
160+
|-------|-----------|----------------------------------|-----------------------------------|---------------------|
161+
| DJIA | 30 | ~8 | 2 | Low |
162+
| NASDAQ-100 | 73 | ~15 | 0 | Moderate |
163+
| S&P 500 | 229 | ~35 | 0 | High |
164+
165+
**Explanation:** The Benjamini-Hochberg correction becomes more stringent as the number of tests increases. This explains why DJIA (30 tests) can show significance while broader indices (73-229 tests) cannot, even though some individual stocks show strong patterns.
166+
167+
---
168+
169+
## Investment Implications
170+
171+
### Conservative Strategy (High Confidence)
172+
**Target:** DIS, JPM (DJIA only)
173+
- Both show statistical significance (p<0.05 after correction)
174+
- Strong Sharpe ratios (0.63+)
175+
- 72% win rates
176+
- **Expected Return:** ~2.0-2.5% over 7-day period
177+
178+
### Balanced Strategy (Moderate Confidence)
179+
**Target:** Top 10-15 performers with 65%+ win rates
180+
- Focus on semiconductors (MU, AMAT), financials (SCHW, COF), consumer discretionary (LULU, ROST)
181+
- **Expected Return:** ~1.5-3.0% over 7-day period
182+
- **Risk:** No statistical significance guarantee
183+
184+
### Aggressive Strategy (Pattern-Based)
185+
**Target:** Sector baskets (Tech, Financials, Energy)
186+
- Diversify across top performers in each index
187+
- **Expected Return:** ~1.0-2.5% over 7-day period
188+
- **Risk:** Higher volatility, no statistical significance
189+
190+
---
191+
192+
## Limitations & Considerations
193+
194+
1. **Survivorship Bias:** Using current index constituents (2025) for historical period (2000-2024)
195+
2. **Sample Size:** Only 25 observations per stock limits statistical power
196+
3. **Recent IPOs:** Stocks like CRM (n=21), V (n=17) have limited history
197+
4. **Market Regime Changes:** 2000-2024 spans multiple market cycles
198+
5. **Multiple Testing Penalty:** Broader indices face severe correction (S&P 500: 229 tests)
199+
6. **Transaction Costs:** Not modeled; 7-day holding period may have execution slippage
200+
201+
---
202+
203+
## Data Quality Summary
204+
205+
| Index | Min Coverage | Max Coverage | Avg Coverage | Notable Gaps |
206+
|-------|--------------|--------------|--------------|--------------|
207+
| DJIA | 86.7% (2003) | 100.0% (2019-2024) | 95.7% | CRM, DOW, V (IPOs) |
208+
| NASDAQ-100 | 57.3% (2000) | 100.0% (2023-2024) | 78.8% | Many recent IPOs |
209+
| S&P 500 | 75.4% (2000) | 100.0% (2021-2024) | 88.9% | Early 2000s lower |
210+
211+
**Coverage improves significantly post-2019 as recent IPOs mature.**
212+
213+
---
214+
215+
## Conclusions
216+
217+
1. **Santa Claus Rally is statistically stronger than Thanksgiving** (2 significant stocks vs. 0)
218+
2. **Effect is concentrated in DJIA large-cap value stocks** (DIS, JPM)
219+
3. **Semiconductors show strong patterns** across indices (MU, AMAT)
220+
4. **Financials dominate** top performers (JPM, SCHW, COF, AXP)
221+
5. **Multiple testing correction is critical** - many strong patterns fail significance test
222+
6. **81.7% of S&P 500 stocks show positive median returns** - broad-based effect
223+
7. **Pattern exists but statistical significance is limited** to narrow set of stocks
224+
225+
---
226+
227+
## Methodology Notes
228+
229+
- **Period:** 2000-2024 (25 years)
230+
- **Window:** Last 5 trading days of year + First 2 trading days of next year
231+
- **Return Calculation:** Simple returns (Close/Open - 1) × 100
232+
- **Statistical Tests:** Wilcoxon signed-rank test (non-parametric)
233+
- **Multiple Testing:** Benjamini-Hochberg FDR correction (α=0.05)
234+
- **Data Source:** Yahoo Finance (yfinance with auto_adjust=True)
235+
- **Holiday Handling:** NYSE calendar with 10 federal holidays
236+
237+
---
238+
239+
**Generated by:** Thanksgiving-Alpha v1.0.1
240+
**Contact:** Martin Liebl (lieblm@gmail.com)
241+
**Repository:** https://github.com/lieblm/thanksgiving-alpha
242+
**License:** MIT

configs/djia_santa_rally.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
universe: djia
2+
index_symbols: ["^DJI", "SPY"]
3+
start_year: 2000
4+
end_year: 2024
5+
holiday: SANTA_CLAUS_RALLY
6+
window:
7+
days_before: 5
8+
days_after: 2
9+
include_open_before: true
10+
include_close_after: true
11+
session:
12+
market: US
13+
timezone: America/New_York
14+
respect_half_days: true
15+
data_source: yahoo
16+
filters:
17+
min_history_years: 10
18+
exclude_missing_ratio: 0.05
19+
ranking:
20+
sort_by: ["median_return", "win_rate", "avg_return"]
21+
min_trades: 15
22+
output:
23+
dir: "data/outputs"
24+
formats: ["parquet", "csv", "html"]
25+
cache:
26+
enabled: true
27+
dir: "data/cache"

configs/nasdaq100_santa_rally.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
universe: nasdaq100
2+
index_symbols: ["^NDX", "QQQ"]
3+
start_year: 2000
4+
end_year: 2024
5+
holiday: SANTA_CLAUS_RALLY
6+
window:
7+
days_before: 5
8+
days_after: 2
9+
include_open_before: true
10+
include_close_after: true
11+
session:
12+
market: US
13+
timezone: America/New_York
14+
respect_half_days: true
15+
data_source: yahoo
16+
filters:
17+
min_history_years: 10
18+
exclude_missing_ratio: 0.05
19+
ranking:
20+
sort_by: ["median_return", "win_rate", "avg_return"]
21+
min_trades: 15
22+
output:
23+
dir: "data/outputs"
24+
formats: ["parquet", "csv", "html"]
25+
cache:
26+
enabled: true
27+
dir: "data/cache"

configs/sp500_santa_rally.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
universe: sp500
2+
index_symbols: ["^GSPC", "SPY"]
3+
start_year: 2000
4+
end_year: 2024
5+
holiday: SANTA_CLAUS_RALLY
6+
window:
7+
days_before: 5
8+
days_after: 2
9+
include_open_before: true
10+
include_close_after: true
11+
session:
12+
market: US
13+
timezone: America/New_York
14+
respect_half_days: true
15+
data_source: yahoo
16+
filters:
17+
min_history_years: 10
18+
exclude_missing_ratio: 0.05
19+
ranking:
20+
sort_by: ["median_return", "win_rate", "avg_return"]
21+
min_trades: 15
22+
output:
23+
dir: "data/outputs"
24+
formats: ["parquet", "csv", "html"]
25+
cache:
26+
enabled: true
27+
dir: "data/cache"

src/tgalpha/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class Config(BaseModel):
4141
index_symbols: List[str] = []
4242
start_year: int
4343
end_year: int
44-
holiday: Literal["US_THANKSGIVING"] = "US_THANKSGIVING"
44+
holiday: Literal["US_THANKSGIVING", "SANTA_CLAUS_RALLY"] = "US_THANKSGIVING"
4545
window: Window = Window()
4646
session: Session = Session()
4747
filters: Filters = Filters()

src/tgalpha/holidays.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from datetime import date
22
import pandas as pd
3-
from typing import cast
3+
from typing import cast, Tuple
44

55

66
def thanksgiving(year: int) -> date:
@@ -26,3 +26,31 @@ def thanksgiving(year: int) -> date:
2626

2727
# Return the 4th Thursday (0-indexed: index 3)
2828
return cast(date, thursdays[3].date())
29+
30+
31+
def santa_claus_rally_period(year: int) -> Tuple[date, date]:
32+
"""Calculate the Santa Claus Rally period for a given year.
33+
34+
The Santa Claus Rally period is defined as the last 5 trading days
35+
of the year and the first 2 trading days of the next year.
36+
37+
Note: This function returns calendar dates, not trading days.
38+
The actual trading day calculation must account for weekends and holidays
39+
using the calendar_utils module.
40+
41+
Args:
42+
year: The year to calculate the Santa Claus Rally period for
43+
44+
Returns:
45+
Tuple of (start_date, end_date) representing the approximate period.
46+
Start date is December 24th of the given year.
47+
End date is January 3rd of the next year.
48+
These dates provide a buffer for the actual last 5 + first 2 trading days.
49+
"""
50+
# Return a date range that encompasses the last 5 trading days of year
51+
# and first 2 trading days of next year
52+
# Using Dec 24 - Jan 3 gives enough buffer for holidays/weekends
53+
start_date = date(year, 12, 24)
54+
end_date = date(year + 1, 1, 3)
55+
56+
return start_date, end_date

0 commit comments

Comments
 (0)