Alvin3y1 commited on
Commit
c91e0bd
·
verified ·
1 Parent(s): b3cc425

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +190 -32
app.py CHANGED
@@ -97,25 +97,17 @@ def calculate_micro_price_structure(diff_x, diff_y_net, current_mid, best_bid, b
97
  }
98
 
99
  def calculate_polr(bids, asks, mid):
100
- """
101
- Path of Least Resistance:
102
- Simulates eating volume on both sides.
103
- """
104
  if not bids or not asks: return []
105
 
106
- sorted_bids = sorted(bids.items(), key=lambda x: -x[0]) # High to Low
107
- sorted_asks = sorted(asks.items(), key=lambda x: x[0]) # Low to High
108
 
109
  path_points = []
110
-
111
- # MODIFIED: Increased to 60 steps for high resolution
112
- # Steps from 0.5 BTC up to 30.0 BTC
113
  volume_steps = [i * 0.5 for i in range(1, 61)]
114
 
115
  current_time = time.time()
116
 
117
  for i, target_vol in enumerate(volume_steps):
118
- # 1. Find price cost to eat 'target_vol' UP
119
  ask_cost_dist = 0
120
  cum_vol = 0
121
  target_ask_price = mid
@@ -126,7 +118,6 @@ def calculate_polr(bids, asks, mid):
126
  break
127
  ask_cost_dist = target_ask_price - mid
128
 
129
- # 2. Find price cost to eat 'target_vol' DOWN
130
  bid_cost_dist = 0
131
  cum_vol = 0
132
  target_bid_price = mid
@@ -137,21 +128,11 @@ def calculate_polr(bids, asks, mid):
137
  break
138
  bid_cost_dist = mid - target_bid_price
139
 
140
- # 3. Compare Resistance
141
  if bid_cost_dist <= 0: bid_cost_dist = 0.01
142
  if ask_cost_dist <= 0: ask_cost_dist = 0.01
143
 
144
  projected_p = mid
145
  if ask_cost_dist > bid_cost_dist:
146
- # UP is harder (more distance for same vol), so price slides to bid
147
- # Conventional logic: Price takes path of least resistance.
148
- # If ask is thin (low dist), bid is thick (high dist) -> price goes up.
149
- # Here: Ask Dist > Bid Dist means Ask is THINNER? No.
150
- # Distance = Price Impact.
151
- # High Distance = High Impact = THIN Liquidity.
152
- # Low Distance = Low Impact = THICK Liquidity.
153
- # Price moves towards THIN liquidity (Least Resistance).
154
- # So if Ask Cost Dist > Bid Cost Dist, Ask is THIN. Price goes UP.
155
  projected_p = target_ask_price
156
  else:
157
  projected_p = target_bid_price
@@ -228,7 +209,6 @@ def process_market_data():
228
  {"bids": bid_walls, "asks": ask_walls}
229
  )
230
 
231
- # Calculate Path of Least Resistance
232
  polr_path = calculate_polr(market_state['bids'], market_state['asks'], mid)
233
 
234
  if analysis:
@@ -475,15 +455,12 @@ HTML_PAGE = f"""
475
 
476
  const priceChart = LightweightCharts.createChart(document.getElementById('tv-price'), chartOpts);
477
 
478
- // --- POLR RIVER INITIALIZATION ---
479
- // Creating 60 distinct series for high resolution flow
480
  const polrLines = [];
481
- const polrCount = 60; // Matches backend
482
 
483
  for(let i=0; i<polrCount; i++) {{
484
- // Calculate opacity: Nearest = 1.0, Furthest = 0.05
485
  const opacity = 1.0 - (i / (polrCount + 5));
486
- const color = `rgba(213, 0, 249, ${{opacity.toFixed(2)}})`; // Purple fade
487
 
488
  polrLines.push(
489
  priceChart.addLineSeries({{
@@ -496,7 +473,6 @@ HTML_PAGE = f"""
496
  }})
497
  );
498
  }}
499
- // --------------------------------
500
 
501
  const priceSeries = priceChart.addLineSeries({{ color: '#2979ff', lineWidth: 2, title: 'Price' }});
