🚀 AI 一键生成 qmt 策略代码

QMT Python 量化:Coppock Curve 指标的计算与交易逻辑

qmt | 发布时间: 2025/12/29
以下内容由 EasyQuant 生成。

问题描述

请写一个估波曲线 Coppock Curve 的长线买入策略

解决方案

这是一个基于 估波曲线 (Coppock Curve) 的长线量化交易策略。

策略原理

估波曲线(Coppock Curve)是一种用于识别股市长期底部买入机会的动量指标。它通过计算两个不同周期的变动率(ROC)之和,再对其进行加权移动平均(WMA)得出。

经典参数:

  1. ROC长周期:14个月
  2. ROC短周期:11个月
  3. 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)

代码关键点解析

  1. 数据频率转换

    • 虽然策略可以在日线(1d)上回测或运行,但代码中使用了 ContextInfo.get_market_data_ex(..., period='1mon') 强制获取月线数据。这是因为估波曲线本质上是一个长线月度指标。
    • 这样设计的好处是,你可以在每天盘中运行策略,一旦当月的数据计算结果满足条件,即可触发交易。
  2. WMA 计算

    • Python 的 pandas 库默认没有直接的 WMA 函数,代码中自定义了 wma 函数,利用 numpy 的点积(dot product)来实现加权平均。
  3. 信号稳定性

    • 代码取的是 iloc[-1](当前月)和 iloc[-2](上个月)。
    • 注意:在实盘或回测中,如果当前月份还没结束,iloc[-1] 的数据是会随着每天行情的波动而变化的(即信号可能会“闪烁”)。对于长线策略,这通常是可以接受的,或者你可以修改逻辑为只在月末最后一天交易。
  4. 买卖条件

    • 买入:严格遵循 Coppock 的定义,即在零轴下方(负值区)发生拐点向上。
    • 卖出:代码实现了一个简单的趋势跟随卖出(指标值下降即卖出)。你可以根据需要修改为“下穿零轴卖出”或“固定百分比止盈止损”。

如何使用

  1. 将代码复制到 QMT 的 Python 策略编辑器中。
  2. 修改 init 函数中的 ContextInfo.account_id 为你的实际账号。
  3. 修改 ContextInfo.target_list 为你关注的股票列表(建议选择大盘蓝筹股或ETF,因为估波曲线对波动较大的小盘股可能产生滞后)。
  4. 点击“回测”查看历史表现,或点击“运行”进行实盘/模拟盘监控。

Q&A

Q: 为什么使用月线数据而不是日线数据?
A: 估波曲线的设计初衷就是为了过滤短期市场噪音,捕捉长达数年或数月的市场底部。使用日线计算会导致信号过于频繁,失去长线指标的意义。

Q: 如何调整策略的灵敏度?
A: 可以调整 roc_long_period (14), roc_short_period (11), wma_period (10) 这三个参数。减小数值会使指标更灵敏,但虚假信号也会增加。

Q: 可以在实盘中自动下单吗?
A: 可以。代码中使用了 passorder 函数。在实盘模式下,只要账号绑定正确且资金充足,满足条件时会自动发出委托。请务必先在模拟盘充分测试。