归档内容: - core/ (数据源、因子计算、通用工具) → archive/legacy_core/ - strategies/rotation/engine.py, portfolio.py, report.py → archive/legacy_core/ - scripts/ (run_rotation, daily_scheduler) → archive/legacy_scripts/ - examples/ → archive/legacy_examples/ - tests/ (实验、对比测试) → archive/legacy_tests/ - 单独文件 (fetch_*.py, 动量.py, 全球市场.py等) → archive/single_files/ 保留新结构: - framework/ (抽象接口) - strategies/shared/ (定制组件) - strategies/rotation/strategy.py (新策略) - 外层配置: .env, .dockerignore, build-and-push.sh, hk_ecs.pem, README.md, requirements.txt - Docker相关: Dockerfile, Dockerfile_base, docker-compose.yml 更新README反映新框架架构
183 lines
5.8 KiB
Python
183 lines
5.8 KiB
Python
"""
|
||
A/B测试:添加标普500对轮动策略的影响
|
||
对比:
|
||
- A组(对照组):当前11只标的配置
|
||
- B组(实验组):添加标普500后的12只标的配置
|
||
"""
|
||
|
||
import sys
|
||
from pathlib import Path
|
||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||
|
||
from strategies.rotation.engine import RotationStrategy
|
||
import pandas as pd
|
||
|
||
|
||
def create_config_with_spx(base_config: dict) -> dict:
|
||
"""在基础配置上添加标普500"""
|
||
config = base_config.copy()
|
||
config['code_list'] = base_config['code_list'].copy()
|
||
|
||
# 添加标普500(美股大类内)
|
||
config['code_list']['SPX'] = {
|
||
'name': '标普500',
|
||
'etf': '513500.SH',
|
||
'market': 'US' # 与纳指100同属美股大类
|
||
}
|
||
|
||
return config
|
||
|
||
|
||
def run_backtest(config: dict, label: str) -> dict:
|
||
"""运行回测并返回关键指标"""
|
||
print(f"\n{'='*60}")
|
||
print(f" {label}")
|
||
print(f"{'='*60}")
|
||
|
||
strategy = RotationStrategy(config)
|
||
result = strategy.run() # result 是 DataFrame
|
||
|
||
if result is None or len(result) == 0:
|
||
return None
|
||
|
||
# 从 DataFrame 中直接计算指标
|
||
strategy_nav = result['轮动策略净值']
|
||
strategy_ret = result['轮动策略日收益率']
|
||
benchmark_nav = result['基准净值']
|
||
benchmark_ret = result['基准日收益率']
|
||
|
||
# 累计收益
|
||
total_return = strategy_nav.iloc[-1] - 1
|
||
|
||
# CAGR (交易日口径)
|
||
days = len(result)
|
||
years = days / 250
|
||
cagr = (strategy_nav.iloc[-1] ** (1/years)) - 1
|
||
|
||
# Sharpe
|
||
excess_ret = strategy_ret.mean() * 250 # 年化收益
|
||
vol = strategy_ret.std() * (250 ** 0.5) # 年化波动
|
||
sharpe = excess_ret / vol if vol > 0 else 0
|
||
|
||
# 最大回撤
|
||
rolling_max = strategy_nav.cummax()
|
||
drawdown = (strategy_nav - rolling_max) / rolling_max
|
||
max_dd = drawdown.min()
|
||
|
||
# Calmar
|
||
calmar = cagr / abs(max_dd) if max_dd < 0 else 0
|
||
|
||
# 日胜率
|
||
win_rate = (strategy_ret > 0).sum() / len(strategy_ret)
|
||
|
||
# 提取关键指标
|
||
metrics = {
|
||
'label': label,
|
||
'标的数': len(config['code_list']),
|
||
'累计收益': total_return,
|
||
'CAGR': cagr,
|
||
'Sharpe': sharpe,
|
||
'MaxDD': max_dd,
|
||
'Calmar': calmar,
|
||
'日胜率': win_rate,
|
||
}
|
||
|
||
print(f"\n标的池: {len(config['code_list'])}只")
|
||
print(f"累计收益: {metrics['累计收益']:.2%}")
|
||
print(f"CAGR: {metrics['CAGR']:.2%}")
|
||
print(f"Sharpe: {metrics['Sharpe']:.2f}")
|
||
print(f"MaxDD: {metrics['MaxDD']:.2%}")
|
||
print(f"Calmar: {metrics['Calmar']:.2f}")
|
||
print(f"日胜率: {metrics['日胜率']:.2%}")
|
||
|
||
return metrics
|
||
|
||
|
||
def compare_results(a_metrics: dict, b_metrics: dict):
|
||
"""对比两组结果"""
|
||
print(f"\n{'='*60}")
|
||
print(f" 对比结果")
|
||
print(f"{'='*60}")
|
||
|
||
print(f"\n{'指标':<12} {'A组(无SPX)':<15} {'B组(有SPX)':<15} {'差异':<15}")
|
||
print("-" * 60)
|
||
|
||
metrics_keys = ['标的数', '累计收益', 'CAGR', 'Sharpe', 'MaxDD', 'Calmar', '日胜率']
|
||
|
||
for key in metrics_keys:
|
||
a_val = a_metrics.get(key, 0)
|
||
b_val = b_metrics.get(key, 0)
|
||
|
||
if key == '标的数':
|
||
diff = b_val - a_val
|
||
diff_str = f"+{diff}" if diff > 0 else str(diff)
|
||
else:
|
||
diff = b_val - a_val
|
||
if key in ['累计收益', 'CAGR', 'MaxDD', '日胜率']:
|
||
diff_str = f"{diff*100:+.2f}%"
|
||
else:
|
||
diff_str = f"{diff:+.2f}"
|
||
|
||
if key in ['累计收益', 'CAGR', 'MaxDD', '日胜率']:
|
||
a_str = f"{a_val:.2%}"
|
||
b_str = f"{b_val:.2%}"
|
||
else:
|
||
a_str = str(a_val)
|
||
b_str = str(b_val)
|
||
|
||
print(f"{key:<12} {a_str:<15} {b_str:<15} {diff_str:<15}")
|
||
|
||
print("-" * 60)
|
||
|
||
# 分析美股大类内部切换情况
|
||
print(f"\n【关键发现】")
|
||
print(f"添加标普500后:")
|
||
print(f" - 美股大类从1只→2只(纳指100 + 标普500)")
|
||
print(f" - 类内竞争:纳指100 vs 标普500,得分高者代表美股大类")
|
||
print(f" - 跨类分散不变:美股大类还是只输出1只冠军进入Top3")
|
||
|
||
if b_metrics['累计收益'] != a_metrics['累计收益']:
|
||
print(f" - 累计收益变化:{a_metrics['累计收益']:.2%} → {b_metrics['累计收益']:.2%}")
|
||
|
||
|
||
def main():
|
||
"""主函数"""
|
||
import yaml
|
||
|
||
# 加载基础配置
|
||
config_path = Path(__file__).parent.parent / 'config' / 'strategies' / 'rotation.yaml'
|
||
with open(config_path, 'r') as f:
|
||
base_config = yaml.safe_load(f)
|
||
|
||
# 添加缺失的 end_date(使用今天日期)
|
||
from datetime import datetime
|
||
base_config['end_date'] = datetime.now().strftime('%Y-%m-%d')
|
||
|
||
print(f"\n{'='*60}")
|
||
print(f" A/B测试:添加标普500对diversified模式的影响")
|
||
print(f"{'='*60}")
|
||
print(f"\n测试假设:")
|
||
print(f" - diversified=true 模式下,每大类只选1只冠军")
|
||
print(f" - 添加标普500(同属美股大类)不会增加跨类分散")
|
||
print(f" - 但可能增加类内切换频率和换手率")
|
||
|
||
# A组:当前配置(11只,无标普500)
|
||
a_metrics = run_backtest(base_config, "A组: 当前配置(11只,无标普500)")
|
||
|
||
# B组:添加标普500后的配置(12只)
|
||
config_with_spx = create_config_with_spx(base_config)
|
||
b_metrics = run_backtest(config_with_spx, "B组: 添加标普500(12只)")
|
||
|
||
# 对比结果
|
||
if a_metrics and b_metrics:
|
||
compare_results(a_metrics, b_metrics)
|
||
|
||
# 保存对比结果
|
||
results_df = pd.DataFrame([a_metrics, b_metrics])
|
||
results_path = Path(__file__).parent.parent / 'results' / 'ab_test_spx.csv'
|
||
results_df.to_csv(results_path, index=False)
|
||
print(f"\n对比结果已保存: {results_path}")
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main() |