Alvin3y1 commited on
Commit
7740db1
·
verified ·
1 Parent(s): 6d1f4cf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +64 -119
app.py CHANGED
@@ -2,34 +2,22 @@ import asyncio
2
  import json
3
  import logging
4
  import time
5
- import bisect
6
  import math
7
- import statistics
8
  import aiohttp
9
  from collections import deque
10
  from aiohttp import web
11
  import websockets
 
12
 
13
  SYMBOL_KRAKEN = "BTC/USD"
14
  PORT = 7860
15
  HISTORY_LENGTH = 300
16
  BROADCAST_RATE = 0.1
17
-
18
- # --- MATH CONSTANTS ---
19
- # DECAY for Micro-Price weights (focus on near-price liquidity)
20
  MICROPRICE_DECAY = 0.05
21
- # Lookback for VWAP and Volatility
22
- WINDOW_SIZE = 60
23
 
24
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
25
 
26
- # --- MATHEMATICAL HELPERS ---
27
-
28
  class OnlineStats:
29
- """
30
- Welford's Online Algorithm for calculating Mean and Variance
31
- in a single pass (O(1) complexity).
32
- """
33
  def __init__(self):
34
  self.count = 0
35
  self.mean = 0.0
@@ -52,17 +40,12 @@ class OnlineStats:
52
  return math.sqrt(self.variance)
53
 
54
  class KalmanVelocity:
55
- """
56
- Kalman Filter specifically tuned for tracking Velocity (Trend)
57
- rather than Position.
58
- Model: Constant Velocity
59
- """
60
  def __init__(self, R=0.001, Q=0.0001):
61
- self.z = 0.0 # Position
62
- self.v = 0.0 # Velocity
63
- self.P = 1.0 # Covariance
64
- self.R = R # Measurement Noise
65
- self.Q = Q # Process Noise
66
  self.last_ts = time.time()
67
 
68
  def update(self, price):
@@ -71,48 +54,56 @@ class KalmanVelocity:
71
  self.last_ts = now
72
  if dt <= 0: return
73
 
74
- # Predict
75
  pred_z = self.z + self.v * dt
76
  pred_v = self.v
77
  p_cov = self.P + self.Q
78
 
79
- # Update
80
- y = price - pred_z # Residual
81
- K = p_cov / (p_cov + self.R) # Kalman Gain
82
 
83
  self.z = pred_z + K * y
84
- # Velocity update derived from residual
85
  self.v = pred_v + (K / dt) * y
86
  self.P = (1 - K) * p_cov
87
 
88
- # --- STATE MANAGEMENT ---
89
-
90
  market_state = {
91
  "bids": {},
92
  "asks": {},
93
  "history": [],
94
- "trade_history": deque(maxlen=2000), # Store recent trades for VWAP
95
  "ohlc_history": [],
96
  "current_vol_window": {"buy": 0.0, "sell": 0.0, "start": time.time()},
97
  "current_mid": 0.0,
98
  "ready": False,
99
  "kalman": KalmanVelocity(),
100
- "stats": OnlineStats(), # Online Volatility Tracker
101
- "vwap_numerator": 0.0,
102
- "vwap_denominator": 0.0
103
  }
104
 
105
  connected_clients = set()
106
 
107
- # --- CORE MATH LOGIC ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
109
  def calculate_weighted_micro_price(mid_price):
110
- """
111
- Calculates the 'Micro-Price' (Stoikov).
112
- The mid-price is adjusted by the imbalance of liquidity
113
- weighted by distance from the mid.
114
- """
115
- bids = sorted(market_state['bids'].items(), reverse=True)[:50] # Top 50 levels
116
  asks = sorted(market_state['asks'].items())[:50]
117
 
118
  if not bids or not asks: return mid_price
@@ -120,9 +111,7 @@ def calculate_weighted_micro_price(mid_price):
120
  sum_wb = 0.0
121
  sum_wa = 0.0
122
 
123
- # Calculate Volume-Weighted Imbalance
124
  for p, q in bids:
125
- # Weight decays exponentially as distance from mid increases
126
  distance = abs(mid_price - p)
