问题描述
解决方案
这是一个基于 订单簿不平衡 (Order Book Imbalance, OBI) 的 Tick 级高频交易策略设计。
策略原理
订单簿不平衡 (OBI) 是衡量买卖盘力量对比的一个经典微观结构指标。
-
计算公式:
$$ OBI = \frac{V_{bid} - V_{ask}}{V_{bid} + V_{ask}} $$- $V_{bid}$:买盘总量(通常取买一到买五的总挂单量)。
- $V_{ask}$:卖盘总量(通常取卖一到卖五的总挂单量)。
-
指标含义:
- OBI 的取值范围在 $[-1, 1]$ 之间。
- OBI > 0:买盘挂单量大于卖盘,市场存在潜在的上涨压力。
- OBI < 0:卖盘挂单量大于买盘,市场存在潜在的下跌压力。
-
交易逻辑:
- 开仓(买入):当 $OBI > \text{阈值}$(例如 0.4),且当前无持仓时,以对手价(卖一价)买入。
- 平仓(卖出):当 $OBI < -\text{阈值}$(例如 -0.4),且当前有持仓时,以对手价(买一价)卖出。
- 止损:如果价格亏损超过一定比例,强制平仓。
PTrade 策略代码实现
注意:
tick_data函数仅在交易模式(实盘或模拟盘)下生效,且需要开通 Level-2 行情权限才能获取完整的逐笔和五档数据。- 普通的回测模式(日线/分钟线)无法运行此代码,因为它们不触发
tick_data。 - 代码兼容 Python 3.5 语法。
# 策略名称:Tick级订单簿不平衡(OBI)策略
# 运行环境:PTrade 交易/模拟交易模式 (需L2行情权限)
def initialize(context):
"""
初始化函数,设置股票池和全局变量
"""
# 设定要交易的股票,建议选择流动性好的标的
g.security = '600570.SS'
set_universe(g.security)
# 策略参数设置
g.obi_threshold = 0.4 # OBI阈值,超过此值触发信号
g.trade_qty = 100 # 每次交易数量
g.stop_loss_pct = 0.01 # 止损百分比 (1%)
# 交易状态控制
g.last_entry_price = 0.0 # 记录最后一次开仓价格
log.info("OBI策略初始化完成,监控标的: %s" % g.security)
def tick_data(context, data):
"""
Tick数据处理函数,每3秒或有新Tick时触发
"""
security = g.security
# 1. 获取当前Tick数据
# data[security] 包含了 'tick', 'order', 'transcation'
if security not in data:
return
tick_info = data[security].get('tick')
if not tick_info:
return
# 2. 提取五档买卖盘数据
# bid_grp 和 offer_grp 是字典,key为档位(1-5),value为列表 [价格, 数量, 笔数]
bid_grp = tick_info.get('bid_grp')
offer_grp = tick_info.get('offer_grp')
if not bid_grp or not offer_grp:
return
# 3. 计算买卖盘总量 (取前5档)
total_bid_vol = 0.0
total_ask_vol = 0.0
# 遍历1到5档
for i in range(1, 6):
# 确保档位存在且数据有效
if i in bid_grp:
# bid_grp[i][1] 是该档位的委托数量
total_bid_vol += bid_grp[i][1]
if i in offer_grp:
# offer_grp[i][1] 是该档位的委托数量
total_ask_vol += offer_grp[i][1]
# 防止除以零
if total_bid_vol + total_ask_vol == 0:
return
# 4. 计算 OBI 指标
obi = (total_bid_vol - total_ask_vol) / (total_bid_vol + total_ask_vol)
# 获取当前最新价和持仓信息
current_price = tick_info['last_px']
position = get_position(security)
curr_amount = position.amount # 当前持仓数量
enable_amount = position.enable_amount # 可用数量
# 5. 止损逻辑 (优先级最高)
if curr_amount > 0 and g.last_entry_price > 0:
# 计算收益率
pnl_pct = (current_price - g.last_entry_price) / g.last_entry_price
if pnl_pct < -g.stop_loss_pct:
log.info("触发止损: 当前价格 %s, 开仓价格 %s, 亏损 %.2f%%" % (current_price, g.last_entry_price, pnl_pct * 100))
# 卖出平仓,使用 '1' 代表以买一价(对手价)快速成交
order_tick(security, -enable_amount, priceGear='1')
g.last_entry_price = 0.0
return
# 6. 信号生成与交易执行
# 场景 A: 买入信号 (OBI > 阈值 且 空仓)
if obi > g.obi_threshold and curr_amount == 0:
log.info("买入信号: OBI=%.4f, 买盘量=%d, 卖盘量=%d" % (obi, total_bid_vol, total_ask_vol))
# 以卖一价(对手价)下单买入,priceGear='-1' 代表卖一档
order_tick(security, g.trade_qty, priceGear='-1')
g.last_entry_price = current_price
# 场景 B: 卖出信号 (OBI < -阈值 且 有持仓)
elif obi < -g.obi_threshold and enable_amount > 0:
log.info("卖出信号: OBI=%.4f, 买盘量=%d, 卖盘量=%d" % (obi, total_bid_vol, total_ask_vol))
# 以买一价(对手价)下单卖出,priceGear='1' 代表买一档
order_tick(security, -enable_amount, priceGear='1')
g.last_entry_price = 0.0
def handle_data(context, data):
"""
必须实现的函数,但在Tick策略中主要逻辑写在 tick_data 中
"""
pass
代码关键点解析
-
数据获取 (
tick_data):- 使用
data[security]['tick']['bid_grp']获取买盘档位数据。 - PTrade 的档位数据结构通常是字典
{1: [价格, 数量, 笔数], 2: [...], ...}。 - 我们循环
range(1, 6)来累加前五档的挂单量。
- 使用
-
OBI 计算:
- 公式简单直接,反映了当前时刻买卖盘的静态压力。
total_bid_vol和total_ask_vol的对比是核心。
-
下单方式 (
order_tick):- 这是专门用于
tick_data中的下单函数。 priceGear='-1':表示以卖一价下单(用于买入,追求立即成交)。priceGear='1':表示以买一价下单(用于卖出,追求立即成交)。- 这种方式比指定
limit_price更适合高频场景,因为它自动跟随盘口。
- 这是专门用于
-
风险控制:
- 加入了简单的百分比止损逻辑,防止单边行情造成过大亏损。
- 检查
enable_amount(可用持仓)以避免在 T+1 制度下卖出当日买入的股票(如果是股票交易)。
常见问题 Q&A
Q: 这个策略可以在回测中运行吗?
A: 标准的 PTrade 回测通常是分钟或日线级别的,不会触发 tick_data。要测试此策略,通常需要在“研究”模块中自行获取历史 Tick 数据进行模拟撮合,或者在提供 Tick 级回测的高级环境中运行。
Q: 为什么使用 order_tick 而不是 order?
A: 在 tick_data 这种高频回调函数中,order_tick 是专用的接口,它允许通过 priceGear 参数快速指定盘口档位(如买一、卖一),比手动获取价格再传给 order 函数效率更高,且更符合盘口博弈的逻辑。
Q: 如何调整策略的灵敏度?
A: 修改 g.obi_threshold。
- 调大阈值(如 0.6):信号更少,但理论上信号质量更高(买卖盘差异巨大)。
- 调小阈值(如 0.2):信号更频繁,但可能遭遇更多假信号(噪音)。