Alvin3y1 commited on
Commit
75f59d6
·
verified ·
1 Parent(s): 8aefbf1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +96 -97
app.py CHANGED
@@ -28,52 +28,55 @@ HTML_PAGE = f"""
28
  <!DOCTYPE html>
29
  <html>
30
  <head>
31
- <title>BTC-USD Wall Sharpness</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
  #container {{ display: flex; flex-direction: column; height: 100vh; width: 100vw; }}
 
 
37
  .row {{ flex: 1; display: flex; width: 100%; border-bottom: 1px solid #333; }}
38
- .col-left {{ width: 50%; border-right: 1px solid #333; position: relative; }}
39
- .col-right {{ width: 50%; position: relative; }}
40
- .col-full {{ width: 100%; position: relative; }}
 
 
 
41
  .chart {{ width: 100%; height: 100%; }}
42
 
43
- #status {{ position: absolute; top: 10px; left: 60px; z-index: 100; font-size: 14px; background: rgba(0,0,0,0.8); padding: 8px 12px; border-radius: 4px; pointer-events: none; border: 1px solid #333; box-shadow: 0 0 10px rgba(0,0,0,0.5); }}
 
44
  .green {{ color: #00e676; }}
45
  .red {{ color: #ff1744; }}
46
- .white {{ color: #fff; font-weight: bold; }}
47
  </style>
48
  </head>
49
  <body>
50
  <div id="status">Connecting...</div>
51
 
52
  <div id="container">
53
- <!-- ROW 1: Price & Pressure % -->
54
  <div class="row">
55
- <div class="col-left">
 
56
  <div id="price-chart" class="chart"></div>
57
  </div>
58
- <div class="col-right">
 
59
  <div id="imb-percent-chart" class="chart"></div>
60
  </div>
61
  </div>
62
 
63
- <!-- ROW 2: Volume & Net Diff -->
64
  <div class="row">
65
- <div class="col-left">
 
66
  <div id="vol-chart" class="chart"></div>
67
  </div>
68
- <div class="col-right">
69
- <div id="diff-chart" class="chart"></div>
70
- </div>
71
- </div>
72
-
73
- <!-- ROW 3: Integration -->
74
- <div class="row">
75
- <div class="col-full">
76
- <div id="integral-chart" class="chart"></div>
77
  </div>
78
  </div>
79
  </div>
@@ -82,11 +85,11 @@ HTML_PAGE = f"""
82
  const priceDiv = document.getElementById('price-chart');
83
  const imbPercentDiv = document.getElementById('imb-percent-chart');
84
  const volDiv = document.getElementById('vol-chart');
85
- const diffDiv = document.getElementById('diff-chart');
86
- const intDiv = document.getElementById('integral-chart');
87
  const statusDiv = document.getElementById('status');
88
 
89
- let initPrice=0, initImbP=0, initVol=0, initDiff=0, initInt=0;
90
 
91
  const commonConfig = {{ responsive: true, displayModeBar: false }};
