Update app.py
Browse files
app.py
CHANGED
|
@@ -12,8 +12,13 @@ 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 |
|
| 18 |
IMPACT_SENSITIVITY = 0.5
|
| 19 |
|
|
@@ -25,6 +30,7 @@ market_state = {
|
|
| 25 |
"history": [],
|
| 26 |
"pred_history_short": [],
|
| 27 |
"pred_history_long": [],
|
|
|
|
| 28 |
"current_mid": 0.0,
|
| 29 |
"prev_mid": 0.0,
|
| 30 |
"ready": False
|
|
@@ -36,8 +42,9 @@ def analyze_structure(diff_x, diff_y, current_mid):
|
|
| 36 |
if not diff_y or len(diff_y) < 5:
|
| 37 |
return None
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
|
|
|
| 41 |
prev_vol = 0.0
|
| 42 |
|
| 43 |
for i in range(len(diff_x)):
|
|
@@ -47,26 +54,26 @@ def analyze_structure(diff_x, diff_y, current_mid):
|
|
| 47 |
marginal_vol = cum_vol - prev_vol
|
| 48 |
prev_vol = cum_vol
|
| 49 |
|
| 50 |
-
|
| 51 |
-
|
| 52 |
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
if w_imbalance_short < 0: impact_s = -impact_s
|
| 59 |
-
else: impact_s = 0.0
|
| 60 |
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
if
|
| 64 |
-
|
|
|
|
| 65 |
|
| 66 |
return {
|
| 67 |
-
"proj_short": current_mid +
|
| 68 |
-
"proj_long": current_mid +
|
| 69 |
-
"
|
|
|
|
| 70 |
}
|
| 71 |
|
| 72 |
def process_market_data():
|
|
@@ -75,8 +82,9 @@ def process_market_data():
|
|
| 75 |
|
| 76 |
mid = market_state['current_mid']
|
| 77 |
|
| 78 |
-
|
| 79 |
-
|
|
|
|
| 80 |
|
| 81 |
d_b_x, d_b_y, cum = [], [], 0
|
| 82 |
for p, q in raw_bids:
|
|
@@ -117,19 +125,27 @@ def process_market_data():
|
|
| 117 |
now = time.time()
|
| 118 |
if analysis:
|
| 119 |
if not market_state['pred_history_short'] or (now - market_state['pred_history_short'][-1]['t'] > 0.5):
|
|
|
|
| 120 |
market_state['pred_history_short'].append({'t': now, 'p': analysis['proj_short']})
|
| 121 |
if len(market_state['pred_history_short']) > HISTORY_LENGTH:
|
| 122 |
market_state['pred_history_short'].pop(0)
|
| 123 |
-
|
|
|
|
| 124 |
market_state['pred_history_long'].append({'t': now, 'p': analysis['proj_long']})
|
| 125 |
if len(market_state['pred_history_long']) > HISTORY_LENGTH:
|
| 126 |
market_state['pred_history_long'].pop(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
|
| 128 |
return {
|
| 129 |
"mid": mid,
|
| 130 |
"history": market_state['history'],
|
| 131 |
"pred_history_short": market_state['pred_history_short'],
|
| 132 |
"pred_history_long": market_state['pred_history_long'],
|
|
|
|
| 133 |
"depth_x": diff_x,
|
| 134 |
"depth_net": diff_y,
|
| 135 |
"depth_bids": chart_bids,
|
|
@@ -152,6 +168,7 @@ HTML_PAGE = f"""
|
|
| 152 |
--accent-green: #66fcf1;
|
| 153 |
--accent-red: #ff3b3b;
|
| 154 |
--accent-purple: #ce93d8;
|
|
|
|
| 155 |
--border: #2d3842;
|
| 156 |
}}
|
| 157 |
body {{ margin: 0; padding: 0; background-color: var(--bg-color); color: var(--text-main); font-family: monospace; overflow: hidden; height: 100vh; width: 100vw; }}
|
|
@@ -185,6 +202,7 @@ HTML_PAGE = f"""
|
|
| 185 |
.green {{ color: var(--accent-green); }}
|
| 186 |
.red {{ color: var(--accent-red); }}
|
| 187 |
.purple {{ color: var(--accent-purple); }}
|
|
|
|
| 188 |
|
| 189 |
#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); }}
|
| 190 |
</style>
|
|
@@ -217,7 +235,7 @@ HTML_PAGE = f"""
|
|
| 217 |
<div class="panel-header">HFT ANALYTICS ENGINE</div>
|
| 218 |
<div class="stats-content">
|
| 219 |
<div class="stat-box">
|
| 220 |
-
<span class="stat-label">WEIGHTED IMBALANCE (
|
| 221 |
<span id="score-val" class="stat-value">0</span>
|
| 222 |
</div>
|
| 223 |
<div class="stat-box" style="border: 1px solid #444;">
|
|
@@ -225,9 +243,13 @@ HTML_PAGE = f"""
|
|
| 225 |
<span id="proj-short" class="stat-value">---</span>
|
| 226 |
</div>
|
| 227 |
<div class="stat-box" style="border: 1px solid #7b1fa2;">
|
| 228 |
-
<span class="stat-label" style="color:var(--accent-purple);">LONG TERM
|
| 229 |
<span id="proj-long" class="stat-value purple">---</span>
|
| 230 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
</div>
|
| 232 |
</div>
|
| 233 |
</div>
|
|
@@ -240,7 +262,8 @@ HTML_PAGE = f"""
|
|
| 240 |
price: document.getElementById('live-price'),
|
| 241 |
scoreVal: document.getElementById('score-val'),
|
| 242 |
projShort: document.getElementById('proj-short'),
|
| 243 |
-
projLong: document.getElementById('proj-long')
|
|
|
|
| 244 |
}};
|
| 245 |
|
| 246 |
const chartCommon = {{
|
|
@@ -255,11 +278,15 @@ HTML_PAGE = f"""
|
|
| 255 |
|
| 256 |
const priceSeries = priceChart.addLineSeries({{ color: '#2962FF', lineWidth: 2, title: 'Price' }});
|
| 257 |
|
| 258 |
-
|
| 259 |
-
const
|
|
|
|
| 260 |
|
| 261 |
-
|
| 262 |
-
const
|
|
|
|
|
|
|
|
|
|
| 263 |
|
| 264 |
const rawChart = LightweightCharts.createChart(document.getElementById('tv-raw'), {{
|
| 265 |
...chartCommon,
|
|
@@ -329,22 +356,33 @@ HTML_PAGE = f"""
|
|
| 329 |
}});
|
| 330 |
}}
|
| 331 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 332 |
if (cleanHistory.length) {{
|
| 333 |
priceSeries.setData(cleanHistory);
|
| 334 |
pastShortSeries.setData(predShort);
|
| 335 |
pastLongSeries.setData(predLong);
|
|
|
|
| 336 |
|
| 337 |
const last = cleanHistory[cleanHistory.length-1];
|
| 338 |
dom.price.innerText = last.value.toLocaleString(undefined, {{minimumFractionDigits: 2}});
|
| 339 |
|
| 340 |
if (data.analysis) {{
|
| 341 |
-
const {{ proj_short, proj_long, net_score }} = data.analysis;
|
| 342 |
|
|
|
|
| 343 |
futureShortSeries.setData([last, {{ time: last.time + 60, value: proj_short }}]);
|
| 344 |
-
futureLongSeries.setData([last, {{ time: last.time + 300, value: proj_long }}]);
|
| 345 |
|
| 346 |
dom.projShort.innerText = proj_short.toLocaleString(undefined, {{minimumFractionDigits: 0, maximumFractionDigits: 0}});
|
| 347 |
dom.projLong.innerText = proj_long.toLocaleString(undefined, {{minimumFractionDigits: 0, maximumFractionDigits: 0}});
|
|
|
|
| 348 |
|
| 349 |
dom.scoreVal.innerText = net_score.toFixed(2);
|
| 350 |
dom.scoreVal.className = net_score > 0 ? "stat-value green" : "stat-value red";
|
|
|
|
| 12 |
HISTORY_LENGTH = 300
|
| 13 |
BROADCAST_RATE = 0.1
|
| 14 |
|
| 15 |
+
# --- Tuning Parameters ---
|
| 16 |
+
# Short: Scalping/Spread focus
|
| 17 |
DECAY_LAMBDA_SHORT = 100.0
|
| 18 |
+
# Long: Swing/Structure focus
|
| 19 |
DECAY_LAMBDA_LONG = 500.0
|
| 20 |
+
# Macro: Deep Orderbook/Whale focus
|
| 21 |
+
DECAY_LAMBDA_MACRO = 2000.0
|
| 22 |
|
| 23 |
IMPACT_SENSITIVITY = 0.5
|
| 24 |
|
|
|
|
| 30 |
"history": [],
|
| 31 |
"pred_history_short": [],
|
| 32 |
"pred_history_long": [],
|
| 33 |
+
"pred_history_macro": [],
|
| 34 |
"current_mid": 0.0,
|
| 35 |
"prev_mid": 0.0,
|
| 36 |
"ready": False
|
|
|
|
| 42 |
if not diff_y or len(diff_y) < 5:
|
| 43 |
return None
|
| 44 |
|
| 45 |
+
w_short = 0.0
|
| 46 |
+
w_long = 0.0
|
| 47 |
+
w_macro = 0.0
|
| 48 |
prev_vol = 0.0
|
| 49 |
|
| 50 |
for i in range(len(diff_x)):
|
|
|
|
| 54 |
marginal_vol = cum_vol - prev_vol
|
| 55 |
prev_vol = cum_vol
|
| 56 |
|
| 57 |
+
# 1. Short Term Decay
|
| 58 |
+
w_short += marginal_vol * math.exp(-dist / DECAY_LAMBDA_SHORT)
|
| 59 |
|
| 60 |
+
# 2. Long Term Decay
|
| 61 |
+
w_long += marginal_vol * math.exp(-dist / DECAY_LAMBDA_LONG)
|
| 62 |
+
|
| 63 |
+
# 3. Macro Term Decay
|
| 64 |
+
w_macro += marginal_vol * math.exp(-dist / DECAY_LAMBDA_MACRO)
|
|
|
|
|
|
|
| 65 |
|
| 66 |
+
# Helper for impact calc
|
| 67 |
+
def get_impact(w_imb):
|
| 68 |
+
if w_imb == 0: return 0.0
|
| 69 |
+
imp = math.sqrt(abs(w_imb)) * IMPACT_SENSITIVITY
|
| 70 |
+
return -imp if w_imb < 0 else imp
|
| 71 |
|
| 72 |
return {
|
| 73 |
+
"proj_short": current_mid + get_impact(w_short),
|
| 74 |
+
"proj_long": current_mid + get_impact(w_long),
|
| 75 |
+
"proj_macro": current_mid + get_impact(w_macro),
|
| 76 |
+
"net_score": w_short # We drive the gauge with the short-term pressure
|
| 77 |
}
|
| 78 |
|
| 79 |
def process_market_data():
|
|
|
|
| 82 |
|
| 83 |
mid = market_state['current_mid']
|
| 84 |
|
| 85 |
+
# We need a deeper snapshot for Macro analysis
|
| 86 |
+
raw_bids = sorted(market_state['bids'].items(), key=lambda x: -x[0])[:600]
|
| 87 |
+
raw_asks = sorted(market_state['asks'].items(), key=lambda x: x[0])[:600]
|
| 88 |
|
| 89 |
d_b_x, d_b_y, cum = [], [], 0
|
| 90 |
for p, q in raw_bids:
|
|
|
|
| 125 |
now = time.time()
|
| 126 |
if analysis:
|
| 127 |
if not market_state['pred_history_short'] or (now - market_state['pred_history_short'][-1]['t'] > 0.5):
|
| 128 |
+
# Short
|
| 129 |
market_state['pred_history_short'].append({'t': now, 'p': analysis['proj_short']})
|
| 130 |
if len(market_state['pred_history_short']) > HISTORY_LENGTH:
|
| 131 |
market_state['pred_history_short'].pop(0)
|
| 132 |
+
|
| 133 |
+
# Long
|
| 134 |
market_state['pred_history_long'].append({'t': now, 'p': analysis['proj_long']})
|
| 135 |
if len(market_state['pred_history_long']) > HISTORY_LENGTH:
|
| 136 |
market_state['pred_history_long'].pop(0)
|
| 137 |
+
|
| 138 |
+
# Macro
|
| 139 |
+
market_state['pred_history_macro'].append({'t': now, 'p': analysis['proj_macro']})
|
| 140 |
+
if len(market_state['pred_history_macro']) > HISTORY_LENGTH:
|
| 141 |
+
market_state['pred_history_macro'].pop(0)
|
| 142 |
|
| 143 |
return {
|
| 144 |
"mid": mid,
|
| 145 |
"history": market_state['history'],
|
| 146 |
"pred_history_short": market_state['pred_history_short'],
|
| 147 |
"pred_history_long": market_state['pred_history_long'],
|
| 148 |
+
"pred_history_macro": market_state['pred_history_macro'],
|
| 149 |
"depth_x": diff_x,
|
| 150 |
"depth_net": diff_y,
|
| 151 |
"depth_bids": chart_bids,
|
|
|
|
| 168 |
--accent-green: #66fcf1;
|
| 169 |
--accent-red: #ff3b3b;
|
| 170 |
--accent-purple: #ce93d8;
|
| 171 |
+
--accent-cyan: #4dd0e1;
|
| 172 |
--border: #2d3842;
|
| 173 |
}}
|
| 174 |
body {{ margin: 0; padding: 0; background-color: var(--bg-color); color: var(--text-main); font-family: monospace; overflow: hidden; height: 100vh; width: 100vw; }}
|
|
|
|
| 202 |
.green {{ color: var(--accent-green); }}
|
| 203 |
.red {{ color: var(--accent-red); }}
|
| 204 |
.purple {{ color: var(--accent-purple); }}
|
| 205 |
+
.cyan {{ color: var(--accent-cyan); }}
|
| 206 |
|
| 207 |
#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); }}
|
| 208 |
</style>
|
|
|
|
| 235 |
<div class="panel-header">HFT ANALYTICS ENGINE</div>
|
| 236 |
<div class="stats-content">
|
| 237 |
<div class="stat-box">
|
| 238 |
+
<span class="stat-label">WEIGHTED IMBALANCE (SCALP)</span>
|
| 239 |
<span id="score-val" class="stat-value">0</span>
|
| 240 |
</div>
|
| 241 |
<div class="stat-box" style="border: 1px solid #444;">
|
|
|
|
| 243 |
<span id="proj-short" class="stat-value">---</span>
|
| 244 |
</div>
|
| 245 |
<div class="stat-box" style="border: 1px solid #7b1fa2;">
|
| 246 |
+
<span class="stat-label" style="color:var(--accent-purple);">LONG TERM (SWING)</span>
|
| 247 |
<span id="proj-long" class="stat-value purple">---</span>
|
| 248 |
</div>
|
| 249 |
+
<div class="stat-box" style="border: 1px solid #00acc1;">
|
| 250 |
+
<span class="stat-label" style="color:var(--accent-cyan);">MACRO (WHALE)</span>
|
| 251 |
+
<span id="proj-macro" class="stat-value cyan">---</span>
|
| 252 |
+
</div>
|
| 253 |
</div>
|
| 254 |
</div>
|
| 255 |
</div>
|
|
|
|
| 262 |
price: document.getElementById('live-price'),
|
| 263 |
scoreVal: document.getElementById('score-val'),
|
| 264 |
projShort: document.getElementById('proj-short'),
|
| 265 |
+
projLong: document.getElementById('proj-long'),
|
| 266 |
+
projMacro: document.getElementById('proj-macro')
|
| 267 |
}};
|
| 268 |
|
| 269 |
const chartCommon = {{
|
|
|
|
| 278 |
|
| 279 |
const priceSeries = priceChart.addLineSeries({{ color: '#2962FF', lineWidth: 2, title: 'Price' }});
|
| 280 |
|
| 281 |
+
// Short Term: Show Past + Future Dotted
|
| 282 |
+
const pastShortSeries = priceChart.addLineSeries({{ color: '#555555', lineWidth: 1, title: 'Short (Past)' }});
|
| 283 |
+
const futureShortSeries = priceChart.addLineSeries({{ color: '#ff9800', lineWidth: 2, lineStyle: 2, title: 'Short (Fut)' }});
|
| 284 |
|
| 285 |
+
// Long Term: Past Only
|
| 286 |
+
const pastLongSeries = priceChart.addLineSeries({{ color: '#7b1fa2', lineWidth: 1, title: 'Long (Past)' }});
|
| 287 |
+
|
| 288 |
+
// Macro Term: Past Only
|
| 289 |
+
const pastMacroSeries = priceChart.addLineSeries({{ color: '#4dd0e1', lineWidth: 1, title: 'Macro (Past)' }});
|
| 290 |
|
| 291 |
const rawChart = LightweightCharts.createChart(document.getElementById('tv-raw'), {{
|
| 292 |
...chartCommon,
|
|
|
|
| 356 |
}});
|
| 357 |
}}
|
| 358 |
|
| 359 |
+
const predMacro = [];
|
| 360 |
+
const seenM = new Set();
|
| 361 |
+
if(data.pred_history_macro) {{
|
| 362 |
+
data.pred_history_macro.forEach(d => {{
|
| 363 |
+
const t = Math.floor(d.t);
|
| 364 |
+
if(!seenM.has(t)) {{ seenM.add(t); predMacro.push({{ time: t, value: d.p }}); }}
|
| 365 |
+
}});
|
| 366 |
+
}}
|
| 367 |
+
|
| 368 |
if (cleanHistory.length) {{
|
| 369 |
priceSeries.setData(cleanHistory);
|
| 370 |
pastShortSeries.setData(predShort);
|
| 371 |
pastLongSeries.setData(predLong);
|
| 372 |
+
pastMacroSeries.setData(predMacro);
|
| 373 |
|
| 374 |
const last = cleanHistory[cleanHistory.length-1];
|
| 375 |
dom.price.innerText = last.value.toLocaleString(undefined, {{minimumFractionDigits: 2}});
|
| 376 |
|
| 377 |
if (data.analysis) {{
|
| 378 |
+
const {{ proj_short, proj_long, proj_macro, net_score }} = data.analysis;
|
| 379 |
|
| 380 |
+
// Only plot short term future line
|
| 381 |
futureShortSeries.setData([last, {{ time: last.time + 60, value: proj_short }}]);
|
|
|
|
| 382 |
|
| 383 |
dom.projShort.innerText = proj_short.toLocaleString(undefined, {{minimumFractionDigits: 0, maximumFractionDigits: 0}});
|
| 384 |
dom.projLong.innerText = proj_long.toLocaleString(undefined, {{minimumFractionDigits: 0, maximumFractionDigits: 0}});
|
| 385 |
+
dom.projMacro.innerText = proj_macro.toLocaleString(undefined, {{minimumFractionDigits: 0, maximumFractionDigits: 0}});
|
| 386 |
|
| 387 |
dom.scoreVal.innerText = net_score.toFixed(2);
|
| 388 |
dom.scoreVal.className = net_score > 0 ? "stat-value green" : "stat-value red";
|