问题描述
解决方案
这是一个基于 估波曲线 (Coppock Curve) 的长线量化交易策略。
策略原理
估波曲线(Coppock Curve)是一种用于识别股市长期底部买入机会的动量指标。它通过计算两个不同周期的变动率(ROC)之和,再对其进行加权移动平均(WMA)得出。
经典参数:
- ROC长周期:14个月
- ROC短周期:11个月
- WMA平滑周期:10个月
交易信号逻辑:
- 买入信号:当估波曲线在零轴下方(负值区域)出现拐点向上时(即当前值 > 上期值,且上期值 < 0)。这是经典的抄底信号。
- 卖出信号:估波曲线原本主要用于买入,这里为了策略完整性,增加了卖出逻辑。当估波曲线数值下降(当前值 < 上期值)时,或者持仓发生亏损超过一定比例(止损)时卖出。
策略代码
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
'''
策略名称:估波曲线 (Coppock Curve) 长线策略
策略周期:建议在 日线 (1d) 周期上运行,但在内部调用 月线 (1mon) 数据计算指标
'''
def init(ContextInfo):
# 1. 设置资金账号
# 请替换为您自己的资金账号
ContextInfo.account_id = '6000000000'
ContextInfo.account_type = 'STOCK'
ContextInfo.set_account(ContextInfo.account_id)
# 2. 设置股票池 (示例使用 沪深300成分股,可根据需要修改)
# 为了演示方便,这里只选取几个代表性标的,实际使用可使用 get_sector 获取板块
ContextInfo.target_list = ['600000.SH', '000001.SZ', '600519.SH', '000858.SZ']
ContextInfo.set_universe(ContextInfo.target_list)
# 3. 策略参数设置 (经典Coppock参数:14, 11, 10)
ContextInfo.roc_long_period = 14 # 长周期 ROC (月)
ContextInfo.roc_short_period = 11 # 短周期 ROC (月)
ContextInfo.wma_period = 10 # WMA 平滑周期 (月)
# 每次下单金额占总资产的比例
ContextInfo.trade_ratio = 0.1
def wma(series, window):
'''
计算加权移动平均 (Weighted Moving Average)
公式: (P1*1 + P2*2 + ... + Pn*n) / (1 + 2 + ... + n)
'''
weights = np.arange(1, window + 1)
# 使用 rolling apply 计算 WMA
return series.rolling(window).apply(lambda x: np.dot(x, weights) / weights.sum(), raw=True)
def calculate_coppock(close_series, roc_long, roc_short, wma_p):
'''
计算估波曲线值
'''
# 1. 计算长周期 ROC: (当前价 - N前价) / N前价 * 100
roc_1 = close_series.pct_change(periods=roc_long) * 100
# 2. 计算短周期 ROC
roc_2 = close_series.pct_change(periods=roc_short) * 100
# 3. 两者相加
roc_sum = roc_1 + roc_2
# 4. 计算 WMA
coppock = wma(roc_sum, wma_p)
return coppock
def handlebar(ContextInfo):
# 获取当前K线位置,避免在历史数据不足时计算报错
if ContextInfo.barpos < 30:
return
# 获取当前时间
current_date = timetag_to_datetime(ContextInfo.get_bar_timetag(ContextInfo.barpos), '%Y%m%d')
# 获取股票池
stock_list = ContextInfo.get_universe()
# 获取历史行情数据
# 注意:Coppock是基于月线的指标,所以这里强制取 '1mon' 数据
# 需要的数据长度 = 长ROC周期 + WMA周期 + 安全余量
data_count = ContextInfo.roc_long_period + ContextInfo.wma_period + 20
market_data = ContextInfo.get_market_data_ex(
['close'],
stock_list,
period='1mon',
count=data_count,
dividend_type='front' # 前复权
)
# 获取账户持仓信息
positions = get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'POSITION')
holding_stocks = [pos.m_strInstrumentID for pos in positions if pos.m_nVolume > 0]
for stock in stock_list:
if stock not in market_data:
continue
df = market_data[stock]
# 数据清洗,去除空值
df.dropna(inplace=True)
# 数据长度不足无法计算
if len(df) < (ContextInfo.roc_long_period + ContextInfo.wma_period):
continue
# 计算指标
df['coppock'] = calculate_coppock(
df['close'],
ContextInfo.roc_long_period,
ContextInfo.roc_short_period,
ContextInfo.wma_period
)
# 获取最近两个月的指标值
# iloc[-1] 是当前月(可能是未走完的),iloc[-2] 是上个月
# 为了信号稳定,通常建议使用已完成的月份数据,或者在月末最后一天交易
# 这里取最后两个有效值进行比较
curr_coppock = df['coppock'].iloc[-1]
prev_coppock = df['coppock'].iloc[-2]
# ---------------- 交易逻辑 ----------------
# 1. 买入逻辑:
# 条件A: 曲线位于零轴下方 (prev_coppock < 0)
# 条件B: 曲线拐头向上 (curr_coppock > prev_coppock)
# 条件C: 当前没有持仓
if prev_coppock < 0 and curr_coppock > prev_coppock and stock not in holding_stocks:
print(f"[{current_date}] {stock} 触发买入信号: 估波曲线底部拐头 (前值:{prev_coppock:.2f}, 现值:{curr_coppock:.2f})")
# 资金管理:按比例下单
passorder(23, 1113, ContextInfo.account_id, stock, 5, -1, ContextInfo.trade_ratio, ContextInfo)
# 2. 卖出逻辑:
# 估波曲线主要用于买入,卖出通常结合趋势反转或止损
# 简单逻辑:当曲线数值开始下降时卖出 (curr_coppock < prev_coppock)
elif stock in holding_stocks:
if curr_coppock < prev_coppock:
print(f"[{current_date}] {stock} 触发卖出信号: 估波曲线下降")
passorder(24, 1101, ContextInfo.account_id, stock, 5, -1, 1, ContextInfo) # 1代表卖出全部持仓
# 也可以加入止损逻辑,例如:
# pos = next((p for p in positions if p.m_strInstrumentID == stock), None)
# if pos and (current_price - pos.m_dOpenPrice) / pos.m_dOpenPrice < -0.10:
# passorder(24, 1101, ContextInfo.account_id, stock, 5, -1, 1, ContextInfo)
代码关键点解析
-
数据频率转换:
- 虽然策略可以在日线(
1d)上回测或运行,但代码中使用了ContextInfo.get_market_data_ex(..., period='1mon')强制获取月线数据。这是因为估波曲线本质上是一个长线月度指标。 - 这样设计的好处是,你可以在每天盘中运行策略,一旦当月的数据计算结果满足条件,即可触发交易。
- 虽然策略可以在日线(
-
WMA 计算:
- Python 的
pandas库默认没有直接的 WMA 函数,代码中自定义了wma函数,利用numpy的点积(dot product)来实现加权平均。
- Python 的
-
信号稳定性:
- 代码取的是
iloc[-1](当前月)和iloc[-2](上个月)。 - 注意:在实盘或回测中,如果当前月份还没结束,
iloc[-1]的数据是会随着每天行情的波动而变化的(即信号可能会“闪烁”)。对于长线策略,这通常是可以接受的,或者你可以修改逻辑为只在月末最后一天交易。
- 代码取的是
-
买卖条件:
- 买入:严格遵循 Coppock 的定义,即在零轴下方(负值区)发生拐点向上。
- 卖出:代码实现了一个简单的趋势跟随卖出(指标值下降即卖出)。你可以根据需要修改为“下穿零轴卖出”或“固定百分比止盈止损”。
如何使用
- 将代码复制到 QMT 的 Python 策略编辑器中。
- 修改
init函数中的ContextInfo.account_id为你的实际账号。 - 修改
ContextInfo.target_list为你关注的股票列表(建议选择大盘蓝筹股或ETF,因为估波曲线对波动较大的小盘股可能产生滞后)。 - 点击“回测”查看历史表现,或点击“运行”进行实盘/模拟盘监控。
Q&A
Q: 为什么使用月线数据而不是日线数据?
A: 估波曲线的设计初衷就是为了过滤短期市场噪音,捕捉长达数年或数月的市场底部。使用日线计算会导致信号过于频繁,失去长线指标的意义。
Q: 如何调整策略的灵敏度?
A: 可以调整 roc_long_period (14), roc_short_period (11), wma_period (10) 这三个参数。减小数值会使指标更灵敏,但虚假信号也会增加。
Q: 可以在实盘中自动下单吗?
A: 可以。代码中使用了 passorder 函数。在实盘模式下,只要账号绑定正确且资金充足,满足条件时会自动发出委托。请务必先在模拟盘充分测试。