222 lines
7.0 KiB
Python
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()
|