File size: 2,992 Bytes
f86fb3b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <gtest/gtest.h>
#include "../include/order_book/matching_engine.hpp"

using namespace quant::core;
using namespace quant::order_book;
using namespace quant::protocol;

TEST(OrderBookMatchingTest, BasicLimitOrderMatching) {
    std::vector<ExecutionReportPayload> reports;
    MatchingEngine engine(Symbol("TSLA"), [&](const ExecutionReportPayload& r) {
        reports.push_back(r);
    });

    // Maker: Sell 100 @ 250.00
    engine.process_new_order(1, 101, Side::SELL, OrderType::LIMIT, Price::from_double(250.0), 100, 1000);
    ASSERT_EQ(reports.size(), 1);
    EXPECT_EQ(reports[0].exec_type, static_cast<uint8_t>(ExecType::NEW));
    EXPECT_EQ(reports[0].leaves_qty, 100);

    // Taker: Buy 60 @ 250.00
    engine.process_new_order(2, 201, Side::BUY, OrderType::LIMIT, Price::from_double(250.0), 60, 2000);
    // Should trigger 2 reports: maker partial fill, taker fill
    ASSERT_EQ(reports.size(), 3);
    EXPECT_EQ(reports[1].client_id, 1);
    EXPECT_EQ(reports[1].exec_type, static_cast<uint8_t>(ExecType::PARTIAL_FILL));
    EXPECT_EQ(reports[1].fill_qty, 60);
    EXPECT_EQ(reports[1].leaves_qty, 40);

    EXPECT_EQ(reports[2].client_id, 2);
    EXPECT_EQ(reports[2].exec_type, static_cast<uint8_t>(ExecType::FILL));
    EXPECT_EQ(reports[2].fill_qty, 60);
    EXPECT_EQ(reports[2].leaves_qty, 0);

    EXPECT_TRUE(engine.check_invariants());
}

TEST(OrderBookMatchingTest, IOCImmediateOrCancel) {
    std::vector<ExecutionReportPayload> reports;
    MatchingEngine engine(Symbol("AAPL"), [&](const ExecutionReportPayload& r) {
        reports.push_back(r);
    });

    // Maker: Sell 50 @ 180.00
    engine.process_new_order(1, 1, Side::SELL, OrderType::LIMIT, Price::from_double(180.0), 50, 1000);

    // Taker: IOC Buy 100 @ 180.00 (Fills 50, remaining 50 canceled)
    engine.process_new_order(2, 2, Side::BUY, OrderType::IOC, Price::from_double(180.0), 100, 2000);

    // Reports:
    // 0: Maker NEW
    // 1: Maker FILL 50
    // 2: Taker PARTIAL_FILL 50
    // 3: Taker CANCELED 50 leaves
    ASSERT_EQ(reports.size(), 4);
    EXPECT_EQ(reports[3].client_id, 2);
    EXPECT_EQ(reports[3].exec_type, static_cast<uint8_t>(ExecType::CANCELED));
    EXPECT_EQ(reports[3].leaves_qty, 50);

    EXPECT_TRUE(engine.book().bids().empty());
    EXPECT_TRUE(engine.book().asks().empty());
}

TEST(OrderBookMatchingTest, CancelOrder) {
    std::vector<ExecutionReportPayload> reports;
    MatchingEngine engine(Symbol("MSFT"), [&](const ExecutionReportPayload& r) {
        reports.push_back(r);
    });

    engine.process_new_order(1, 999, Side::BUY, OrderType::LIMIT, Price::from_double(400.0), 100, 1000);
    ASSERT_EQ(reports.size(), 1);
    uint64_t engine_order_id = reports[0].engine_order_id;

    bool canceled = engine.cancel_order(1, 999, engine_order_id, 2000);
    EXPECT_TRUE(canceled);
    ASSERT_EQ(reports.size(), 2);
    EXPECT_EQ(reports[1].exec_type, static_cast<uint8_t>(ExecType::CANCELED));
    EXPECT_TRUE(engine.book().bids().empty());
}