Alvin3y1 commited on
Commit
04e02fe
·
verified ·
1 Parent(s): a574eba

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +8 -43
app.py CHANGED
@@ -9,13 +9,11 @@ from datetime import datetime
9
  from aiohttp import web
10
  import websockets
11
 
12
- # --- CONFIGURATION ---
13
  SYMBOL_KRAKEN = "BTC/USD"
14
  PORT = 7860
15
  HISTORY_LENGTH = 300
16
  BROADCAST_RATE = 0.1
17
 
18
- # Mathematical Constants
19
  DECAY_LAMBDA = 50.0
20
  IMPACT_SENSITIVITY = 2.0
21
  WALL_DAMPENING = 0.8
@@ -24,14 +22,13 @@ WALL_LOOKBACK = 200
24
 
25
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
26
 
27
- # --- GLOBAL STATE ---
28
  market_state = {
29
  "bids": {},
30
  "asks": {},
31
  "history": [],
32
  "pred_history": [],
33
  "trade_vol_history": [],
34
- "ohlc_history": [], # Stores 1m candles
35
  "current_vol_window": {"buy": 0.0, "sell": 0.0, "start": time.time()},
36
  "current_mid": 0.0,
37
  "ready": False
@@ -39,8 +36,6 @@ market_state = {
39
 
40
  connected_clients = set()
41
 
42
- # --- QUANTITATIVE METHODS ---
43
-
44
  def detect_anomalies(orders, scan_depth):
45
  if len(orders) < 10: return []
46
  relevant_orders = orders[:scan_depth]
@@ -105,7 +100,6 @@ def process_market_data():
105
 
106
  mid = market_state['current_mid']
107
 
108
- # Process Trade Volume Window (Reset every 1 second)
109
  now = time.time()
110
  if now - market_state['current_vol_window']['start'] >= 1.0:
111
  market_state['trade_vol_history'].append({
@@ -186,7 +180,6 @@ def process_market_data():
186
  "walls": {"bids": bid_walls, "asks": ask_walls}
187
  }
188
 
189
- # --- FRONTEND ---
190
  HTML_PAGE = f"""
191
  <!DOCTYPE html>
192
  <html lang="en">
@@ -248,7 +241,6 @@ HTML_PAGE = f"""
248
 
249
  #p-chart {{ grid-column: 1 / 2; grid-row: 2 / 3; }}
250
 
251
- /* BOTTOM GRID */
252
  #p-bottom {{
253
  grid-column: 1 / 2; grid-row: 3 / 4;
254
  display: grid;
@@ -258,7 +250,6 @@ HTML_PAGE = f"""
258
  }}
259
  .bottom-sub {{ background: var(--bg-panel); display: flex; flex-direction: column; position: relative; }}
260
 
261
- /* SIDEBAR */
262
  #p-sidebar {{
263
  grid-column: 2 / 3;
264
  grid-row: 2 / 4;
@@ -306,12 +297,11 @@ HTML_PAGE = f"""
306
  .list-item span:first-child {{ color: #e0e0e0; }}
307
  .list-item:last-child {{ border: none; }}
308
 
309
- /* SIDEBAR CHARTS */
310
  .sidebar-chart-box {{
311
  flex: 1;
312
  display: flex;
313
  flex-direction: column;
314
- min-height: 0; /* Important for flex scaling */
315
  }}
316
  .mini-chart {{
317
  flex: 1;
@@ -333,13 +323,11 @@ HTML_PAGE = f"""
333
  <div class="status-right" id="clock">00:00:00 UTC</div>
334
  </div>
335
 
336
- <!-- TOP LEFT: Price Line -->
337
  <div id="p-chart" class="panel">
338
  <div class="chart-header">PRICE ACTION (BLUE) // PREDICTION (YELLOW)</div>
339
  <div id="tv-price" style="flex: 1; width: 100%;"></div>
340
  </div>
341
 
342
- <!-- BOTTOM: Candles + Imbalance -->
343
  <div id="p-bottom">
344
  <div class="bottom-sub">
345
  <div class="chart-header">1M KLINE (KRAKEN OHLC)</div>
@@ -351,10 +339,8 @@ HTML_PAGE = f"""
351
  </div>
352
  </div>
353
 
354
- <!-- SIDEBAR -->
355
  <div id="p-sidebar" class="panel">
356
 
357
- <!-- STATS -->
358
  <div class="data-group">
359
  <span class="label">Micro-Price Delta</span>
360
  <div style="display:flex; align-items: baseline; gap: 10px;">
@@ -372,7 +358,6 @@ HTML_PAGE = f"""
372
 
373
  <div class="divider"></div>
374
 
375
- <!-- WALLS -->
376
  <div class="data-group">
377
  <span class="label">Detected Walls (Z > 3.0)</span>
378
  <div id="wall-list" class="list-container">
@@ -380,13 +365,11 @@ HTML_PAGE = f"""
380
  </div>
381
  </div>
382
 
383
- <!-- VOL CHART -->
384
  <div class="sidebar-chart-box">
385
  <span class="label" style="margin-bottom:4px;">Real-time Volume Ticks</span>
386
  <div id="sidebar-vol" class="mini-chart"></div>
387
  </div>
388
 
389
- <!-- DENSITY CHART -->
390
  <div class="sidebar-chart-box">
391
  <span class="label" style="margin-bottom:4px;">Liquidity Density</span>
392
  <div id="sidebar-density" class="mini-chart"></div>
@@ -417,12 +400,10 @@ HTML_PAGE = f"""
417
  crosshair: {{ mode: 1, vertLine: {{ color: '#444', labelBackgroundColor: '#444' }}, horzLine: {{ color: '#444', labelBackgroundColor: '#444' }} }}
418
  }};
419
 
420
- // 1. PRICE LINE
421
  const priceChart = LightweightCharts.createChart(document.getElementById('tv-price'), chartOpts);
422
  const priceSeries = priceChart.addLineSeries({{ color: '#2979ff', lineWidth: 2, title: 'Price' }});
423
  const predSeries = priceChart.addLineSeries({{ color: '#ffeb3b', lineWidth: 2, lineStyle: 2, title: 'Forecast' }});
424
 
425
- // 2. CANDLESTICK CHART (NEW)
426
  const candleChart = LightweightCharts.createChart(document.getElementById('tv-candles'), {{
427
  ...chartOpts,
428
  timeScale: {{ timeVisible: true, secondsVisible: false }}
@@ -431,13 +412,11 @@ HTML_PAGE = f"""
431
  upColor: '#00ff9d', downColor: '#ff3b3b', borderVisible: false, wickUpColor: '#00ff9d', wickDownColor: '#ff3b3b'
432
  }});
433
 
434
- // 3. NET IMBALANCE
435
  const netChart = LightweightCharts.createChart(document.getElementById('tv-net'), {{
436
  ...chartOpts, localization: {{ timeFormatter: t => '$' + t.toFixed(2) }}
437
  }});
438
  const netSeries = netChart.addHistogramSeries({{ color: '#2979ff' }});
439
 
440
- // 4. SIDEBAR: VOLUME
441
  const volChart = LightweightCharts.createChart(document.getElementById('sidebar-vol'), {{
442
  ...chartOpts,
443
  grid: {{ vertLines: {{ visible: false }}, horzLines: {{ visible: false }} }},
@@ -448,12 +427,11 @@ HTML_PAGE = f"""
448
  const volBuySeries = volChart.addHistogramSeries({{ color: '#00ff9d' }});
449
  const volSellSeries = volChart.addHistogramSeries({{ color: '#ff3b3b' }});
450
 
451
- // 5. SIDEBAR: DENSITY (MOVED)
452
  const denChart = LightweightCharts.createChart(document.getElementById('sidebar-density'), {{
453
  ...chartOpts,
454
  grid: {{ vertLines: {{ visible: false }}, horzLines: {{ visible: false }} }},
455
  rightPriceScale: {{ visible: false }},
456
- timeScale: {{ visible: false }}, /* Hide X axis to fit */
457
  handleScroll: false, handleScale: false
458
  }});
459
  const bidSeries = denChart.addAreaSeries({{ lineColor: '#00ff9d', topColor: 'rgba(0, 255, 157, 0.15)', bottomColor: 'rgba(0,0,0,0)', lineWidth: 1 }});
@@ -461,7 +439,6 @@ HTML_PAGE = f"""
461
 
462
  let activeLines = [];
463
 
464
- // RESIZE OBSERVER
465
  new ResizeObserver(entries => {{
466
  for(let entry of entries) {{
467
  const {{width, height}} = entry.contentRect;
@@ -476,7 +453,6 @@ HTML_PAGE = f"""
476
  ['tv-price', 'tv-candles', 'tv-net', 'sidebar-vol', 'sidebar-density'].forEach(id => {{
477
  new ResizeObserver(e => {{
478
  const t = document.getElementById(id);
479
- // Simple check to ensure chart fits container
480
  if (t.clientWidth && t.clientHeight) {{
481
  if(id === 'tv-price') priceChart.applyOptions({{ width: t.clientWidth, height: t.clientHeight }});
482
  if(id === 'tv-candles') candleChart.applyOptions({{ width: t.clientWidth, height: t.clientHeight }});
@@ -494,7 +470,6 @@ HTML_PAGE = f"""
494
  const data = JSON.parse(e.data);
495
  if (data.error) return;
496
 
497
- // MAIN LINE & TEXT
498
  if (data.history.length) {{
499
  const hist = data.history.map(d => ({{ time: Math.floor(d.t), value: d.p }}));
500
  const cleanHist = [...new Map(hist.map(i => [i.time, i])).values()];
@@ -523,9 +498,7 @@ HTML_PAGE = f"""
523
  }}
524
  }}
525
 
526
- // CANDLES
527
  if (data.ohlc && data.ohlc.length) {{
528
- // Map backend format to LW Charts
529
  const candles = data.ohlc.map(c => ({{
530
  time: c.time,
531
  open: c.open,
@@ -533,12 +506,10 @@ HTML_PAGE = f"""
533
  low: c.low,
534
  close: c.close
535
  }}));
536
- // De-dupe by time
537
  const uniqueCandles = [...new Map(candles.map(i => [i.time, i])).values()];
538
  candleSeries.setData(uniqueCandles);
539
  }}
540
 
541
- // WALLS
542
  if (data.walls) {{
543
  activeLines.forEach(l => priceSeries.removePriceLine(l));
544
  activeLines = [];
@@ -553,7 +524,6 @@ HTML_PAGE = f"""
553
  dom.wallList.innerHTML = html || '<span class="c-dim" style="font-size:11px">Scanning...</span>';
554
  }}
555
 
556
- // SIDEBAR: VOL
557
  if (data.trade_history && data.trade_history.length) {{
558
  const buyData = [], sellData = [];
559
  data.trade_history.forEach(t => {{
@@ -565,7 +535,6 @@ HTML_PAGE = f"""
565
  volSellSeries.setData([...new Map(sellData.map(i => [i.time, i])).values()]);
566
  }}
567
 
568
- // SIDEBAR: DENSITY (Moved)
569
  if (data.depth_x.length) {{
570
  const bids = [], asks = [], nets = [];
571
  for(let i=0; i<data.depth_x.length; i++) {{
@@ -588,7 +557,6 @@ HTML_PAGE = f"""
588
  </html>
589
  """
590
 
591
- # --- SERVER ---
592
  async def kraken_worker():
593
  global market_state
594
  while True:
@@ -604,7 +572,6 @@ async def kraken_worker():
604
  "method": "subscribe",
605
  "params": {"channel": "trade", "symbol": [SYMBOL_KRAKEN]}
606
  }))
607
- # OHLC Interval 1 minute
608
  await ws.send(json.dumps({
609
  "method": "subscribe",
610
  "params": {"channel": "ohlc", "symbol": [SYMBOL_KRAKEN], "interval": 1}
@@ -650,20 +617,15 @@ async def kraken_worker():
650
  except: pass
651
 
652
  elif channel == "ohlc":
653
- # Kraken v2 OHLC data format:
654
- # [{"time": "...", "open": "...", "high": "...", "low": "...", "close": "...", ...}]
655
  for candle in data:
656
  try:
657
  c_data = {
658
- # Kraken sends end time as timestamp, we use it for chart
659
  'time': int(float(candle['endtime'])),
660
  'open': float(candle['open']),
661
  'high': float(candle['high']),
662
  'low': float(candle['low']),
663
  'close': float(candle['close'])
664
  }
665
- # Update or Append
666
- # If last candle has same time, replace it, else append
667
  if market_state['ohlc_history'] and market_state['ohlc_history'][-1]['time'] == c_data['time']:
668
  market_state['ohlc_history'][-1] = c_data
669
  else:
@@ -691,8 +653,11 @@ async def websocket_handler(request):
691
  ws = web.WebSocketResponse()
692
  await ws.prepare(request)
693
  connected_clients.add(ws)
694
- try: async for msg in ws: pass
695
- finally: connected_clients.remove(ws)
 
 
 
696
  return ws
697
 
698
  async def handle_index(request):
 
9
  from aiohttp import web
10
  import websockets
11
 
 
12
  SYMBOL_KRAKEN = "BTC/USD"
13
  PORT = 7860
14
  HISTORY_LENGTH = 300
15
  BROADCAST_RATE = 0.1
16
 
 
17
  DECAY_LAMBDA = 50.0
18
  IMPACT_SENSITIVITY = 2.0
19
  WALL_DAMPENING = 0.8
 
22
 
23
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
24
 
 
25
  market_state = {
26
  "bids": {},
27
  "asks": {},
28
  "history": [],
29
  "pred_history": [],
30
  "trade_vol_history": [],
31
+ "ohlc_history": [],
32
  "current_vol_window": {"buy": 0.0, "sell": 0.0, "start": time.time()},
33
  "current_mid": 0.0,
34
  "ready": False
 
36
 
37
  connected_clients = set()
38
 
 
 
39
  def detect_anomalies(orders, scan_depth):
40
  if len(orders) < 10: return []
41
  relevant_orders = orders[:scan_depth]
 
100
 
101
  mid = market_state['current_mid']
102
 
 
103
  now = time.time()
104
  if now - market_state['current_vol_window']['start'] >= 1.0:
105
  market_state['trade_vol_history'].append({
 
180
  "walls": {"bids": bid_walls, "asks": ask_walls}
181
  }
182
 
 
183
  HTML_PAGE = f"""
184
  <!DOCTYPE html>
185
  <html lang="en">
 
241
 
242
  #p-chart {{ grid-column: 1 / 2; grid-row: 2 / 3; }}
243
 
 
244
  #p-bottom {{
245
  grid-column: 1 / 2; grid-row: 3 / 4;
246
  display: grid;
 
250
  }}
251
  .bottom-sub {{ background: var(--bg-panel); display: flex; flex-direction: column; position: relative; }}
252
 
 
253
  #p-sidebar {{
254
  grid-column: 2 / 3;
255
  grid-row: 2 / 4;
 
297
  .list-item span:first-child {{ color: #e0e0e0; }}
298
  .list-item:last-child {{ border: none; }}
299
 
 
300
  .sidebar-chart-box {{
301
  flex: 1;
302
  display: flex;
303
  flex-direction: column;
304
+ min-height: 0;
305
  }}
306
  .mini-chart {{
307
  flex: 1;
 
323
  <div class="status-right" id="clock">00:00:00 UTC</div>
324
  </div>
325
 
 
326
  <div id="p-chart" class="panel">
327
  <div class="chart-header">PRICE ACTION (BLUE) // PREDICTION (YELLOW)</div>
328
  <div id="tv-price" style="flex: 1; width: 100%;"></div>
329
  </div>
330
 
 
331
  <div id="p-bottom">
332
  <div class="bottom-sub">
333
  <div class="chart-header">1M KLINE (KRAKEN OHLC)</div>
 
339
  </div>
340
  </div>
341
 
 
342
  <div id="p-sidebar" class="panel">
343
 
 
344
  <div class="data-group">
345
  <span class="label">Micro-Price Delta</span>
346
  <div style="display:flex; align-items: baseline; gap: 10px;">
 
358
 
359
  <div class="divider"></div>
360
 
 
361
  <div class="data-group">
362
  <span class="label">Detected Walls (Z > 3.0)</span>
363
  <div id="wall-list" class="list-container">
 
365
  </div>
366
  </div>
367
 
 
368
  <div class="sidebar-chart-box">
369
  <span class="label" style="margin-bottom:4px;">Real-time Volume Ticks</span>
370
  <div id="sidebar-vol" class="mini-chart"></div>
371
  </div>
372
 
 
373
  <div class="sidebar-chart-box">
374
  <span class="label" style="margin-bottom:4px;">Liquidity Density</span>
375
  <div id="sidebar-density" class="mini-chart"></div>
 
400
  crosshair: {{ mode: 1, vertLine: {{ color: '#444', labelBackgroundColor: '#444' }}, horzLine: {{ color: '#444', labelBackgroundColor: '#444' }} }}
401
  }};
402
 
 
403
  const priceChart = LightweightCharts.createChart(document.getElementById('tv-price'), chartOpts);
404
  const priceSeries = priceChart.addLineSeries({{ color: '#2979ff', lineWidth: 2, title: 'Price' }});
405
  const predSeries = priceChart.addLineSeries({{ color: '#ffeb3b', lineWidth: 2, lineStyle: 2, title: 'Forecast' }});
406
 
 
407
  const candleChart = LightweightCharts.createChart(document.getElementById('tv-candles'), {{
408
  ...chartOpts,
409
  timeScale: {{ timeVisible: true, secondsVisible: false }}
 
412
  upColor: '#00ff9d', downColor: '#ff3b3b', borderVisible: false, wickUpColor: '#00ff9d', wickDownColor: '#ff3b3b'
413
  }});
414
 
 
415
  const netChart = LightweightCharts.createChart(document.getElementById('tv-net'), {{
416
  ...chartOpts, localization: {{ timeFormatter: t => '$' + t.toFixed(2) }}
417
  }});
418
  const netSeries = netChart.addHistogramSeries({{ color: '#2979ff' }});
419
 
 
420
  const volChart = LightweightCharts.createChart(document.getElementById('sidebar-vol'), {{
421
  ...chartOpts,
422
  grid: {{ vertLines: {{ visible: false }}, horzLines: {{ visible: false }} }},
 
427
  const volBuySeries = volChart.addHistogramSeries({{ color: '#00ff9d' }});
428
  const volSellSeries = volChart.addHistogramSeries({{ color: '#ff3b3b' }});
429
 
 
430
  const denChart = LightweightCharts.createChart(document.getElementById('sidebar-density'), {{
431
  ...chartOpts,
432
  grid: {{ vertLines: {{ visible: false }}, horzLines: {{ visible: false }} }},
433
  rightPriceScale: {{ visible: false }},
434
+ timeScale: {{ visible: false }},
435
  handleScroll: false, handleScale: false
436
  }});
437
  const bidSeries = denChart.addAreaSeries({{ lineColor: '#00ff9d', topColor: 'rgba(0, 255, 157, 0.15)', bottomColor: 'rgba(0,0,0,0)', lineWidth: 1 }});
 
439
 
440
  let activeLines = [];
441
 
 
442
  new ResizeObserver(entries => {{
443
  for(let entry of entries) {{
444
  const {{width, height}} = entry.contentRect;
 
453
  ['tv-price', 'tv-candles', 'tv-net', 'sidebar-vol', 'sidebar-density'].forEach(id => {{
454
  new ResizeObserver(e => {{
455
  const t = document.getElementById(id);
 
456
  if (t.clientWidth && t.clientHeight) {{
457
  if(id === 'tv-price') priceChart.applyOptions({{ width: t.clientWidth, height: t.clientHeight }});
458
  if(id === 'tv-candles') candleChart.applyOptions({{ width: t.clientWidth, height: t.clientHeight }});
 
470
  const data = JSON.parse(e.data);
471
  if (data.error) return;
472
 
 
473
  if (data.history.length) {{
474
  const hist = data.history.map(d => ({{ time: Math.floor(d.t), value: d.p }}));
475
  const cleanHist = [...new Map(hist.map(i => [i.time, i])).values()];
 
498
  }}
499
  }}
500
 
 
501
  if (data.ohlc && data.ohlc.length) {{
 
502
  const candles = data.ohlc.map(c => ({{
503
  time: c.time,
504
  open: c.open,
 
506
  low: c.low,
507
  close: c.close
508
  }}));
 
509
  const uniqueCandles = [...new Map(candles.map(i => [i.time, i])).values()];
510
  candleSeries.setData(uniqueCandles);
511
  }}
512
 
 
513
  if (data.walls) {{
514
  activeLines.forEach(l => priceSeries.removePriceLine(l));
515
  activeLines = [];
 
524
  dom.wallList.innerHTML = html || '<span class="c-dim" style="font-size:11px">Scanning...</span>';
525
  }}
526
 
 
527
  if (data.trade_history && data.trade_history.length) {{
528
  const buyData = [], sellData = [];
529
  data.trade_history.forEach(t => {{
 
535
  volSellSeries.setData([...new Map(sellData.map(i => [i.time, i])).values()]);
536
  }}
537
 
 
538
  if (data.depth_x.length) {{
539
  const bids = [], asks = [], nets = [];
540
  for(let i=0; i<data.depth_x.length; i++) {{
 
557
  </html>
558
  """
559
 
 
560
  async def kraken_worker():
561
  global market_state
562
  while True:
 
572
  "method": "subscribe",
573
  "params": {"channel": "trade", "symbol": [SYMBOL_KRAKEN]}
574
  }))
 
575
  await ws.send(json.dumps({
576
  "method": "subscribe",
577
  "params": {"channel": "ohlc", "symbol": [SYMBOL_KRAKEN], "interval": 1}
 
617
  except: pass
618
 
619
  elif channel == "ohlc":
 
 
620
  for candle in data:
621
  try:
622
  c_data = {
 
623
  'time': int(float(candle['endtime'])),
624
  'open': float(candle['open']),
625
  'high': float(candle['high']),
626
  'low': float(candle['low']),
627
  'close': float(candle['close'])
628
  }
 
 
629
  if market_state['ohlc_history'] and market_state['ohlc_history'][-1]['time'] == c_data['time']:
630
  market_state['ohlc_history'][-1] = c_data
631
  else:
 
653
  ws = web.WebSocketResponse()
654
  await ws.prepare(request)
655
  connected_clients.add(ws)
656
+ try:
657
+ async for msg in ws:
658
+ pass
659
+ finally:
660
+ connected_clients.remove(ws)
661
  return ws
662
 
663
  async def handle_index(request):