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:
66
research/v5_drawdown_trace.py
Normal file
66
research/v5_drawdown_trace.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""Trace where V3/V5 maximum drawdowns occur and what holdings they had."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from itertools import product
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from research.trend_rider_robustness import (
|
||||
load_price_panel,
|
||||
portfolio_returns,
|
||||
)
|
||||
from strategies.permanent import TrendRiderV3
|
||||
from strategies.trend_rider_v5 import TrendRiderV5
|
||||
|
||||
|
||||
def trace(name: str, weights: pd.DataFrame, prices: pd.DataFrame,
|
||||
start: str = "2015-01-02") -> None:
|
||||
rets = portfolio_returns(weights, prices[weights.columns], 0.001)
|
||||
rets = rets[rets.index >= start]
|
||||
eq = (1 + rets).cumprod()
|
||||
dd = eq / eq.cummax() - 1
|
||||
trough = dd.idxmin()
|
||||
peak = eq.loc[:trough].idxmax()
|
||||
recover = eq.loc[trough:][eq.loc[trough:] >= eq.loc[peak]]
|
||||
rec_dt = recover.index[0] if len(recover) else None
|
||||
|
||||
print(f"\n=== {name} ===")
|
||||
print(f" MDD = {dd.min()*100:.2f}%")
|
||||
print(f" Peak : {peak.date()} equity={eq.loc[peak]:.3f}")
|
||||
print(f" Trough: {trough.date()} equity={eq.loc[trough]:.3f}")
|
||||
print(f" Recovered: {rec_dt.date() if rec_dt is not None else 'NOT YET'}")
|
||||
print(f" Days to trough: {(trough - peak).days}")
|
||||
|
||||
# Show holdings around the drawdown
|
||||
print(f"\n Holdings 5 days before peak through 5 days after trough:")
|
||||
sl = weights.loc[peak - pd.Timedelta(days=10): trough + pd.Timedelta(days=10)]
|
||||
nonzero = (sl != 0).any(axis=0)
|
||||
sl = sl.loc[:, nonzero]
|
||||
sl_disp = sl.copy()
|
||||
# Show only days when holdings change
|
||||
changes = (sl_disp != sl_disp.shift(1)).any(axis=1)
|
||||
sl_disp = sl_disp.loc[changes]
|
||||
print(sl_disp.round(3).head(40).to_string())
|
||||
|
||||
|
||||
def main() -> None:
|
||||
prices = load_price_panel()
|
||||
print(f"Panel: {prices.index.min().date()} to {prices.index.max().date()}")
|
||||
|
||||
candidates = {
|
||||
"V3 default": TrendRiderV3(),
|
||||
"V5 default (panic 1.6/4%)": TrendRiderV5(),
|
||||
"V5 panic 1.8/5%": TrendRiderV5(panic_vol_ratio=1.8, panic_peak_drop_pct=0.05),
|
||||
}
|
||||
for name, strat in candidates.items():
|
||||
w = strat.generate_signals(prices)
|
||||
trace(name, w, prices)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user