🚀 AI 一键生成 qmt 策略代码

如何在 QMT 平台中编写一个基于 OBV 能量潮指标的量价分析策略?

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

问题描述

请帮我写一个基于 OBV 指标的交易策略

解决方案

这是一个基于 QMT 平台的 OBV (On-Balance Volume, 能量潮) 指标交易策略。

策略逻辑

OBV 指标通过统计成交量的变动趋势来推测股价趋势。

  1. OBV 计算
    • 如果今日收盘价 > 昨日收盘价,则今日 OBV = 昨日 OBV + 今日成交量。
    • 如果今日收盘价 < 昨日收盘价,则今日 OBV = 昨日 OBV - 今日成交量。
    • 如果今日收盘价 = 昨日收盘价,则今日 OBV = 昨日 OBV。
  2. 交易信号(OBV 均线交叉策略)
    • 买入信号:当 OBV 线 上穿 OBV 的 N 日移动平均线(MAOBV)时,视为资金流入,看涨买入。
    • 卖出信号:当 OBV 线 下穿 OBV 的 N 日移动平均线(MAOBV)时,视为资金流出,看跌卖出。

策略代码

# -*- coding: gbk -*-
import pandas as pd
import numpy as np

def init(ContextInfo):
    """
    策略初始化函数
    """
    # 1. 设置操作标的(示例:平安银行)
    ContextInfo.stock_code = '000001.SZ'
    
    # 2. 设置股票池
    ContextInfo.set_universe([ContextInfo.stock_code])
    
    # 3. 策略参数设置
    ContextInfo.obv_ma_period = 30  # OBV均线周期
    ContextInfo.trade_amount = 100  # 每次交易股数
    ContextInfo.account_id = 'YOUR_ACCOUNT_ID' # 请替换为您的资金账号
    
    # 4. 设置账号(实盘/回测必须)
    ContextInfo.set_account(ContextInfo.account_id)
    
    print("策略初始化完成,监控标的: {}, OBV均线周期: {}".format(ContextInfo.stock_code, ContextInfo.obv_ma_period))

def handlebar(ContextInfo):
    """
    K线周期运行函数
    """
    # 获取当前正在处理的K线索引
    index = ContextInfo.barpos
    
    # 获取当前图表的时间
    realtime = ContextInfo.get_bar_timetag(index)
    
    # 仅在最后一根K线(最新行情)或回测模式下运行逻辑
    # 注意:如果是实盘,通常只在最新K线运行;如果是回测,每根K线都运行
    if not ContextInfo.is_last_bar():
        # 回测模式下,为了提高速度,可以只在必要时计算,或者直接return让其按序列运行
        # 这里为了演示逻辑完整性,不做return,但在实盘中通常需要判断 is_last_bar
        pass

    # 1. 获取历史行情数据
    # 获取长度:均线周期 + 缓冲数据(用于计算OBV累积)
    data_len = ContextInfo.obv_ma_period + 60 
    
    # 使用 get_market_data_ex 获取数据 (推荐接口)
    # 注意:period='1d' 表示日线,dividend_type='front' 前复权
    market_data = ContextInfo.get_market_data_ex(
        fields=['close', 'volume'], 
        stock_code=[ContextInfo.stock_code], 
        period='1d', 
        count=data_len, 
        dividend_type='front'
    )
    
    if ContextInfo.stock_code not in market_data:
        return
        
    df = market_data[ContextInfo.stock_code]
    
    # 数据长度不足无法计算指标时返回
    if len(df) < ContextInfo.obv_ma_period + 1:
        return

    # 2. 计算 OBV 指标
    # 计算价格变化:今日收盘 - 昨日收盘
    df['price_change'] = df['close'].diff()
    
    # 生成符号向量:涨为1,跌为-1,平为0
    # np.sign: x>0 -> 1, x<0 -> -1, x=0 -> 0
    df['direction'] = np.sign(df['price_change'])
    
    # 修正:第一行 diff 后是 NaN,填充为 0
    df['direction'] = df['direction'].fillna(0)
    
    # 计算带符号的成交量
    df['signed_volume'] = df['direction'] * df['volume']
    
    # 计算 OBV (累积和)
    df['obv'] = df['signed_volume'].cumsum()
    
    # 计算 OBV 的移动平均线 (MAOBV)
    df['obv_ma'] = df['obv'].rolling(window=ContextInfo.obv_ma_period).mean()
    
    # 3. 获取最新信号数据
    # 获取最后两个数据点用于判断交叉
    curr_obv = df['obv'].iloc[-1]
    curr_ma = df['obv_ma'].iloc[-1]
    prev_obv = df['obv'].iloc[-2]
    prev_ma = df['obv_ma'].iloc[-2]
    
    # 打印调试信息 (可选)
    # print("时间: {}, OBV: {:.2f}, MA: {:.2f}".format(timetag_to_datetime(realtime, '%Y-%m-%d'), curr_obv, curr_ma))

    # 4. 获取当前持仓状态
    positions = get_trade_detail_data(ContextInfo.account_id, 'stock', 'position')
    current_vol = 0
    for pos in positions:
        if pos.m_strInstrumentID == ContextInfo.stock_code:
            current_vol = pos.m_nVolume
            break

    # 5. 交易逻辑判断
    
    # 金叉:OBV 上穿 MA (昨日 OBV < 昨日 MA 且 今日 OBV > 今日 MA)
    buy_signal = (prev_obv < prev_ma) and (curr_obv > curr_ma)
    
    # 死叉:OBV 下穿 MA (昨日 OBV > 昨日 MA 且 今日 OBV < 今日 MA)
    sell_signal = (prev_obv > prev_ma) and (curr_obv < curr_ma)

    # 执行买入
    if buy_signal:
        if current_vol == 0: # 简单逻辑:无持仓则买入
            print(">>> 产生买入信号: OBV金叉")
            # 23: 买入, 1101: 单股单账号, 5: 最新价
            passorder(23, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, ContextInfo.trade_amount, ContextInfo)
    
    # 执行卖出
    elif sell_signal:
        if current_vol > 0: # 简单逻辑:有持仓则卖出
            print(">>> 产生卖出信号: OBV死叉")
            # 24: 卖出, 1101: 单股单账号, 5: 最新价
            passorder(24, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, current_vol, ContextInfo)

