Line data Source code
1 : /**
2 : * @file order_book.cpp
3 : * @brief 订单簿实现
4 : */
5 :
6 : #include "app/engine/order_book.hpp"
7 : #include "base/logger.hpp"
8 : #include <sstream>
9 : #include <iomanip>
10 :
11 : namespace fix40 {
12 :
13 50 : OrderBook::OrderBook(const std::string& symbol)
14 50 : : symbol_(symbol)
15 : {
16 50 : }
17 :
18 89 : std::string OrderBook::generateOrderID() {
19 89 : std::ostringstream oss;
20 89 : oss << symbol_ << "-" << std::setfill('0') << std::setw(8) << nextOrderID_++;
21 178 : return oss.str();
22 89 : }
23 :
24 36 : std::string OrderBook::generateTradeID() {
25 36 : std::ostringstream oss;
26 36 : oss << symbol_ << "-T" << std::setfill('0') << std::setw(8) << nextTradeID_++;
27 72 : return oss.str();
28 36 : }
29 :
30 97 : std::vector<Trade> OrderBook::addOrder(Order& order) {
31 : // 输入校验
32 97 : if (order.orderQty <= 0) {
33 2 : LOG() << "[OrderBook:" << symbol_ << "] Rejected: invalid orderQty " << order.orderQty;
34 2 : order.status = OrderStatus::REJECTED;
35 2 : return {};
36 : }
37 95 : if (order.leavesQty <= 0) {
38 0 : LOG() << "[OrderBook:" << symbol_ << "] Rejected: invalid leavesQty " << order.leavesQty;
39 0 : order.status = OrderStatus::REJECTED;
40 0 : return {};
41 : }
42 95 : if (order.ordType == OrderType::LIMIT && order.price <= 0) {
43 2 : LOG() << "[OrderBook:" << symbol_ << "] Rejected: invalid price " << order.price << " for limit order";
44 2 : order.status = OrderStatus::REJECTED;
45 2 : return {};
46 : }
47 93 : if (order.symbol != symbol_) {
48 1 : LOG() << "[OrderBook:" << symbol_ << "] Rejected: symbol mismatch " << order.symbol;
49 1 : order.status = OrderStatus::REJECTED;
50 1 : return {};
51 : }
52 92 : if (order.clOrdID.empty()) {
53 1 : LOG() << "[OrderBook:" << symbol_ << "] Rejected: empty clOrdID";
54 1 : order.status = OrderStatus::REJECTED;
55 1 : return {};
56 : }
57 :
58 : // 市价单检查:必须有对手盘
59 91 : if (order.ordType == OrderType::MARKET) {
60 12 : bool hasCounterparty = (order.side == OrderSide::BUY) ? !asks_.empty() : !bids_.empty();
61 12 : if (!hasCounterparty) {
62 2 : LOG() << "[OrderBook:" << symbol_ << "] Rejected: market order with no counterparty";
63 2 : order.status = OrderStatus::REJECTED;
64 2 : return {};
65 : }
66 : }
67 :
68 : // 生成订单ID
69 89 : order.orderID = generateOrderID();
70 89 : order.updateTime = std::chrono::system_clock::now();
71 :
72 89 : const char* ordTypeStr = (order.ordType == OrderType::MARKET) ? "MARKET" : "LIMIT";
73 89 : LOG() << "[OrderBook:" << symbol_ << "] Adding " << ordTypeStr << " order: "
74 89 : << order.clOrdID << " " << (order.side == OrderSide::BUY ? "BUY" : "SELL")
75 89 : << " " << order.orderQty
76 109 : << (order.ordType == OrderType::LIMIT ? " @ " + std::to_string(order.price) : "");
77 :
78 : // FOK 订单预检查:必须能全部成交
79 89 : if (order.timeInForce == TimeInForce::FOK) {
80 8 : int64_t availableQty = calculateMatchableQty(order);
81 8 : if (availableQty < order.orderQty) {
82 8 : LOG() << "[OrderBook:" << symbol_ << "] FOK order " << order.clOrdID
83 4 : << " rejected: available " << availableQty << " < required " << order.orderQty;
84 4 : order.status = OrderStatus::REJECTED;
85 4 : return {};
86 : }
87 : }
88 :
89 85 : std::vector<Trade> trades;
90 :
91 : // 根据买卖方向撮合
92 85 : if (order.side == OrderSide::BUY) {
93 40 : trades = matchBuyOrder(order);
94 : } else {
95 45 : trades = matchSellOrder(order);
96 : }
97 :
98 : // 处理剩余数量
99 85 : if (order.leavesQty > 0 && !order.isTerminal()) {
100 67 : bool shouldCancelRemaining = false;
101 67 : std::string cancelReason;
102 :
103 : // FOK 订单不应该走到这里(预检查应该已经拒绝)
104 67 : if (order.timeInForce == TimeInForce::FOK) {
105 : // 这是一个实现错误:FOK 预检查通过但未能全部成交
106 0 : LOG() << "[OrderBook:" << symbol_ << "] ERROR: FOK order " << order.clOrdID
107 0 : << " passed pre-check but has remaining qty " << order.leavesQty
108 0 : << " - this indicates a bug in calculateMatchableQty";
109 0 : shouldCancelRemaining = true;
110 0 : cancelReason = "FOK implementation error";
111 67 : } else if (order.timeInForce == TimeInForce::IOC) {
112 : // IOC: 立即成交否则取消(优先于市价单判断,因为 Market+IOC 应显示 IOC)
113 6 : shouldCancelRemaining = true;
114 6 : cancelReason = (order.ordType == OrderType::MARKET) ? "Market IOC" : "IOC";
115 61 : } else if (order.ordType == OrderType::MARKET) {
116 : // 市价单:未成交部分取消
117 2 : shouldCancelRemaining = true;
118 2 : cancelReason = "no more counterparty";
119 : } else {
120 : // DAY/GTC: 限价单挂入订单簿
121 59 : if (order.side == OrderSide::BUY) {
122 20 : addToBids(order);
123 : } else {
124 39 : addToAsks(order);
125 : }
126 :
127 : // 更新订单状态
128 59 : if (order.cumQty == 0) {
129 58 : order.status = OrderStatus::NEW;
130 : }
131 : // 部分成交状态在撮合时已设置
132 : }
133 :
134 67 : if (shouldCancelRemaining) {
135 8 : bool partialFilled = (order.cumQty > 0);
136 8 : order.status = partialFilled ? OrderStatus::CANCELED : OrderStatus::REJECTED;
137 16 : LOG() << "[OrderBook:" << symbol_ << "] Order " << order.clOrdID
138 8 : << " remaining " << order.leavesQty
139 8 : << (partialFilled ? " canceled" : " rejected")
140 8 : << " (" << cancelReason << ")";
141 : }
142 67 : }
143 :
144 85 : return trades;
145 85 : }
146 :
147 40 : std::vector<Trade> OrderBook::matchBuyOrder(Order& buyOrder) {
148 40 : std::vector<Trade> trades;
149 :
150 : // 遍历卖盘(从最低价开始)
151 40 : auto it = asks_.begin();
152 68 : while (it != asks_.end() && buyOrder.leavesQty > 0) {
153 29 : PriceLevel& level = it->second;
154 :
155 : // 检查价格是否可以成交
156 : // 市价单:不检查价格,直接成交
157 : // 限价单:买价 >= 卖价 才能成交
158 29 : if (buyOrder.ordType == OrderType::LIMIT && buyOrder.price < level.price) {
159 1 : break; // 后面的卖价更高,不可能成交
160 : }
161 :
162 : // 遍历该价位的订单
163 28 : auto orderIt = level.orders.begin();
164 56 : while (orderIt != level.orders.end() && buyOrder.leavesQty > 0) {
165 28 : Order& sellOrder = *orderIt;
166 :
167 : // 计算成交数量
168 28 : int64_t matchQty = std::min(buyOrder.leavesQty, sellOrder.leavesQty);
169 28 : double matchPrice = sellOrder.price; // 成交价取被动方价格
170 :
171 : // 更新买单
172 28 : buyOrder.cumQty += matchQty;
173 28 : buyOrder.leavesQty -= matchQty;
174 28 : buyOrder.avgPx = (buyOrder.avgPx * (buyOrder.cumQty - matchQty) + matchPrice * matchQty)
175 28 : / buyOrder.cumQty;
176 28 : auto now = std::chrono::system_clock::now();
177 28 : buyOrder.updateTime = now;
178 :
179 28 : if (buyOrder.leavesQty == 0) {
180 13 : buyOrder.status = OrderStatus::FILLED;
181 : } else {
182 15 : buyOrder.status = OrderStatus::PARTIALLY_FILLED;
183 : }
184 :
185 : // 更新卖单
186 28 : sellOrder.cumQty += matchQty;
187 28 : sellOrder.leavesQty -= matchQty;
188 28 : sellOrder.avgPx = (sellOrder.avgPx * (sellOrder.cumQty - matchQty) + matchPrice * matchQty)
189 28 : / sellOrder.cumQty;
190 28 : sellOrder.updateTime = now;
191 :
192 28 : if (sellOrder.leavesQty == 0) {
193 26 : sellOrder.status = OrderStatus::FILLED;
194 : } else {
195 2 : sellOrder.status = OrderStatus::PARTIALLY_FILLED;
196 : }
197 :
198 : // 创建成交记录(包含完整的双方订单快照)
199 28 : Trade trade;
200 28 : trade.tradeID = generateTradeID();
201 28 : trade.symbol = symbol_;
202 28 : trade.price = matchPrice;
203 28 : trade.qty = matchQty;
204 28 : trade.timestamp = now;
205 :
206 : // 买方信息
207 28 : trade.buyOrderID = buyOrder.orderID;
208 28 : trade.buyClOrdID = buyOrder.clOrdID;
209 28 : trade.buyOrderQty = buyOrder.orderQty;
210 28 : trade.buyPrice = buyOrder.price;
211 28 : trade.buyOrdType = buyOrder.ordType;
212 28 : trade.buyCumQty = buyOrder.cumQty;
213 28 : trade.buyLeavesQty = buyOrder.leavesQty;
214 28 : trade.buyAvgPx = buyOrder.avgPx;
215 28 : trade.buyStatus = buyOrder.status;
216 :
217 : // 卖方信息
218 28 : trade.sellOrderID = sellOrder.orderID;
219 28 : trade.sellClOrdID = sellOrder.clOrdID;
220 28 : trade.sellOrderQty = sellOrder.orderQty;
221 28 : trade.sellPrice = sellOrder.price;
222 28 : trade.sellOrdType = sellOrder.ordType;
223 28 : trade.sellCumQty = sellOrder.cumQty;
224 28 : trade.sellLeavesQty = sellOrder.leavesQty;
225 28 : trade.sellAvgPx = sellOrder.avgPx;
226 28 : trade.sellStatus = sellOrder.status;
227 :
228 28 : trades.push_back(trade);
229 :
230 56 : LOG() << "[OrderBook:" << symbol_ << "] Trade: " << trade.tradeID
231 28 : << " " << matchQty << " @ " << matchPrice
232 28 : << " (Buy:" << buyOrder.clOrdID << " Sell:" << sellOrder.clOrdID << ")";
233 :
234 28 : if (sellOrder.leavesQty == 0) {
235 : // 从索引中移除
236 26 : orderIndex_.erase(sellOrder.clOrdID);
237 26 : orderIt = level.orders.erase(orderIt);
238 26 : askOrderCount_--;
239 : } else {
240 2 : ++orderIt;
241 : }
242 :
243 28 : level.totalQty -= matchQty;
244 28 : }
245 :
246 : // 如果该价位已空,移除
247 28 : if (level.orders.empty()) {
248 25 : it = asks_.erase(it);
249 : } else {
250 3 : ++it;
251 : }
252 : }
253 :
254 80 : return trades;
255 0 : }
256 :
257 45 : std::vector<Trade> OrderBook::matchSellOrder(Order& sellOrder) {
258 45 : std::vector<Trade> trades;
259 :
260 : // 遍历买盘(从最高价开始)
261 45 : auto it = bids_.begin();
262 53 : while (it != bids_.end() && sellOrder.leavesQty > 0) {
263 11 : PriceLevel& level = it->second;
264 :
265 : // 检查价格是否可以成交
266 : // 市价单:不检查价格,直接成交
267 : // 限价单:卖价 <= 买价 才能成交
268 11 : if (sellOrder.ordType == OrderType::LIMIT && sellOrder.price > level.price) {
269 3 : break; // 后面的买价更低,不可能成交
270 : }
271 :
272 : // 遍历该价位的订单
273 8 : auto orderIt = level.orders.begin();
274 16 : while (orderIt != level.orders.end() && sellOrder.leavesQty > 0) {
275 8 : Order& buyOrder = *orderIt;
276 :
277 : // 计算成交数量
278 8 : int64_t matchQty = std::min(sellOrder.leavesQty, buyOrder.leavesQty);
279 8 : double matchPrice = buyOrder.price; // 成交价取被动方价格
280 :
281 : // 更新卖单
282 8 : sellOrder.cumQty += matchQty;
283 8 : sellOrder.leavesQty -= matchQty;
284 8 : sellOrder.avgPx = (sellOrder.avgPx * (sellOrder.cumQty - matchQty) + matchPrice * matchQty)
285 8 : / sellOrder.cumQty;
286 8 : auto now = std::chrono::system_clock::now();
287 8 : sellOrder.updateTime = now;
288 :
289 8 : if (sellOrder.leavesQty == 0) {
290 5 : sellOrder.status = OrderStatus::FILLED;
291 : } else {
292 3 : sellOrder.status = OrderStatus::PARTIALLY_FILLED;
293 : }
294 :
295 : // 更新买单
296 8 : buyOrder.cumQty += matchQty;
297 8 : buyOrder.leavesQty -= matchQty;
298 8 : buyOrder.avgPx = (buyOrder.avgPx * (buyOrder.cumQty - matchQty) + matchPrice * matchQty)
299 8 : / buyOrder.cumQty;
300 8 : buyOrder.updateTime = now;
301 :
302 8 : if (buyOrder.leavesQty == 0) {
303 7 : buyOrder.status = OrderStatus::FILLED;
304 : } else {
305 1 : buyOrder.status = OrderStatus::PARTIALLY_FILLED;
306 : }
307 :
308 : // 创建成交记录(包含完整的双方订单快照)
309 8 : Trade trade;
310 8 : trade.tradeID = generateTradeID();
311 8 : trade.symbol = symbol_;
312 8 : trade.price = matchPrice;
313 8 : trade.qty = matchQty;
314 8 : trade.timestamp = now;
315 :
316 : // 买方信息
317 8 : trade.buyOrderID = buyOrder.orderID;
318 8 : trade.buyClOrdID = buyOrder.clOrdID;
319 8 : trade.buyOrderQty = buyOrder.orderQty;
320 8 : trade.buyPrice = buyOrder.price;
321 8 : trade.buyOrdType = buyOrder.ordType;
322 8 : trade.buyCumQty = buyOrder.cumQty;
323 8 : trade.buyLeavesQty = buyOrder.leavesQty;
324 8 : trade.buyAvgPx = buyOrder.avgPx;
325 8 : trade.buyStatus = buyOrder.status;
326 :
327 : // 卖方信息
328 8 : trade.sellOrderID = sellOrder.orderID;
329 8 : trade.sellClOrdID = sellOrder.clOrdID;
330 8 : trade.sellOrderQty = sellOrder.orderQty;
331 8 : trade.sellPrice = sellOrder.price;
332 8 : trade.sellOrdType = sellOrder.ordType;
333 8 : trade.sellCumQty = sellOrder.cumQty;
334 8 : trade.sellLeavesQty = sellOrder.leavesQty;
335 8 : trade.sellAvgPx = sellOrder.avgPx;
336 8 : trade.sellStatus = sellOrder.status;
337 :
338 8 : trades.push_back(trade);
339 :
340 16 : LOG() << "[OrderBook:" << symbol_ << "] Trade: " << trade.tradeID
341 8 : << " " << matchQty << " @ " << matchPrice
342 8 : << " (Buy:" << buyOrder.clOrdID << " Sell:" << sellOrder.clOrdID << ")";
343 :
344 8 : if (buyOrder.leavesQty == 0) {
345 : // 从索引中移除
346 7 : orderIndex_.erase(buyOrder.clOrdID);
347 7 : orderIt = level.orders.erase(orderIt);
348 7 : bidOrderCount_--;
349 : } else {
350 1 : ++orderIt;
351 : }
352 :
353 8 : level.totalQty -= matchQty;
354 8 : }
355 :
356 : // 如果该价位已空,移除
357 8 : if (level.orders.empty()) {
358 7 : it = bids_.erase(it);
359 : } else {
360 1 : ++it;
361 : }
362 : }
363 :
364 90 : return trades;
365 0 : }
366 :
367 20 : void OrderBook::addToBids(const Order& order) {
368 20 : auto& level = bids_[order.price];
369 20 : if (level.orders.empty()) {
370 19 : level.price = order.price;
371 : }
372 20 : level.orders.push_back(order);
373 20 : level.totalQty += order.leavesQty;
374 :
375 20 : orderIndex_[order.clOrdID] = {OrderSide::BUY, order.price};
376 20 : bidOrderCount_++;
377 :
378 40 : LOG() << "[OrderBook:" << symbol_ << "] Order " << order.clOrdID
379 20 : << " added to bids @ " << order.price;
380 20 : }
381 :
382 39 : void OrderBook::addToAsks(const Order& order) {
383 39 : auto& level = asks_[order.price];
384 39 : if (level.orders.empty()) {
385 38 : level.price = order.price;
386 : }
387 39 : level.orders.push_back(order);
388 39 : level.totalQty += order.leavesQty;
389 :
390 39 : orderIndex_[order.clOrdID] = {OrderSide::SELL, order.price};
391 39 : askOrderCount_++;
392 :
393 78 : LOG() << "[OrderBook:" << symbol_ << "] Order " << order.clOrdID
394 39 : << " added to asks @ " << order.price;
395 39 : }
396 :
397 2 : std::optional<Order> OrderBook::cancelOrder(const std::string& clOrdID) {
398 2 : auto indexIt = orderIndex_.find(clOrdID);
399 2 : if (indexIt == orderIndex_.end()) {
400 1 : LOG() << "[OrderBook:" << symbol_ << "] Cancel failed: order " << clOrdID << " not found";
401 1 : return std::nullopt;
402 : }
403 :
404 1 : return removeOrder(clOrdID, indexIt->second.side);
405 : }
406 :
407 1 : std::optional<Order> OrderBook::removeOrder(const std::string& clOrdID, OrderSide side) {
408 1 : auto indexIt = orderIndex_.find(clOrdID);
409 1 : if (indexIt == orderIndex_.end()) {
410 0 : return std::nullopt;
411 : }
412 :
413 1 : double price = indexIt->second.price;
414 :
415 1 : if (side == OrderSide::BUY) {
416 1 : auto levelIt = bids_.find(price);
417 1 : if (levelIt != bids_.end()) {
418 1 : auto& orders = levelIt->second.orders;
419 1 : for (auto it = orders.begin(); it != orders.end(); ++it) {
420 1 : if (it->clOrdID == clOrdID) {
421 1 : Order canceledOrder = *it;
422 1 : canceledOrder.status = OrderStatus::CANCELED;
423 1 : canceledOrder.updateTime = std::chrono::system_clock::now();
424 :
425 1 : levelIt->second.totalQty -= it->leavesQty;
426 1 : orders.erase(it);
427 1 : bidOrderCount_--;
428 :
429 1 : if (orders.empty()) {
430 1 : bids_.erase(levelIt);
431 : }
432 :
433 1 : orderIndex_.erase(clOrdID);
434 :
435 1 : LOG() << "[OrderBook:" << symbol_ << "] Order " << clOrdID << " canceled";
436 1 : return canceledOrder;
437 1 : }
438 : }
439 : }
440 : } else {
441 0 : auto levelIt = asks_.find(price);
442 0 : if (levelIt != asks_.end()) {
443 0 : auto& orders = levelIt->second.orders;
444 0 : for (auto it = orders.begin(); it != orders.end(); ++it) {
445 0 : if (it->clOrdID == clOrdID) {
446 0 : Order canceledOrder = *it;
447 0 : canceledOrder.status = OrderStatus::CANCELED;
448 0 : canceledOrder.updateTime = std::chrono::system_clock::now();
449 :
450 0 : levelIt->second.totalQty -= it->leavesQty;
451 0 : orders.erase(it);
452 0 : askOrderCount_--;
453 :
454 0 : if (orders.empty()) {
455 0 : asks_.erase(levelIt);
456 : }
457 :
458 0 : orderIndex_.erase(clOrdID);
459 :
460 0 : LOG() << "[OrderBook:" << symbol_ << "] Order " << clOrdID << " canceled";
461 0 : return canceledOrder;
462 0 : }
463 : }
464 : }
465 : }
466 :
467 0 : return std::nullopt;
468 : }
469 :
470 3 : const Order* OrderBook::findOrder(const std::string& clOrdID) const {
471 3 : auto indexIt = orderIndex_.find(clOrdID);
472 3 : if (indexIt == orderIndex_.end()) {
473 2 : return nullptr;
474 : }
475 :
476 1 : double price = indexIt->second.price;
477 1 : OrderSide side = indexIt->second.side;
478 :
479 1 : if (side == OrderSide::BUY) {
480 1 : auto levelIt = bids_.find(price);
481 1 : if (levelIt != bids_.end()) {
482 1 : for (const auto& order : levelIt->second.orders) {
483 1 : if (order.clOrdID == clOrdID) {
484 1 : return ℴ
485 : }
486 : }
487 : }
488 : } else {
489 0 : auto levelIt = asks_.find(price);
490 0 : if (levelIt != asks_.end()) {
491 0 : for (const auto& order : levelIt->second.orders) {
492 0 : if (order.clOrdID == clOrdID) {
493 0 : return ℴ
494 : }
495 : }
496 : }
497 : }
498 :
499 0 : return nullptr;
500 : }
501 :
502 3 : std::optional<double> OrderBook::getBestBid() const {
503 3 : if (bids_.empty()) {
504 1 : return std::nullopt;
505 : }
506 2 : return bids_.begin()->first;
507 : }
508 :
509 4 : std::optional<double> OrderBook::getBestAsk() const {
510 4 : if (asks_.empty()) {
511 1 : return std::nullopt;
512 : }
513 3 : return asks_.begin()->first;
514 : }
515 :
516 1 : std::vector<PriceLevel> OrderBook::getBidLevels(size_t levels) const {
517 1 : std::vector<PriceLevel> result;
518 1 : result.reserve(levels);
519 :
520 1 : size_t count = 0;
521 4 : for (const auto& [price, level] : bids_) {
522 3 : if (count >= levels) break;
523 3 : result.push_back(level);
524 3 : count++;
525 : }
526 :
527 1 : return result;
528 0 : }
529 :
530 1 : std::vector<PriceLevel> OrderBook::getAskLevels(size_t levels) const {
531 1 : std::vector<PriceLevel> result;
532 1 : result.reserve(levels);
533 :
534 1 : size_t count = 0;
535 3 : for (const auto& [price, level] : asks_) {
536 2 : if (count >= levels) break;
537 2 : result.push_back(level);
538 2 : count++;
539 : }
540 :
541 1 : return result;
542 0 : }
543 :
544 8 : int64_t OrderBook::calculateMatchableQty(const Order& order) const {
545 8 : int64_t matchableQty = 0;
546 :
547 8 : if (order.side == OrderSide::BUY) {
548 : // 买单:遍历卖盘计算可成交数量
549 11 : for (const auto& [askPrice, level] : asks_) {
550 : // 市价单不检查价格,限价单检查价格
551 8 : if (order.ordType == OrderType::LIMIT && order.price < askPrice) {
552 1 : break; // 后面的卖价更高,不可能成交
553 : }
554 7 : matchableQty += level.totalQty;
555 7 : if (matchableQty >= order.orderQty) {
556 3 : return order.orderQty; // 已经足够
557 : }
558 : }
559 : } else {
560 : // 卖单:遍历买盘计算可成交数量
561 1 : for (const auto& [bidPrice, level] : bids_) {
562 : // 市价单不检查价格,限价单检查价格
563 1 : if (order.ordType == OrderType::LIMIT && order.price > bidPrice) {
564 0 : break; // 后面的买价更低,不可能成交
565 : }
566 1 : matchableQty += level.totalQty;
567 1 : if (matchableQty >= order.orderQty) {
568 1 : return order.orderQty; // 已经足够
569 : }
570 : }
571 : }
572 :
573 4 : return matchableQty;
574 : }
575 :
576 : } // namespace fix40
|