问题描述
解决方案
利用Tick数据分析买卖压力是高频交易和日内短线策略中的核心技术。在PTrade中,我们可以通过tick_data回调函数获取高频快照数据(Snapshot),利用其中的**买卖盘口挂单量(Order Book)**来计算买卖压力失衡度(Order Book Imbalance, OBI)。
以下是具体的分析逻辑与策略实现代码。
核心逻辑:盘口压力失衡度 (OBI)
买卖压力通常通过比较买盘(Bid)和卖盘(Ask)的挂单数量来衡量。
- 数据来源:
tick_data中的bid_grp(委买档位)和offer_grp(委卖档位)。 - 计算公式:
$$ OBI = \frac{\sum V_{Bid} - \sum V_{Ask}}{\sum V_{Bid} + \sum V_{Ask}} $$- $V_{Bid}$:买一到买五(或买十)的总挂单量。
- $V_{Ask}$:卖一到卖五(或卖十)的总挂单量。
- 信号判断:
- OBI > 阈值(如 0.3):买盘显著大于卖盘,买方压力大(推升股价),视为看多信号。
- OBI < -阈值(如 -0.3):卖盘显著大于买盘,卖方压力大(压低股价),视为看空信号。
PTrade 策略代码实现
以下代码实现了一个基于Tick级别盘口挂单压力的自动交易策略。
def initialize(context):
"""
策略初始化函数
"""
# 设置要操作的股票,这里以恒生电子为例
g.security = '600570.SS'
set_universe(g.security)
# 策略参数设置
g.obi_threshold = 0.4 # 压力失衡阈值,大于0.4买入,小于-0.4卖出
g.trade_qty = 100 # 每次交易数量
# 初始化持仓标志,防止同一分钟内重复下单(可选)
g.last_trade_time = None
def tick_data(context, data):
"""
Tick数据回调函数,每3秒或有行情更新时触发
"""
security = g.security
# 1. 获取该股票的Tick数据
# data结构: 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-10),Value为列表 [价格, 数量, 笔数]
# 注意:部分券商环境可能需要使用 eval() 解析字符串,但在标准API中通常直接为对象
# 这里为了稳健性,增加了类型判断
bid_grp = tick_info.get('bid_grp', {})
offer_grp = tick_info.get('offer_grp', {})
# 如果是字符串格式(某些旧版本接口),则进行解析
if isinstance(bid_grp, str):
try:
bid_grp = eval(bid_grp)
offer_grp = eval(offer_grp)
except:
return
# 3. 计算买卖总挂单量 (取前5档)
total_bid_vol = 0.0
total_ask_vol = 0.0
# 遍历前5档 (如果开通L2可能有10档,这里保守取前5档)
for i in range(1, 6):
# 获取买档数据
if i in bid_grp:
# bid_grp[i] 格式通常为 [price, volume, count]
# 确保数据有效且有量
if len(bid_grp[i]) >= 2:
total_bid_vol += bid_grp[i][1]
# 获取卖档数据
if i in offer_grp:
if len(offer_grp[i]) >= 2:
total_ask_vol += offer_grp[i][1]
# 防止分母为0
if total_bid_vol + total_ask_vol == 0:
return
# 4. 计算压力失衡度 (Order Book Imbalance)
# 范围在 [-1, 1] 之间
obi = (total_bid_vol - total_ask_vol) / (total_bid_vol + total_ask_vol)
# 获取当前最新价,用于日志记录
current_price = tick_info.get('last_px', 0)
# 5. 交易逻辑
# 获取当前持仓
position = get_position(security)
curr_amount = position.amount
# 简单的信号过滤:避免频繁开仓,这里仅作演示
# 实际实盘中可能需要结合资金流向或成交主动性进一步过滤
# 信号:买盘压力显著,且空仓 -> 买入
if obi > g.obi_threshold and curr_amount == 0:
log.info("买盘压力大 (OBI: %.2f), 触发买入: %s" % (obi, security))
# 使用 order_tick 接口下单,以卖一价(对手价)下单以保证成交
# priceGear: '-1' 代表卖一价
order_tick(security, g.trade_qty, priceGear='-1')
# 信号:卖盘压力显著,且有持仓 -> 卖出
elif obi < -g.obi_threshold and curr_amount > 0:
log.info("卖盘压力大 (OBI: %.2f), 触发卖出: %s" % (obi, security))
# 以买一价(对手价)下单
# priceGear: '1' 代表买一价
order_tick(security, -curr_amount, priceGear='1')
def handle_data(context, data):
"""
必须实现的函数,但在Tick策略中通常留空或用于每日级别的辅助逻辑
"""
pass
代码关键点解析
-
tick_data入口:- 这是PTrade专门处理Tick级别数据的函数。与
handle_data不同,它在交易时间内只要有新的Tick推送(通常间隔3秒)就会执行一次。 - 注意:此函数仅在交易模式下有效,回测模式下如果选择分钟级回测,不会触发此函数;必须选择Tick级回测。
- 这是PTrade专门处理Tick级别数据的函数。与
-
数据提取 (
bid_grp/offer_grp):- PTrade的Tick数据结构中,买卖档位是一个字典。
- Key是档位(1, 2, 3...),Value是一个列表
[价格, 数量, 笔数]。 - 代码中累加了前5档的挂单量(Volume),这是衡量压力的直接指标。
-
OBI 指标计算:
obi = (BidVol - AskVol) / (BidVol + AskVol)- 结果接近 1 表示几乎全是买单(涨停板附近常见)。
- 结果接近 -1 表示几乎全是卖单(跌停板附近常见)。
- 结果接近 0 表示买卖力量均衡。
-
下单接口
order_tick:- 在
tick_data中必须使用order_tick而不是普通的order。 priceGear参数非常实用:'1':买一价。'-1':卖一价。- 使用对手价(买入用卖一,卖出用买一)可以提高高频交易的成交概率。
- 在
进阶分析思路(可扩展)
除了上述的基础盘口压力,你还可以结合以下维度进行更深入的分析:
-
加权平均价格 (VWAP) 偏离:
计算盘口挂单的加权均价,如果盘口均价显著高于最新成交价,说明上方抛压重但下方承接力也强,或者买方意愿更强(视具体分布而定)。 -
逐笔成交分析 (Transaction Data):
利用data[security]['transcation'](注意API文档中的拼写)获取最近一笔成交的business_direction(成交方向)。- 如果
obi > 0且 成交方向为 主动买入 (Buy),则上涨概率更高。 - 如果
obi > 0但 成交方向持续为 主动卖出 (Sell),则可能是“假托单”,即主力在盘口挂大买单诱多,实际在出货。
- 如果
-
撤单率分析:
通过对比连续两个Tick的order_no或挂单量变化,判断是否存在频繁撤单(虚假挂单)的行为。
Q&A
Q1: 为什么在回测中 tick_data 没有触发?
A1: 请检查回测频率设置。必须将回测频率设置为 “Tick级别”。如果是日线或分钟线回测,tick_data 函数不会被调用。
Q2: bid_grp 返回的数据有时候是字符串有时候是字典?
A2: 这取决于PTrade的具体部署版本和券商接口。标准的Python对象应该是字典,但部分旧接口可能返回JSON字符串。代码中加入了 isinstance(..., str) 的判断和 eval() 处理以增强兼容性。
Q3: 如何获取 Level-2 的十档行情?
A3: 如果你的账户开通了 Level-2 权限,bid_grp 和 offer_grp 字典中会包含 Key 为 6 到 10 的数据。你只需将代码中的循环范围 range(1, 6) 改为 range(1, 11) 即可纳入计算。
Q4: order_tick 下单后如何确认成交?
A4: order_tick 返回的是委托流水号。你可以通过 get_orders() 或监听 on_trade_response(交易主推)回调函数来实时获取成交状态。在高频策略中,通常建议使用 on_trade_response 以获得更低的延迟反馈。