Alvin3y1 commited on
Commit
3f0765d
·
verified ·
1 Parent(s): ac8213b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +67 -126
app.py CHANGED
@@ -28,7 +28,7 @@ HTML_PAGE = f"""
28
  <!DOCTYPE html>
29
  <html>
30
  <head>
31
- <title>BTC-USD Imbalance Dashboard</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; }}
@@ -36,10 +36,10 @@ HTML_PAGE = f"""
36
  /* Layout Grid */
37
  #container {{ display: flex; flex-direction: column; height: 100vh; width: 100vw; }}
38
 
39
- /* Row 1: High Priority (Price & Imbalance %) */
40
  #row-top {{ flex: 1; display: flex; width: 100%; border-bottom: 1px solid #333; }}
41
 
42
- /* Row 2: Secondary Data (Depth & Volume) */
43
  #row-bot {{ flex: 1; display: flex; width: 100%; }}
44
 
45
  /* Columns */
@@ -59,7 +59,7 @@ HTML_PAGE = f"""
59
  <div id="status">Connecting...</div>
60
 
61
  <div id="container">
62
- <!-- TOP ROW: PRICE & IMBALANCE % -->
63
  <div id="row-top">
64
  <div class="col-left">
65
  <div id="price-chart" class="chart"></div>
@@ -69,33 +69,32 @@ HTML_PAGE = f"""
69
  </div>
70
  </div>
71
 
72
- <!-- BOTTOM ROW: DEPTH & VOL -->
73
  <div id="row-bot">
74
  <div class="col-left">
75
- <div id="depth-chart" class="chart"></div>
 
76
  </div>
77
  <div class="col-right">
78
- <div id="vol-chart" class="chart"></div>
 
79
  </div>
80
  </div>
81
  </div>
82
 
83
  <script>
84
- // Chart Targets
85
  const priceDiv = document.getElementById('price-chart');
86
  const imbPercentDiv = document.getElementById('imb-percent-chart');
87
- const depthDiv = document.getElementById('depth-chart');
88
  const volDiv = document.getElementById('vol-chart');
 
89
 
90
  const statusDiv = document.getElementById('status');
91
 
92
- // Init Flags
93
  let initPrice = false;
94
  let initImbP = false;
95
- let initDepth = false;
96
  let initVol = false;
 
97
 
98
- // Shared Config
99
  const commonConfig = {{ responsive: true, displayModeBar: false }};
