🚀 AI 一键生成 qmt 策略代码

QMT 策略核心:ContextInfo 对象的正确使用与技巧

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

问题描述

请详细解释一下策略中的上下文(Context)对象,以及如何用它来存储和传递自定义变量?

解决方案

QMT 策略中的 ContextInfo 对象详解

在 QMT(Quantitative Trading Platform)的 Python 策略开发中,ContextInfo 是最核心、最重要的对象。它贯穿于策略运行的整个生命周期,充当着环境容器API 接口入口以及全局状态载体的角色。

以下是对 ContextInfo 对象的详细解释,以及如何使用它来管理自定义变量。

1. 什么是 ContextInfo 对象?

ContextInfo 是 QMT 平台在运行策略时自动创建并传递给策略函数的一个 Python 对象。它的主要作用体现在以下三个方面:

(1) API 接口的入口

QMT 提供的绝大多数功能函数都是作为 ContextInfo 的成员方法存在的。

  • 获取数据:如 ContextInfo.get_market_data_ex()(获取行情)、ContextInfo.get_financial_data()(获取财务数据)。
  • 交易操作:虽然 passorder 是独立函数,但在回测模式下,资金账号、费率设置等都依赖 ContextInfo 的属性(如 ContextInfo.capital)。
  • 环境感知:获取当前运行到了哪一根 K 线(ContextInfo.barpos)、当前策略运行的周期(ContextInfo.period)等。

(2) 全局上下文环境 (Global Context)

在 Python 策略中,init() 只运行一次,而 handlebar() 会随着 K 线或 Tick 的更新被多次调用。普通的局部变量在函数调用结束后会被销毁。ContextInfo 对象在策略启动时被创建,并一直存活到策略停止。因此,它是存储跨函数跨周期数据的最佳场所。

(3) 策略状态的载体

它记录了策略运行时的所有状态信息,包括回测的起止时间、股票池设定、基准设定等。


2. 如何存储和传递自定义变量?

由于 ContextInfo 是一个标准的 Python 对象,且在 inithandlebar 函数中都作为参数传入,利用 Python 的动态特性,我们可以直接向其添加自定义属性来存储变量。

核心机制

  1. 初始化 (init):在 init(ContextInfo) 中定义并初始化变量。这是为了确保变量在使用前已经存在,避免 AttributeError
  2. 读写与更新 (handlebar):在 handlebar(ContextInfo) 中读取、修改这些变量。由于 ContextInfo 在多次调用中是同一个实例,修改后的值会保留到下一次调用。

代码示例

假设我们需要实现一个简单的逻辑:记录策略运行以来,收盘价上涨的天数。我们需要一个变量来累加计数,这个变量必须在不同的 K 线之间保持记忆。

# -*- coding: gbk -*-

def init(ContextInfo):
    """
    初始化函数:策略启动时调用一次
    """
    # 1. 在 ContextInfo 上绑定自定义变量
    # 建议使用 distinctive 的命名,避免与系统内置属性冲突
    ContextInfo.up_days_count = 0  
    ContextInfo.last_entry_price = None
    ContextInfo.my_custom_flag = False
    
    print("策略初始化完成,自定义变量已设置。")

def handlebar(ContextInfo):
    """
    行情事件函数:每根 K 线调用一次
    """
    # 获取当前 K 线索引
    index = ContextInfo.barpos
    
    # 获取收盘价数据 (取当前根和前一根)
    # 注意:get_market_data_ex 返回的是字典,需要解析
    # 这里为了演示简单,使用伪代码逻辑获取收盘价
    # 实际开发建议使用 get_market_data_ex
    close_data = ContextInfo.get_market_data(['close']) 
    
    # 确保有足够的数据进行比较
    if len(close_data) < 2:
        return

    # 2. 读取并更新 ContextInfo 中的自定义变量
    current_close = close_data[-1]
    prev_close = close_data[-2]
    
    if current_close > prev_close:
        # 修改变量值,这个值会被保存到下一次 handlebar 调用
        ContextInfo.up_days_count += 1
        print(f"K线 {index}: 收盘价上涨。累计上涨天数: {ContextInfo.up_days_count}")
    
    # 3. 传递逻辑示例
    # 如果累计上涨超过 5 天且未标记,则设置标记
    if ContextInfo.up_days_count > 5 and not ContextInfo.my_custom_flag:
        ContextInfo.my_custom_flag = True
        print("触发条件:累计上涨超过5天!")

运行流程解析

  1. Start: 平台调用 init(ContextInfo)ContextInfo.up_days_count 被设为 0。
  2. Bar 0: 平台调用 handlebar(ContextInfo)。代码读取 up_days_count (0),逻辑判断后可能将其加 1。
  3. Bar 1: 平台再次调用 handlebar(ContextInfo)。此时读取到的 up_days_count 是 Bar 0 运行结束后的值(0 或 1),而不是初始值 0。
  4. Loop: 如此循环,实现了状态的传递。

3. 注意事项与最佳实践

在使用 ContextInfo 传递自定义变量时,请注意以下几点:

  1. 命名冲突风险

    • 不要使用 QMT 内置的属性名称作为自定义变量名。
    • 禁止覆盖的内置属性barpos, period, stockcode, capital, benchmark, universe 等。
    • 建议:给自定义变量加前缀,例如 ContextInfo.g_countContextInfo.user_status
  2. 必须在 init 中初始化

    • 虽然 Python 允许随时绑定属性,但强烈建议在 init 中对所有需要用到的全局变量赋初值(如 None, 0, [])。如果在 handlebar 中直接读取一个未定义的属性,会导致程序报错崩溃。
  3. 对象引用问题

    • ContextInfo 传递的是对象的引用。这意味着你在函数内部对 ContextInfo 属性的修改,会直接影响到该对象本身,从而影响后续的执行。
  4. 回测与实盘的区别

    • 回测模式下,ContextInfo 承载的数据流是连续的历史时间序列。
    • 实盘模式下,如果策略重启,init 会重新运行,ContextInfo 中的自定义变量会被重置为初始值(除非你使用了外部文件或数据库来持久化存储数据)。

Q&A

Q: 为什么不直接使用 Python 的 global 关键字来定义全局变量?
A: 虽然可以使用 global 关键字,但在量化框架中,使用 ContextInfo 是更规范的做法。

  1. 封装性ContextInfo 将策略的所有状态封装在一个对象中,便于管理和调试。
  2. 框架兼容性:QMT 框架设计初衷就是通过 ContextInfo 传递状态,这样可以避免全局命名空间污染,且在某些多线程或特殊运行模式下更安全。

Q: ContextInfo 可以存储哪些类型的数据?
A: 它可以存储任何 Python 支持的数据类型,包括:

  • 基础类型:int, float, bool, str
  • 集合类型:list, dict, set, tuple
  • 复杂对象:pandas.DataFrame, numpy.array,甚至是你自定义的类的实例。

Q: 如何查看 ContextInfo 中有哪些内置属性?
A: 你可以在 inithandlebar 中使用 dir(ContextInfo) 命令打印该对象的所有属性和方法列表,从而了解其内部结构。