Alvin3y1 commited on
Commit
c2b455a
·
verified ·
1 Parent(s): 6eaed7c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +144 -78
app.py CHANGED
@@ -14,10 +14,11 @@ SYMBOL_KRAKEN = "BTC/USD"
14
  PORT = 7860
15
  HISTORY_LENGTH = 300
16
  BROADCAST_RATE = 0.1
17
- DECAY_LAMBDA = 100.0
18
- IMPACT_SENSITIVITY = 0.5
19
 
20
- # Wall Detection Parameters
 
 
 
21
  Z_SCORE_THRESHOLD = 3.0
22
  WALL_LOOKBACK = 200
23
 
@@ -36,8 +37,33 @@ market_state = {
36
 
37
  connected_clients = set()
38
 
39
- # --- BACKEND LOGIC ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  def detect_anomalies(orders, scan_depth):
 
 
 
41
  if len(orders) < 10: return []
42
  relevant_orders = orders[:scan_depth]
43
  volumes = [q for p, q in relevant_orders]
@@ -57,82 +83,151 @@ def detect_anomalies(orders, scan_depth):
57
  if z_score > Z_SCORE_THRESHOLD:
58
  walls.append({"price": price, "vol": qty, "z_score": z_score})
59
 
 
60
  walls.sort(key=lambda x: x['z_score'], reverse=True)
61
  return walls[:3]
62
 
63
- def analyze_structure(diff_x, diff_y, current_mid):
64
- if not diff_y or len(diff_y) < 5: return None
65
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  weighted_imbalance = 0.0
67
- prev_vol = 0.0
68
 
69
  for i in range(len(diff_x)):
70
  dist = diff_x[i]
71
- cum_vol = diff_y[i]
72
- marginal_vol = cum_vol - prev_vol
73
- prev_vol = cum_vol
74
  weight = math.exp(-dist / DECAY_LAMBDA)
75
- weighted_imbalance += marginal_vol * weight
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
- if weighted_imbalance != 0:
78
- impact = math.sqrt(abs(weighted_imbalance)) * IMPACT_SENSITIVITY
79
- if weighted_imbalance < 0: impact = -impact
80
- else:
81
- impact = 0.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
- projected_price = current_mid + impact
84
- return {"projected": projected_price, "net_score": weighted_imbalance}
 
 
 
85
 
86
  def process_market_data():
87
  if not market_state['ready']: return {"error": "Initializing..."}
88
 
89
  mid = market_state['current_mid']
 
 
90
  sorted_bids = sorted(market_state['bids'].items(), key=lambda x: -x[0])
91
  sorted_asks = sorted(market_state['asks'].items(), key=lambda x: x[0])
 
 
92
 
93
- raw_bids = sorted_bids[:300]
94
- raw_asks = sorted_asks[:300]
95
 
 
96
  bid_walls = detect_anomalies(sorted_bids, WALL_LOOKBACK)
97
  ask_walls = detect_anomalies(sorted_asks, WALL_LOOKBACK)
98
 
 
99
  d_b_x, d_b_y, cum = [], [], 0
100
- for p, q in raw_bids:
101
  d = mid - p
102
  if d >= 0:
103
  cum += q
104
  d_b_x.append(d); d_b_y.append(cum)
105
 
106
  d_a_x, d_a_y, cum = [], [], 0
107
- for p, q in raw_asks:
108
  d = p - mid
109
  if d >= 0:
110
  cum += q
111
  d_a_x.append(d); d_a_y.append(cum)
112
 
113
- diff_x, diff_y = [], []
 
114
  chart_bids, chart_asks = [], []
115
 
116
  if d_b_x and d_a_x:
117
  max_dist = min(d_b_x[-1], d_a_x[-1])
 
118
  step_size = max_dist / 100
119
  steps = [i * step_size for i in range(1, 101)]
120
 
121
  for s in steps:
 
122
  idx_b = bisect.bisect_right(d_b_x, s)
123
  vol_b = d_b_y[idx_b-1] if idx_b > 0 else 0
 
 
124
  idx_a = bisect.bisect_right(d_a_x, s)
125
  vol_a = d_a_y[idx_a-1] if idx_a > 0 else 0
126
 
127
  diff_x.append(s)
128
- diff_y.append(vol_b - vol_a)
 
129
  chart_bids.append(vol_b)
130
  chart_asks.append(vol_a)
131
 
132
- analysis = analyze_structure(diff_x, diff_y, mid)
 
 
 
 
 
 
 
 
133
 
134
  now = time.time()
135
  if analysis:
 
136
  if not market_state['pred_history'] or (now - market_state['pred_history'][-1]['t'] > 0.5):
137
  market_state['pred_history'].append({'t': now, 'p': analysis['projected']})
138
  if len(market_state['pred_history']) > HISTORY_LENGTH:
@@ -143,14 +238,14 @@ def process_market_data():
143
  "history": market_state['history'],
144
  "pred_history": market_state['pred_history'],
145
  "depth_x": diff_x,
146
- "depth_net": diff_y,
147
  "depth_bids": chart_bids,
148
  "depth_asks": chart_asks,
149
  "analysis": analysis,
150
  "walls": {"bids": bid_walls, "asks": ask_walls}
151
  }
