策略结构与事件
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_start、handle_data、after_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_data 的 data 参数是当前周期的 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_start、handle_data、after_trading_end执行后会保存g上的可序列化变量。服务器重启后策略会被重新拉起,initialize和before_trading_start会再次执行——所以不要在这两个函数里下委托,否则重启会导致重复下单;可用set_parameters(not_restart_trade='1')控制重启行为。 - 异常处理:数据缺失或接口报错若不被捕获,策略会终止。建议对取数和下单用
try/except包裹,保证单次失败不拖垮全天。
小结
- 最小策略 =
initialize+handle_data;其余事件按需添加。 g存全局变量、context读账户与时间、data取当前 K 线、get_position查持仓、get_order查委托。- 注意 T+1、持久化、重启重复下单等工程细节。骨架搭好了,下一步学具体怎么下单:股票交易函数。