🚀 AI 一键生成 joinquant 策略代码

JoinQuant 进阶指标:CMO 钱德动量摆动指标策略实现

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

问题描述

请帮我实现 CMO 钱德动量摆动指标的交易策略

解决方案

这是一个基于 CMO (Chande Momentum Oscillator, 钱德动量摆动指标) 的交易策略实现。

策略说明

CMO 指标简介:
钱德动量摆动指标(CMO)由 Tushar Chande 发明,用于衡量证券价格动量的强度。它的取值范围在 -100 到 +100 之间。

  • 计算公式:$CMO = \frac{(Su - Sd)}{(Su + Sd)} \times 100$
    • $Su$:N日内收盘价上涨幅度的总和。
    • $Sd$:N日内收盘价下跌幅度的绝对值总和。

交易逻辑(均值回归策略):
本策略采用经典的超买超卖逻辑:

  1. 买入信号:当 CMO 值低于 超卖线(例如 -50)时,认为市场过度悲观,可能反弹,执行买入。
  2. 卖出信号:当 CMO 值高于 超买线(例如 +50)时,认为市场过度乐观,可能回调,执行卖出。

策略代码

# -*- coding: utf-8 -*-
import jqdata
import pandas as pd
import numpy as np

def initialize(context):
    """
    初始化函数,设定基准、手续费、全局变量等
    """
    # 设定沪深300作为基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    # 过滤掉order系列API产生的比error级别低的log
    log.set_level('order', 'error')
    
    # 股票类每笔交易时的手续费是:买入时佣金万分之三,卖出时佣金万分之三加千分之一印花税, 每笔交易佣金最低扣5块钱
    set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
    
    # --- 策略参数设置 ---
    # 要操作的标的:平安银行
    g.security = '000001.XSHE'
    # CMO计算周期
    g.n = 14
    # 超买阈值 (卖出)
    g.overbought = 50
    # 超卖阈值 (买入)
    g.oversold = -50
    
    # 每天开盘时运行
    run_daily(market_open, time='every_bar')

def calculate_cmo(security, n_days):
    """
    计算CMO指标
    :param security: 标的代码
    :param n_days: 计算周期
    :return: CMO值
    """
    # 获取过去 n_days + 1 天的收盘价,因为计算diff需要多一天数据
    # 这里的 '1d' 表示日频数据
    h_data = attribute_history(security, n_days + 1, '1d', ['close'])
    
    # 如果数据长度不足,返回None
    if len(h_data) < n_days + 1:
        return None
    
    close_prices = h_data['close']
    
    # 计算每日变动
    delta = close_prices.diff()
    
    # 去除第一个NaN值
    delta = delta.dropna()
    
    # 分离上涨和下跌
    # where(条件, 不满足条件时的值)
    # gains: 只保留上涨的幅度,下跌记为0
    gains = delta.where(delta > 0, 0)
    # losses: 只保留下跌的幅度的绝对值,上涨记为0
    losses = np.abs(delta.where(delta < 0, 0))
    
    # 计算周期内的总和
    sum_gains = gains.sum()
    sum_losses = losses.sum()
    
    # 防止分母为0
    if sum_gains + sum_losses == 0:
        return 0
        
    # 计算CMO公式: 100 * (Su - Sd) / (Su + Sd)
    cmo = 100 * (sum_gains - sum_losses) / (sum_gains + sum_losses)
    
    return cmo

def market_open(context):
    """
    每日交易逻辑
    """
    security = g.security
    
    # 计算当前的CMO值
    cmo_value = calculate_cmo(security, g.n)
    
    if cmo_value is None:
        return
        
    # 获取当前现金和持仓
    cash = context.portfolio.available_cash
    position = context.portfolio.positions[security]
    
    # 打印日志方便调试
    # log.info(f"日期: {context.current_dt.date()}, CMO: {cmo_value:.2f}")
    
    # --- 交易信号判断 ---
    
    # 1. 买入信号:CMO < 超卖阈值 (-50) 且 当前无持仓
    if cmo_value < g.oversold and position.closeable_amount == 0:
        # 全仓买入
        order_value(security, cash)
        log.info(f"买入信号触发 (CMO={cmo_value:.2f} < {g.oversold}),买入 {security}")
        
    # 2. 卖出信号:CMO > 超买阈值 (50) 且 当前有持仓
    elif cmo_value > g.overbought and position.closeable_amount > 0:
        # 清仓卖出
        order_target(security, 0)
        log.info(f"卖出信号触发 (CMO={cmo_value:.2f} > {g.overbought}),卖出 {security}")
        
    # 记录CMO值到图表
    record(CMO=cmo_value)

常见问题 Q&A

Q: CMO 指标和 RSI 指标有什么区别?
A: 两者非常相似,都是衡量动量的震荡指标。RSI 的范围是 0 到 100,而 CMO 的范围是 -100 到 +100。CMO 直接使用上涨总和与下跌总和的差值作为分子,这使得它对极端价格变动更加敏感。实际上,CMO 和 RSI 之间存在数学换算关系。

Q: 如何优化这个策略?
A:

  1. 增加趋势过滤:CMO 在强趋势行情中可能会出现“钝化”(长期停留在超买或超卖区)。可以结合均线(如 MA200)判断大趋势,只在上升趋势中做多。
  2. 调整阈值:默认的 +/- 50 比较激进。如果希望交易频率降低但信号更可靠,可以尝试调整为 +/- 60 或 +/- 70。
  3. 止损机制:当前代码是纯指标反转策略,建议加入固定比例止损(如亏损 5% 止损)来控制风险。

Q: 为什么代码中使用 attribute_history 获取 n+1 天数据?
A: CMO 计算需要用到每日的价格变动(今日收盘价 - 昨日收盘价)。要计算过去 N 天的变动,我们需要 N+1 个收盘价数据(第 1 天的数据仅用于作为第 2 天的参考基准来计算变动)。