问题描述
解决方案
这是一个关于量化交易策略设计和代码实现的问题。
策略概述:佳庆资金流量 (CMF)
佳庆资金流量 (Chaikin Money Flow, CMF) 是由 Marc Chaikin 开发的技术指标,用于衡量一段时间内资金的流入和流出情况。它结合了价格和成交量,核心思想是:如果收盘价接近当天的最高价,且成交量放大,说明有资金流入(积累);反之,如果收盘价接近最低价,且成交量放大,说明有资金流出(派发)。
策略逻辑:
- 计算 CMF 值:通常使用 20 日周期。
- 买入信号:当 CMF 值大于 0(或设定的正阈值,如 0.05),表示市场处于强势资金流入状态,执行买入。
- 卖出信号:当 CMF 值小于 0(或设定的负阈值,如 -0.05),表示市场处于资金流出状态,执行卖出或清仓。
QMT 策略代码实现
以下是基于 QMT Python API 的完整策略代码。代码包含了数据获取、指标计算和交易执行部分。
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
策略初始化函数
"""
# 1. 设置策略参数
ContextInfo.cmf_period = 20 # CMF计算周期,通常为20或21
ContextInfo.buy_threshold = 0.05 # 买入阈值,CMF大于此值买入
ContextInfo.sell_threshold = -0.05 # 卖出阈值,CMF小于此值卖出
# 2. 设置股票池 (此处示例使用沪深300成分股,实际使用可根据需要调整)
# 注意:回测时请在界面设置好股票池,或者在此处硬编码
ContextInfo.target_list = ['600000.SH', '000001.SZ', '600519.SH', '000300.SH']
ContextInfo.set_universe(ContextInfo.target_list)
# 3. 设置资金账号 (实盘或模拟盘需填写真实账号)
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
ContextInfo.set_account(ContextInfo.account_id)
# 4. 设置每只股票的仓位比例 (例如每只票最多买入总资金的20%)
ContextInfo.position_ratio = 0.2
def calculate_cmf(df, period):
"""
计算 CMF 指标
公式:
1. MFM = ((Close - Low) - (High - Close)) / (High - Low)
2. MFV = MFM * Volume
3. CMF = Sum(MFV, period) / Sum(Volume, period)
"""
# 避免除以0的情况,如果 High == Low,则分母设为极小值或处理 MFM 为 0
high_low_diff = df['high'] - df['low']
high_low_diff[high_low_diff == 0] = 0.0001 # 防止除零错误
# 计算资金流量乘数 (Money Flow Multiplier)
mfm = ((df['close'] - df['low']) - (df['high'] - df['close'])) / high_low_diff
# 计算资金流量体积 (Money Flow Volume)
mfv = mfm * df['volume']
# 计算周期内的 CMF
# 注意:这里我们需要的是最近 period 天的累加和
cmf = mfv.rolling(window=period).sum() / df['volume'].rolling(window=period).sum()
return cmf
def handlebar(ContextInfo):
"""
K线周期运行函数
"""
# 获取当前设置的股票池
stock_list = ContextInfo.get_universe()
# 获取当前 Bar 的索引
index = ContextInfo.barpos
# 获取当前时间,用于日志
realtime = ContextInfo.get_bar_timetag(index)
date_str = timetag_to_datetime(realtime, '%Y-%m-%d')
# 获取历史行情数据
# 我们需要 period + 缓冲期 的数据来计算 rolling
count = ContextInfo.cmf_period + 5
# 使用 get_market_data_ex 获取多股数据,效率更高
# 字段需要:最高、最低、收盘、成交量
market_data = ContextInfo.get_market_data_ex(
['high', 'low', 'close', 'volume'],
stock_list,
period='1d',
count=count,
dividend_type='front' # 前复权
)
# 获取当前持仓信息
positions = get_trade_detail_data(ContextInfo.account_id, 'stock', 'position')
holding_stocks = [obj.m_strInstrumentID + '.' + obj.m_strExchangeID for obj in positions]
for stock in stock_list:
if stock not in market_data:
continue
df = market_data[stock]
# 数据长度不足无法计算
if len(df) < ContextInfo.cmf_period:
continue
# 计算 CMF
cmf_series = calculate_cmf(df, ContextInfo.cmf_period)
current_cmf = cmf_series.iloc[-1] # 获取最新一个值的 CMF
# 排除无效值 (NaN)
if np.isnan(current_cmf):
continue
# --- 交易逻辑 ---
# 1. 买入逻辑: CMF > 买入阈值 且 当前无持仓
if current_cmf > ContextInfo.buy_threshold:
if stock not in holding_stocks:
print(f"{date_str} [买入信号] {stock} CMF: {current_cmf:.4f}")
# 使用目标比例下单,自动计算股数
order_target_percent(stock, ContextInfo.position_ratio, ContextInfo, ContextInfo.account_id)
# 2. 卖出逻辑: CMF < 卖出阈值 且 当前有持仓
elif current_cmf < ContextInfo.sell_threshold:
if stock in holding_stocks:
print(f"{date_str} [卖出信号] {stock} CMF: {current_cmf:.4f}")
# 清仓该股票
order_target_percent(stock, 0, ContextInfo, ContextInfo.account_id)
代码关键点解析
-
数据获取 (
get_market_data_ex):- 我们使用了
get_market_data_ex接口,这是 QMT 中获取历史数据最高效的方式。 - 我们需要
high(最高价),low(最低价),close(收盘价),volume(成交量) 四个字段来计算 CMF。 count设置为ContextInfo.cmf_period + 5,是为了确保有足够的数据进行rolling(滚动) 计算。
- 我们使用了
-
指标计算 (
calculate_cmf):- MFM (资金流量乘数): 衡量收盘价在当日价格区间的位置。如果收盘价靠上,MFM 为正;靠下,MFM 为负。
- MFV (资金流量体积): 将 MFM 与成交量结合。
- CMF: 是 MFV 的 N 日累加值除以 成交量的 N 日累加值。
- 代码中使用了
pandas的rolling(window=period).sum()来实现滑动窗口求和,这是向量化计算,速度很快。
-
交易执行 (
order_target_percent):- 使用了
order_target_percent函数。这是一个非常方便的高级交易函数。 - 当买入时,传入
0.2(即 20%),系统会自动根据当前账户总资产计算需要买入多少股,并自动调整到 100 的倍数。 - 当卖出时,传入
0,系统会自动计算当前持仓量并全部卖出。
- 使用了
-
防错处理:
- 在计算 MFM 时,分母是
High - Low。如果遇到一字涨停或跌停(High == Low),分母为 0 会导致报错。代码中加入了high_low_diff[high_low_diff == 0] = 0.0001进行平滑处理。 - 检查了数据长度,防止新股上市时间不足导致计算越界。
- 在计算 MFM 时,分母是
如何在 QMT 中运行
- 打开 QMT 客户端,进入 “模型研究” -> “策略编辑器”。
- 新建一个 Python 策略。
- 将上述代码完整复制粘贴到编辑器中。
- 修改
init函数中的ContextInfo.account_id为你的实际资金账号(如果是回测则不需要修改,但需在右侧参数栏设置初始资金)。 - 在右侧设置好回测的 “基准”、“开始时间”、“结束时间” 和 “周期” (选择日线 1d)。
- 点击 “编译” 检查无误后,点击 “回测” 查看结果。
常见问题 (Q&A)
Q: 为什么我的 CMF 计算结果和通达信/同花顺不一样?
A: CMF 的标准周期通常是 20 或 21 天。另外,不同的软件对于 High == Low 的处理方式可能略有不同,或者复权方式(前复权/不复权)不同也会导致计算结果差异。建议在 QMT 中统一使用 front (前复权)。
Q: 如何过滤掉停牌的股票?
A: 可以在 handlebar 中使用 ContextInfo.is_suspended_stock(stock) 函数进行判断,如果是停牌股票则 continue 跳过。
Q: 这个策略可以用于分钟线吗?
A: 可以。只需在回测或运行时将周期设置为 1m, 5m 等,并将 get_market_data_ex 中的 period 参数修改为对应的分钟周期即可。但在分钟级别上,CMF 的阈值可能需要重新调整,因为日内资金流向波动更剧烈。