Files
etf/rotation/test_start_year_analysis.py
aszerW 09ecac9e56 docs(experiments): add experiment 010 - start year sensitivity analysis
- Reproduce historical results: ca933e4 code achieves 43.20% annual return
- Attribution analysis: crash filter simplification (+4pp) + data extension (+2pp)
- Start year traversal: 2020-2025, all years show 34-57% annual return
- Compare ca933e4 vs HEAD (cabfee2) across different start years
- Add test_start_year_analysis.py for reproducibility
2026-06-17 23:24:17 +08:00

113 lines
3.8 KiB
Python

"""
Test different start years with select_num=1
"""
import os
import sys
import yaml
from pathlib import Path
from datetime import datetime
PROJECT_ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(PROJECT_ROOT))
from dotenv import load_dotenv
load_dotenv(PROJECT_ROOT / '.env')
from rotation.config_loader import load_rotation_config
from rotation.simple_rotation import SimpleRotationStrategy
def run_test(start_date: str, select_num: int) -> dict:
"""Run backtest with specified start date and select_num."""
config_path = PROJECT_ROOT / 'rotation' / 'config_simple.yaml'
with open(config_path, 'r') as f:
config = yaml.safe_load(f)
config['backtest']['start_date'] = start_date
config['rotation']['select_num'] = select_num
temp_config_path = PROJECT_ROOT / 'rotation' / 'temp_config.yaml'
with open(temp_config_path, 'w') as f:
yaml.dump(config, f)
try:
strategy = SimpleRotationStrategy(str(temp_config_path))
result = strategy.run()
return result['metrics']
finally:
if temp_config_path.exists():
temp_config_path.unlink()
def main():
select_num = 1
years = [2020, 2021, 2022, 2023, 2024, 2025]
print(f"\n{'='*80}")
print(f"Testing select_num={select_num} with different start years")
print(f"{'='*80}")
results = []
for year in years:
start_date = f"{year}-01-01"
print(f"\nTesting start_date={start_date}...")
try:
metrics = run_test(start_date, select_num)
results.append({
'start_year': year,
'start_date': start_date,
'select_num': select_num,
'total_return': metrics.get('total_return', 0),
'annual_return': metrics.get('annual_return', 0),
'max_drawdown': metrics.get('max_drawdown', 0),
'sharpe_ratio': metrics.get('sharpe_ratio', 0),
'rebalance_count': metrics.get('rebalance_count', 0),
'win_rate': metrics.get('win_rate', 0),
})
print(f" Total Return: {metrics.get('total_return', 0)*100:.2f}%")
print(f" Annual Return: {metrics.get('annual_return', 0)*100:.2f}%")
print(f" Max Drawdown: {metrics.get('max_drawdown', 0)*100:.2f}%")
print(f" Sharpe Ratio: {metrics.get('sharpe_ratio', 0):.3f}")
print(f" Rebalance Count: {metrics.get('rebalance_count', 0)}")
except Exception as e:
print(f" Error: {e}")
results.append({
'start_year': year,
'start_date': start_date,
'select_num': select_num,
'error': str(e)
})
# Print summary table
print(f"\n{'='*80}")
print(f"SUMMARY TABLE (select_num={select_num})")
print(f"{'='*80}")
print(f"{'Start Year':<12} {'Total Return':<15} {'Annual Return':<15} {'Max Drawdown':<15} {'Sharpe':<10} {'Rebal':<8}")
print(f"{'-'*80}")
for r in results:
if 'error' in r:
print(f"{r['start_year']:<12} {'ERROR':<15}")
else:
print(f"{r['start_year']:<12} {r['total_return']*100:>13.2f}% {r['annual_return']*100:>13.2f}% {r['max_drawdown']*100:>13.2f}% {r['sharpe_ratio']:>9.3f} {r['rebalance_count']:>7}")
# Save results to YAML
output_path = PROJECT_ROOT / 'rotation' / 'results' / 'start_year_analysis.yaml'
output_path.parent.mkdir(exist_ok=True)
with open(output_path, 'w') as f:
yaml.dump({
'select_num': select_num,
'test_date': datetime.now().isoformat(),
'results': results
}, f, default_flow_style=False)
print(f"\nResults saved to: {output_path}")
if __name__ == '__main__':
main()