问题描述
解决方案
这是一个基于 估波曲线 (Coppock Curve) 的长线交易策略实现。
策略原理
估波曲线(Coppock Curve)是一种主要用于识别股票市场长期底部动量指标。它的计算逻辑如下:
- 计算变化率 (ROC):分别计算 14 个月和 11 个月的价格变化率。
- 求和:将两个 ROC 值相加。
- 加权移动平均 (WMA):对上述和值进行 10 个月的加权移动平均。
交易信号:
- 买入:当估波曲线在零轴下方(或任意位置,视激进程度而定)拐头向上时。
- 卖出:当估波曲线拐头向下时(原版主要用于抄底,卖出信号通常结合其他指标,但本策略演示采用拐头向下即卖出)。
策略代码
本策略以 沪深300 ETF (510300.XSHG) 为标的,每月第一个交易日运行一次,捕捉长期趋势。
# -*- coding: utf-8 -*-
import numpy as np
import pandas as pd
def initialize(context):
"""
初始化函数
"""
# 设定标的:这里使用沪深300ETF作为长线投资标的
g.security = '510300.XSHG'
# 设定基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉order系列API产生的比error级别低的log
log.set_level('order', 'error')
# 设定ETF交易手续费(模拟真实ETF费率)
set_order_cost(OrderCost(close_tax=0, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='fund')
# 设定按月运行:每月第一个交易日开盘运行
run_monthly(trade_logic, 1, time='open')
def calculate_wma(series, weights):
"""
计算加权移动平均 (WMA)
"""
if len(series) != len(weights):
return None
return np.average(series, weights=weights)
def get_coppock_curve(security, context):
"""
计算估波曲线指标
"""
# 获取过去 30 个月的收盘价数据 (14个月ROC + 10个月WMA + 缓冲)
# unit='1M' 获取的是过去每个月的月线数据
h = attribute_history(security, 30, '1M', ['close'], df=True)
if len(h) < 25:
return None, None
close = h['close']
# 1. 计算 14个月 和 11个月 的 ROC (Rate of Change)
# pct_change(N) 计算的是 (现价 - N天前价格) / N天前价格 * 100
roc_14 = close.pct_change(14) * 100
roc_11 = close.pct_change(11) * 100
# 2. 将两个 ROC 相加
roc_sum = roc_14 + roc_11
# 去除前面的 NaN 值
roc_sum = roc_sum.dropna()
# 3. 计算 10 个月的 WMA (加权移动平均)
# 权重为 1, 2, 3, ..., 10 (越近的数据权重越大)
weights = np.arange(1, 11)
if len(roc_sum) < 11:
return None, None
# 计算当前的 Coppock 值 (使用最近10个数据)
current_series = roc_sum.iloc[-10:]
coppock_curr = calculate_wma(current_series, weights)
# 计算上个月的 Coppock 值 (使用倒数第11到倒数第2个数据)
prev_series = roc_sum.iloc[-11:-1]
coppock_prev = calculate_wma(prev_series, weights)
return coppock_curr, coppock_prev
def trade_logic(context):
"""
交易逻辑函数
"""
security = g.security
# 获取当前和上期的估波值
cc_curr, cc_prev = get_coppock_curve(security, context)
if cc_curr is None or cc_prev is None:
log.info("数据不足,跳过计算")
return
# 记录指标值以便在回测图中查看
record(Coppock=cc_curr)
# 获取当前持仓
position = context.portfolio.positions[security].total_amount
cash = context.portfolio.available_cash
# --- 交易信号判断 ---
# 信号1:买入
# 逻辑:曲线拐头向上 (当前值 > 上期值) 且 上期值 < 0 (经典用法是在零轴下方金叉,也可去掉 <0 限制做趋势跟随)
# 这里采用经典用法:底部反转
if cc_curr > cc_prev and cc_prev < 0:
if position == 0:
log.info("估波曲线底部拐头向上 (Curr: %.2f, Prev: %.2f),全仓买入" % (cc_curr, cc_prev))
order_value(security, cash)
# 信号2:卖出
# 逻辑:曲线拐头向下 (当前值 < 上期值)
elif cc_curr < cc_prev:
if position > 0:
log.info("估波曲线拐头向下 (Curr: %.2f, Prev: %.2f),清仓卖出" % (cc_curr, cc_prev))
order_target(security, 0)
代码要点解析
-
数据频率 (
1M):- 估波曲线是一个长线指标,传统定义是基于月线数据的。
- 使用
attribute_history(security, 30, '1M', ...)获取过去30个月的收盘价。 - 使用
run_monthly确保策略只在每月的第一个交易日运行,避免日内波动干扰。
-
WMA (加权移动平均) 实现:
- Pandas 默认没有直接的 WMA 函数(只有 EMA 和 SMA)。
- 代码中定义了
calculate_wma函数,利用numpy.average配合weights参数(权重序列为 1 到 10)来实现线性加权。
-
买卖逻辑:
- 买入:
cc_curr > cc_prev and cc_prev < 0。这表示曲线在负值区域(通常意味着市场处于低估或熊市底部)开始向上转折。这是 Coppock Curve 最核心的用法。 - 卖出:
cc_curr < cc_prev。当动量减弱,曲线开始掉头向下时离场。
- 买入:
-
标的选择:
- 代码中使用了
510300.XSHG(沪深300 ETF)。估波曲线最适合用于大盘指数或宽基 ETF,用于判断宏观市场的牛熊转换,而不适合波动剧烈的小盘股。
- 代码中使用了
如何在聚宽中使用
- 点击聚宽导航栏的“策略研究” -> “我的策略” -> “新建策略”。
- 将上述代码完整复制到代码编辑区。
- 设置回测时间,建议设置较长的时间跨度(例如 2010年至今),因为这是一个长线策略,信号频率很低。
- 资金建议设置 10万 以上。
- 点击“编译运行”或“运行回测”。
Q&A
Q: 为什么买入条件要加上 cc_prev < 0?
A: 估波曲线的设计初衷是寻找熊市底部。在零轴下方的拐头通常代表市场极度悲观后的复苏。如果在零轴上方拐头向上,可能只是上涨中继,虽然也可以买入,但风险收益比通常不如底部反转高。
Q: 这个策略的缺点是什么?
A: 这是一个极其滞后的指标。因为它使用了14个月和11个月的ROC,再加上10个月的平滑,它对市场反应很慢。它能让你吃到牛市中间最肥美的一段,但绝对买不到最低点,也卖不到最高点。在震荡市中可能会失效。
Q: 可以应用在个股上吗?
A: 可以,但建议选择大盘蓝筹股。对于妖股或小盘股,由于波动率过大,ROC指标会产生剧烈抖动,导致WMA平滑后依然产生虚假信号。