Alvin3y1 commited on
Commit
74c1714
·
verified ·
1 Parent(s): 0eee686

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +158 -131
app.py CHANGED
@@ -3,6 +3,7 @@ import json
3
  import logging
4
  import time
5
  import bisect
 
6
  from aiohttp import web
7
  import websockets
8
 
@@ -20,99 +21,168 @@ market_state = {
20
  "asks": {},
21
  "history": [],
22
  "current_mid": 0.0,
23
- "ready": False
 
 
24
  }
25
 
26
- # --- HTML Frontend (Plotly.js) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  HTML_PAGE = f"""
28
  <!DOCTYPE html>
29
  <html>
30
  <head>
31
- <title>BTC-USD 6-Layer Deep Dive</title>
32
  <script src="https://cdn.plot.ly/plotly-2.24.1.min.js"></script>
33
  <style>
34
- body {{ margin: 0; padding: 0; background-color: #0e0e0e; color: #ccc; font-family: sans-serif; overflow: hidden; }}
35
 
36
- /* Layout Grid - 3 Rows x 2 Columns */
37
  #container {{ display: flex; flex-direction: column; height: 100vh; width: 100vw; }}
38
 
39
- /* Rows */
40
- .row {{ flex: 1; display: flex; width: 100%; border-bottom: 1px solid #333; }}
 
 
 
 
41
 
42
- /* Columns */
43
- .col {{ width: 50%; height: 100%; border-right: 1px solid #333; position: relative; }}
44
- .col:last-child {{ border-right: none; }}
 
 
 
 
 
 
 
 
 
 
45
 
46
  /* Charts fill their containers */
47
  .chart {{ width: 100%; height: 100%; }}
48
 
49
- /* Status Badge */
50
- #status {{ position: absolute; top: 10px; left: 60px; z-index: 100; font-size: 14px; background: rgba(0,0,0,0.7); padding: 5px 10px; border-radius: 4px; pointer-events: none; border: 1px solid #333; }}
51
  .green {{ color: #00e676; }}
52
  .red {{ color: #ff1744; }}
53
  </style>
54
  </head>
55
  <body>
56
- <div id="status">Connecting...</div>
57
 
58
  <div id="container">
59
- <!-- ROW 1 -->
60
- <div class="row">
61
- <div class="col">
62
  <div id="price-chart" class="chart"></div>
63
  </div>
64
- <div class="col">
65
- <div id="imb-percent-chart" class="chart"></div>
66
- </div>
67
- </div>
68
-
69
- <!-- ROW 2 -->
70
- <div class="row">
71
- <div class="col">
72
  <div id="vol-chart" class="chart"></div>
73
  </div>
74
- <div class="col">
75
- <div id="diff-chart" class="chart"></div>
76
- </div>
77
  </div>
78
 
79
- <!-- ROW 3 (New) -->
80
- <div class="row">
81
- <div class="col">
82
- <!-- Integral of Net Diff -->
83
- <div id="int-diff-chart" class="chart"></div>
84
- </div>
85
- <div class="col">
86
- <!-- Avg Integral (Integral / Steps) -->
87
- <div id="avg-int-chart" class="chart"></div>
88
- </div>
89
  </div>
90
  </div>
91
 
92
  <script>
93
  const priceDiv = document.getElementById('price-chart');
94
- const imbPercentDiv = document.getElementById('imb-percent-chart');
95
  const volDiv = document.getElementById('vol-chart');
96
- const diffDiv = document.getElementById('diff-chart');
97
- const intDiffDiv = document.getElementById('int-diff-chart');
98
- const avgIntDiv = document.getElementById('avg-int-chart');
99
-
100
  const statusDiv = document.getElementById('status');
101
 
102
- let initPrice = false, initImbP = false, initVol = false;
103
- let initDiff = false, initIntDiff = false, initAvgInt = false;
104
 
105
  const commonConfig = {{ responsive: true, displayModeBar: false }};
106
  const commonLayout = {{
107
  paper_bgcolor: '#0e0e0e',
108
  plot_bgcolor: '#0e0e0e',
109
- font: {{ color: '#aaa' }},
110
  margin: {{ t: 30, b: 25, l: 40, r: 20 }},
111
  showlegend: false,
112
  xaxis: {{ gridcolor: '#222' }},
113
  yaxis: {{ gridcolor: '#222' }}
114
  }};
115
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  async function updateCharts() {{
117
  try {{
118
  const res = await fetch('/data');
@@ -123,50 +193,29 @@ HTML_PAGE = f"""
123
  return;
124
  }}
125
 
126
- statusDiv.innerHTML = `Mid: <span class="${{data.mid >= data.prev_mid ? 'green' : 'red'}}">$${{data.mid.toLocaleString(undefined, {{minimumFractionDigits: 2}})}}</span> | Avg Bias: ${{data.avg_int.last_val ? data.avg_int.last_val.toFixed(2) : 0}}`;
127
-
128
- // 1. PRICE HISTORY
129
- if (!initPrice) {{
130
- Plotly.newPlot(priceDiv, [{{ x: data.history.map(d=>new Date(d.t*1000)), y: data.history.map(d=>d.p), type: 'scatter', mode:'lines', line: {{color: '#29b6f6', width: 2}} }}],
131
- {{ ...commonLayout, title: '<b>Midprice</b>', xaxis: {{type:'date', gridcolor:'#222'}} }}, commonConfig);
132
- initPrice = true;
133
- }} else {{
134
- Plotly.react(priceDiv, [{{ x: data.history.map(d=>new Date(d.t*1000)), y: data.history.map(d=>d.p), type: 'scatter', mode:'lines', line: {{color: '#29b6f6', width: 2}} }}],
135
- {{ ...commonLayout, title: '<b>Midprice</b>', xaxis: {{type:'date', gridcolor:'#222'}} }}, commonConfig);
136
- }}
137
 
138
- // 2. IMBALANCE %
139
- const layoutImbP = {{ ...commonLayout, title: '<b>Pressure % (-100 to 100)</b>', yaxis: {{range: [-105, 105], gridcolor:'#222'}} }};
140
- if (!initImbP) {{ Plotly.newPlot(imbPercentDiv, [{{ x: data.imbalance_pct.x, y: data.imbalance_pct.y, type: 'scatter', mode: 'lines', fill: 'tozeroy', line: {{color: '#ffeb3b'}} }}], layoutImbP, commonConfig); initImbP = true; }}
141
- else {{ Plotly.react(imbPercentDiv, [{{ x: data.imbalance_pct.x, y: data.imbalance_pct.y, type: 'scatter', mode: 'lines', fill: 'tozeroy', line: {{color: '#ffeb3b'}} }}], layoutImbP, commonConfig); }}
142
 
143
- // 3. VOLUME BY DISTANCE
144
  const tracesVol = [
145
- {{ x: data.imbalance_vol.dist_bids, y: data.imbalance_vol.vol_bids, type: 'scatter', name: 'Bid', line: {{color: '#00e676'}} }},
146
- {{ x: data.imbalance_vol.dist_asks, y: data.imbalance_vol.vol_asks, type: 'scatter', name: 'Ask', line: {{color: '#ff1744'}} }}
147
  ];
148
- if (!initVol) {{ Plotly.newPlot(volDiv, tracesVol, {{ ...commonLayout, title: '<b>Cumulative Volume</b>' }}, commonConfig); initVol = true; }}
149
- else {{ Plotly.react(volDiv, tracesVol, {{ ...commonLayout, title: '<b>Cumulative Volume</b>' }}, commonConfig); }}
150
-
151
- // 4. NET DIFFERENCE (Bids - Asks)
152
- const traceDiff = {{ x: data.diff.x, y: data.diff.y, type: 'scatter', mode: 'lines', fill: 'tozeroy', line: {{color: '#e040fb'}} }};
153
- if (!initDiff) {{ Plotly.newPlot(diffDiv, [traceDiff], {{ ...commonLayout, title: '<b>Net Liquidity (Bids - Asks)</b>' }}, commonConfig); initDiff = true; }}
154
- else {{ Plotly.react(diffDiv, [traceDiff], {{ ...commonLayout, title: '<b>Net Liquidity (Bids - Asks)</b>' }}, commonConfig); }}
155
 
156
- // 5. INTEGRAL OF NET DIFFERENCE
157
- const traceIntDiff = {{ x: data.int_diff.x, y: data.int_diff.y, type: 'scatter', mode: 'lines', fill: 'tozeroy', line: {{color: '#00bcd4'}} }};
158
- if (!initIntDiff) {{ Plotly.newPlot(intDiffDiv, [traceIntDiff], {{ ...commonLayout, title: '<b>Integrated Net Liquidity (Sum of Diff)</b>' }}, commonConfig); initIntDiff = true; }}
159
- else {{ Plotly.react(intDiffDiv, [traceIntDiff], {{ ...commonLayout, title: '<b>Integrated Net Liquidity (Sum of Diff)</b>' }}, commonConfig); }}
160
-
161
- // 6. AVERAGE INTEGRATED DIFFERENCE (Integral / Steps)
162
- const traceAvgInt = {{ x: data.avg_int.x, y: data.avg_int.y, type: 'scatter', mode: 'lines', fill: 'tozeroy', line: {{color: '#ff9800'}} }};
163
- if (!initAvgInt) {{ Plotly.newPlot(avgIntDiv, [traceAvgInt], {{ ...commonLayout, title: '<b>Avg Integrated Liq (Int / Steps)</b>' }}, commonConfig); initAvgInt = true; }}
164
- else {{ Plotly.react(avgIntDiv, [traceAvgInt], {{ ...commonLayout, title: '<b>Avg Integrated Liq (Int / Steps)</b>' }}, commonConfig); }}
165
 
166
  }} catch (e) {{ console.error("Fetch error:", e); }}
167
  }}
168
 
169
- setInterval(updateCharts, 500);
170
  </script>
171
  </body>
172
  </html>
@@ -202,6 +251,7 @@ async def kraken_worker():
202
  if market_state['bids'] and market_state['asks']:
203
  best_bid = max(market_state['bids'].keys())
204
  best_ask = min(market_state['asks'].keys())
 
205
  mid = (best_bid + best_ask) / 2
206
  market_state['current_mid'] = mid
207
  market_state['ready'] = True
@@ -225,73 +275,50 @@ async def handle_data(request):
225
 
226
  mid = market_state['current_mid']
227
 
228
- # --- Raw Data ---
229
  raw_bids = sorted(market_state['bids'].items(), key=lambda x: -x[0])[:300]
230
  raw_asks = sorted(market_state['asks'].items(), key=lambda x: x[0])[:300]
231
 
232
- # --- Distance Data ---
233
- dist_bids = [(mid - p, q) for p, q in raw_bids if mid - p >= 0]
234
- d_b_x, d_b_y = [], []
235
- cum_d = 0
236
- for d, q in dist_bids:
237
- cum_d += q
238
- d_b_x.append(d); d_b_y.append(cum_d)
239
-
240
- dist_asks = [(p - mid, q) for p, q in raw_asks if p - mid >= 0]
241
- d_a_x, d_a_y = [], []
242
- cum_d = 0
243
- for d, q in dist_asks:
244
- cum_d += q
245
- d_a_x.append(d); d_a_y.append(cum_d)
246
-
247
- # --- Calculations ---
248
- imb_x, imb_y = [], []
249
- diff_x, diff_y = [], []
250
- int_diff_x, int_diff_y = [], []
251
- avg_int_x, avg_int_y = [], []
252
 
253
- running_diff_sum = 0
254
-
255
  if d_b_x and d_a_x:
256
  max_dist = min(d_b_x[-1], d_a_x[-1])
257
- step_size = max_dist / 100
258
- steps = [i * step_size for i in range(1, 101)]
259
 
260
- for i, s in enumerate(steps):
261
- # 1. Get Volumes
262
  idx_b = bisect.bisect_right(d_b_x, s)
263
  vol_b = d_b_y[idx_b-1] if idx_b > 0 else 0
264
 
265
  idx_a = bisect.bisect_right(d_a_x, s)
266
  vol_a = d_a_y[idx_a-1] if idx_a > 0 else 0
267
 
268
- total_vol = vol_b + vol_a
269
-
270
- # 2. Imbalance %
271
- pct = ((vol_b - vol_a) / total_vol) * 100 if total_vol > 0 else 0
272
- imb_x.append(s); imb_y.append(pct)
273
-
274
- # 3. Net Difference (Bids - Asks)
275
- diff = vol_b - vol_a
276
- diff_x.append(s); diff_y.append(diff)
277
-
278
- # 4. Integral of Net Difference
279
- running_diff_sum += diff
280
- int_diff_x.append(s); int_diff_y.append(running_diff_sum)
281
 
282
- # 5. Average Integrated Difference (Int / Steps)
283
- step_count = i + 1
284
- avg_int = running_diff_sum / step_count
285
- avg_int_x.append(s); avg_int_y.append(avg_int)
286
 
287
  return web.json_response({
288
  "mid": mid,
289
- "prev_mid": market_state['history'][-2]['p'] if len(market_state['history']) > 1 else mid,
290
- "imbalance_vol": { "dist_bids": d_b_x, "vol_bids": d_b_y, "dist_asks": d_a_x, "vol_asks": d_a_y },
291
- "imbalance_pct": { "x": imb_x, "y": imb_y },
292
- "diff": { "x": diff_x, "y": diff_y },
293
- "int_diff": { "x": int_diff_x, "y": int_diff_y },
294
- "avg_int": { "x": avg_int_x, "y": avg_int_y, "last_val": avg_int_y[-1] if avg_int_y else 0 },
295
  "history": market_state['history']
296
  })
297
 
@@ -315,7 +342,7 @@ async def main():
315
  site = web.TCPSite(runner, '0.0.0.0', PORT)
316
  await site.start()
317
 
318
- print(f"🚀 BTC-USD 6-Layer Dashboard: http://localhost:{PORT}")
319
  await asyncio.Event().wait()
320
 
321
  if __name__ == "__main__":
 
3
  import logging
4
  import time
5
  import bisect
6
+ import random
7
  from aiohttp import web
8
  import websockets
9
 
 
21
  "asks": {},
22
  "history": [],
23
  "current_mid": 0.0,
24
+ "prev_mid": 0.0,
25
+ "ready": False,
26
+ "last_comment_time": 0
27
  }
28
 
29
+ # --- AI Logic Helper ---
30
+ def generate_ai_commentary(diff_y, mid, prev_mid):
31
+ """
32
+ Analyzes the Net Liquidity (diff_y) and Price Action to generate commentary.
33
+ diff_y is a list of (BidVol - AskVol) at increasing distances.
34
+ """
35
+ if not diff_y:
36
+ return {"text": "Initializing analysis...", "sentiment": "neutral"}
37
+
38
+ # 1. Calculate Aggregates
39
+ net_total = diff_y[-1] # Total Net Liquidity at max depth
40
+ avg_liquidity = sum(diff_y) / len(diff_y)
41
+
42
+ # 2. Price Trend
43
+ price_delta = mid - prev_mid
44
+
45
+ # 3. Logic Engine
46
+ msg = ""
47
+ sentiment = "neutral"
48
+
49
+ # -- SCENARIO 1: STRONG DIRECTIONAL --
50
+ if net_total > 50:
51
+ sentiment = "bullish"
52
+ msg = f"🚀 <b>STRONG BUY SUPPORT:</b> Net surplus of {int(net_total)} BTC. Orderbook is heavily tilted towards Bids."
53
+ elif net_total < -50:
54
+ sentiment = "bearish"
55
+ msg = f"📉 <b>HEAVY SELL PRESSURE:</b> Net deficit of {int(net_total)} BTC. Sellers are dominating the book."
56
+
57
+ # -- SCENARIO 2: ABSORPTION / DIVERGENCE --
58
+ # Price dropping, but Orderbook is Bullish (Bids absorbing sells)
59
+ elif price_delta < 0 and net_total > 20:
60
+ sentiment = "warning"
61
+ msg = f"🛡️ <b>ABSORPTION DETECTED:</b> Price is falling, but Bid depth is increasing (+{int(net_total)} BTC). Passive buyers are catching the dump."
62
+
63
+ # Price rising, but Orderbook is Bearish (Asks absorbing buys)
64
+ elif price_delta > 0 and net_total < -20:
65
+ sentiment = "warning"
66
+ msg = f"🧱 <b>HIDDEN WALL:</b> Price is rising into heavy Sell liquidity ({int(net_total)} BTC diff). Breakout might fail."
67
+
68
+ # -- SCENARIO 3: EQUILIBRIUM --
69
+ elif abs(net_total) < 10:
70
+ sentiment = "neutral"
71
+ msg = f"⚖️ <b>EQUILIBRIUM:</b> Bids and Asks are perfectly balanced. Expect low volatility or a sudden breakout."
72
+
73
+ # -- SCENARIO 4: MOMENTUM --
74
+ else:
75
+ if net_total > 0:
76
+ msg = f"📈 <b>Bullish Bias:</b> Moderate buy support (+{int(net_total)} BTC). Path of least resistance is UP."
77
+ sentiment = "bullish"
78
+ else:
79
+ msg = f"📉 <b>Bearish Bias:</b> Moderate sell overhang ({int(net_total)} BTC). Path of least resistance is DOWN."
80
+
81
+ return {"text": msg, "sentiment": sentiment, "net": net_total}
82
+
83
+ # --- HTML Frontend ---
84
  HTML_PAGE = f"""
85
  <!DOCTYPE html>
86
  <html>
87
  <head>
88
+ <title>BTC-USD AI Analyst</title>
89
  <script src="https://cdn.plot.ly/plotly-2.24.1.min.js"></script>
90
  <style>
91
+ body {{ margin: 0; padding: 0; background-color: #0e0e0e; color: #ccc; font-family: 'Courier New', monospace; overflow: hidden; }}
92
 
93
+ /* Layout Grid */
94
  #container {{ display: flex; flex-direction: column; height: 100vh; width: 100vw; }}
95
 
96
+ /* Row 1: Charts (60% Height) */
97
+ #row-charts {{ flex: 6; display: flex; width: 100%; border-bottom: 2px solid #333; }}
98
+ .col-chart {{ width: 50%; height: 100%; border-right: 1px solid #333; }}
99
+
100
+ /* Row 2: AI Terminal (40% Height) */
101
+ #row-terminal {{ flex: 4; display: flex; flex-direction: column; background-color: #050505; padding: 10px; overflow-y: auto; }}
102
 
103
+ /* Terminal Styling */
104
+ .terminal-header {{ color: #00bcd4; font-weight: bold; border-bottom: 1px dashed #333; padding-bottom: 5px; margin-bottom: 10px; }}
105
+ .log-entry {{ margin-bottom: 6px; font-size: 14px; line-height: 1.4; border-left: 3px solid transparent; padding-left: 8px; }}
106
+ .log-time {{ color: #666; font-size: 12px; margin-right: 10px; }}
107
+
108
+ /* Sentiment Colors */
109
+ .bullish {{ border-left-color: #00e676; color: #e8f5e9; }}
110
+ .bearish {{ border-left-color: #ff1744; color: #ffebee; }}
111
+ .neutral {{ border-left-color: #999; color: #ccc; }}
112
+ .warning {{ border-left-color: #ff9800; color: #fff3e0; }}
113
+
114
+ /* Highlight classes for inner HTML */
115
+ b {{ font-weight: bold; }}
116
 
117
  /* Charts fill their containers */
118
  .chart {{ width: 100%; height: 100%; }}
119
 
120
+ #status {{ position: absolute; top: 10px; left: 60px; z-index: 100; font-size: 14px; background: rgba(0,0,0,0.8); padding: 5px 10px; border-radius: 4px; border: 1px solid #333; }}
 
121
  .green {{ color: #00e676; }}
122
  .red {{ color: #ff1744; }}
123
  </style>
124
  </head>
125
  <body>
126
+ <div id="status">Connecting to Neural Net...</div>
127
 
128
  <div id="container">
129
+ <!-- ROW 1: CONTEXT CHARTS -->
130
+ <div id="row-charts">
131
+ <div class="col-chart">
132
  <div id="price-chart" class="chart"></div>
133
  </div>
134
+ <div class="col-chart">
 
 
 
 
 
 
 
135
  <div id="vol-chart" class="chart"></div>
136
  </div>
 
 
 
137
  </div>
138
 
139
+ <!-- ROW 2: AI COMMENTATOR -->
140
+ <div id="row-terminal">
141
+ <div class="terminal-header">> AI MARKET ANALYST (Based on Net Liquidity)</div>
142
+ <div id="terminal-logs"></div>
 
 
 
 
 
 
143
  </div>
144
  </div>
145
 
146
  <script>
147
  const priceDiv = document.getElementById('price-chart');
 
148
  const volDiv = document.getElementById('vol-chart');
149
+ const termLogs = document.getElementById('terminal-logs');
 
 
 
150
  const statusDiv = document.getElementById('status');
151
 
152
+ let initPrice = false, initVol = false;
153
+ let lastLogText = "";
154
 
155
  const commonConfig = {{ responsive: true, displayModeBar: false }};
156
  const commonLayout = {{
157
  paper_bgcolor: '#0e0e0e',
158
  plot_bgcolor: '#0e0e0e',
159
+ font: {{ color: '#aaa', family: 'Courier New' }},
160
  margin: {{ t: 30, b: 25, l: 40, r: 20 }},
161
  showlegend: false,
162
  xaxis: {{ gridcolor: '#222' }},
163
  yaxis: {{ gridcolor: '#222' }}
164
  }};
165
 
166
+ function addLog(data) {{
167
+ // Prevent spamming the exact same message
168
+ if (data.comment.text === lastLogText) return;
169
+ lastLogText = data.comment.text;
170
+
171
+ const div = document.createElement('div');
172
+ div.className = `log-entry ${{data.comment.sentiment}}`;
173
+
174
+ const timeStr = new Date().toLocaleTimeString();
175
+ div.innerHTML = `<span class="log-time">[${{timeStr}}]</span> ${{data.comment.text}}`;
176
+
177
+ // Insert at top
178
+ termLogs.prepend(div);
179
+
180
+ // Keep max 20 logs
181
+ if (termLogs.children.length > 20) {{
182
+ termLogs.removeChild(termLogs.lastChild);
183
+ }}
184
+ }}
185
+
186
  async function updateCharts() {{
187
  try {{
188
  const res = await fetch('/data');
 
193
  return;
194
  }}
195
 
196
+ // Update Status
197
+ statusDiv.innerHTML = `Mid: <span class="${{data.mid >= data.prev_mid ? 'green' : 'red'}}">$${{data.mid.toLocaleString(undefined, {{minimumFractionDigits: 2}})}}</span> | Net Liq: ${{data.comment.net.toFixed(2)}} BTC`;
 
 
 
 
 
 
 
 
 
198
 
199
+ // 1. PRICE CHART
200
+ const tracePrice = {{ x: data.history.map(d=>new Date(d.t*1000)), y: data.history.map(d=>d.p), type: 'scatter', mode:'lines', line: {{color: '#29b6f6', width: 2}} }};
201
+ if (!initPrice) {{ Plotly.newPlot(priceDiv, [tracePrice], {{ ...commonLayout, title: '<b>Midprice</b>', xaxis: {{type:'date', gridcolor:'#222'}} }}, commonConfig); initPrice = true; }}
202
+ else {{ Plotly.react(priceDiv, [tracePrice], {{ ...commonLayout, title: '<b>Midprice</b>', xaxis: {{type:'date', gridcolor:'#222'}} }}, commonConfig); }}
203
 
204
+ // 2. VOLUME CHART
205
  const tracesVol = [
206
+ {{ x: data.vol.dist_bids, y: data.vol.vol_bids, type: 'scatter', name: 'Bids', line: {{color: '#00e676'}} }},
207
+ {{ x: data.vol.dist_asks, y: data.vol.vol_asks, type: 'scatter', name: 'Asks', line: {{color: '#ff1744'}} }}
208
  ];
209
+ if (!initVol) {{ Plotly.newPlot(volDiv, tracesVol, {{ ...commonLayout, title: '<b>Cumulative Volume by Distance</b>', xaxis: {{title:'Distance ($)'}} }}, commonConfig); initVol = true; }}
210
+ else {{ Plotly.react(volDiv, tracesVol, {{ ...commonLayout, title: '<b>Cumulative Volume by Distance</b>', xaxis: {{title:'Distance ($)'}} }}, commonConfig); }}
 
 
 
 
 
211
 
212
+ // 3. AI COMMENTARY LOG
213
+ addLog(data);
 
 
 
 
 
 
 
214
 
215
  }} catch (e) {{ console.error("Fetch error:", e); }}
216
  }}
217
 
218
+ setInterval(updateCharts, 750); // Slower update for readability
219
  </script>
220
  </body>
221
  </html>
 
251
  if market_state['bids'] and market_state['asks']:
252
  best_bid = max(market_state['bids'].keys())
253
  best_ask = min(market_state['asks'].keys())
254
+ market_state['prev_mid'] = market_state['current_mid']
255
  mid = (best_bid + best_ask) / 2
256
  market_state['current_mid'] = mid
257
  market_state['ready'] = True
 
275
 
276
  mid = market_state['current_mid']
277
 
278
+ # --- Prepare Data ---
279
  raw_bids = sorted(market_state['bids'].items(), key=lambda x: -x[0])[:300]
280
  raw_asks = sorted(market_state['asks'].items(), key=lambda x: x[0])[:300]
281
 
282
+ # Calculate Distances & Cum Volumes
283
+ d_b_x, d_b_y, cum = [], [], 0
284
+ for p, q in raw_bids:
285
+ d = mid - p
286
+ if d >= 0:
287
+ cum += q
288
+ d_b_x.append(d); d_b_y.append(cum)
289
+
290
+ d_a_x, d_a_y, cum = [], [], 0
291
+ for p, q in raw_asks:
292
+ d = p - mid
293
+ if d >= 0:
294
+ cum += q
295
+ d_a_x.append(d); d_a_y.append(cum)
296
+
297
+ # --- Calculate Net Liquidity Array for AI ---
298
+ diff_values = []
 
 
 
299
 
 
 
300
  if d_b_x and d_a_x:
301
  max_dist = min(d_b_x[-1], d_a_x[-1])
302
+ step_size = max_dist / 50 # 50 Sampling points for AI
303
+ steps = [i * step_size for i in range(1, 51)]
304
 
305
+ for s in steps:
 
306
  idx_b = bisect.bisect_right(d_b_x, s)
307
  vol_b = d_b_y[idx_b-1] if idx_b > 0 else 0
308
 
309
  idx_a = bisect.bisect_right(d_a_x, s)
310
  vol_a = d_a_y[idx_a-1] if idx_a > 0 else 0
311
 
312
+ diff_values.append(vol_b - vol_a)
 
 
 
 
 
 
 
 
 
 
 
 
313
 
314
+ # --- Generate AI Commentary ---
315
+ ai_output = generate_ai_commentary(diff_values, mid, market_state['prev_mid'])
 
 
316
 
317
  return web.json_response({
318
  "mid": mid,
319
+ "prev_mid": market_state['prev_mid'],
320
+ "vol": { "dist_bids": d_b_x, "vol_bids": d_b_y, "dist_asks": d_a_x, "vol_asks": d_a_y },
321
+ "comment": ai_output,
 
 
 
322
  "history": market_state['history']
323
  })
324
 
 
342
  site = web.TCPSite(runner, '0.0.0.0', PORT)
343
  await site.start()
344
 
345
+ print(f"🚀 BTC-USD AI Dashboard: http://localhost:{PORT}")
346
  await asyncio.Event().wait()
347
 
348
  if __name__ == "__main__":