策略结构与事件

聚宽策略本质是一个 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 处理。 骨架清楚了,下一步学怎么取数据:数据获取函数