代码使用说明

  1. 账号设置
    • 请务必将代码中的 ContextInfo.account_id = 'YOUR_ACCOUNT_ID' 替换为您真实的 QMT 资金账号。
  2. 标的与参数
    • ContextInfo.stock_code:修改为您想交易的股票代码(如 '600000.SH')。
    • ContextInfo.obv_ma_period:OBV 均线的周期,默认为 30 日,可根据需要调整。
  3. 数据获取
    • 使用了 get_market_data_ex 接口,这是 QMT 推荐的高效数据接口。
    • 使用了 pandas 库进行向量化计算,计算 OBV 及其均线。
  4. 交易执行
    • 使用了 passorder 函数进行下单。
    • opType 23 代表买入,24 代表卖出。
    • prType 5 代表使用最新价下单(实盘中可能需要考虑滑点或使用对手价)。
  5. 运行模式
    • 该策略可以在 回测模式 下查看历史表现。
    • 实盘/模拟盘 模式下,请确保在“模型交易”界面运行,并勾选正确的账号。

常见问题 Q&A

Q: 为什么 OBV 计算需要获取比均线周期更多的数据?
A: OBV 是一个累积指标,虽然理论上它需要从上市第一天开始累积,但在量化计算中,我们通常取一段足够长的时间窗口来计算相对变化。此外,计算 MA(移动平均)需要前 N 天的数据,因此获取的数据长度必须大于 obv_ma_period

Q: 如何在盘中实时触发交易?
A: 上述代码基于 K 线结束时的收盘价计算。如果需要在盘中实时触发(例如分钟线或 Tick 级),需要将 period 改为 '1m' 或更小周期,或者在 passorder 中设置 quickTrade=1(立即下单),但这需要更复杂的信号过滤以防止信号闪烁。

Q: 报错 NameError: name 'get_trade_detail_data' is not defined 怎么办?
A: 请确保您是在 QMT 的完整 Python 环境中运行。如果是回测,某些交易函数可能表现不同。在实盘模式下,这些函数是内置在全局命名空间中的。如果是在纯本地 Python IDE 调试,无法调用这些 QMT 专用函数。