4、深度聚合算法:价格精度归一化、多交易所深度合并、加权平均深度计算、流动性切片聚合
好,咱们进入正题。深度聚合,说白了就是把多个交易所的订单簿揉在一起,给你一个全局视角。这事儿听起来简单,做起来坑不少。我最早做这个模块的时候,以为就是简单地把买单卖单堆一块儿,结果回测数据一跑,全是锯齿,根本没法用。
为什么会这样?因为每个交易所的价格精度不一样,撮合规则也不同。你想想看,Binance的最小价格变动是0.01,OKX可能是0.001,火币又是0.1。直接合并?那深度图就跟狗啃的一样。
4.1 价格精度归一化
归一化,就是给所有交易所定一个统一的“尺子”。我个人习惯的做法是:先确定目标精度,然后把所有价格都对齐到这个精度上。
举个例子,假设我们要把精度归一化到0.01:
def normalize_price(price, tick_size=0.01):
return round(price / tick_size) * tick_size
# 不同交易所的原始价格
prices = {
'binance': 100.01,
'okx': 100.005,
'huobi': 100.1
}
normalized = {ex: normalize_price(p) for ex, p in prices.items()}
print(normalized)
# 输出: {'binance': 100.01, 'okx': 100.01, 'huobi': 100.1}
嗯,这里要注意。归一化不是简单的四舍五入。我在项目中遇到过一个问题:如果tick_size选得太粗,比如0.1,那很多小价差就丢失了,策略的灵敏度会下降。如果选得太细,比如0.001,那深度图会变得很稀疏,流动性看起来比实际少。
4.2 多交易所深度合并
归一化之后,就可以合并了。合并的核心逻辑是:把同一价格档位的订单量累加起来。
你想想看,如果Binance在100.01有10个BTC的买单,OKX在100.01有5个,那合并后这个档位就是15个。
def merge_depth(depths):
merged_bids = {}
merged_asks = {}
for exchange, depth in depths.items():
for price, volume in depth['bids']:
normalized_price = normalize_price(price)
merged_bids[normalized_price] = merged_bids.get(normalized_price, 0) + volume
for price, volume in depth['asks']:
normalized_price = normalize_price(price)
merged_asks[normalized_price] = merged_asks.get(normalized_price, 0) + volume
# 排序
sorted_bids = sorted(merged_bids.items(), reverse=True)
sorted_asks = sorted(merged_asks.items())
return {'bids': sorted_bids, 'asks': sorted_asks}
我曾经踩过一个坑:不同交易所的深度数据到达时间不一样。如果你直接合并,可能会把1秒前的Binance数据和5秒前的OKX数据混在一起。那深度图就是“时空错乱”的。所以,合并前一定要做时间戳对齐,或者至少保证所有数据都在同一个时间窗口内。
4.3 加权平均深度计算
合并之后,我们得到了一个全局深度。但有时候,我们想知道“如果我要买100个BTC,平均成本是多少?”这时候就需要加权平均深度了。
加权平均价格的计算公式很简单:
加权平均价格 = Σ(价格 × 数量) / Σ(数量)
但要注意,这个计算是从最优价格开始,逐档累加,直到满足目标数量为止。
def weighted_avg_price(depth, target_volume, side='asks'):
orders = depth[side]
total_volume = 0
total_cost = 0
for price, volume in orders:
if total_volume + volume >= target_volume:
needed = target_volume - total_volume
total_cost += price * needed
total_volume = target_volume
break
else:
total_cost += price * volume
total_volume += volume
if total_volume < target_volume:
return None # 深度不够
return total_cost / total_volume
我个人习惯把这个函数封装成一个工具,因为做策略的时候经常要用到。比如,你想知道“吃掉卖一到卖五需要多少钱”,直接调这个函数就行。
4.4 流动性切片
流动性切片,就是把深度图切成若干段,每段代表一个价格区间内的总流动性。这玩意儿在风控和仓位管理里特别有用。
举个例子,你可以把深度切成10个切片,每个切片代表10%的价格偏移区间。然后看每个切片里有多少流动性。
def slice_depth(depth, mid_price, num_slices=10, slice_width=0.01):
slices = []
for i in range(1, num_slices + 1):
lower_price = mid_price * (1 - i * slice_width)
upper_price = mid_price * (1 - (i - 1) * slice_width)
slice_volume = 0
for price, volume in depth['bids']:
if lower_price <= price < upper_price:
slice_volume += volume
slices.append({
'range': (lower_price, upper_price),
'volume': slice_volume
})
return slices
嗯,这里有个细节。切片宽度怎么定?我建议根据币种的波动率来动态调整。波动大的币,切片宽一点;波动小的,窄一点。我在做ETH深度切片时,宽度设0.5%,结果发现大部分流动性都集中在第一个切片里,后面的切片几乎是空的。后来改成0.2%,才看到有意义的分布。
知识体系图
下面这张图,把整个深度聚合的流程串起来了。你可以把它当作一个参考框架。
这张图从左到右,从上到下,就是整个深度聚合的流水线。原始数据进来,先归一化,再合并,然后根据你的需求选择加权平均或者切片分析。最后输出到策略或者风控模块。
好了,这一章的内容就这些。记住,深度聚合的核心不是算法有多复杂,而是细节处理有多到位。时间戳对齐、精度选择、切片宽度——这些才是真正决定你系统能不能用的关键。
公众号:蓝海资料掘金营,微信deep3321