File size: 6,085 Bytes
8f4d2d0
 
 
 
 
 
 
 
44cddac
 
 
8f4d2d0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44cddac
 
 
8f4d2d0
 
 
 
44cddac
 
 
8f4d2d0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// ════════════════════════════════════════════════════════════════════
// Integration test: full actor-based matching engine
//
// Tests the OEGateway β†’ OrderBook β†’ MarketData actor pipeline.
// Verifies that the actor topology produces correct trades,
// execution reports, and market data snapshots.
// ════════════════════════════════════════════════════════════════════

#include "actors/MECoreActor.hpp"
#include "actors/OEGActor.hpp"
#include "actors/MDGActor.hpp"
#include <iostream>
#include <cassert>

using namespace eunex;

static int testsPassed = 0;
static int testsFailed = 0;

#define TEST(name) \
    std::cout << "  " << #name << "... "; \
    try { test_##name(); std::cout << "PASS\n"; ++testsPassed; } \
    catch (const std::exception& e) { std::cout << "FAIL: " << e.what() << "\n"; ++testsFailed; }

#define ASSERT_EQ(a, b) \
    if ((a) != (b)) throw std::runtime_error( \
        std::string("Expected ") + std::to_string(static_cast<long long>(b)) + " got " + std::to_string(static_cast<long long>(a)))

#define ASSERT_TRUE(x) \
    if (!(x)) throw std::runtime_error("Assertion failed: " #x)

// ── Test fixtures ──────────────────────────────────────────────────

struct Fixture {
    std::unique_ptr<OEGActor>  oeGateway;
    std::unique_ptr<MDGActor> mdActor;
    std::unique_ptr<MECoreActor>  book;
    static constexpr SymbolIndex_t SYM = 1;
    static constexpr SessionId_t SESS = 1;

    Fixture() {
        oeGateway = std::make_unique<OEGActor>();
        mdActor   = std::make_unique<MDGActor>();
        book      = std::make_unique<MECoreActor>(
            SYM, oeGateway->getActorId(), mdActor->getActorId());
        oeGateway->mapSymbol(SYM, book->getActorId());
    }
};

// ── Tests ──────────────────────────────────────────────────────────

void test_order_routed_to_book() {
    Fixture f;
    f.oeGateway->submitNewOrder(1, f.SYM, Side::Buy, OrderType::Limit,
                                 TimeInForce::Day, toFixedPrice(100.0), 50, f.SESS);

    ASSERT_TRUE(f.oeGateway->getReports().size() > 0);
    ASSERT_EQ(f.oeGateway->getReports().back().status, OrderStatus::New);
}

void test_trade_reaches_market_data() {
    Fixture f;

    f.oeGateway->submitNewOrder(1, f.SYM, Side::Sell, OrderType::Limit,
                                 TimeInForce::Day, toFixedPrice(50.0), 100, f.SESS);
    f.oeGateway->submitNewOrder(2, f.SYM, Side::Buy, OrderType::Limit,
                                 TimeInForce::Day, toFixedPrice(50.0), 60, f.SESS);

    ASSERT_EQ(f.mdActor->getRecentTrades().size(), 1UL);
    ASSERT_EQ(f.mdActor->getRecentTrades()[0].quantity, 60UL);
}

void test_market_data_snapshot_updated() {
    Fixture f;

    f.oeGateway->submitNewOrder(1, f.SYM, Side::Sell, OrderType::Limit,
                                 TimeInForce::Day, toFixedPrice(50.0), 100, f.SESS);
    f.oeGateway->submitNewOrder(2, f.SYM, Side::Buy, OrderType::Limit,
                                 TimeInForce::Day, toFixedPrice(49.0), 100, f.SESS);

    auto* snap = f.mdActor->getSnapshot(f.SYM);
    ASSERT_TRUE(snap != nullptr);
    ASSERT_EQ(snap->bestBid, toFixedPrice(49.0));
    ASSERT_EQ(snap->bestAsk, toFixedPrice(50.0));
}

void test_cancel_via_gateway() {
    Fixture f;

    f.oeGateway->submitNewOrder(1, f.SYM, Side::Sell, OrderType::Limit,
                                 TimeInForce::Day, toFixedPrice(50.0), 100, f.SESS);

    auto orderId = f.oeGateway->getReports().back().orderId;
    f.oeGateway->clearReports();

    f.oeGateway->submitCancel(orderId, 1, f.SYM, f.SESS);

    ASSERT_TRUE(f.oeGateway->getReports().size() > 0);
    ASSERT_EQ(f.oeGateway->getReports().back().status, OrderStatus::Cancelled);
}

void test_multiple_fills_generate_reports() {
    Fixture f;

    f.oeGateway->submitNewOrder(1, f.SYM, Side::Sell, OrderType::Limit,
                                 TimeInForce::Day, toFixedPrice(50.0), 30, f.SESS);
    f.oeGateway->submitNewOrder(2, f.SYM, Side::Sell, OrderType::Limit,
                                 TimeInForce::Day, toFixedPrice(51.0), 30, f.SESS);
    f.oeGateway->clearReports();

    f.oeGateway->submitNewOrder(3, f.SYM, Side::Buy, OrderType::Limit,
                                 TimeInForce::Day, toFixedPrice(51.0), 50, f.SESS);

    // Should have reports for: resting sell fill(s) + incoming buy
    ASSERT_TRUE(f.oeGateway->getReports().size() >= 2);
    ASSERT_EQ(f.mdActor->getRecentTrades().size(), 2UL);
}

void test_unknown_symbol_ignored() {
    Fixture f;
    f.oeGateway->submitNewOrder(1, 999, Side::Buy, OrderType::Limit,
                                 TimeInForce::Day, toFixedPrice(50.0), 50, f.SESS);
    ASSERT_EQ(f.oeGateway->getReports().size(), 0UL);
}

// ── Main ───────────────────────────────────────────────────────────

int main() {
    std::cout << "Matching Engine Integration Tests\n";
    std::cout << "───────────────────────────────────────────\n";

    TEST(order_routed_to_book);
    TEST(trade_reaches_market_data);
    TEST(market_data_snapshot_updated);
    TEST(cancel_via_gateway);
    TEST(multiple_fills_generate_reports);
    TEST(unknown_symbol_ignored);

    std::cout << "───────────────────────────────────────────\n";
    std::cout << testsPassed << " passed, " << testsFailed << " failed\n";
    return testsFailed > 0 ? 1 : 0;
}