Update app.py
Browse files
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 |
|
|
@@ -24,113 +25,77 @@ market_state = {
|
|
| 24 |
"ready": False
|
| 25 |
}
|
| 26 |
|
| 27 |
-
# --- AI Logic Helper ---
|
| 28 |
-
def
|
| 29 |
"""
|
| 30 |
-
Analyzes the
|
| 31 |
-
Splits data into 'Near' (Close to price) and 'Deep' (Far from price).
|
| 32 |
"""
|
| 33 |
-
if not diff_y or len(diff_y) <
|
| 34 |
-
return
|
| 35 |
-
|
| 36 |
-
# 1.
|
| 37 |
-
#
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
-
|
| 45 |
-
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
)
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
)
|
| 71 |
-
|
| 72 |
-
# 3. CONSISTENTLY POSITIVE (Full Bull)
|
| 73 |
-
elif near_avg > 0 and deep_avg > 0:
|
| 74 |
-
sentiment = "bullish"
|
| 75 |
-
strength = "Mild" if deep_avg < 50 else "Extreme"
|
| 76 |
-
msg = (
|
| 77 |
-
f"π <b>FULL BULLISH CONVICTION:</b><br>"
|
| 78 |
-
f"Bids exceed Asks at both local and deep levels.<br>"
|
| 79 |
-
f"<i>Why?</i> There is no overhead supply. Buyers are aggressive at the current price AND stacking orders below. "
|
| 80 |
-
f"Path of least resistance is purely UP."
|
| 81 |
-
)
|
| 82 |
-
|
| 83 |
-
# 4. CONSISTENTLY NEGATIVE (Full Bear)
|
| 84 |
-
elif near_avg < 0 and deep_avg < 0:
|
| 85 |
-
sentiment = "bearish"
|
| 86 |
-
msg = (
|
| 87 |
-
f"π» <b>FULL BEARISH CONVICTION:</b><br>"
|
| 88 |
-
f"Asks exceed Bids at all levels.<br>"
|
| 89 |
-
f"<i>Why?</i> There is no support below. Sellers are active at the current price AND stacking orders higher. "
|
| 90 |
-
f"Price is likely to drift lower until buyers step in."
|
| 91 |
-
)
|
| 92 |
-
|
| 93 |
-
# 5. MIXED / CHOP
|
| 94 |
-
else:
|
| 95 |
-
diff = near_avg - deep_avg
|
| 96 |
-
if abs(diff) < 5:
|
| 97 |
-
msg = "βοΈ <b>PERFECT BALANCE:</b> Liquidity is symmetrical. Market is waiting for a trigger."
|
| 98 |
-
else:
|
| 99 |
-
msg = "π <b>TRANSITIONING:</b> Orderbook structure is shifting. Watch for a dominant side to emerge."
|
| 100 |
-
|
| 101 |
-
return {"text": msg, "sentiment": sentiment}
|
| 102 |
|
| 103 |
# --- HTML Frontend ---
|
| 104 |
HTML_PAGE = f"""
|
| 105 |
<!DOCTYPE html>
|
| 106 |
<html>
|
| 107 |
<head>
|
| 108 |
-
<title>BTC-USD
|
| 109 |
<script src="https://cdn.plot.ly/plotly-2.24.1.min.js"></script>
|
| 110 |
<style>
|
| 111 |
body {{ margin: 0; padding: 0; background-color: #0e0e0e; color: #ccc; font-family: 'Courier New', monospace; overflow: hidden; }}
|
| 112 |
|
| 113 |
#container {{ display: flex; flex-direction: column; height: 100vh; width: 100vw; }}
|
| 114 |
|
| 115 |
-
/* Row 1: Charts */
|
| 116 |
#row-top {{ flex: 1; display: flex; width: 100%; border-bottom: 1px solid #333; }}
|
| 117 |
-
|
| 118 |
-
/* Row 2: Charts + AI */
|
| 119 |
#row-bot {{ flex: 1; display: flex; width: 100%; }}
|
| 120 |
|
| 121 |
.col {{ width: 50%; height: 100%; border-right: 1px solid #333; position: relative; }}
|
| 122 |
-
.col-ai {{ width: 50%; height: 100%; background-color: #
|
| 123 |
|
| 124 |
-
|
| 125 |
-
.
|
| 126 |
-
.log-
|
| 127 |
-
.log-time {{ color: #555; font-size: 11px; display: block; margin-bottom: 4px; }}
|
| 128 |
|
| 129 |
-
|
| 130 |
-
.
|
| 131 |
-
.
|
| 132 |
-
.neutral {{ border-left-color: #999; }}
|
| 133 |
-
.warning {{ border-left-color: #ff9800; }}
|
| 134 |
|
| 135 |
.chart {{ width: 100%; height: 100%; }}
|
| 136 |
|
|
@@ -140,12 +105,13 @@ HTML_PAGE = f"""
|
|
| 140 |
</style>
|
| 141 |
</head>
|
| 142 |
<body>
|
| 143 |
-
<div id="status">
|
| 144 |
|
| 145 |
<div id="container">
|
| 146 |
<!-- ROW 1 -->
|
| 147 |
<div id="row-top">
|
| 148 |
<div class="col">
|
|
|
|
| 149 |
<div id="price-chart" class="chart"></div>
|
| 150 |
</div>
|
| 151 |
<div class="col">
|
|
@@ -156,12 +122,10 @@ HTML_PAGE = f"""
|
|
| 156 |
<!-- ROW 2 -->
|
| 157 |
<div id="row-bot">
|
| 158 |
<div class="col">
|
| 159 |
-
<!-- NET DIFFERENCE CHART -->
|
| 160 |
<div id="diff-chart" class="chart"></div>
|
| 161 |
</div>
|
| 162 |
<div class="col-ai">
|
| 163 |
-
|
| 164 |
-
<div class="terminal-header">> DEPTH STRUCTURE ANALYSIS</div>
|
| 165 |
<div id="terminal-logs"></div>
|
| 166 |
</div>
|
| 167 |
</div>
|
|
@@ -188,19 +152,15 @@ HTML_PAGE = f"""
|
|
| 188 |
yaxis: {{ gridcolor: '#222' }}
|
| 189 |
}};
|
| 190 |
|
| 191 |
-
function addLog(
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
lastLogText = data.comment.text;
|
| 195 |
|
| 196 |
const div = document.createElement('div');
|
| 197 |
-
div.className = `log-entry ${{
|
| 198 |
-
|
| 199 |
-
div.innerHTML = `<span class="log-time">${{timeStr}}</span> ${{data.comment.text}}`;
|
| 200 |
-
|
| 201 |
termLogs.prepend(div);
|
| 202 |
-
|
| 203 |
-
if (termLogs.children.length > 10) termLogs.removeChild(termLogs.lastChild);
|
| 204 |
}}
|
| 205 |
|
| 206 |
async function updateCharts() {{
|
|
@@ -215,41 +175,105 @@ HTML_PAGE = f"""
|
|
| 215 |
|
| 216 |
statusDiv.innerHTML = `Mid: <span class="${{data.mid >= data.prev_mid ? 'green' : 'red'}}">$${{data.mid.toLocaleString(undefined, {{minimumFractionDigits: 2}})}}</span>`;
|
| 217 |
|
| 218 |
-
// 1.
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
|
| 223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
const tracesVol = [
|
| 225 |
-
{{ x: data.vol.dist_bids, y: data.vol.vol_bids, type: 'scatter', name: '
|
| 226 |
{{ x: data.vol.dist_asks, y: data.vol.vol_asks, type: 'scatter', name: 'Ask', line: {{color: '#ff1744'}} }}
|
| 227 |
];
|
| 228 |
if (!initVol) {{ Plotly.newPlot(volDiv, tracesVol, {{ ...commonLayout, title: '<b>Cumulative Volume</b>', xaxis: {{title:'Distance ($)'}} }}, commonConfig); initVol = true; }}
|
| 229 |
else {{ Plotly.react(volDiv, tracesVol, {{ ...commonLayout, title: '<b>Cumulative Volume</b>', xaxis: {{title:'Distance ($)'}} }}, commonConfig); }}
|
| 230 |
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
mode: 'lines',
|
| 237 |
-
fill: 'tozeroy',
|
| 238 |
-
line: {{color: '#e040fb', width: 2}}
|
| 239 |
-
}};
|
| 240 |
-
// Draw a zero line for reference
|
| 241 |
-
const layoutDiff = {{
|
| 242 |
...commonLayout,
|
| 243 |
title: '<b>Net Liquidity (Bids - Asks)</b>',
|
| 244 |
xaxis: {{title:'Distance ($)'}},
|
| 245 |
-
shapes: [{{ type: 'line', x0: 0, x1: 1, xref: 'paper', y0: 0, y1: 0, line: {{color: '#666', width: 1
|
| 246 |
}};
|
| 247 |
|
| 248 |
-
if (!initDiff) {{ Plotly.newPlot(diffDiv, [traceDiff],
|
| 249 |
-
else {{ Plotly.react(diffDiv, [traceDiff],
|
| 250 |
-
|
| 251 |
-
// 4. AI Log
|
| 252 |
-
addLog(data);
|
| 253 |
|
| 254 |
}} catch (e) {{ console.error("Fetch error:", e); }}
|
| 255 |
}}
|
|
@@ -318,7 +342,7 @@ async def handle_data(request):
|
|
| 318 |
raw_bids = sorted(market_state['bids'].items(), key=lambda x: -x[0])[:300]
|
| 319 |
raw_asks = sorted(market_state['asks'].items(), key=lambda x: x[0])[:300]
|
| 320 |
|
| 321 |
-
# Calculate Distances
|
| 322 |
d_b_x, d_b_y, cum = [], [], 0
|
| 323 |
for p, q in raw_bids:
|
| 324 |
d = mid - p
|
|
@@ -348,19 +372,18 @@ async def handle_data(request):
|
|
| 348 |
idx_a = bisect.bisect_right(d_a_x, s)
|
| 349 |
vol_a = d_a_y[idx_a-1] if idx_a > 0 else 0
|
| 350 |
|
| 351 |
-
val = vol_b - vol_a
|
| 352 |
diff_x.append(s)
|
| 353 |
-
diff_y.append(
|
| 354 |
|
| 355 |
-
# ---
|
| 356 |
-
|
| 357 |
|
| 358 |
return web.json_response({
|
| 359 |
"mid": mid,
|
| 360 |
"prev_mid": market_state['prev_mid'],
|
| 361 |
"vol": { "dist_bids": d_b_x, "vol_bids": d_b_y, "dist_asks": d_a_x, "vol_asks": d_a_y },
|
| 362 |
"diff": { "x": diff_x, "y": diff_y },
|
| 363 |
-
"
|
| 364 |
"history": market_state['history']
|
| 365 |
})
|
| 366 |
|
|
@@ -384,7 +407,7 @@ async def main():
|
|
| 384 |
site = web.TCPSite(runner, '0.0.0.0', PORT)
|
| 385 |
await site.start()
|
| 386 |
|
| 387 |
-
print(f"π BTC-USD
|
| 388 |
await asyncio.Event().wait()
|
| 389 |
|
| 390 |
if __name__ == "__main__":
|
|
|
|
| 3 |
import logging
|
| 4 |
import time
|
| 5 |
import bisect
|
| 6 |
+
import random
|
| 7 |
from aiohttp import web
|
| 8 |
import websockets
|
| 9 |
|
|
|
|
| 25 |
"ready": False
|
| 26 |
}
|
| 27 |
|
| 28 |
+
# --- AI Logic Helper (Prediction & S/R) ---
|
| 29 |
+
def analyze_structure(diff_x, diff_y, current_mid):
|
| 30 |
"""
|
| 31 |
+
Analyzes the Net Liquidity Curve to find Support, Resistance, and Projected Trend.
|
|
|
|
| 32 |
"""
|
| 33 |
+
if not diff_y or len(diff_y) < 5:
|
| 34 |
+
return None
|
| 35 |
+
|
| 36 |
+
# 1. Momentum Projection (Weighted average of the whole book)
|
| 37 |
+
# If the book is net positive, price wants to go up.
|
| 38 |
+
net_total = diff_y[-1] # Cumulative sum at max depth
|
| 39 |
+
# Scaling factor: heavily damped to prevent unrealistic predictions
|
| 40 |
+
momentum_shift = net_total * 0.5
|
| 41 |
+
projected_price = current_mid + momentum_shift
|
| 42 |
+
|
| 43 |
+
# 2. Find Structural Reversals (Zero Crossings)
|
| 44 |
+
support_level = None
|
| 45 |
+
resistance_level = None
|
| 46 |
|
| 47 |
+
# We look at the 'Near' book (first 50% of depth) for immediate reversal levels
|
| 48 |
+
scan_limit = len(diff_y) // 2
|
| 49 |
|
| 50 |
+
for i in range(1, scan_limit):
|
| 51 |
+
prev_val = diff_y[i-1]
|
| 52 |
+
curr_val = diff_y[i]
|
| 53 |
+
dist = diff_x[i]
|
| 54 |
+
|
| 55 |
+
# Case A: Positive -> Negative (The "Ceiling")
|
| 56 |
+
# Buyers were in control, but suddenly Sellers take over at this distance.
|
| 57 |
+
# This implies a Resistance Wall at (Mid + Distance).
|
| 58 |
+
if prev_val > 0 and curr_val < 0 and resistance_level is None:
|
| 59 |
+
resistance_level = current_mid + dist
|
| 60 |
+
|
| 61 |
+
# Case B: Negative -> Positive (The "Floor")
|
| 62 |
+
# Sellers were in control, but suddenly Buyers take over at this distance.
|
| 63 |
+
# This implies a Support Wall at (Mid - Distance).
|
| 64 |
+
if prev_val < 0 and curr_val > 0 and support_level is None:
|
| 65 |
+
support_level = current_mid - dist
|
| 66 |
+
|
| 67 |
+
return {
|
| 68 |
+
"projected": projected_price,
|
| 69 |
+
"support": support_level,
|
| 70 |
+
"resistance": resistance_level,
|
| 71 |
+
"net_score": net_total
|
| 72 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
# --- HTML Frontend ---
|
| 75 |
HTML_PAGE = f"""
|
| 76 |
<!DOCTYPE html>
|
| 77 |
<html>
|
| 78 |
<head>
|
| 79 |
+
<title>BTC-USD Predictive Dashboard</title>
|
| 80 |
<script src="https://cdn.plot.ly/plotly-2.24.1.min.js"></script>
|
| 81 |
<style>
|
| 82 |
body {{ margin: 0; padding: 0; background-color: #0e0e0e; color: #ccc; font-family: 'Courier New', monospace; overflow: hidden; }}
|
| 83 |
|
| 84 |
#container {{ display: flex; flex-direction: column; height: 100vh; width: 100vw; }}
|
| 85 |
|
|
|
|
| 86 |
#row-top {{ flex: 1; display: flex; width: 100%; border-bottom: 1px solid #333; }}
|
|
|
|
|
|
|
| 87 |
#row-bot {{ flex: 1; display: flex; width: 100%; }}
|
| 88 |
|
| 89 |
.col {{ width: 50%; height: 100%; border-right: 1px solid #333; position: relative; }}
|
| 90 |
+
.col-ai {{ width: 50%; height: 100%; background-color: #050505; display: flex; flex-direction: column; padding: 10px; overflow-y: auto; }}
|
| 91 |
|
| 92 |
+
.terminal-header {{ color: #00bcd4; font-weight: bold; border-bottom: 1px dashed #333; padding-bottom: 5px; margin-bottom: 10px; }}
|
| 93 |
+
.log-entry {{ margin-bottom: 8px; font-size: 13px; border-left: 3px solid transparent; padding-left: 8px; }}
|
| 94 |
+
.log-time {{ color: #555; font-size: 11px; margin-right: 8px; }}
|
|
|
|
| 95 |
|
| 96 |
+
.bullish {{ border-left-color: #00e676; color: #e8f5e9; }}
|
| 97 |
+
.bearish {{ border-left-color: #ff1744; color: #ffebee; }}
|
| 98 |
+
.neutral {{ border-left-color: #999; color: #ccc; }}
|
|
|
|
|
|
|
| 99 |
|
| 100 |
.chart {{ width: 100%; height: 100%; }}
|
| 101 |
|
|
|
|
| 105 |
</style>
|
| 106 |
</head>
|
| 107 |
<body>
|
| 108 |
+
<div id="status">Initializing AI Model...</div>
|
| 109 |
|
| 110 |
<div id="container">
|
| 111 |
<!-- ROW 1 -->
|
| 112 |
<div id="row-top">
|
| 113 |
<div class="col">
|
| 114 |
+
<!-- PRICE + PREDICTION -->
|
| 115 |
<div id="price-chart" class="chart"></div>
|
| 116 |
</div>
|
| 117 |
<div class="col">
|
|
|
|
| 122 |
<!-- ROW 2 -->
|
| 123 |
<div id="row-bot">
|
| 124 |
<div class="col">
|
|
|
|
| 125 |
<div id="diff-chart" class="chart"></div>
|
| 126 |
</div>
|
| 127 |
<div class="col-ai">
|
| 128 |
+
<div class="terminal-header">> PREDICTIVE ANALYTICS ENGINE</div>
|
|
|
|
| 129 |
<div id="terminal-logs"></div>
|
| 130 |
</div>
|
| 131 |
</div>
|
|
|
|
| 152 |
yaxis: {{ gridcolor: '#222' }}
|
| 153 |
}};
|
| 154 |
|
| 155 |
+
function addLog(text, type) {{
|
| 156 |
+
if (text === lastLogText) return;
|
| 157 |
+
lastLogText = text;
|
|
|
|
| 158 |
|
| 159 |
const div = document.createElement('div');
|
| 160 |
+
div.className = `log-entry ${{type}}`;
|
| 161 |
+
div.innerHTML = `<span class="log-time">${{new Date().toLocaleTimeString()}}</span> ${{text}}`;
|
|
|
|
|
|
|
| 162 |
termLogs.prepend(div);
|
| 163 |
+
if (termLogs.children.length > 15) termLogs.removeChild(termLogs.lastChild);
|
|
|
|
| 164 |
}}
|
| 165 |
|
| 166 |
async function updateCharts() {{
|
|
|
|
| 175 |
|
| 176 |
statusDiv.innerHTML = `Mid: <span class="${{data.mid >= data.prev_mid ? 'green' : 'red'}}">$${{data.mid.toLocaleString(undefined, {{minimumFractionDigits: 2}})}}</span>`;
|
| 177 |
|
| 178 |
+
// --- 1. PRICE CHART WITH PREDICTION & S/R LINES ---
|
| 179 |
+
|
| 180 |
+
// Historical Price Trace
|
| 181 |
+
const historyX = data.history.map(d => new Date(d.t*1000));
|
| 182 |
+
const historyY = data.history.map(d => d.p);
|
| 183 |
+
const tracePrice = {{ x: historyX, y: historyY, type: 'scatter', mode:'lines', line: {{color: '#29b6f6', width: 2}}, name: 'Price' }};
|
| 184 |
+
|
| 185 |
+
// Future Projection Trace
|
| 186 |
+
let traceProj = {{}};
|
| 187 |
+
const lastTime = historyX[historyX.length-1];
|
| 188 |
+
if (data.analysis && lastTime) {{
|
| 189 |
+
const futureTime = new Date(lastTime.getTime() + 60000); // +1 min
|
| 190 |
+
traceProj = {{
|
| 191 |
+
x: [lastTime, futureTime],
|
| 192 |
+
y: [historyY[historyY.length-1], data.analysis.projected],
|
| 193 |
+
type: 'scatter', mode: 'lines',
|
| 194 |
+
line: {{color: '#ff9800', width: 2, dash: 'dot'}},
|
| 195 |
+
name: 'Forecast'
|
| 196 |
+
}};
|
| 197 |
+
}}
|
| 198 |
+
|
| 199 |
+
// Support & Resistance Shapes
|
| 200 |
+
const shapes = [];
|
| 201 |
+
let logMsg = "";
|
| 202 |
+
let logType = "neutral";
|
| 203 |
+
|
| 204 |
+
if (data.analysis) {{
|
| 205 |
+
// Support Line (Green)
|
| 206 |
+
if (data.analysis.support) {{
|
| 207 |
+
shapes.push({{
|
| 208 |
+
type: 'line', x0: historyX[0], x1: lastTime,
|
| 209 |
+
y0: data.analysis.support, y1: data.analysis.support,
|
| 210 |
+
line: {{color: '#00e676', width: 1, dash: 'dash'}}
|
| 211 |
+
}});
|
| 212 |
+
// Add Annotation via Layout? keeping it simple with shapes for now
|
| 213 |
+
}}
|
| 214 |
+
|
| 215 |
+
// Resistance Line (Red)
|
| 216 |
+
if (data.analysis.resistance) {{
|
| 217 |
+
shapes.push({{
|
| 218 |
+
type: 'line', x0: historyX[0], x1: lastTime,
|
| 219 |
+
y0: data.analysis.resistance, y1: data.analysis.resistance,
|
| 220 |
+
line: {{color: '#ff1744', width: 1, dash: 'dash'}}
|
| 221 |
+
}});
|
| 222 |
+
}}
|
| 223 |
+
|
| 224 |
+
// AI Commentary Logic based on Prediction
|
| 225 |
+
const net = data.analysis.net_score;
|
| 226 |
+
if (data.analysis.support && !data.analysis.resistance) {{
|
| 227 |
+
logMsg = `π§± <b>FLOOR DETECTED:</b> Strong Support at $${{data.analysis.support.toFixed(0)}}. Price expected to bounce.`;
|
| 228 |
+
logType = "bullish";
|
| 229 |
+
}} else if (!data.analysis.support && data.analysis.resistance) {{
|
| 230 |
+
logMsg = `π§± <b>CEILING DETECTED:</b> Resistance at $${{data.analysis.resistance.toFixed(0)}}. Upside capped.`;
|
| 231 |
+
logType = "bearish";
|
| 232 |
+
}} else if (net > 30) {{
|
| 233 |
+
logMsg = `π <b>MOMENTUM:</b> Liquidity is purely Bullish. Target: $${{data.analysis.projected.toFixed(0)}}`;
|
| 234 |
+
logType = "bullish";
|
| 235 |
+
}} else if (net < -30) {{
|
| 236 |
+
logMsg = `π <b>MOMENTUM:</b> Liquidity is purely Bearish. Target: $${{data.analysis.projected.toFixed(0)}}`;
|
| 237 |
+
logType = "bearish";
|
| 238 |
+
}} else {{
|
| 239 |
+
logMsg = `βοΈ <b>RANGE BOUND:</b> Price stuck between flows.`;
|
| 240 |
+
}}
|
| 241 |
+
}}
|
| 242 |
+
|
| 243 |
+
if (logMsg) addLog(logMsg, logType);
|
| 244 |
|
| 245 |
+
const priceLayout = {{
|
| 246 |
+
...commonLayout,
|
| 247 |
+
title: '<b>Price Analysis & Forecast</b>',
|
| 248 |
+
xaxis: {{type:'date', gridcolor:'#222'}},
|
| 249 |
+
shapes: shapes
|
| 250 |
+
}};
|
| 251 |
+
|
| 252 |
+
if (!initPrice) {{ Plotly.newPlot(priceDiv, [tracePrice, traceProj], priceLayout, commonConfig); initPrice = true; }}
|
| 253 |
+
else {{ Plotly.react(priceDiv, [tracePrice, traceProj], priceLayout, commonConfig); }}
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
// --- 2. VOLUME CHART ---
|
| 257 |
const tracesVol = [
|
| 258 |
+
{{ x: data.vol.dist_bids, y: data.vol.vol_bids, type: 'scatter', name: 'Bids', line: {{color: '#00e676'}} }},
|
| 259 |
{{ x: data.vol.dist_asks, y: data.vol.vol_asks, type: 'scatter', name: 'Ask', line: {{color: '#ff1744'}} }}
|
| 260 |
];
|
| 261 |
if (!initVol) {{ Plotly.newPlot(volDiv, tracesVol, {{ ...commonLayout, title: '<b>Cumulative Volume</b>', xaxis: {{title:'Distance ($)'}} }}, commonConfig); initVol = true; }}
|
| 262 |
else {{ Plotly.react(volDiv, tracesVol, {{ ...commonLayout, title: '<b>Cumulative Volume</b>', xaxis: {{title:'Distance ($)'}} }}, commonConfig); }}
|
| 263 |
|
| 264 |
+
|
| 265 |
+
// --- 3. NET DIFFERENCE ---
|
| 266 |
+
const traceDiff = {{ x: data.diff.x, y: data.diff.y, type: 'scatter', mode: 'lines', fill: 'tozeroy', line: {{color: '#e040fb', width: 2}} }};
|
| 267 |
+
// Add Zero Line
|
| 268 |
+
const diffLayout = {{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
...commonLayout,
|
| 270 |
title: '<b>Net Liquidity (Bids - Asks)</b>',
|
| 271 |
xaxis: {{title:'Distance ($)'}},
|
| 272 |
+
shapes: [{{ type: 'line', x0: 0, x1: 1, xref: 'paper', y0: 0, y1: 0, line: {{color: '#666', width: 1}} }}]
|
| 273 |
}};
|
| 274 |
|
| 275 |
+
if (!initDiff) {{ Plotly.newPlot(diffDiv, [traceDiff], diffLayout, commonConfig); initDiff = true; }}
|
| 276 |
+
else {{ Plotly.react(diffDiv, [traceDiff], diffLayout, commonConfig); }}
|
|
|
|
|
|
|
|
|
|
| 277 |
|
| 278 |
}} catch (e) {{ console.error("Fetch error:", e); }}
|
| 279 |
}}
|
|
|
|
| 342 |
raw_bids = sorted(market_state['bids'].items(), key=lambda x: -x[0])[:300]
|
| 343 |
raw_asks = sorted(market_state['asks'].items(), key=lambda x: x[0])[:300]
|
| 344 |
|
| 345 |
+
# Calculate Distances
|
| 346 |
d_b_x, d_b_y, cum = [], [], 0
|
| 347 |
for p, q in raw_bids:
|
| 348 |
d = mid - p
|
|
|
|
| 372 |
idx_a = bisect.bisect_right(d_a_x, s)
|
| 373 |
vol_a = d_a_y[idx_a-1] if idx_a > 0 else 0
|
| 374 |
|
|
|
|
| 375 |
diff_x.append(s)
|
| 376 |
+
diff_y.append(vol_b - vol_a)
|
| 377 |
|
| 378 |
+
# --- Run AI Analysis ---
|
| 379 |
+
analysis = analyze_structure(diff_x, diff_y, mid)
|
| 380 |
|
| 381 |
return web.json_response({
|
| 382 |
"mid": mid,
|
| 383 |
"prev_mid": market_state['prev_mid'],
|
| 384 |
"vol": { "dist_bids": d_b_x, "vol_bids": d_b_y, "dist_asks": d_a_x, "vol_asks": d_a_y },
|
| 385 |
"diff": { "x": diff_x, "y": diff_y },
|
| 386 |
+
"analysis": analysis,
|
| 387 |
"history": market_state['history']
|
| 388 |
})
|
| 389 |
|
|
|
|
| 407 |
site = web.TCPSite(runner, '0.0.0.0', PORT)
|
| 408 |
await site.start()
|
| 409 |
|
| 410 |
+
print(f"π BTC-USD Predictive Dashboard: http://localhost:{PORT}")
|
| 411 |
await asyncio.Event().wait()
|
| 412 |
|
| 413 |
if __name__ == "__main__":
|