Update app.py
Browse files
app.py
CHANGED
|
@@ -150,25 +150,25 @@ def process_market_data():
|
|
| 150 |
"walls": {"bids": bid_walls, "asks": ask_walls}
|
| 151 |
}
|
| 152 |
|
| 153 |
-
# --- FRONTEND (
|
| 154 |
HTML_PAGE = f"""
|
| 155 |
<!DOCTYPE html>
|
| 156 |
<html lang="en">
|
| 157 |
<head>
|
| 158 |
<meta charset="UTF-8">
|
| 159 |
-
<title>
|
| 160 |
<script src="https://unpkg.com/lightweight-charts@4.1.1/dist/lightweight-charts.standalone.production.js"></script>
|
| 161 |
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@
|
| 162 |
<style>
|
| 163 |
:root {{
|
| 164 |
-
--bg-base: #
|
| 165 |
--bg-panel: #0a0a0a;
|
| 166 |
-
--border: #
|
| 167 |
-
--text-main: #
|
| 168 |
-
--text-dim: #
|
| 169 |
-
--green: #
|
| 170 |
-
--red: #
|
| 171 |
-
--blue: #
|
| 172 |
}}
|
| 173 |
body {{
|
| 174 |
margin: 0; padding: 0;
|
|
@@ -182,15 +182,15 @@ HTML_PAGE = f"""
|
|
| 182 |
/* THE GRID */
|
| 183 |
.layout {{
|
| 184 |
display: grid;
|
| 185 |
-
grid-template-rows:
|
| 186 |
grid-template-columns: 3fr 1fr;
|
| 187 |
-
gap: 1px;
|
| 188 |
-
background-color: var(--border);
|
| 189 |
height: 100vh;
|
| 190 |
box-sizing: border-box;
|
| 191 |
}}
|
| 192 |
|
| 193 |
-
.panel {{ background: var(--bg-panel);
|
| 194 |
|
| 195 |
/* STATUS BAR HEADER */
|
| 196 |
.status-bar {{
|
|
@@ -200,15 +200,17 @@ HTML_PAGE = f"""
|
|
| 200 |
display: flex;
|
| 201 |
align-items: center;
|
| 202 |
justify-content: space-between;
|
| 203 |
-
padding: 0
|
| 204 |
font-family: 'JetBrains Mono', monospace;
|
| 205 |
-
font-size:
|
| 206 |
text-transform: uppercase;
|
|
|
|
|
|
|
| 207 |
}}
|
| 208 |
-
.status-left {{ display: flex; gap:
|
| 209 |
-
.status-right {{ color: var(--text-dim); }}
|
| 210 |
-
.live-dot {{ width:
|
| 211 |
-
.
|
| 212 |
|
| 213 |
/* MAIN CHART AREA */
|
| 214 |
#p-chart {{ grid-column: 1 / 2; grid-row: 2 / 3; }}
|
|
@@ -221,7 +223,7 @@ HTML_PAGE = f"""
|
|
| 221 |
gap: 1px;
|
| 222 |
background: var(--border);
|
| 223 |
}}
|
| 224 |
-
.depth-sub {{ background: var(--bg-panel); display: flex; flex-direction: column; }}
|
| 225 |
|
| 226 |
/* SIDEBAR */
|
| 227 |
#p-sidebar {{
|
|
@@ -230,15 +232,31 @@ HTML_PAGE = f"""
|
|
| 230 |
padding: 20px;
|
| 231 |
display: flex;
|
| 232 |
flex-direction: column;
|
| 233 |
-
gap:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
}}
|
| 235 |
|
| 236 |
/* COMPONENT STYLES */
|
| 237 |
.data-group {{ display: flex; flex-direction: column; gap: 4px; }}
|
| 238 |
-
.label {{ font-size: 10px; color: var(--text-dim); font-weight:
|
| 239 |
-
.value {{ font-family: 'JetBrains Mono', monospace; font-size: 20px; font-weight:
|
| 240 |
-
.value-lg {{ font-size:
|
| 241 |
-
.value-sub {{ font-family: 'JetBrains Mono', monospace; font-size: 11px; margin-top: 2px; }}
|
| 242 |
|
| 243 |
.divider {{ height: 1px; background: var(--border); width: 100%; }}
|
| 244 |
|
|
@@ -248,23 +266,17 @@ HTML_PAGE = f"""
|
|
| 248 |
.c-dim {{ color: var(--text-dim); }}
|
| 249 |
|
| 250 |
/* LISTS */
|
| 251 |
-
.list-container {{ display: flex; flex-direction: column; gap: 8px; }}
|
| 252 |
.list-item {{
|
| 253 |
display: flex; justify-content: space-between;
|
| 254 |
font-family: 'JetBrains Mono', monospace;
|
| 255 |
font-size: 11px;
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
.list-item span:first-child {{ color: #fff; }}
|
| 259 |
-
|
| 260 |
-
/* CHART OVERLAYS */
|
| 261 |
-
.chart-label {{
|
| 262 |
-
position: absolute; top: 10px; left: 15px;
|
| 263 |
-
z-index: 10;
|
| 264 |
-
font-size: 10px;
|
| 265 |
-
color: var(--text-dim);
|
| 266 |
-
font-weight: 600;
|
| 267 |
}}
|
|
|
|
|
|
|
|
|
|
| 268 |
</style>
|
| 269 |
</head>
|
| 270 |
<body>
|
|
@@ -274,27 +286,27 @@ HTML_PAGE = f"""
|
|
| 274 |
<div class="status-bar">
|
| 275 |
<div class="status-left">
|
| 276 |
<span class="live-dot"></span>
|
| 277 |
-
<span
|
| 278 |
-
<span id="price-ticker"
|
| 279 |
</div>
|
| 280 |
-
<div class="status-right" id="clock"
|
| 281 |
</div>
|
| 282 |
|
| 283 |
<!-- PRICE CHART -->
|
| 284 |
<div id="p-chart" class="panel">
|
| 285 |
-
<div class="chart-
|
| 286 |
-
<div id="tv-price" style="flex: 1;"></div>
|
| 287 |
</div>
|
| 288 |
|
| 289 |
<!-- DEPTH CHARTS -->
|
| 290 |
<div id="p-depth">
|
| 291 |
<div class="depth-sub">
|
| 292 |
-
<div class="chart-
|
| 293 |
-
<div id="tv-raw" style="flex: 1;"></div>
|
| 294 |
</div>
|
| 295 |
<div class="depth-sub">
|
| 296 |
-
<div class="chart-
|
| 297 |
-
<div id="tv-net" style="flex: 1;"></div>
|
| 298 |
</div>
|
| 299 |
</div>
|
| 300 |
|
|
@@ -304,9 +316,9 @@ HTML_PAGE = f"""
|
|
| 304 |
<!-- 1. PREDICTED IMPACT -->
|
| 305 |
<div class="data-group">
|
| 306 |
<span class="label">Projected Impact (5s)</span>
|
| 307 |
-
<div style="display:flex; align-items: baseline; gap:
|
| 308 |
<span id="proj-pct" class="value value-lg">--%</span>
|
| 309 |
-
<span id="proj-val" class="value-sub
|
| 310 |
</div>
|
| 311 |
</div>
|
| 312 |
|
|
@@ -314,7 +326,7 @@ HTML_PAGE = f"""
|
|
| 314 |
|
| 315 |
<!-- 2. IMBALANCE SCORE -->
|
| 316 |
<div class="data-group">
|
| 317 |
-
<span class="label">Order Flow
|
| 318 |
<span id="score-val" class="value">0.00</span>
|
| 319 |
</div>
|
| 320 |
|
|
@@ -322,15 +334,15 @@ HTML_PAGE = f"""
|
|
| 322 |
|
| 323 |
<!-- 3. WALLS -->
|
| 324 |
<div class="data-group" style="flex: 1;">
|
| 325 |
-
<span class="label" style="margin-bottom: 10px;">
|
| 326 |
<div id="wall-list" class="list-container">
|
| 327 |
-
<span class="c-dim" style="font-size: 11px;">
|
| 328 |
</div>
|
| 329 |
</div>
|
| 330 |
|
| 331 |
<div style="margin-top: auto;">
|
| 332 |
<span class="label">System Latency</span>
|
| 333 |
-
<div class="value-sub c-green">
|
| 334 |
</div>
|
| 335 |
</div>
|
| 336 |
</div>
|
|
@@ -351,45 +363,55 @@ HTML_PAGE = f"""
|
|
| 351 |
wallList: document.getElementById('wall-list')
|
| 352 |
}};
|
| 353 |
|
| 354 |
-
//
|
| 355 |
const chartOpts = {{
|
| 356 |
-
layout: {{ background: {{ type: 'solid', color: '#0a0a0a' }}, textColor: '#
|
| 357 |
-
grid: {{ vertLines: {{ color: '#
|
| 358 |
-
rightPriceScale: {{ borderColor: '#
|
| 359 |
-
timeScale: {{ borderColor: '#
|
| 360 |
-
crosshair: {{ mode: 1, vertLine: {{ color: '#
|
| 361 |
}};
|
| 362 |
|
| 363 |
// 1. PRICE
|
| 364 |
const priceChart = LightweightCharts.createChart(document.getElementById('tv-price'), chartOpts);
|
| 365 |
-
const priceSeries = priceChart.addLineSeries({{ color: '#
|
| 366 |
-
const predSeries = priceChart.addLineSeries({{ color: '#
|
| 367 |
|
| 368 |
// 2. RAW
|
| 369 |
const rawChart = LightweightCharts.createChart(document.getElementById('tv-raw'), {{
|
| 370 |
...chartOpts,
|
| 371 |
localization: {{ timeFormatter: t => '$' + t.toFixed(2) }}
|
| 372 |
}});
|
| 373 |
-
const bidSeries = rawChart.addAreaSeries({{ lineColor: '#
|
| 374 |
-
const askSeries = rawChart.addAreaSeries({{ lineColor: '#
|
| 375 |
|
| 376 |
// 3. NET
|
| 377 |
const netChart = LightweightCharts.createChart(document.getElementById('tv-net'), {{
|
| 378 |
...chartOpts,
|
| 379 |
localization: {{ timeFormatter: t => '$' + t.toFixed(2) }}
|
| 380 |
}});
|
| 381 |
-
const netSeries = netChart.addHistogramSeries({{ color: '#
|
| 382 |
|
| 383 |
let activeLines = [];
|
| 384 |
|
| 385 |
-
// RESIZE
|
| 386 |
-
new ResizeObserver(
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 393 |
|
| 394 |
function connect() {{
|
| 395 |
const ws = new WebSocket((location.protocol === 'https:' ? 'wss' : 'ws') + '://' + location.host + '/ws');
|
|
@@ -438,18 +460,18 @@ HTML_PAGE = f"""
|
|
| 438 |
let html = "";
|
| 439 |
|
| 440 |
const addWall = (w, type) => {{
|
| 441 |
-
const color = type === 'BID' ? '#
|
| 442 |
activeLines.push(priceSeries.createPriceLine({{ price: w.price, color: color, lineWidth: 1, lineStyle: 2, axisLabelVisible: false }}));
|
| 443 |
html += `<div class="list-item">
|
| 444 |
<span style="color:${{color}}">${{type}} ${{w.price}}</span>
|
| 445 |
-
<span>Z:${{w.z_score.toFixed(1)}}</span>
|
| 446 |
</div>`;
|
| 447 |
}};
|
| 448 |
|
| 449 |
data.walls.asks.forEach(w => addWall(w, 'ASK'));
|
| 450 |
data.walls.bids.forEach(w => addWall(w, 'BID'));
|
| 451 |
|
| 452 |
-
dom.wallList.innerHTML = html || '<span class="c-dim" style="font-size:11px">No
|
| 453 |
}}
|
| 454 |
|
| 455 |
// DEPTH
|
|
@@ -459,7 +481,7 @@ HTML_PAGE = f"""
|
|
| 459 |
const t = data.depth_x[i];
|
| 460 |
bids.push({{ time: t, value: data.depth_bids[i] }});
|
| 461 |
asks.push({{ time: t, value: data.depth_asks[i] }});
|
| 462 |
-
nets.push({{ time: t, value: data.depth_net[i], color: data.depth_net[i] > 0 ? '#
|
| 463 |
}}
|
| 464 |
bidSeries.setData(bids);
|
| 465 |
askSeries.setData(asks);
|
|
|
|
| 150 |
"walls": {"bids": bid_walls, "asks": ask_walls}
|
| 151 |
}
|
| 152 |
|
| 153 |
+
# --- FRONTEND (HIGH CONTRAST, NO OVERLAP) ---
|
| 154 |
HTML_PAGE = f"""
|
| 155 |
<!DOCTYPE html>
|
| 156 |
<html lang="en">
|
| 157 |
<head>
|
| 158 |
<meta charset="UTF-8">
|
| 159 |
+
<title>{SYMBOL_KRAKEN}</title>
|
| 160 |
<script src="https://unpkg.com/lightweight-charts@4.1.1/dist/lightweight-charts.standalone.production.js"></script>
|
| 161 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@500;600&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
|
| 162 |
<style>
|
| 163 |
:root {{
|
| 164 |
+
--bg-base: #000000;
|
| 165 |
--bg-panel: #0a0a0a;
|
| 166 |
+
--border: #252525;
|
| 167 |
+
--text-main: #FFFFFF;
|
| 168 |
+
--text-dim: #999999;
|
| 169 |
+
--green: #00ff9d;
|
| 170 |
+
--red: #ff3b3b;
|
| 171 |
+
--blue: #2979ff;
|
| 172 |
}}
|
| 173 |
body {{
|
| 174 |
margin: 0; padding: 0;
|
|
|
|
| 182 |
/* THE GRID */
|
| 183 |
.layout {{
|
| 184 |
display: grid;
|
| 185 |
+
grid-template-rows: 34px 1fr 1fr; /* Explicit Header Height */
|
| 186 |
grid-template-columns: 3fr 1fr;
|
| 187 |
+
gap: 1px;
|
| 188 |
+
background-color: var(--border);
|
| 189 |
height: 100vh;
|
| 190 |
box-sizing: border-box;
|
| 191 |
}}
|
| 192 |
|
| 193 |
+
.panel {{ background: var(--bg-panel); display: flex; flex-direction: column; overflow: hidden; }}
|
| 194 |
|
| 195 |
/* STATUS BAR HEADER */
|
| 196 |
.status-bar {{
|
|
|
|
| 200 |
display: flex;
|
| 201 |
align-items: center;
|
| 202 |
justify-content: space-between;
|
| 203 |
+
padding: 0 12px;
|
| 204 |
font-family: 'JetBrains Mono', monospace;
|
| 205 |
+
font-size: 12px;
|
| 206 |
text-transform: uppercase;
|
| 207 |
+
border-bottom: 1px solid var(--border);
|
| 208 |
+
z-index: 50;
|
| 209 |
}}
|
| 210 |
+
.status-left {{ display: flex; gap: 20px; align-items: center; }}
|
| 211 |
+
.status-right {{ color: var(--text-dim); letter-spacing: 1px; }}
|
| 212 |
+
.live-dot {{ width: 8px; height: 8px; background-color: var(--green); border-radius: 50%; display: inline-block; box-shadow: 0 0 8px var(--green); }}
|
| 213 |
+
.ticker-val {{ font-weight: 700; color: #fff; font-size: 13px; }}
|
| 214 |
|
| 215 |
/* MAIN CHART AREA */
|
| 216 |
#p-chart {{ grid-column: 1 / 2; grid-row: 2 / 3; }}
|
|
|
|
| 223 |
gap: 1px;
|
| 224 |
background: var(--border);
|
| 225 |
}}
|
| 226 |
+
.depth-sub {{ background: var(--bg-panel); display: flex; flex-direction: column; position: relative; }}
|
| 227 |
|
| 228 |
/* SIDEBAR */
|
| 229 |
#p-sidebar {{
|
|
|
|
| 232 |
padding: 20px;
|
| 233 |
display: flex;
|
| 234 |
flex-direction: column;
|
| 235 |
+
gap: 25px;
|
| 236 |
+
border-left: 1px solid var(--border);
|
| 237 |
+
}}
|
| 238 |
+
|
| 239 |
+
/* CHART INTERNAL HEADER (PREVENTS OVERLAP) */
|
| 240 |
+
.chart-header {{
|
| 241 |
+
height: 24px;
|
| 242 |
+
min-height: 24px;
|
| 243 |
+
display: flex;
|
| 244 |
+
align-items: center;
|
| 245 |
+
padding-left: 12px;
|
| 246 |
+
font-size: 10px;
|
| 247 |
+
font-weight: 700;
|
| 248 |
+
color: var(--text-dim);
|
| 249 |
+
background: #050505; /* Slightly darker to separate */
|
| 250 |
+
border-bottom: 1px solid #151515;
|
| 251 |
+
letter-spacing: 0.5px;
|
| 252 |
}}
|
| 253 |
|
| 254 |
/* COMPONENT STYLES */
|
| 255 |
.data-group {{ display: flex; flex-direction: column; gap: 4px; }}
|
| 256 |
+
.label {{ font-size: 10px; color: var(--text-dim); font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }}
|
| 257 |
+
.value {{ font-family: 'JetBrains Mono', monospace; font-size: 20px; font-weight: 700; color: #fff; }}
|
| 258 |
+
.value-lg {{ font-size: 26px; }}
|
| 259 |
+
.value-sub {{ font-family: 'JetBrains Mono', monospace; font-size: 11px; margin-top: 2px; color: #666; }}
|
| 260 |
|
| 261 |
.divider {{ height: 1px; background: var(--border); width: 100%; }}
|
| 262 |
|
|
|
|
| 266 |
.c-dim {{ color: var(--text-dim); }}
|
| 267 |
|
| 268 |
/* LISTS */
|
| 269 |
+
.list-container {{ display: flex; flex-direction: column; gap: 8px; overflow-y: hidden; }}
|
| 270 |
.list-item {{
|
| 271 |
display: flex; justify-content: space-between;
|
| 272 |
font-family: 'JetBrains Mono', monospace;
|
| 273 |
font-size: 11px;
|
| 274 |
+
border-bottom: 1px solid #151515;
|
| 275 |
+
padding-bottom: 4px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
}}
|
| 277 |
+
.list-item span:first-child {{ color: #e0e0e0; }}
|
| 278 |
+
.list-item:last-child {{ border: none; }}
|
| 279 |
+
|
| 280 |
</style>
|
| 281 |
</head>
|
| 282 |
<body>
|
|
|
|
| 286 |
<div class="status-bar">
|
| 287 |
<div class="status-left">
|
| 288 |
<span class="live-dot"></span>
|
| 289 |
+
<span style="font-weight:700; color:#fff;">{SYMBOL_KRAKEN}</span>
|
| 290 |
+
<span id="price-ticker" class="ticker-val">---</span>
|
| 291 |
</div>
|
| 292 |
+
<div class="status-right" id="clock">00:00:00 UTC</div>
|
| 293 |
</div>
|
| 294 |
|
| 295 |
<!-- PRICE CHART -->
|
| 296 |
<div id="p-chart" class="panel">
|
| 297 |
+
<div class="chart-header">PRICE ACTION // ANOMALIES</div>
|
| 298 |
+
<div id="tv-price" style="flex: 1; width: 100%;"></div>
|
| 299 |
</div>
|
| 300 |
|
| 301 |
<!-- DEPTH CHARTS -->
|
| 302 |
<div id="p-depth">
|
| 303 |
<div class="depth-sub">
|
| 304 |
+
<div class="chart-header">LIQUIDITY DENSITY</div>
|
| 305 |
+
<div id="tv-raw" style="flex: 1; width: 100%;"></div>
|
| 306 |
</div>
|
| 307 |
<div class="depth-sub">
|
| 308 |
+
<div class="chart-header">NET IMBALANCE</div>
|
| 309 |
+
<div id="tv-net" style="flex: 1; width: 100%;"></div>
|
| 310 |
</div>
|
| 311 |
</div>
|
| 312 |
|
|
|
|
| 316 |
<!-- 1. PREDICTED IMPACT -->
|
| 317 |
<div class="data-group">
|
| 318 |
<span class="label">Projected Impact (5s)</span>
|
| 319 |
+
<div style="display:flex; align-items: baseline; gap: 10px;">
|
| 320 |
<span id="proj-pct" class="value value-lg">--%</span>
|
| 321 |
+
<span id="proj-val" class="value-sub">---</span>
|
| 322 |
</div>
|
| 323 |
</div>
|
| 324 |
|
|
|
|
| 326 |
|
| 327 |
<!-- 2. IMBALANCE SCORE -->
|
| 328 |
<div class="data-group">
|
| 329 |
+
<span class="label">Order Flow Score</span>
|
| 330 |
<span id="score-val" class="value">0.00</span>
|
| 331 |
</div>
|
| 332 |
|
|
|
|
| 334 |
|
| 335 |
<!-- 3. WALLS -->
|
| 336 |
<div class="data-group" style="flex: 1;">
|
| 337 |
+
<span class="label" style="margin-bottom: 10px;">Detected Walls (Z > 3.0)</span>
|
| 338 |
<div id="wall-list" class="list-container">
|
| 339 |
+
<span class="c-dim" style="font-size: 11px;">Initializing scan...</span>
|
| 340 |
</div>
|
| 341 |
</div>
|
| 342 |
|
| 343 |
<div style="margin-top: auto;">
|
| 344 |
<span class="label">System Latency</span>
|
| 345 |
+
<div class="value-sub c-green">Connected</div>
|
| 346 |
</div>
|
| 347 |
</div>
|
| 348 |
</div>
|
|
|
|
| 363 |
wallList: document.getElementById('wall-list')
|
| 364 |
}};
|
| 365 |
|
| 366 |
+
// HIGH CONTRAST CHART CONFIG
|
| 367 |
const chartOpts = {{
|
| 368 |
+
layout: {{ background: {{ type: 'solid', color: '#0a0a0a' }}, textColor: '#888', fontFamily: 'JetBrains Mono' }},
|
| 369 |
+
grid: {{ vertLines: {{ color: '#151515' }}, horzLines: {{ color: '#151515' }} }},
|
| 370 |
+
rightPriceScale: {{ borderColor: '#222', scaleMargins: {{ top: 0.1, bottom: 0.1 }} }},
|
| 371 |
+
timeScale: {{ borderColor: '#222', timeVisible: true, secondsVisible: true }},
|
| 372 |
+
crosshair: {{ mode: 1, vertLine: {{ color: '#444', labelBackgroundColor: '#444' }}, horzLine: {{ color: '#444', labelBackgroundColor: '#444' }} }}
|
| 373 |
}};
|
| 374 |
|
| 375 |
// 1. PRICE
|
| 376 |
const priceChart = LightweightCharts.createChart(document.getElementById('tv-price'), chartOpts);
|
| 377 |
+
const priceSeries = priceChart.addLineSeries({{ color: '#FFFFFF', lineWidth: 1, title: 'Price' }});
|
| 378 |
+
const predSeries = priceChart.addLineSeries({{ color: '#2979ff', lineWidth: 1, lineStyle: 2, title: 'Forecast' }});
|
| 379 |
|
| 380 |
// 2. RAW
|
| 381 |
const rawChart = LightweightCharts.createChart(document.getElementById('tv-raw'), {{
|
| 382 |
...chartOpts,
|
| 383 |
localization: {{ timeFormatter: t => '$' + t.toFixed(2) }}
|
| 384 |
}});
|
| 385 |
+
const bidSeries = rawChart.addAreaSeries({{ lineColor: '#00ff9d', topColor: 'rgba(0, 255, 157, 0.15)', bottomColor: 'rgba(0,0,0,0)', lineWidth: 1 }});
|
| 386 |
+
const askSeries = rawChart.addAreaSeries({{ lineColor: '#ff3b3b', topColor: 'rgba(255, 59, 59, 0.15)', bottomColor: 'rgba(0,0,0,0)', lineWidth: 1 }});
|
| 387 |
|
| 388 |
// 3. NET
|
| 389 |
const netChart = LightweightCharts.createChart(document.getElementById('tv-net'), {{
|
| 390 |
...chartOpts,
|
| 391 |
localization: {{ timeFormatter: t => '$' + t.toFixed(2) }}
|
| 392 |
}});
|
| 393 |
+
const netSeries = netChart.addHistogramSeries({{ color: '#2979ff' }});
|
| 394 |
|
| 395 |
let activeLines = [];
|
| 396 |
|
| 397 |
+
// RESIZE HANDLER
|
| 398 |
+
new ResizeObserver(entries => {{
|
| 399 |
+
for(let entry of entries) {{
|
| 400 |
+
const {{width, height}} = entry.contentRect;
|
| 401 |
+
if(entry.target.id === 'tv-price') priceChart.applyOptions({{width, height}});
|
| 402 |
+
if(entry.target.id === 'tv-raw') rawChart.applyOptions({{width, height}});
|
| 403 |
+
if(entry.target.id === 'tv-net') netChart.applyOptions({{width, height}});
|
| 404 |
+
}}
|
| 405 |
+
}}).observe(document.body); // Observer attached to body to catch layout shifts, logic handles IDs
|
| 406 |
+
|
| 407 |
+
// Specific element observers
|
| 408 |
+
['tv-price', 'tv-raw', 'tv-net'].forEach(id => {{
|
| 409 |
+
new ResizeObserver(e => {{
|
| 410 |
+
if(id === 'tv-price') priceChart.applyOptions({{ width: e[0].contentRect.width, height: e[0].contentRect.height }});
|
| 411 |
+
if(id === 'tv-raw') rawChart.applyOptions({{ width: e[0].contentRect.width, height: e[0].contentRect.height }});
|
| 412 |
+
if(id === 'tv-net') netChart.applyOptions({{ width: e[0].contentRect.width, height: e[0].contentRect.height }});
|
| 413 |
+
}}).observe(document.getElementById(id));
|
| 414 |
+
}});
|
| 415 |
|
| 416 |
function connect() {{
|
| 417 |
const ws = new WebSocket((location.protocol === 'https:' ? 'wss' : 'ws') + '://' + location.host + '/ws');
|
|
|
|
| 460 |
let html = "";
|
| 461 |
|
| 462 |
const addWall = (w, type) => {{
|
| 463 |
+
const color = type === 'BID' ? '#00ff9d' : '#ff3b3b';
|
| 464 |
activeLines.push(priceSeries.createPriceLine({{ price: w.price, color: color, lineWidth: 1, lineStyle: 2, axisLabelVisible: false }}));
|
| 465 |
html += `<div class="list-item">
|
| 466 |
<span style="color:${{color}}">${{type}} ${{w.price}}</span>
|
| 467 |
+
<span class="c-dim">Z:${{w.z_score.toFixed(1)}}</span>
|
| 468 |
</div>`;
|
| 469 |
}};
|
| 470 |
|
| 471 |
data.walls.asks.forEach(w => addWall(w, 'ASK'));
|
| 472 |
data.walls.bids.forEach(w => addWall(w, 'BID'));
|
| 473 |
|
| 474 |
+
dom.wallList.innerHTML = html || '<span class="c-dim" style="font-size:11px">No significant walls.</span>';
|
| 475 |
}}
|
| 476 |
|
| 477 |
// DEPTH
|
|
|
|
| 481 |
const t = data.depth_x[i];
|
| 482 |
bids.push({{ time: t, value: data.depth_bids[i] }});
|
| 483 |
asks.push({{ time: t, value: data.depth_asks[i] }});
|
| 484 |
+
nets.push({{ time: t, value: data.depth_net[i], color: data.depth_net[i] > 0 ? '#00ff9d' : '#ff3b3b' }});
|
| 485 |
}}
|
| 486 |
bidSeries.setData(bids);
|
| 487 |
askSeries.setData(asks);
|