NOT-OMEGA commited on
Commit
ac89d60
·
verified ·
1 Parent(s): 923e619

Delete tests

Browse files
tests/test_order_book.cpp DELETED
@@ -1,415 +0,0 @@
1
- // tests/test_order_book.cpp
2
- // Unit tests for the matching engine core
3
- // Build: cmake --build build --target hft_tests && ./build/hft_tests
4
-
5
- #include "core/order_book.hpp"
6
- #include "core/types.hpp"
7
- #include <cassert>
8
- #include <iostream>
9
- #include <string>
10
- #include <vector>
11
- #include <cmath>
12
-
13
- using namespace hft;
14
-
15
- static int tests_passed = 0;
16
- static int tests_failed = 0;
17
-
18
- #define TEST(name) \
19
- void name(); \
20
- struct name##_register { name##_register() { std::cout << " " << #name << "... "; name(); } } name##_instance; \
21
- void name()
22
-
23
- #define ASSERT_EQ(a, b) do { \
24
- if ((a) != (b)) { \
25
- std::cerr << "FAIL: " << #a << " == " << (a) << ", expected " << (b) << " at line " << __LINE__ << "\n"; \
26
- ++tests_failed; return; \
27
- } \
28
- } while(0)
29
-
30
- #define ASSERT_TRUE(x) do { \
31
- if (!(x)) { \
32
- std::cerr << "FAIL: " << #x << " at line " << __LINE__ << "\n"; \
33
- ++tests_failed; return; \
34
- } \
35
- } while(0)
36
-
37
- #define PASS() do { std::cout << "PASS\n"; ++tests_passed; } while(0)
38
-
39
- // ─── Price Conversion Tests ──────────────────────────────────────────────────
40
-
41
- TEST(test_to_price_positive) {
42
- ASSERT_EQ(to_price(185.50), 1855000);
43
- ASSERT_EQ(to_price(0.0001), 1);
44
- ASSERT_EQ(to_price(100.0), 1000000);
45
- PASS();
46
- }
47
-
48
- TEST(test_to_price_negative) {
49
- ASSERT_EQ(to_price(-1.5), -15000);
50
- ASSERT_EQ(to_price(-0.0001), -1);
51
- ASSERT_EQ(to_price(-100.0), -1000000);
52
- PASS();
53
- }
54
-
55
- TEST(test_to_price_roundtrip) {
56
- double original = 185.4567;
57
- Price p = to_price(original);
58
- double back = from_price(p);
59
- ASSERT_TRUE(std::abs(back - original) < 0.00005);
60
- PASS();
61
- }
62
-
63
- // ─── Order Book: Basic Insert ────────────────────────────────────────────────
64
-
65
- TEST(test_add_limit_order_no_match) {
66
- OrderBook book("AAPL");
67
- Order buy;
68
- buy.id = 1; buy.symbol = "AAPL"; buy.side = Side::BUY;
69
- buy.type = OrderType::LIMIT; buy.price = to_price(185.0); buy.qty = 100;
70
-
71
- auto trades = book.add_order(buy);
72
- ASSERT_EQ(trades.size(), 0u);
73
- ASSERT_EQ(book.order_count(), 1u);
74
- ASSERT_EQ(book.bid_levels(), 1u);
75
- ASSERT_EQ(book.ask_levels(), 0u);
76
- ASSERT_EQ(book.best_bid(), to_price(185.0));
77
- PASS();
78
- }
79
-
80
- TEST(test_multiple_levels) {
81
- OrderBook book("AAPL");
82
- Order o1, o2, o3;
83
- o1.id = 1; o1.symbol = "AAPL"; o1.side = Side::BUY;
84
- o1.type = OrderType::LIMIT; o1.price = to_price(185.0); o1.qty = 100;
85
-
86
- o2.id = 2; o2.symbol = "AAPL"; o2.side = Side::BUY;
87
- o2.type = OrderType::LIMIT; o2.price = to_price(184.0); o2.qty = 200;
88
-
89
- o3.id = 3; o3.symbol = "AAPL"; o3.side = Side::SELL;
90
- o3.type = OrderType::LIMIT; o3.price = to_price(186.0); o3.qty = 150;
91
-
92
- book.add_order(o1);
93
- book.add_order(o2);
94
- book.add_order(o3);
95
-
96
- ASSERT_EQ(book.bid_levels(), 2u);
97
- ASSERT_EQ(book.ask_levels(), 1u);
98
- ASSERT_EQ(book.best_bid(), to_price(185.0));
99
- ASSERT_EQ(book.best_ask(), to_price(186.0));
100
- PASS();
101
- }
102
-
103
- // ─── Order Book: Matching ────────────────────────────────────────────────────
104
-
105
- TEST(test_limit_buy_matches_ask) {
106
- OrderBook book("AAPL");
107
-
108
- Order sell;
109
- sell.id = 1; sell.symbol = "AAPL"; sell.side = Side::SELL;
110
- sell.type = OrderType::LIMIT; sell.price = to_price(185.0); sell.qty = 100;
111
- book.add_order(sell);
112
-
113
- Order buy;
114
- buy.id = 2; buy.symbol = "AAPL"; buy.side = Side::BUY;
115
- buy.type = OrderType::LIMIT; buy.price = to_price(185.0); buy.qty = 100;
116
- auto trades = book.add_order(buy);
117
-
118
- ASSERT_EQ(trades.size(), 1u);
119
- ASSERT_EQ(trades[0].qty, 100);
120
- ASSERT_EQ(trades[0].price, to_price(185.0));
121
- ASSERT_EQ(book.order_count(), 0u);
122
- PASS();
123
- }
124
-
125
- TEST(test_limit_sell_matches_bid) {
126
- OrderBook book("AAPL");
127
-
128
- Order buy;
129
- buy.id = 1; buy.symbol = "AAPL"; buy.side = Side::BUY;
130
- buy.type = OrderType::LIMIT; buy.price = to_price(185.0); buy.qty = 100;
131
- book.add_order(buy);
132
-
133
- Order sell;
134
- sell.id = 2; sell.symbol = "AAPL"; sell.side = Side::SELL;
135
- sell.type = OrderType::LIMIT; sell.price = to_price(185.0); sell.qty = 100;
136
- auto trades = book.add_order(sell);
137
-
138
- ASSERT_EQ(trades.size(), 1u);
139
- ASSERT_EQ(trades[0].qty, 100);
140
- ASSERT_EQ(book.order_count(), 0u);
141
- PASS();
142
- }
143
-
144
- TEST(test_partial_fill) {
145
- OrderBook book("AAPL");
146
-
147
- Order sell;
148
- sell.id = 1; sell.symbol = "AAPL"; sell.side = Side::SELL;
149
- sell.type = OrderType::LIMIT; sell.price = to_price(185.0); sell.qty = 200;
150
- book.add_order(sell);
151
-
152
- Order buy;
153
- buy.id = 2; buy.symbol = "AAPL"; buy.side = Side::BUY;
154
- buy.type = OrderType::LIMIT; buy.price = to_price(185.0); buy.qty = 50;
155
- auto trades = book.add_order(buy);
156
-
157
- ASSERT_EQ(trades.size(), 1u);
158
- ASSERT_EQ(trades[0].qty, 50);
159
- ASSERT_EQ(book.order_count(), 1u); // resting sell still has 150
160
- ASSERT_EQ(book.ask_levels(), 1u);
161
- PASS();
162
- }
163
-
164
- TEST(test_multi_level_sweep) {
165
- OrderBook book("AAPL");
166
-
167
- Order s1, s2;
168
- s1.id = 1; s1.symbol = "AAPL"; s1.side = Side::SELL;
169
- s1.type = OrderType::LIMIT; s1.price = to_price(185.0); s1.qty = 100;
170
-
171
- s2.id = 2; s2.symbol = "AAPL"; s2.side = Side::SELL;
172
- s2.type = OrderType::LIMIT; s2.price = to_price(186.0); s2.qty = 100;
173
-
174
- book.add_order(s1);
175
- book.add_order(s2);
176
-
177
- // Aggressive buy sweeps both levels
178
- Order buy;
179
- buy.id = 3; buy.symbol = "AAPL"; buy.side = Side::BUY;
180
- buy.type = OrderType::LIMIT; buy.price = to_price(187.0); buy.qty = 200;
181
- auto trades = book.add_order(buy);
182
-
183
- ASSERT_EQ(trades.size(), 2u);
184
- ASSERT_EQ(trades[0].price, to_price(185.0)); // best ask first
185
- ASSERT_EQ(trades[1].price, to_price(186.0));
186
- ASSERT_EQ(book.order_count(), 0u);
187
- ASSERT_EQ(book.ask_levels(), 0u);
188
- PASS();
189
- }
190
-
191
- TEST(test_sell_side_multi_level_sweep) {
192
- // This specifically tests the sell-side iterator fix (Bug #4)
193
- OrderBook book("AAPL");
194
-
195
- Order b1, b2, b3;
196
- b1.id = 1; b1.symbol = "AAPL"; b1.side = Side::BUY;
197
- b1.type = OrderType::LIMIT; b1.price = to_price(185.0); b1.qty = 100;
198
-
199
- b2.id = 2; b2.symbol = "AAPL"; b2.side = Side::BUY;
200
- b2.type = OrderType::LIMIT; b2.price = to_price(184.0); b2.qty = 100;
201
-
202
- b3.id = 3; b3.symbol = "AAPL"; b3.side = Side::BUY;
203
- b3.type = OrderType::LIMIT; b3.price = to_price(183.0); b3.qty = 100;
204
-
205
- book.add_order(b1);
206
- book.add_order(b2);
207
- book.add_order(b3);
208
-
209
- // Aggressive sell sweeps all 3 bid levels
210
- Order sell;
211
- sell.id = 4; sell.symbol = "AAPL"; sell.side = Side::SELL;
212
- sell.type = OrderType::MARKET; sell.price = 0; sell.qty = 300;
213
- auto trades = book.add_order(sell);
214
-
215
- ASSERT_EQ(trades.size(), 3u);
216
- ASSERT_EQ(trades[0].price, to_price(185.0)); // best bid first
217
- ASSERT_EQ(trades[1].price, to_price(184.0));
218
- ASSERT_EQ(trades[2].price, to_price(183.0));
219
- ASSERT_EQ(book.bid_levels(), 0u);
220
- ASSERT_EQ(book.order_count(), 0u);
221
- PASS();
222
- }
223
-
224
- // ─── Order Types ─────────────────────────────────────────────────────────────
225
-
226
- TEST(test_market_order_no_counterparty) {
227
- OrderBook book("AAPL");
228
-
229
- Order buy;
230
- buy.id = 1; buy.symbol = "AAPL"; buy.side = Side::BUY;
231
- buy.type = OrderType::MARKET; buy.price = 0; buy.qty = 100;
232
- auto trades = book.add_order(buy);
233
-
234
- ASSERT_EQ(trades.size(), 0u);
235
- ASSERT_EQ(buy.status, OrderStatus::CANCELLED);
236
- ASSERT_EQ(book.order_count(), 0u);
237
- PASS();
238
- }
239
-
240
- TEST(test_ioc_partial_fill) {
241
- OrderBook book("AAPL");
242
-
243
- Order sell;
244
- sell.id = 1; sell.symbol = "AAPL"; sell.side = Side::SELL;
245
- sell.type = OrderType::LIMIT; sell.price = to_price(185.0); sell.qty = 50;
246
- book.add_order(sell);
247
-
248
- Order buy;
249
- buy.id = 2; buy.symbol = "AAPL"; buy.side = Side::BUY;
250
- buy.type = OrderType::IOC; buy.price = to_price(185.0); buy.qty = 100;
251
- auto trades = book.add_order(buy);
252
-
253
- ASSERT_EQ(trades.size(), 1u);
254
- ASSERT_EQ(trades[0].qty, 50);
255
- ASSERT_EQ(buy.status, OrderStatus::EXPIRED); // unfilled remainder expired
256
- ASSERT_EQ(book.order_count(), 0u); // IOC not added to book
257
- PASS();
258
- }
259
-
260
- TEST(test_fok_rejected_insufficient_liquidity) {
261
- OrderBook book("AAPL");
262
-
263
- Order sell;
264
- sell.id = 1; sell.symbol = "AAPL"; sell.side = Side::SELL;
265
- sell.type = OrderType::LIMIT; sell.price = to_price(185.0); sell.qty = 50;
266
- book.add_order(sell);
267
-
268
- Order buy;
269
- buy.id = 2; buy.symbol = "AAPL"; buy.side = Side::BUY;
270
- buy.type = OrderType::FOK; buy.price = to_price(185.0); buy.qty = 100;
271
- auto trades = book.add_order(buy);
272
-
273
- ASSERT_EQ(trades.size(), 0u);
274
- ASSERT_EQ(buy.status, OrderStatus::EXPIRED);
275
- ASSERT_EQ(book.order_count(), 1u); // resting sell untouched
276
- PASS();
277
- }
278
-
279
- TEST(test_fok_filled_sufficient_liquidity) {
280
- OrderBook book("AAPL");
281
-
282
- Order sell;
283
- sell.id = 1; sell.symbol = "AAPL"; sell.side = Side::SELL;
284
- sell.type = OrderType::LIMIT; sell.price = to_price(185.0); sell.qty = 100;
285
- book.add_order(sell);
286
-
287
- Order buy;
288
- buy.id = 2; buy.symbol = "AAPL"; buy.side = Side::BUY;
289
- buy.type = OrderType::FOK; buy.price = to_price(185.0); buy.qty = 100;
290
- auto trades = book.add_order(buy);
291
-
292
- ASSERT_EQ(trades.size(), 1u);
293
- ASSERT_EQ(trades[0].qty, 100);
294
- ASSERT_EQ(book.order_count(), 0u);
295
- PASS();
296
- }
297
-
298
- // ─── Cancel & Modify ─────────────────────────────────────────────────────────
299
-
300
- TEST(test_cancel_order) {
301
- OrderBook book("AAPL");
302
-
303
- Order buy;
304
- buy.id = 1; buy.symbol = "AAPL"; buy.side = Side::BUY;
305
- buy.type = OrderType::LIMIT; buy.price = to_price(185.0); buy.qty = 100;
306
- book.add_order(buy);
307
-
308
- ASSERT_TRUE(book.cancel_order(1));
309
- ASSERT_EQ(book.order_count(), 0u);
310
- ASSERT_EQ(book.bid_levels(), 0u);
311
- ASSERT_EQ(buy.status, OrderStatus::CANCELLED);
312
- PASS();
313
- }
314
-
315
- TEST(test_cancel_nonexistent) {
316
- OrderBook book("AAPL");
317
- ASSERT_TRUE(!book.cancel_order(999));
318
- PASS();
319
- }
320
-
321
- TEST(test_modify_order) {
322
- OrderBook book("AAPL");
323
-
324
- Order buy;
325
- buy.id = 1; buy.symbol = "AAPL"; buy.side = Side::BUY;
326
- buy.type = OrderType::LIMIT; buy.price = to_price(185.0); buy.qty = 100;
327
- book.add_order(buy);
328
-
329
- ASSERT_TRUE(book.modify_order(1, to_price(186.0), 200));
330
- ASSERT_EQ(book.best_bid(), to_price(186.0));
331
- PASS();
332
- }
333
-
334
- // ─── Snapshot ────────────────────────────────────────────────────────────────
335
-
336
- TEST(test_snapshot_depth) {
337
- OrderBook book("AAPL");
338
- for (int i = 1; i <= 20; ++i) {
339
- Order o;
340
- o.id = i; o.symbol = "AAPL"; o.side = Side::BUY;
341
- o.type = OrderType::LIMIT; o.price = to_price(185.0 - i * 0.01); o.qty = 100;
342
- book.add_order(o);
343
- }
344
-
345
- auto snap = book.snapshot(5);
346
- ASSERT_EQ((int)snap.bids.size(), 5); // only top 5
347
- ASSERT_TRUE(snap.bids[0].price > snap.bids[1].price); // sorted descending
348
- PASS();
349
- }
350
-
351
- TEST(test_snapshot_spread) {
352
- OrderBook book("AAPL");
353
- Order buy, sell;
354
- buy.id = 1; buy.symbol = "AAPL"; buy.side = Side::BUY;
355
- buy.type = OrderType::LIMIT; buy.price = to_price(185.0); buy.qty = 100;
356
-
357
- sell.id = 2; sell.symbol = "AAPL"; sell.side = Side::SELL;
358
- sell.type = OrderType::LIMIT; sell.price = to_price(185.10); sell.qty = 100;
359
-
360
- book.add_order(buy);
361
- book.add_order(sell);
362
-
363
- auto snap = book.snapshot();
364
- ASSERT_TRUE(snap.spread() > 0);
365
- ASSERT_TRUE(std::abs(snap.spread() - 0.10) < 0.001);
366
- PASS();
367
- }
368
-
369
- TEST(test_empty_book_spread) {
370
- OrderBookSnapshot empty;
371
- ASSERT_TRUE(empty.spread() == 0.0);
372
- ASSERT_TRUE(empty.mid() == 0);
373
- PASS();
374
- }
375
-
376
- // ─── FIFO Priority ──────────────────────────────────────────────────────────
377
-
378
- TEST(test_fifo_priority) {
379
- OrderBook book("AAPL");
380
-
381
- // Two sells at same price — first should fill first
382
- Order s1, s2;
383
- s1.id = 1; s1.symbol = "AAPL"; s1.side = Side::SELL;
384
- s1.type = OrderType::LIMIT; s1.price = to_price(185.0); s1.qty = 100;
385
-
386
- s2.id = 2; s2.symbol = "AAPL"; s2.side = Side::SELL;
387
- s2.type = OrderType::LIMIT; s2.price = to_price(185.0); s2.qty = 100;
388
-
389
- book.add_order(s1);
390
- book.add_order(s2);
391
-
392
- Order buy;
393
- buy.id = 3; buy.symbol = "AAPL"; buy.side = Side::BUY;
394
- buy.type = OrderType::LIMIT; buy.price = to_price(185.0); buy.qty = 100;
395
- auto trades = book.add_order(buy);
396
-
397
- ASSERT_EQ(trades.size(), 1u);
398
- ASSERT_EQ(trades[0].sell_order_id, 1u); // s1 filled first (FIFO)
399
- ASSERT_EQ(book.order_count(), 1u); // s2 still resting
400
- PASS();
401
- }
402
-
403
- // ─── Main ────────────────────────────────────────────────────────────────────
404
-
405
- int main() {
406
- std::cout << "\n=== Orderflow Replay Engine — Unit Tests ===\n\n";
407
- // Tests run automatically via static initialization
408
-
409
- std::cout << "\n─────────────────────────────────────────────\n";
410
- std::cout << " Passed: " << tests_passed << "\n";
411
- std::cout << " Failed: " << tests_failed << "\n";
412
- std::cout << "─────────────────────────────────────────────\n\n";
413
-
414
- return tests_failed > 0 ? 1 : 0;
415
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tests/test_risk_manager.cpp DELETED
@@ -1,241 +0,0 @@
1
- // tests/test_risk_manager.cpp
2
- // Unit tests for the risk management gateway
3
-
4
- #include "risk/risk_manager.hpp"
5
- #include "core/types.hpp"
6
- #include <cassert>
7
- #include <iostream>
8
- #include <string>
9
- #include <thread>
10
- #include <chrono>
11
-
12
- using namespace hft;
13
-
14
- static int tests_passed = 0;
15
- static int tests_failed = 0;
16
-
17
- #define TEST(name) \
18
- void name(); \
19
- struct name##_register { name##_register() { std::cout << " " << #name << "... "; name(); } } name##_instance; \
20
- void name()
21
-
22
- #define ASSERT_EQ(a, b) do { \
23
- if ((a) != (b)) { \
24
- std::cerr << "FAIL: " << #a << " == " << (a) << ", expected " << (b) << " at line " << __LINE__ << "\n"; \
25
- ++tests_failed; return; \
26
- } \
27
- } while(0)
28
-
29
- #define ASSERT_TRUE(x) do { \
30
- if (!(x)) { \
31
- std::cerr << "FAIL: " << #x << " at line " << __LINE__ << "\n"; \
32
- ++tests_failed; return; \
33
- } \
34
- } while(0)
35
-
36
- #define PASS() do { std::cout << "PASS\n"; ++tests_passed; } while(0)
37
-
38
- static Order make_order(const std::string& sym, Side side, OrderType type,
39
- double price, Quantity qty) {
40
- Order o;
41
- o.id = 1; o.symbol = sym; o.side = side; o.type = type;
42
- o.price = to_price(price); o.qty = qty;
43
- return o;
44
- }
45
-
46
- // ─── Basic Approval ──────────────────────────────────────────────────────────
47
-
48
- TEST(test_valid_order_approved) {
49
- RiskLimits limits;
50
- limits.max_order_qty = 1000;
51
- limits.max_position = 10000;
52
- limits.max_notional_usd = 1000000;
53
- limits.max_orders_per_sec = 100;
54
- RiskManager rm(limits);
55
-
56
- auto o = make_order("AAPL", Side::BUY, OrderType::LIMIT, 185.0, 100);
57
- auto result = rm.check_order(o);
58
-
59
- ASSERT_TRUE(result.approved);
60
- ASSERT_EQ(rm.orders_checked(), 1);
61
- ASSERT_EQ(rm.orders_rejected(), 0);
62
- PASS();
63
- }
64
-
65
- // ─── Rejection Tests ─────────────────────────────────────────────────────────
66
-
67
- TEST(test_reject_qty_too_large) {
68
- RiskLimits limits;
69
- limits.max_order_qty = 100;
70
- RiskManager rm(limits);
71
-
72
- auto o = make_order("AAPL", Side::BUY, OrderType::LIMIT, 185.0, 500);
73
- auto result = rm.check_order(o);
74
-
75
- ASSERT_TRUE(!result.approved);
76
- ASSERT_EQ(result.reason, RiskRejectReason::MAX_ORDER_QTY);
77
- PASS();
78
- }
79
-
80
- TEST(test_reject_zero_qty) {
81
- RiskLimits limits;
82
- RiskManager rm(limits);
83
-
84
- auto o = make_order("AAPL", Side::BUY, OrderType::LIMIT, 185.0, 0);
85
- auto result = rm.check_order(o);
86
-
87
- ASSERT_TRUE(!result.approved);
88
- ASSERT_EQ(result.reason, RiskRejectReason::INVALID_QTY);
89
- PASS();
90
- }
91
-
92
- TEST(test_reject_negative_qty) {
93
- RiskLimits limits;
94
- RiskManager rm(limits);
95
-
96
- auto o = make_order("AAPL", Side::BUY, OrderType::LIMIT, 185.0, -10);
97
- auto result = rm.check_order(o);
98
-
99
- ASSERT_TRUE(!result.approved);
100
- ASSERT_EQ(result.reason, RiskRejectReason::INVALID_QTY);
101
- PASS();
102
- }
103
-
104
- TEST(test_reject_negative_limit_price) {
105
- RiskLimits limits;
106
- RiskManager rm(limits);
107
-
108
- auto o = make_order("AAPL", Side::BUY, OrderType::LIMIT, -5.0, 100);
109
- auto result = rm.check_order(o);
110
-
111
- ASSERT_TRUE(!result.approved);
112
- ASSERT_EQ(result.reason, RiskRejectReason::INVALID_PRICE);
113
- PASS();
114
- }
115
-
116
- TEST(test_reject_notional_too_large) {
117
- RiskLimits limits;
118
- limits.max_order_qty = 100000;
119
- limits.max_notional_usd = 1000.0; // very low limit
120
- RiskManager rm(limits);
121
-
122
- auto o = make_order("AAPL", Side::BUY, OrderType::LIMIT, 185.0, 100);
123
- // notional = 185 * 100 = 18500 > 1000
124
- auto result = rm.check_order(o);
125
-
126
- ASSERT_TRUE(!result.approved);
127
- ASSERT_EQ(result.reason, RiskRejectReason::MAX_NOTIONAL);
128
- PASS();
129
- }
130
-
131
- TEST(test_reject_position_limit) {
132
- RiskLimits limits;
133
- limits.max_order_qty = 10000;
134
- limits.max_position = 100;
135
- limits.max_notional_usd = 1e9;
136
- limits.max_orders_per_sec = 10000;
137
- RiskManager rm(limits);
138
-
139
- // Fill a position of 90
140
- rm.on_fill("AAPL", Side::BUY, 90, 185.0);
141
-
142
- // Try to buy 20 more → projected = 110 > max 100
143
- auto o = make_order("AAPL", Side::BUY, OrderType::LIMIT, 185.0, 20);
144
- auto result = rm.check_order(o);
145
-
146
- ASSERT_TRUE(!result.approved);
147
- ASSERT_EQ(result.reason, RiskRejectReason::MAX_POSITION);
148
- PASS();
149
- }
150
-
151
- // ─── Rate Limiter ────────────────────────────────────────────────────────────
152
-
153
- TEST(test_rejected_orders_dont_count_toward_rate_limit) {
154
- // This is the Bug #9 regression test
155
- RiskLimits limits;
156
- limits.max_order_qty = 100;
157
- limits.max_orders_per_sec = 5;
158
- limits.max_notional_usd = 1e9;
159
- limits.max_position = 1000000;
160
- RiskManager rm(limits);
161
-
162
- // Send 10 INVALID orders (qty too large) ��� these should NOT count
163
- for (int i = 0; i < 10; ++i) {
164
- auto bad = make_order("AAPL", Side::BUY, OrderType::LIMIT, 185.0, 500);
165
- rm.check_order(bad);
166
- }
167
-
168
- // Now send a valid order — it should still be approved
169
- auto good = make_order("AAPL", Side::BUY, OrderType::LIMIT, 185.0, 50);
170
- auto result = rm.check_order(good);
171
-
172
- ASSERT_TRUE(result.approved); // rate limit not exhausted by rejects
173
- PASS();
174
- }
175
-
176
- // ─── Position & PnL Tracking ─────────────────────────────────────────────────
177
-
178
- TEST(test_position_after_fill) {
179
- RiskLimits limits;
180
- RiskManager rm(limits);
181
-
182
- rm.on_fill("AAPL", Side::BUY, 100, 185.0);
183
- auto pos = rm.get_position("AAPL");
184
-
185
- ASSERT_EQ(pos.net_qty, 100);
186
- ASSERT_TRUE(std::abs(pos.avg_price - 185.0) < 0.01);
187
- PASS();
188
- }
189
-
190
- TEST(test_realized_pnl) {
191
- RiskLimits limits;
192
- RiskManager rm(limits);
193
-
194
- // Buy 100 @ 185
195
- rm.on_fill("AAPL", Side::BUY, 100, 185.0);
196
- // Sell 100 @ 190 → realized = (190 - 185) * 100 = 500
197
- rm.on_fill("AAPL", Side::SELL, 100, 190.0);
198
-
199
- auto pos = rm.get_position("AAPL");
200
- ASSERT_EQ(pos.net_qty, 0);
201
- ASSERT_TRUE(std::abs(pos.realized_pnl - 500.0) < 0.01);
202
- PASS();
203
- }
204
-
205
- TEST(test_unrealized_pnl) {
206
- RiskLimits limits;
207
- RiskManager rm(limits);
208
-
209
- rm.on_fill("AAPL", Side::BUY, 100, 185.0);
210
- rm.update_market_price("AAPL", 190.0);
211
-
212
- auto pos = rm.get_position("AAPL");
213
- // unrealized = (190 - 185) * 100 = 500
214
- ASSERT_TRUE(std::abs(pos.unrealized_pnl - 500.0) < 0.01);
215
- PASS();
216
- }
217
-
218
- TEST(test_disabled_risk_approves_all) {
219
- RiskLimits limits;
220
- limits.enabled = false;
221
- RiskManager rm(limits);
222
-
223
- auto o = make_order("AAPL", Side::BUY, OrderType::LIMIT, 185.0, 999999);
224
- auto result = rm.check_order(o);
225
-
226
- ASSERT_TRUE(result.approved);
227
- PASS();
228
- }
229
-
230
- // ─── Main ────────────────────────────────────────────────────────────────────
231
-
232
- int main() {
233
- std::cout << "\n=== Risk Manager — Unit Tests ===\n\n";
234
-
235
- std::cout << "\n─────────────────────────────────────────────\n";
236
- std::cout << " Passed: " << tests_passed << "\n";
237
- std::cout << " Failed: " << tests_failed << "\n";
238
- std::cout << "─────────────────────────────────────────────\n\n";
239
-
240
- return tests_failed > 0 ? 1 : 0;
241
- }