技术修复: - 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作为美股大类代表 - 不添加同大类新标的(避免类内切换成本) - 新增标的应优先考虑新大类(增加跨类分散)
113 lines
4.1 KiB
Python
113 lines
4.1 KiB
Python
"""
|
|
持仓数量 (select_num) 敏感度测试
|
|
测试 select_num 分别为 1, 2, 3, 4, 5 时的策略表现
|
|
基于最终精选的 11 只标的池
|
|
"""
|
|
|
|
import sys
|
|
import pandas as pd
|
|
import numpy as np
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
import matplotlib.pyplot as plt
|
|
|
|
# 添加项目根目录
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
from strategies.rotation.engine import RotationStrategy
|
|
|
|
# ==================== 基础配置 ====================
|
|
FINAL_POOL = {
|
|
"399006.SZ": {"name": "创业板指", "market": "A", "etf": "159915.SZ"},
|
|
"H30269.CSI": {"name": "中证红利低波", "market": "A", "etf": "512890.SH"},
|
|
"000015.SH": {"name": "上证红利", "market": "A", "etf": "510880.SH"},
|
|
"NDX": {"name": "纳指100", "market": "US", "etf": "513100.SH"},
|
|
"N225": {"name": "日经225", "market": "JP", "etf": "513520.SH"},
|
|
"GDAXI": {"name": "德国DAX", "market": "EU", "etf": "513030.SH"},
|
|
"HSI": {"name": "恒生指数", "market": "HK", "etf": "159920.SZ"},
|
|
"HSTECH.HK": {"name": "恒生科技", "market": "HK", "etf": "513130.SH"},
|
|
"AU.SHF": {"name": "黄金", "market": "COMMODITY", "etf": "518880.SH"},
|
|
"CL.NYM": {"name": "原油", "market": "COMMODITY", "etf": "160723.SZ"},
|
|
"931862.CSI": {"name": "30年国债", "market": "BOND", "etf": "511090.SH"}
|
|
}
|
|
|
|
BASE_CONFIG = {
|
|
"start_date": "2019-01-01",
|
|
"end_date": datetime.now().strftime('%Y-%m-%d'),
|
|
"code_list": FINAL_POOL,
|
|
"factor_type": "weighted_momentum",
|
|
"auto_day": False, # 使用当前设定的固定窗口
|
|
"n_days": 25,
|
|
"diversified": True,
|
|
"rebalance_days": 1,
|
|
"rebalance_threshold": 0.0,
|
|
"trade_cost": 0.001,
|
|
"premium_control": {"enabled": True, "default_threshold": 0.10},
|
|
"use_cache": True,
|
|
"ssh_tunnel": {"enabled": True, "host": "8.218.167.69", "port": 22, "username": "root", "key_path": "hk_ecs.pem", "local_port": 1080}
|
|
}
|
|
|
|
def run_sensitivity_test():
|
|
test_values = [1, 2, 3, 4, 5]
|
|
results = []
|
|
|
|
for val in test_values:
|
|
print(f"\n测试 select_num = {val} ...")
|
|
cfg = BASE_CONFIG.copy()
|
|
cfg["select_num"] = val
|
|
|
|
strategy = RotationStrategy(cfg)
|
|
try:
|
|
res_df = strategy.run()
|
|
|
|
nav = res_df['轮动策略净值']
|
|
total_ret = nav.iloc[-1] - 1
|
|
days = (nav.index[-1] - nav.index[0]).days
|
|
cagr = (1 + total_ret)**(365.25/days) - 1
|
|
|
|
daily_ret = res_df['轮动策略日收益率']
|
|
sharpe = daily_ret.mean() / daily_ret.std() * np.sqrt(252) if daily_ret.std() > 0 else 0
|
|
|
|
peak = nav.cummax()
|
|
dd = (nav - peak) / peak
|
|
max_dd = dd.min()
|
|
|
|
results.append({
|
|
"select_num": val,
|
|
"total_ret": total_ret,
|
|
"cagr": cagr,
|
|
"max_dd": max_dd,
|
|
"sharpe": sharpe,
|
|
"nav": nav
|
|
})
|
|
except Exception as e:
|
|
print(f"测试失败 (select_num={val}): {e}")
|
|
|
|
# ==================== 汇总报告 ====================
|
|
print(f"\n\n{'='*90}")
|
|
print(f"{'持仓数量 (select_num) 敏感度测试报告':^90}")
|
|
print(f"{'='*90}")
|
|
print(f"{'持仓数':<10} | {'累计收益':>12} | {'年化(CAGR)':>12} | {'最大回撤':>12} | {'夏普比率':>10}")
|
|
print(f"{'-'*90}")
|
|
|
|
for r in results:
|
|
print(f"{r['select_num']:<10} | {r['total_ret']:>12.2%} | {r['cagr']:>12.2%} | {r['max_dd']:>12.2%} | {r['sharpe']:>10.2f}")
|
|
print(f"{'='*90}")
|
|
|
|
# ==================== 绘图 ====================
|
|
plt.figure(figsize=(14, 7))
|
|
for r in results:
|
|
plt.plot(r['nav'].index, r['nav'], label=f"select_num = {r['select_num']}")
|
|
|
|
plt.yscale('log')
|
|
plt.title("持仓数量对净值的影响 (select_num 1-5)", fontsize=14)
|
|
plt.legend()
|
|
plt.grid(True, alpha=0.3)
|
|
|
|
output_path = Path(__file__).parent.parent / "results" / "select_num_test.png"
|
|
plt.savefig(output_path)
|
|
print(f"\n对比图表已保存至: {output_path}")
|
|
|
|
if __name__ == "__main__":
|
|
run_sensitivity_test()
|