🚀 AI 一键生成 qmt 策略代码

基于 Pivot Points 的日内支撑阻力策略 Python 实现

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

问题描述

如何用 Python 编写 Pivot Points 枢轴点日内交易策略?

解决方案

Pivot Points 枢轴点日内交易策略

Pivot Points(枢轴点)是一种经典的日内交易策略,广泛用于期货、股票和外汇市场。它利用前一个交易日的最高价(High)、最低价(Low)和收盘价(Close)来计算当前交易日的关键支撑位和阻力位。

策略逻辑

本策略基于 经典 Pivot Points 公式 实现一个简单的 日内突破策略

  1. 计算关键点位(基于前一日数据):
    • 枢轴点 (P) = (前日最高 + 前日最低 + 前日收盘) / 3
    • 阻力位 1 (R1) = 2 * P - 前日最低
    • 支撑位 1 (S1) = 2 * P - 前日最高
  2. 交易信号(基于当前分钟数据):
    • 买入开仓:当价格突破 R1 时,视为多头强势,买入。
    • 卖出平仓(止损):当价格跌破 P(枢轴点)时,视为趋势反转,止损平仓。
    • 尾盘平仓:日内策略不持仓过夜,在收盘前(如 14:55)强制平仓。

Python 代码实现

以下代码适用于 QMT 平台的 Python 策略编辑器。该策略设计为在 1分钟或 5分钟 周期上运行(主图周期),但会内部调用 日线 数据进行计算。

# -*- coding: gbk -*-
import pandas as pd
import time

def init(ContextInfo):
    """
    初始化函数,策略启动时调用一次
    """
    # 1. 设置交易账号 (请替换为您的真实资金账号)
    ContextInfo.account_id = '6000000000' 
    ContextInfo.account_type = 'STOCK' # 股票: 'STOCK', 期货: 'FUTURE'
    ContextInfo.set_account(ContextInfo.account_id)
    
    # 2. 设置交易标的 (以浦发银行为例,也可用于期货如 'IF00.IF')
    ContextInfo.stock_code = '600000.SH'
    ContextInfo.set_universe([ContextInfo.stock_code])
    
    # 3. 策略参数
    ContextInfo.trade_qty = 100      # 每次交易数量 (股/手)
    ContextInfo.holding = 0          # 持仓状态: 0-无持仓, 1-多头
    
    # 4. 每日计算的枢轴点变量缓存
    ContextInfo.current_date = ''    # 记录当前日期,用于判断是否需要重新计算Pivot
    ContextInfo.P = 0                # Pivot Point
    ContextInfo.R1 = 0               # Resistance 1
    ContextInfo.S1 = 0               # Support 1

def get_pivot_points(ContextInfo):
    """
    计算枢轴点数据的辅助函数
    """
    # 获取过去2根日线数据 (昨天和今天,因为今天还没走完,我们需要的是昨天)
    # 注意:period='1d'
    daily_data = ContextInfo.get_market_data_ex(
        ['high', 'low', 'close'], 
        [ContextInfo.stock_code], 
        period='1d', 
        count=2,
        subscribe=True
    )
    
    if ContextInfo.stock_code not in daily_data:
        return False
        
    df = daily_data[ContextInfo.stock_code]
    
    # 确保有足够的数据(至少需要前一天的数据)
    # 在盘中运行时,df通常包含[昨天, 今天(未完成)],所以取 iloc[-2] 为昨天
    # 如果是回测,逻辑类似,取上一根已完成的日线
    if len(df) < 2:
        return False
        
    # 获取前一日数据
    prev_day = df.iloc[-2]
    
    high = prev_day['high']
    low = prev_day['low']
    close = prev_day['close']
    
    # 计算 Pivot Points
    P = (high + low + close) / 3
    R1 = 2 * P - low
    S1 = 2 * P - high
    
    # 更新到全局变量
    ContextInfo.P = P
    ContextInfo.R1 = R1
    ContextInfo.S1 = S1
    
    print(f"日期: {df.index[-1]} | 昨日数据 H:{high} L:{low} C:{close}")
    print(f"今日枢轴点: P={P:.2f}, R1={R1:.2f}, S1={S1:.2f}")
    
    return True

