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反映新框架架构
This commit is contained in:
2026-05-11 23:34:23 +08:00
parent f663d51b87
commit 1fca536c95
61 changed files with 221 additions and 159 deletions

View File

@@ -0,0 +1,208 @@
"""
A/B测试添加东南亚科技ETF的影响受限测试
对比:
- A组对照组当前配置无东南亚
- B组实验组添加东南亚科技ETF
限制说明:
- 东南亚科技ETF513730.SH2023年12月上市数据仅约2年
- 新交所泛东南亚科技指数在YFinance中暂无数据
- 本次测试使用ETF价格作为信号源非最佳实践仅作参考
- 回测时间范围将被缩短
核心问题新兴市场ETF流动性是否优于印度LOF
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from strategies.rotation.engine import RotationStrategy
import pandas as pd
import yaml
def create_config_with_sea(base_config: dict) -> dict:
"""在基础配置上添加东南亚科技"""
config = base_config.copy()
config['code_list'] = base_config['code_list'].copy()
# 添加东南亚科技(新大类)
# 注意由于指数数据不可用使用ETF价格作为信号源
# 513730.SH 同时作为指数代码和ETF代码
config['code_list']['513730.SH'] = {
'name': '东南亚科技',
'etf': '513730.SH', # 华泰柏瑞东南亚科技ETF
'market': 'SEA' # 东南亚大类
}
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()
if result is None or len(result) == 0:
return None
# 计算指标
strategy_nav = result['轮动策略净值']
strategy_ret = result['轮动策略日收益率']
total_return = strategy_nav.iloc[-1] - 1
days = len(result)
years = days / 250
cagr = (strategy_nav.iloc[-1] ** (1/years)) - 1 if years > 0 else 0
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 = cagr / abs(max_dd) if max_dd < 0 else 0
win_rate = (strategy_ret > 0).sum() / len(strategy_ret)
# 统计大类数量
markets = set()
for code_info in config['code_list'].values():
markets.add(code_info.get('market', 'A'))
metrics = {
'label': label,
'大类数量': len(markets),
'回测天数': days,
'回测年数': years,
'累计收益': total_return,
'CAGR': cagr,
'Sharpe': sharpe,
'MaxDD': max_dd,
'Calmar': calmar,
'日胜率': win_rate,
}
print(f"\n大类数量: {metrics['大类数量']}")
print(f"回测天数: {metrics['回测天数']}")
print(f"回测年数: {metrics['回测年数']:.2f}")
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{'指标':<15} {'A组(无东南亚)':<15} {'B组(有东南亚)':<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)
diff = b_val - a_val
if key in ['累计收益', 'CAGR', 'MaxDD', '日胜率']:
a_str = f"{a_val:.2%}"
b_str = f"{b_val:.2%}"
diff_str = f"{diff*100:+.2f}%"
elif key in ['大类数量', '回测天数']:
a_str = str(int(a_val))
b_str = str(int(b_val))
diff_str = f"+{int(diff)}" if diff > 0 else str(int(diff))
else:
a_str = f"{a_val:.2f}"
b_str = f"{b_val:.2f}"
diff_str = f"{diff:+.2f}"
print(f"{key:<15} {a_str:<15} {b_str:<15} {diff_str:<15}")
print("-" * 60)
print(f"\n【限制说明】")
print(f" ⚠ 本次测试数据量受限东南亚ETF仅2年数据")
print(f" ⚠ 使用ETF价格作为信号源指数数据暂不可用")
print(f" ⚠ 结果仅供参考,不建议直接用于决策")
print(f"\n【关键发现】")
if b_metrics['大类数量'] > a_metrics['大类数量']:
print(f" ✓ 大类数量增加 {b_metrics['大类数量'] - a_metrics['大类数量']}")
if b_metrics['累计收益'] > a_metrics['累计收益']:
print(f" ✓ 累计收益提升 {b_metrics['累计收益'] - a_metrics['累计收益']:.2%}")
else:
print(f" ✗ 累计收益下降 {a_metrics['累计收益'] - b_metrics['累计收益']:.2%}")
if b_metrics['Sharpe'] > a_metrics['Sharpe']:
print(f" ✓ Sharpe改善 {b_metrics['Sharpe'] - a_metrics['Sharpe']:.2f}")
else:
print(f" ✗ Sharpe下降 {a_metrics['Sharpe'] - b_metrics['Sharpe']:.2f}")
print(f"\n【策略建议】")
print(f" 建议等待东南亚科技ETF积累更多数据后再测试")
print(f" 原因:")
print(f" 1. 数据量不足(仅{b_metrics['回测年数']:.1f}年)")
print(f" 2. 指数信号源暂不可用")
print(f" 3. ETF价格作为信号源存在溢价干扰")
def main():
"""主函数"""
config_path = Path(__file__).parent.parent.parent / 'config' / 'strategies' / 'rotation.yaml'
with open(config_path, 'r') as f:
base_config = yaml.safe_load(f)
# 设置回测结束日期
from datetime import datetime
base_config['end_date'] = datetime.now().strftime('%Y-%m-%d')
# ⚠ 重要由于东南亚ETF数据从2023年12月开始
# 需要调整start_date以匹配数据可用范围
# 本次测试将使用较短的时间窗口
print(f"\n{'='*60}")
print(f" A/B测试添加东南亚科技ETF受限测试")
print(f"{'='*60}")
print(f"\n⚠ 限制说明:")
print(f" - 东南亚科技ETF513730.SH2023年12月上市")
print(f" - 数据仅约2年回测时间范围受限")
print(f" - 指数数据暂不可用使用ETF价格作为信号源")
print(f" - 结果仅供参考,不建议直接用于决策")
# A组当前配置使用较短时间窗口
config_a = base_config.copy()
config_a['start_date'] = '2024-01-01' # 调整为东南亚ETF有数据的起始时间
a_metrics = run_backtest(config_a, "A组: 当前配置2024年起")
# B组添加东南亚科技
config_b = create_config_with_sea(base_config)
config_b['start_date'] = '2024-01-01'
b_metrics = run_backtest(config_b, "B组: 添加东南亚科技")
# 对比
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.parent / 'results' / 'ab_test_sea_etf.csv'
results_df.to_csv(results_path, index=False)
print(f"\n对比结果已保存: {results_path}")
if __name__ == '__main__':
main()