research: add strategy evaluation and exploration scripts
Add 28 research scripts covering DCA simulation, momentum evaluation, Sharpe optimization, trend rider analysis, and US fundamentals exploration.
This commit is contained in:
114
research/dca_simulation.py
Normal file
114
research/dca_simulation.py
Normal file
@@ -0,0 +1,114 @@
|
||||
"""
|
||||
DCA simulation: $10,000 initial + $5,000 every Feb & Aug from 2017.
|
||||
Uses SharpeBoostedEnsembleStrategy daily returns.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
import os, sys
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from strategies.ensemble_alpha import SharpeBoostedEnsembleStrategy
|
||||
import data_manager
|
||||
from universe import get_sp500
|
||||
|
||||
|
||||
def main():
|
||||
# Load data and generate daily returns
|
||||
tickers = get_sp500()
|
||||
data_manager.update("us", tickers)
|
||||
data = data_manager.load("us")
|
||||
|
||||
strat = SharpeBoostedEnsembleStrategy()
|
||||
weights = strat.generate_signals(data)
|
||||
daily_rets = (weights * data.pct_change().fillna(0.0)).sum(axis=1)
|
||||
|
||||
# Also compute SPY buy-and-hold for comparison
|
||||
spy_rets = data["SPY"].pct_change().fillna(0.0)
|
||||
|
||||
# Trim to evaluation period
|
||||
start = "2016-04-01"
|
||||
end = "2026-05-13"
|
||||
daily_rets = daily_rets.loc[start:end]
|
||||
spy_rets = spy_rets.loc[start:end]
|
||||
|
||||
# --- DCA simulation ---
|
||||
# Initial: $10,000 at start
|
||||
# Contributions: $5,000 on first trading day of Feb and Aug, starting 2017
|
||||
|
||||
# Find contribution dates (first trading day of each Feb and Aug from 2017)
|
||||
contrib_dates = []
|
||||
for year in range(2017, 2027):
|
||||
for month in [2, 8]:
|
||||
target = pd.Timestamp(f"{year}-{month:02d}-01")
|
||||
# Find first trading day on or after target
|
||||
mask = daily_rets.index >= target
|
||||
if mask.any():
|
||||
contrib_dates.append(daily_rets.index[mask][0])
|
||||
|
||||
# Filter to only dates within our data range
|
||||
contrib_dates = [d for d in contrib_dates if d <= daily_rets.index[-1]]
|
||||
|
||||
print("=" * 70)
|
||||
print("DCA SIMULATION: SharpeBoostedEnsembleStrategy")
|
||||
print("=" * 70)
|
||||
print(f"Initial investment: $10,000 on {daily_rets.index[0].strftime('%Y-%m-%d')}")
|
||||
print(f"Contributions: $5,000 on first trading day of Feb & Aug (from 2017)")
|
||||
print(f"End date: {daily_rets.index[-1].strftime('%Y-%m-%d')}")
|
||||
print(f"Total contribution dates: {len(contrib_dates)}")
|
||||
print()
|
||||
|
||||
# Simulate for both strategy and SPY
|
||||
for label, rets in [("Strategy", daily_rets), ("SPY (Buy & Hold)", spy_rets)]:
|
||||
portfolio_value = 10000.0
|
||||
total_contributed = 10000.0
|
||||
contrib_idx = 0
|
||||
|
||||
# Track milestones
|
||||
yearly_values = {}
|
||||
|
||||
for i, date in enumerate(rets.index):
|
||||
# Apply daily return
|
||||
portfolio_value *= (1 + rets.iloc[i])
|
||||
|
||||
# Check if today is a contribution date
|
||||
if contrib_idx < len(contrib_dates) and date >= contrib_dates[contrib_idx]:
|
||||
portfolio_value += 5000.0
|
||||
total_contributed += 5000.0
|
||||
contrib_idx += 1
|
||||
|
||||
# Record year-end values
|
||||
if i == len(rets.index) - 1 or rets.index[i].year != rets.index[i + 1].year if i < len(rets.index) - 1 else True:
|
||||
yearly_values[date.year] = portfolio_value
|
||||
|
||||
profit = portfolio_value - total_contributed
|
||||
roi = profit / total_contributed * 100
|
||||
|
||||
print(f"--- {label} ---")
|
||||
print(f" Total contributed: ${total_contributed:,.0f}")
|
||||
print(f" Final portfolio: ${portfolio_value:,.0f}")
|
||||
print(f" Total profit: ${profit:,.0f}")
|
||||
print(f" ROI on contributions: {roi:.1f}%")
|
||||
print(f" Multiple on capital: {portfolio_value/total_contributed:.2f}x")
|
||||
print()
|
||||
|
||||
# Year-end snapshots
|
||||
print(f" Year-end portfolio values:")
|
||||
for year, val in sorted(yearly_values.items()):
|
||||
# How much contributed by that year
|
||||
contribs_by_year = 10000 + 5000 * len([d for d in contrib_dates if d.year <= year])
|
||||
print(f" {year}: ${val:>12,.0f} (contributed: ${contribs_by_year:>8,.0f}, "
|
||||
f"gain: ${val - contribs_by_year:>+10,.0f})")
|
||||
print()
|
||||
|
||||
# --- Monthly detail of contributions ---
|
||||
print("--- Contribution schedule ---")
|
||||
for i, d in enumerate(contrib_dates):
|
||||
print(f" {i+1:2d}. {d.strftime('%Y-%m-%d')} (${5000:,})")
|
||||
print(f" Total contributions (excl. initial): ${5000 * len(contrib_dates):,}")
|
||||
print(f" Total capital deployed: ${10000 + 5000 * len(contrib_dates):,}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user