Alvin3y1 commited on
Commit
d6d83d7
·
verified ·
1 Parent(s): 78b3589

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +107 -14
app.py CHANGED
@@ -96,6 +96,77 @@ def calculate_micro_price_structure(diff_x, diff_y_net, current_mid, best_bid, b
96
  "rho": rho
97
  }
98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  def process_market_data():
100
  if not market_state['ready']: return {"error": "Initializing..."}
101
 
@@ -160,6 +231,9 @@ def process_market_data():
160
  diff_x, diff_y_net, mid, best_bid, best_ask,
161
  {"bids": bid_walls, "asks": ask_walls}
162
  )
 
 
 
163
 
164
  if analysis:
165
  if not market_state['pred_history'] or (now - market_state['pred_history'][-1]['t'] > 0.5):
@@ -171,6 +245,7 @@ def process_market_data():
171
  "mid": mid,
172
  "history": market_state['history'],
173
  "pred_history": market_state['pred_history'],
 
174
  "trade_history": market_state['trade_vol_history'],
175
  "ohlc": market_state['ohlc_history'],
176
  "depth_x": diff_x,
@@ -200,6 +275,7 @@ HTML_PAGE = f"""
200
  --red: #ff3b3b;
201
  --blue: #2979ff;
202
  --yellow: #ffeb3b;
 
203
  }}
204
  body {{
205
  margin: 0; padding: 0;
@@ -284,6 +360,7 @@ HTML_PAGE = f"""
284
  .c-green {{ color: var(--green); }}
285
  .c-red {{ color: var(--red); }}
286
  .c-dim {{ color: var(--text-dim); }}
 
287
 
288
  .list-container {{ display: flex; flex-direction: column; gap: 8px; overflow-y: auto; height: 100px; }}
289
  .list-item {{
@@ -322,7 +399,9 @@ HTML_PAGE = f"""
322
  </div>
323
 
324
  <div id="p-chart" class="panel">
325
- <div class="chart-header">PRICE ACTION (BLUE) // PREDICTION (YELLOW)</div>
 
 
326
  <div id="tv-price" style="flex: 1; width: 100%;"></div>
327
  </div>
328
 
@@ -400,7 +479,12 @@ HTML_PAGE = f"""
400
 
401
  const priceChart = LightweightCharts.createChart(document.getElementById('tv-price'), chartOpts);
402
  const priceSeries = priceChart.addLineSeries({{ color: '#2979ff', lineWidth: 2, title: 'Price' }});
403
- const predSeries = priceChart.addLineSeries({{ color: '#ffeb3b', lineWidth: 2, lineStyle: 2, title: 'Forecast' }});
 
 
 
 
 
404
 
405
  const candleChart = LightweightCharts.createChart(document.getElementById('tv-candles'), {{
406
  ...chartOpts,
@@ -475,26 +559,38 @@ HTML_PAGE = f"""
475
  priceSeries.setData(cleanHist);
476
 
477
  const lastP = cleanHist[cleanHist.length-1].value;
 
478
  dom.ticker.innerText = lastP.toLocaleString('en-US', {{ minimumFractionDigits: 2 }});
479
 
 
480
  if (data.analysis) {{
481
  const proj = data.analysis.projected;
482
  const rho = data.analysis.rho;
483
-
484
  predSeries.setData([
485
  cleanHist[cleanHist.length-1],
486
- {{ time: cleanHist[cleanHist.length-1].time + 60, value: proj }}
487
  ]);
488
-
489
  const pct = ((proj - lastP) / lastP) * 100;
490
  const sign = pct >= 0 ? "+" : "";
491
-
492
  dom.projPct.innerText = `${{sign}}${{pct.toFixed(4)}}%`;
493
  dom.projPct.style.color = pct >= 0 ? "var(--green)" : "var(--red)";
494
  dom.projVal.innerText = proj.toLocaleString('en-US', {{ minimumFractionDigits: 2 }});
495
  dom.score.innerText = rho.toFixed(3);
496
  dom.score.style.color = rho > 0 ? "var(--green)" : (rho < 0 ? "var(--red)" : "var(--text-main)");
497
  }}
 
 
 
 
 
 
 
 
 
 
 
 
498
  }}
499
 
500
  if (data.ohlc && data.ohlc.length) {{
@@ -505,7 +601,6 @@ HTML_PAGE = f"""
505
  low: c.low,
506
  close: c.close
507
  }}));
508
- // Deduplicate candles
509
  const uniqueCandles = [...new Map(candles.map(i => [i.time, i])).values()];
510
  candleSeries.setData(uniqueCandles);
511
  }}