100
  const commonLayout = {{
101
  paper_bgcolor: '#0e0e0e',
@@ -117,107 +116,55 @@ HTML_PAGE = f"""
117
  return;
118
  }}
119
 
120
- // Status Header
121
- const lastImb = data.imbalance_pct.last_val;
122
- let imbColor = '#aaa';
123
- if(lastImb > 5) imbColor = '#00e676';
124
- if(lastImb < -5) imbColor = '#ff1744';
125
 
126
- statusDiv.innerHTML = `Mid: <span class="${{data.mid >= data.prev_mid ? 'green' : 'red'}}">$${{data.mid.toLocaleString(undefined, {{minimumFractionDigits: 2}})}}</span> | Pressure: <span style="color:${{imbColor}}">${{lastImb ? lastImb.toFixed(2) + '%' : '-'}}</span>`;
127
-
128
- // ----------------------------------------------------
129
  // 1. PRICE HISTORY (Top Left)
130
- // ----------------------------------------------------
131
- const timeData = data.history.map(d => new Date(d.t * 1000));
132
- const priceData = data.history.map(d => d.p);
133
- const layoutPrice = {{ ...commonLayout, title: '<b>Midprice History</b>', xaxis: {{type:'date', gridcolor:'#222'}} }};
134
-
135
  if (!initPrice) {{
136
- Plotly.newPlot(priceDiv, [
137
- {{ x: timeData, y: priceData, type: 'scatter', mode:'lines', name: 'Mid', line: {{color: '#29b6f6', width: 2}} }}
138
- ], layoutPrice, commonConfig);
139
  initPrice = true;
140
  }} else {{
141
- Plotly.react(priceDiv, [
142
- {{ x: timeData, y: priceData, type: 'scatter', mode:'lines', name: 'Mid', line: {{color: '#29b6f6', width: 2}} }}
143
- ], layoutPrice, commonConfig);
144
  }}
145
 
146
- // ----------------------------------------------------
147
- // 2. IMBALANCE PERCENTAGE (Top Right)
148
- // ----------------------------------------------------
149
- const layoutImbP = {{ ...commonLayout,
150
- title: '<b>Liquidity Pressure (-100% to +100%)</b>',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  xaxis: {{title: 'Distance ($)', gridcolor:'#222'}},
152
- yaxis: {{title: 'Imbalance %', range: [-105, 105], gridcolor:'#222', zeroline: true, zerolinecolor: '#fff'}},
153
- shapes: [
154
- // Zero line
155
- {{ type: 'line', x0: 0, x1: 1, xref: 'paper', y0: 0, y1: 0, line: {{color: '#fff', width: 1}} }}
156
- ]
157
  }};
158
 
159
- // Color logic: Fill to zero.
160
- // We use a gradient or just a solid line. Let's use a nice Cyan line with 'tozeroy' fill.
161
- if (!initImbP) {{
162
- Plotly.newPlot(imbPercentDiv, [
163
- {{
164
- x: data.imbalance_pct.x,
165
- y: data.imbalance_pct.y,
166
- type: 'scatter',
167
- mode: 'lines',
168
- name: 'Imbalance',
169
- fill: 'tozeroy',
170
- line: {{color: '#ffeb3b', width: 2}} // Yellow line stands out
171
- }}
172
- ], layoutImbP, commonConfig);
173
- initImbP = true;
174
- }} else {{
175
- Plotly.react(imbPercentDiv, [
176
- {{
177
- x: data.imbalance_pct.x,
178
- y: data.imbalance_pct.y,
179
- type: 'scatter',
180
- mode: 'lines',
181
- name: 'Imbalance',
182
- fill: 'tozeroy',
183
- line: {{color: '#ffeb3b', width: 2}}
184
- }}
185
- ], layoutImbP, commonConfig);
186
- }}
187
-
188
- // ----------------------------------------------------
189
- // 3. DEPTH CHART (Bottom Left)
190
- // ----------------------------------------------------
191
- const layoutDepth = {{ ...commonLayout, title: 'Orderbook Depth', xaxis: {{title: 'Price', gridcolor:'#222'}} }};
192
- if (!initDepth) {{
193
- Plotly.newPlot(depthDiv, [
194
- {{ x: data.depth.bids_x, y: data.depth.bids_y, fill: 'tozeroy', type: 'scatter', name: 'Bids', line: {{color: '#00e676'}} }},
195
- {{ x: data.depth.asks_x, y: data.depth.asks_y, fill: 'tozeroy', type: 'scatter', name: 'Asks', line: {{color: '#ff1744'}} }}
196
- ], layoutDepth, commonConfig);
197
- initDepth = true;
198
- }} else {{
199
- Plotly.react(depthDiv, [
200
- {{ x: data.depth.bids_x, y: data.depth.bids_y, fill: 'tozeroy', type: 'scatter', name: 'Bids', line: {{color: '#00e676'}} }},
201
- {{ x: data.depth.asks_x, y: data.depth.asks_y, fill: 'tozeroy', type: 'scatter', name: 'Asks', line: {{color: '#ff1744'}} }}
202
- ], layoutDepth, commonConfig);
203
- }}
204
 
205
- // ----------------------------------------------------
206
- // 4. IMBALANCE VOL (Bottom Right)
207
- // ----------------------------------------------------
208
- const layoutVol = {{ ...commonLayout, title: 'Volume by Distance', xaxis: {{title: 'Distance ($)', gridcolor:'#222'}} }};
209
- if (!initVol) {{
210
- Plotly.newPlot(volDiv, [
211
- {{ x: data.imbalance_vol.dist_bids, y: data.imbalance_vol.vol_bids, type: 'scatter', name: 'Bid Vol', line: {{color: '#00e676'}} }},
212
- {{ x: data.imbalance_vol.dist_asks, y: data.imbalance_vol.vol_asks, type: 'scatter', name: 'Ask Vol', line: {{color: '#ff1744'}} }}
213
- ], layoutVol, commonConfig);
214
- initVol = true;
215
- }} else {{
216
- Plotly.react(volDiv, [
217
- {{ x: data.imbalance_vol.dist_bids, y: data.imbalance_vol.vol_bids, type: 'scatter', name: 'Bid Vol', line: {{color: '#00e676'}} }},
218
- {{ x: data.imbalance_vol.dist_asks, y: data.imbalance_vol.vol_asks, type: 'scatter', name: 'Ask Vol', line: {{color: '#ff1744'}} }}
219
- ], layoutVol, commonConfig);
220
- }}
221
 
222
  }} catch (e) {{ console.error("Fetch error:", e); }}
223
  }}
@@ -281,25 +228,11 @@ async def handle_data(request):
281
 
282
  mid = market_state['current_mid']
283
 
284
- # --- Prepare Raw Data ---
285
  raw_bids = sorted(market_state['bids'].items(), key=lambda x: -x[0])[:300]
286
  raw_asks = sorted(market_state['asks'].items(), key=lambda x: x[0])[:300]
287
 
288
- # 1. Depth (Price)
289
- b_x, b_y = [], []
290
- cum = 0
291
- for p, q in raw_bids:
292
- cum += q
293
- b_x.append(p); b_y.append(cum)
294
- b_x.reverse(); b_y.reverse()
295
-
296
- a_x, a_y = [], []
297
- cum = 0
298
- for p, q in raw_asks:
299
- cum += q
300
- a_x.append(p); a_y.append(cum)
301
-
302
- # 2. Distance Arrays
303
  dist_bids = [(mid - p, q) for p, q in raw_bids if mid - p >= 0]
304
  d_b_x, d_b_y = [], []
305
  cum_d = 0
@@ -314,35 +247,43 @@ async def handle_data(request):
314
  cum_d += q
315
  d_a_x.append(d); d_a_y.append(cum_d)
316
 
317
- # 3. Imbalance Calculation (-100 to +100)
318
- # Formula: (Vol_Bid - Vol_Ask) / (Vol_Bid + Vol_Ask) * 100
319
  imb_x, imb_y = [], []
 
 
 
320
  if d_b_x and d_a_x:
321
  max_dist = min(d_b_x[-1], d_a_x[-1])
322
  step_size = max_dist / 100
323
  steps = [i * step_size for i in range(1, 101)]
324
 
325
  for s in steps:
326
- # Get Bid Vol at distance s
327
  idx_b = bisect.bisect_right(d_b_x, s)
328
  vol_b = d_b_y[idx_b-1] if idx_b > 0 else 0
329
 
330
- # Get Ask Vol at distance s
331
  idx_a = bisect.bisect_right(d_a_x, s)
332
  vol_a = d_a_y[idx_a-1] if idx_a > 0 else 0
333
 
334
  total_vol = vol_b + vol_a
 
335
  if total_vol > 0:
336
  pct = ((vol_b - vol_a) / total_vol) * 100
337
- imb_x.append(s)
338
- imb_y.append(pct)
 
 
 
 
 
 
339
 
340
  return web.json_response({
341
  "mid": mid,
342
  "prev_mid": market_state['history'][-2]['p'] if len(market_state['history']) > 1 else mid,
343
- "depth": { "bids_x": b_x, "bids_y": b_y, "asks_x": a_x, "asks_y": a_y },
344
  "imbalance_vol": { "dist_bids": d_b_x, "vol_bids": d_b_y, "dist_asks": d_a_x, "vol_asks": d_a_y },
345
  "imbalance_pct": { "x": imb_x, "y": imb_y, "last_val": imb_y[-1] if imb_y else 0 },
 
346
  "history": market_state['history']
347
  })
348
 
@@ -366,7 +307,7 @@ async def main():
366
  site = web.TCPSite(runner, '0.0.0.0', PORT)
367
  await site.start()
368
 
369
- print(f"🚀 BTC-USD Analysis Dashboard: http://localhost:{PORT}")
370
  await asyncio.Event().wait()
371
 
372
  if __name__ == "__main__":
 
28
  <!DOCTYPE html>
29
  <html>
30
  <head>
31
+ <title>BTC-USD Integral Analysis</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; }}
 
36
  /* Layout Grid */
37
  #container {{ display: flex; flex-direction: column; height: 100vh; width: 100vw; }}
38
 
39
+ /* Row 1: Price & Pressure % */
40
  #row-top {{ flex: 1; display: flex; width: 100%; border-bottom: 1px solid #333; }}
41
 
42
+ /* Row 2: Volume & Integral */
43
  #row-bot {{ flex: 1; display: flex; width: 100%; }}
44
 
45
  /* Columns */
 
59
  <div id="status">Connecting...</div>
60
 
61
  <div id="container">
62
+ <!-- TOP ROW -->
63
  <div id="row-top">
64
  <div class="col-left">
65
  <div id="price-chart" class="chart"></div>
 
69
  </div>
70
  </div>
71
 
72
+ <!-- BOTTOM ROW -->
73
  <div id="row-bot">
74
  <div class="col-left">
75
+ <!-- Moved Volume Chart Here -->
76
+ <div id="vol-chart" class="chart"></div>
77
  </div>
78
  <div class="col-right">
79
+ <!-- New Integral Chart Here -->
80
+ <div id="integral-chart" class="chart"></div>
81
  </div>
82
  </div>
83
  </div>
84
 
85
  <script>
 
86
  const priceDiv = document.getElementById('price-chart');
87
  const imbPercentDiv = document.getElementById('imb-percent-chart');
 
88
  const volDiv = document.getElementById('vol-chart');
89
+ const integDiv = document.getElementById('integral-chart');
90
 
91
  const statusDiv = document.getElementById('status');
92
 
 
93
  let initPrice = false;
94
  let initImbP = false;
 
95
  let initVol = false;
96
+ let initInteg = false;
97
 
 
98
  const commonConfig = {{ responsive: true, displayModeBar: false }};
99
  const commonLayout = {{
100
  paper_bgcolor: '#0e0e0e',
 
116
  return;
117
  }}
118
 
119
+ statusDiv.innerHTML = `Mid: <span class="${{data.mid >= data.prev_mid ? 'green' : 'red'}}">$${{data.mid.toLocaleString(undefined, {{minimumFractionDigits: 2}})}}</span> | Integral: ${{data.integral.last_val ? data.integral.last_val.toFixed(0) : 0}}`;
 
 
 
 
120
 
 
 
 
121
  // 1. PRICE HISTORY (Top Left)
 
 
 
 
 
122
  if (!initPrice) {{
123
+ 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}} }}],
124
+ {{ ...commonLayout, title: '<b>Midprice History</b>', xaxis: {{type:'date', gridcolor:'#222'}} }}, commonConfig);
 
125
  initPrice = true;
126
  }} else {{
127
+ 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}} }}],
128
+ {{ ...commonLayout, title: '<b>Midprice History</b>', xaxis: {{type:'date', gridcolor:'#222'}} }}, commonConfig);
 
129
  }}
130
 
131
+ // 2. IMBALANCE % (Top Right)
132
+ const layoutImbP = {{ ...commonLayout, title: '<b>Liquidity Pressure (-100% to +100%)</b>', xaxis: {{title: 'Distance ($)', gridcolor:'#222'}}, yaxis: {{range: [-105, 105], gridcolor:'#222', zeroline: true}} }};
133
+ const traceImbP = {{ x: data.imbalance_pct.x, y: data.imbalance_pct.y, type: 'scatter', mode: 'lines', fill: 'tozeroy', line: {{color: '#ffeb3b', width: 2}} }};
134
+
135
+ if (!initImbP) {{ Plotly.newPlot(imbPercentDiv, [traceImbP], layoutImbP, commonConfig); initImbP = true; }}
136
+ else {{ Plotly.react(imbPercentDiv, [traceImbP], layoutImbP, commonConfig); }}
137
+
138
+ // 3. VOLUME BY DISTANCE (Bottom Left - Moved)
139
+ const layoutVol = {{ ...commonLayout, title: '<b>Volume by Distance</b>', xaxis: {{title: 'Distance ($)', gridcolor:'#222'}} }};
140
+ const tracesVol = [
141
+ {{ x: data.imbalance_vol.dist_bids, y: data.imbalance_vol.vol_bids, type: 'scatter', name: 'Bid', line: {{color: '#00e676'}} }},
142
+ {{ x: data.imbalance_vol.dist_asks, y: data.imbalance_vol.vol_asks, type: 'scatter', name: 'Ask', line: {{color: '#ff1744'}} }}
143
+ ];
144
+ if (!initVol) {{ Plotly.newPlot(volDiv, tracesVol, layoutVol, commonConfig); initVol = true; }}
145
+ else {{ Plotly.react(volDiv, tracesVol, layoutVol, commonConfig); }}
146
+
147
+ // 4. INTEGRAL OF PRESSURE (Bottom Right - New)
148
+ const layoutInteg = {{ ...commonLayout,
149
+ title: '<b>Cumulative Pressure (Integration)</b>',
150
  xaxis: {{title: 'Distance ($)', gridcolor:'#222'}},
151
+ yaxis: {{gridcolor:'#222', zeroline: true}}
 
 
 
 
152
  }};
153
 
154
+ // Color based on if total is positive or negative
155
+ const integColor = (data.integral.last_val >= 0) ? '#e040fb' : '#e040fb';
156
+
157
+ const traceInteg = {{
158
+ x: data.integral.x,
159
+ y: data.integral.y,
160
+ type: 'scatter',
161
+ mode: 'lines',
162
+ fill: 'tozeroy',
163
+ line: {{color: integColor, width: 2}}
164
+ }};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
 
166
+ if (!initInteg) {{ Plotly.newPlot(integDiv, [traceInteg], layoutInteg, commonConfig); initInteg = true; }}
167
+ else {{ Plotly.react(integDiv, [traceInteg], layoutInteg, commonConfig); }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
  }} catch (e) {{ console.error("Fetch error:", e); }}
170
  }}
 
228
 
229
  mid = market_state['current_mid']
230
 
231
+ # --- Raw Data ---
232
  raw_bids = sorted(market_state['bids'].items(), key=lambda x: -x[0])[:300]
233
  raw_asks = sorted(market_state['asks'].items(), key=lambda x: x[0])[:300]
234
 
235
+ # --- Distance Data ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  dist_bids = [(mid - p, q) for p, q in raw_bids if mid - p >= 0]
237
  d_b_x, d_b_y = [], []
238
  cum_d = 0
 
247
  cum_d += q
248
  d_a_x.append(d); d_a_y.append(cum_d)
249
 
250
+ # --- Imbalance % & Integration ---
 
251
  imb_x, imb_y = [], []
252
+ integ_x, integ_y = [], []
253
+ running_integral = 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 s in steps:
261
+ # 1. Calculate Imbalance %
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
+ pct = 0
270
  if total_vol > 0:
271
  pct = ((vol_b - vol_a) / total_vol) * 100
272
+
273
+ imb_x.append(s)
274
+ imb_y.append(pct)
275
+
276
+ # 2. Calculate Integration (Cumulative Sum of Pressure)
277
+ running_integral += pct
278
+ integ_x.append(s)
279
+ integ_y.append(running_integral)
280
 
281
  return web.json_response({
282
  "mid": mid,
283
  "prev_mid": market_state['history'][-2]['p'] if len(market_state['history']) > 1 else mid,
 
284
  "imbalance_vol": { "dist_bids": d_b_x, "vol_bids": d_b_y, "dist_asks": d_a_x, "vol_asks": d_a_y },
285
  "imbalance_pct": { "x": imb_x, "y": imb_y, "last_val": imb_y[-1] if imb_y else 0 },
286
+ "integral": { "x": integ_x, "y": integ_y, "last_val": integ_y[-1] if integ_y else 0 },
287
  "history": market_state['history']
288
  })
289
 
 
307
  site = web.TCPSite(runner, '0.0.0.0', PORT)
308
  await site.start()
309
 
310
+ print(f"🚀 BTC-USD Integral Dashboard: http://localhost:{PORT}")
311
  await asyncio.Event().wait()
312
 
313
  if __name__ == "__main__":