问题描述
解决方案
这是一个基于 CMO (Chande Momentum Oscillator, 钱德动量摆动指标) 的交易策略实现。
策略说明
CMO 指标简介:
钱德动量摆动指标(CMO)由 Tushar Chande 发明,用于衡量证券价格动量的强度。它的取值范围在 -100 到 +100 之间。
- 计算公式:$CMO = \frac{(Su - Sd)}{(Su + Sd)} \times 100$
- $Su$:N日内收盘价上涨幅度的总和。
- $Sd$:N日内收盘价下跌幅度的绝对值总和。
交易逻辑(均值回归策略):
本策略采用经典的超买超卖逻辑:
- 买入信号:当 CMO 值低于 超卖线(例如 -50)时,认为市场过度悲观,可能反弹,执行买入。
- 卖出信号:当 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:
- 增加趋势过滤:CMO 在强趋势行情中可能会出现“钝化”(长期停留在超买或超卖区)。可以结合均线(如 MA200)判断大趋势,只在上升趋势中做多。
- 调整阈值:默认的 +/- 50 比较激进。如果希望交易频率降低但信号更可靠,可以尝试调整为 +/- 60 或 +/- 70。
- 止损机制:当前代码是纯指标反转策略,建议加入固定比例止损(如亏损 5% 止损)来控制风险。
Q: 为什么代码中使用 attribute_history 获取 n+1 天数据?
A: CMO 计算需要用到每日的价格变动(今日收盘价 - 昨日收盘价)。要计算过去 N 天的变动,我们需要 N+1 个收盘价数据(第 1 天的数据仅用于作为第 2 天的参考基准来计算变动)。