策略结构与事件
聚宽策略本质是一个 Python 文件,由几个固定名称的函数组成——平台在特定时机自动调用它们。 理解这个「骨架」,你就知道该把代码写在哪里。本章把所有生命周期函数讲清楚。
全局变量 g
g 是平台提供的全局对象,用来存你的自定义变量。它会被自动持久化(模拟盘重启不丢):
def initialize(context):
g.security = '000001.XSHE' # 存到 g 上,后续都能用
g.threshold = 1.05
以
__开头的变量(如g.__query)不会被序列化,适合存放不能被 pickle 的对象。
initialize — 初始化
整个回测/模拟最开始执行一次,用来设基准、佣金、全局变量:
def initialize(context):
set_benchmark('000300.XSHG')
set_option('use_real_price', True)
g.security = '000001.XSHE'
run_daily(market_open, time='09:35')
模拟盘中直接改 initialize 的代码不会立即生效,需要用
after_code_changed处理。
定时运行 — run_daily / run_weekly / run_monthly
这是聚宽推荐的驱动方式,比 handle_data 更灵活、更可控:
def initialize(context):
# 每月第一个交易日 09:40 运行
run_monthly(rebalance, 1, '09:40')
# 每天收盘前运行
run_daily(before_close, '14:50')
# 每分钟运行(需分钟级频率)
run_daily(check, 'every_bar')
| 参数 | 说明 |
|---|---|
func | 自定义函数,只接受 context 一个参数,必须是全局函数(不能是类方法) |
monthday / weekday | 第几个交易日,负数表倒数(-1 = 最后一个) |
time | 具体时间如 '10:00';或 'every_bar'(每个 bar);或 'open+5m'(开盘后 5 分钟,常用于期货) |
reference_security | 时间参照标的,期货需改成对应主力合约 |
force | 注册晚于执行时间是否就近补跑,建议设 False |
用了定时函数后,
handle_data可以不实现。两者不要混用run_daily(handle_data, ...)。
handle_data — 按单位时间运行
每个单位时间(按天则每天、按分钟则每分钟)调用一次:
def handle_data(context, data):
order('000001.XSHE', 100)
data是字典,data[code]取上一单位时间的数据。- Tick 频率不支持此函数。
盘前 / 盘后钩子
| 函数 | 时机 | 典型用途 |
|---|---|---|
before_trading_start(context) | 每天 09:00 | 每日初始化、选股、订阅 Tick |
after_trading_end(context) | 每天 15:30 | 记录成交、统计、发消息 |
on_strategy_end(context) | 回测/模拟正常结束时 | 输出总结 |
def before_trading_start(context):
g.stocks = get_index_stocks('000300.XSHG')
def after_trading_end(context):
trades = get_trades()
for t in trades.values():
log.info(t)
进程重启相关
模拟盘每天会重启进程,以下函数处理重启场景:
| 函数 | 作用 |
|---|---|
process_initialize(context) | 每次进程启动时执行,初始化不能被持久化的对象(如 query 对象) |
after_code_changed(context) | 检测到代码被修改后执行,可重新设置定时任务 |
def process_initialize(context):
g.__query = query(valuation) # query 不能序列化,每次重启重建
def after_code_changed(context):
unschedule_all()
run_daily(market_open, '10:00')
事件回调 on_event
持仓标的发生特定事件时触发,用 isinstance 判断类型:
def on_event(context, event):
from jqdata import DividendsEvent, ForcedLiquidationEvent
if isinstance(event, DividendsEvent):
log.info('分红送股:%s' % event.security)
elif isinstance(event, ForcedLiquidationEvent):
log.info('强平:%s 数量 %s' % (event.security, event.amount))
一个完整骨架
def initialize(context):
set_benchmark('000300.XSHG')
set_option('use_real_price', True)
g.security = '000001.XSHE'
run_daily(before_market, '09:00')
run_daily(market_open, '09:30')
run_daily(after_market, '15:30')
def before_market(context):
log.info('准备开盘')
def market_open(context):
order(g.security, 100)
def after_market(context):
log.info('收盘,今日成交:%s' % len(get_trades()))
小结
initialize全局跑一次,设基准/佣金/全局变量。- 优先用
run_daily/weekly/monthly驱动交易,handle_data为可选。 - 盘前用
before_trading_start,盘后用after_trading_end。 - 模拟盘重启用
process_initialize/after_code_changed处理。 骨架清楚了,下一步学怎么取数据:数据获取函数。