502
  const predSeries = priceChart.addLineSeries({{ color: '#ffeb3b', lineWidth: 2, lineStyle: 2, title: 'Math Forecast' }});
@@ -564,7 +540,6 @@ HTML_PAGE = f"""
564
  const lastTime = cleanHist[cleanHist.length-1].time;
565
  dom.ticker.innerText = lastP.toLocaleString('en-US', {{ minimumFractionDigits: 2 }});
566
 
567
- // 1. Yellow Line (Math Forecast)
568
  if (data.analysis) {{
569
  const proj = data.analysis.projected;
570
  const rho = data.analysis.rho;
@@ -572,7 +547,6 @@ HTML_PAGE = f"""
572
  cleanHist[cleanHist.length-1],
573
  {{ time: lastTime + 60, value: proj }}
574
  ]);
575
- // Text updates
576
  const pct = ((proj - lastP) / lastP) * 100;
577
  const sign = pct >= 0 ? "+" : "";
578
  dom.projPct.innerText = `${{sign}}${{pct.toFixed(4)}}%`;
@@ -582,7 +556,6 @@ HTML_PAGE = f"""
582
  dom.score.style.color = rho > 0 ? "var(--green)" : (rho < 0 ? "var(--red)" : "var(--text-main)");
583
  }}
584
 
585
- // 2. Purple River (POLR)
586
  if (data.polr && data.polr.length) {{
587
  data.polr.forEach((point, index) => {{
588
  if (index < polrLines.length) {{
@@ -658,4 +631,189 @@ HTML_PAGE = f"""
658
  }});
659
  </script>
660
  </body>
661
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  }
98
 
99
  def calculate_polr(bids, asks, mid):
 
 
 
 
100
  if not bids or not asks: return []
101
 
102
+ sorted_bids = sorted(bids.items(), key=lambda x: -x[0])
103
+ sorted_asks = sorted(asks.items(), key=lambda x: x[0])
104
 
105
  path_points = []
 
 
 
106
  volume_steps = [i * 0.5 for i in range(1, 61)]
107
 
108
  current_time = time.time()
109
 
110
  for i, target_vol in enumerate(volume_steps):
 
111
  ask_cost_dist = 0
112
  cum_vol = 0
113
  target_ask_price = mid
 
118
  break
119
  ask_cost_dist = target_ask_price - mid
120
 
 
121
  bid_cost_dist = 0
122
  cum_vol = 0
123
  target_bid_price = mid
 
128
  break
129
  bid_cost_dist = mid - target_bid_price
130
 
 
131
  if bid_cost_dist <= 0: bid_cost_dist = 0.01
132
  if ask_cost_dist <= 0: ask_cost_dist = 0.01
133
 
134
  projected_p = mid
135
  if ask_cost_dist > bid_cost_dist:
 
 
 
 
 
 
 
 
 
136
  projected_p = target_ask_price
137
  else:
138
  projected_p = target_bid_price
 
209
  {"bids": bid_walls, "asks": ask_walls}
210
  )
211
 
 
212
  polr_path = calculate_polr(market_state['bids'], market_state['asks'], mid)
213
 
214
  if analysis:
 
455
 
456
  const priceChart = LightweightCharts.createChart(document.getElementById('tv-price'), chartOpts);
457
 
 
 
458
  const polrLines = [];
459
+ const polrCount = 60;
460
 
461
  for(let i=0; i<polrCount; i++) {{
 
462
  const opacity = 1.0 - (i / (polrCount + 5));
463
+ const color = `rgba(213, 0, 249, ${{opacity.toFixed(2)}})`;
464
 
465
  polrLines.push(
466
  priceChart.addLineSeries({{
 
473
  }})
474
  );
475
  }}
 
476
 
477
  const priceSeries = priceChart.addLineSeries({{ color: '#2979ff', lineWidth: 2, title: 'Price' }});
478
  const predSeries = priceChart.addLineSeries({{ color: '#ffeb3b', lineWidth: 2, lineStyle: 2, title: 'Math Forecast' }});
 
540
  const lastTime = cleanHist[cleanHist.length-1].time;
541
  dom.ticker.innerText = lastP.toLocaleString('en-US', {{ minimumFractionDigits: 2 }});
