🚀 AI 一键生成 qmt 策略代码

捕捉主力资金流向:CMF 指标选股策略 Python 实现

qmt | 发布时间: 2025/12/23
以下内容由 EasyQuant 生成。

问题描述

如何编写基于佳庆资金流量 (CMF) 的选股策略?

解决方案

这是一个关于量化交易策略设计和代码实现的问题。

策略概述:佳庆资金流量 (CMF)

佳庆资金流量 (Chaikin Money Flow, CMF) 是由 Marc Chaikin 开发的技术指标,用于衡量一段时间内资金的流入和流出情况。它结合了价格和成交量,核心思想是:如果收盘价接近当天的最高价,且成交量放大,说明有资金流入(积累);反之,如果收盘价接近最低价,且成交量放大,说明有资金流出(派发)。

策略逻辑:

  1. 计算 CMF 值:通常使用 20 日周期。
  2. 买入信号:当 CMF 值大于 0(或设定的正阈值,如 0.05),表示市场处于强势资金流入状态,执行买入。
  3. 卖出信号:当 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)

代码关键点解析

  1. 数据获取 (get_market_data_ex):

    • 我们使用了 get_market_data_ex 接口,这是 QMT 中获取历史数据最高效的方式。
    • 我们需要 high (最高价), low (最低价), close (收盘价), volume (成交量) 四个字段来计算 CMF。
    • count 设置为 ContextInfo.cmf_period + 5,是为了确保有足够的数据进行 rolling (滚动) 计算。
  2. 指标计算 (calculate_cmf):

    • MFM (资金流量乘数): 衡量收盘价在当日价格区间的位置。如果收盘价靠上,MFM 为正;靠下,MFM 为负。
    • MFV (资金流量体积): 将 MFM 与成交量结合。
    • CMF: 是 MFV 的 N 日累加值除以 成交量的 N 日累加值。
    • 代码中使用了 pandasrolling(window=period).sum() 来实现滑动窗口求和,这是向量化计算,速度很快。
  3. 交易执行 (order_target_percent):

    • 使用了 order_target_percent 函数。这是一个非常方便的高级交易函数。
    • 当买入时,传入 0.2 (即 20%),系统会自动根据当前账户总资产计算需要买入多少股,并自动调整到 100 的倍数。
    • 当卖出时,传入 0,系统会自动计算当前持仓量并全部卖出。
  4. 防错处理:

    • 在计算 MFM 时,分母是 High - Low。如果遇到一字涨停或跌停(High == Low),分母为 0 会导致报错。代码中加入了 high_low_diff[high_low_diff == 0] = 0.0001 进行平滑处理。
    • 检查了数据长度,防止新股上市时间不足导致计算越界。

如何在 QMT 中运行

  1. 打开 QMT 客户端,进入 “模型研究” -> “策略编辑器”
  2. 新建一个 Python 策略
  3. 将上述代码完整复制粘贴到编辑器中。
  4. 修改 init 函数中的 ContextInfo.account_id 为你的实际资金账号(如果是回测则不需要修改,但需在右侧参数栏设置初始资金)。
  5. 在右侧设置好回测的 “基准”“开始时间”“结束时间”“周期” (选择日线 1d)。
  6. 点击 “编译” 检查无误后,点击 “回测” 查看结果。

常见问题 (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 的阈值可能需要重新调整,因为日内资金流向波动更剧烈。