NOT-OMEGA commited on
Commit
91c3a87
·
verified ·
1 Parent(s): 7a7284d

Delete index.html

Browse files
Files changed (1) hide show
  1. index.html +0 -1278
index.html DELETED
@@ -1,1278 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>HFT Exchange Simulator</title>
7
- <style>
8
- @import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;600;700&family=Space+Mono:wght@400;700&display=swap');
9
-
10
- :root {
11
- --bg: #080c10;
12
- --bg1: #0d1219;
13
- --bg2: #121820;
14
- --bg3: #1a2230;
15
- --border: #1e2d3d;
16
- --border2: #253545;
17
- --text: #c8d8e8;
18
- --dim: #5a7a90;
19
- --green: #00e676;
20
- --green2: #00c853;
21
- --red: #ff1744;
22
- --red2: #d50000;
23
- --yellow: #ffea00;
24
- --blue: #40c4ff;
25
- --purple: #ea80fc;
26
- --orange: #ff9100;
27
- --cyan: #18ffff;
28
- --accent: #00e5ff;
29
- --glow: 0 0 12px rgba(0,229,255,0.25);
30
- --glow-g: 0 0 12px rgba(0,230,118,0.3);
31
- --glow-r: 0 0 12px rgba(255,23,68,0.3);
32
- --mono: 'JetBrains Mono', 'Space Mono', monospace;
33
- }
34
-
35
- * { box-sizing: border-box; margin: 0; padding: 0; }
36
-
37
- body {
38
- font-family: var(--mono);
39
- background: var(--bg);
40
- color: var(--text);
41
- min-height: 100vh;
42
- overflow-x: hidden;
43
- font-size: 12px;
44
- }
45
-
46
- /* Scanline overlay */
47
- body::before {
48
- content: '';
49
- position: fixed; inset: 0;
50
- background: repeating-linear-gradient(0deg,
51
- transparent, transparent 2px,
52
- rgba(0,0,0,0.03) 2px, rgba(0,0,0,0.03) 4px);
53
- pointer-events: none; z-index: 9999;
54
- }
55
-
56
- /* Header */
57
- .header {
58
- display: flex; align-items: center; justify-content: space-between;
59
- padding: 10px 20px;
60
- background: linear-gradient(90deg, #050a10 0%, #0d1825 50%, #050a10 100%);
61
- border-bottom: 1px solid var(--accent);
62
- box-shadow: 0 0 30px rgba(0,229,255,0.1);
63
- position: sticky; top: 0; z-index: 100;
64
- }
65
-
66
- .logo {
67
- font-size: 15px; font-weight: 700; letter-spacing: 3px;
68
- color: var(--accent);
69
- text-shadow: 0 0 20px rgba(0,229,255,0.6);
70
- }
71
- .logo span { color: var(--green); }
72
-
73
- .header-stats {
74
- display: flex; gap: 24px;
75
- }
76
- .hstat { text-align: center; }
77
- .hstat-label { font-size: 9px; color: var(--dim); text-transform: uppercase; letter-spacing: 1px; }
78
- .hstat-val { font-size: 13px; font-weight: 600; }
79
-
80
- .conn-dot {
81
- width: 8px; height: 8px; border-radius: 50%;
82
- background: var(--green);
83
- box-shadow: 0 0 8px var(--green);
84
- animation: pulse 2s ease-in-out infinite;
85
- }
86
- @keyframes pulse {
87
- 0%,100% { opacity: 1; } 50% { opacity: 0.4; }
88
- }
89
-
90
- /* Main grid */
91
- .grid {
92
- display: grid;
93
- grid-template-columns: 320px 1fr 300px;
94
- grid-template-rows: auto auto auto;
95
- gap: 1px;
96
- background: var(--border);
97
- height: calc(100vh - 48px);
98
- }
99
-
100
- .panel {
101
- background: var(--bg1);
102
- overflow: hidden;
103
- display: flex; flex-direction: column;
104
- }
105
-
106
- .panel-title {
107
- font-size: 9px; font-weight: 700;
108
- text-transform: uppercase; letter-spacing: 2px;
109
- color: var(--dim);
110
- padding: 8px 14px;
111
- border-bottom: 1px solid var(--border);
112
- display: flex; align-items: center; justify-content: space-between;
113
- background: var(--bg2);
114
- flex-shrink: 0;
115
- }
116
- .panel-title .tag {
117
- font-size: 8px; padding: 2px 6px;
118
- background: var(--bg3); border-radius: 2px;
119
- color: var(--accent); letter-spacing: 1px;
120
- }
121
-
122
- .panel-body { flex: 1; overflow-y: auto; padding: 12px; }
123
- .panel-body::-webkit-scrollbar { width: 3px; }
124
- .panel-body::-webkit-scrollbar-track { background: var(--bg1); }
125
- .panel-body::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 2px; }
126
-
127
- /* ORDER BOOK */
128
- .book-header {
129
- display: grid; grid-template-columns: 1fr 80px 80px;
130
- font-size: 9px; color: var(--dim); letter-spacing: 1px;
131
- padding: 4px 12px; border-bottom: 1px solid var(--border);
132
- text-transform: uppercase;
133
- }
134
-
135
- .book-row {
136
- display: grid; grid-template-columns: 1fr 80px 80px;
137
- padding: 3px 12px;
138
- position: relative; cursor: default;
139
- transition: background 0.1s;
140
- }
141
- .book-row:hover { background: rgba(255,255,255,0.03); }
142
-
143
- .book-bar {
144
- position: absolute; top: 0; bottom: 0;
145
- opacity: 0.12; pointer-events: none;
146
- transition: width 0.3s ease;
147
- }
148
-
149
- .ask-row .book-bar { background: var(--red); right: 0; }
150
- .bid-row .book-bar { background: var(--green); left: 0; }
151
-
152
- .ask-row .price { color: var(--red); font-weight: 600; }
153
- .bid-row .price { color: var(--green); font-weight: 600; }
154
- .qty-val { text-align: right; color: var(--text); }
155
- .orders-val { text-align: right; color: var(--dim); }
156
-
157
- .spread-row {
158
- display: flex; align-items: center; justify-content: center;
159
- padding: 4px; font-size: 9px; color: var(--yellow);
160
- border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
161
- background: var(--bg2);
162
- letter-spacing: 2px;
163
- }
164
-
165
- /* ORDER ENTRY */
166
- .tabs {
167
- display: flex; border-bottom: 1px solid var(--border);
168
- flex-shrink: 0;
169
- }
170
- .tab {
171
- flex: 1; padding: 8px; font-family: var(--mono); font-size: 9px;
172
- font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
173
- border: none; cursor: pointer;
174
- transition: all 0.2s;
175
- background: var(--bg2); color: var(--dim);
176
- }
177
- .tab.active-buy { background: rgba(0,230,118,0.1); color: var(--green); border-bottom: 2px solid var(--green); }
178
- .tab.active-sell { background: rgba(255,23,68,0.1); color: var(--red); border-bottom: 2px solid var(--red); }
179
- .tab:hover { background: var(--bg3); }
180
-
181
- .form-group { margin-bottom: 10px; }
182
- label { font-size: 9px; color: var(--dim); text-transform: uppercase; letter-spacing: 1px; display: block; margin-bottom: 4px; }
183
-
184
- input, select {
185
- width: 100%; padding: 8px 10px;
186
- background: var(--bg3); border: 1px solid var(--border2);
187
- color: var(--text); font-family: var(--mono); font-size: 12px;
188
- border-radius: 2px; outline: none;
189
- transition: border-color 0.2s, box-shadow 0.2s;
190
- }
191
- input:focus, select:focus {
192
- border-color: var(--accent);
193
- box-shadow: var(--glow);
194
- }
195
- select option { background: var(--bg2); }
196
-
197
- .btn {
198
- width: 100%; padding: 10px;
199
- font-family: var(--mono); font-size: 11px; font-weight: 700;
200
- letter-spacing: 2px; text-transform: uppercase;
201
- border: none; border-radius: 2px; cursor: pointer;
202
- transition: all 0.2s; margin-top: 4px;
203
- }
204
- .btn-buy {
205
- background: linear-gradient(135deg, #00c853, #00e676);
206
- color: #001a0a;
207
- box-shadow: 0 0 15px rgba(0,230,118,0.3);
208
- }
209
- .btn-buy:hover { box-shadow: 0 0 25px rgba(0,230,118,0.5); transform: translateY(-1px); }
210
- .btn-sell {
211
- background: linear-gradient(135deg, #d50000, #ff1744);
212
- color: #1a0000;
213
- box-shadow: 0 0 15px rgba(255,23,68,0.3);
214
- }
215
- .btn-sell:hover { box-shadow: 0 0 25px rgba(255,23,68,0.5); transform: translateY(-1px); }
216
- .btn-neutral {
217
- background: var(--bg3); color: var(--text);
218
- border: 1px solid var(--border2);
219
- }
220
- .btn-neutral:hover { border-color: var(--accent); color: var(--accent); }
221
- .btn-accent {
222
- background: linear-gradient(135deg, #006080, #00acc1);
223
- color: #001a20;
224
- box-shadow: 0 0 15px rgba(0,229,255,0.2);
225
- }
226
- .btn-accent:hover { box-shadow: 0 0 25px rgba(0,229,255,0.4); transform: translateY(-1px); }
227
-
228
- /* PRICE CHART */
229
- canvas { display: block; }
230
- #price-canvas { width: 100%; }
231
-
232
- /* LATENCY / STATS */
233
- .stat-row {
234
- display: flex; justify-content: space-between;
235
- padding: 5px 0; border-bottom: 1px solid rgba(30,45,61,0.5);
236
- }
237
- .stat-key { color: var(--dim); font-size: 10px; }
238
- .stat-val { font-weight: 600; font-size: 11px; }
239
- .green { color: var(--green); }
240
- .red { color: var(--red); }
241
- .yellow { color: var(--yellow); }
242
- .blue { color: var(--blue); }
243
- .cyan { color: var(--cyan); }
244
-
245
- /* TRADE LOG */
246
- .trade-entry {
247
- display: flex; gap: 8px; padding: 5px 0;
248
- border-bottom: 1px solid rgba(30,45,61,0.4);
249
- font-size: 10px; align-items: center;
250
- animation: fadeIn 0.3s ease;
251
- }
252
- @keyframes fadeIn { from { opacity: 0; transform: translateX(-5px); } to { opacity: 1; transform: translateX(0); } }
253
-
254
- .trade-side {
255
- padding: 2px 5px; font-size: 8px; font-weight: 700;
256
- letter-spacing: 1px; border-radius: 2px;
257
- min-width: 30px; text-align: center;
258
- }
259
- .buy-badge { background: rgba(0,230,118,0.15); color: var(--green); }
260
- .sell-badge { background: rgba(255,23,68,0.15); color: var(--red); }
261
-
262
- /* POSITION TABLE */
263
- .pos-table { width: 100%; border-collapse: collapse; font-size: 10px; }
264
- .pos-table th {
265
- text-align: left; padding: 4px 6px;
266
- color: var(--dim); font-size: 8px; text-transform: uppercase; letter-spacing: 1px;
267
- border-bottom: 1px solid var(--border);
268
- }
269
- .pos-table td { padding: 5px 6px; border-bottom: 1px solid rgba(30,45,61,0.3); }
270
-
271
- /* BACKTEST PANEL */
272
- .bt-result {
273
- background: var(--bg2); border: 1px solid var(--border2);
274
- border-radius: 3px; padding: 10px; margin-top: 10px;
275
- }
276
- .bt-metric {
277
- display: flex; justify-content: space-between;
278
- padding: 3px 0; font-size: 10px;
279
- }
280
-
281
- /* EQUITY CURVE */
282
- #equity-canvas { width: 100%; border-top: 1px solid var(--border); }
283
-
284
- /* LATENCY BARS */
285
- .lat-bar-wrap { margin: 4px 0; }
286
- .lat-bar-label { display: flex; justify-content: space-between; font-size: 9px; margin-bottom: 2px; }
287
- .lat-bar { height: 6px; background: var(--bg3); border-radius: 3px; overflow: hidden; }
288
- .lat-bar-fill { height: 100%; border-radius: 3px; transition: width 0.5s ease; }
289
-
290
- /* RISK PANEL */
291
- .risk-meter {
292
- height: 8px; background: var(--bg3); border-radius: 4px; overflow: hidden;
293
- margin: 4px 0 10px;
294
- }
295
- .risk-fill {
296
- height: 100%; border-radius: 4px; transition: width 0.5s ease;
297
- }
298
-
299
- /* FEED CONTROL */
300
- .feed-control {
301
- display: flex; gap: 6px; padding: 8px 12px;
302
- border-top: 1px solid var(--border);
303
- flex-shrink: 0;
304
- }
305
- .feed-control .btn { margin: 0; flex: 1; padding: 7px; font-size: 9px; }
306
-
307
- /* Symbol selector strip */
308
- .sym-strip {
309
- display: flex; gap: 4px; padding: 6px 12px;
310
- border-bottom: 1px solid var(--border);
311
- overflow-x: auto; flex-shrink: 0;
312
- }
313
- .sym-btn {
314
- padding: 3px 10px; font-family: var(--mono); font-size: 9px;
315
- font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
316
- border: 1px solid var(--border2); border-radius: 2px;
317
- cursor: pointer; background: var(--bg3); color: var(--dim);
318
- transition: all 0.15s; white-space: nowrap;
319
- }
320
- .sym-btn.active, .sym-btn:hover {
321
- border-color: var(--accent); color: var(--accent);
322
- background: rgba(0,229,255,0.08);
323
- }
324
-
325
- /* Notification toast */
326
- .toast {
327
- position: fixed; top: 60px; right: 20px;
328
- background: var(--bg2); border: 1px solid var(--accent);
329
- padding: 10px 16px; border-radius: 3px;
330
- font-size: 11px; z-index: 1000;
331
- animation: toastIn 0.3s ease, toastOut 0.3s ease 2.7s forwards;
332
- box-shadow: var(--glow);
333
- }
334
- @keyframes toastIn { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: translateX(0); } }
335
- @keyframes toastOut { to { opacity: 0; transform: translateX(20px); } }
336
-
337
- /* Scrollable sections */
338
- .scroll-area { flex: 1; overflow-y: auto; }
339
- .scroll-area::-webkit-scrollbar { width: 3px; }
340
- .scroll-area::-webkit-scrollbar-thumb { background: var(--border2); }
341
-
342
- /* Responsive col spans */
343
- .span2 { grid-column: span 2; }
344
- .col-left { grid-column: 1; grid-row: 1 / span 3; }
345
- .col-mid { grid-column: 2; }
346
- .col-right { grid-column: 3; grid-row: 1 / span 3; }
347
-
348
- .grid-inner {
349
- display: grid;
350
- grid-template-columns: 320px 1fr 300px;
351
- grid-template-rows: 360px 260px 1fr;
352
- gap: 1px;
353
- background: var(--border);
354
- height: calc(100vh - 48px);
355
- }
356
-
357
- /* Alert for rejected orders */
358
- .rejected { border-left: 3px solid var(--red) !important; animation: shake 0.3s; }
359
- @keyframes shake {
360
- 0%,100% { transform: translateX(0); }
361
- 25% { transform: translateX(-4px); }
362
- 75% { transform: translateX(4px); }
363
- }
364
-
365
- /* Blinking cursor */
366
- .cursor::after { content: '█'; animation: blink 1s step-end infinite; color: var(--accent); }
367
- @keyframes blink { 50% { opacity: 0; } }
368
-
369
- /* Number animations */
370
- .num-up { animation: numUp 0.4s ease; }
371
- .num-down { animation: numDown 0.4s ease; }
372
- @keyframes numUp { 0% { color: var(--green); } 100% { color: inherit; } }
373
- @keyframes numDown { 0% { color: var(--red); } 100% { color: inherit; } }
374
-
375
- /* Progress ring for latency */
376
- .ring-wrap { position: relative; display: inline-flex; align-items: center; justify-content: center; }
377
-
378
- /* Hover row highlight for order book */
379
- .book-asks .book-row:hover .price { text-shadow: 0 0 8px var(--red); }
380
- .book-bids .book-row:hover .price { text-shadow: 0 0 8px var(--green); }
381
-
382
- /* Section divider */
383
- .divider { height: 1px; background: var(--border); margin: 10px 0; }
384
-
385
- /* Blinking red for reject */
386
- .flash-red { animation: flashRed 0.5s; }
387
- @keyframes flashRed { 0%,100% { background: var(--bg1); } 50% { background: rgba(255,23,68,0.15); } }
388
-
389
- /* Toggle switch */
390
- .toggle { position: relative; display: inline-flex; align-items: center; gap: 8px; cursor: pointer; }
391
- .toggle input { display: none; }
392
- .toggle-track {
393
- width: 32px; height: 16px; background: var(--bg3);
394
- border: 1px solid var(--border2); border-radius: 8px;
395
- transition: background 0.2s;
396
- position: relative;
397
- }
398
- .toggle input:checked + .toggle-track { background: var(--green2); border-color: var(--green); }
399
- .toggle-thumb {
400
- position: absolute; top: 1px; left: 1px;
401
- width: 12px; height: 12px; border-radius: 6px;
402
- background: var(--dim); transition: transform 0.2s, background 0.2s;
403
- }
404
- .toggle input:checked ~ .toggle-thumb {
405
- transform: translateX(16px); background: white;
406
- }
407
-
408
- .mode-tabs { display: flex; gap: 1px; border-bottom: 1px solid var(--border); flex-shrink: 0; }
409
- .mode-tab {
410
- flex: 1; padding: 6px; font-family: var(--mono); font-size: 9px; font-weight: 700;
411
- letter-spacing: 1px; text-transform: uppercase; border: none; cursor: pointer;
412
- background: var(--bg2); color: var(--dim); transition: all 0.15s;
413
- }
414
- .mode-tab.active { background: var(--bg3); color: var(--accent); border-bottom: 1px solid var(--accent); }
415
- </style>
416
- </head>
417
- <body>
418
-
419
- <!-- HEADER -->
420
- <div class="header">
421
- <div style="display:flex;align-items:center;gap:16px">
422
- <div class="conn-dot" id="conn-dot"></div>
423
- <div class="logo">HFT<span>·</span>EXCHANGE<span>·</span>SIM</div>
424
- <div style="font-size:9px;color:var(--dim);letter-spacing:1px">C++ MATCHING ENGINE</div>
425
- </div>
426
- <div class="header-stats">
427
- <div class="hstat"><div class="hstat-label">Orders</div><div class="hstat-val cyan" id="h-orders">0</div></div>
428
- <div class="hstat"><div class="hstat-label">Trades</div><div class="hstat-val green" id="h-trades">0</div></div>
429
- <div class="hstat"><div class="hstat-label">Rejects</div><div class="hstat-val red" id="h-rejects">0</div></div>
430
- <div class="hstat"><div class="hstat-label">P&L</div><div class="hstat-val" id="h-pnl">$0.00</div></div>
431
- <div class="hstat"><div class="hstat-label">p99 Latency</div><div class="hstat-val yellow" id="h-p99">—</div></div>
432
- <div class="hstat"><div class="hstat-label">Throughput</div><div class="hstat-val blue" id="h-thru">—</div></div>
433
- </div>
434
- </div>
435
-
436
- <!-- MAIN GRID -->
437
- <div class="grid-inner">
438
-
439
- <!-- COL 1: Order Book + Order Entry -->
440
- <div class="panel" style="grid-column:1;grid-row:1/span 3;display:flex;flex-direction:column">
441
-
442
- <!-- Symbol strip -->
443
- <div class="sym-strip" id="sym-strip"></div>
444
-
445
- <!-- Order Book -->
446
- <div class="panel-title">
447
- Order Book — L2 <span id="book-symbol-tag" class="tag">AAPL</span>
448
- </div>
449
- <div class="book-header">
450
- <span>PRICE</span><span style="text-align:right">QTY</span><span style="text-align:right">ODS</span>
451
- </div>
452
-
453
- <div style="flex:1;overflow-y:auto;display:flex;flex-direction:column">
454
- <div class="book-asks scroll-area" id="book-asks" style="flex:1;display:flex;flex-direction:column;justify-content:flex-end"></div>
455
- <div class="spread-row" id="spread-row">SPREAD: —</div>
456
- <div class="book-bids scroll-area" id="book-bids" style="flex:1"></div>
457
- </div>
458
-
459
- <!-- Order Entry -->
460
- <div class="divider" style="margin:0"></div>
461
- <div class="panel-title">Order Entry</div>
462
-
463
- <div class="tabs">
464
- <button class="tab active-buy" id="tab-buy" onclick="setOrderSide('BUY')">▲ BUY</button>
465
- <button class="tab" id="tab-sell" onclick="setOrderSide('SELL')">▼ SELL</button>
466
- </div>
467
-
468
- <div style="padding:12px;flex-shrink:0">
469
- <div class="form-group">
470
- <label>Type</label>
471
- <select id="oe-type" onchange="updateOrderForm()">
472
- <option value="LIMIT">LIMIT</option>
473
- <option value="MARKET">MARKET</option>
474
- <option value="IOC">IOC (Immediate-Or-Cancel)</option>
475
- <option value="FOK">FOK (Fill-Or-Kill)</option>
476
- </select>
477
- </div>
478
- <div class="form-group">
479
- <label>Symbol</label>
480
- <select id="oe-symbol">
481
- <option>AAPL</option><option>GOOG</option><option>MSFT</option>
482
- <option>TSLA</option><option>NVDA</option>
483
- </select>
484
- </div>
485
- <div class="form-group" id="oe-price-group">
486
- <label>Price</label>
487
- <input type="number" id="oe-price" step="0.01" placeholder="0.00">
488
- </div>
489
- <div class="form-group">
490
- <label>Quantity</label>
491
- <input type="number" id="oe-qty" value="100" min="1">
492
- </div>
493
- <div class="form-group" style="display:none">
494
- <label>Client ID (optional)</label>
495
- <input type="text" id="oe-cid" placeholder="STRAT_01">
496
- </div>
497
- <button class="btn btn-buy" id="submit-btn" onclick="submitOrder()">▲ PLACE BUY ORDER</button>
498
- </div>
499
-
500
- <div class="feed-control">
501
- <button class="btn btn-neutral" onclick="feedStep(10)">▶ +10 TICKS</button>
502
- <button class="btn btn-neutral" onclick="feedStep(100)">▶▶ +100</button>
503
- <button class="btn btn-accent" onclick="autoFeed()" id="auto-btn">AUTO</button>
504
- </div>
505
- </div>
506
-
507
- <!-- COL 2, ROW 1: Price Chart -->
508
- <div class="panel" style="grid-column:2;grid-row:1">
509
- <div class="panel-title">
510
- Price Feed — Real-time Tick
511
- <div style="display:flex;gap:10px;align-items:center">
512
- <span id="chart-symbol" class="tag">AAPL</span>
513
- <span id="chart-price" style="font-size:13px;font-weight:700;color:var(--accent)">—</span>
514
- </div>
515
- </div>
516
- <canvas id="price-canvas" height="290"></canvas>
517
- </div>
518
-
519
- <!-- COL 2, ROW 2: Latency / Throughput -->
520
- <div class="panel" style="grid-column:2;grid-row:2">
521
- <div class="panel-title">Latency Profiler <span class="tag">C++ ENGINE</span></div>
522
- <div class="panel-body" style="display:grid;grid-template-columns:1fr 1fr;gap:16px">
523
- <div>
524
- <div style="font-size:9px;color:var(--dim);text-transform:uppercase;letter-spacing:1px;margin-bottom:8px">Order Latency</div>
525
- <div class="lat-bar-wrap">
526
- <div class="lat-bar-label"><span style="color:var(--dim)">p50</span><span id="lat-o-p50" style="color:var(--green)">—</span></div>
527
- <div class="lat-bar"><div class="lat-bar-fill" id="lat-o-p50-bar" style="background:var(--green);width:0%"></div></div>
528
- </div>
529
- <div class="lat-bar-wrap">
530
- <div class="lat-bar-label"><span style="color:var(--dim)">p99</span><span id="lat-o-p99" style="color:var(--yellow)">—</span></div>
531
- <div class="lat-bar"><div class="lat-bar-fill" id="lat-o-p99-bar" style="background:var(--yellow);width:0%"></div></div>
532
- </div>
533
- <div class="lat-bar-wrap">
534
- <div class="lat-bar-label"><span style="color:var(--dim)">avg</span><span id="lat-o-avg" style="color:var(--blue)">—</span></div>
535
- <div class="lat-bar"><div class="lat-bar-fill" id="lat-o-avg-bar" style="background:var(--blue);width:0%"></div></div>
536
- </div>
537
- <div class="divider"></div>
538
- <div class="stat-row"><span class="stat-key">Checked</span><span id="risk-checked" class="stat-val cyan">0</span></div>
539
- <div class="stat-row"><span class="stat-key">Rejected</span><span id="risk-rejected" class="stat-val red">0</span></div>
540
- <div class="stat-row"><span class="stat-key">Fill Rate</span><span id="fill-rate" class="stat-val green">0%</span></div>
541
- </div>
542
- <div>
543
- <div style="font-size:9px;color:var(--dim);text-transform:uppercase;letter-spacing:1px;margin-bottom:8px">Tick Latency</div>
544
- <div class="lat-bar-wrap">
545
- <div class="lat-bar-label"><span style="color:var(--dim)">p50</span><span id="lat-t-p50" style="color:var(--green)">—</span></div>
546
- <div class="lat-bar"><div class="lat-bar-fill" id="lat-t-p50-bar" style="background:var(--green);width:0%"></div></div>
547
- </div>
548
- <div class="lat-bar-wrap">
549
- <div class="lat-bar-label"><span style="color:var(--dim)">p99</span><span id="lat-t-p99" style="color:var(--yellow)">—</span></div>
550
- <div class="lat-bar"><div class="lat-bar-fill" id="lat-t-p99-bar" style="background:var(--yellow);width:0%"></div></div>
551
- </div>
552
- <div class="divider"></div>
553
- <div class="stat-row"><span class="stat-key">Feed Size</span><span id="feed-size" class="stat-val">—</span></div>
554
- <div class="stat-row"><span class="stat-key">Feed Index</span><span id="feed-idx" class="stat-val">—</span></div>
555
- <div class="stat-row"><span class="stat-key">Total P&L</span><span id="stat-pnl" class="stat-val">—</span></div>
556
- </div>
557
- </div>
558
- </div>
559
-
560
- <!-- COL 2, ROW 3: Trade Log -->
561
- <div class="panel" style="grid-column:2;grid-row:3">
562
- <div class="panel-title">
563
- Trade Log
564
- <div style="display:flex;gap:8px">
565
- <span id="trade-count" class="tag">0 trades</span>
566
- <button class="btn btn-neutral" style="padding:2px 8px;font-size:8px;margin:0;width:auto" onclick="resetSim()">RESET</button>
567
- </div>
568
- </div>
569
- <div class="scroll-area" style="flex:1" id="trade-log"></div>
570
- </div>
571
-
572
- <!-- COL 3: Positions + Backtest + Risk -->
573
- <div class="panel" style="grid-column:3;grid-row:1/span 3;display:flex;flex-direction:column">
574
-
575
- <div class="mode-tabs">
576
- <button class="mode-tab active" onclick="setRightMode('positions')">POSITIONS</button>
577
- <button class="mode-tab" onclick="setRightMode('backtest')">BACKTEST</button>
578
- <button class="mode-tab" onclick="setRightMode('risk')">RISK</button>
579
- </div>
580
-
581
- <!-- POSITIONS VIEW -->
582
- <div id="view-positions" style="flex:1;overflow:hidden;display:flex;flex-direction:column">
583
- <div class="panel-title">Live Positions</div>
584
- <div class="panel-body" style="overflow:auto">
585
- <table class="pos-table" id="pos-table">
586
- <thead><tr>
587
- <th>SYM</th><th>POS</th><th>AVG PX</th><th>RPNL</th><th>UPNL</th>
588
- </tr></thead>
589
- <tbody id="pos-body"></tbody>
590
- </table>
591
- <div class="divider"></div>
592
- <div style="font-size:9px;color:var(--dim);text-transform:uppercase;letter-spacing:1px;margin-bottom:6px">Recent Orders</div>
593
- <div id="order-log" style="max-height:200px;overflow-y:auto"></div>
594
- </div>
595
- </div>
596
-
597
- <!-- BACKTEST VIEW -->
598
- <div id="view-backtest" style="flex:1;overflow:hidden;display:flex;flex-direction:column;display:none">
599
- <div class="panel-title">Backtest Engine <span class="tag">DETERMINISTIC</span></div>
600
- <div class="panel-body" style="overflow:auto">
601
- <div class="form-group">
602
- <label>Strategy</label>
603
- <select id="bt-strategy">
604
- <option value="MarketMaking">Market Making (MM)</option>
605
- <option value="Momentum">Momentum (EMA Cross)</option>
606
- <option value="MeanReversion">Mean Reversion (Z-Score)</option>
607
- </select>
608
- </div>
609
- <div class="form-group">
610
- <label>Symbol</label>
611
- <select id="bt-symbol">
612
- <option>AAPL</option><option>GOOG</option><option>MSFT</option>
613
- <option>TSLA</option><option>NVDA</option>
614
- </select>
615
- </div>
616
- <div class="form-group">
617
- <label>Ticks to Replay</label>
618
- <input type="number" id="bt-ticks" value="5000" min="200" max="50000">
619
- </div>
620
- <div class="form-group">
621
- <label>Start Price</label>
622
- <input type="number" id="bt-price" value="185.00" step="0.01">
623
- </div>
624
- <button class="btn btn-accent" onclick="runBacktest()" id="bt-btn">▶ RUN BACKTEST</button>
625
-
626
- <div id="bt-result" style="display:none" class="bt-result">
627
- <div style="font-size:9px;color:var(--dim);letter-spacing:1px;margin-bottom:8px">BACKTEST RESULTS</div>
628
- <div class="bt-metric"><span class="stat-key">Strategy</span><span id="bt-r-strategy" class="stat-val cyan"></span></div>
629
- <div class="bt-metric"><span class="stat-key">Ticks</span><span id="bt-r-ticks" class="stat-val"></span></div>
630
- <div class="bt-metric"><span class="stat-key">Orders</span><span id="bt-r-orders" class="stat-val"></span></div>
631
- <div class="bt-metric"><span class="stat-key">Fills</span><span id="bt-r-fills" class="stat-val green"></span></div>
632
- <div class="bt-metric"><span class="stat-key">Rejected</span><span id="bt-r-rej" class="stat-val red"></span></div>
633
- <div class="bt-metric"><span class="stat-key">Trades</span><span id="bt-r-trades" class="stat-val"></span></div>
634
- <div class="bt-metric"><span class="stat-key">Total P&L</span><span id="bt-r-pnl" class="stat-val"></span></div>
635
- <div class="bt-metric"><span class="stat-key">Max Drawdown</span><span id="bt-r-dd" class="stat-val red"></span></div>
636
- <div class="bt-metric"><span class="stat-key">Sharpe</span><span id="bt-r-sharpe" class="stat-val yellow"></span></div>
637
- <div class="bt-metric"><span class="stat-key">Order p50</span><span id="bt-r-p50" class="stat-val green"></span></div>
638
- <div class="bt-metric"><span class="stat-key">Order p99</span><span id="bt-r-p99" class="stat-val yellow"></span></div>
639
- <div class="bt-metric"><span class="stat-key">Throughput</span><span id="bt-r-thru" class="stat-val blue"></span></div>
640
- <canvas id="equity-canvas" height="100" style="margin-top:10px;width:100%;border:1px solid var(--border)"></canvas>
641
- </div>
642
- </div>
643
- </div>
644
-
645
- <!-- RISK VIEW -->
646
- <div id="view-risk" style="flex:1;overflow:hidden;display:flex;flex-direction:column;display:none">
647
- <div class="panel-title">Pre-Trade Risk <span class="tag">LIVE</span></div>
648
- <div class="panel-body" style="overflow:auto">
649
- <div class="form-group">
650
- <label>Max Order Qty</label>
651
- <input type="number" id="r-max-qty" value="10000">
652
- </div>
653
- <div class="form-group">
654
- <label>Max Position</label>
655
- <input type="number" id="r-max-pos" value="500000">
656
- </div>
657
- <div class="form-group">
658
- <label>Max Notional ($)</label>
659
- <input type="number" id="r-max-notional" value="50000000">
660
- </div>
661
- <div class="form-group">
662
- <label>Max Orders/sec</label>
663
- <input type="number" id="r-max-rate" value="2000">
664
- </div>
665
- <button class="btn btn-accent" onclick="setRiskLimits()">UPDATE LIMITS</button>
666
-
667
- <div class="divider"></div>
668
- <div style="font-size:9px;color:var(--dim);text-transform:uppercase;letter-spacing:1px;margin-bottom:8px">Risk Utilization</div>
669
- <div class="lat-bar-wrap">
670
- <div class="lat-bar-label"><span style="color:var(--dim)">Order Rate</span><span id="r-rate-pct" style="color:var(--yellow)">0%</span></div>
671
- <div class="lat-bar"><div class="lat-bar-fill" id="r-rate-bar" style="width:0%;background:var(--yellow)"></div></div>
672
- </div>
673
- <div class="lat-bar-wrap">
674
- <div class="lat-bar-label"><span style="color:var(--dim)">Reject Rate</span><span id="r-rej-pct" style="color:var(--red)">0%</span></div>
675
- <div class="lat-bar"><div class="lat-bar-fill" id="r-rej-bar" style="width:0%;background:var(--red)"></div></div>
676
- </div>
677
- <div class="divider"></div>
678
- <div class="stat-row"><span class="stat-key">Checked</span><span id="r-checked" class="stat-val">0</span></div>
679
- <div class="stat-row"><span class="stat-key">Approved</span><span id="r-approved" class="stat-val green">0</span></div>
680
- <div class="stat-row"><span class="stat-key">Rejected</span><span id="r-rejected2" class="stat-val red">0</span></div>
681
-
682
- <div class="divider"></div>
683
- <div style="font-size:9px;color:var(--dim);text-transform:uppercase;letter-spacing:1px;margin-bottom:8px">Risk Check Types</div>
684
- <div style="font-size:10px;color:var(--text);line-height:1.8">
685
- ✓ Max Order Size<br>
686
- ✓ Max Position Limit<br>
687
- ✓ Max Notional Exposure<br>
688
- ✓ Max Order Rate (1s window)<br>
689
- ✓ Invalid Price/Qty<br>
690
- ✓ Symbol Validation
691
- </div>
692
- </div>
693
- </div>
694
-
695
- </div>
696
- </div>
697
-
698
- <script>
699
- // ── State ─────────────────────────────────────────────────────────────────────
700
- const API = '';
701
- let selectedSymbol = 'AAPL';
702
- let orderSide = 'BUY';
703
- let autoFeedInterval = null;
704
- let priceHistory = {}; // symbol -> [{ts, price}]
705
- let rightMode = 'positions';
706
-
707
- // Price chart
708
- const priceCtx = document.getElementById('price-canvas');
709
- let priceChart = null;
710
-
711
- // ── Init ──────────────────────────────────────────────────────────────────────
712
- window.addEventListener('DOMContentLoaded', () => {
713
- buildSymbolStrip();
714
- selectSymbol('AAPL');
715
- startPolling();
716
- feedStep(50); // warm up
717
- });
718
-
719
- function buildSymbolStrip() {
720
- const syms = ['AAPL','GOOG','MSFT','TSLA','NVDA'];
721
- const strip = document.getElementById('sym-strip');
722
- strip.innerHTML = syms.map(s =>
723
- `<button class="sym-btn ${s===selectedSymbol?'active':''}" onclick="selectSymbol('${s}')">${s}</button>`
724
- ).join('');
725
- }
726
-
727
- function selectSymbol(sym) {
728
- selectedSymbol = sym;
729
- document.getElementById('oe-symbol').value = sym;
730
- document.getElementById('book-symbol-tag').textContent = sym;
731
- document.getElementById('chart-symbol').textContent = sym;
732
- buildSymbolStrip();
733
- refreshBook();
734
- refreshChart();
735
- }
736
-
737
- // ── API calls ─────────────────────────────────────────────────────────────────
738
- async function api(method, path, body=null) {
739
- const opts = { method, headers: {'Content-Type':'application/json'} };
740
- if (body) opts.body = JSON.stringify(body);
741
- const r = await fetch(API + path, opts);
742
- return r.json();
743
- }
744
-
745
- // ── Polling ───────────────────────────────────────────────────────────────────
746
- function startPolling() {
747
- refreshStatus();
748
- refreshBook();
749
- refreshPositions();
750
- refreshTrades();
751
- setInterval(refreshStatus, 1500);
752
- setInterval(refreshBook, 500);
753
- setInterval(refreshPositions, 2000);
754
- setInterval(refreshTrades, 1000);
755
- }
756
-
757
- async function refreshStatus() {
758
- const d = await api('GET', '/api/status');
759
- if (d.error) return;
760
-
761
- setVal('h-orders', d.total_orders);
762
- setVal('h-trades', d.total_trades);
763
- setVal('h-rejects', d.total_rejects);
764
-
765
- const pnl = d.total_pnl || 0;
766
- const pnlEl = document.getElementById('h-pnl');
767
- pnlEl.textContent = '$' + pnl.toFixed(2);
768
- pnlEl.className = 'hstat-val ' + (pnl >= 0 ? 'green' : 'red');
769
-
770
- const p99 = d.order_p99_us || 0;
771
- document.getElementById('h-p99').textContent = p99 > 0 ? p99.toFixed(1)+'µs' : '—';
772
-
773
- // Latency bars (relative to 1000µs max)
774
- setLatBar('lat-o-p50', 'lat-o-p50-bar', d.order_p50_us, 500);
775
- setLatBar('lat-o-p99', 'lat-o-p99-bar', d.order_p99_us, 500);
776
- setLatBar('lat-t-p50', 'lat-t-p50-bar', d.tick_p50_us, 100);
777
- setLatBar('lat-t-p99', 'lat-t-p99-bar', d.tick_p99_us, 100);
778
-
779
- setVal('risk-checked', d.risk_checked);
780
- setVal('risk-rejected', d.risk_rejected);
781
- setVal('r-checked', d.risk_checked);
782
- setVal('r-rejected2', d.risk_rejected);
783
- setVal('r-approved', Math.max(0, d.risk_checked - d.risk_rejected));
784
-
785
- const fillRate = d.total_orders > 0
786
- ? ((d.total_trades / d.total_orders) * 100).toFixed(1)+'%'
787
- : '0%';
788
- document.getElementById('fill-rate').textContent = fillRate;
789
-
790
- setVal('feed-size', d.feed_size);
791
- setVal('feed-idx', d.feed_idx);
792
-
793
- const pnlEl2 = document.getElementById('stat-pnl');
794
- pnlEl2.textContent = '$' + pnl.toFixed(2);
795
- pnlEl2.className = 'stat-val ' + (pnl >= 0 ? 'green' : 'red');
796
-
797
- // Reject rate bar
798
- if (d.risk_checked > 0) {
799
- const rejPct = (d.risk_rejected / d.risk_checked * 100);
800
- setBarWidth('r-rej-bar', rejPct);
801
- document.getElementById('r-rej-pct').textContent = rejPct.toFixed(1)+'%';
802
- }
803
-
804
- // Connection indicator
805
- document.getElementById('conn-dot').style.background = 'var(--green)';
806
- }
807
-
808
- function setLatBar(valId, barId, val, maxVal) {
809
- const el = document.getElementById(valId);
810
- const bar = document.getElementById(barId);
811
- if (!el || !bar) return;
812
- if (val === undefined || val === null) return;
813
- el.textContent = val > 0 ? val.toFixed(2)+'µs' : '< 1µs';
814
- const pct = Math.min(100, (val / maxVal) * 100);
815
- bar.style.width = pct + '%';
816
- }
817
-
818
- function setBarWidth(id, pct) {
819
- const el = document.getElementById(id);
820
- if (el) el.style.width = Math.min(100, pct) + '%';
821
- }
822
-
823
- function setVal(id, val) {
824
- const el = document.getElementById(id);
825
- if (el) el.textContent = val;
826
- }
827
-
828
- // ── Order Book ────────────────────────────────────────────────────────────────
829
- async function refreshBook() {
830
- const d = await api('GET', `/api/book/${selectedSymbol}`);
831
- if (d.error || !d.asks) return;
832
- renderBook(d);
833
- }
834
-
835
- function renderBook(d) {
836
- const maxQty = Math.max(
837
- ...(d.bids||[]).map(b=>b.qty),
838
- ...(d.asks||[]).map(a=>a.qty), 1
839
- );
840
-
841
- // Asks: show in ascending order but display reversed (best ask at bottom)
842
- const asks = (d.asks||[]).slice(0,10);
843
- const asksEl = document.getElementById('book-asks');
844
- asksEl.innerHTML = [...asks].reverse().map(a => {
845
- const pct = (a.qty / maxQty * 100).toFixed(1);
846
- return `<div class="book-row ask-row">
847
- <div class="book-bar" style="width:${pct}%"></div>
848
- <span class="price">${a.price.toFixed(4)}</span>
849
- <span class="qty-val">${fmtNum(a.qty)}</span>
850
- <span class="orders-val">${a.orders}</span>
851
- </div>`;
852
- }).join('');
853
-
854
- const spreadEl = document.getElementById('spread-row');
855
- if (d.spread > 0) {
856
- spreadEl.textContent = `BID ${d.best_bid.toFixed(4)} ─── SPREAD ${d.spread.toFixed(4)} ─── ASK ${d.best_ask.toFixed(4)}`;
857
- }
858
-
859
- // Update chart price
860
- const mid = d.mid || d.best_bid;
861
- if (mid > 0) {
862
- document.getElementById('chart-price').textContent = '$' + mid.toFixed(2);
863
- }
864
-
865
- const bids = (d.bids||[]).slice(0,10);
866
- const bidsEl = document.getElementById('book-bids');
867
- bidsEl.innerHTML = bids.map(b => {
868
- const pct = (b.qty / maxQty * 100).toFixed(1);
869
- return `<div class="book-row bid-row">
870
- <div class="book-bar" style="width:${pct}%"></div>
871
- <span class="price">${b.price.toFixed(4)}</span>
872
- <span class="qty-val">${fmtNum(b.qty)}</span>
873
- <span class="orders-val">${b.orders}</span>
874
- </div>`;
875
- }).join('');
876
- }
877
-
878
- // ── Price Chart ───────────────────────────────────────────────────────────────
879
- const MAX_HISTORY = 300;
880
-
881
- async function refreshChart() {
882
- // Just use feed data from status — we'll seed from order book mid
883
- }
884
-
885
- function addPricePoint(sym, price, ts) {
886
- if (!priceHistory[sym]) priceHistory[sym] = [];
887
- priceHistory[sym].push({price, ts});
888
- if (priceHistory[sym].length > MAX_HISTORY)
889
- priceHistory[sym].shift();
890
- if (sym === selectedSymbol) drawPriceChart();
891
- }
892
-
893
- function drawPriceChart() {
894
- const canvas = document.getElementById('price-canvas');
895
- const ctx = canvas.getContext('2d');
896
- const W = canvas.parentElement.offsetWidth;
897
- const H = 290;
898
- canvas.width = W; canvas.height = H;
899
-
900
- ctx.fillStyle = '#0d1219';
901
- ctx.fillRect(0, 0, W, H);
902
-
903
- const data = priceHistory[selectedSymbol] || [];
904
- if (data.length < 2) {
905
- ctx.fillStyle = '#1e2d3d';
906
- ctx.font = '11px JetBrains Mono';
907
- ctx.textAlign = 'center';
908
- ctx.fillText('Feed ticks to populate chart', W/2, H/2);
909
- return;
910
- }
911
-
912
- const prices = data.map(d => d.price);
913
- const minP = Math.min(...prices);
914
- const maxP = Math.max(...prices);
915
- const range = maxP - minP || 1;
916
- const pad = { t:20, r:60, b:30, l:10 };
917
- const cW = W - pad.l - pad.r;
918
- const cH = H - pad.t - pad.b;
919
-
920
- const xOf = i => pad.l + (i / (data.length-1)) * cW;
921
- const yOf = p => pad.t + (1 - (p - minP) / range) * cH;
922
-
923
- // Grid lines
924
- ctx.strokeStyle = '#1a2230';
925
- ctx.lineWidth = 1;
926
- for (let i = 0; i <= 4; i++) {
927
- const y = pad.t + (i / 4) * cH;
928
- ctx.beginPath(); ctx.moveTo(pad.l, y); ctx.lineTo(W - pad.r, y); ctx.stroke();
929
- const price = maxP - (i / 4) * range;
930
- ctx.fillStyle = '#5a7a90'; ctx.font = '9px JetBrains Mono';
931
- ctx.textAlign = 'right';
932
- ctx.fillText('$' + price.toFixed(2), W - 5, y + 3);
933
- }
934
-
935
- // Gradient fill
936
- const grad = ctx.createLinearGradient(0, pad.t, 0, H - pad.b);
937
- const last = data[data.length - 1];
938
- const first = data[0];
939
- const isUp = last.price >= first.price;
940
- if (isUp) {
941
- grad.addColorStop(0, 'rgba(0,230,118,0.2)');
942
- grad.addColorStop(1, 'rgba(0,230,118,0.01)');
943
- } else {
944
- grad.addColorStop(0, 'rgba(255,23,68,0.2)');
945
- grad.addColorStop(1, 'rgba(255,23,68,0.01)');
946
- }
947
-
948
- ctx.beginPath();
949
- ctx.moveTo(xOf(0), yOf(data[0].price));
950
- for (let i = 1; i < data.length; i++) {
951
- const x1 = xOf(i-1), x2 = xOf(i);
952
- const y1 = yOf(data[i-1].price), y2 = yOf(data[i].price);
953
- const cpX = (x1 + x2) / 2;
954
- ctx.bezierCurveTo(cpX, y1, cpX, y2, x2, y2);
955
- }
956
- ctx.lineTo(xOf(data.length-1), H - pad.b);
957
- ctx.lineTo(xOf(0), H - pad.b);
958
- ctx.closePath();
959
- ctx.fillStyle = grad;
960
- ctx.fill();
961
-
962
- // Line
963
- ctx.beginPath();
964
- ctx.moveTo(xOf(0), yOf(data[0].price));
965
- for (let i = 1; i < data.length; i++) {
966
- const x1 = xOf(i-1), x2 = xOf(i);
967
- const y1 = yOf(data[i-1].price), y2 = yOf(data[i].price);
968
- const cpX = (x1 + x2) / 2;
969
- ctx.bezierCurveTo(cpX, y1, cpX, y2, x2, y2);
970
- }
971
- ctx.strokeStyle = isUp ? '#00e676' : '#ff1744';
972
- ctx.lineWidth = 2;
973
- ctx.shadowColor = isUp ? '#00e676' : '#ff1744';
974
- ctx.shadowBlur = 6;
975
- ctx.stroke();
976
- ctx.shadowBlur = 0;
977
-
978
- // Last price dot
979
- const lx = xOf(data.length - 1);
980
- const ly = yOf(last.price);
981
- ctx.beginPath(); ctx.arc(lx, ly, 4, 0, Math.PI*2);
982
- ctx.fillStyle = isUp ? '#00e676' : '#ff1744';
983
- ctx.fill();
984
-
985
- // OHLC mini labels
986
- ctx.fillStyle = '#5a7a90'; ctx.font = '9px JetBrains Mono';
987
- ctx.textAlign = 'left';
988
- ctx.fillText(`↑ ${maxP.toFixed(2)}`, pad.l + 5, pad.t + 12);
989
- ctx.fillText(`↓ ${minP.toFixed(2)}`, pad.l + 5, H - pad.b - 5);
990
- }
991
-
992
- // ── Feed ──────────────────────────────────────────────────────────────────────
993
- async function feedStep(n) {
994
- const ticks = await api('POST', '/api/feed/step', {n});
995
- if (!Array.isArray(ticks)) return;
996
- ticks.forEach(t => {
997
- addPricePoint(t.symbol, t.last, t.ts);
998
- if (t.symbol === selectedSymbol) {
999
- document.getElementById('chart-price').textContent = '$' + t.last.toFixed(2);
1000
- // Pre-fill order price
1001
- const priceInput = document.getElementById('oe-price');
1002
- if (!priceInput.value || priceInput.value == '0') {
1003
- priceInput.value = t.bid.toFixed(2);
1004
- }
1005
- }
1006
- });
1007
- refreshBook();
1008
- }
1009
-
1010
- let autoRunning = false;
1011
- function autoFeed() {
1012
- const btn = document.getElementById('auto-btn');
1013
- if (autoRunning) {
1014
- clearInterval(autoFeedInterval);
1015
- autoRunning = false;
1016
- btn.textContent = 'AUTO';
1017
- btn.classList.remove('btn-sell');
1018
- btn.classList.add('btn-accent');
1019
- } else {
1020
- autoRunning = true;
1021
- btn.textContent = '■ STOP';
1022
- btn.classList.remove('btn-accent');
1023
- btn.classList.add('btn-sell');
1024
- autoFeedInterval = setInterval(() => feedStep(5), 200);
1025
- }
1026
- }
1027
-
1028
- // ── Order Entry ───────────────────────────────────────────────────────────────
1029
- function setOrderSide(side) {
1030
- orderSide = side;
1031
- const buyTab = document.getElementById('tab-buy');
1032
- const sellTab = document.getElementById('tab-sell');
1033
- const btn = document.getElementById('submit-btn');
1034
- if (side === 'BUY') {
1035
- buyTab.className = 'tab active-buy';
1036
- sellTab.className = 'tab';
1037
- btn.className = 'btn btn-buy';
1038
- btn.textContent = '▲ PLACE BUY ORDER';
1039
- } else {
1040
- buyTab.className = 'tab';
1041
- sellTab.className = 'tab active-sell';
1042
- btn.className = 'btn btn-sell';
1043
- btn.textContent = '▼ PLACE SELL ORDER';
1044
- }
1045
- }
1046
-
1047
- function updateOrderForm() {
1048
- const type = document.getElementById('oe-type').value;
1049
- const priceGroup = document.getElementById('oe-price-group');
1050
- priceGroup.style.display = (type === 'MARKET') ? 'none' : 'block';
1051
- }
1052
-
1053
- async function submitOrder() {
1054
- const sym = document.getElementById('oe-symbol').value;
1055
- const type = document.getElementById('oe-type').value;
1056
- const price = parseFloat(document.getElementById('oe-price').value) || 0;
1057
- const qty = parseInt(document.getElementById('oe-qty').value) || 100;
1058
- const cid = document.getElementById('oe-cid').value;
1059
-
1060
- const body = {
1061
- symbol: sym, side: orderSide, type,
1062
- price, qty, client_id: cid,
1063
- market_price: price
1064
- };
1065
-
1066
- const r = await api('POST', '/api/order', body);
1067
- if (!r) return;
1068
-
1069
- if (r.approved) {
1070
- showToast(`✓ ${orderSide} ${qty} ${sym} @ ${type === 'MARKET' ? 'MKT' : '$'+price} — ${r.order.status}`, 'green');
1071
- if (r.trades && r.trades.length > 0) {
1072
- showToast(`⚡ ${r.trades.length} trade(s) executed!`, 'green');
1073
- }
1074
- } else {
1075
- showToast(`✗ REJECTED: ${r.reject_reason}`, 'red');
1076
- document.body.classList.add('flash-red');
1077
- setTimeout(() => document.body.classList.remove('flash-red'), 500);
1078
- }
1079
-
1080
- refreshBook();
1081
- refreshPositions();
1082
- refreshTrades();
1083
- }
1084
-
1085
- // ── Positions ─────────────────────────────────────────────────────────────────
1086
- async function refreshPositions() {
1087
- const pos = await api('GET', '/api/positions');
1088
- if (!Array.isArray(pos)) return;
1089
-
1090
- const tbody = document.getElementById('pos-body');
1091
- tbody.innerHTML = pos.filter(p => p.net_qty !== 0).map(p => {
1092
- const rPnl = p.realized_pnl || 0;
1093
- const uPnl = p.unrealized_pnl || 0;
1094
- const posDir = p.net_qty > 0 ? '+' : '';
1095
- return `<tr>
1096
- <td style="color:var(--cyan);font-weight:700">${p.symbol}</td>
1097
- <td style="color:${p.net_qty>0?'var(--green)':'var(--red)'}">${posDir}${fmtNum(p.net_qty)}</td>
1098
- <td>$${p.avg_price.toFixed(2)}</td>
1099
- <td style="color:${rPnl>=0?'var(--green)':'var(--red)'}">${rPnl>=0?'+':''}$${rPnl.toFixed(2)}</td>
1100
- <td style="color:${uPnl>=0?'var(--green)':'var(--red)'}">${uPnl>=0?'+':''}$${uPnl.toFixed(2)}</td>
1101
- </tr>`;
1102
- }).join('') || '<tr><td colspan="5" style="text-align:center;color:var(--dim);padding:10px">No open positions</td></tr>';
1103
-
1104
- // Order log
1105
- const orders = await api('GET', '/api/orders');
1106
- if (!Array.isArray(orders)) return;
1107
- const logEl = document.getElementById('order-log');
1108
- logEl.innerHTML = orders.slice(-20).reverse().map(o => {
1109
- const isRej = o.status === 'REJECTED';
1110
- return `<div class="trade-entry" style="${isRej?'border-left:2px solid var(--red)':''}">
1111
- <span class="trade-side ${o.side==='BUY'?'buy-badge':'sell-badge'}">${o.side}</span>
1112
- <span style="color:var(--cyan)">${o.symbol}</span>
1113
- <span style="color:var(--dim)">${o.type}</span>
1114
- <span>$${(o.price||0).toFixed(2)}</span>
1115
- <span style="color:var(--dim)">×${fmtNum(o.qty)}</span>
1116
- <span style="color:${isRej?'var(--red)':o.status==='FILLED'?'var(--green)':'var(--dim)'}">
1117
- ${o.status}
1118
- </span>
1119
- </div>`;
1120
- }).join('');
1121
- }
1122
-
1123
- // ── Trades ────────────────────────────────────────────────────────────────────
1124
- async function refreshTrades() {
1125
- const trades = await api('GET', '/api/trades');
1126
- if (!Array.isArray(trades)) return;
1127
-
1128
- document.getElementById('trade-count').textContent = `${trades.length} trades`;
1129
- document.getElementById('h-trades').textContent = trades.length;
1130
-
1131
- const logEl = document.getElementById('trade-log');
1132
- logEl.innerHTML = trades.slice().reverse().slice(0, 100).map(t => {
1133
- const time = new Date(t.timestamp / 1e6).toISOString().substr(11,12);
1134
- return `<div class="trade-entry" style="padding:5px 12px">
1135
- <span style="color:var(--dim);font-size:9px">${time}</span>
1136
- <span style="color:var(--cyan);font-weight:700">${t.symbol}</span>
1137
- <span style="color:var(--text)">$${t.price.toFixed(4)}</span>
1138
- <span style="color:var(--dim)">×</span>
1139
- <span style="font-weight:600">${fmtNum(t.qty)}</span>
1140
- <span style="color:var(--dim);font-size:9px">#${t.trade_id}</span>
1141
- </div>`;
1142
- }).join('');
1143
- }
1144
-
1145
- // ── Backtest ──────────────────────────────────────────────────────────────────
1146
- async function runBacktest() {
1147
- const btn = document.getElementById('bt-btn');
1148
- btn.textContent = '⟳ RUNNING...'; btn.disabled = true;
1149
-
1150
- try {
1151
- const r = await api('POST', '/api/backtest', {
1152
- strategy: document.getElementById('bt-strategy').value,
1153
- symbol: document.getElementById('bt-symbol').value,
1154
- ticks: parseInt(document.getElementById('bt-ticks').value),
1155
- start_price: parseFloat(document.getElementById('bt-price').value)
1156
- });
1157
-
1158
- document.getElementById('bt-result').style.display = 'block';
1159
- setVal('bt-r-strategy', r.strategy);
1160
- setVal('bt-r-ticks', fmtNum(r.ticks_processed));
1161
- setVal('bt-r-orders', fmtNum(r.orders_submitted));
1162
- setVal('bt-r-fills', fmtNum(r.orders_filled));
1163
- setVal('bt-r-rej', fmtNum(r.orders_rejected));
1164
- setVal('bt-r-trades', fmtNum(r.trades_count));
1165
-
1166
- const pnlEl = document.getElementById('bt-r-pnl');
1167
- pnlEl.textContent = (r.total_pnl >= 0 ? '+' : '') + '$' + r.total_pnl.toFixed(2);
1168
- pnlEl.className = 'stat-val ' + (r.total_pnl >= 0 ? 'green' : 'red');
1169
-
1170
- document.getElementById('bt-r-dd').textContent = '-$' + r.max_drawdown.toFixed(2);
1171
- document.getElementById('bt-r-sharpe').textContent = r.sharpe.toFixed(3);
1172
- document.getElementById('bt-r-p50').textContent = r.order_p50_us.toFixed(2) + 'µs';
1173
- document.getElementById('bt-r-p99').textContent = r.order_p99_us.toFixed(2) + 'µs';
1174
- document.getElementById('bt-r-thru').textContent = fmtNum(Math.round(r.throughput_eps)) + ' ev/s';
1175
-
1176
- drawEquityCurve(r.equity_curve || []);
1177
- } finally {
1178
- btn.textContent = '▶ RUN BACKTEST'; btn.disabled = false;
1179
- }
1180
- }
1181
-
1182
- function drawEquityCurve(curve) {
1183
- const canvas = document.getElementById('equity-canvas');
1184
- const ctx = canvas.getContext('2d');
1185
- const W = canvas.parentElement.offsetWidth - 2;
1186
- const H = 100;
1187
- canvas.width = W; canvas.height = H;
1188
-
1189
- ctx.fillStyle = '#0d1219';
1190
- ctx.fillRect(0,0,W,H);
1191
-
1192
- if (curve.length < 2) { return; }
1193
-
1194
- const minV = Math.min(...curve);
1195
- const maxV = Math.max(...curve);
1196
- const range = maxV - minV || 1;
1197
- const pad = 15;
1198
-
1199
- const xOf = i => pad + (i / (curve.length-1)) * (W - 2*pad);
1200
- const yOf = v => pad + (1 - (v - minV)/range) * (H - 2*pad);
1201
-
1202
- // Zero line
1203
- const zy = yOf(0);
1204
- if (zy > pad && zy < H-pad) {
1205
- ctx.strokeStyle = '#1e2d3d';
1206
- ctx.setLineDash([3,3]);
1207
- ctx.beginPath(); ctx.moveTo(pad, zy); ctx.lineTo(W-pad, zy); ctx.stroke();
1208
- ctx.setLineDash([]);
1209
- }
1210
-
1211
- ctx.beginPath();
1212
- ctx.moveTo(xOf(0), yOf(curve[0]));
1213
- for (let i = 1; i < curve.length; i++) ctx.lineTo(xOf(i), yOf(curve[i]));
1214
- ctx.strokeStyle = curve[curve.length-1] >= 0 ? '#00e676' : '#ff1744';
1215
- ctx.lineWidth = 1.5;
1216
- ctx.stroke();
1217
-
1218
- ctx.fillStyle = '#5a7a90'; ctx.font = '8px JetBrains Mono';
1219
- ctx.textAlign = 'right';
1220
- ctx.fillText('$'+maxV.toFixed(1), W-2, pad+8);
1221
- ctx.fillText('$'+minV.toFixed(1), W-2, H-pad);
1222
- }
1223
-
1224
- // ── Risk Limits ───────────────────────────────────────────────────────────────
1225
- async function setRiskLimits() {
1226
- const r = await api('POST', '/api/risk/limits', {
1227
- max_order_qty: parseInt(document.getElementById('r-max-qty').value),
1228
- max_position: parseInt(document.getElementById('r-max-pos').value),
1229
- max_notional_usd: parseFloat(document.getElementById('r-max-notional').value),
1230
- max_orders_per_sec: parseInt(document.getElementById('r-max-rate').value),
1231
- });
1232
- showToast(r.ok ? '✓ Risk limits updated' : '✗ Failed', r.ok ? 'green' : 'red');
1233
- }
1234
-
1235
- // ── Reset ─────────────────────────────────────────────────────────────────────
1236
- async function resetSim() {
1237
- await api('POST', '/api/reset');
1238
- priceHistory = {};
1239
- document.getElementById('trade-log').innerHTML = '';
1240
- showToast('↺ Simulator reset', 'cyan');
1241
- }
1242
-
1243
- // ── Right mode ────────────────────────────────────────────────────────────────
1244
- function setRightMode(mode) {
1245
- rightMode = mode;
1246
- ['positions','backtest','risk'].forEach(m => {
1247
- document.getElementById('view-'+m).style.display = (m === mode) ? 'flex' : 'none';
1248
- });
1249
- document.querySelectorAll('.mode-tab').forEach((t,i) => {
1250
- const modes = ['positions','backtest','risk'];
1251
- t.className = 'mode-tab' + (modes[i]===mode ? ' active' : '');
1252
- });
1253
- }
1254
-
1255
- // ── Helpers ───────────────────────────────────────────────────────────────────
1256
- function fmtNum(n) {
1257
- if (n >= 1e6) return (n/1e6).toFixed(1)+'M';
1258
- if (n >= 1e3) return (n/1e3).toFixed(1)+'K';
1259
- return n.toString();
1260
- }
1261
-
1262
- function showToast(msg, color='cyan') {
1263
- const t = document.createElement('div');
1264
- t.className = 'toast';
1265
- t.style.borderColor = `var(--${color})`;
1266
- t.style.color = `var(--${color})`;
1267
- t.textContent = msg;
1268
- document.body.appendChild(t);
1269
- setTimeout(() => t.remove(), 3000);
1270
- }
1271
-
1272
- // Resize handler for chart
1273
- window.addEventListener('resize', () => {
1274
- if (priceHistory[selectedSymbol]) drawPriceChart();
1275
- });
1276
- </script>
1277
- </body>
1278
- </html>