策略结构与事件

PTrade 策略的本质是一组约定名称的事件函数——引擎在特定时间点回调它们,你在里面写逻辑就行。本章把骨架和核心对象一次讲清,掌握之后绝大多数策略都能照着拼出来。

最小可运行策略

一个策略最少要实现两个函数:

def initialize(context):
    g.security = '600570.SS'
    set_universe(g.security)

def handle_data(context, data):
    pass

initialize 在启动时跑一次,做初始化;handle_data 按运行频率(日/分钟)被反复回调,是盘中逻辑的主战场。其余事件都是可选的。

事件函数一览

事件必选可用场景触发时机
initialize(context)回测 + 交易启动时执行一次
before_trading_start(context, data)回测 + 交易每个交易日盘前(回测 8:30 / 交易 9:10)
handle_data(context, data)回测 + 交易按日/分钟频率盘中触发
after_trading_end(context, data)回测 + 交易每日收盘后(约 15:30)
tick_data(context, data)仅交易每 3 秒一次(9:30~14:59)
on_order_response(context, order_list)仅交易委托状态变化主推
on_trade_response(context, trade_list)仅交易成交回报主推

on_order_response / on_trade_response 是柜台主推回调,比引擎轮询 get_orders() 更新订单状态更快,适合对成交时效敏感的策略。

核心对象:g

g 是全局对象,挂在它身上的变量可在所有函数间共享:

def initialize(context):
    g.security = '600570.SS'   # 股票池
    g.flag = False             # 下单开关
    g.count = 0                # 计数器
    set_universe(g.security)

引擎会在 before_trading_starthandle_dataafter_trading_end 等时机用 pickle 持久化 g 上的变量,以应对服务器重启。两条规则:

  • 名字以 __(双下划线)开头的变量视为私有,不持久化
  • 文件句柄、数据库连接、类实例等不可序列化的变量不会被保存。

核心对象:context

context 携带账户和回测元信息,最常用的属性:

属性含义
context.portfolio账户资产对象(见下)
context.blotter.current_dt当前周期时间,datetime 对象
context.previous_date前一个交易日
context.capital_base起始资金
def handle_data(context, data):
    now = context.blotter.current_dt
    log.info('当前时间 %s' % now.strftime('%Y-%m-%d %H:%M'))

核心对象:portfolio

context.portfolio 汇总账户资金与持仓:

属性含义
cash当前可用资金(不含冻结)
portfolio_value总资产(现金 + 持仓市值)
positions_value持仓总市值
positions持仓字典,{标的代码: Position}
returns相对初始资金的累计收益率
pnl当前总资产 − 初始总资产
cash = context.portfolio.cash
positions = context.portfolio.positions
pos = positions.get('600570.SS')   # 直接取持仓对象,None 表示无持仓

核心对象:data(BarData)

handle_datadata 参数是当前周期的 K 线数据,按代码取属性:

def handle_data(context, data):
    bar = data['600570.SS']
    log.info('开 %s%s%s%s%s' % (
        bar.open, bar.high, bar.low, bar.close, bar.volume))
    price = data['600570.SS'].price   # 当前周期最新价

常用属性:open / high / low / close / volume / money / price,日线还返回 preclose(昨收)、high_limit(涨停价)、low_limit(跌停价)、is_open(是否停牌)。

核心对象:Position

get_position(code)context.portfolio.positions[code] 返回的持仓对象:

属性含义
sid标的代码
amount总持仓数量
enable_amount可用数量(可卖出部分)
cost_basis持仓成本价
last_sale_price最新价
today_amount今日开仓数量

A 股 T+1:today_amount 部分不能在当日卖出,判断能否卖出用 enable_amount

核心对象:Order

下单函数返回 Order 对象的 id(字符串),用 get_order(id) 查询详情:

属性含义
id订单号
symbol标的代码
amount委托数量(买入为正、卖出为负)
filled已成交数量
limit限价
status委托状态(见下)

委托状态码:'0' 未报、'1' 待报、'2' 已报、'7' 部成、'8' 已成、'6' 已撤、'9' 废单('3'/'4'/'5' 为待撤/部撤相关)。

一个完整骨架

def initialize(context):
    g.security = '600570.SS'
    set_universe(g.security)
    g.flag = False

def before_trading_start(context, data):
    g.flag = False                      # 每日重置开关
    g.close = get_history(20, '1d', 'close', g.security)['close'].values

def handle_data(context, data):
    if g.flag:
        return
    price = data[g.security].close
    ma5 = g.close[-5:].mean()
    if price > ma5 and get_position(g.security).amount == 0:
        order_value(g.security, context.portfolio.cash)
        g.flag = True

def after_trading_end(context, data):
    log.info('今日持仓 %s' % get_positions())

持久化与异常处理

  • 持久化before_trading_starthandle_dataafter_trading_end 执行后会保存 g 上的可序列化变量。服务器重启后策略会被重新拉起,initializebefore_trading_start 会再次执行——所以不要在这两个函数里下委托,否则重启会导致重复下单;可用 set_parameters(not_restart_trade='1') 控制重启行为。
  • 异常处理:数据缺失或接口报错若不被捕获,策略会终止。建议对取数和下单用 try/except 包裹,保证单次失败不拖垮全天。

小结

  • 最小策略 = initialize + handle_data;其余事件按需添加。
  • g 存全局变量、context 读账户与时间、data 取当前 K 线、get_position 查持仓、get_order 查委托。
  • 注意 T+1、持久化、重启重复下单等工程细节。骨架搭好了,下一步学具体怎么下单:股票交易函数