92
  const commonLayout = {{
@@ -104,51 +107,56 @@ HTML_PAGE = f"""
104
  const res = await fetch('/data');
105
  const data = await res.json();
106
 
107
- if (data.error) {{ statusDiv.innerHTML = "Waiting for data..."; return; }}
108
-
109
- // --- SHARPNESS DISPLAY LOGIC ---
110
- let slope = data.sharpness;
111
- let slopeColor = '#aaa';
112
- let slopeText = 'Neutral';
113
-
114
- // Thresholds for sharpness (adjust as needed)
115
- if (slope > 0.5) {{ slopeColor = '#00e676'; slopeText = 'Sharp BUY Wall'; }}
116
- else if (slope < -0.5) {{ slopeColor = '#ff1744'; slopeText = 'Sharp SELL Wall'; }}
117
- else if (slope > 0) {{ slopeColor = '#81c784'; slopeText = 'Leaning Buy'; }}
118
- else {{ slopeColor = '#e57373'; slopeText = 'Leaning Sell'; }}
119
-
120
- statusDiv.innerHTML = `
121
- Mid: <span class="${{data.mid >= data.prev_mid ? 'green' : 'red'}}">$${{data.mid.toLocaleString(undefined, {{minimumFractionDigits: 0}})}}</span> |
122
- Wall Sharpness: <span style="color:${{slopeColor}}; font-weight:bold;">${{slope.toFixed(2)}} (${{slopeText}})</span> |
123
- Trend: ${{data.integral.last_val.toFixed(0)}}
124
- `;
125
-
126
- // 1. PRICE
127
- if (!initPrice) {{ 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'}} }}], {{...commonLayout, title:'Midprice'}}, commonConfig); initPrice=1; }}
128
- else {{ 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'}} }}], {{...commonLayout, title:'Midprice'}}, commonConfig); }}
129
-
130
- // 2. PRESSURE % (With annotation for sharpness?)
131
- if (!initImbP) {{ Plotly.newPlot(imbPercentDiv, [{{ x: data.imbalance_pct.x, y: data.imbalance_pct.y, type:'scatter', mode:'lines', fill:'tozeroy', line:{{color:'#ffeb3b'}} }}], {{...commonLayout, title:'Pressure %', yaxis:{{range:[-105,105], gridcolor:'#222'}}}}, commonConfig); initImbP=1; }}
132
- else {{ Plotly.react(imbPercentDiv, [{{ x: data.imbalance_pct.x, y: data.imbalance_pct.y, type:'scatter', mode:'lines', fill:'tozeroy', line:{{color:'#ffeb3b'}} }}], {{...commonLayout, title:'Pressure %', yaxis:{{range:[-105,105], gridcolor:'#222'}}}}, commonConfig); }}
133
-
134
- // 3. VOLUME
135
- if (!initVol) {{ Plotly.newPlot(volDiv, [
136
- {{ x: data.imbalance_vol.dist_bids, y: data.imbalance_vol.vol_bids, type:'scatter', line:{{color:'#00e676'}} }},
137
- {{ x: data.imbalance_vol.dist_asks, y: data.imbalance_vol.vol_asks, type:'scatter', line:{{color:'#ff1744'}} }}
138
- ], {{...commonLayout, title:'Volume'}}, commonConfig); initVol=1; }}
139
- else {{ Plotly.react(volDiv, [
140
- {{ x: data.imbalance_vol.dist_bids, y: data.imbalance_vol.vol_bids, type:'scatter', line:{{color:'#00e676'}} }},
141
- {{ x: data.imbalance_vol.dist_asks, y: data.imbalance_vol.vol_asks, type:'scatter', line:{{color:'#ff1744'}} }}
142
- ], {{...commonLayout, title:'Volume'}}, commonConfig); }}
143
-
144
- // 4. NET DIFF
145
- if (!initDiff) {{ Plotly.newPlot(diffDiv, [{{ x: data.diff.x, y: data.diff.y, type:'scatter', mode:'lines', fill:'tozeroy', line:{{color:'#00bcd4'}} }}], {{...commonLayout, title:'Net Diff'}}, commonConfig); initDiff=1; }}
146
- else {{ Plotly.react(diffDiv, [{{ x: data.diff.x, y: data.diff.y, type:'scatter', mode:'lines', fill:'tozeroy', line:{{color:'#00bcd4'}} }}], {{...commonLayout, title:'Net Diff'}}, commonConfig); }}
147
-
148
- // 5. INTEGRAL
149
- let intColor = data.integral.last_val >= 0 ? '#e040fb' : '#e040fb';
150
- if (!initInt) {{ Plotly.newPlot(intDiv, [{{ x: data.integral.x, y: data.integral.y, type:'scatter', mode:'lines', fill:'tozeroy', line:{{color:intColor}} }}], {{...commonLayout, title:'Integral Trend'}}, commonConfig); initInt=1; }}
151
- else {{ Plotly.react(intDiv, [{{ x: data.integral.x, y: data.integral.y, type:'scatter', mode:'lines', fill:'tozeroy', line:{{color:intColor}} }}], {{...commonLayout, title:'Integral Trend'}}, commonConfig); }}
 
 
 
 
 
152
 
153
  }} catch (e) {{ console.error("Fetch error:", e); }}
154
  }}
@@ -212,11 +220,11 @@ async def handle_data(request):
212
 
213
  mid = market_state['current_mid']
214
 
215
- # --- Prepare Data ---
216
  raw_bids = sorted(market_state['bids'].items(), key=lambda x: -x[0])[:300]
217
  raw_asks = sorted(market_state['asks'].items(), key=lambda x: x[0])[:300]
218
 
219
- # Volumes by distance
220
  dist_bids = [(mid - p, q) for p, q in raw_bids if mid - p >= 0]
221
  d_b_x, d_b_y = [], []
222
  cum_d = 0
@@ -233,18 +241,18 @@ async def handle_data(request):
233
 
234
  # --- Calculations ---
235
  imb_x, imb_y = [], []
236
- diff_x, diff_y = [], []
237
- int_x, int_y = [], []
238
- running_int = 0
239
- sharpness = 0
240
 
241
  if d_b_x and d_a_x:
242
  max_dist = min(d_b_x[-1], d_a_x[-1])
 
243
  step_size = max_dist / 100
244
  steps = [i * step_size for i in range(1, 101)]
245
 
 
 
246
  for i, s in enumerate(steps):
247
- # 1. Volume Lookups
248
  idx_b = bisect.bisect_right(d_b_x, s)
249
  vol_b = d_b_y[idx_b-1] if idx_b > 0 else 0
250
 
@@ -254,37 +262,28 @@ async def handle_data(request):
254
  # 2. Imbalance %
255
  total_vol = vol_b + vol_a
256
  pct = ((vol_b - vol_a) / total_vol) * 100 if total_vol > 0 else 0
 
257
  imb_x.append(s)
258
  imb_y.append(pct)
259
 
260
- # 3. Net Diff
261
- diff = vol_b - vol_a
262
- diff_x.append(s)
263
- diff_y.append(diff)
 
 
 
 
264
 
265
- # 4. Integral
266
- running_int += diff
267
- int_x.append(s)
268
- int_y.append(running_int)
269
-
270
- # --- SHARPNESS CALCULATION ---
271
- # We take the pressure at the 5th step (approx 5% into the book)
272
- # Sharpness = Pressure / Distance
273
- target_idx = 4 # 5th element
274
- if len(imb_x) > target_idx:
275
- # e.g., Pressure is 50% at $10 distance -> Sharpness = 5.0
276
- # Dividing by distance normalizes it.
277
- if imb_x[target_idx] > 0:
278
- sharpness = imb_y[target_idx] / imb_x[target_idx]
279
 
280
  return web.json_response({
281
  "mid": mid,
282
  "prev_mid": market_state['history'][-2]['p'] if len(market_state['history']) > 1 else mid,
283
- "sharpness": sharpness,
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 },
286
- "diff": { "x": diff_x, "y": diff_y },
287
- "integral": { "x": int_x, "y": int_y, "last_val": int_y[-1] if int_y else 0 },
288
  "history": market_state['history']
289
  })
290
 
@@ -308,7 +307,7 @@ async def main():
308
  site = web.TCPSite(runner, '0.0.0.0', PORT)
309
  await site.start()
310
 
311
- print(f"🚀 BTC-USD Dashboard with Sharpness: http://localhost:{PORT}")
312
  await asyncio.Event().wait()
313
 
314
  if __name__ == "__main__":
 
28
  <!DOCTYPE html>
29
  <html>
30
  <head>
31
+ <title>BTC-USD Gradient 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; }}
35
 
36
+ /* Layout Grid - 2 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
+ <!-- Top Left: Price -->
63
  <div id="price-chart" class="chart"></div>
64
  </div>
65
+ <div class="col">
66
+ <!-- Top Right: Pressure % -->
67
  <div id="imb-percent-chart" class="chart"></div>
68
  </div>
69
  </div>
70
 
71
+ <!-- ROW 2 -->
72
  <div class="row">
73
+ <div class="col">
74
+ <!-- Bottom Left: Volume -->
75
  <div id="vol-chart" class="chart"></div>
76
  </div>
77
+ <div class="col">
78
+ <!-- Bottom Right: Differentiation (Gradient) -->
79
+ <div id="deriv-chart" class="chart"></div>
 
 
 
 
 
 
80
  </div>
81
  </div>
82
  </div>
 
85
  const priceDiv = document.getElementById('price-chart');
86
  const imbPercentDiv = document.getElementById('imb-percent-chart');
87
  const volDiv = document.getElementById('vol-chart');
88
+ const derivDiv = document.getElementById('deriv-chart');
89
+
90
  const statusDiv = document.getElementById('status');
91
 
92
+ let initPrice = false, initImbP = false, initVol = false, initDeriv = false;
93
 
94
  const commonConfig = {{ responsive: true, displayModeBar: false }};
95
  const commonLayout = {{
 
107
  const res = await fetch('/data');
108
  const data = await res.json();
109
 
110
+ if (data.error) {{
111
+ statusDiv.innerHTML = "Waiting for data...";
112
+ return;
113
+ }}
114
+
115
+ statusDiv.innerHTML = `Mid: <span class="${{data.mid >= data.prev_mid ? 'green' : 'red'}}">$${{data.mid.toLocaleString(undefined, {{minimumFractionDigits: 2}})}}</span> | Max Grad: ${{data.deriv.max_val ? data.deriv.max_val.toFixed(2) : 0}}`;
116
+
117
+ // 1. PRICE HISTORY
118
+ if (!initPrice) {{
119
+ 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}} }}],
120
+ {{ ...commonLayout, title: '<b>Midprice</b>', xaxis: {{type:'date', gridcolor:'#222'}} }}, commonConfig);
121
+ initPrice = true;
122
+ }} else {{
123
+ 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}} }}],
124
+ {{ ...commonLayout, title: '<b>Midprice</b>', xaxis: {{type:'date', gridcolor:'#222'}} }}, commonConfig);
125
+ }}
126
+
127
+ // 2. IMBALANCE %
128
+ const layoutImbP = {{ ...commonLayout, title: '<b>Pressure % (-100 to 100)</b>', yaxis: {{range: [-105, 105], gridcolor:'#222'}} }};
129
+ 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; }}
130
+ 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); }}
131
+
132
+ // 3. VOLUME BY DISTANCE
133
+ const layoutVol = {{ ...commonLayout, title: '<b>Volume by Distance</b>' }};
134
+ const tracesVol = [
135
+ {{ x: data.imbalance_vol.dist_bids, y: data.imbalance_vol.vol_bids, type: 'scatter', name: 'Bid', line: {{color: '#00e676'}} }},
136
+ {{ x: data.imbalance_vol.dist_asks, y: data.imbalance_vol.vol_asks, type: 'scatter', name: 'Ask', line: {{color: '#ff1744'}} }}
137
+ ];
138
+ if (!initVol) {{ Plotly.newPlot(volDiv, tracesVol, layoutVol, commonConfig); initVol = true; }}
139
+ else {{ Plotly.react(volDiv, tracesVol, layoutVol, commonConfig); }}
140
+
141
+ // 4. DIFFERENTIATION (Gradient)
142
+ // This shows the rate of change of pressure. Large spikes = walls.
143
+ const layoutDeriv = {{ ...commonLayout,
144
+ title: '<b>Pressure Gradient (Rate of Change)</b>',
145
+ xaxis: {{title: 'Distance ($)', gridcolor:'#222'}},
146
+ yaxis: {{title: 'Δ% / Step', gridcolor:'#222'}}
147
+ }};
148
+
149
+ const traceDeriv = {{
150
+ x: data.deriv.x,
151
+ y: data.deriv.y,
152
+ type: 'scatter',
153
+ mode: 'lines',
154
+ fill: 'tozeroy',
155
+ line: {{color: '#e040fb', width: 1.5}} // Purple line
156
+ }};
157
+
158
+ if (!initDeriv) {{ Plotly.newPlot(derivDiv, [traceDeriv], layoutDeriv, commonConfig); initDeriv = true; }}
159
+ else {{ Plotly.react(derivDiv, [traceDeriv], layoutDeriv, commonConfig); }}
160
 
161
  }} catch (e) {{ console.error("Fetch error:", e); }}
162
  }}
 
220
 
221
  mid = market_state['current_mid']
222
 
223
+ # --- Raw Data ---
224
  raw_bids = sorted(market_state['bids'].items(), key=lambda x: -x[0])[:300]
225
  raw_asks = sorted(market_state['asks'].items(), key=lambda x: x[0])[:300]
226
 
227
+ # --- Distance Data ---
228
  dist_bids = [(mid - p, q) for p, q in raw_bids if mid - p >= 0]
229
  d_b_x, d_b_y = [], []
230
  cum_d = 0
 
241
 
242
  # --- Calculations ---
243
  imb_x, imb_y = [], []
244
+ deriv_x, deriv_y = [], []
 
 
 
245
 
246
  if d_b_x and d_a_x:
247
  max_dist = min(d_b_x[-1], d_a_x[-1])
248
+ # Use 100 uniform steps for smooth differentiation
249
  step_size = max_dist / 100
250
  steps = [i * step_size for i in range(1, 101)]
251
 
252
+ last_pct = 0
253
+
254
  for i, s in enumerate(steps):
255
+ # 1. Get Volumes
256
  idx_b = bisect.bisect_right(d_b_x, s)
257
  vol_b = d_b_y[idx_b-1] if idx_b > 0 else 0
258
 
 
262
  # 2. Imbalance %
263
  total_vol = vol_b + vol_a
264
  pct = ((vol_b - vol_a) / total_vol) * 100 if total_vol > 0 else 0
265
+
266
  imb_x.append(s)
267
  imb_y.append(pct)
268
 
269
+ # 3. Differentiation (Rate of Change)
270
+ # Change in percentage per step
271
+ if i > 0:
272
+ change = pct - last_pct
273
+ deriv_x.append(s)
274
+ deriv_y.append(change)
275
+
276
+ last_pct = pct
277
 
278
+ # Find max gradient for display
279
+ max_grad = max(deriv_y) if deriv_y else 0
 
 
 
 
 
 
 
 
 
 
 
 
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 },
286
+ "deriv": { "x": deriv_x, "y": deriv_y, "max_val": max_grad },
 
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 Gradient Dashboard: http://localhost:{PORT}")
311
  await asyncio.Event().wait()
312
 
313
  if __name__ == "__main__":