127
  weight = q * math.exp(-MICROPRICE_DECAY * distance)
128
  sum_wb += weight
@@ -135,23 +124,16 @@ def calculate_weighted_micro_price(mid_price):
135
  total_w = sum_wb + sum_wa
136
  if total_w == 0: return mid_price
137
 
138
- # Imbalance Ratio
139
  imbalance = (sum_wb - sum_wa) / total_w
140
-
141
- # Adjust spread based on imbalance
142
  spread = asks[0][0] - bids[0][0]
143
-
144
- # Formula: MicroPrice = Mid + (Imbalance * (Spread / 2))
145
  micro_price = mid_price + (imbalance * (spread / 2))
146
  return micro_price
147
 
148
  def calculate_vwap_1m():
149
- """Calculates Volume Weighted Average Price over the last 60 seconds."""
150
  cutoff = time.time() - 60
151
  v_sum = 0.0
152
  pv_sum = 0.0
153
 
154
- # Iterate trades in reverse (newest first)
155
  for trade in reversed(market_state['trade_history']):
156
  if trade['t'] < cutoff: break
157
  pv_sum += trade['p'] * trade['q']
@@ -160,13 +142,7 @@ def calculate_vwap_1m():
160
  return pv_sum / v_sum if v_sum > 0 else market_state['current_mid']
161
 
162
  def calculate_kyle_lambda(volatility, volume_window):
163
- """
164
- Kyle's Lambda: A measure of market impact (Liquidity Cost).
165
- Lambda ~ sigma / Volume
166
- Quantifies how much price moves per $1 of order flow.
167
- """
168
  if volume_window <= 0: return 0
169
- # Scaling factor (heuristic normalization for BTC/USD typical volumes)
170
  return (volatility * 1000) / (math.sqrt(volume_window) + 1)
171
 
172
  def process_market_data():
@@ -175,19 +151,15 @@ def process_market_data():
175
  mid = market_state['current_mid']
176
  now = time.time()
177
 
178
- # 1. Update Volatility Stats (Welford)
179
  market_state['stats'].update(mid)
180
  volatility = market_state['stats'].std_dev
181
- if volatility == 0: volatility = 1.0 # fallback
182
 
183
- # 2. Update Kalman Filter (Velocity Trend)
184
  market_state['kalman'].update(mid)
185
 
186
- # 3. Calculate Micro-Structure Features
187
  micro_price = calculate_weighted_micro_price(mid)
188
  vwap = calculate_vwap_1m()
189
 
190
- # 4. Calculate Order Flow Imbalance (OFI) - Last 10 seconds
191
  ofi_buy = 0.0
192
  ofi_sell = 0.0
193
  ofi_window = 10.0
@@ -200,36 +172,19 @@ def process_market_data():
200
  net_ofi = ofi_buy - ofi_sell
201
  total_vol_10s = ofi_buy + ofi_sell
202
 
203
- # 5. PREDICTION ENGINE (The Math Part)
204
-
205
- # A. Kyle's Impact Term
206
- # How much should the price move based on recent net buying/selling?
207
- # Impact = Net_Volume * Lambda
208
  k_lambda = calculate_kyle_lambda(volatility, total_vol_10s)
209
  impact_term = net_ofi * k_lambda
210
 
211
- # B. Mean Reversion Term (Ornstein-Uhlenbeck proxy)
212
- # Prices tend to revert to VWAP in the short term.
213
- # Pull = (VWAP - Price) * alpha
214
- mean_reversion_alpha = 0.1 # Reversion strength coefficient
215
  reversion_term = (vwap - mid) * mean_reversion_alpha
216
 
217
- # C. Micro-Price Alpha
218
- # The divergence between MicroPrice and MidPrice predicts immediate tick direction.
219
- micro_alpha = (micro_price - mid) * 0.8 # 0.8 is a sensitivity weight
220
 
221
- # D. Trend Term (Kalman)
222
- # Project current velocity 60 seconds out
223
  trend_term = market_state['kalman'].v * 60.0
224
 
225
- # TOTAL PREDICTED CHANGE (Delta)
226
- # We combine Market Impact (OFI), Micro-structure pressure (MP), Trend, and Mean Reversion.
227
  predicted_delta = impact_term + micro_alpha + trend_term + reversion_term
