Update app.py
Browse files
app.py
CHANGED
|
@@ -11,11 +11,7 @@ SYMBOL_KRAKEN = "BTC/USD"
|
|
| 11 |
PORT = 7860
|
| 12 |
HISTORY_LENGTH = 300
|
| 13 |
BROADCAST_RATE = 0.1
|
| 14 |
-
|
| 15 |
-
DECAY_LAMBDA_SHORT = 100.0
|
| 16 |
-
DECAY_LAMBDA_LONG = 500.0
|
| 17 |
-
DECAY_LAMBDA_MACRO = 15000.0
|
| 18 |
-
|
| 19 |
IMPACT_SENSITIVITY = 0.5
|
| 20 |
|
| 21 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
|
|
@@ -24,9 +20,7 @@ market_state = {
|
|
| 24 |
"bids": {},
|
| 25 |
"asks": {},
|
| 26 |
"history": [],
|
| 27 |
-
"
|
| 28 |
-
"pred_history_long": [],
|
| 29 |
-
"pred_history_macro": [],
|
| 30 |
"current_mid": 0.0,
|
| 31 |
"prev_mid": 0.0,
|
| 32 |
"ready": False
|
|
@@ -38,9 +32,7 @@ def analyze_structure(diff_x, diff_y, current_mid):
|
|
| 38 |
if not diff_y or len(diff_y) < 5:
|
| 39 |
return None
|
| 40 |
|
| 41 |
-
|
| 42 |
-
w_long = 0.0
|
| 43 |
-
w_macro = 0.0
|
| 44 |
prev_vol = 0.0
|
| 45 |
|
| 46 |
for i in range(len(diff_x)):
|
|
@@ -50,20 +42,21 @@ def analyze_structure(diff_x, diff_y, current_mid):
|
|
| 50 |
marginal_vol = cum_vol - prev_vol
|
| 51 |
prev_vol = cum_vol
|
| 52 |
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
-
|
| 58 |
-
if w_imb == 0: return 0.0
|
| 59 |
-
imp = math.sqrt(abs(w_imb)) * IMPACT_SENSITIVITY
|
| 60 |
-
return -imp if w_imb < 0 else imp
|
| 61 |
|
| 62 |
return {
|
| 63 |
-
"
|
| 64 |
-
"
|
| 65 |
-
"proj_macro": current_mid + get_impact(w_macro),
|
| 66 |
-
"net_score": w_short
|
| 67 |
}
|
| 68 |
|
| 69 |
def process_market_data():
|
|
@@ -72,8 +65,8 @@ def process_market_data():
|
|
| 72 |
|
| 73 |
mid = market_state['current_mid']
|
| 74 |
|
| 75 |
-
raw_bids = sorted(market_state['bids'].items(), key=lambda x: -x[0])[:
|
| 76 |
-
raw_asks = sorted(market_state['asks'].items(), key=lambda x: x[0])[:
|
| 77 |
|
| 78 |
d_b_x, d_b_y, cum = [], [], 0
|
| 79 |
for p, q in raw_bids:
|
|
@@ -113,25 +106,15 @@ def process_market_data():
|
|
| 113 |
|
| 114 |
now = time.time()
|
| 115 |
if analysis:
|
| 116 |
-
if not market_state['
|
| 117 |
-
market_state['
|
| 118 |
-
if len(market_state['
|
| 119 |
-
market_state['
|
| 120 |
-
|
| 121 |
-
market_state['pred_history_long'].append({'t': now, 'p': analysis['proj_long']})
|
| 122 |
-
if len(market_state['pred_history_long']) > HISTORY_LENGTH:
|
| 123 |
-
market_state['pred_history_long'].pop(0)
|
| 124 |
-
|
| 125 |
-
market_state['pred_history_macro'].append({'t': now, 'p': analysis['proj_macro']})
|
| 126 |
-
if len(market_state['pred_history_macro']) > HISTORY_LENGTH:
|
| 127 |
-
market_state['pred_history_macro'].pop(0)
|
| 128 |
|
| 129 |
return {
|
| 130 |
"mid": mid,
|
| 131 |
"history": market_state['history'],
|
| 132 |
-
"
|
| 133 |
-
"pred_history_long": market_state['pred_history_long'],
|
| 134 |
-
"pred_history_macro": market_state['pred_history_macro'],
|
| 135 |
"depth_x": diff_x,
|
| 136 |
"depth_net": diff_y,
|
| 137 |
"depth_bids": chart_bids,
|
|
@@ -153,8 +136,6 @@ HTML_PAGE = f"""
|
|
| 153 |
--text-main: #c5c6c7;
|
| 154 |
--accent-green: #66fcf1;
|
| 155 |
--accent-red: #ff3b3b;
|
| 156 |
-
--accent-purple: #ce93d8;
|
| 157 |
-
--accent-cyan: #4dd0e1;
|
| 158 |
--border: #2d3842;
|
| 159 |
}}
|
| 160 |
body {{ margin: 0; padding: 0; background-color: var(--bg-color); color: var(--text-main); font-family: monospace; overflow: hidden; height: 100vh; width: 100vw; }}
|
|
@@ -187,8 +168,6 @@ HTML_PAGE = f"""
|
|
| 187 |
.stat-value {{ font-size: 24px; font-weight: bold; }}
|
| 188 |
.green {{ color: var(--accent-green); }}
|
| 189 |
.red {{ color: var(--accent-red); }}
|
| 190 |
-
.purple {{ color: var(--accent-purple); }}
|
| 191 |
-
.cyan {{ color: var(--accent-cyan); }}
|
| 192 |
|
| 193 |
#loader {{ position: absolute; top:0; left:0; width:100%; height:100%; background: rgba(0,0,0,0.95); z-index: 999; display: flex; flex-direction: column; justify-content: center; align-items: center; color: var(--accent-green); }}
|
| 194 |
</style>
|
|
@@ -221,20 +200,12 @@ HTML_PAGE = f"""
|
|
| 221 |
<div class="panel-header">HFT ANALYTICS ENGINE</div>
|
| 222 |
<div class="stats-content">
|
| 223 |
<div class="stat-box">
|
| 224 |
-
<span class="stat-label">WEIGHTED IMBALANCE
|
| 225 |
<span id="score-val" class="stat-value">0</span>
|
| 226 |
</div>
|
| 227 |
<div class="stat-box" style="border: 1px solid #444;">
|
| 228 |
-
<span class="stat-label" style="color
|
| 229 |
-
<span id="proj-
|
| 230 |
-
</div>
|
| 231 |
-
<div class="stat-box" style="border: 1px solid #7b1fa2;">
|
| 232 |
-
<span class="stat-label" style="color:var(--accent-purple);">LONG TERM (SWING)</span>
|
| 233 |
-
<span id="proj-long" class="stat-value purple">---</span>
|
| 234 |
-
</div>
|
| 235 |
-
<div class="stat-box" style="border: 1px solid #00acc1;">
|
| 236 |
-
<span class="stat-label" style="color:var(--accent-cyan);">MACRO (WHALE)</span>
|
| 237 |
-
<span id="proj-macro" class="stat-value cyan">---</span>
|
| 238 |
</div>
|
| 239 |
</div>
|
| 240 |
</div>
|
|
@@ -247,9 +218,7 @@ HTML_PAGE = f"""
|
|
| 247 |
status: document.getElementById('loading-status'),
|
| 248 |
price: document.getElementById('live-price'),
|
| 249 |
scoreVal: document.getElementById('score-val'),
|
| 250 |
-
|
| 251 |
-
projLong: document.getElementById('proj-long'),
|
| 252 |
-
projMacro: document.getElementById('proj-macro')
|
| 253 |
}};
|
| 254 |
|
| 255 |
const chartCommon = {{
|
|
@@ -261,24 +230,28 @@ HTML_PAGE = f"""
|
|
| 261 |
}};
|
| 262 |
|
| 263 |
const priceChart = LightweightCharts.createChart(document.getElementById('tv-price'), chartCommon);
|
| 264 |
-
|
| 265 |
const priceSeries = priceChart.addLineSeries({{ color: '#2962FF', lineWidth: 2, title: 'Price' }});
|
| 266 |
|
| 267 |
-
const
|
| 268 |
-
|
|
|
|
|
|
|
|
|
|
| 269 |
|
| 270 |
-
const
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
|
|
|
|
|
|
| 274 |
|
| 275 |
const rawChart = LightweightCharts.createChart(document.getElementById('tv-raw'), {{
|
| 276 |
...chartCommon,
|
| 277 |
timeScale: {{ tickMarkFormatter: (time) => parseFloat(time).toFixed(0) }},
|
| 278 |
localization: {{ timeFormatter: (time) => 'Dist: $' + parseFloat(time).toFixed(2) }}
|
| 279 |
}});
|
| 280 |
-
const rawBidSeries = rawChart.addAreaSeries({{ lineColor: '#00e676', topColor: 'rgba(0, 230, 118, 0.2)', bottomColor: 'rgba(0, 230, 118, 0.0)', lineWidth: 2 }});
|
| 281 |
-
const rawAskSeries = rawChart.addAreaSeries({{ lineColor: '#ff1744', topColor: 'rgba(255, 23, 68, 0.2)', bottomColor: 'rgba(255, 23, 68, 0.0)', lineWidth: 2 }});
|
| 282 |
|
| 283 |
const netChart = LightweightCharts.createChart(document.getElementById('tv-net'), {{
|
| 284 |
...chartCommon,
|
|
@@ -322,52 +295,31 @@ HTML_PAGE = f"""
|
|
| 322 |
if (!seen.has(t)) {{ seen.add(t); cleanHistory.push({{ time: t, value: d.p }}); }}
|
| 323 |
}});
|
| 324 |
|
| 325 |
-
const
|
| 326 |
-
const
|
| 327 |
-
if(data.
|
| 328 |
-
data.
|
| 329 |
const t = Math.floor(d.t);
|
| 330 |
-
if(!
|
| 331 |
-
}});
|
| 332 |
-
}}
|
| 333 |
-
|
| 334 |
-
const predLong = [];
|
| 335 |
-
const seenL = new Set();
|
| 336 |
-
if(data.pred_history_long) {{
|
| 337 |
-
data.pred_history_long.forEach(d => {{
|
| 338 |
-
const t = Math.floor(d.t);
|
| 339 |
-
if(!seenL.has(t)) {{ seenL.add(t); predLong.push({{ time: t, value: d.p }}); }}
|
| 340 |
-
}});
|
| 341 |
-
}}
|
| 342 |
-
|
| 343 |
-
const predMacro = [];
|
| 344 |
-
const seenM = new Set();
|
| 345 |
-
if(data.pred_history_macro) {{
|
| 346 |
-
data.pred_history_macro.forEach(d => {{
|
| 347 |
-
const t = Math.floor(d.t);
|
| 348 |
-
if(!seenM.has(t)) {{ seenM.add(t); predMacro.push({{ time: t, value: d.p }}); }}
|
| 349 |
}});
|
| 350 |
}}
|
| 351 |
|
| 352 |
if (cleanHistory.length) {{
|
| 353 |
priceSeries.setData(cleanHistory);
|
| 354 |
-
|
| 355 |
-
pastLongSeries.setData(predLong);
|
| 356 |
-
pastMacroSeries.setData(predMacro);
|
| 357 |
|
| 358 |
const last = cleanHistory[cleanHistory.length-1];
|
| 359 |
dom.price.innerText = last.value.toLocaleString(undefined, {{minimumFractionDigits: 2}});
|
| 360 |
|
| 361 |
if (data.analysis) {{
|
| 362 |
-
const {{
|
| 363 |
-
|
| 364 |
-
futureShortSeries.setData([last, {{ time: last.time + 60, value: proj_short }}]);
|
| 365 |
|
| 366 |
-
|
|
|
|
|
|
|
|
|
|
| 367 |
|
| 368 |
-
dom.
|
| 369 |
-
dom.projLong.innerText = proj_long.toLocaleString(undefined, {{minimumFractionDigits: 0, maximumFractionDigits: 0}});
|
| 370 |
-
dom.projMacro.innerText = proj_macro.toLocaleString(undefined, {{minimumFractionDigits: 0, maximumFractionDigits: 0}});
|
| 371 |
|
| 372 |
dom.scoreVal.innerText = net_score.toFixed(2);
|
| 373 |
dom.scoreVal.className = net_score > 0 ? "stat-value green" : "stat-value red";
|
|
|
|
| 11 |
PORT = 7860
|
| 12 |
HISTORY_LENGTH = 300
|
| 13 |
BROADCAST_RATE = 0.1
|
| 14 |
+
DECAY_LAMBDA = 100.0
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
IMPACT_SENSITIVITY = 0.5
|
| 16 |
|
| 17 |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
|
|
|
|
| 20 |
"bids": {},
|
| 21 |
"asks": {},
|
| 22 |
"history": [],
|
| 23 |
+
"pred_history": [],
|
|
|
|
|
|
|
| 24 |
"current_mid": 0.0,
|
| 25 |
"prev_mid": 0.0,
|
| 26 |
"ready": False
|
|
|
|
| 32 |
if not diff_y or len(diff_y) < 5:
|
| 33 |
return None
|
| 34 |
|
| 35 |
+
weighted_imbalance = 0.0
|
|
|
|
|
|
|
| 36 |
prev_vol = 0.0
|
| 37 |
|
| 38 |
for i in range(len(diff_x)):
|
|
|
|
| 42 |
marginal_vol = cum_vol - prev_vol
|
| 43 |
prev_vol = cum_vol
|
| 44 |
|
| 45 |
+
weight = math.exp(-dist / DECAY_LAMBDA)
|
| 46 |
+
weighted_imbalance += marginal_vol * weight
|
| 47 |
+
|
| 48 |
+
if weighted_imbalance != 0:
|
| 49 |
+
impact = math.sqrt(abs(weighted_imbalance)) * IMPACT_SENSITIVITY
|
| 50 |
+
if weighted_imbalance < 0:
|
| 51 |
+
impact = -impact
|
| 52 |
+
else:
|
| 53 |
+
impact = 0.0
|
| 54 |
|
| 55 |
+
projected_price = current_mid + impact
|
|
|
|
|
|
|
|
|
|
| 56 |
|
| 57 |
return {
|
| 58 |
+
"projected": projected_price,
|
| 59 |
+
"net_score": weighted_imbalance
|
|
|
|
|
|
|
| 60 |
}
|
| 61 |
|
| 62 |
def process_market_data():
|
|
|
|
| 65 |
|
| 66 |
mid = market_state['current_mid']
|
| 67 |
|
| 68 |
+
raw_bids = sorted(market_state['bids'].items(), key=lambda x: -x[0])[:300]
|
| 69 |
+
raw_asks = sorted(market_state['asks'].items(), key=lambda x: x[0])[:300]
|
| 70 |
|
| 71 |
d_b_x, d_b_y, cum = [], [], 0
|
| 72 |
for p, q in raw_bids:
|
|
|
|
| 106 |
|
| 107 |
now = time.time()
|
| 108 |
if analysis:
|
| 109 |
+
if not market_state['pred_history'] or (now - market_state['pred_history'][-1]['t'] > 0.5):
|
| 110 |
+
market_state['pred_history'].append({'t': now, 'p': analysis['projected']})
|
| 111 |
+
if len(market_state['pred_history']) > HISTORY_LENGTH:
|
| 112 |
+
market_state['pred_history'].pop(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 113 |
|
| 114 |
return {
|
| 115 |
"mid": mid,
|
| 116 |
"history": market_state['history'],
|
| 117 |
+
"pred_history": market_state['pred_history'],
|
|
|
|
|
|
|
| 118 |
"depth_x": diff_x,
|
| 119 |
"depth_net": diff_y,
|
| 120 |
"depth_bids": chart_bids,
|
|
|
|
| 136 |
--text-main: #c5c6c7;
|
| 137 |
--accent-green: #66fcf1;
|
| 138 |
--accent-red: #ff3b3b;
|
|
|
|
|
|
|
| 139 |
--border: #2d3842;
|
| 140 |
}}
|
| 141 |
body {{ margin: 0; padding: 0; background-color: var(--bg-color); color: var(--text-main); font-family: monospace; overflow: hidden; height: 100vh; width: 100vw; }}
|
|
|
|
| 168 |
.stat-value {{ font-size: 24px; font-weight: bold; }}
|
| 169 |
.green {{ color: var(--accent-green); }}
|
| 170 |
.red {{ color: var(--accent-red); }}
|
|
|
|
|
|
|
| 171 |
|
| 172 |
#loader {{ position: absolute; top:0; left:0; width:100%; height:100%; background: rgba(0,0,0,0.95); z-index: 999; display: flex; flex-direction: column; justify-content: center; align-items: center; color: var(--accent-green); }}
|
| 173 |
</style>
|
|
|
|
| 200 |
<div class="panel-header">HFT ANALYTICS ENGINE</div>
|
| 201 |
<div class="stats-content">
|
| 202 |
<div class="stat-box">
|
| 203 |
+
<span class="stat-label">WEIGHTED IMBALANCE SCORE</span>
|
| 204 |
<span id="score-val" class="stat-value">0</span>
|
| 205 |
</div>
|
| 206 |
<div class="stat-box" style="border: 1px solid #444;">
|
| 207 |
+
<span class="stat-label" style="color:var(--accent-green);">IMPACT PROJECTION</span>
|
| 208 |
+
<span id="proj-val" class="stat-value">---</span>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
</div>
|
| 210 |
</div>
|
| 211 |
</div>
|
|
|
|
| 218 |
status: document.getElementById('loading-status'),
|
| 219 |
price: document.getElementById('live-price'),
|
| 220 |
scoreVal: document.getElementById('score-val'),
|
| 221 |
+
projVal: document.getElementById('proj-val')
|
|
|
|
|
|
|
| 222 |
}};
|
| 223 |
|
| 224 |
const chartCommon = {{
|
|
|
|
| 230 |
}};
|
| 231 |
|
| 232 |
const priceChart = LightweightCharts.createChart(document.getElementById('tv-price'), chartCommon);
|
|
|
|
| 233 |
const priceSeries = priceChart.addLineSeries({{ color: '#2962FF', lineWidth: 2, title: 'Price' }});
|
| 234 |
|
| 235 |
+
const pastPredSeries = priceChart.addLineSeries({{
|
| 236 |
+
color: '#555555',
|
| 237 |
+
lineWidth: 1,
|
| 238 |
+
title: 'Past Prediction'
|
| 239 |
+
}});
|
| 240 |
|
| 241 |
+
const futurePredSeries = priceChart.addLineSeries({{
|
| 242 |
+
color: '#ff9800',
|
| 243 |
+
lineWidth: 2,
|
| 244 |
+
lineStyle: 2,
|
| 245 |
+
title: 'Projection'
|
| 246 |
+
}});
|
| 247 |
|
| 248 |
const rawChart = LightweightCharts.createChart(document.getElementById('tv-raw'), {{
|
| 249 |
...chartCommon,
|
| 250 |
timeScale: {{ tickMarkFormatter: (time) => parseFloat(time).toFixed(0) }},
|
| 251 |
localization: {{ timeFormatter: (time) => 'Dist: $' + parseFloat(time).toFixed(2) }}
|
| 252 |
}});
|
| 253 |
+
const rawBidSeries = rawChart.addAreaSeries({{ lineColor: '#00e676', topColor: 'rgba(0, 230, 118, 0.2)', bottomColor: 'rgba(0, 230, 118, 0.0)', lineWidth: 2, title: "Bids" }});
|
| 254 |
+
const rawAskSeries = rawChart.addAreaSeries({{ lineColor: '#ff1744', topColor: 'rgba(255, 23, 68, 0.2)', bottomColor: 'rgba(255, 23, 68, 0.0)', lineWidth: 2, title: "Asks" }});
|
| 255 |
|
| 256 |
const netChart = LightweightCharts.createChart(document.getElementById('tv-net'), {{
|
| 257 |
...chartCommon,
|
|
|
|
| 295 |
if (!seen.has(t)) {{ seen.add(t); cleanHistory.push({{ time: t, value: d.p }}); }}
|
| 296 |
}});
|
| 297 |
|
| 298 |
+
const predHistory = [];
|
| 299 |
+
const seenP = new Set();
|
| 300 |
+
if(data.pred_history) {{
|
| 301 |
+
data.pred_history.forEach(d => {{
|
| 302 |
const t = Math.floor(d.t);
|
| 303 |
+
if(!seenP.has(t)) {{ seenP.add(t); predHistory.push({{ time: t, value: d.p }}); }}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 304 |
}});
|
| 305 |
}}
|
| 306 |
|
| 307 |
if (cleanHistory.length) {{
|
| 308 |
priceSeries.setData(cleanHistory);
|
| 309 |
+
pastPredSeries.setData(predHistory);
|
|
|
|
|
|
|
| 310 |
|
| 311 |
const last = cleanHistory[cleanHistory.length-1];
|
| 312 |
dom.price.innerText = last.value.toLocaleString(undefined, {{minimumFractionDigits: 2}});
|
| 313 |
|
| 314 |
if (data.analysis) {{
|
| 315 |
+
const {{ projected, net_score }} = data.analysis;
|
|
|
|
|
|
|
| 316 |
|
| 317 |
+
futurePredSeries.setData([
|
| 318 |
+
last,
|
| 319 |
+
{{ time: last.time + 60, value: projected }}
|
| 320 |
+
]);
|
| 321 |
|
| 322 |
+
dom.projVal.innerText = projected.toLocaleString(undefined, {{minimumFractionDigits: 0, maximumFractionDigits: 0}});
|
|
|
|
|
|
|
| 323 |
|
| 324 |
dom.scoreVal.innerText = net_score.toFixed(2);
|
| 325 |
dom.scoreVal.className = net_score > 0 ? "stat-value green" : "stat-value red";
|