152
 
153
- # --- FRONTEND (HIGH CONTRAST, NO OVERLAP) ---
154
  HTML_PAGE = f"""
155
  <!DOCTYPE html>
156
  <html lang="en">
@@ -179,10 +274,9 @@ HTML_PAGE = f"""
179
  height: 100vh; width: 100vw;
180
  }}
181
 
182
- /* THE GRID */
183
  .layout {{
184
  display: grid;
185
- grid-template-rows: 34px 1fr 1fr; /* Explicit Header Height */
186
  grid-template-columns: 3fr 1fr;
187
  gap: 1px;
188
  background-color: var(--border);
@@ -192,7 +286,6 @@ HTML_PAGE = f"""
192
 
193
  .panel {{ background: var(--bg-panel); display: flex; flex-direction: column; overflow: hidden; }}
194
 
195
- /* STATUS BAR HEADER */
196
  .status-bar {{
197
  grid-column: 1 / 3;
198
  grid-row: 1 / 2;
@@ -212,10 +305,8 @@ HTML_PAGE = f"""
212
  .live-dot {{ width: 8px; height: 8px; background-color: var(--green); border-radius: 50%; display: inline-block; box-shadow: 0 0 8px var(--green); }}
213
  .ticker-val {{ font-weight: 700; color: #fff; font-size: 13px; }}
214
 
215
- /* MAIN CHART AREA */
216
  #p-chart {{ grid-column: 1 / 2; grid-row: 2 / 3; }}
217
 
218
- /* DEPTH AREA */
219
  #p-depth {{
220
  grid-column: 1 / 2; grid-row: 3 / 4;
221
  display: grid;
@@ -225,7 +316,6 @@ HTML_PAGE = f"""
225
  }}
226
  .depth-sub {{ background: var(--bg-panel); display: flex; flex-direction: column; position: relative; }}
227
 
228
- /* SIDEBAR */
229
  #p-sidebar {{
230
  grid-column: 2 / 3;
231
  grid-row: 2 / 4;
@@ -236,7 +326,6 @@ HTML_PAGE = f"""
236
  border-left: 1px solid var(--border);
237
  }}
238
 
239
- /* CHART INTERNAL HEADER (PREVENTS OVERLAP) */
240
  .chart-header {{
241
  height: 24px;
242
  min-height: 24px;
@@ -246,12 +335,11 @@ HTML_PAGE = f"""
246
  font-size: 10px;
247
  font-weight: 700;
248
  color: var(--text-dim);
249
- background: #050505; /* Slightly darker to separate */
250
  border-bottom: 1px solid #151515;
251
  letter-spacing: 0.5px;
252
  }}
253
 
254
- /* COMPONENT STYLES */
255
  .data-group {{ display: flex; flex-direction: column; gap: 4px; }}
256
  .label {{ font-size: 10px; color: var(--text-dim); font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }}
257
  .value {{ font-family: 'JetBrains Mono', monospace; font-size: 20px; font-weight: 700; color: #fff; }}
@@ -259,13 +347,10 @@ HTML_PAGE = f"""
259
  .value-sub {{ font-family: 'JetBrains Mono', monospace; font-size: 11px; margin-top: 2px; color: #666; }}
260
 
261
  .divider {{ height: 1px; background: var(--border); width: 100%; }}
262
-
263
- /* COLORS */
264
  .c-green {{ color: var(--green); }}
265
  .c-red {{ color: var(--red); }}
266
  .c-dim {{ color: var(--text-dim); }}
267
 
268
- /* LISTS */
269
  .list-container {{ display: flex; flex-direction: column; gap: 8px; overflow-y: hidden; }}
270
  .list-item {{
271
  display: flex; justify-content: space-between;
@@ -276,13 +361,11 @@ HTML_PAGE = f"""
276
  }}
277
  .list-item span:first-child {{ color: #e0e0e0; }}
278
  .list-item:last-child {{ border: none; }}
279
-
280
  </style>
281
  </head>
282
  <body>
283
 
284
  <div class="layout">
285
- <!-- STATUS BAR -->
286
  <div class="status-bar">
287
  <div class="status-left">
288
  <span class="live-dot"></span>
@@ -292,30 +375,26 @@ HTML_PAGE = f"""
292
  <div class="status-right" id="clock">00:00:00 UTC</div>
293
  </div>
294
 
295
- <!-- PRICE CHART -->
296
  <div id="p-chart" class="panel">
297
- <div class="chart-header">PRICE ACTION // ANOMALIES</div>
298
  <div id="tv-price" style="flex: 1; width: 100%;"></div>
299
  </div>
300
 
301
- <!-- DEPTH CHARTS -->
302
  <div id="p-depth">
303
  <div class="depth-sub">
304
- <div class="chart-header">LIQUIDITY DENSITY</div>
305
  <div id="tv-raw" style="flex: 1; width: 100%;"></div>
306
  </div>
307
  <div class="depth-sub">
308
- <div class="chart-header">NET IMBALANCE</div>
309
  <div id="tv-net" style="flex: 1; width: 100%;"></div>
310
  </div>
311
  </div>
312
 
313
- <!-- SIDEBAR -->
314
  <div id="p-sidebar" class="panel">
315
 
316
- <!-- 1. PREDICTED IMPACT -->
317
  <div class="data-group">
318
- <span class="label">Projected Impact (5s)</span>
319
  <div style="display:flex; align-items: baseline; gap: 10px;">
320
  <span id="proj-pct" class="value value-lg">--%</span>
321
  <span id="proj-val" class="value-sub">---</span>
@@ -324,31 +403,29 @@ HTML_PAGE = f"""
324
 
325
  <div class="divider"></div>
326
 
327
- <!-- 2. IMBALANCE SCORE -->
328
  <div class="data-group">
329
- <span class="label">Order Flow Score</span>
330
  <span id="score-val" class="value">0.00</span>
 
331
  </div>
332
 
333
  <div class="divider"></div>
334
 
335
- <!-- 3. WALLS -->
336
  <div class="data-group" style="flex: 1;">
337
- <span class="label" style="margin-bottom: 10px;">Detected Walls (Z > 3.0)</span>
338
  <div id="wall-list" class="list-container">
339
- <span class="c-dim" style="font-size: 11px;">Initializing scan...</span>
340
  </div>
341
  </div>
342
 
343
  <div style="margin-top: auto;">
344
- <span class="label">System Latency</span>
345
- <div class="value-sub c-green">Connected</div>
346
  </div>
347
  </div>
348
  </div>
349
 
350
  <script>
351
- // UPDATE CLOCK
352
  setInterval(() => {{
353
  const now = new Date();
354
  document.getElementById('clock').innerText = now.toISOString().split('T')[1].split('.')[0] + ' UTC';
@@ -363,7 +440,6 @@ HTML_PAGE = f"""
363
  wallList: document.getElementById('wall-list')
364
  }};
365
 
366
- // HIGH CONTRAST CHART CONFIG
367
  const chartOpts = {{
368
  layout: {{ background: {{ type: 'solid', color: '#0a0a0a' }}, textColor: '#888', fontFamily: 'JetBrains Mono' }},
369
  grid: {{ vertLines: {{ color: '#151515' }}, horzLines: {{ color: '#151515' }} }},
@@ -372,12 +448,10 @@ HTML_PAGE = f"""
372
  crosshair: {{ mode: 1, vertLine: {{ color: '#444', labelBackgroundColor: '#444' }}, horzLine: {{ color: '#444', labelBackgroundColor: '#444' }} }}
373
  }};
374
 
375
- // 1. PRICE
376
  const priceChart = LightweightCharts.createChart(document.getElementById('tv-price'), chartOpts);
377
  const priceSeries = priceChart.addLineSeries({{ color: '#FFFFFF', lineWidth: 1, title: 'Price' }});
378
  const predSeries = priceChart.addLineSeries({{ color: '#2979ff', lineWidth: 1, lineStyle: 2, title: 'Forecast' }});
379
 
380
- // 2. RAW
381
  const rawChart = LightweightCharts.createChart(document.getElementById('tv-raw'), {{
382
  ...chartOpts,
383
  localization: {{ timeFormatter: t => '$' + t.toFixed(2) }}
@@ -385,7 +459,6 @@ HTML_PAGE = f"""
385
  const bidSeries = rawChart.addAreaSeries({{ lineColor: '#00ff9d', topColor: 'rgba(0, 255, 157, 0.15)', bottomColor: 'rgba(0,0,0,0)', lineWidth: 1 }});
386
  const askSeries = rawChart.addAreaSeries({{ lineColor: '#ff3b3b', topColor: 'rgba(255, 59, 59, 0.15)', bottomColor: 'rgba(0,0,0,0)', lineWidth: 1 }});
387
 
388
- // 3. NET
389
  const netChart = LightweightCharts.createChart(document.getElementById('tv-net'), {{
390
  ...chartOpts,
391
  localization: {{ timeFormatter: t => '$' + t.toFixed(2) }}
@@ -394,7 +467,6 @@ HTML_PAGE = f"""
394
 
395
  let activeLines = [];
396
 
397
- // RESIZE HANDLER
398
  new ResizeObserver(entries => {{
399
  for(let entry of entries) {{
400
  const {{width, height}} = entry.contentRect;
@@ -402,9 +474,8 @@ HTML_PAGE = f"""
402
  if(entry.target.id === 'tv-raw') rawChart.applyOptions({{width, height}});
403
  if(entry.target.id === 'tv-net') netChart.applyOptions({{width, height}});
404
  }}
405
- }}).observe(document.body); // Observer attached to body to catch layout shifts, logic handles IDs
406
 
407
- // Specific element observers
408
  ['tv-price', 'tv-raw', 'tv-net'].forEach(id => {{
409
  new ResizeObserver(e => {{
410
  if(id === 'tv-price') priceChart.applyOptions({{ width: e[0].contentRect.width, height: e[0].contentRect.height }});
@@ -420,7 +491,6 @@ HTML_PAGE = f"""
420
  const data = JSON.parse(e.data);
421
  if (data.error) return;
422
 
423
- // HISTORY & PRICE
424
  if (data.history.length) {{
425
  const hist = data.history.map(d => ({{ time: Math.floor(d.t), value: d.p }}));
426
  const cleanHist = [...new Map(hist.map(i => [i.time, i])).values()];
@@ -429,17 +499,16 @@ HTML_PAGE = f"""
429
  const lastP = cleanHist[cleanHist.length-1].value;
430
  dom.ticker.innerText = lastP.toLocaleString('en-US', {{ minimumFractionDigits: 2 }});
431
 
432
- // ANALYSIS
433
  if (data.analysis) {{
434
  const proj = data.analysis.projected;
435
- const score = data.analysis.net_score;
 
436
 
437
  predSeries.setData([
438
  cleanHist[cleanHist.length-1],
439
  {{ time: cleanHist[cleanHist.length-1].time + 60, value: proj }}
440
  ]);
441
 
442
- // PCT CALC
443
  const pct = ((proj - lastP) / lastP) * 100;
444
  const sign = pct >= 0 ? "+" : "";
445
 
@@ -448,12 +517,11 @@ HTML_PAGE = f"""
448
 
449
  dom.projVal.innerText = proj.toLocaleString('en-US', {{ minimumFractionDigits: 2 }});
450
 
451
- dom.score.innerText = score.toFixed(2);
452
- dom.score.style.color = score > 0 ? "var(--green)" : (score < 0 ? "var(--red)" : "var(--text-main)");
453
  }}
454
  }}
455
 
456
- // WALLS
457
  if (data.walls) {{
458
  activeLines.forEach(l => priceSeries.removePriceLine(l));
459
  activeLines = [];
@@ -471,10 +539,9 @@ HTML_PAGE = f"""
471
  data.walls.asks.forEach(w => addWall(w, 'ASK'));
472
  data.walls.bids.forEach(w => addWall(w, 'BID'));
473
 
474
- dom.wallList.innerHTML = html || '<span class="c-dim" style="font-size:11px">No significant walls.</span>';
475
  }}
476
 
477
- // DEPTH
478
  if (data.depth_x.length) {{
479
  const bids = [], asks = [], nets = [];
480
  for(let i=0; i<data.depth_x.length; i++) {{
@@ -498,7 +565,7 @@ HTML_PAGE = f"""
498
  </html>
499
  """
500
 
501
- # --- SERVER BOILERPLATE ---
502
  async def kraken_worker():
503
  global market_state
504
  while True:
@@ -559,8 +626,7 @@ async def websocket_handler(request):
559
  await ws.prepare(request)
560
  connected_clients.add(ws)
561
  try:
562
- async for msg in ws:
563
- pass
564
  finally:
565
  connected_clients.remove(ws)
566
  return ws
@@ -588,7 +654,7 @@ async def main():
588
  await runner.setup()
589
  site = web.TCPSite(runner, '0.0.0.0', PORT)
590
  await site.start()
591
- print(f"🚀 AI Liquidity Dashboard: http://localhost:{PORT}")
592
  await asyncio.Event().wait()
593
 
594
  if __name__ == "__main__":
 
14
  PORT = 7860
15
  HISTORY_LENGTH = 300
16
  BROADCAST_RATE = 0.1
 
 
17
 
18
+ # Mathematical Constants
19
+ DECAY_LAMBDA = 50.0 # Decay factor for liquidity weighting (closer orders matter more)
20
+ IMPACT_SENSITIVITY = 2.0 # Multiplier for Micro-Price deviation
21
+ WALL_DAMPENING = 0.8 # How much a Z-Score > 3 wall reduces momentum (0.0-1.0)
22
  Z_SCORE_THRESHOLD = 3.0
23
  WALL_LOOKBACK = 200
24
 
 
37
 
38
  connected_clients = set()
39
 
40
+ # --- QUANTITATIVE METHODS ---
41
+
42
+ def calculate_ols_slope(x_values, y_values):
43
+ """
44
+ Calculates the slope (m) of the liquidity density using Ordinary Least Squares (OLS).
45
+ y = mx + c
46
+ Steep slope = High Liquidity Density (Hard to move price).
47
+ Flat slope = Low Liquidity Density (Price slips easily).
48
+ """
49
+ n = len(x_values)
50
+ if n < 2: return 0.0
51
+
52
+ sum_x = sum(x_values)
53
+ sum_y = sum(y_values)
54
+ sum_xy = sum(x*y for x, y in zip(x_values, y_values))
55
+ sum_xx = sum(x*x for x in x_values)
56
+
57
+ denominator = (n * sum_xx - sum_x * sum_x)
58
+ if denominator == 0: return 0.0
59
+
60
+ slope = (n * sum_xy - sum_x * sum_y) / denominator
61
+ return slope
62
+
63
  def detect_anomalies(orders, scan_depth):
64
+ """
65
+ Standard Z-Score Outlier Detection.
66
+ """
67
  if len(orders) < 10: return []
68
  relevant_orders = orders[:scan_depth]
69
  volumes = [q for p, q in relevant_orders]
 
83
  if z_score > Z_SCORE_THRESHOLD:
84
  walls.append({"price": price, "vol": qty, "z_score": z_score})
85
 
86
+ # Sort by Z-Score (Strongest first)
87
  walls.sort(key=lambda x: x['z_score'], reverse=True)
88
  return walls[:3]
89
 
90
+ def calculate_micro_price_structure(diff_x, diff_y_raw, current_mid, best_bid, best_ask, walls):
91
+ """
92
+ Advanced Prediction Engine using:
93
+ 1. Weighted Imbalance (Exponential Decay)
94
+ 2. Liquidity Slope Elasticity
95
+ 3. Wall Friction Dampening
96
+ """
97
+ if not diff_x or len(diff_x) < 5: return None
98
+
99
+ # 1. Calculate Weighted Volume Imbalance (VOI)
100
+ # Using exponential decay to value liquidity near the spread higher than deep liquidity
101
+ sum_weighted_bid = 0.0
102
+ sum_weighted_ask = 0.0
103
+
104
+ # Reconstruct raw bid/ask curves from the net diff arrays for calculation
105
+ # (This is an approximation based on the diffs passed in, ideally we use raw state)
106
+ # For efficiency, we calculate 'imbalance' directly from the net curve
107
  weighted_imbalance = 0.0
 
108
 
109
  for i in range(len(diff_x)):
110
  dist = diff_x[i]
111
+ net_vol = diff_y_raw[i] # This is BidVol - AskVol at this depth
112
+
113
+ # Decay function: e^(-x / lambda)
114
  weight = math.exp(-dist / DECAY_LAMBDA)
115
+ weighted_imbalance += net_vol * weight
116
+
117
+ # Normalize Imbalance (-1 to 1 range roughly)
118
+ # We divide by the total weighted volume estimate to get a ratio (rho)
119
+ # Estimate total volume based on abs(net_vol) as a proxy
120
+ total_weighted_vol = sum(abs(v) * math.exp(-d/DECAY_LAMBDA) for d, v in zip(diff_x, diff_y_raw))
121
+ if total_weighted_vol == 0: rho = 0
122
+ else: rho = weighted_imbalance / total_weighted_vol
123
+
124
+ # 2. Base Micro-Price Projection
125
+ # P_micro = P_mid + (Spread * ImbalanceRatio * Sensitivity)
126
+ spread = best_ask - best_bid
127
+ theoretical_delta = (spread / 2) * rho * IMPACT_SENSITIVITY
128
+
129
+ projected_price = current_mid + theoretical_delta
130
 
131
+ # 3. Wall Friction Logic
132
+ # If the projection tries to cross a wall, the Z-Score of that wall reduces the delta.
133
+
134
+ final_delta = theoretical_delta
135
+
136
+ # Check Ask Walls (Resistance)
137
+ if final_delta > 0 and walls['asks']:
138
+ nearest_wall = walls['asks'][0] # Strongest wall
139
+ if projected_price >= nearest_wall['price']:
140
+ # Dampen based on Z-Score. Higher Z = More damping.
141
+ # Factor: 1 / (1 + (Z * 0.1))
142
+ damp_factor = 1.0 / (1.0 + (nearest_wall['z_score'] * 0.2))
143
+ final_delta *= damp_factor
144
+
145
+ # Check Bid Walls (Support)
146
+ elif final_delta < 0 and walls['bids']:
147
+ nearest_wall = walls['bids'][0] # Strongest wall
148
+ if projected_price <= nearest_wall['price']:
149
+ damp_factor = 1.0 / (1.0 + (nearest_wall['z_score'] * 0.2))
150
+ final_delta *= damp_factor
151
+
152
+ final_projected = current_mid + final_delta
153
 
154
+ return {
155
+ "projected": final_projected,
156
+ "net_score": weighted_imbalance, # Raw score for the UI meter
157
+ "rho": rho # Imbalance ratio
158
+ }
159
 
160
  def process_market_data():
161
  if not market_state['ready']: return {"error": "Initializing..."}
162
 
163
  mid = market_state['current_mid']
164
+
165
+ # Get raw sorted orders
166
  sorted_bids = sorted(market_state['bids'].items(), key=lambda x: -x[0])
167
  sorted_asks = sorted(market_state['asks'].items(), key=lambda x: x[0])
168
+
169
+ if not sorted_bids or not sorted_asks: return {"error": "Empty Book"}
170
 
171
+ best_bid = sorted_bids[0][0]
172
+ best_ask = sorted_asks[0][0]
173
 
174
+ # --- WALL DETECTION ---
175
  bid_walls = detect_anomalies(sorted_bids, WALL_LOOKBACK)
176
  ask_walls = detect_anomalies(sorted_asks, WALL_LOOKBACK)
177
 
178
+ # --- DEPTH AGGREGATION ---
179
  d_b_x, d_b_y, cum = [], [], 0
180
+ for p, q in sorted_bids[:300]:
181
  d = mid - p
182
  if d >= 0:
183
  cum += q
184
  d_b_x.append(d); d_b_y.append(cum)
185
 
186
  d_a_x, d_a_y, cum = [], [], 0
187
+ for p, q in sorted_asks[:300]:
188
  d = p - mid
189
  if d >= 0:
190
  cum += q
191
  d_a_x.append(d); d_a_y.append(cum)
192
 
193
+ # --- UNIFIED GRID FOR ANALYSIS ---
194
+ diff_x, diff_y_net = [], []
195
  chart_bids, chart_asks = [], []
196
 
197
  if d_b_x and d_a_x:
198
  max_dist = min(d_b_x[-1], d_a_x[-1])
199
+ # Create 100 buckets up to the max common depth
200
  step_size = max_dist / 100
201
  steps = [i * step_size for i in range(1, 101)]
202
 
203
  for s in steps:
204
+ # Interpolate Bid Volume at step s
205
  idx_b = bisect.bisect_right(d_b_x, s)
206
  vol_b = d_b_y[idx_b-1] if idx_b > 0 else 0
207
+
208
+ # Interpolate Ask Volume at step s
209
  idx_a = bisect.bisect_right(d_a_x, s)
210
  vol_a = d_a_y[idx_a-1] if idx_a > 0 else 0
211
 
212
  diff_x.append(s)
213
+ diff_y_net.append(vol_b - vol_a) # Net Imbalance at this depth
214
+
215
  chart_bids.append(vol_b)
216
  chart_asks.append(vol_a)
217
 
218
+ # --- MATHEMATICAL ANALYSIS ---
219
+ analysis = calculate_micro_price_structure(
220
+ diff_x,
221
+ diff_y_net,
222
+ mid,
223
+ best_bid,
224
+ best_ask,
225
+ {"bids": bid_walls, "asks": ask_walls}
226
+ )
227
 
228
  now = time.time()
229
  if analysis:
230
+ # Rate limit prediction history update
231
  if not market_state['pred_history'] or (now - market_state['pred_history'][-1]['t'] > 0.5):
232
  market_state['pred_history'].append({'t': now, 'p': analysis['projected']})
233
  if len(market_state['pred_history']) > HISTORY_LENGTH:
 
238
  "history": market_state['history'],
239
  "pred_history": market_state['pred_history'],
240
  "depth_x": diff_x,
241
+ "depth_net": diff_y_net,
242
  "depth_bids": chart_bids,
243
  "depth_asks": chart_asks,
244
  "analysis": analysis,
245
  "walls": {"bids": bid_walls, "asks": ask_walls}
246
  }
247
 
248
+ # --- FRONTEND ---
249
  HTML_PAGE = f"""
250
  <!DOCTYPE html>
251
  <html lang="en">
 
274
  height: 100vh; width: 100vw;
275
  }}
276
 
 
277
  .layout {{
278
  display: grid;
279
+ grid-template-rows: 34px 1fr 1fr;
280
  grid-template-columns: 3fr 1fr;
281
  gap: 1px;
282
  background-color: var(--border);
 
286
 
287
  .panel {{ background: var(--bg-panel); display: flex; flex-direction: column; overflow: hidden; }}
288
 
 
289
  .status-bar {{
290
  grid-column: 1 / 3;
291
  grid-row: 1 / 2;
 
305
  .live-dot {{ width: 8px; height: 8px; background-color: var(--green); border-radius: 50%; display: inline-block; box-shadow: 0 0 8px var(--green); }}
306
  .ticker-val {{ font-weight: 700; color: #fff; font-size: 13px; }}
307
 
 
308
  #p-chart {{ grid-column: 1 / 2; grid-row: 2 / 3; }}
309
 
 
310
  #p-depth {{
311
  grid-column: 1 / 2; grid-row: 3 / 4;
312
  display: grid;
 
316
  }}
317
  .depth-sub {{ background: var(--bg-panel); display: flex; flex-direction: column; position: relative; }}
318
 
 
319
  #p-sidebar {{
320
  grid-column: 2 / 3;
321
  grid-row: 2 / 4;
 
326
  border-left: 1px solid var(--border);
327
  }}
328
 
 
329
  .chart-header {{
330
  height: 24px;
331
  min-height: 24px;
 
335
  font-size: 10px;
336
  font-weight: 700;
337
  color: var(--text-dim);
338
+ background: #050505;
339
  border-bottom: 1px solid #151515;
340
  letter-spacing: 0.5px;
341
  }}
342
 
 
343
  .data-group {{ display: flex; flex-direction: column; gap: 4px; }}
344
  .label {{ font-size: 10px; color: var(--text-dim); font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }}
345
  .value {{ font-family: 'JetBrains Mono', monospace; font-size: 20px; font-weight: 700; color: #fff; }}
 
347
  .value-sub {{ font-family: 'JetBrains Mono', monospace; font-size: 11px; margin-top: 2px; color: #666; }}
348
 
349
  .divider {{ height: 1px; background: var(--border); width: 100%; }}
 
 
350
  .c-green {{ color: var(--green); }}
351
  .c-red {{ color: var(--red); }}
352
  .c-dim {{ color: var(--text-dim); }}
353
 
 
354
  .list-container {{ display: flex; flex-direction: column; gap: 8px; overflow-y: hidden; }}
355
  .list-item {{
356
  display: flex; justify-content: space-between;
 
361
  }}
362
  .list-item span:first-child {{ color: #e0e0e0; }}
363
  .list-item:last-child {{ border: none; }}
 
364
  </style>
365
  </head>
366
  <body>
367
 
368
  <div class="layout">
 
369
  <div class="status-bar">
370
  <div class="status-left">
371
  <span class="live-dot"></span>
 
375
  <div class="status-right" id="clock">00:00:00 UTC</div>
376
  </div>
377
 
 
378
  <div id="p-chart" class="panel">
379
+ <div class="chart-header">PRICE ACTION // MICRO-STRUCTURE FORECAST</div>
380
  <div id="tv-price" style="flex: 1; width: 100%;"></div>
381
  </div>
382
 
 
383
  <div id="p-depth">
384
  <div class="depth-sub">
385
+ <div class="chart-header">LIQUIDITY DENSITY PROFILE</div>
386
  <div id="tv-raw" style="flex: 1; width: 100%;"></div>
387
  </div>
388
  <div class="depth-sub">
389
+ <div class="chart-header">ORDER FLOW IMBALANCE (OFI)</div>
390
  <div id="tv-net" style="flex: 1; width: 100%;"></div>
391
  </div>
392
  </div>
393
 
 
394
  <div id="p-sidebar" class="panel">
395
 
 
396
  <div class="data-group">
397
+ <span class="label">Micro-Price Delta (5s)</span>
398
  <div style="display:flex; align-items: baseline; gap: 10px;">
399
  <span id="proj-pct" class="value value-lg">--%</span>
400
  <span id="proj-val" class="value-sub">---</span>
 
403
 
404
  <div class="divider"></div>
405
 
 
406
  <div class="data-group">
407
+ <span class="label">OFI Imbalance Ratio (ρ)</span>
408
  <span id="score-val" class="value">0.00</span>
409
+ <span class="value-sub">Range: -1.0 (Bear) to 1.0 (Bull)</span>
410
  </div>
411
 
412
  <div class="divider"></div>
413
 
 
414
  <div class="data-group" style="flex: 1;">
415
+ <span class="label" style="margin-bottom: 10px;">High Sigma Walls (Z > 3.0)</span>
416
  <div id="wall-list" class="list-container">
417
+ <span class="c-dim" style="font-size: 11px;">Initializing Quantitative Scan...</span>
418
  </div>
419
  </div>
420
 
421
  <div style="margin-top: auto;">
422
+ <span class="label">Model Latency</span>
423
+ <div class="value-sub c-green">Optimized</div>
424
  </div>
425
  </div>
426
  </div>
427
 
428
  <script>
 
429
  setInterval(() => {{
430
  const now = new Date();
431
  document.getElementById('clock').innerText = now.toISOString().split('T')[1].split('.')[0] + ' UTC';
 
440
  wallList: document.getElementById('wall-list')
441
  }};
442
 
 
443
  const chartOpts = {{
444
  layout: {{ background: {{ type: 'solid', color: '#0a0a0a' }}, textColor: '#888', fontFamily: 'JetBrains Mono' }},
445
  grid: {{ vertLines: {{ color: '#151515' }}, horzLines: {{ color: '#151515' }} }},
 
448
  crosshair: {{ mode: 1, vertLine: {{ color: '#444', labelBackgroundColor: '#444' }}, horzLine: {{ color: '#444', labelBackgroundColor: '#444' }} }}
449
  }};
450
 
 
451
  const priceChart = LightweightCharts.createChart(document.getElementById('tv-price'), chartOpts);
452
  const priceSeries = priceChart.addLineSeries({{ color: '#FFFFFF', lineWidth: 1, title: 'Price' }});
453
  const predSeries = priceChart.addLineSeries({{ color: '#2979ff', lineWidth: 1, lineStyle: 2, title: 'Forecast' }});
454
 
 
455
  const rawChart = LightweightCharts.createChart(document.getElementById('tv-raw'), {{
456
  ...chartOpts,
457
  localization: {{ timeFormatter: t => '$' + t.toFixed(2) }}
 
459
  const bidSeries = rawChart.addAreaSeries({{ lineColor: '#00ff9d', topColor: 'rgba(0, 255, 157, 0.15)', bottomColor: 'rgba(0,0,0,0)', lineWidth: 1 }});
460
  const askSeries = rawChart.addAreaSeries({{ lineColor: '#ff3b3b', topColor: 'rgba(255, 59, 59, 0.15)', bottomColor: 'rgba(0,0,0,0)', lineWidth: 1 }});
461
 
 
462
  const netChart = LightweightCharts.createChart(document.getElementById('tv-net'), {{
463
  ...chartOpts,
464
  localization: {{ timeFormatter: t => '$' + t.toFixed(2) }}
 
467
 
468
  let activeLines = [];
469
 
 
470
  new ResizeObserver(entries => {{
471
  for(let entry of entries) {{
472
  const {{width, height}} = entry.contentRect;
 
474
  if(entry.target.id === 'tv-raw') rawChart.applyOptions({{width, height}});
475
  if(entry.target.id === 'tv-net') netChart.applyOptions({{width, height}});
476
  }}
477
+ }}).observe(document.body);
478
 
 
479
  ['tv-price', 'tv-raw', 'tv-net'].forEach(id => {{
480
  new ResizeObserver(e => {{
481
  if(id === 'tv-price') priceChart.applyOptions({{ width: e[0].contentRect.width, height: e[0].contentRect.height }});
 
491
  const data = JSON.parse(e.data);
492
  if (data.error) return;
493
 
 
494
  if (data.history.length) {{
495
  const hist = data.history.map(d => ({{ time: Math.floor(d.t), value: d.p }}));
496
  const cleanHist = [...new Map(hist.map(i => [i.time, i])).values()];
 
499
  const lastP = cleanHist[cleanHist.length-1].value;
500
  dom.ticker.innerText = lastP.toLocaleString('en-US', {{ minimumFractionDigits: 2 }});
501
 
 
502
  if (data.analysis) {{
503
  const proj = data.analysis.projected;
504
+ // Using the Imbalance Ratio (rho) for the score display
505
+ const rho = data.analysis.rho;
506
 
507
  predSeries.setData([
508
  cleanHist[cleanHist.length-1],
509
  {{ time: cleanHist[cleanHist.length-1].time + 60, value: proj }}
510
  ]);
511
 
 
512
  const pct = ((proj - lastP) / lastP) * 100;
513
  const sign = pct >= 0 ? "+" : "";
514
 
 
517
 
518
  dom.projVal.innerText = proj.toLocaleString('en-US', {{ minimumFractionDigits: 2 }});
519
 
520
+ dom.score.innerText = rho.toFixed(3);
521
+ dom.score.style.color = rho > 0 ? "var(--green)" : (rho < 0 ? "var(--red)" : "var(--text-main)");
522
  }}
523
  }}
524
 
 
525
  if (data.walls) {{
526
  activeLines.forEach(l => priceSeries.removePriceLine(l));
527
  activeLines = [];
 
539
  data.walls.asks.forEach(w => addWall(w, 'ASK'));
540
  data.walls.bids.forEach(w => addWall(w, 'BID'));
541
 
542
+ dom.wallList.innerHTML = html || '<span class="c-dim" style="font-size:11px">Scanning liquidity surface...</span>';
543
  }}
544
 
 
545
  if (data.depth_x.length) {{
546
  const bids = [], asks = [], nets = [];
547
  for(let i=0; i<data.depth_x.length; i++) {{
 
565
  </html>
566
  """
567
 
568
+ # --- SERVER ---
569
  async def kraken_worker():
570
  global market_state
571
  while True:
 
626
  await ws.prepare(request)
627
  connected_clients.add(ws)
628
  try:
629
+ async for msg in ws: pass
 
630
  finally:
631
  connected_clients.remove(ws)
632
  return ws
 
654
  await runner.setup()
655
  site = web.TCPSite(runner, '0.0.0.0', PORT)
656
  await site.start()
657
+ print(f"🚀 Quant Dashboard: http://localhost:{PORT}")
658
  await asyncio.Event().wait()
659
 
660
  if __name__ == "__main__":