Update app.py
Browse files
app.py
CHANGED
|
@@ -5,11 +5,12 @@ import time
|
|
| 5 |
import bisect
|
| 6 |
import math
|
| 7 |
import statistics
|
|
|
|
| 8 |
from aiohttp import web
|
| 9 |
import websockets
|
| 10 |
|
| 11 |
# --- CONFIGURATION ---
|
| 12 |
-
SYMBOL_KRAKEN = "
|
| 13 |
PORT = 7860
|
| 14 |
HISTORY_LENGTH = 300
|
| 15 |
BROADCAST_RATE = 0.1
|
|
@@ -149,319 +150,324 @@ def process_market_data():
|
|
| 149 |
"walls": {"bids": bid_walls, "asks": ask_walls}
|
| 150 |
}
|
| 151 |
|
| 152 |
-
# --- FRONTEND (
|
| 153 |
HTML_PAGE = f"""
|
| 154 |
<!DOCTYPE html>
|
| 155 |
<html lang="en">
|
| 156 |
<head>
|
| 157 |
<meta charset="UTF-8">
|
| 158 |
-
<title>
|
| 159 |
<script src="https://unpkg.com/lightweight-charts@4.1.1/dist/lightweight-charts.standalone.production.js"></script>
|
| 160 |
-
<link href="https://fonts.googleapis.com/css2?family=
|
| 161 |
<style>
|
| 162 |
:root {{
|
| 163 |
-
--bg-
|
| 164 |
-
--panel
|
| 165 |
-
--border: #
|
| 166 |
-
--
|
| 167 |
-
--
|
| 168 |
-
--
|
| 169 |
-
--
|
| 170 |
-
--
|
| 171 |
-
--glass: rgba(15, 17, 22, 0.85);
|
| 172 |
}}
|
| 173 |
body {{
|
| 174 |
margin: 0; padding: 0;
|
| 175 |
-
background-color: var(--bg-
|
| 176 |
color: var(--text-main);
|
| 177 |
-
font-family: '
|
| 178 |
overflow: hidden;
|
| 179 |
height: 100vh; width: 100vw;
|
| 180 |
}}
|
| 181 |
|
| 182 |
-
/*
|
| 183 |
.layout {{
|
| 184 |
display: grid;
|
| 185 |
-
grid-template-rows:
|
| 186 |
grid-template-columns: 3fr 1fr;
|
| 187 |
-
gap:
|
|
|
|
| 188 |
height: 100vh;
|
| 189 |
-
padding: 5px;
|
| 190 |
box-sizing: border-box;
|
| 191 |
}}
|
| 192 |
|
| 193 |
-
|
| 194 |
-
|
|
|
|
|
|
|
| 195 |
grid-column: 1 / 3;
|
| 196 |
grid-row: 1 / 2;
|
| 197 |
-
background: var(--panel
|
| 198 |
-
border: 1px solid var(--border);
|
| 199 |
-
border-radius: 4px;
|
| 200 |
display: flex;
|
| 201 |
align-items: center;
|
| 202 |
-
padding: 0 20px;
|
| 203 |
justify-content: space-between;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
}}
|
| 205 |
-
.
|
| 206 |
-
.
|
| 207 |
-
|
|
|
|
| 208 |
|
| 209 |
-
/*
|
| 210 |
-
.panel {{ background: var(--panel-bg); border: 1px solid var(--border); border-radius: 4px; overflow: hidden; position: relative; display: flex; flex-direction: column; }}
|
| 211 |
-
|
| 212 |
#p-chart {{ grid-column: 1 / 2; grid-row: 2 / 3; }}
|
| 213 |
-
#p-depth {{ grid-column: 1 / 2; grid-row: 3 / 4; display: flex; flex-direction: row; gap: 5px; border: none; background: transparent; }}
|
| 214 |
|
| 215 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
|
| 217 |
-
/*
|
| 218 |
#p-sidebar {{
|
| 219 |
grid-column: 2 / 3;
|
| 220 |
grid-row: 2 / 4;
|
| 221 |
-
padding:
|
| 222 |
display: flex;
|
| 223 |
flex-direction: column;
|
| 224 |
-
gap:
|
| 225 |
-
overflow-y: auto;
|
| 226 |
}}
|
| 227 |
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
font-size: 10px;
|
| 233 |
-
font-weight: 700;
|
| 234 |
color: var(--text-dim);
|
| 235 |
-
|
| 236 |
-
letter-spacing: 0.5px;
|
| 237 |
-
}}
|
| 238 |
-
|
| 239 |
-
/* CARD STYLES */
|
| 240 |
-
.card {{
|
| 241 |
-
background: rgba(255,255,255,0.03);
|
| 242 |
-
border: 1px solid var(--border);
|
| 243 |
-
border-radius: 4px;
|
| 244 |
-
padding: 15px;
|
| 245 |
-
}}
|
| 246 |
-
.card-title {{ font-size: 10px; color: var(--text-dim); margin-bottom: 5px; display: block; }}
|
| 247 |
-
.card-value {{ font-size: 24px; font-weight: 700; display: block; }}
|
| 248 |
-
.card-sub {{ font-size: 12px; margin-left: 5px; font-weight: 400; }}
|
| 249 |
-
|
| 250 |
-
/* TEXT COLORS */
|
| 251 |
-
.green {{ color: var(--accent-green); }}
|
| 252 |
-
.red {{ color: var(--accent-red); }}
|
| 253 |
-
.blue {{ color: var(--accent-blue); }}
|
| 254 |
-
.dim {{ color: var(--text-dim); }}
|
| 255 |
-
|
| 256 |
-
/* WALL LIST */
|
| 257 |
-
.wall-item {{
|
| 258 |
-
display: flex; justify-content: space-between;
|
| 259 |
-
padding: 8px 5px; border-bottom: 1px solid #1a1d26; font-size: 11px;
|
| 260 |
}}
|
| 261 |
-
.wall-item:last-child {{ border: none; }}
|
| 262 |
-
|
| 263 |
-
/* LOADER */
|
| 264 |
-
#loader {{ position: fixed; top:0; left:0; width:100%; height:100%; background: #000; z-index: 999; display: flex; flex-direction: column; justify-content: center; align-items: center; color: var(--accent-blue); }}
|
| 265 |
</style>
|
| 266 |
</head>
|
| 267 |
<body>
|
| 268 |
|
| 269 |
-
<div id="loader">
|
| 270 |
-
<div style="font-family: 'Orbitron'; font-size: 24px;">SYSTEM INITIALIZING</div>
|
| 271 |
-
<div id="loading-status" style="margin-top: 10px; font-size: 12px; color: #666;">Connecting to Feed...</div>
|
| 272 |
-
</div>
|
| 273 |
-
|
| 274 |
<div class="layout">
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
<div class="
|
| 278 |
-
<span class="
|
| 279 |
-
<span
|
|
|
|
| 280 |
</div>
|
|
|
|
| 281 |
</div>
|
| 282 |
|
| 283 |
-
<!--
|
| 284 |
<div id="p-chart" class="panel">
|
| 285 |
-
<div class="
|
| 286 |
-
<div id="tv-price" style="flex: 1;
|
| 287 |
</div>
|
| 288 |
|
| 289 |
-
<!-- DEPTH CHARTS
|
| 290 |
<div id="p-depth">
|
| 291 |
-
<div class="sub
|
| 292 |
-
<div class="
|
| 293 |
-
<div id="tv-raw" style="flex: 1;
|
| 294 |
</div>
|
| 295 |
-
<div class="sub
|
| 296 |
-
<div class="
|
| 297 |
-
<div id="tv-net" style="flex: 1;
|
| 298 |
</div>
|
| 299 |
</div>
|
| 300 |
|
| 301 |
-
<!-- SIDEBAR
|
| 302 |
<div id="p-sidebar" class="panel">
|
| 303 |
|
| 304 |
-
<!--
|
| 305 |
-
<div class="
|
| 306 |
-
<span class="
|
| 307 |
-
<div style="display:
|
| 308 |
-
<span id="proj-
|
| 309 |
-
|
| 310 |
-
<!-- PERCENTAGE CHANGE INDICATOR -->
|
| 311 |
-
<div style="margin-top: 5px;">
|
| 312 |
-
<span class="card-title" style="display:inline;">DELTA: </span>
|
| 313 |
-
<span id="proj-pct" style="font-weight: bold;">--%</span>
|
| 314 |
</div>
|
| 315 |
</div>
|
| 316 |
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
<span
|
|
|
|
| 322 |
</div>
|
| 323 |
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
|
|
|
|
|
|
| 329 |
</div>
|
| 330 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
</div>
|
| 332 |
</div>
|
| 333 |
|
| 334 |
<script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 335 |
document.addEventListener('DOMContentLoaded', () => {{
|
| 336 |
const dom = {{
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
mainPrice: document.getElementById('main-price'),
|
| 340 |
-
scoreVal: document.getElementById('score-val'),
|
| 341 |
projVal: document.getElementById('proj-val'),
|
| 342 |
projPct: document.getElementById('proj-pct'),
|
| 343 |
wallList: document.getElementById('wall-list')
|
| 344 |
}};
|
| 345 |
|
| 346 |
-
// CHART
|
| 347 |
-
const
|
| 348 |
-
layout: {{ background: {{ type: 'solid', color: '#
|
| 349 |
-
grid: {{ vertLines: {{ color: '#
|
| 350 |
-
rightPriceScale: {{ borderColor: '#
|
| 351 |
-
timeScale: {{ borderColor: '#
|
| 352 |
-
crosshair: {{ mode: 1, vertLine: {{ color: '#
|
| 353 |
}};
|
| 354 |
|
| 355 |
-
// 1. PRICE
|
| 356 |
-
const priceChart = LightweightCharts.createChart(document.getElementById('tv-price'),
|
| 357 |
-
const priceSeries = priceChart.addLineSeries({{ color: '#
|
| 358 |
-
const predSeries = priceChart.addLineSeries({{ color: '#
|
| 359 |
|
| 360 |
-
// 2. RAW
|
| 361 |
const rawChart = LightweightCharts.createChart(document.getElementById('tv-raw'), {{
|
| 362 |
-
...
|
| 363 |
-
|
| 364 |
-
localization: {{ timeFormatter: t => 'Dist: $' + t.toFixed(2) }}
|
| 365 |
}});
|
| 366 |
-
const bidSeries = rawChart.addAreaSeries({{ lineColor: '#
|
| 367 |
-
const askSeries = rawChart.addAreaSeries({{ lineColor: '#
|
| 368 |
|
| 369 |
-
// 3. NET
|
| 370 |
const netChart = LightweightCharts.createChart(document.getElementById('tv-net'), {{
|
| 371 |
-
...
|
| 372 |
-
|
| 373 |
-
localization: {{ timeFormatter: t => 'Dist: $' + t.toFixed(2) }}
|
| 374 |
}});
|
| 375 |
-
const netSeries = netChart.addHistogramSeries({{ color: '#
|
| 376 |
|
| 377 |
-
let
|
| 378 |
|
| 379 |
-
// RESIZE
|
| 380 |
-
new ResizeObserver(
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
}});
|
| 386 |
}}).observe(document.body);
|
| 387 |
|
| 388 |
function connect() {{
|
| 389 |
-
const ws = new WebSocket((
|
| 390 |
|
| 391 |
-
ws.onopen = () => dom.status.innerText = "Stream Connected. Buffering...";
|
| 392 |
-
ws.onclose = () => {{ dom.loader.style.display = 'flex'; dom.status.innerText = "Reconnecting..."; setTimeout(connect, 3000); }};
|
| 393 |
-
|
| 394 |
ws.onmessage = (e) => {{
|
| 395 |
const data = JSON.parse(e.data);
|
| 396 |
if (data.error) return;
|
| 397 |
-
dom.loader.style.display = 'none';
|
| 398 |
|
| 399 |
-
//
|
| 400 |
if (data.history.length) {{
|
| 401 |
const hist = data.history.map(d => ({{ time: Math.floor(d.t), value: d.p }}));
|
| 402 |
-
const
|
| 403 |
-
priceSeries.setData(
|
| 404 |
-
|
| 405 |
-
const
|
| 406 |
-
dom.
|
| 407 |
|
| 408 |
-
//
|
| 409 |
if (data.analysis) {{
|
| 410 |
const proj = data.analysis.projected;
|
| 411 |
const score = data.analysis.net_score;
|
| 412 |
-
|
| 413 |
-
// 1. Draw projected line
|
| 414 |
predSeries.setData([
|
| 415 |
-
|
| 416 |
-
{{ time:
|
| 417 |
]);
|
| 418 |
|
| 419 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 420 |
dom.projVal.innerText = proj.toLocaleString('en-US', {{ minimumFractionDigits: 2 }});
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
dom.
|
| 424 |
-
dom.scoreVal.style.color = score > 0 ? "var(--accent-green)" : "var(--accent-red)";
|
| 425 |
-
|
| 426 |
-
// 3. CALCULATE PERCENTAGE CHANGE
|
| 427 |
-
const pctChange = ((proj - lastPrice) / lastPrice) * 100;
|
| 428 |
-
const sign = pctChange >= 0 ? "+" : "";
|
| 429 |
-
dom.projPct.innerText = `${{sign}}${{pctChange.toFixed(3)}}%`;
|
| 430 |
-
dom.projPct.style.color = pctChange >= 0 ? "var(--accent-green)" : "var(--accent-red)";
|
| 431 |
}}
|
| 432 |
}}
|
| 433 |
|
| 434 |
// WALLS
|
| 435 |
if (data.walls) {{
|
| 436 |
-
|
| 437 |
-
|
| 438 |
let html = "";
|
| 439 |
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 444 |
|
| 445 |
-
|
| 446 |
-
activePriceLines.push(priceSeries.createPriceLine({{ price: w.price, color: '#FF1744', lineWidth: 1, lineStyle: 2, axisLabelVisible: true, title: 'SELL WALL' }}));
|
| 447 |
-
html += `<div class="wall-item"><span class="red">ASK ${{w.price}}</span><span class="dim">Vol: ${{w.vol.toFixed(1)}} (Z:${{w.z_score.toFixed(1)}})</span></div>`;
|
| 448 |
-
}});
|
| 449 |
-
dom.wallList.innerHTML = html || '<div style="padding:10px; font-size:10px; color:#555;">No significant anomalies detected.</div>';
|
| 450 |
}}
|
| 451 |
|
| 452 |
// DEPTH
|
| 453 |
if (data.depth_x.length) {{
|
| 454 |
const bids = [], asks = [], nets = [];
|
| 455 |
for(let i=0; i<data.depth_x.length; i++) {{
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
|
|
|
| 459 |
}}
|
| 460 |
bidSeries.setData(bids);
|
| 461 |
askSeries.setData(asks);
|
| 462 |
netSeries.setData(nets);
|
| 463 |
}}
|
| 464 |
}};
|
|
|
|
|
|
|
| 465 |
}}
|
| 466 |
connect();
|
| 467 |
}});
|
|
|
|
| 5 |
import bisect
|
| 6 |
import math
|
| 7 |
import statistics
|
| 8 |
+
from datetime import datetime
|
| 9 |
from aiohttp import web
|
| 10 |
import websockets
|
| 11 |
|
| 12 |
# --- CONFIGURATION ---
|
| 13 |
+
SYMBOL_KRAKEN = "BTC/USD"
|
| 14 |
PORT = 7860
|
| 15 |
HISTORY_LENGTH = 300
|
| 16 |
BROADCAST_RATE = 0.1
|
|
|
|
| 150 |
"walls": {"bids": bid_walls, "asks": ask_walls}
|
| 151 |
}
|
| 152 |
|
| 153 |
+
# --- FRONTEND (MINIMALIST UI) ---
|
| 154 |
HTML_PAGE = f"""
|
| 155 |
<!DOCTYPE html>
|
| 156 |
<html lang="en">
|
| 157 |
<head>
|
| 158 |
<meta charset="UTF-8">
|
| 159 |
+
<title>Terminal | {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@400;500;600&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
|
| 162 |
<style>
|
| 163 |
:root {{
|
| 164 |
+
--bg-base: #050505;
|
| 165 |
+
--bg-panel: #0a0a0a;
|
| 166 |
+
--border: #1a1a1a;
|
| 167 |
+
--text-main: #e0e0e0;
|
| 168 |
+
--text-dim: #555555;
|
| 169 |
+
--green: #2ebd85;
|
| 170 |
+
--red: #f6465d;
|
| 171 |
+
--blue: #3772ff;
|
|
|
|
| 172 |
}}
|
| 173 |
body {{
|
| 174 |
margin: 0; padding: 0;
|
| 175 |
+
background-color: var(--bg-base);
|
| 176 |
color: var(--text-main);
|
| 177 |
+
font-family: 'Inter', sans-serif;
|
| 178 |
overflow: hidden;
|
| 179 |
height: 100vh; width: 100vw;
|
| 180 |
}}
|
| 181 |
|
| 182 |
+
/* THE GRID */
|
| 183 |
.layout {{
|
| 184 |
display: grid;
|
| 185 |
+
grid-template-rows: 28px 1fr 1fr; /* Slim header */
|
| 186 |
grid-template-columns: 3fr 1fr;
|
| 187 |
+
gap: 1px; /* The 'border' is just the gap showing the background */
|
| 188 |
+
background-color: var(--border); /* Acts as border color */
|
| 189 |
height: 100vh;
|
|
|
|
| 190 |
box-sizing: border-box;
|
| 191 |
}}
|
| 192 |
|
| 193 |
+
.panel {{ background: var(--bg-panel); position: relative; display: flex; flex-direction: column; overflow: hidden; }}
|
| 194 |
+
|
| 195 |
+
/* STATUS BAR HEADER */
|
| 196 |
+
.status-bar {{
|
| 197 |
grid-column: 1 / 3;
|
| 198 |
grid-row: 1 / 2;
|
| 199 |
+
background: var(--bg-panel);
|
|
|
|
|
|
|
| 200 |
display: flex;
|
| 201 |
align-items: center;
|
|
|
|
| 202 |
justify-content: space-between;
|
| 203 |
+
padding: 0 15px;
|
| 204 |
+
font-family: 'JetBrains Mono', monospace;
|
| 205 |
+
font-size: 11px;
|
| 206 |
+
text-transform: uppercase;
|
| 207 |
}}
|
| 208 |
+
.status-left {{ display: flex; gap: 15px; align-items: center; }}
|
| 209 |
+
.status-right {{ color: var(--text-dim); }}
|
| 210 |
+
.live-dot {{ width: 6px; height: 6px; background-color: var(--green); border-radius: 50%; display: inline-block; box-shadow: 0 0 5px var(--green); }}
|
| 211 |
+
.symbol-tag {{ color: var(--text-dim); font-weight: 600; color: #fff; }}
|
| 212 |
|
| 213 |
+
/* MAIN CHART AREA */
|
|
|
|
|
|
|
| 214 |
#p-chart {{ grid-column: 1 / 2; grid-row: 2 / 3; }}
|
|
|
|
| 215 |
|
| 216 |
+
/* DEPTH AREA */
|
| 217 |
+
#p-depth {{
|
| 218 |
+
grid-column: 1 / 2; grid-row: 3 / 4;
|
| 219 |
+
display: grid;
|
| 220 |
+
grid-template-columns: 1fr 1fr;
|
| 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 {{
|
| 228 |
grid-column: 2 / 3;
|
| 229 |
grid-row: 2 / 4;
|
| 230 |
+
padding: 20px;
|
| 231 |
display: flex;
|
| 232 |
flex-direction: column;
|
| 233 |
+
gap: 30px;
|
|
|
|
| 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: 500; letter-spacing: 0.5px; text-transform: uppercase; }}
|
| 239 |
+
.value {{ font-family: 'JetBrains Mono', monospace; font-size: 20px; font-weight: 400; color: #fff; }}
|
| 240 |
+
.value-lg {{ font-size: 28px; font-weight: 500; }}
|
| 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 |
+
|
| 245 |
+
/* COLORS */
|
| 246 |
+
.c-green {{ color: var(--green); }}
|
| 247 |
+
.c-red {{ color: var(--red); }}
|
| 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 |
+
color: #888;
|
| 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>
|
| 271 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
<div class="layout">
|
| 273 |
+
<!-- STATUS BAR -->
|
| 274 |
+
<div class="status-bar">
|
| 275 |
+
<div class="status-left">
|
| 276 |
+
<span class="live-dot"></span>
|
| 277 |
+
<span class="symbol-tag">{SYMBOL_KRAKEN}</span>
|
| 278 |
+
<span id="price-ticker" style="color: #fff;">---</span>
|
| 279 |
</div>
|
| 280 |
+
<div class="status-right" id="clock">--:--:-- UTC</div>
|
| 281 |
</div>
|
| 282 |
|
| 283 |
+
<!-- PRICE CHART -->
|
| 284 |
<div id="p-chart" class="panel">
|
| 285 |
+
<div class="chart-label">PRICE ACTION // ANOMALIES</div>
|
| 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-label">LIQUIDITY DENSITY</div>
|
| 293 |
+
<div id="tv-raw" style="flex: 1;"></div>
|
| 294 |
</div>
|
| 295 |
+
<div class="depth-sub">
|
| 296 |
+
<div class="chart-label">NET IMBALANCE</div>
|
| 297 |
+
<div id="tv-net" style="flex: 1;"></div>
|
| 298 |
</div>
|
| 299 |
</div>
|
| 300 |
|
| 301 |
+
<!-- SIDEBAR -->
|
| 302 |
<div id="p-sidebar" class="panel">
|
| 303 |
|
| 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: 8px;">
|
| 308 |
+
<span id="proj-pct" class="value value-lg">--%</span>
|
| 309 |
+
<span id="proj-val" class="value-sub c-dim">---</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
</div>
|
| 311 |
</div>
|
| 312 |
|
| 313 |
+
<div class="divider"></div>
|
| 314 |
+
|
| 315 |
+
<!-- 2. IMBALANCE SCORE -->
|
| 316 |
+
<div class="data-group">
|
| 317 |
+
<span class="label">Order Flow Imbalance</span>
|
| 318 |
+
<span id="score-val" class="value">0.00</span>
|
| 319 |
</div>
|
| 320 |
|
| 321 |
+
<div class="divider"></div>
|
| 322 |
+
|
| 323 |
+
<!-- 3. WALLS -->
|
| 324 |
+
<div class="data-group" style="flex: 1;">
|
| 325 |
+
<span class="label" style="margin-bottom: 10px;">Significant Structures (Z > 3.0)</span>
|
| 326 |
+
<div id="wall-list" class="list-container">
|
| 327 |
+
<span class="c-dim" style="font-size: 11px;">Scanning...</span>
|
| 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">12ms</div>
|
| 334 |
+
</div>
|
| 335 |
</div>
|
| 336 |
</div>
|
| 337 |
|
| 338 |
<script>
|
| 339 |
+
// UPDATE CLOCK
|
| 340 |
+
setInterval(() => {{
|
| 341 |
+
const now = new Date();
|
| 342 |
+
document.getElementById('clock').innerText = now.toISOString().split('T')[1].split('.')[0] + ' UTC';
|
| 343 |
+
}}, 1000);
|
| 344 |
+
|
| 345 |
document.addEventListener('DOMContentLoaded', () => {{
|
| 346 |
const dom = {{
|
| 347 |
+
ticker: document.getElementById('price-ticker'),
|
| 348 |
+
score: document.getElementById('score-val'),
|
|
|
|
|
|
|
| 349 |
projVal: document.getElementById('proj-val'),
|
| 350 |
projPct: document.getElementById('proj-pct'),
|
| 351 |
wallList: document.getElementById('wall-list')
|
| 352 |
}};
|
| 353 |
|
| 354 |
+
// MINIMALIST CHART CONFIG
|
| 355 |
+
const chartOpts = {{
|
| 356 |
+
layout: {{ background: {{ type: 'solid', color: '#0a0a0a' }}, textColor: '#444', fontFamily: 'JetBrains Mono' }},
|
| 357 |
+
grid: {{ vertLines: {{ color: '#111' }}, horzLines: {{ color: '#111' }} }},
|
| 358 |
+
rightPriceScale: {{ borderColor: '#111', scaleMargins: {{ top: 0.1, bottom: 0.1 }} }},
|
| 359 |
+
timeScale: {{ borderColor: '#111', timeVisible: true, secondsVisible: true }},
|
| 360 |
+
crosshair: {{ mode: 1, vertLine: {{ color: '#333', labelBackgroundColor: '#333' }}, horzLine: {{ color: '#333', labelBackgroundColor: '#333' }} }}
|
| 361 |
}};
|
| 362 |
|
| 363 |
+
// 1. PRICE
|
| 364 |
+
const priceChart = LightweightCharts.createChart(document.getElementById('tv-price'), chartOpts);
|
| 365 |
+
const priceSeries = priceChart.addLineSeries({{ color: '#e0e0e0', lineWidth: 1, title: 'Price' }});
|
| 366 |
+
const predSeries = priceChart.addLineSeries({{ color: '#3772ff', lineWidth: 1, lineStyle: 2, title: 'Forecast' }});
|
| 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: '#2ebd85', topColor: 'rgba(46, 189, 133, 0.1)', bottomColor: 'rgba(0,0,0,0)', lineWidth: 1 }});
|
| 374 |
+
const askSeries = rawChart.addAreaSeries({{ lineColor: '#f6465d', topColor: 'rgba(246, 70, 93, 0.1)', bottomColor: 'rgba(0,0,0,0)', lineWidth: 1 }});
|
| 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: '#3772ff' }});
|
| 382 |
|
| 383 |
+
let activeLines = [];
|
| 384 |
|
| 385 |
+
// RESIZE
|
| 386 |
+
new ResizeObserver(e => {{
|
| 387 |
+
e.forEach(x => {{
|
| 388 |
+
if(x.target.id === 'tv-price') priceChart.applyOptions({{ width: x.contentRect.width, height: x.contentRect.height }});
|
| 389 |
+
if(x.target.id === 'tv-raw') rawChart.applyOptions({{ width: x.contentRect.width, height: x.contentRect.height }});
|
| 390 |
+
if(x.target.id === 'tv-net') netChart.applyOptions({{ width: x.contentRect.width, height: x.contentRect.height }});
|
| 391 |
}});
|
| 392 |
}}).observe(document.body);
|
| 393 |
|
| 394 |
function connect() {{
|
| 395 |
+
const ws = new WebSocket((location.protocol === 'https:' ? 'wss' : 'ws') + '://' + location.host + '/ws');
|
| 396 |
|
|
|
|
|
|
|
|
|
|
| 397 |
ws.onmessage = (e) => {{
|
| 398 |
const data = JSON.parse(e.data);
|
| 399 |
if (data.error) return;
|
|
|
|
| 400 |
|
| 401 |
+
// HISTORY & PRICE
|
| 402 |
if (data.history.length) {{
|
| 403 |
const hist = data.history.map(d => ({{ time: Math.floor(d.t), value: d.p }}));
|
| 404 |
+
const cleanHist = [...new Map(hist.map(i => [i.time, i])).values()];
|
| 405 |
+
priceSeries.setData(cleanHist);
|
| 406 |
+
|
| 407 |
+
const lastP = cleanHist[cleanHist.length-1].value;
|
| 408 |
+
dom.ticker.innerText = lastP.toLocaleString('en-US', {{ minimumFractionDigits: 2 }});
|
| 409 |
|
| 410 |
+
// ANALYSIS
|
| 411 |
if (data.analysis) {{
|
| 412 |
const proj = data.analysis.projected;
|
| 413 |
const score = data.analysis.net_score;
|
| 414 |
+
|
|
|
|
| 415 |
predSeries.setData([
|
| 416 |
+
cleanHist[cleanHist.length-1],
|
| 417 |
+
{{ time: cleanHist[cleanHist.length-1].time + 60, value: proj }}
|
| 418 |
]);
|
| 419 |
|
| 420 |
+
// PCT CALC
|
| 421 |
+
const pct = ((proj - lastP) / lastP) * 100;
|
| 422 |
+
const sign = pct >= 0 ? "+" : "";
|
| 423 |
+
|
| 424 |
+
dom.projPct.innerText = `${{sign}}${{pct.toFixed(4)}}%`;
|
| 425 |
+
dom.projPct.style.color = pct >= 0 ? "var(--green)" : "var(--red)";
|
| 426 |
+
|
| 427 |
dom.projVal.innerText = proj.toLocaleString('en-US', {{ minimumFractionDigits: 2 }});
|
| 428 |
+
|
| 429 |
+
dom.score.innerText = score.toFixed(2);
|
| 430 |
+
dom.score.style.color = score > 0 ? "var(--green)" : (score < 0 ? "var(--red)" : "var(--text-main)");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
}}
|
| 432 |
}}
|
| 433 |
|
| 434 |
// WALLS
|
| 435 |
if (data.walls) {{
|
| 436 |
+
activeLines.forEach(l => priceSeries.removePriceLine(l));
|
| 437 |
+
activeLines = [];
|
| 438 |
let html = "";
|
| 439 |
|
| 440 |
+
const addWall = (w, type) => {{
|
| 441 |
+
const color = type === 'BID' ? '#2ebd85' : '#f6465d';
|
| 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 anomalies.</span>';
|
|
|
|
|
|
|
|
|
|
|
|
|
| 453 |
}}
|
| 454 |
|
| 455 |
// DEPTH
|
| 456 |
if (data.depth_x.length) {{
|
| 457 |
const bids = [], asks = [], nets = [];
|
| 458 |
for(let i=0; i<data.depth_x.length; i++) {{
|
| 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 ? '#2ebd85' : '#f6465d' }});
|
| 463 |
}}
|
| 464 |
bidSeries.setData(bids);
|
| 465 |
askSeries.setData(asks);
|
| 466 |
netSeries.setData(nets);
|
| 467 |
}}
|
| 468 |
}};
|
| 469 |
+
|
| 470 |
+
ws.onclose = () => setTimeout(connect, 2000);
|
| 471 |
}}
|
| 472 |
connect();
|
| 473 |
}});
|