- Reproduce historical results:ca933e4code 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 - Compareca933e4vs HEAD (cabfee2) across different start years - Add test_start_year_analysis.py for reproducibility
113 lines
3.8 KiB
Python
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()
|