问题描述
解决方案
在 QMT 量化交易平台中,init(ContextInfo) 是策略的初始化函数。它是策略生命周期的起点,在整个策略运行过程中(无论是回测还是实盘),它只会在开始时被调用一次。
它的核心作用是配置环境和定义全局状态。你可以把它理解为策略的“设置面板”和“内存仓库”。
以下是关于 init 函数中应该包含内容的详细说明:
1. init 函数的核心职责
在 init 中,你通常需要完成以下五类操作:
(1) 设置账号信息 (实盘/回测必须)
这是连接策略与交易柜台的关键。
- API:
ContextInfo.set_account(account_id) - 作用: 绑定资金账号。在回测模式下,它会模拟一个账号;在实盘模式下,它决定了交易指令发送到哪个真实账户。如果不设置,后续的交易函数(如
passorder)将无法正常工作或无法接收回报。
(2) 设置股票池 (Universe) (强烈建议)
定义策略关注的标的集合。
- API:
ContextInfo.set_universe(stock_list) - 作用: 告诉系统你需要哪些股票的数据。很多数据获取函数(如
get_history_data)默认会使用这里设置的股票池。如果不设置,后续获取数据可能需要每次手动指定代码列表,且回测效率可能降低。
(3) 设置回测参数 (回测模式专用)
虽然可以在界面上设置,但在代码中设置可以确保策略逻辑的一致性,且代码设置的优先级高于界面。
- 资金:
ContextInfo.capital = 1000000 - 费率:
ContextInfo.set_commission(...) - 滑点:
ContextInfo.set_slippage(...) - 时间:
ContextInfo.start/ContextInfo.end
(4) 定义全局变量 (逻辑核心)
由于 handlebar 函数会随行情频繁运行,定义在 handlebar 内部的普通变量会在每次运行结束后销毁。如果你需要记录**“上一次买入价格”、“当前持仓天数”、“策略状态开关”**等跨周期的数据,必须在 init 中将它们挂载到 ContextInfo 对象上。
- 示例:
ContextInfo.my_last_buy_price = 0
(5) 设置定时器 (进阶用法)
如果你需要策略在特定的时间点运行(例如每天 14:50 进行尾盘选股),而不是每根 K 线都运行,可以在这里注册定时任务。
- API:
ContextInfo.run_time(...)
2. 哪些操作是必须的?
严格来说,Python 语法要求函数体不能为空(至少要写个 pass),但从策略运行的角度看:
-
必须项:
- 函数定义:
def init(ContextInfo):必须存在。 - 设置账号:
ContextInfo.set_account(...)。如果没有这一步,交易回报回调(如order_callback)不会生效,下单时若不指定账号也会报错。
- 函数定义:
-
非必须但极重要项:
- 设置股票池:
ContextInfo.set_universe(...)。除非你的策略完全不依赖历史数据接口的默认行为,否则这是基础。 - 全局变量初始化: 除非你的策略是完全无状态的(即当下的决策只依赖当下的 K 线,完全不依赖之前的状态),否则必须在此初始化变量。
- 设置股票池:
3. 代码示例
下面是一个标准的 init 函数模板,涵盖了上述要点:
# -*- coding: gbk -*-
def init(ContextInfo):
# 1. 【必须】设置资金账号
# 实盘中请替换为真实的资金账号,回测时可随意填写
my_account = '600000248'
ContextInfo.set_account(my_account)
# 2. 【建议】设置股票池
# 设定策略要操作的标的,例如:平安银行和贵州茅台
stock_list = ['000001.SZ', '600519.SH']
ContextInfo.set_universe(stock_list)
# 3. 【回测专用】设置回测参数 (代码优先级 > 界面设置)
# 设置初始资金为 100 万
ContextInfo.capital = 1000000
# 设置手续费:股票买入万三,卖出万三加千一印花税
# 参数顺序: [买入印花税, 卖出印花税, 开仓佣金, 平仓佣金, 平今佣金, 最低佣金]
ContextInfo.set_commission(0, [0, 0.001, 0.0003, 0.0003, 0, 5])
# 设置滑点:按固定价格 0.01 元
ContextInfo.set_slippage(1, 0.01)
# 4. 【逻辑核心】定义全局变量
# 注意:必须挂载在 ContextInfo 下,才能在 handlebar 中被调用和修改
ContextInfo.hold_days = 0 # 记录持仓天数
ContextInfo.last_entry_price = {} # 记录每只股票的入场价格
ContextInfo.stop_loss_pct = 0.05 # 定义全局止损比例 5%
# 5. 【进阶】设置定时器 (可选)
# 例如:每天 10:00:00 运行一次名为 on_time_check 的自定义函数
# ContextInfo.run_time("on_time_check", "1nDay", "2023-01-01 10:00:00", "SH")
print("策略初始化完成")
总结
init 函数是策略的配置中心。
- 不要在
init里写交易逻辑(如判断均线金叉下单),因为这里没有行情驱动,只运行一次。 - 一定要在
init里把所有后续需要用到的“状态变量”都定义好,防止在handlebar中出现NameError或变量重置的问题。
Q&A
Q1: 如果我在 init 里写了死循环 while True 会怎么样?
A1: 策略会卡死在初始化阶段,永远无法进入 handlebar 处理行情,甚至导致客户端无响应。init 必须快速执行完毕。
Q2: ContextInfo.benchmark (基准) 可以在 init 里设置吗?
A2: QMT 的 Python API 中 ContextInfo.benchmark 是只读属性,不支持通过代码设置。回测基准需要在策略编辑器的“回测参数”面板中手动选择。
Q3: 我可以在 init 里获取历史数据吗?
A3: 可以。你可以调用 ContextInfo.get_history_data 来计算一些盘前指标(例如计算过去一年的波动率作为今天的参考),但要注意此时获取的是策略启动时刻之前的数据。
Q4: 为什么我的全局变量在 handlebar 里报错说不存在?
A4: 请检查是否在 init 中使用了 ContextInfo.变量名 = 值 的形式。如果只是写 my_var = 0,它只是 init 函数内的局部变量,函数运行完就销毁了,必须挂载到 ContextInfo 对象上才能跨函数传递。