问题描述
解决方案
这是一个基于 佳庆摆动指标 (Chaikin Oscillator, CHO) 的 QMT 量化策略。
策略原理
佳庆摆动指标 (CHO) 是通过计算累积/派发线 (Accumulation/Distribution Line, ADL) 的移动平均线差值来衡量市场的动量。
- 计算公式:$CHO = EMA(ADL, ShortPeriod) - EMA(ADL, LongPeriod)$
- 默认参数:短期周期通常为 3,长期周期通常为 10。
- 交易逻辑:
- 买入信号:当 CHO 从下向上穿越 0 轴时(金叉),视为资金流入,动量转强,执行买入。
- 卖出信号:当 CHO 从上向下穿越 0 轴时(死叉),视为资金流出,动量转弱,执行卖出(清仓)。
策略代码
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
import talib
def init(ContextInfo):
"""
策略初始化函数
"""
# 设置要交易的股票池,这里以平安银行为例
ContextInfo.target_list = ['000001.SZ']
ContextInfo.set_universe(ContextInfo.target_list)
# 设置资金账号 (请替换为您自己的资金账号)
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
ContextInfo.set_account(ContextInfo.account_id)
# CHO 指标参数
ContextInfo.fast_period = 3 # 短期 EMA 周期
ContextInfo.slow_period = 10 # 长期 EMA 周期
# 设置回测参数 (仅在回测模式下生效,实盘需在界面设置)
ContextInfo.set_slippage(1, 0.002) # 设置滑点
ContextInfo.set_commission(0, [0.0003, 0.0013, 0.0003, 0.0003, 0.0003, 5]) # 设置手续费
def handlebar(ContextInfo):
"""
K线周期运行函数
"""
# 获取当前设置的股票池
stock_list = ContextInfo.get_universe()
# 获取当前周期 (例如 '1d')
period = ContextInfo.period
# 批量获取历史行情数据
# CHO 需要 High, Low, Close, Volume 四个字段
# count 设置为 100 以确保有足够的数据计算 EMA
data_map = ContextInfo.get_market_data_ex(
['high', 'low', 'close', 'volume'],
stock_list,
period=period,
count=100,
dividend_type='front' # 使用前复权
)
for stock in stock_list:
# 检查该股票是否有数据
if stock not in data_map:
continue
df = data_map[stock]
# 数据长度不足以计算指标时跳过
if len(df) < ContextInfo.slow_period + 5:
continue
# 提取数据并转换为 numpy 数组 (TA-Lib 需要 numpy 格式)
high = df['high'].values
low = df['low'].values
close = df['close'].values
volume = df['volume'].values
# 处理 NaN 值 (简单的清洗,防止计算报错)
if np.isnan(close).any():
continue
# --- 计算 CHO 指标 ---
# talib.ADOSC(high, low, close, volume, fastperiod, slowperiod)
try:
cho = talib.ADOSC(
high,
low,
close,
volume,
fastperiod=ContextInfo.fast_period,
slowperiod=ContextInfo.slow_period
)
except Exception as e:
print(f"计算指标出错 {stock}: {e}")
continue
# 获取最近两个周期的 CHO 值
curr_cho = cho[-1] # 当前 K 线 CHO
prev_cho = cho[-2] # 上一根 K 线 CHO
# 过滤无效的指标值
if np.isnan(curr_cho) or np.isnan(prev_cho):
continue
# --- 交易逻辑 ---
# 获取当前持仓
positions = get_trade_detail_data(ContextInfo.account_id, 'stock', 'position')
current_pos = 0
for pos in positions:
if pos.m_strInstrumentID + '.' + pos.m_strExchangeID == stock:
current_pos = pos.m_nVolume
break
# 1. 买入信号:CHO 上穿 0 轴 (金叉)
if prev_cho < 0 and curr_cho > 0:
# 如果当前没有持仓,则全仓买入 (目标仓位 100%)
# 注意:这里使用 order_target_percent 需要在回测或实盘中正确配置资金
print(f"{stock} 触发买入信号: CHO上穿0轴 ({prev_cho:.2f} -> {curr_cho:.2f})")
order_target_percent(stock, 1.0, ContextInfo, ContextInfo.account_id)
# 2. 卖出信号:CHO 下穿 0 轴 (死叉)
elif prev_cho > 0 and curr_cho < 0:
# 如果有持仓,则清仓 (目标仓位 0%)
if current_pos > 0:
print(f"{stock} 触发卖出信号: CHO下穿0轴 ({prev_cho:.2f} -> {curr_cho:.2f})")
order_target_percent(stock, 0.0, ContextInfo, ContextInfo.account_id)
代码详解
-
初始化 (
init):- 设定了交易标的(示例为平安银行
000001.SZ)。 - 设定了 CHO 指标的快慢周期参数(3 和 10)。
- 绑定了资金账号。
- 设定了交易标的(示例为平安银行
-
数据获取 (
handlebar):- 使用
ContextInfo.get_market_data_ex接口获取high,low,close,volume数据。这是计算 CHO 必不可少的四个要素。 - 使用了
dividend_type='front'进行前复权,保证价格连续性,避免因分红派息导致的指标失真。
- 使用
-
指标计算:
- 利用 QMT 内置的
talib库中的ADOSC函数直接计算佳庆摆动指标。 talib.ADOSC实际上就是 Chaikin Oscillator 的标准实现函数。
- 利用 QMT 内置的
-
交易信号:
- 金叉:
prev_cho < 0且curr_cho > 0。 - 死叉:
prev_cho > 0且curr_cho < 0。
- 金叉:
-
下单执行:
- 使用
order_target_percent函数。 - 买入时设定目标仓位为
1.0(即满仓该股票,如果是多只股票需调整比例)。 - 卖出时设定目标仓位为
0.0(即清仓)。
- 使用
注意事项
- 账号设置:请务必将代码中的
'YOUR_ACCOUNT_ID'替换为您实际的 QMT 资金账号。 - 数据长度:CHO 依赖 EMA 计算,需要一定的历史数据预热。代码中
count=100能够满足大部分需求,但在刚上市的新股上可能会因为数据不足计算出NaN。 - 实盘与回测:该策略既可用于回测,也可用于实盘。在实盘模式下,建议结合
ContextInfo.is_last_bar()判断,防止在 K 线未走完时信号闪烁。
Q&A
Q: 为什么使用 get_market_data_ex 而不是 get_market_data?
A: get_market_data_ex 是 QMT 推荐的新版数据接口,它返回的数据结构更清晰(字典包含 DataFrame),且在处理多只股票和多字段时性能更好,更易于进行向量化计算。
Q: CHO 指标和 MACD 有什么区别?
A: MACD 是基于价格的移动平均线差值,而 CHO 是基于累积/派发线 (ADL) 的移动平均线差值。CHO 结合了价格和成交量,更能反映资金流动的动量。
Q: 如何在多只股票上运行此策略?
A: 代码已经支持多只股票。只需在 init 函数中修改 ContextInfo.target_list,例如 ['000001.SZ', '600000.SH'],并在下单逻辑中将 order_target_percent 的比例调整为 1.0 / len(stock_list) 即可实现等权买入。