542
 
 
543
  if (data.analysis) {{
544
  const proj = data.analysis.projected;
545
  const rho = data.analysis.rho;
 
547
  cleanHist[cleanHist.length-1],
548
  {{ time: lastTime + 60, value: proj }}
549
  ]);
 
550
  const pct = ((proj - lastP) / lastP) * 100;
551
  const sign = pct >= 0 ? "+" : "";
552
  dom.projPct.innerText = `${{sign}}${{pct.toFixed(4)}}%`;
 
556
  dom.score.style.color = rho > 0 ? "var(--green)" : (rho < 0 ? "var(--red)" : "var(--text-main)");
557
  }}
558
 
 
559
  if (data.polr && data.polr.length) {{
560
  data.polr.forEach((point, index) => {{
561
  if (index < polrLines.length) {{
 
631
  }});
632
  </script>
633
  </body>
634
+ </html>
635
+ """
636
+
637
+ async def kraken_worker():
638
+ global market_state
639
+ try:
640
+ async with aiohttp.ClientSession() as session:
641
+ url = "https://api.kraken.com/0/public/OHLC?pair=XBTUSD&interval=1"
642
+ async with session.get(url) as response:
643
+ if response.status == 200:
644
+ data = await response.json()
645
+ if 'result' in data:
646
+ for key in data['result']:
647
+ if key != 'last':
648
+ raw_candles = data['result'][key]
649
+ market_state['ohlc_history'] = [
650
+ {
651
+ 'time': int(c[0]),
652
+ 'open': float(c[1]),
653
+ 'high': float(c[2]),
654
+ 'low': float(c[3]),
655
+ 'close': float(c[4])
656
+ }
657
+ for c in raw_candles[-120:]
658
+ ]
659
+ break
660
+ except Exception as e:
661
+ logging.error(f"History fetch failed: {e}")
662
+
663
+ while True:
664
+ try:
665
+ async with websockets.connect("wss://ws.kraken.com/v2") as ws:
666
+ logging.info(f"🔌 Connected to Kraken ({SYMBOL_KRAKEN})")
667
+
668
+ await ws.send(json.dumps({
669
+ "method": "subscribe",
670
+ "params": {"channel": "book", "symbol": [SYMBOL_KRAKEN], "depth": 500}
671
+ }))
672
+ await ws.send(json.dumps({
673
+ "method": "subscribe",
674
+ "params": {"channel": "trade", "symbol": [SYMBOL_KRAKEN]}
675
+ }))
676
+ await ws.send(json.dumps({
677
+ "method": "subscribe",
678
+ "params": {"channel": "ohlc", "symbol": [SYMBOL_KRAKEN], "interval": 1}
679
+ }))
680
+
681
+ async for message in ws:
682
+ payload = json.loads(message)
683
+ channel = payload.get("channel")
684
+ data = payload.get("data", [])
685
+
686
+ if channel == "book":
687
+ for item in data:
688
+ for bid in item.get('bids', []):
689
+ q, p = float(bid['qty']), float(bid['price'])
690
+ if q == 0: market_state['bids'].pop(p, None)
691
+ else: market_state['bids'][p] = q
692
+ for ask in item.get('asks', []):
693
+ q, p = float(ask['qty']), float(ask['price'])
694
+ if q == 0: market_state['asks'].pop(p, None)
695
+ else: market_state['asks'][p] = q
696
+
697
+ if market_state['bids'] and market_state['asks']:
698
+ best_bid = max(market_state['bids'].keys())
699
+ best_ask = min(market_state['asks'].keys())
700
+ mid = (best_bid + best_ask) / 2
701
+ market_state['prev_mid'] = market_state['current_mid']
702
+ market_state['current_mid'] = mid
703
+ market_state['ready'] = True
704
+
705
+ now = time.time()
706
+ if not market_state['history'] or (now - market_state['history'][-1]['t'] > 0.5):
707
+ market_state['history'].append({'t': now, 'p': mid})
708
+ if len(market_state['history']) > HISTORY_LENGTH:
709
+ market_state['history'].pop(0)
710
+
711
+ elif channel == "trade":
712
+ for trade in data:
713
+ try:
714
+ qty = float(trade['qty'])
715
+ price = float(trade['price'])
716
+ side = trade['side']
717
+
718
+ if side == 'buy': market_state['current_vol_window']['buy'] += qty
719
+ else: market_state['current_vol_window']['sell'] += qty
720
+
721
+ current_minute_start = int(time.time()) // 60 * 60
722
+
723
+ if market_state['ohlc_history']:
724
+ last_candle = market_state['ohlc_history'][-1]
725
+
726
+ if last_candle['time'] == current_minute_start:
727
+ last_candle['close'] = price
728
+ if price > last_candle['high']: last_candle['high'] = price
729
+ if price < last_candle['low']: last_candle['low'] = price
730
+
731
+ elif current_minute_start > last_candle['time']:
732
+ new_candle = {
733
+ 'time': current_minute_start,
734
+ 'open': price,
735
+ 'high': price,
736
+ 'low': price,
737
+ 'close': price
738
+ }
739
+ market_state['ohlc_history'].append(new_candle)
740
+ if len(market_state['ohlc_history']) > 200:
741
+ market_state['ohlc_history'].pop(0)
742
+ except: pass
743
+
744
+ elif channel == "ohlc":
745
+ for candle in data:
746
+ try:
747
+ start_time = int(float(candle['endtime'])) - 60
748
+ c_data = {
749
+ 'time': start_time,
750
+ 'open': float(candle['open']),
751
+ 'high': float(candle['high']),
752
+ 'low': float(candle['low']),
753
+ 'close': float(candle['close'])
754
+ }
755
+
756
+ if market_state['ohlc_history']:
757
+ if market_state['ohlc_history'][-1]['time'] == start_time:
758
+ market_state['ohlc_history'][-1] = c_data
759
+ elif market_state['ohlc_history'][-1]['time'] < start_time:
760
+ market_state['ohlc_history'].append(c_data)
761
+ if len(market_state['ohlc_history']) > 200:
762
+ market_state['ohlc_history'].pop(0)
763
+ except Exception as e:
764
+ pass
765
+
766
+ except Exception as e:
767
+ logging.warning(f"⚠️ Reconnecting: {e}")
768
+ await asyncio.sleep(3)
769
+
770
+ async def broadcast_worker():
771
+ while True:
772
+ if connected_clients and market_state['ready']:
773
+ payload = process_market_data()
774
+ msg = json.dumps(payload)
775
+ for ws in list(connected_clients):
776
+ try: await ws.send_str(msg)
777
+ except: pass
778
+ await asyncio.sleep(BROADCAST_RATE)
779
+
780
+ async def websocket_handler(request):
781
+ ws = web.WebSocketResponse()
782
+ await ws.prepare(request)
783
+ connected_clients.add(ws)
784
+ try:
785
+ async for msg in ws:
786
+ pass
787
+ finally:
788
+ connected_clients.remove(ws)
789
+ return ws
790
+
791
+ async def handle_index(request):
792
+ return web.Response(text=HTML_PAGE, content_type='text/html')
793
+
794
+ async def start_background(app):
795
+ app['kraken_task'] = asyncio.create_task(kraken_worker())
796
+ app['broadcast_task'] = asyncio.create_task(broadcast_worker())
797
+
798
+ async def cleanup_background(app):
799
+ app['kraken_task'].cancel()
800
+ app['broadcast_task'].cancel()
801
+ try: await app['kraken_task']; await app['broadcast_task']
802
+ except: pass
803
+
804
+ async def main():
805
+ app = web.Application()
806
+ app.router.add_get('/', handle_index)
807
+ app.router.add_get('/ws', websocket_handler)
808
+ app.on_startup.append(start_background)
809
+ app.on_cleanup.append(cleanup_background)
810
+ runner = web.AppRunner(app)
811
+ await runner.setup()
812
+ site = web.TCPSite(runner, '0.0.0.0', PORT)
813
+ await site.start()
814
+ print(f"🚀 Quant Dashboard: http://localhost:{PORT}")
815
+ await asyncio.Event().wait()
816
+
817
+ if __name__ == "__main__":
818
+ try: asyncio.run(main())
819
+ except KeyboardInterrupt: pass