Files
etf/scripts/run_rotation.py
aszerW 7b41bb8c6d feat(scripts): 迁移轮动策略定时调度器
新增文件:
- scripts/daily_scheduler.py: 定时调度器,支持交易日判断、回测执行、OSS上传、钉钉推送
- scripts/run_rotation.py: 回测入口脚本,支持Flask API和本地数据源切换
- config/settings.py: 配置管理模块,支持钉钉多群配置

功能:
1. 每天15:30自动检查交易日
2. 交易日执行策略回测生成报告
3. 上传报告图片到OSS
4. 发送图片链接到钉钉群

修复:
- 添加oss2库SyntaxWarning过滤(Python 3.12兼容)
- 钉钉消息精简为标题+图片格式
2026-05-18 00:57:59 +08:00

115 lines
3.3 KiB
Python
Raw 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.

#!/usr/bin/env python3
"""
轮动策略回测入口脚本
用法:
python scripts/run_rotation.py --config strategies/rotation/config.yaml
python scripts/run_rotation.py --config strategies/rotation/config.yaml --save-path results/report
"""
import sys
import argparse
from pathlib import Path
from datetime import datetime
# 添加项目根目录到路径
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
# 加载环境变量
from dotenv import load_dotenv
load_dotenv()
from strategies.rotation.strategy import RotationStrategy
def load_config(config_path: str) -> dict:
"""加载配置"""
import yaml
with open(config_path, 'r', encoding='utf-8') as f:
return yaml.safe_load(f)
def main():
parser = argparse.ArgumentParser(description='ETF轮动策略回测')
parser.add_argument(
'--config',
type=str,
default='strategies/rotation/config.yaml',
help='配置文件路径'
)
parser.add_argument(
'--save-path',
type=str,
default=None,
help='报告保存路径前缀'
)
parser.add_argument(
'--no-api',
action='store_true',
help='不使用Flask API使用本地数据源'
)
args = parser.parse_args()
start_time = datetime.now()
print("=" * 60)
print(" ETF轮动策略 回测系统")
print("=" * 60)
# 加载配置
print(f"\n加载配置: {args.config}")
config = load_config(args.config)
# 显示配置摘要
code_list = list(config.get('code_list', {}).keys())
print(f"候选标的: {len(code_list)}")
print(f"回测区间: {config.get('start_date', 'N/A')} ~ {config.get('end_date', 'N/A')}")
print(f"因子类型: {config.get('factor_type', 'momentum')}")
print(f"窗口天数: {config.get('n_days', 25)}")
print(f"选股数量: {config.get('select_num', 3)}")
print(f"调仓周期: {config.get('rebalance_days', 1)}")
print(f"交易成本: {config.get('trade_cost', 0.001):.2%}")
# 初始化策略
print("\n初始化策略...")
strategy = RotationStrategy.from_yaml(args.config)
# 设置保存路径
if args.save_path is None:
report_date = datetime.now().strftime('%Y%m%d')
args.save_path = f"results/report_{report_date}"
# 执行回测
print("\n" + "=" * 60)
print("开始回测...")
print("=" * 60)
# 使用Flask API或本地数据源
use_flask_api = not args.no_api
data = strategy.get_data(use_flask_api=use_flask_api)
result = strategy.run_backtest(data=data, save_path=args.save_path)
# 输出结果
if result.get('result') is not None:
final_nav = result['result']['策略净值'].iloc[-1]
total_return = (final_nav - 1) * 100
print("\n" + "=" * 60)
print("回测完成!")
print("=" * 60)
print(f"最终净值: {final_nav:.4f}")
print(f"累计收益: {total_return:.2f}%")
print(f"调仓次数: {len(result.get('rebalance_events', []))}")
print(f"报告保存: {args.save_path}_*.csv")
elapsed = datetime.now() - start_time
print(f"\n总耗时: {elapsed.total_seconds():.1f}")
return result
if __name__ == '__main__':
main()