问题描述
解决方案
这是一个关于如何在 QMT (Quantitative Trading Platform) 平台上实现 钱德动量摆动指标 (Chande Momentum Oscillator, CMO) 量化策略的详细指南。
1. 策略原理:CMO 指标
CMO 是由 Tushar Chande 发明的动量震荡指标。它通过计算特定周期内价格上涨总和与下跌总和的差值,除以总的价格变动量,来衡量市场的强弱。
公式:
$$CMO = \frac{Su - Sd}{Su + Sd} \times 100$$
- $Su$:N 日内收盘价上涨幅度的总和。
- $Sd$:N 日内收盘价下跌幅度的总和(取绝对值)。
- 取值范围:-100 到 +100。
策略逻辑(均值回归/超买超卖):
本策略采用经典的超买超卖逻辑:
- 买入信号:当 CMO 从下方穿过 超卖线(例如 -50),视为市场超卖反弹,执行买入。
- 卖出信号:当 CMO 从上方穿过 超买线(例如 +50),视为市场超买回调,执行卖出(或平仓)。
2. QMT 策略代码实现
以下代码是一个完整的 Python 策略,可以直接复制到 QMT 的策略编辑器中使用。代码包含了数据获取、指标计算和交易执行。
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
初始化函数,设定策略参数和变量
"""
# 1. 设置策略参数
ContextInfo.stock_code = '600000.SH' # 标的股票:浦发银行
ContextInfo.account_id = '6000000248' # 请替换为您真实的资金账号
ContextInfo.period = '1d' # 周期:日线
ContextInfo.cmo_n = 14 # CMO 计算周期
ContextInfo.overbought = 50 # 超买阈值
ContextInfo.oversold = -50 # 超卖阈值
# 2. 设置股票池(用于回测)
ContextInfo.set_universe([ContextInfo.stock_code])
# 3. 设置交易账号
ContextInfo.set_account(ContextInfo.account_id)
def calculate_cmo(close_prices, n):
"""
计算 CMO 指标的辅助函数
:param close_prices: 收盘价序列 (pandas Series)
:param n: 周期
:return: CMO 序列
"""
# 计算每日价格变动
delta = close_prices.diff()
# 分离上涨和下跌的幅度
# where(条件, 满足时的值, 不满足时的值)
up = np.where(delta > 0, delta, 0)
down = np.where(delta < 0, -delta, 0)
# 转换为 Series 以使用 rolling 函数
up_series = pd.Series(up)
down_series = pd.Series(down)
# 计算 N 周期内的上涨总和 (Su) 和下跌总和 (Sd)
sum_up = up_series.rolling(window=n).sum()
sum_down = down_series.rolling(window=n).sum()
# 计算 CMO: (Su - Sd) / (Su + Sd) * 100
# 注意处理分母为0的情况(虽然极少见)
total_move = sum_up + sum_down
cmo = 100 * (sum_up - sum_down) / total_move
# 填充 NaN 值
return cmo.fillna(0).values
def handlebar(ContextInfo):
"""
K线处理函数,每根K线执行一次
"""
# 获取当前 K 线索引
index = ContextInfo.barpos
# 获取当前时间
realtime = ContextInfo.get_bar_timetag(index)
# 1. 获取历史行情数据
# 获取足够长度的数据以计算指标,N + 缓冲
count = ContextInfo.cmo_n + 20
# 使用 get_market_data_ex 获取数据 (推荐使用 ex 接口)
# 返回格式: {code: dataframe}
market_data = ContextInfo.get_market_data_ex(
['close'],
[ContextInfo.stock_code],
period=ContextInfo.period,
count=count,
dividend_type='front', # 前复权
subscribe=True
)
if ContextInfo.stock_code not in market_data:
return
df = market_data[ContextInfo.stock_code]
# 确保数据量足够计算
if len(df) < ContextInfo.cmo_n + 1:
return
# 2. 计算 CMO 指标
close_prices = df['close']
cmo_values = calculate_cmo(close_prices, ContextInfo.cmo_n)
# 获取最近两个 CMO 值用于判断交叉
# cmo_curr: 当前 K 线的 CMO
# cmo_prev: 上一根 K 线的 CMO
cmo_curr = cmo_values[-1]
cmo_prev = cmo_values[-2]
# 打印日志方便调试 (仅在回测或实盘的最后一根K线打印)
if ContextInfo.is_last_bar():
print(f"时间: {timetag_to_datetime(realtime, '%Y-%m-%d %H:%M:%S')}, CMO: {cmo_curr:.2f}")
# 3. 交易逻辑
# 获取当前持仓
positions = ContextInfo.get_trade_detail_data(ContextInfo.account_id, 'stock', 'position')
current_holding = 0
for pos in positions:
if pos.m_strInstrumentID == ContextInfo.stock_code:
current_holding = pos.m_nVolume
break
# 信号 1: 买入 (CMO 上穿超卖线 -50)
# 逻辑: 上一刻 <= -50 且 当前 > -50
if cmo_prev <= ContextInfo.oversold and cmo_curr > ContextInfo.oversold:
if current_holding == 0:
print(f"买入信号触发: CMO上穿 {ContextInfo.oversold}")
# 全仓买入 (目标仓位 100%)
order_target_percent(ContextInfo.stock_code, 1.0, ContextInfo, ContextInfo.account_id)
# 信号 2: 卖出 (CMO 下穿超买线 +50)
# 逻辑: 上一刻 >= 50 且 当前 < 50
elif cmo_prev >= ContextInfo.overbought and cmo_curr < ContextInfo.overbought:
if current_holding > 0:
print(f"卖出信号触发: CMO下穿 {ContextInfo.overbought}")
# 清仓卖出 (目标仓位 0%)
order_target_percent(ContextInfo.stock_code, 0.0, ContextInfo, ContextInfo.account_id)
3. 代码关键点解析
-
数据获取 (
get_market_data_ex):- 我们使用了
get_market_data_ex接口,这是 QMT 推荐的获取行情数据的方式。 count参数设置为ContextInfo.cmo_n + 20,是为了确保有足够的数据进行滚动窗口计算(Rolling Window)。如果数据太少,计算出的 CMO 前面部分会是 NaN。
- 我们使用了
-
指标计算 (
calculate_cmo):- 虽然 QMT 内置了
talib库(可以直接调用talib.CMO),但为了保证代码在不同环境下的兼容性以及逻辑的透明度,我在代码中使用了pandas和numpy手动实现了 CMO 算法。 - 逻辑:先计算
diff,然后分离出up(上涨部分) 和down(下跌部分),最后用rolling(n).sum()求和并套用公式。
- 虽然 QMT 内置了
-
交易信号:
- 上穿 (Cross Over):
cmo_prev <= -50且cmo_curr > -50。这代表动量从极度悲观开始转强。 - 下穿 (Cross Under):
cmo_prev >= 50且cmo_curr < 50。这代表动量从极度乐观开始转弱。
- 上穿 (Cross Over):
-
下单执行 (
order_target_percent):- 使用
order_target_percent是最简单的仓位管理方式。 1.0代表调整仓位至总资产的 100%(全仓买入)。0.0代表调整仓位至 0%(清仓卖出)。
- 使用
4. 如何在 QMT 中运行
- 打开 QMT 客户端,进入 “模型研究” -> “策略编辑器”。
- 新建一个 Python 策略。
- 将上述代码完整复制粘贴到编辑器中。
- 修改参数:
- 将
ContextInfo.account_id修改为您自己的模拟或实盘资金账号。 - 如果需要,修改
stock_code为您想交易的股票。
- 将
- 点击 “编译” 检查是否有语法错误。
- 点击 “回测” 查看历史表现,或在行情图上运行查看信号。
Q&A
Q: 如何调整 CMO 的灵敏度?
A: 修改 init 函数中的 ContextInfo.cmo_n。数值越小(如 9),指标越敏感,信号越多但假信号也多;数值越大(如 20),指标越平滑,信号越少但滞后性增加。
Q: 为什么使用 get_market_data_ex 而不是 get_market_data?
A: get_market_data_ex 是 QMT 的新版接口,返回的数据结构更清晰(字典包含 DataFrame),且支持更多参数(如复权方式、订阅开关),是官方推荐的用法。
Q: 这个策略可以用于期货吗?
A: 可以。只需将 stock_code 改为期货合约代码(如 'IF2306.IF'),并将 order_target_percent 替换为期货专用的下单函数(如 buy_open, sell_close 等),因为期货涉及开平仓和保证金机制,不能简单使用目标价值下单。