-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMatchingEngineUI.java
More file actions
180 lines (150 loc) · 8.24 KB
/
Copy pathMatchingEngineUI.java
File metadata and controls
180 lines (150 loc) · 8.24 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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import javax.swing.*;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableRowSorter;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.*;
public class MatchingEngineUI extends JFrame {
// --- Binance Dark Theme Colors ---
private final Color BG_DARK = new Color(22, 26, 30); // Main background
private final Color BG_PANEL = new Color(30, 35, 41); // Panel background
private final Color TEXT_MAIN = new Color(183, 189, 198); // Standard text
private final Color TEXT_BUY = new Color(14, 203, 129); // Binance Green
private final Color TEXT_SELL = new Color(246, 70, 93); // Binance Red
private final Color BORDER_COLOR = new Color(43, 49, 57);
private final DefaultTableModel buyModel, sellModel, cancelModel, tradeModel;
// Analytics Labels
private final JLabel lblTrades = new JLabel("Trades: 0"), lblVolume = new JLabel("Volume: $0.00");
private final JLabel lblThroughput = new JLabel("Throughput: 0 /sec"), lblSpread = new JLabel("Spread: N/A");
private final JLabel lblDepth = new JLabel("Depth: 0 Bids / 0 Asks"), lblImbalance = new JLabel("Imbalance: 0.0%");
public MatchingEngineUI() {
setTitle("Matching Engine - Live Order Book");
setSize(1200, 800);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new BorderLayout(10, 10));
getContentPane().setBackground(BG_DARK);
// --- 1. SETUP ANALYTICS TOP BAR ---
JPanel analyticsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 30, 15));
analyticsPanel.setBackground(BG_PANEL);
analyticsPanel.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, BORDER_COLOR));
Font font = new Font("SansSerif", Font.BOLD, 14);
JLabel[] labels = {lblTrades, lblVolume, lblThroughput, lblSpread, lblDepth, lblImbalance};
for (JLabel lbl : labels) {
lbl.setFont(font);
lbl.setForeground(TEXT_MAIN);
analyticsPanel.add(lbl);
}
add(analyticsPanel, BorderLayout.NORTH);
// --- 2. SETUP TABLES ---
JPanel tablesPanel = new JPanel(new GridLayout(2, 2, 10, 10));
tablesPanel.setBackground(BG_DARK);
tablesPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
buyModel = new DefaultTableModel(new String[]{"Bid ID", "Price", "Quantity"}, 0);
sellModel = new DefaultTableModel(new String[]{"Ask ID", "Price", "Quantity"}, 0);
cancelModel = new DefaultTableModel(new String[]{"Cancel ID", "Target ID", "Status"}, 0);
tradeModel = new DefaultTableModel(new String[]{"Trade ID", "Buy ID", "Sell ID", "Price", "Qty"}, 0);
tablesPanel.add(createTablePanel("Order Book - Bids", buyModel, TEXT_BUY));
tablesPanel.add(createTablePanel("Order Book - Asks", sellModel, TEXT_SELL));
tablesPanel.add(createTablePanel("Event Log - Cancels", cancelModel, TEXT_MAIN));
tablesPanel.add(createTablePanel("Event Log - Trades", tradeModel, TEXT_MAIN));
add(tablesPanel, BorderLayout.CENTER);
}
private JPanel createTablePanel(String title, DefaultTableModel model, Color textColor) {
JPanel panel = new JPanel(new BorderLayout());
panel.setBackground(BG_PANEL);
panel.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(BORDER_COLOR), title, 0, 0, new Font("SansSerif", Font.BOLD, 14), TEXT_MAIN
));
JTable table = new JTable(model);
table.setBackground(BG_PANEL);
table.setForeground(textColor);
table.setGridColor(BORDER_COLOR);
table.setFillsViewportHeight(true);
table.setFont(new Font("Monospaced", Font.BOLD, 12));
table.getTableHeader().setBackground(BG_DARK);
table.getTableHeader().setForeground(TEXT_MAIN);
table.getTableHeader().setFont(new Font("SansSerif", Font.BOLD, 12));
TableRowSorter<DefaultTableModel> sorter = new TableRowSorter<>(model);
table.setRowSorter(sorter);
// Search Bar Styling
JPanel searchPanel = new JPanel(new BorderLayout());
searchPanel.setBackground(BG_PANEL);
searchPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 5, 0));
JLabel searchLabel = new JLabel(" Search: ");
searchLabel.setForeground(TEXT_MAIN);
JTextField searchField = new JTextField();
searchField.setBackground(BG_DARK);
searchField.setForeground(Color.WHITE);
searchField.setCaretColor(Color.WHITE);
searchField.setBorder(BorderFactory.createLineBorder(BORDER_COLOR));
searchPanel.add(searchLabel, BorderLayout.WEST);
searchPanel.add(searchField, BorderLayout.CENTER);
searchField.getDocument().addDocumentListener(new DocumentListener() {
@Override public void insertUpdate(DocumentEvent e) { filter(); }
@Override public void removeUpdate(DocumentEvent e) { filter(); }
@Override public void changedUpdate(DocumentEvent e) { filter(); }
private void filter() {
String text = searchField.getText();
sorter.setRowFilter(text.trim().isEmpty() ? null : RowFilter.regexFilter("(?i)" + text));
}
});
JScrollPane scrollPane = new JScrollPane(table);
scrollPane.getViewport().setBackground(BG_PANEL);
scrollPane.setBorder(BorderFactory.createEmptyBorder());
panel.add(searchPanel, BorderLayout.NORTH);
panel.add(scrollPane, BorderLayout.CENTER);
return panel;
}
// --- STATEFUL UI METHODS ---
public void addOrder(Order order) {
// Fix: Take an immutable snapshot BEFORE passing to the Swing thread
final String seqId = "#" + order.getSequenceId();
final String price = String.format("$%.2f", order.getPrice());
final int initialQty = order.getQuantity();
final Order.Side side = order.getSide();
SwingUtilities.invokeLater(() -> {
if (side == Order.Side.BUY) buyModel.insertRow(0, new Object[]{seqId, price, initialQty});
else if (side == Order.Side.SELL) sellModel.insertRow(0, new Object[]{seqId, price, initialQty});
});
}
public void updateOrderQty(int seqId, boolean isBuy, int newQty) {
SwingUtilities.invokeLater(() -> {
DefaultTableModel model = isBuy ? buyModel : sellModel;
String targetId = "#" + seqId;
for (int i = 0; i < model.getRowCount(); i++) {
if (model.getValueAt(i, 0).equals(targetId)) {
if (newQty <= 0) {
model.removeRow(i); // Order completely filled, remove from book
} else {
model.setValueAt(newQty, i, 2); // Update remaining quantity
}
break;
}
}
});
}
public void removeOrder(int seqId, boolean isBuy) {
updateOrderQty(seqId, isBuy, 0); // Re-use the method, passing 0 removes it
}
public void addCancelEvent(int cancelId, int targetId) {
SwingUtilities.invokeLater(() -> {
cancelModel.insertRow(0, new Object[]{"#" + cancelId, "#" + targetId, "CANCELLED"});
});
}
public void addTrade(TradeRecord trade) {
SwingUtilities.invokeLater(() -> {
tradeModel.insertRow(0, new Object[]{"#" + trade.tradeId, "#" + trade.buyOrderId, "#" + trade.sellOrderId, String.format("$%.2f", trade.price), trade.quantity});
});
}
public void updateAnalytics(long trades, double volumeUSD, double throughput, double spread, int bidDepth, int askDepth, double imbalance) {
SwingUtilities.invokeLater(() -> {
lblTrades.setText(String.format("Trades: %,d", trades));
lblVolume.setText(String.format("Volume: $%,.2f", volumeUSD));
lblThroughput.setText(String.format("Throughput: %,.0f /sec", throughput));
lblSpread.setText(spread >= 0 ? String.format("Spread: $%.4f", spread) : "Spread: N/A");
lblDepth.setText(String.format("Depth: %d Bids / %d Asks", bidDepth, askDepth));
lblImbalance.setText(String.format("Imbalance: %.1f%%", Math.abs(imbalance)));
});
}
}