这是一个聚宽平台上的 ETF动量轮动策略,我快速梳理一下核心逻辑和几个值得注意的点:
策略架构概览
text
Copy
固定ETF池(~100只) ──┐
├─→ 流动性过滤 ──→ 合并池 ──→ 动量计算 ──→ 过滤 ──→ 排名 ──→ 持仓1只
动态ETF池(行业冠军) ──┘ ↑ ↑
每日动态阈值 R²×年化收益
核心流程(每天13:09执行)
动量得分 = 加权线性回归斜率算出的年化收益 × R²(趋势一致性)
多层过滤:R²、短期风控(3日跌幅)、成交量比等
排名选股:取前10名 → 用第N名得分×0.9作阈值 → 结合现有持仓决定买卖
只持1只ETF(g.holdings_num = 1)
几个值得关注的问题
? 代码Bug
get_final_ranked_etfs 函数中有一个 提前return 的严重问题:
python
Copy
full_log = "\n".join(log_lines_step1 + log_lines_step2)
log.info(full_log)
return [] # ← 这里直接返回空列表了!下面的第三步、第四步永远不会执行
这意味着策略 永远买入防御ETF或空仓,动量选股的最终结果(第三步、第四步)永远不会被返回。需要删除这个 return []。
? 性能风险
分钟级止损注册了约360个定时任务(9:30-11:30 + 13:00-14:57 每分钟),每个任务遍历所有持仓,可能触发聚宽的运行时间/API调用限制
calculate_global_etf_threshold 每天拉取 全市场所有ETF 的3天成交额,数据量巨大
? 逻辑细节
动态池的名称清理逻辑复杂但 cleaned[:2] 分组较粗糙,可能导致不同行业被归为一组
成交量过滤 volume_ratio < volume_threshold(要求缩量),但阈值设为1.0,等于只过滤放量ETF——这和常见的"放量突破"思路相反,确认是有意为之?
get_volume_ratio 取分钟级当日成交量,在13:09调用时盘中数据是有的,但 g.volume_lookback=5 取的是过去5天日均量——交易日不足5天的新ETF会被跳过
? 优点
冷却期机制设计合理,止损后不立即追回
smart_order_target_value 考虑了停牌、涨跌停、T+1、最小交易额
动态流动性阈值比固定值更适应市场变化
最关键的是那个 return [] 的bug——修复它之前,策略实际上不会按动量轮动逻辑运作。
25天前