228
-
229
  pred_close = mid + predicted_delta
230
 
231
- # Calculate Confidence Interval (2 Sigma) for the ghost candle
232
- # Volatility scales with square root of time
233
  sigma_1m = volatility * math.sqrt(60)
234
 
235
  pred_candle = {
@@ -240,31 +195,17 @@ def process_market_data():
240
  'low': min(mid, pred_close) - (2 * sigma_1m)
241
  }
242
 
243
- # Data management for charts
244
  if not market_state['history'] or (now - market_state['history'][-1]['t'] > 0.5):
245
  market_state['history'].append({'t': now, 'p': mid})
246
  if len(market_state['history']) > HISTORY_LENGTH:
247
  market_state['history'].pop(0)
248
 
249
- # Calculate Depth Chart Arrays
250
- bids = sorted(market_state['bids'].items(), reverse=True)[:100]
251
- asks = sorted(market_state['asks'].items())[:100]
252
- depth_x, depth_net, depth_bids, depth_asks = [], [], [], []
253
-
254
- if bids and asks:
255
- center_price = mid
256
- for i in range(min(len(bids), len(asks))):
257
- dist = (asks[i][0] - bids[i][0]) / 2
258
- p_level = dist
259
- depth_x.append(p_level)
260
- depth_bids.append(bids[i][1])
261
- depth_asks.append(asks[i][1])
262
- depth_net.append(bids[i][1] - asks[i][1])
263
-
264
- # Analysis Object for Frontend
265
  analysis = {
266
  "projected": pred_close,
267
- "rho": (micro_price - mid), # Storing MP divergence as 'rho'
268
  "vwap": vwap,
269
  "lambda": k_lambda
270
  }
@@ -273,17 +214,11 @@ def process_market_data():
273
  "mid": mid,
274
  "history": market_state['history'],
275
  "ohlc": market_state['ohlc_history'],
276
- "trade_history": [], # Reduced payload, handled by client cumulative logic
277
  "pred_candle": pred_candle,
278
- "depth_x": depth_x,
279
- "depth_net": depth_net,
280
- "depth_bids": depth_bids,
281
- "depth_asks": depth_asks,
282
  "analysis": analysis,
283
- "walls": {"bids": [], "asks": []} # Removed legacy walls for cleaner math view
284
  }
285
 
286
- # --- HTML FRONTEND (Unchanged visual structure, updated data mapping) ---
287
  HTML_PAGE = f"""
288
  <!DOCTYPE html>
289
  <html lang="en">
@@ -323,7 +258,7 @@ HTML_PAGE = f"""
323
  <div id="tv-price" style="flex: 1;"></div>
324
  </div>
325
  <div id="p-bottom" class="panel">
326
- <div class="chart-header">1M KLINE + GHOST PREDICTION (PURPLE)</div>
327
  <div id="tv-candles" style="flex: 1;"></div>
328
  </div>
329
  <div id="p-sidebar" class="panel">
@@ -361,7 +296,14 @@ HTML_PAGE = f"""
361
  const candleSeries = candleChart.addCandlestickSeries({{ upColor: '#00ff9d', downColor: '#ff3b3b', borderVisible: false }});
362
  const ghostSeries = candleChart.addCandlestickSeries({{ upColor: 'rgba(213, 0, 249, 0.5)', downColor: 'rgba(213, 0, 249, 0.5)', borderVisible: true, borderColor: '#d500f9' }});
363
 
364
- new ResizeObserver(e => {{ priceChart.timeScale().fitContent(); }}).observe(document.body);
 
 
 
 
 
 
 
365
 
366
  function connect() {{
367
  const ws = new WebSocket((location.protocol === 'https:' ? 'wss' : 'ws') + '://' + location.host + '/ws');
@@ -376,23 +318,20 @@ HTML_PAGE = f"""
376
  dom.ticker.innerText = cleanHist[cleanHist.length-1].value.toFixed(2);
377
 
