问题描述
解决方案
这是一个基于 QMT (QuantTrader) 平台的 Python 策略代码。
该策略实现了你描述的逻辑:
- 计算 CCI 指标(默认周期 N=14)。
- 买入信号:当 CCI 指标下穿 -100 时(前一根 K 线 > -100,当前 K 线 < -100),执行买入(示例为调整仓位到 100%)。
- 卖出信号:当 CCI 指标上穿 +100 时(前一根 K 线 < 100,当前 K 线 > 100),执行卖出(示例为清仓)。
QMT 策略代码
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
初始化函数,设定策略参数和账户
"""
# 1. 设置要操作的股票池 (这里以浦发银行为例,可自行修改)
ContextInfo.stock_code = '600000.SH'
ContextInfo.set_universe([ContextInfo.stock_code])
# 2. 设置资金账号 (请替换为你自己的资金账号)
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
ContextInfo.set_account(ContextInfo.account_id)
# 3. CCI 指标参数
ContextInfo.cci_n = 14 # CCI 计算周期
# 4. 策略运行参数
ContextInfo.period = '1d' # 运行周期,如 '1d', '15m'
def get_cci(high, low, close, n=14):
"""
计算 CCI 指标的辅助函数
"""
tp = (high + low + close) / 3
ma = tp.rolling(window=n).mean()
# 计算平均绝对偏差 (Mean Deviation)
# pandas 没有直接的 rolling_mad,需要用 apply 实现
def mean_deviation(x):
return np.mean(np.abs(x - np.mean(x)))
md = tp.rolling(window=n).apply(mean_deviation, raw=True)
cci = (tp - ma) / (0.015 * md)
return cci
def handlebar(ContextInfo):
"""
K线处理函数,每根K线执行一次
"""
# 获取当前正在处理的股票
stock = ContextInfo.stock_code
# 获取历史行情数据
# 多取一些数据以确保计算指标时前面没有 NaN
count = ContextInfo.cci_n + 20
# 使用 get_market_data_ex 获取数据 (推荐使用此接口)
# 返回格式: {stock_code: DataFrame}
data_map = ContextInfo.get_market_data_ex(
['high', 'low', 'close'],
[stock],
period=ContextInfo.period,
count=count,
dividend_type='front' # 前复权
)
if stock not in data_map:
return
df = data_map[stock]
# 如果数据长度不足,无法计算指标,直接返回
if len(df) < ContextInfo.cci_n:
return
# 计算 CCI
df['cci'] = get_cci(df['high'], df['low'], df['close'], ContextInfo.cci_n)
# 获取最近两根 K 线的 CCI 值
# iloc[-1] 是当前最新 K 线,iloc[-2] 是上一根 K 线
current_cci = df['cci'].iloc[-1]
prev_cci = df['cci'].iloc[-2]
# 打印当前 CCI 值方便调试 (在回测或实盘日志中查看)
# print(f"Time: {df.index[-1]}, CCI: {current_cci:.2f}")
# 避免数据计算初期的 NaN 值干扰
if np.isnan(current_cci) or np.isnan(prev_cci):
return
# --- 交易逻辑 ---
# 1. 买入条件:CCI 下穿 -100
# 逻辑:上一根 > -100 且 当前 < -100
if prev_cci > -100 and current_cci < -100:
print(f"触发买入信号: {stock}, 前值={prev_cci:.2f}, 现值={current_cci:.2f}")
# 示例:全仓买入 (调整目标仓位到 100%)
# 注意:实盘中请根据风控需求修改下单函数,如 order_shares 或 passorder
order_target_percent(stock, 1.0, ContextInfo, ContextInfo.account_id)
# 2. 卖出条件:CCI 上穿 +100
# 逻辑:上一根 < 100 且 当前 > 100
elif prev_cci < 100 and current_cci > 100:
print(f"触发卖出信号: {stock}, 前值={prev_cci:.2f}, 现值={current_cci:.2f}")
# 示例:清仓卖出 (调整目标仓位到 0%)
order_target_percent(stock, 0.0, ContextInfo, ContextInfo.account_id)
代码说明与注意事项
-
编码格式:
- 第一行
# -*- coding: gbk -*-是必须的,因为 QMT 的 Python 环境默认使用 GBK 编码,防止中文注释乱码。
- 第一行
-
CCI 计算逻辑:
- CCI 的标准公式中用到的是 平均绝对偏差 (Mean Deviation),而不是标准差 (Standard Deviation)。代码中使用了
rolling().apply()来准确计算 MD。 - 公式:$CCI = (TP - MA) / (0.015 \times MD)$。
- CCI 的标准公式中用到的是 平均绝对偏差 (Mean Deviation),而不是标准差 (Standard Deviation)。代码中使用了
-
交易信号逻辑:
- 下穿 -100:通常被视为超卖区域。在某些策略中,这被视为“跌破支撑,趋势转弱”的信号,但在震荡策略中,这可能被视为“抄底”信号。代码严格按照你的要求执行:
prev > -100且curr < -100时买入。 - 上穿 +100:通常被视为进入超买区域或强势区域。代码按照你的要求:
prev < 100且curr > 100时卖出。
- 下穿 -100:通常被视为超卖区域。在某些策略中,这被视为“跌破支撑,趋势转弱”的信号,但在震荡策略中,这可能被视为“抄底”信号。代码严格按照你的要求执行:
-
下单函数:
- 代码使用了
order_target_percent。这是一个高级封装函数,1.0代表买入至总资产的 100%(全仓),0.0代表卖出至 0%(空仓)。 - 实盘注意:如果你想买入固定数量(如 100 股),请将下单行改为
order_shares(stock, 100, ContextInfo, ContextInfo.account_id)。
- 代码使用了
-
如何运行:
- 将代码复制到 QMT 的【策略编辑器】中。
- 修改
init函数中的ContextInfo.account_id为你真实的资金账号。 - 在界面右侧设置好回测时间或直接点击“运行”进行实盘/模拟盘监控。
Q&A
Q: 为什么使用 get_market_data_ex 而不是 get_market_data?
A: get_market_data_ex 是 QMT 较新的接口,返回的数据结构更统一(字典包含 DataFrame),处理多只股票或单只股票时代码逻辑一致性更好,且性能通常更优。
Q: 如何修改 CCI 的周期,比如改成 20?
A: 在 init 函数中,将 ContextInfo.cci_n = 14 修改为 ContextInfo.cci_n = 20 即可。
Q: 这个策略是做趋势还是做反转?
A: 根据你设定的条件(下穿 -100 买入),这通常属于**左侧交易(逆势/抄底)**逻辑,即在价格急剧下跌进入超卖区时买入,博取反弹;而在价格冲高突破 +100 时卖出。请注意这种策略在单边下跌趋势中可能会面临风险。