3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
这是一个非常经典的微观结构交易策略。订单簿不平衡(Order Book Imbalance, OBI)通过衡量买单和卖单在数量上的差异,来预测短期价格的微小变动方向。
1. OBI 定义
OBI 是衡量买卖压力的指标。最基础的计算公式如下:
$$ OBI = \frac{V_{bid} - V_{ask}}{V_{bid} + V_{ask}} $$
2. 交易逻辑
3. PTrade 实现细节
tick_data 函数处理高频 Tick 数据(3秒一次)。data[security]['tick'] 中的 bid_grp(买档)和 offer_grp(卖档)。tick_data 中,bid_grp 和 offer_grp 返回的是 DataFrame 中的一列,且内容通常是字符串格式的字典,需要使用 eval() 进行解析才能获取具体的量价数据。以下是完整的 PTrade 策略代码。该代码选取了前 5 档行情来计算 OBI。
def initialize(context):
"""
初始化函数
"""
# 设定关注的股票,这里以恒生电子为例
g.security = '600570.SS'
set_universe(g.security)
# 策略参数设置
g.obi_threshold = 0.3 # OBI 阈值,超过此值触发交易
g.trade_amount = 100 # 每次交易的数量
g.depth_level = 5 # 计算 OBI 使用的档位数(1-5档)
def tick_data(context, data):
"""
Tick 级别数据处理函数,每 3 秒触发一次
"""
security = g.security
# 1. 获取 Tick 数据
# data[security] 包含 'tick', 'order', 'transcation'
# 我们主要需要 'tick' 数据
if security not in data:
return
tick_df = data[security]['tick']
# 检查数据是否为空
if tick_df is None or len(tick_df) == 0:
return
# 2. 解析盘口数据
# PTrade 的 tick 数据中,bid_grp 和 offer_grp 往往是字符串形式的字典
# 格式示例: {1: [价格, 量, 笔数], 2: [...], ...}
try:
# 获取第一行数据(当前切片)
bid_grp_str = tick_df['bid_grp'][0]
offer_grp_str = tick_df['offer_grp'][0]
# 使用 eval 将字符串转换为字典
bid_grp = eval(bid_grp_str)
offer_grp = eval(offer_grp_str)
except Exception as e:
# 如果解析失败或数据格式异常,直接返回
return
# 3. 计算买盘和卖盘的总挂单量 (前 N 档)
total_bid_vol = 0.0
total_ask_vol = 0.0
# 遍历前 g.depth_level 档
for i in range(1, g.depth_level + 1):
# 字典的 key 是档位 (1, 2, 3, 4, 5)
# value 是 list: [价格, 数量, 笔数]
if i in bid_grp:
total_bid_vol += bid_grp[i][1] # 累加买单数量
if i in offer_grp:
total_ask_vol += offer_grp[i][1] # 累加卖单数量
# 4. 计算 OBI (Order Book Imbalance)
# 防止除以零
if (total_bid_vol + total_ask_vol) == 0:
obi = 0
else:
obi = (total_bid_vol - total_ask_vol) / (total_bid_vol + total_ask_vol)
# 打印日志方便调试 (生产环境可注释掉)
# log.info("Time: %s, OBI: %.4f, BidVol: %s, AskVol: %s" % (
# tick_df['hsTimeStamp'][0], obi, total_bid_vol, total_ask_vol))
# 5. 获取当前持仓
position = get_position(security)
curr_amount = position.amount
# 6. 交易逻辑
# 信号:买入
# 逻辑:OBI 大于阈值 (买压大) 且 当前无持仓
if obi > g.obi_threshold and curr_amount == 0:
# 使用 order_tick 下单
# priceGear='-1' 代表卖一价,即以对手价买入,保证成交速度
order_tick(security, g.trade_amount, priceGear='-1')
log.info("买入触发 - OBI: %.2f, 数量: %s" % (obi, g.trade_amount))
# 信号:卖出
# 逻辑:OBI 小于负阈值 (卖压大) 且 当前有持仓
elif obi < -g.obi_threshold and curr_amount > 0:
# priceGear='1' 代表买一价,即以对手价卖出
# 卖出时数量为负数
order_tick(security, -curr_amount, priceGear='1')
log.info("卖出触发 - OBI: %.2f, 数量: %s" % (obi, curr_amount))
def handle_data(context, data):
"""
必须实现的函数,但在 Tick 策略中通常留空
"""
pass
数据解析 (eval):
tick_data 返回的 DataFrame 中,bid_grp 和 offer_grp 字段存储的是字符串形式的字典(例如 "{1:[10.5, 1000, 5], ...}")。eval() 函数将其还原为 Python 字典对象,才能访问具体的档位数据。{档位: [价格, 数量, 笔数]},我们取索引 [1] 来获取数量。OBI 计算:
下单方式 (order_tick):
tick_data 中必须使用 order_tick。priceGear='-1':表示以卖一价(对手方最优价)下单买入,这在追涨策略中很重要,因为我们需要立即成交。priceGear='1':表示以买一价下单卖出。异常处理:
try...except 块,防止因为行情数据缺失或格式错误导致策略崩溃。Q: 这个策略适合回测吗?
A: PTrade 支持 Tick 级别的回测,但速度较慢。OBI 策略依赖极短期的微观结构,回测时的撮合逻辑(Match Logic)可能与实盘有差异(回测通常假设你的小单能完全成交,不影响盘口),实盘中需要考虑滑点和冲击成本。
Q: 如何优化 OBI 指标?
A:
transcation 数据,看主动买入和主动卖出的成交量。Q: 为什么使用 eval?
A: 这是 PTrade 接口特有的数据格式处理方式。在 tick_data 中,L2 的盘口数据被序列化为字符串存储在 DataFrame 中,必须反序列化才能使用。