Files
etf/archive/legacy_tests/tests/experiments/ab_test_spx.py
aszerW 1fca536c95 refactor: 归档旧代码,保留新框架结构
归档内容:
- 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反映新框架架构
2026-05-11 23:34:23 +08:00

183 lines
5.8 KiB
Python
Raw Permalink 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.

"""
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组: 添加标普50012只")
# 对比结果
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()