File size: 5,081 Bytes
f86fb3b
da6ede7
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
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
#include <gtest/gtest.h>
#include <cstring>
#include "../include/protocol/itch50_protocol.hpp"
#include "../include/protocol/ouch_protocol.hpp"
#include "../include/gateway/itch_ouch_simulator.hpp"

using namespace quant::protocol::itch50;
using namespace quant::protocol::ouch;
namespace itch50 = quant::protocol::itch50;
namespace ouch = quant::protocol::ouch;
using namespace quant::gateway;
using namespace quant::core;

// 1. Official Nasdaq TotalView-ITCH 5.0 Specification Byte Size Conformance
TEST(NasdaqITCHConformanceTest, OfficialSpecByteOffsets) {
    EXPECT_EQ(sizeof(SystemEventMsg), 12);
    EXPECT_EQ(sizeof(StockDirectoryMsg), 39);
    EXPECT_EQ(sizeof(StockTradingActionMsg), 25);
    EXPECT_EQ(sizeof(AddOrderMsg), 36);
    EXPECT_EQ(sizeof(AddOrderMPIDMsg), 40);
    EXPECT_EQ(sizeof(itch50::OrderExecutedMsg), 31);
    EXPECT_EQ(sizeof(OrderExecutedWithPriceMsg), 36);
    EXPECT_EQ(sizeof(OrderCancelMsg), 23);
    EXPECT_EQ(sizeof(OrderDeleteMsg), 19);
    EXPECT_EQ(sizeof(OrderReplaceMsg), 35);
    EXPECT_EQ(sizeof(TradeMsg), 44);
    // OUCH sizes
    EXPECT_EQ(sizeof(EnterOrderMsg), 48);
    EXPECT_EQ(sizeof(ouch::OrderExecutedMsg), 40);
    EXPECT_EQ(sizeof(OrderAcceptedMsg), 57);
    EXPECT_EQ(sizeof(OrderCanceledMsg), 28);
    EXPECT_EQ(sizeof(OrderRejectedMsg), 24);
}

// 2. Big-Endian Decoding & 48-Bit Timestamp Precision
TEST(NasdaqITCHConformanceTest, BigEndianTimestampAndPriceDecoding) {
    AddOrderMsg raw_wire{};
    raw_wire.msg_type = 'A';
    raw_wire.stock_locate = be16_to_cpu(101);
    raw_wire.tracking_num = be16_to_cpu(1);
    uint64_t expected_time_ns = 34200000000000ULL; // 9:30:00.000 AM
    cpu_to_be48(expected_time_ns, raw_wire.timestamp);
    raw_wire.order_ref_num = be64_to_cpu(888999111ULL);
    raw_wire.buy_sell_indicator = 'B';
    raw_wire.shares = be32_to_cpu(500);
    std::memcpy(raw_wire.stock, "NVDA    ", 8);
    raw_wire.price = be32_to_cpu(1255000); // $125.5000

    std::span<const uint8_t> span(reinterpret_cast<const uint8_t*>(&raw_wire), sizeof(raw_wire));
    DecodedAddOrder decoded{};
    ASSERT_TRUE(ITCH50Decoder::decode_add_order(span, decoded));

    EXPECT_EQ(decoded.stock_locate, 101);
    EXPECT_EQ(decoded.timestamp_ns, expected_time_ns);
    EXPECT_EQ(decoded.order_ref_num, 888999111ULL);
    EXPECT_EQ(decoded.side, 'B');
    EXPECT_EQ(decoded.shares, 500);
    EXPECT_EQ(decoded.symbol, "NVDA");
    EXPECT_DOUBLE_EQ(decoded.price, 125.50);
}

// 3. End-to-End Exchange Simulator: OUCH Order Entry -> ITCH 5.0 Feed & Snapshot
TEST(NasdaqITCHConformanceTest, ExchangeSimulatorOrderLifecycle) {
    std::vector<std::vector<uint8_t>> broadcasted_itch;
    ITCHOUCHExchangeSimulator simulator(Symbol("AAPL"), [&](std::span<const uint8_t> itch_msg) {
        broadcasted_itch.emplace_back(itch_msg.begin(), itch_msg.end());
    });

    // Step A: Client enters Limit Sell Order via OUCH
    EnterOrderMsg ouch_enter{};
    ouch_enter.msg_type = 'O';
    OUCHCodec::set_token(ouch_enter.order_token, "CLIENT_ORD_01");
    ouch_enter.buy_sell = 'S';
    ouch_enter.shares = 200;
    OUCHCodec::set_symbol(ouch_enter.stock, "AAPL");
    ouch_enter.price = 1800000; // $180.0000
    ouch_enter.time_in_force = 99999;

    auto resp_bytes = simulator.process_enter_order(ouch_enter);
    ASSERT_EQ(resp_bytes.size(), sizeof(OrderAcceptedMsg));

    const auto* accepted = reinterpret_cast<const OrderAcceptedMsg*>(resp_bytes.data());
    EXPECT_EQ(accepted->msg_type, 'A');
    EXPECT_EQ(OUCHCodec::token_view(accepted->order_token), "CLIENT_ORD_01");
    EXPECT_EQ(accepted->shares, 200);

    // Verify ITCH Add Order 'A' was broadcasted
    ASSERT_GE(broadcasted_itch.size(), 1);
    const auto* itch_add = reinterpret_cast<const AddOrderMsg*>(broadcasted_itch[0].data());
    EXPECT_EQ(itch_add->msg_type, 'A');
    EXPECT_EQ(itch_add->buy_sell_indicator, 'S');
    EXPECT_EQ(be32_to_cpu(itch_add->shares), 200);
    EXPECT_EQ(be32_to_cpu(itch_add->price), 1800000);

    // Step B: Client Replaces Order to $181.00 for 150 shares
    ReplaceOrderMsg ouch_replace{};
    ouch_replace.msg_type = 'U';
    OUCHCodec::set_token(ouch_replace.existing_order_token, "CLIENT_ORD_01");
    ouch_replace.shares = 150;
    ouch_replace.price = 1810000; // $181.0000

    auto replace_resp = simulator.process_replace_order(ouch_replace);
    ASSERT_EQ(replace_resp.size(), sizeof(OrderAcceptedMsg));

    // Verify ITCH Order Replace 'U' broadcasted
    ASSERT_GE(broadcasted_itch.size(), 2);
    const auto* itch_rep = reinterpret_cast<const OrderReplaceMsg*>(broadcasted_itch[1].data());
    EXPECT_EQ(itch_rep->msg_type, 'U');
    EXPECT_EQ(be32_to_cpu(itch_rep->shares), 150);
    EXPECT_EQ(be32_to_cpu(itch_rep->price), 1810000);

    // Step C: Generate Snapshot
    auto snapshot_packet = simulator.generate_snapshot_packet();
    ASSERT_GE(snapshot_packet.size(), sizeof(SnapshotHeaderMsg));
    const auto* snap_hdr = reinterpret_cast<const SnapshotHeaderMsg*>(snapshot_packet.data());
    EXPECT_EQ(snap_hdr->msg_type, 'Z');
    EXPECT_EQ(be32_to_cpu(snap_hdr->ask_levels_count), 1);
}