字数 1497,阅读大约需 8 分钟
周日晚上随手刷朋友圈,看到了猫哥写的一篇关于ETF轮动策略改进的文章,读下来倍受启发。
Claude Code 开发 100 个量化策略:ETF 三因子动量轮动策略(7 年 13 倍)
原文链接:https://mp.weixin.qq.com/s/GPqJrd4UiIKIgCV5DtfLJA
这篇文章在传统 ETF 轮动策略的基础上,引入了三个动量因子:
- • 乖离动量
- • 斜率动量
- • 效率动量
相比基础版本(仅仅使用斜率动量),这种思路明显更“稳健”,不是只盯着一个信号,而是多维度一起评估趋势强弱,看起来就更靠谱一些。

看完之后,按捺不住心中的激动,跃跃欲试想把这个策略在本地用Python实现。

😮💨 不想搬砖
说实话,实现这个策略本身并不难。
我之前已经在本地 Python 环境里实现了基础版的ETF轮动策略,数据、回测框架、代码结构都已经搭好了。按照猫哥文章里的思路去改,无非就是多加几个因子、多写几段计算逻辑。
# 计算每一天四只ETF的得分,持仓得分最高的ETF
for idx, day in enumerate(trade_day['cal_date']):
if idx < reg_num: continue
scores = pd.Series(index = etf_libs, dtype = 'float64')
for stk in etf_libs:
temp_close = df_all[stk].iloc[idx-reg_num: idx]['close']
y = np.log(temp_close)
x = np.arange(y.size)
slope, intercept = np.polyfit(x, y, 1)
annualized_returns = math.pow(math.exp(slope), 250) - 1
r_squared = 1 - (sum((y - (slope * x + intercept))**2) / ((len(y) - 1) * np.var(y, ddof=1)))
scores[stk] = annualized_returns * r_squared
etf_tohold = scores.idxmax()
# 打印策略结果
print(day, scores.idxmax())
print(scores)症结在于: 按照猫哥的思路在本地改进策略,基本就属于纯牛马活 。
当我真的打开 IDE,准备开始改代码的时候,刚写了一行,就已经开始烦了,“臣妾”是真的不想写啊...

🤖 交给 Codex
既然不想写,那就别写了。
于是干脆打开 VS Code 里的 Codex 插件,把 原来的基础策略代码 丢进去,再把 猫哥文章中关于策略改进的那段文字一并贴上 ,顺手下了个指令:
我想按如下方式对策略进行改进,麻烦在我的代码基础上进行修改,谢谢!

虽然是周日晚上,但 Codex 随叫随到,完全不受作息影响,也不受法定节假日的影响。
⚙️ 自动完成
Prompt 提交之后,Codex 很快就开始分析代码结构和策略逻辑。
它并不是简单地“照着描述往里塞代码”,而是会主动去理解我原本的策略脚本和回测脚本之间的数据关系,然后改进策略脚本,并把一些改进后与回测脚本对不上的地方一起调整掉。

整个过程非常安静,但效率极高。不到两分钟,代码就已经改完了。
Codex 在改完代码之后,会自动帮我运行一遍,确认逻辑是否能正常执行。我把生成好的脚本直接拉到本地跑,结果一次就跑通了,没有任何报错...

回测结果出来之后,比原始版本要好不少。
我不太放心,又把代码从头到尾过了一遍,一行一行地检查有没有潜在问题。结果看下来,逻辑清晰,结构也干净,愣是没找出什么明显的 bug,真的是优雅极了!
# 乖离动量:价格相对长期均线的偏离趋势斜率
def bias_momentum(close_prices: pd.Series) -> float:
if close_prices.empty:
return np.nan
bias = close_prices / close_prices.rolling(window=BIAS_N, min_periods=1).mean()
if len(bias) < MOMENTUM_DAY or bias.iloc[-MOMENTUM_DAY] == 0:
return np.nan
bias_recent = bias.iloc[-MOMENTUM_DAY:]
base = bias_recent.iloc[0]
if base == 0:
return np.nan
x = np.arange(MOMENTUM_DAY).reshape(-1, 1)
y = (bias_recent / base).values.reshape(-1, 1)
lr = LinearRegression()
lr.fit(x, y)
return float(lr.coef_[0]) * 10000
# 斜率动量:标准化价格的线性回归斜率 * R^2
def slope_momentum(close_prices: pd.Series) -> float:
if len(close_prices) < SLOPE_N:
return np.nan
normalized_prices = close_prices.iloc[-SLOPE_N:] / close_prices.iloc[-SLOPE_N]
x = np.arange(1, SLOPE_N + 1).reshape(-1, 1)
y = normalized_prices.values.reshape(-1, 1)
lr = LinearRegression()
lr.fit(x, y)
slope = float(lr.coef_[0])
r_squared = float(lr.score(x, y))
return 10000 * slope * r_squared
# 效率动量:净移动距离/总波动 * 动量
def efficiency_momentum(df: pd.DataFrame) -> float:
if len(df) < EFFICIENCY_N:
return np.nan
window = df.iloc[-EFFICIENCY_N:]
pivot = (window['open'] + window['high'] + window['low'] + window['close']) / 4.0
pivot = pivot.ffill()
if pivot.iloc[0] <= 0 or pivot.iloc[-1] <= 0:
return np.nan
momentum = 100 * np.log(pivot.iloc[-1] / pivot.iloc[0])
direction = abs(np.log(pivot.iloc[-1]) - np.log(pivot.iloc[0]))
volatility = np.log(pivot).diff().abs().sum()
efficiency_ratio = direction / volatility if volatility > 0 else 0
return momentum * efficiency_ratio🎛️ 参数空间
这个策略本身不复杂,但相比于原始策略多了好几个参数。
# 因子参数
BIAS_N = 25 # 乖离均线窗口长度
MOMENTUM_DAY = 25 # 乖离动量回归天数
SLOPE_N = 25 # 斜率动量回看窗口
EFFICIENCY_N = 25 # 效率动量回看窗口
FACTOR_WEIGHTS = {'bias': 0.2, 'slope': 0.3, 'efficiency': 0.5} # 三因子权重
REBALANCE_THRESHOLD = 1.5 # 调仓阈值系数,新候选需超过当前1.5倍才换仓比如各个因子的回溯窗口、权重分配,以及调仓触发条件,这些都会对结果产生不小的影响。我后面手动试了几组不同参数,效果提升相当明显。回测区间为2019年到2026年。


在我当前这组参数下,年化收益、夏普比率都有明显提升,年化收益直接增加了10%,同时最大回撤反而下降了,这真是相当不错的改进。
| 指标 | 基础策略 | 优化策略 |
| 累计收益 | 716.68% | 1227.81% |
| 年化收益率 | 36.27% | 46.39% |
| 夏普比率 | 1.296 | 1.557 |
| 最大回撤 | 31.22% | 26.53% |
| 胜率 | 0.549 | 0.596 |
| 盈亏比 | 2.950 | 3.754 |
🤝 人机协作
从刷到猫哥那篇文章,到代码改完、回测跑通、初步优化参数,整个过程用时不到半个小时。
如果换成我自己写,至少得拖个几天才能完成,而且代码写的肯定比Codex写的丑多了...
不得不赞叹:这真是一次漂亮、高效的人机协作!
没有评论:
发表评论