-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMatchingEngine.java
More file actions
141 lines (111 loc) · 5.79 KB
/
Copy pathMatchingEngine.java
File metadata and controls
141 lines (111 loc) · 5.79 KB
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
import java.util.*;
import java.util.concurrent.BlockingQueue;
public class MatchingEngine extends Thread {
// Order books
private final PriorityQueue<Order> bids =
new PriorityQueue<>(Comparator.comparingDouble(Order::getPrice).reversed());
private final PriorityQueue<Order> asks =
new PriorityQueue<>(Comparator.comparingDouble(Order::getPrice));
// Source of truth for active orders — enables O(1) lazy cancellation
private final Map<Integer, Order> orderLookup = new HashMap<>();
private final BlockingQueue<Order> incomingQueue;
// Optional Add-ons
private AnalyticsModule analytics;
private StorageLayer storage;
private MatchingEngineUI ui;
private int totalTrades = 0;
private double totalVolume = 0;
public MatchingEngine(BlockingQueue<Order> incomingQueue) {
this.incomingQueue = incomingQueue;
}
// --- Dependency Setters ---
public void setAnalytics(AnalyticsModule analytics) { this.analytics = analytics; }
public void setStorage (StorageLayer storage) { this.storage = storage; }
public void setUI (MatchingEngineUI ui) { this.ui = ui; }
@Override
public void run() {
while (true) {
try {
Order incoming = incomingQueue.take();
if (incoming == Order.POISON_PILL) break;
// 1. Handle CANCEL orders first
if (incoming.getSide() == Order.Side.CANCEL) {
Order target = orderLookup.remove(incoming.getCancelTargetId());
if (target != null) {
boolean isTargetBuy = target.getSide() == Order.Side.BUY;
if (isTargetBuy) bids.remove(target);
else asks.remove(target);
// Tell UI to remove from book and log the cancel
if (ui != null) {
ui.removeOrder(target.getSequenceId(), isTargetBuy);
ui.addCancelEvent(incoming.getSequenceId(), target.getSequenceId());
}
}
continue; // Skip the matching logic for cancels
}
// 2. Add standard BUY/SELL to UI snapshot
if (ui != null) ui.addOrder(incoming);
boolean isBuy = (incoming.getSide() == Order.Side.BUY);
PriorityQueue<Order> sameSide = isBuy ? bids : asks;
PriorityQueue<Order> oppositeSide = isBuy ? asks : bids;
// 3. Match against the opposite side of the book
while (!oppositeSide.isEmpty() && incoming.getQuantity() > 0) {
Order best = oppositeSide.peek();
boolean priceMatch = isBuy
? (incoming.getPrice() >= best.getPrice())
: (incoming.getPrice() <= best.getPrice());
if (!priceMatch) break;
// Calculate matched quantity and execution price
int qty = Math.min(incoming.getQuantity(), best.getQuantity());
double price = best.getPrice();
totalTrades++;
totalVolume += price * qty;
// Update memory quantities
incoming.setQuantity(incoming.getQuantity() - qty);
best.setQuantity(best.getQuantity() - qty);
int buyId = isBuy ? incoming.getSequenceId() : best.getSequenceId();
int sellId = isBuy ? best.getSequenceId() : incoming.getSequenceId();
// --- Update UI Book Quantities ---
if (ui != null) {
ui.updateOrderQty(incoming.getSequenceId(), isBuy, incoming.getQuantity());
ui.updateOrderQty(best.getSequenceId(), !isBuy, best.getQuantity());
}
// Record trade in analytics and storage
if (analytics != null) analytics.recordTrade(price, qty);
TradeRecord trade = new TradeRecord(totalTrades, buyId, sellId, price, qty);
if (storage != null) storage.record(trade);
if (ui != null) ui.addTrade(trade); // Log trade on screen
// Remove filled orders from the book
if (best.getQuantity() == 0) {
orderLookup.remove(oppositeSide.poll().getSequenceId());
}
}
// 4. If the incoming order isn't fully filled, add it to the book
if (incoming.getQuantity() > 0) {
sameSide.add(incoming);
orderLookup.put(incoming.getSequenceId(), incoming);
}
// Update analytics book depth (optional, keeps metrics accurate)
if (analytics != null) {
analytics.updateBookState(
bids.isEmpty() ? 0 : bids.peek().getPrice(),
asks.isEmpty() ? 0 : asks.peek().getPrice(),
bids.size(),
asks.size()
);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
printFinalStats();
}
private void printFinalStats() {
System.out.println("\n========= Final Stats =========");
System.out.printf("Total trades: %,d%n", totalTrades);
System.out.printf("Total volume: $%,.2f%n", totalVolume);
System.out.printf("Active orders in map:%,d%n", orderLookup.size());
System.out.println("================================");
}
}