问题描述
解决方案
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 对象,且在 init 和 handlebar 函数中都作为参数传入,利用 Python 的动态特性,我们可以直接向其添加自定义属性来存储变量。
核心机制
- 初始化 (init):在
init(ContextInfo)中定义并初始化变量。这是为了确保变量在使用前已经存在,避免AttributeError。 - 读写与更新 (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天!")
运行流程解析
- Start: 平台调用
init(ContextInfo)。ContextInfo.up_days_count被设为 0。 - Bar 0: 平台调用
handlebar(ContextInfo)。代码读取up_days_count(0),逻辑判断后可能将其加 1。 - Bar 1: 平台再次调用
handlebar(ContextInfo)。此时读取到的up_days_count是 Bar 0 运行结束后的值(0 或 1),而不是初始值 0。 - Loop: 如此循环,实现了状态的传递。
3. 注意事项与最佳实践
在使用 ContextInfo 传递自定义变量时,请注意以下几点:
-
命名冲突风险:
- 不要使用 QMT 内置的属性名称作为自定义变量名。
- 禁止覆盖的内置属性:
barpos,period,stockcode,capital,benchmark,universe等。 - 建议:给自定义变量加前缀,例如
ContextInfo.g_count或ContextInfo.user_status。
-
必须在 init 中初始化:
- 虽然 Python 允许随时绑定属性,但强烈建议在
init中对所有需要用到的全局变量赋初值(如None,0,[])。如果在handlebar中直接读取一个未定义的属性,会导致程序报错崩溃。
- 虽然 Python 允许随时绑定属性,但强烈建议在
-
对象引用问题:
ContextInfo传递的是对象的引用。这意味着你在函数内部对ContextInfo属性的修改,会直接影响到该对象本身,从而影响后续的执行。
-
回测与实盘的区别:
- 在回测模式下,
ContextInfo承载的数据流是连续的历史时间序列。 - 在实盘模式下,如果策略重启,
init会重新运行,ContextInfo中的自定义变量会被重置为初始值(除非你使用了外部文件或数据库来持久化存储数据)。
- 在回测模式下,
Q&A
Q: 为什么不直接使用 Python 的 global 关键字来定义全局变量?
A: 虽然可以使用 global 关键字,但在量化框架中,使用 ContextInfo 是更规范的做法。
- 封装性:
ContextInfo将策略的所有状态封装在一个对象中,便于管理和调试。 - 框架兼容性:QMT 框架设计初衷就是通过
ContextInfo传递状态,这样可以避免全局命名空间污染,且在某些多线程或特殊运行模式下更安全。
Q: ContextInfo 可以存储哪些类型的数据?
A: 它可以存储任何 Python 支持的数据类型,包括:
- 基础类型:
int,float,bool,str - 集合类型:
list,dict,set,tuple - 复杂对象:
pandas.DataFrame,numpy.array,甚至是你自定义的类的实例。
Q: 如何查看 ContextInfo 中有哪些内置属性?
A: 你可以在 init 或 handlebar 中使用 dir(ContextInfo) 命令打印该对象的所有属性和方法列表,从而了解其内部结构。