4. 价格发现的核心算法:最大成交量原则
集合竞价的核心,说白了就是一句话:找出一个价格,让想买的人和想卖的人都能尽可能多地成交。这个价格,就是咱们常说的开盘价或收盘价。
我刚开始接触这个算法时,觉得不就是找个交点嘛,跟小学的相遇问题差不多。后来真上了生产环境才发现,这里面的门道比想象中深得多。嗯,咱们今天就把这个「最大成交量原则」彻底拆开看看。
4.1 什么是最大成交量原则?
先给个正式定义:
在集合竞价阶段,系统会遍历所有可能的成交价格,然后选择那个能够使成交量达到最大的价格作为最终成交价。
你想想看,如果价格定高了,买方不愿意买;定低了,卖方不愿意卖。只有那个恰到好处的价格,才能让买卖双方都满意,成交量自然最大。
我在项目中遇到过这样一个案例:某只股票在集合竞价时,有3个价格都能产生相同的最大成交量。这时候怎么办?别急,后面会讲处理规则。
4.2 算法核心逻辑
最大成交量原则的实现,其实就三步:
- 收集订单:把所有买卖委托按价格排序
- 计算累计量:从最低价到最高价,逐档计算累计买单和累计卖单
- 找最大值:在每个价格上,取累计买单和累计卖单的较小值,找到那个最大值
我习惯用一张图来理解这个过程:
4.3 一个具体的计算例子
光说理论太干,咱们直接上例子。假设某只股票在集合竞价阶段收到以下订单:
| 价格(元) | 累计买单量(手) | 累计卖单量(手) | 可成交量(手) |
|---|---|---|---|
| 10.00 | 1000 | 200 | 200 |
| 10.02 | 800 | 400 | 400 |
| 10.04 | 600 | 600 | 600 ← 最大 |
| 10.06 | 400 | 900 | 400 |
| 10.08 | 200 | 1200 | 200 |
看到没?在10.04元这个价格上,累计买单600手,累计卖单600手,可成交量是600手。其他价格要么买方不够,要么卖方不够,成交量都小于600。所以,10.04元就是最终的集合竞价成交价。
核心公式:
在每个价格 P 上,可成交量 V(P) = min(累计买单量(P), 累计卖单量(P))
最终成交价 = argmax V(P)
4.4 多价格平局怎么办?
刚才说了,有时候会出现多个价格都能产生相同的最大成交量。这时候交易所会有一套「破局规则」。我以A股为例:
- 优先选最小剩余量:计算每个价格上未成交的买卖单量之和,选最小的那个
- 如果还平局:选最接近前收盘价的那个
- 如果还平局:选中间价(比如两个价格,取平均值)
我曾经在回测系统里遇到过这种平局情况,当时没处理这个细节,结果回测结果跟实盘差了0.02%。排查了两天才发现是平局处理逻辑没写对。嗯,这种边界情况最容易出问题。
4.5 代码实现示例
下面是我个人习惯用的一个简化版实现,用Python写的:
def find_max_volume_price(buy_orders, sell_orders):
"""
最大成交量原则实现
buy_orders: 买方订单列表,每个元素为 (价格, 数量)
sell_orders: 卖方订单列表,每个元素为 (价格, 数量)
"""
# 按价格排序
buy_orders.sort(key=lambda x: x[0], reverse=True) # 从高到低
sell_orders.sort(key=lambda x: x[0]) # 从低到高
# 计算累计量
cum_buy = 0
cum_sell = 0
price_volume = {}
# 合并所有价格点
all_prices = sorted(set([p for p, _ in buy_orders] + [p for p, _ in sell_orders]))
buy_idx = 0
sell_idx = 0
for price in all_prices:
# 累计所有高于等于当前价格的买单
while buy_idx < len(buy_orders) and buy_orders[buy_idx][0] >= price:
cum_buy += buy_orders[buy_idx][1]
buy_idx += 1
# 累计所有低于等于当前价格的卖单
while sell_idx < len(sell_orders) and sell_orders[sell_idx][0] <= price:
cum_sell += sell_orders[sell_idx][1]
sell_idx += 1
# 计算可成交量
volume = min(cum_buy, cum_sell)
price_volume[price] = volume
# 找最大成交量对应的价格
max_volume = max(price_volume.values())
candidates = [p for p, v in price_volume.items() if v == max_volume]
# 平局处理(简化版)
if len(candidates) == 1:
return candidates[0]
else:
# 这里可以加入更复杂的平局处理逻辑
return candidates[len(candidates) // 2] # 取中间价
避坑指南:
我曾经在实现时犯过一个低级错误——直接用价格作为字典key,但浮点数精度问题导致两个本该相等的价格被当成不同key。后来我改用整数(价格乘以最小变动单位)才解决。建议你们也注意这个细节。
4.6 实际应用中的注意事项
- 订单簿快照:集合竞价期间,订单簿是动态变化的,算法需要处理实时更新的订单流
- 价格档位:不同交易所的最小价格变动单位不同,A股是0.01元,有些品种是0.001元
- 大单处理:遇到大单时,累计量的变化可能很剧烈,算法要能平滑处理
- 时间优先级:在价格相同的情况下,时间优先原则仍然适用
重要提醒:
最大成交量原则虽然看起来简单,但在高频交易场景下,毫秒级的订单变化都会影响最终结果。如果你在做量化策略,建议用真实的历史数据回测,别光靠理论推导。
好了,关于最大成交量原则的核心内容就这些。说白了,它就是集合竞价价格发现的「定海神针」,所有复杂的规则最终都是为了实现这个目标——让最多的人成交。