def handlebar(ContextInfo):
    """
    行情驱动函数,每根K线调用一次
    """
    # 获取当前K线的时间
    index = ContextInfo.barpos
    timetag = ContextInfo.get_bar_timetag(index)
    # 转换为日期字符串 YYYYMMDD
    current_date_str = timetag_to_datetime(timetag, '%Y%m%d')
    # 转换为时间字符串 HHMMSS
    current_time_str = timetag_to_datetime(timetag, '%H%M%S')
    
    # ----------------------------------------------------------------
    # 1. 每日开盘前或新的一天,重新计算 Pivot Points
    # ----------------------------------------------------------------
    if ContextInfo.current_date != current_date_str:
        if get_pivot_points(ContextInfo):
            ContextInfo.current_date = current_date_str
            ContextInfo.holding = 0 # 新的一天重置持仓状态逻辑(可选)
        else:
            print("数据不足,无法计算枢轴点")
            return

    # ----------------------------------------------------------------
    # 2. 获取当前最新价格
    # ----------------------------------------------------------------
    # 获取当前周期的最新收盘价
    current_price_data = ContextInfo.get_market_data_ex(
        ['close'], 
        [ContextInfo.stock_code], 
        period='follow', 
        count=1
    )
    
    if ContextInfo.stock_code not in current_price_data:
        return
        
    current_price = current_price_data[ContextInfo.stock_code].iloc[-1]['close']
    
    # ----------------------------------------------------------------
    # 3. 交易逻辑
    # ----------------------------------------------------------------
    
    # 尾盘平仓逻辑 (例如 14:55 之后强制平仓)
    if current_time_str >= '145500':
        if ContextInfo.holding > 0:
            print(f"尾盘平仓: 现价 {current_price}")
            passorder(24, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, ContextInfo.trade_qty, ContextInfo)
            ContextInfo.holding = 0
        return

    # 开仓逻辑: 价格突破 R1 且当前无持仓
    if ContextInfo.holding == 0:
        if current_price > ContextInfo.R1:
            print(f"突破 R1 ({ContextInfo.R1:.2f}),买入开仓: 现价 {current_price}")
            # 23: 买入, 1101: 单股/单账号/普通/股数下单, 5: 最新价
            passorder(23, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, ContextInfo.trade_qty, ContextInfo)
            ContextInfo.holding = 1
            
    # 止损逻辑: 持有多头,价格跌破 P (枢轴点)
    elif ContextInfo.holding > 0:
        if current_price < ContextInfo.P:
            print(f"跌破 Pivot ({ContextInfo.P:.2f}),止损平仓: 现价 {current_price}")
            # 24: 卖出
            passorder(24, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, ContextInfo.trade_qty, ContextInfo)
            ContextInfo.holding = 0

    # (可选) 绘图:在图表上画出 P, R1, S1
    ContextInfo.paint('Pivot', ContextInfo.P, -1, 0, 'yellow')
    ContextInfo.paint('R1', ContextInfo.R1, -1, 0, 'red')
    ContextInfo.paint('S1', ContextInfo.S1, -1, 0, 'green')

代码关键点解析

  1. 数据获取 (get_market_data_ex):

    • 我们在 get_pivot_points 函数中使用 period='1d' 获取日线数据。
    • count=2 是关键。在盘中运行时,返回的 DataFrame 最后一行通常是“今天(尚未结束)”的数据,倒数第二行才是“昨天(已结束)”的数据。因此我们取 iloc[-2] 来计算 Pivot Points。
  2. 时间控制:

    • 使用 timetag_to_datetime 将时间戳转换为易读的字符串。
    • ContextInfo.current_date 用于确保每天只计算一次 Pivot Points,避免重复计算消耗资源。
    • 增加了 145500 (14:55) 的尾盘强平逻辑,符合日内交易(Intraday)不持仓过夜的原则。
  3. 交易函数 (passorder):

    • 23 代表股票买入,24 代表股票卖出。
    • 1101 代表普通单账号按股数下单。
    • 5 代表使用最新价下单(市价成交的近似模拟)。
    • 注意:如果是期货交易,需要将 opType 改为期货对应的开平仓代码(如 0 开多, 1 平昨多 等),并将账号类型改为 FUTURE
  4. 绘图 (paint):

    • 代码最后使用 ContextInfo.paint 将计算出的 P、R1、S1 画在主图上,方便回测时直观验证策略逻辑。

如何运行

  1. 打开 QMT 客户端,进入 模型研究/策略编辑器
  2. 新建一个 Python 策略。
  3. 将上述代码复制粘贴进去。
  4. 修改 init 函数中的 account_id 为你的真实或模拟账号。
  5. 在右侧设置运行周期为 1分钟5分钟
  6. 点击 运行回测

常见问题与优化

  • Q: 如何做空?
    • A: 如果交易标的是期货或支持融券的股票,可以在 current_price < S1 时执行开空操作。对于期货,使用 passorder(3, ...) 开空,passorder(2, ...) 平今多。
  • Q: 为什么没有 R2, S2, R3, S3?
    • A: 为了代码简洁,示例只计算了第一层级。你可以根据公式 $R2 = P + (High - Low)$ 自行添加更多层级作为止盈目标。
  • Q: 止盈怎么设置?
    • A: 当前代码仅包含止损(跌破 P 点)和尾盘平仓。你可以添加逻辑:当价格达到 R2 时平仓止盈。

Q&A

Q: QMT 中如何获取前一天的日线数据而不包含当天的实时数据?
A: 使用 get_market_data_ex 获取日线时,通常会包含当天正在生成的 Bar。最稳妥的方法是获取 count=2 或更多,然后通过索引 iloc[-2] 获取确定的昨日数据。或者使用 get_history_data 并配合 ContextInfo.get_trading_dates 确定具体的日期范围。

Q: 这个策略适合什么品种?
A: Pivot Points 策略最适合波动性较大且日内趋势明显的品种,如 股指期货 (IF, IC)商品期货高波动率的 ETF。对于波动极小的银行股,效果可能不佳。

Q: passorder 中的 quickTrade 参数有什么用?
A: 在 passorder 的完整参数列表中有一个 quickTrade 参数。在回测模式下它无效。在实盘模式下,如果设置为 1,则信号触发即立即下单;如果为 0,通常会在当前 K 线走完后的下一根 K 线开盘时下单。本示例使用的是简化版调用,默认行为取决于 QMT 版本和设置,通常建议在实盘中明确指定该参数以获得更快的执行速度。