378
  if(data.analysis) {{
379
- // Plot Prediction Line
380
  predSeries.setData([
381
  cleanHist[cleanHist.length-1],
382
  {{ time: cleanHist[cleanHist.length-1].time + 60, value: data.analysis.projected }}
383
  ]);
384
 
385
- // Plot VWAP Line (simple point for now)
386
  vwapSeries.setData([
387
  {{ time: cleanHist[0].time, value: data.analysis.vwap }},
388
  {{ time: cleanHist[cleanHist.length-1].time, value: data.analysis.vwap }}
389
  ]);
390
 
391
- // Update Sidebar
392
- dom.lambdaVal.innerText = (data.analysis.lambda * 1000).toFixed(4); // Scaled for display
393
  const vwapDiff = cleanHist[cleanHist.length-1].value - data.analysis.vwap;
394
  dom.vwapDiv.innerText = vwapDiff.toFixed(2);
395
- dom.vwapDiv.style.color = vwapDiff > 0 ? '#ff3b3b' : '#00ff9d'; // Red if price > vwap (overbought)
396
  }}
397
  }}
398
 
@@ -400,6 +339,17 @@ HTML_PAGE = f"""
400
  candleSeries.setData(data.ohlc.map(c => ({{ time: c.time, open: c.open, high: c.high, low: c.low, close: c.close }})));
401
  }}
402
 
 
 
 
 
 
 
 
 
 
 
 
403
  if (data.pred_candle) {{
404
  ghostSeries.setData([data.pred_candle]);
405
  const currentP = parseFloat(dom.ticker.innerText);
@@ -420,8 +370,6 @@ HTML_PAGE = f"""
420
 
421
  async def kraken_worker():
422
  global market_state
423
-
424
- # Initial fetch for OHLC
425
  try:
426
  async with aiohttp.ClientSession() as session:
427
  url = "https://api.kraken.com/0/public/OHLC?pair=XBTUSD&interval=1"
@@ -429,7 +377,6 @@ async def kraken_worker():
429
  if response.status == 200:
430
  data = await response.json()
431
  if 'result' in data:
432
- # Extract OHLC
433
  raw = list(data['result'].values())[0]
