问题描述
解决方案
在 QMT (Quantitative Trading Platform) 中,避免使用 Python 原生 global 关键字来保存策略状态的最佳实践是利用平台提供的 ContextInfo 对象。
ContextInfo 是一个贯穿策略整个生命周期的对象。它不仅包含 API 方法,还是一个容器,允许你将自定义变量绑定为它的属性。这样,变量就可以在 init、handlebar 以及其他回调函数之间传递和持久化,起到类似“类实例属性”的作用,从而完全替代全局变量。
核心思路
- 初始化状态:在
init(ContextInfo)中,通过ContextInfo.变量名 = 初始值的方式定义参数和状态变量。 - 读取与更新状态:在
handlebar(ContextInfo)中,通过ContextInfo.变量名来读取或修改这些值。
示例代码:双均线策略(无全局变量版)
下面是一个完整的双均线策略示例。该策略将所有参数(如均线周期、股票代码、账号)和状态都保存在 ContextInfo 对象上。
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
初始化函数:在这里定义所有需要的状态变量,绑定到 ContextInfo 上
"""
print("策略初始化...")
# 1. 绑定策略参数到 ContextInfo
ContextInfo.stock_code = '600000.SH' # 交易标的:浦发银行
ContextInfo.short_period = 5 # 短周期
ContextInfo.long_period = 20 # 长周期
ContextInfo.account_id = '6000000248' # 资金账号 (请修改为实际账号)
ContextInfo.trade_qty = 100 # 每次交易数量
# 2. 设置股票池
ContextInfo.set_universe([ContextInfo.stock_code])
# 3. 绑定自定义状态变量 (替代全局变量)
# 例如:记录上一次的交易信号,或者自定义的持仓状态
ContextInfo.last_signal = 0 # 0:无信号, 1:金叉, -1:死叉
# 4. 设置账号
ContextInfo.set_account(ContextInfo.account_id)
def handlebar(ContextInfo):
"""
行情驱动函数:通过 ContextInfo 获取参数和更新状态
"""
# 获取当前K线位置
index = ContextInfo.barpos
# 获取当前主图代码(或者使用我们在 init 中定义的 ContextInfo.stock_code)
code = ContextInfo.stock_code
# 获取历史行情数据 (使用 get_market_data_ex)
# 获取足够计算长周期均线的数据量
data_len = ContextInfo.long_period + 5
# 获取日线数据
market_data = ContextInfo.get_market_data_ex(
fields=['close'],
stock_code=[code],
period='1d',
count=data_len,
dividend_type='front'
)
# 检查数据是否获取成功
if code not in market_data or market_data[code].empty:
return
df = market_data[code]
close_prices = df['close']
# 确保数据长度足够
if len(close_prices) < ContextInfo.long_period:
return
# 计算均线
# 注意:这里直接使用 ContextInfo.short_period 读取参数
ma_short = close_prices.rolling(window=ContextInfo.short_period).mean()
ma_long = close_prices.rolling(window=ContextInfo.long_period).mean()
# 获取最新和上一个时间点的均线值
current_short = ma_short.iloc[-1]
current_long = ma_long.iloc[-1]
prev_short = ma_short.iloc[-2]
prev_long = ma_long.iloc[-2]
# --- 交易逻辑 ---
# 1. 金叉判断 (短线上穿长线)
is_golden_cross = (prev_short <= prev_long) and (current_short > current_long)
# 2. 死叉判断 (短线下穿长线)
is_death_cross = (prev_short >= prev_long) and (current_short < current_long)
# 获取当前持仓 (为了演示,这里通过 ContextInfo 获取实际持仓,而不是依赖全局变量记录)
positions = get_trade_detail_data(ContextInfo.account_id, 'stock', 'position')
current_holding = 0
for pos in positions:
if pos.m_strInstrumentID + '.' + pos.m_strExchangeID == code:
current_holding = pos.m_nVolume
break
# 执行买入
if is_golden_cross:
# 只有当 ContextInfo.last_signal 不为 1 时才打印,避免重复日志
if ContextInfo.last_signal != 1:
print(f"[{timetag_to_datetime(ContextInfo.get_bar_timetag(index), '%Y-%m-%d')}] 金叉出现,尝试买入")
if current_holding == 0:
# 使用 ContextInfo.trade_qty 读取交易数量
passorder(23, 1101, ContextInfo.account_id, code, 5, -1, ContextInfo.trade_qty, ContextInfo)
print(f"买入 {code} {ContextInfo.trade_qty} 股")
# 更新状态:记录当前信号状态到 ContextInfo
ContextInfo.last_signal = 1
# 执行卖出
elif is_death_cross:
if ContextInfo.last_signal != -1:
print(f"[{timetag_to_datetime(ContextInfo.get_bar_timetag(index), '%Y-%m-%d')}] 死叉出现,尝试卖出")
if current_holding > 0:
passorder(24, 1101, ContextInfo.account_id, code, 5, -1, current_holding, ContextInfo)
print(f"卖出 {code} {current_holding} 股")
# 更新状态:记录当前信号状态到 ContextInfo
ContextInfo.last_signal = -1
代码解析
-
ContextInfo的角色:- 在 Python 的类(Class)编程中,我们使用
self.variable来在方法间共享数据。 - 在 QMT 的脚本模式中,
ContextInfo扮演了类似self的角色。 - 代码中没有出现
global short_period或global last_signal。所有的变量都挂载在ContextInfo下(例如ContextInfo.short_period)。
- 在 Python 的类(Class)编程中,我们使用
-
状态持久化:
ContextInfo.last_signal被用来记录上一次的信号状态。即使handlebar函数执行完毕,下一次行情到来再次触发handlebar时,ContextInfo.last_signal依然保留着上一次赋值的内容。
-
参数管理:
- 将
stock_code、account_id等硬编码的参数全部放在init中并绑定到ContextInfo,使得handlebar逻辑更加纯粹,修改参数只需在init中进行。
- 将
总结
在 QMT 中编写策略时,应当始终优先使用 ContextInfo 来存储状态。这不仅避免了全局变量带来的命名空间污染和潜在的逻辑混乱,也使得策略代码结构更清晰,更符合 QMT 框架的设计规范。
Q&A
Q: 为什么在 QMT 中不推荐使用 Python 的 global 关键字?
A: 虽然技术上可行,但使用 global 会导致变量散落在模块层级,难以管理。在进行回测或实盘切换,或者策略重置时,全局变量的状态管理可能会变得复杂。使用 ContextInfo 可以确保所有状态都封装在策略实例的上下文中,生命周期由平台管理,更加安全规范。
Q: ContextInfo 中可以存储什么类型的数据?
A: ContextInfo 是一个 Python 对象,你可以将任何 Python 数据类型绑定给它,包括整数、浮点数、字符串、列表、字典,甚至是 Pandas DataFrame 或自定义类的实例。
Q: 如果我在 handlebar 中修改了 ContextInfo.my_var,下一次 handlebar 调用时它会重置吗?
A: 不会。ContextInfo 对象在策略启动后一直存在,直到策略停止。你在 handlebar 中对 ContextInfo 属性所做的任何修改都会保留到下一次调用。只有在 init 函数重新运行时(例如重启策略),这些值才会被重置。