Files
etf/tests/utils/export_rotation_data.py
aszerW 6b59855c28 experiment(rotation): 同大类扩充与纳指vs标普替换对比实验
技术修复:
- SOCKS5代理IPv6问题:socks5:// → socks5h:// (hybrid_source.py, yfinance_source.py)

目录整理:
- scripts/ → 仅保留策略入口(daily_scheduler, run_rotation, run_cci_screener)
- 实验脚本移至 tests/experiments/
- 工具脚本移至 tests/utils/
- 实验记录新增 docs/experiments/
- results/ 添加到 gitignore

实验结果:

实验001 - 同大类扩充(添加标普500):
├─ 累计收益: 1467.35% → 1176.26% (-291%)
├─ CAGR: 48.10% → 43.82% (-4.28%)
├─ 调仓次数: 459 → 501 (+42次)
└─ 结论: 添加同大类标的不增加跨类分散,反而侵蚀收益

实验002 - 纳指vs标普替换对比:
├─ 累计收益: 1467.35% → 1118.77% (-348%)
├─ CAGR: 48.10% → 42.87% (-5.22%)
├─ Sharpe: 2.21 → 2.08 (-0.13)
├─ MaxDD: -17.33% → -15.14% (+2.18%)
└─ 结论: 纳指100优于标普500,成长风格更适合动量策略

策略建议:
- 保持纳指100作为美股大类代表
- 不添加同大类新标的(避免类内切换成本)
- 新增标的应优先考虑新大类(增加跨类分散)
2026-05-06 20:43:38 +08:00

138 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
导出轮动策略回测所用的原始数据到本地文件夹
导出内容:
1. index_data.csv - 指数价格数据(宽格式,用于因子计算)
2. etf_data.csv - ETF价格数据宽格式用于收益计算
3. etf_nav_data.csv - ETF净值数据宽格式用于溢价率计算
4. benchmark_data.csv - 基准数据
5. config_snapshot.yaml - 当时使用的策略配置快照
"""
import sys
import os
import time
import shutil
from datetime import datetime
from pathlib import Path
# 添加项目根目录
sys.path.insert(0, str(Path(__file__).parent.parent))
import yaml
import pandas as pd
from dotenv import load_dotenv
load_dotenv()
from strategies.rotation.engine import RotationStrategy
from config.settings import DEFAULT_BENCHMARK_CODE
def main():
# 加载配置
config_path = Path(__file__).parent.parent / "config" / "strategies" / "rotation.yaml"
with open(config_path, "r", encoding="utf-8") as f:
config = yaml.safe_load(f)
# 如果未设置 end_date默认使用今天
if not config.get("end_date"):
config["end_date"] = datetime.now().strftime("%Y-%m-%d")
start_date = config["start_date"]
end_date = config["end_date"]
print("=" * 60)
print(" 导出轮动策略回测数据")
print("=" * 60)
print(f" 回测区间: {start_date} ~ {end_date}")
print(f" 候选标的: {len(config.get('code_list', {}))}")
# 创建输出目录
export_dir = Path(__file__).parent.parent / "data" / "rotation_backtest_data"
export_dir.mkdir(parents=True, exist_ok=True)
print(f" 输出目录: {export_dir}")
# 创建策略实例(仅用于获取数据)
strategy = RotationStrategy(config)
# 获取数据
print("\n" + "=" * 60)
print("开始下载数据...")
print("=" * 60)
benchmark_code = config.get("benchmark", {}).get("code", DEFAULT_BENCHMARK_CODE)
code_config = config.get("code_list", {})
with strategy.data_source:
index_data, etf_data, etf_nav_data, benchmark_data, valid_codes = (
strategy.data_source.fetch_all(
code_config, benchmark_code, start_date, end_date
)
)
# 保存数据
print("\n" + "=" * 60)
print("保存数据到本地...")
print("=" * 60)
saved_files = []
# 1. 指数价格数据
if index_data is not None:
path = export_dir / "index_data.csv"
index_data.to_csv(path)
saved_files.append(("index_data.csv", index_data.shape, "指数价格(因子计算用)"))
print(f" ✓ index_data.csv: {index_data.shape[0]}× {index_data.shape[1]}")
# 2. ETF价格数据
if etf_data is not None:
path = export_dir / "etf_data.csv"
etf_data.to_csv(path)
saved_files.append(("etf_data.csv", etf_data.shape, "ETF价格收益计算用"))
print(f" ✓ etf_data.csv: {etf_data.shape[0]}× {etf_data.shape[1]}")
# 3. ETF净值数据
if etf_nav_data is not None:
path = export_dir / "etf_nav_data.csv"
etf_nav_data.to_csv(path)
saved_files.append(("etf_nav_data.csv", etf_nav_data.shape, "ETF净值溢价率计算用"))
print(f" ✓ etf_nav_data.csv: {etf_nav_data.shape[0]}× {etf_nav_data.shape[1]}")
# 4. 基准数据
if benchmark_data is not None:
path = export_dir / "benchmark_data.csv"
benchmark_data.to_csv(path)
saved_files.append(("benchmark_data.csv", benchmark_data.shape, "基准指数"))
print(f" ✓ benchmark_data.csv: {benchmark_data.shape[0]}")
# 5. 有效代码列表
codes_path = export_dir / "valid_codes.txt"
with open(codes_path, "w") as f:
for code in valid_codes:
name = code_config.get(code, {}).get("name", code)
etf = code_config.get(code, {}).get("etf", "")
market = code_config.get(code, {}).get("market", "")
f.write(f"{code}\t{name}\t{etf or '-'}\t{market}\n")
print(f" ✓ valid_codes.txt: {len(valid_codes)} 只有效标的")
# 6. 策略配置快照
config_snapshot_path = export_dir / "config_snapshot.yaml"
shutil.copy2(config_path, config_snapshot_path)
print(f" ✓ config_snapshot.yaml: 策略配置快照")
# 汇总
print("\n" + "=" * 60)
print("导出完成!")
print("=" * 60)
print(f" 目录: {export_dir}")
print(f" 文件数: {len(saved_files) + 2}")
print(f" 数据区间: {start_date} ~ {end_date}")
print(f" 有效标的: {len(valid_codes)}")
for fname, shape, desc in saved_files:
print(f" - {fname}: {shape} ({desc})")
if __name__ == "__main__":
main()