434
  market_state['ohlc_history'] = [
435
  {'time': int(c[0]), 'open': float(c[1]), 'high': float(c[2]), 'low': float(c[3]), 'close': float(c[4])}
@@ -441,7 +388,7 @@ async def kraken_worker():
441
  while True:
442
  try:
443
  async with websockets.connect("wss://ws.kraken.com/v2") as ws:
444
- logging.info(f"🔌 Connected to Kraken ({SYMBOL_KRAKEN})")
445
 
446
  await ws.send(json.dumps({"method": "subscribe", "params": {"channel": "book", "symbol": [SYMBOL_KRAKEN], "depth": 100}}))
447
  await ws.send(json.dumps({"method": "subscribe", "params": {"channel": "trade", "symbol": [SYMBOL_KRAKEN]}}))
@@ -459,7 +406,6 @@ async def kraken_worker():
459
  for ask in item.get('asks', []):
460
  market_state['asks'][float(ask['price'])] = float(ask['qty'])
461
 
462
- # Cleanup zero qty
463
  market_state['bids'] = {k: v for k, v in market_state['bids'].items() if v > 0}
464
  market_state['asks'] = {k: v for k, v in market_state['asks'].items() if v > 0}
465
 
@@ -482,7 +428,6 @@ async def kraken_worker():
482
  except: pass
483
 
484
  elif channel == "ohlc":
485
- # Update OHLC array
486
  for c in data:
487
  c_data = {
488
  'time': int(float(c['endtime'])),
@@ -541,7 +486,7 @@ async def main():
541
  await runner.setup()
542
  site = web.TCPSite(runner, '0.0.0.0', PORT)
543
  await site.start()
544
- print(f"🚀 Quant Dashboard: http://localhost:{PORT}")
545
  await asyncio.Event().wait()
546
 
547
  if __name__ == "__main__":
 
2
  import json
3
  import logging
4
  import time
 
5
  import math
 
6
  import aiohttp
7
  from collections import deque
8
  from aiohttp import web
9
  import websockets
10
+ import statistics
11
 
12
  SYMBOL_KRAKEN = "BTC/USD"
13
  PORT = 7860
14
  HISTORY_LENGTH = 300
15
  BROADCAST_RATE = 0.1
 
 
 
16
  MICROPRICE_DECAY = 0.05
 
 
17
 
18
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
19
 
 
 
20
  class OnlineStats:
 
 
 
 
21
  def __init__(self):
22
  self.count = 0
23
  self.mean = 0.0
 
40
  return math.sqrt(self.variance)
41
 
42
  class KalmanVelocity:
 
 
 
 
 
43
  def __init__(self, R=0.001, Q=0.0001):
44
+ self.z = 0.0
45
+ self.v = 0.0
46
+ self.P = 1.0
47
+ self.R = R
48
+ self.Q = Q
49
  self.last_ts = time.time()
50
 
51
  def update(self, price):
 
54
  self.last_ts = now
55
  if dt <= 0: return
56
 
 
57
  pred_z = self.z + self.v * dt
58
  pred_v = self.v
59
  p_cov = self.P + self.Q
60
 
61
+ y = price - pred_z
62
+ K = p_cov / (p_cov + self.R)
 
63
 
64
  self.z = pred_z + K * y
 
65
  self.v = pred_v + (K / dt) * y
66
  self.P = (1 - K) * p_cov
67
 
 
 
68
  market_state = {
69
  "bids": {},
70
  "asks": {},
71
  "history": [],
72
+ "trade_history": deque(maxlen=2000),
73
  "ohlc_history": [],
74
  "current_vol_window": {"buy": 0.0, "sell": 0.0, "start": time.time()},
75
  "current_mid": 0.0,
76
  "ready": False,
77
  "kalman": KalmanVelocity(),
78
+ "stats": OnlineStats(),
79
+ "walls": {"bids": [], "asks": []}
 
80
  }
81
 
82
  connected_clients = set()
83
 
84
+ def detect_walls(order_book, side_name):
85
+ if not order_book: return []
86
+
87
+ sorted_book = sorted(order_book.items(), key=lambda x: x[0], reverse=(side_name == 'bids'))
88
+ relevant = sorted_book[:50]
89
+
90
+ volumes = [q for p, q in relevant]
91
+ if not volumes: return []
92
+
93
+ avg_vol = statistics.mean(volumes)
94
+ std_vol = statistics.stdev(volumes) if len(volumes) > 1 else 0
95
+
96
+ walls = []
97
+ for p, q in relevant:
98
+ if std_vol > 0:
99
+ z = (q - avg_vol) / std_vol
100
+ if z > 2.5:
101
+ walls.append({'p': p, 'q': q, 'z': z})
102
+
103
+ return walls[:3]
104
 
105
  def calculate_weighted_micro_price(mid_price):
106
+ bids = sorted(market_state['bids'].items(), reverse=True)[:50]
 
 
 
 
 
107
  asks = sorted(market_state['asks'].items())[:50]
108
 
109
  if not bids or not asks: return mid_price
 
111
  sum_wb = 0.0
112
  sum_wa = 0.0
113
 
 
114
  for p, q in bids:
 
115
  distance = abs(mid_price - p)
116
  weight = q * math.exp(-MICROPRICE_DECAY * distance)
117
  sum_wb += weight
 
124
  total_w = sum_wb + sum_wa
125
  if total_w == 0: return mid_price
126
 
 
127
  imbalance = (sum_wb - sum_wa) / total_w
 
 
128
  spread = asks[0][0] - bids[0][0]
 
 
129
  micro_price = mid_price + (imbalance * (spread / 2))
130
  return micro_price
131
 
132
  def calculate_vwap_1m():
 
133
  cutoff = time.time() - 60
134
  v_sum = 0.0
135
  pv_sum = 0.0
136
 
 
137
  for trade in reversed(market_state['trade_history']):
138
  if trade['t'] < cutoff: break
139
  pv_sum += trade['p'] * trade['q']
 
142
  return pv_sum / v_sum if v_sum > 0 else market_state['current_mid']
143
 
144
  def calculate_kyle_lambda(volatility, volume_window):
 
 
 
 
 
145
  if volume_window <= 0: return 0
 
146
  return (volatility * 1000) / (math.sqrt(volume_window) + 1)
147
 
148
  def process_market_data():
 
151
  mid = market_state['current_mid']
152
  now = time.time()
153
 
 
154
  market_state['stats'].update(mid)
155
  volatility = market_state['stats'].std_dev
156
+ if volatility == 0: volatility = 1.0
157
 
 
158
  market_state['kalman'].update(mid)
159
 
 
160
  micro_price = calculate_weighted_micro_price(mid)
161
  vwap = calculate_vwap_1m()
162
 
 
163
  ofi_buy = 0.0
164
  ofi_sell = 0.0
165
  ofi_window = 10.0
 
172
  net_ofi = ofi_buy - ofi_sell
173
  total_vol_10s = ofi_buy + ofi_sell
174
 
 
 
 
 
 
175
  k_lambda = calculate_kyle_lambda(volatility, total_vol_10s)
176
  impact_term = net_ofi * k_lambda
177
 
178
+ mean_reversion_alpha = 0.1
 
 
 
179
  reversion_term = (vwap - mid) * mean_reversion_alpha
180
 
181
+ micro_alpha = (micro_price - mid) * 0.8
 
 
182
 
 
 
183
  trend_term = market_state['kalman'].v * 60.0
184
 
 
 
185
  predicted_delta = impact_term + micro_alpha + trend_term + reversion_term
 
186
  pred_close = mid + predicted_delta
187
 
 
 
188
  sigma_1m = volatility * math.sqrt(60)
189
 
190
  pred_candle = {
 
195
  'low': min(mid, pred_close) - (2 * sigma_1m)
196
  }
197
 
 
198
  if not market_state['history'] or (now - market_state['history'][-1]['t'] > 0.5):
199
  market_state['history'].append({'t': now, 'p': mid})
200
  if len(market_state['history']) > HISTORY_LENGTH:
201
  market_state['history'].pop(0)
202
 
203
+ bid_walls = detect_walls(market_state['bids'], 'bids')
204
+ ask_walls = detect_walls(market_state['asks'], 'asks')
205
+
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  analysis = {
207
  "projected": pred_close,
208
+ "rho": (micro_price - mid),
209
  "vwap": vwap,
210
  "lambda": k_lambda
211
  }
 
214
  "mid": mid,
215
  "history": market_state['history'],
216
  "ohlc": market_state['ohlc_history'],
 
217
  "pred_candle": pred_candle,
 
 
 
 
218
  "analysis": analysis,
219
+ "walls": {"bids": bid_walls, "asks": ask_walls}
220
  }
221
 
 
222
  HTML_PAGE = f"""
223
  <!DOCTYPE html>
224
  <html lang="en">
 
258
  <div id="tv-price" style="flex: 1;"></div>
259
  </div>
260
  <div id="p-bottom" class="panel">
261
+ <div class="chart-header">1M KLINE + WALLS + GHOST PREDICTION (PURPLE)</div>
262
  <div id="tv-candles" style="flex: 1;"></div>
263
  </div>
264
  <div id="p-sidebar" class="panel">
 
296
  const candleSeries = candleChart.addCandlestickSeries({{ upColor: '#00ff9d', downColor: '#ff3b3b', borderVisible: false }});
297
  const ghostSeries = candleChart.addCandlestickSeries({{ upColor: 'rgba(213, 0, 249, 0.5)', downColor: 'rgba(213, 0, 249, 0.5)', borderVisible: true, borderColor: '#d500f9' }});
298
 
299
+ let activeWallLines = [];
300
+
301
+ new ResizeObserver(e => {{
302
+ const t1 = document.getElementById('tv-price');
303
+ const t2 = document.getElementById('tv-candles');
304
+ priceChart.applyOptions({{ width: t1.clientWidth, height: t1.clientHeight }});
305
+ candleChart.applyOptions({{ width: t2.clientWidth, height: t2.clientHeight }});
306
+ }}).observe(document.body);
307
 
308
  function connect() {{
309
  const ws = new WebSocket((location.protocol === 'https:' ? 'wss' : 'ws') + '://' + location.host + '/ws');
 
318
  dom.ticker.innerText = cleanHist[cleanHist.length-1].value.toFixed(2);
319
 
320
  if(data.analysis) {{
 
321
  predSeries.setData([
322
  cleanHist[cleanHist.length-1],
323
  {{ time: cleanHist[cleanHist.length-1].time + 60, value: data.analysis.projected }}
324
  ]);
325
 
 
326
  vwapSeries.setData([
327
  {{ time: cleanHist[0].time, value: data.analysis.vwap }},
328
  {{ time: cleanHist[cleanHist.length-1].time, value: data.analysis.vwap }}
329
  ]);
330
 
331
+ dom.lambdaVal.innerText = (data.analysis.lambda * 1000).toFixed(4);
 
332
  const vwapDiff = cleanHist[cleanHist.length-1].value - data.analysis.vwap;
333
  dom.vwapDiv.innerText = vwapDiff.toFixed(2);
334
+ dom.vwapDiv.style.color = vwapDiff > 0 ? '#ff3b3b' : '#00ff9d';
335
  }}
336
  }}
337
 
 
339
  candleSeries.setData(data.ohlc.map(c => ({{ time: c.time, open: c.open, high: c.high, low: c.low, close: c.close }})));
340
  }}
341
 
342
+ if (data.walls) {{
343
+ activeWallLines.forEach(l => candleSeries.removePriceLine(l));
344
+ activeWallLines = [];
345
+ data.walls.bids.forEach(w => {{
346
+ activeWallLines.push(candleSeries.createPriceLine({{ price: w.p, color: '#00ff9d', lineWidth: 2, lineStyle: 2, axisLabelVisible: true, title: 'BID WALL' }}));
347
+ }});
348
+ data.walls.asks.forEach(w => {{
349
+ activeWallLines.push(candleSeries.createPriceLine({{ price: w.p, color: '#ff3b3b', lineWidth: 2, lineStyle: 2, axisLabelVisible: true, title: 'ASK WALL' }}));
350
+ }});
351
+ }}
352
+
353
  if (data.pred_candle) {{
354
  ghostSeries.setData([data.pred_candle]);
355
  const currentP = parseFloat(dom.ticker.innerText);
 
370
 
371
  async def kraken_worker():
372
  global market_state
 
 
373
  try:
374
  async with aiohttp.ClientSession() as session:
375
  url = "https://api.kraken.com/0/public/OHLC?pair=XBTUSD&interval=1"
 
377
  if response.status == 200:
378
  data = await response.json()
379
  if 'result' in data:
 
380
  raw = list(data['result'].values())[0]
381
  market_state['ohlc_history'] = [
382
  {'time': int(c[0]), 'open': float(c[1]), 'high': float(c[2]), 'low': float(c[3]), 'close': float(c[4])}
 
388
  while True:
389
  try:
390
  async with websockets.connect("wss://ws.kraken.com/v2") as ws:
391
+ logging.info(f"Connected to Kraken ({SYMBOL_KRAKEN})")
392
 
393
  await ws.send(json.dumps({"method": "subscribe", "params": {"channel": "book", "symbol": [SYMBOL_KRAKEN], "depth": 100}}))
394
  await ws.send(json.dumps({"method": "subscribe", "params": {"channel": "trade", "symbol": [SYMBOL_KRAKEN]}}))
 
406
  for ask in item.get('asks', []):
407
  market_state['asks'][float(ask['price'])] = float(ask['qty'])
408
 
 
409
  market_state['bids'] = {k: v for k, v in market_state['bids'].items() if v > 0}
410
  market_state['asks'] = {k: v for k, v in market_state['asks'].items() if v > 0}
411
 
 
428
  except: pass
429
 
430
  elif channel == "ohlc":
 
431
  for c in data:
432
  c_data = {
433
  'time': int(float(c['endtime'])),
 
486
  await runner.setup()
487
  site = web.TCPSite(runner, '0.0.0.0', PORT)
488
  await site.start()
489
+ print(f"Quant Dashboard: http://localhost:{PORT}")
490
  await asyncio.Event().wait()
491
 
492
  if __name__ == "__main__":