Files
sanguo_quant_live/strategies/factors-dynamic-weight-timing-20260327/run_backtest.py
T
2026-04-02 08:55:07 +08:00

222 lines
7.0 KiB
Python

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
SingleStockStopLossStrategy 回测执行脚本
集成关羽风控模块(单票止损),调用远程 API 执行回测
Author: 关羽 云长
Date: 2026-03-31
Description: 对集成了单票止损的进阶多因子策略执行回测
"""
import sys
import os
import json
import requests
from datetime import datetime
from typing import Dict, List, Optional
# 配置 API 地址
API_BASE_URL = "http://192.168.2.154:8088"
def test_api_connection() -> bool:
"""测试 API 连接"""
print("=" * 60)
print("1. 测试 API 连接")
print("=" * 60)
try:
url = f"{API_BASE_URL}/ping"
response = requests.get(url, timeout=10)
print(f"✅ API 连接成功: {response.status_code}")
print(f" 响应: {response.text[:200]}")
return True
except Exception as e:
print(f"❌ API 连接失败: {e}")
# 尝试根路径
try:
url = f"{API_BASE_URL}/"
response = requests.get(url, timeout=10)
print(f"根路径访问成功: {response.status_code}")
print(f"响应: {response.text[:200]}")
return True
except Exception as e2:
print(f"根路径访问也失败: {e2}")
return False
def get_backtest_config() -> Dict:
"""获取回测配置"""
config = {
"strategy_name": "SingleStockStopLossStrategy",
"description": "进阶多因子动态加权 + 关羽单票止损风控",
"start_date": "2018-01-01",
"end_date": "2026-03-31",
"initial_capital": 1000000,
"strategy_module": "factors-dynamic-weight-timing-20260327.main_strategy_single_file",
"strategy_class": "MultiFactorDynamicStrategy",
"parameters": {
"rebalance_freq": "M",
"holding_size": 50,
"top_select": 0.1,
"dynamic_weight": True,
"ic_window": 12,
"market_timing": True,
"min_position": 0.3,
"max_position": 1.0,
"max_sector_pct": 0.20
},
"risk_control": {
"enabled": True,
"single_stock_stop_loss": 0.15,
"portfolio_drawdown_control": True,
"black_swan_filter": True
},
"data_source": "remote_api"
}
return config
def run_backtest(config: Dict) -> Optional[Dict]:
"""执行回测"""
print("\n" + "=" * 60)
print("2. 提交回测任务")
print("=" * 60)
try:
url = f"{API_BASE_URL}/api/backtest/run"
response = requests.post(url, json=config, timeout=30)
data = response.json()
if response.status_code == 200 and data.get("status") == "ok":
print(f"✅ 回测任务提交成功")
print(f" 任务ID: {data.get('task_id')}")
return data
else:
print(f"❌ 回测提交失败: {data}")
return None
except Exception as e:
print(f"❌ 回测提交异常: {e}")
return None
def poll_backtest_result(task_id: str) -> Optional[Dict]:
"""轮询回测结果"""
print("\n" + "=" * 60)
print("3. 等待回测完成")
print("=" * 60)
import time
max_wait = 3600 # 1小时超时
wait_step = 10 # 每10秒轮询一次
for i in range(0, max_wait, wait_step):
try:
url = f"{API_BASE_URL}/api/backtest/status/{task_id}"
response = requests.get(url, timeout=10)
data = response.json()
status = data.get("status")
if status == "completed":
print(f"✅ 回测完成!")
return data
elif status == "running":
progress = data.get("progress", 0)
print(f" 回测进行中... 进度: {progress:.1%} ({i}/{max_wait}s)")
elif status == "error":
print(f"❌ 回测执行出错: {data.get('error')}")
return None
else:
print(f" 状态: {status} ({i}/{max_wait}s)")
except Exception as e:
print(f" 轮询异常: {e} ({i}/{max_wait}s)")
time.sleep(wait_step)
print(f"❌ 回测超时")
return None
def print_backtest_result(result: Dict):
"""打印回测结果"""
print("\n" + "=" * 60)
print("4. 回测结果")
print("=" * 60)
metrics = result.get("metrics", {})
print(f"\n📊 回测基本信息:")
print(f" 策略名称: {result.get('strategy_name')}")
print(f" 回测区间: {result.get('start_date')} ~ {result.get('end_date')}")
print(f" 初始资金: {result.get('initial_capital'):,.0f}")
print(f"\n📈 绩效指标:")
if "total_return" in metrics:
print(f" 总收益率: {metrics['total_return']:.2%}")
if "annual_return" in metrics:
print(f" 年化收益率: {metrics['annual_return']:.2%}")
if "max_drawdown" in metrics:
print(f" 最大回撤: {metrics['max_drawdown']:.2%}")
if "sharpe_ratio" in metrics:
print(f" 夏普比率: {metrics['sharpe_ratio']:.2f}")
if "calmar_ratio" in metrics:
print(f" 卡玛比率: {metrics['calmar_ratio']:.2f}")
if "win_rate" in metrics:
print(f" 胜率: {metrics['win_rate']:.2%}")
if "profit_loss_ratio" in metrics:
print(f" 盈亏比: {metrics['profit_loss_ratio']:.2f}")
print(f"\n⚠️ 风控统计:")
if "stop_loss_count" in metrics:
print(f" 触发止损次数: {metrics['stop_loss_count']}")
if "portfolio_rebalance_count" in metrics:
print(f" 组合降仓次数: {metrics['portfolio_rebalance_count']}")
if "filtered_stocks_count" in metrics:
print(f" 黑天鹅过滤数量: {metrics['filtered_stocks_count']}")
print("\n" + "=" * 60)
# 保存结果到文件
result_file = os.path.join(
os.path.dirname(__file__),
f"backtest_result_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
)
with open(result_file, 'w', encoding='utf-8') as f:
json.dump(result, f, indent=2, ensure_ascii=False)
print(f"\n💾 完整结果已保存到: {result_file}")
def main():
"""主函数"""
print("\n")
print("=" * 60)
print("SingleStockStopLossStrategy 回测")
print("进阶多因子动态加权 + 关羽单票止损风控")
print("=" * 60)
# 1. 测试 API 连接
if not test_api_connection():
print("\n❌ API 连接失败,请检查服务是否正常运行")
sys.exit(1)
# 2. 获取回测配置并提交
config = get_backtest_config()
submit_result = run_backtest(config)
if not submit_result:
sys.exit(1)
task_id = submit_result.get("task_id")
if not task_id:
print("❌ 没有获取到任务ID")
sys.exit(1)
# 3. 等待回测完成
result = poll_backtest_result(task_id)
if not result:
sys.exit(1)
# 4. 打印结果
print_backtest_result(result)
print("\n✅ 回测执行完成!")
if __name__ == "__main__":
main()