4. 价格优先与时间优先原则:撮合引擎的“宪法”
做量化交易这么多年,我见过太多人把精力花在预测行情上,却忽略了撮合引擎最底层的规则。说白了,价格优先和时间优先就是交易所的“交通规则”。不懂这个,你写的策略再漂亮,也可能在成交环节吃大亏。
我个人习惯把这两条规则称为“双优先原则”。它们共同决定了订单簿里谁先成交、谁后成交。咱们一条条拆开看。
4.1 价格优先规则:谁出价好,谁先走
价格优先,字面意思就是“价格说了算”。
- 买单:出价高的优先于出价低的。
- 卖单:出价低的优先于出价高的。
你想想看,如果你是卖家,肯定希望卖给愿意出更高价的人。反过来,如果你是买家,肯定想买最便宜的货。交易所也是这么想的——它要保证市场效率最大化。
核心逻辑:价格优先是撮合的第一道门槛。价格不达标,连排队资格都没有。
我在项目中遇到过一种情况:某个高频交易员故意挂一个极端的限价单,比如在买一价下方挂一个超低价买单。这种单子永远不会被成交,但它会出现在订单簿上,干扰其他交易者的判断。嗯,这就是价格优先规则的一个副作用——你可以挂单,但价格不好,你就得一直等着。
4.2 时间优先规则:先来后到,排队公平
当价格相同时,怎么决定谁先成交?这时候就看时间了。谁先挂单,谁就排在前面。
时间优先,说白了就是“先来后到”。
- 同一价格下,先到达撮合引擎的订单优先成交。
- 后到的订单只能排在后面,等前面的订单成交完了才轮到你。
这个规则看起来简单,但实现起来有个坑——时间戳的精度。纳斯达克用的是纳秒级时间戳。为什么?因为在高频交易的世界里,微秒的差距就能决定谁先成交。
避坑指南:我曾经在优化撮合引擎时,发现两个订单的时间戳完全一样。后来排查发现,是系统时钟同步出了问题。记住,分布式环境下,时间同步是时间优先规则的基础。NTP(网络时间协议)的精度不够,得用PTP(精确时间协议)或者硬件时间戳。
4.3 两者结合的实际应用:订单簿的“排队机制”
价格优先和时间优先不是孤立存在的。它们组合在一起,构成了订单簿的完整排队机制。我画了一张图,帮你理解这个过程:
这张图展示了典型的订单簿排队逻辑:
- 第一步:价格筛选。买单按价格从高到低排序,卖单按价格从低到高排序。价格最高的买单和价格最低的卖单最先匹配。
- 第二步:时间排序。如果价格相同,再按到达时间排序。先到的排在前面。
- 第三步:撮合。买1和卖1价格相同(100.50),且都是09:30:00.001到达,所以它们先成交。
实战技巧:我建议你在设计交易策略时,把时间优先规则考虑进去。比如,如果你想抢在别人前面成交,可以稍微提高一点买价或降低一点卖价。但别太贪心——价格差太大,反而会损失利润。
4.4 代码实现:一个简单的订单簿排队逻辑
下面我用C++写一个简化版的订单簿,展示价格优先和时间优先的实现:
#include <map>
#include <queue>
#include <vector>
struct Order {
int id;
double price;
long timestamp; // 纳秒级时间戳
int quantity;
bool isBuy;
};
// 买单比较器:价格高的优先,价格相同时间早的优先
struct BuyComparator {
bool operator()(const Order& a, const Order& b) {
if (a.price != b.price)
return a.price < b.price; // 价格高的优先
return a.timestamp > b.timestamp; // 时间早的优先
}
};
// 卖单比较器:价格低的优先,价格相同时间早的优先
struct SellComparator {
bool operator()(const Order& a, const Order& b) {
if (a.price != b.price)
return a.price > b.price; // 价格低的优先
return a.timestamp > b.timestamp; // 时间早的优先
}
};
class OrderBook {
private:
// 使用优先队列实现价格优先 + 时间优先
std::priority_queue<Order, std::vector<Order>, BuyComparator> buyOrders;
std::priority_queue<Order, std::vector<Order>, SellComparator> sellOrders;
public:
void addOrder(const Order& order) {
if (order.isBuy) {
buyOrders.push(order);
} else {
sellOrders.push(order);
}
matchOrders();
}
void matchOrders() {
while (!buyOrders.empty() && !sellOrders.empty()) {
Order topBuy = buyOrders.top();
Order topSell = sellOrders.top();
// 价格优先:买价必须 >= 卖价才能成交
if (topBuy.price >= topSell.price) {
int tradeQty = std::min(topBuy.quantity, topSell.quantity);
// 执行成交逻辑...
// 更新订单数量或移除已成交订单
buyOrders.pop();
sellOrders.pop();
// 如果还有剩余数量,重新入队
} else {
break; // 没有可匹配的订单
}
}
}
};
这段代码里,我用了两个优先队列。每个队列的比较器都实现了“价格优先 + 时间优先”的逻辑。你想想看,如果不用优先队列,每次撮合都要遍历整个订单簿,性能会差很多。
关键点:在低延迟系统中,优先队列的实现要小心。标准库的std::priority_queue默认用std::vector做底层,插入和删除都是O(log n)。对于高频场景,我建议用更高效的数据结构,比如配对堆(Pairing Heap)或者跳表(Skip List)。
4.5 实际应用中的注意事项
双优先原则看起来简单,但在实际系统中,有几个坑需要特别注意:
| 场景 | 问题 | 解决方案 |
|---|---|---|
| 时间戳精度不足 | 两个订单时间相同,无法区分先后 | 使用纳秒级时间戳,或引入订单序列号 |
| 价格相同但数量不同 | 大单可能被小单“插队” | 时间优先规则保证公平,大单只能等 |
| 撤单后重新挂单 | 时间戳重置,失去原有排队位置 | 撤单后时间重新计算,无法保留位置 |
我记得有一次,一个客户抱怨他的订单总是成交不了。我查了日志,发现他的订单价格比市场价低了0.01美元。嗯,这就是价格优先规则在起作用——价格不好,系统根本不会考虑你的订单。
避坑指南:我曾经在测试环境中发现,当订单量暴增时,时间戳的生成会出现“毛刺”。比如,两个订单的时间戳相差只有1纳秒,但实际到达顺序是反的。后来我们引入了硬件时间戳(比如Intel的DPDK),才彻底解决了这个问题。
总结一下:价格优先和时间优先是撮合引擎的基石。价格优先保证市场效率,时间优先保证公平。两者结合,构成了订单簿的完整排队机制。你在设计交易系统时,一定要把这两条规则刻在脑子里——它们决定了你的订单能不能成交、什么时候成交。