@@ -567,7 +662,7 @@ HTML_PAGE = f"""
567
  async def kraken_worker():
568
  global market_state
569
  try:
570
- # Fetch initial history (Kraken REST API uses start time for candles)
571
  async with aiohttp.ClientSession() as session:
572
  url = "https://api.kraken.com/0/public/OHLC?pair=XBTUSD&interval=1"
573
  async with session.get(url) as response:
@@ -650,19 +745,19 @@ async def kraken_worker():
650
  if side == 'buy': market_state['current_vol_window']['buy'] += qty
651
  else: market_state['current_vol_window']['sell'] += qty
652
 
653
- # LIVE CANDLE UPDATE (Crucial for "actual price")
654
  current_minute_start = int(time.time()) // 60 * 60
655
 
656
  if market_state['ohlc_history']:
657
  last_candle = market_state['ohlc_history'][-1]
658
 
659
- # If still in the same minute, update the current candle
660
  if last_candle['time'] == current_minute_start:
661
  last_candle['close'] = price
662
  if price > last_candle['high']: last_candle['high'] = price
663
  if price < last_candle['low']: last_candle['low'] = price
664
 
665
- # If new minute started, create new candle
666
  elif current_minute_start > last_candle['time']:
667
  new_candle = {
668
  'time': current_minute_start,
@@ -674,13 +769,12 @@ async def kraken_worker():
674
  market_state['ohlc_history'].append(new_candle)
675
  if len(market_state['ohlc_history']) > 200:
676
  market_state['ohlc_history'].pop(0)
677
-
678
  except: pass
679
 
680
  elif channel == "ohlc":
681
  for candle in data:
682
  try:
683
- # Kraken WS sends 'endtime'. We convert to 'starttime' to match REST data and charting lib.
684
  start_time = int(float(candle['endtime'])) - 60
685
  c_data = {
686
  'time': start_time,
@@ -690,7 +784,6 @@ async def kraken_worker():
690
  'close': float(candle['close'])
691
  }
692
 
693
- # Sync with existing history if found
694
  if market_state['ohlc_history']:
695
  if market_state['ohlc_history'][-1]['time'] == start_time:
696
  market_state['ohlc_history'][-1] = c_data
 
96
  "rho": rho
97
  }
98
 
99
+ def calculate_polr(bids, asks, mid):
100
+ """
101
+ Path of Least Resistance:
102
+ Simulates eating volume on both sides.
103
+ Returns the price path where liquidity is thinnest.
104
+ """
105
+ if not bids or not asks: return []
106
+
107
+ sorted_bids = sorted(bids.items(), key=lambda x: -x[0]) # High to Low
108
+ sorted_asks = sorted(asks.items(), key=lambda x: x[0]) # Low to High
109
+
110
+ path_points = []
111
+
112
+ # We simulate volume stepping from 0.1 BTC up to 20 BTC
113
+ # This represents "Time" or "Intensity" on the X-axis of the projection
114
+ volume_steps = [i * 0.5 for i in range(1, 41)] # 0.5, 1.0 ... 20.0
115
+
116
+ current_time = time.time()
117
+
118
+ for i, target_vol in enumerate(volume_steps):
119
+ # 1. Find price cost to eat 'target_vol' UP
120
+ ask_cost_dist = 0
121
+ cum_vol = 0
122
+ target_ask_price = mid
123
+ for p, q in sorted_asks:
124
+ cum_vol += q
125
+ if cum_vol >= target_vol:
126
+ target_ask_price = p
127
+ break
128
+ ask_cost_dist = target_ask_price - mid
129
+
130
+ # 2. Find price cost to eat 'target_vol' DOWN
131
+ bid_cost_dist = 0
132
+ cum_vol = 0
133
+ target_bid_price = mid
134
+ for p, q in sorted_bids:
135
+ cum_vol += q
136
+ if cum_vol >= target_vol:
137
+ target_bid_price = p
138
+ break
139
+ bid_cost_dist = mid - target_bid_price
140
+
141
+ # 3. Compare Resistance
142
+ # If moving up 10$ costs 5 BTC, but moving down 10$ costs 20 BTC,
143
+ # The path of least resistance is UP (it's thinner).
144
+
145
+ # Here we compare the DISTANCE moved for the SAME volume.
146
+ # If for 5 BTC, Price moves UP $10 and DOWN $2.
147
+ # The UP side is "thinner" (less resistance to price change), so price "slips" up.
148
+
149
+ projected_p = mid
150
+
151
+ # Avoid division by zero or tiny spreads
152
+ if bid_cost_dist <= 0: bid_cost_dist = 0.01
153
+ if ask_cost_dist <= 0: ask_cost_dist = 0.01
154
+
155
+ # Logic: Price goes where the book is thinner (Distance is LARGER for same volume)
156
+ if ask_cost_dist > bid_cost_dist:
157
+ # It takes LESS liquidity to move UP (Price moved further for same vol)
158
+ projected_p = target_ask_price
159
+ else:
160
+ projected_p = target_bid_price
161
+
162
+ # Add slight smoothing/dampening so it connects to current price
163
+ path_points.append({
164
+ 't': current_time + (i * 2), # Spread points out every 2 seconds into future
165
+ 'p': projected_p
166
+ })
167
+
168
+ return path_points
169
+
170
  def process_market_data():
171
  if not market_state['ready']: return {"error": "Initializing..."}
172
 
 
231
  diff_x, diff_y_net, mid, best_bid, best_ask,
232
  {"bids": bid_walls, "asks": ask_walls}
233
  )
234
+
235
+ # Calculate Path of Least Resistance
236
+ polr_path = calculate_polr(market_state['bids'], market_state['asks'], mid)
237
 
238
  if analysis:
239
  if not market_state['pred_history'] or (now - market_state['pred_history'][-1]['t'] > 0.5):
 
245
  "mid": mid,
246
  "history": market_state['history'],
247
  "pred_history": market_state['pred_history'],
248
+ "polr": polr_path,
249
  "trade_history": market_state['trade_vol_history'],
250
  "ohlc": market_state['ohlc_history'],
251
  "depth_x": diff_x,
 
275
  --red: #ff3b3b;
276
  --blue: #2979ff;
277
  --yellow: #ffeb3b;
278
+ --purple: #d500f9;
279
  }}
280
  body {{
281
  margin: 0; padding: 0;
 
360
  .c-green {{ color: var(--green); }}
361
  .c-red {{ color: var(--red); }}
362
  .c-dim {{ color: var(--text-dim); }}
363
+ .c-purp {{ color: var(--purple); }}
364
 
365
  .list-container {{ display: flex; flex-direction: column; gap: 8px; overflow-y: auto; height: 100px; }}
366
  .list-item {{
 
399
  </div>
400
 
401
  <div id="p-chart" class="panel">
402
+ <div class="chart-header">
403
+ PRICE (BLUE) // <span class="c-purp">LEAST RESISTANCE (PURPLE)</span> // <span style="color:var(--yellow)">PRED (YELLOW)</span>
404
+ </div>
405
  <div id="tv-price" style="flex: 1; width: 100%;"></div>
406
  </div>
407
 
 
479
 
480
  const priceChart = LightweightCharts.createChart(document.getElementById('tv-price'), chartOpts);
481
  const priceSeries = priceChart.addLineSeries({{ color: '#2979ff', lineWidth: 2, title: 'Price' }});
482
+
483
+ // Yellow Line: Mathematical Micro-Price
484
+ const predSeries = priceChart.addLineSeries({{ color: '#ffeb3b', lineWidth: 2, lineStyle: 2, title: 'Math Forecast' }});
485
+
486
+ // Purple Line: Path of Least Resistance (Lowest Volume)
487
+ const polrSeries = priceChart.addLineSeries({{ color: '#d500f9', lineWidth: 3, lineStyle: 0, title: 'Least Resistance' }});
488
 
489
  const candleChart = LightweightCharts.createChart(document.getElementById('tv-candles'), {{
490
  ...chartOpts,
 
559
  priceSeries.setData(cleanHist);
560
 
561
  const lastP = cleanHist[cleanHist.length-1].value;
562
+ const lastTime = cleanHist[cleanHist.length-1].time;
563
  dom.ticker.innerText = lastP.toLocaleString('en-US', {{ minimumFractionDigits: 2 }});
564
 
565
+ // 1. Yellow Line (Math Forecast)
566
  if (data.analysis) {{
567
  const proj = data.analysis.projected;
568
  const rho = data.analysis.rho;
 
569
  predSeries.setData([
570
  cleanHist[cleanHist.length-1],
571
+ {{ time: lastTime + 60, value: proj }}
572
  ]);
573
+ // Text updates
574
  const pct = ((proj - lastP) / lastP) * 100;
575
  const sign = pct >= 0 ? "+" : "";
 
576
  dom.projPct.innerText = `${{sign}}${{pct.toFixed(4)}}%`;
577
  dom.projPct.style.color = pct >= 0 ? "var(--green)" : "var(--red)";
578
  dom.projVal.innerText = proj.toLocaleString('en-US', {{ minimumFractionDigits: 2 }});
579
  dom.score.innerText = rho.toFixed(3);
580
  dom.score.style.color = rho > 0 ? "var(--green)" : (rho < 0 ? "var(--red)" : "var(--text-main)");
581
  }}
582
+
583
+ // 2. Purple Line (Path of Least Resistance)
584
+ if (data.polr && data.polr.length) {{
585
+ // Attach start of POLR to current price to make it seamless
586
+ const polrData = [
587
+ {{ time: lastTime, value: lastP }},
588
+ ...data.polr.map(d => ({{ time: Math.floor(d.t), value: d.p }}))
589
+ ];
590
+ // Deduplicate times
591
+ const uniquePolr = [...new Map(polrData.map(i => [i.time, i])).values()];
592
+ polrSeries.setData(uniquePolr);
593
+ }}
594
  }}
595
 
596
  if (data.ohlc && data.ohlc.length) {{
 
601
  low: c.low,
602
  close: c.close
603
  }}));
 
604
  const uniqueCandles = [...new Map(candles.map(i => [i.time, i])).values()];
605
  candleSeries.setData(uniqueCandles);
606
  }}
 
662
  async def kraken_worker():
663
  global market_state
664
  try:
665
+ # Fetch initial history
666
  async with aiohttp.ClientSession() as session:
667
  url = "https://api.kraken.com/0/public/OHLC?pair=XBTUSD&interval=1"
668
  async with session.get(url) as response:
 
745
  if side == 'buy': market_state['current_vol_window']['buy'] += qty
746
  else: market_state['current_vol_window']['sell'] += qty
747
 
748
+ # LIVE CANDLE UPDATE
749
  current_minute_start = int(time.time()) // 60 * 60
750
 
751
  if market_state['ohlc_history']:
752
  last_candle = market_state['ohlc_history'][-1]
753
 
754
+ # If still in the same minute
755
  if last_candle['time'] == current_minute_start:
756
  last_candle['close'] = price
757
  if price > last_candle['high']: last_candle['high'] = price
758
  if price < last_candle['low']: last_candle['low'] = price
759
 
760
+ # If new minute started
761
  elif current_minute_start > last_candle['time']:
762
  new_candle = {
763
  'time': current_minute_start,
 
769
  market_state['ohlc_history'].append(new_candle)
770
  if len(market_state['ohlc_history']) > 200:
771
  market_state['ohlc_history'].pop(0)
 
772
  except: pass
773
 
774
  elif channel == "ohlc":
775
  for candle in data:
776
  try:
777
+ # Kraken sends endtime, adjust to starttime
778
  start_time = int(float(candle['endtime'])) - 60
779
  c_data = {
780
  'time': start_time,
 
784
  'close': float(candle['close'])
785
  }
786
 
 
787
  if market_state['ohlc_history']:
788
  if market_state['ohlc_history'][-1]['time'] == start_time:
789
  market_state['ohlc_history'][-1] = c_data