讨论背景: - 159980.SZ(有色ETF)是商品型基金,跟踪上期所有色金属期货价格指数 - 应使用期货价格(CU.SHF沪铜)作为信号源,与黄金(AU.SHF)/原油(CL.NYM)保持一致 - 上证红利(000015.SH)与中证红利低波(H30269.CSI)高度相关,同属A股大类 在diversified模式下只能选1个,保留两个无实际意义 配置变更: - rotation.yaml: 新增CU.SHF→159980.SZ映射(market=COMMODITY) - rotation.yaml: 移除000015.SH上证红利(与红利低波冗余) - hybrid_source.py: FUTURES_CODE_MAP新增CU.SHF铜期货 - ab_test_iterations.py: 同步更新有色market为COMMODITY 实证结果 - CU.SHF加入前后对比(11只池,2019-2026): 无CU(11只): CAGR=47.37%, Sharpe=2.25, MaxDD=-17.86%, Calmar=2.65 含CU(12只): CAGR=46.16%, Sharpe=2.21, MaxDD=-17.86%, Calmar=2.58 影响: CAGR-1.2%, 商品大类内部竞争加剧(黄金/原油/有色三选一) 2020/2022铜价暴涨时有色贡献额外收益,整体影响很小 实证结果 - 移除上证红利后(11只,2019-2026): 含上证红利: CAGR=46.16%, Sharpe=2.21, MaxDD=-17.86%, Calmar=2.58 移除后: CAGR=46.42%, Sharpe=2.22, MaxDD=-17.33%, Calmar=2.68 所有指标均改善,消除冗余标的提升选择效率 实证结果 - diversified=true vs false(11只,select_num=3): true(跨类分散): CAGR=46.45%, Sharpe=2.22, MaxDD=-17.33%, Calmar=2.68 false(纯Top3): CAGR=44.19%, Sharpe=2.13, MaxDD=-18.12%, Calmar=2.44 关键差异在2022年(+17.63%): false模式选3只商品同时回调 结论: diversified=true全面优于false,保持当前配置 最终候选池(11只,7大类): A股: 创业板(399006.SZ), 红利低波(H30269.CSI) 美股: 纳指100(NDX) | 日本: 日经225(N225) | 欧洲: 德国DAX(GDAXI) 港股: 恒生指数(HSI), 恒生科技(HSTECH.HK) 商品: 黄金(AU.SHF), 原油(CL.NYM), 有色金属(CU.SHF) 固收: 30年国债(931862.CSI)
183 lines
6.8 KiB
Python
183 lines
6.8 KiB
Python
"""
|
|
策略迭代 A/B 对比实验脚本
|
|
量化三个维度的改进贡献度:
|
|
1. 标的池: 原始全市场池 vs. 精选11只核心池
|
|
2. 评分公式: 简单斜率(slope_r2) vs. 年化收益率*R2 (weighted_momentum)
|
|
3. 观察窗口: 固定25日窗口 vs. 动态ATR窗口 (20-60天)
|
|
"""
|
|
|
|
import sys
|
|
import pandas as pd
|
|
import numpy as np
|
|
from pathlib import Path
|
|
from datetime import datetime
|
|
|
|
# 添加项目根目录
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
from strategies.rotation.engine import RotationStrategy
|
|
import matplotlib.pyplot as plt
|
|
|
|
# ==================== 标的池定义 ====================
|
|
ORIGINAL_POOL = {
|
|
"000300.SH": {"name": "沪深300", "market": "A", "etf": "510300.SH"},
|
|
"000905.SH": {"name": "中证500", "market": "A", "etf": "510500.SH"},
|
|
"000852.SH": {"name": "中证1000", "market": "A", "etf": "512100.SH"},
|
|
"399006.SZ": {"name": "创业板指", "market": "A", "etf": "159915.SZ"},
|
|
"000015.SH": {"name": "上证红利", "market": "A", "etf": "510880.SH"},
|
|
"399986.SZ": {"name": "中证银行", "market": "A", "etf": "516310.SH"},
|
|
"399997.SZ": {"name": "中证白酒", "market": "A", "etf": "512690.SH"},
|
|
"399989.SZ": {"name": "中证医疗", "market": "A", "etf": "512170.SH"},
|
|
"399395.SZ": {"name": "国证有色", "market": "COMMODITY", "etf": "159880.SZ"},
|
|
"399998.SZ": {"name": "中证煤炭", "market": "A", "etf": "515220.SH"},
|
|
"399967.SZ": {"name": "中证军工", "market": "A", "etf": "512660.SH"},
|
|
"HSTECH.HK": {"name": "恒生科技", "market": "HK", "etf": "513180.SH"},
|
|
"NDX": {"name": "纳指100", "market": "US", "etf": "513100.SH"},
|
|
"AU.SHF": {"name": "黄金", "market": "COMMODITY", "etf": "518880.SH"}
|
|
}
|
|
|
|
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"}
|
|
}
|
|
|
|
# ==================== 实验配置 ====================
|
|
ITERATIONS = [
|
|
{
|
|
"label": "1. 原始基准 (原始池+简单评分+固定窗口)",
|
|
"config": {
|
|
"code_list": ORIGINAL_POOL,
|
|
"factor_type": "slope_r2",
|
|
"auto_day": False,
|
|
"n_days": 25,
|
|
"diversified": False
|
|
}
|
|
},
|
|
{
|
|
"label": "2. 标的池优化 (精选池+简单评分+固定窗口)",
|
|
"config": {
|
|
"code_list": FINAL_POOL,
|
|
"factor_type": "slope_r2",
|
|
"auto_day": False,
|
|
"n_days": 25,
|
|
"diversified": True # 开启跨大类分散
|
|
}
|
|
},
|
|
{
|
|
"label": "3. 评分公式优化 (精选池+加权评分+固定窗口)",
|
|
"config": {
|
|
"code_list": FINAL_POOL,
|
|
"factor_type": "weighted_momentum",
|
|
"auto_day": False,
|
|
"n_days": 25,
|
|
"diversified": True
|
|
}
|
|
},
|
|
{
|
|
"label": "4. 终极版本 (精选池+加权评分+动态窗口)",
|
|
"config": {
|
|
"code_list": FINAL_POOL,
|
|
"factor_type": "weighted_momentum",
|
|
"auto_day": True,
|
|
"n_days": 25, # 提供默认窗口作为 fallback
|
|
"min_days": 20,
|
|
"max_days": 60,
|
|
"diversified": True
|
|
}
|
|
}
|
|
]
|
|
|
|
COMMON_CONFIG = {
|
|
"start_date": "2019-01-01",
|
|
"end_date": datetime.now().strftime('%Y-%m-%d'),
|
|
"select_num": 3,
|
|
"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_experiment():
|
|
results = []
|
|
|
|
for i, item in enumerate(ITERATIONS):
|
|
print(f"\n{'='*80}")
|
|
print(f"运行实验 {item['label']}")
|
|
print(f"{'='*80}")
|
|
|
|
cfg = COMMON_CONFIG.copy()
|
|
cfg.update(item['config'])
|
|
|
|
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({
|
|
"label": item['label'],
|
|
"total_ret": total_ret,
|
|
"cagr": cagr,
|
|
"max_dd": max_dd,
|
|
"sharpe": sharpe,
|
|
"nav": nav
|
|
})
|
|
print(f"完成: CAGR={cagr:.2%}, MaxDD={max_dd:.2%}, Sharpe={sharpe:.2f}")
|
|
except Exception as e:
|
|
print(f"实验失败: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
# ==================== 汇总报告 ====================
|
|
print(f"\n\n{'='*100}")
|
|
print(f"{'策略迭代对比报告':^100}")
|
|
print(f"{'='*100}")
|
|
print(f"{'版本':<40} | {'累计收益':>10} | {'年化(CAGR)':>10} | {'最大回撤':>10} | {'夏普比率':>8} | {'贡献增量':>10}")
|
|
print(f"{'-'*100}")
|
|
|
|
prev_cagr = 0
|
|
for i, r in enumerate(results):
|
|
delta = f"+{(r['cagr'] - prev_cagr)*100:>.2f}%" if i > 0 else "-"
|
|
print(f"{r['label']:<40} | {r['total_ret']:>10.2%} | {r['cagr']:>10.2%} | {r['max_dd']:>10.2%} | {r['sharpe']:>8.2f} | {delta:>10}")
|
|
prev_cagr = r['cagr']
|
|
print(f"{'='*100}")
|
|
|
|
# ==================== 绘图 ====================
|
|
plt.figure(figsize=(15, 8))
|
|
for r in results:
|
|
plt.plot(r['nav'].index, r['nav'], label=r['label'], linewidth=1.5)
|
|
|
|
plt.yscale('log')
|
|
plt.title("策略迭代 A/B 对比 - 净值曲线 (对数坐标)", fontsize=14)
|
|
plt.legend()
|
|
plt.grid(True, alpha=0.3)
|
|
|
|
output_path = Path(__file__).parent.parent / "results" / "ab_test_iterations.png"
|
|
plt.savefig(output_path)
|
|
print(f"\n对比图表已保存至: {output_path}")
|
|
|
|
if __name__ == "__main__":
|
|
run_experiment()
|