Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -7,6 +7,7 @@ REQUIRED_PACKAGES = {
|
|
| 7 |
'numpy': 'numpy',
|
| 8 |
'pandas': 'pandas',
|
| 9 |
'httpx': 'httpx',
|
|
|
|
| 10 |
'fastapi': 'fastapi',
|
| 11 |
'uvicorn': 'uvicorn',
|
| 12 |
'requests': 'requests'
|
|
@@ -21,14 +22,15 @@ for module_name, pip_name in REQUIRED_PACKAGES.items():
|
|
| 21 |
print(f"✅ {pip_name} установлен!")
|
| 22 |
|
| 23 |
# ============================================
|
| 24 |
-
# 👑 TOMIRIS SPACE 26
|
| 25 |
# ============================================
|
| 26 |
import os, time, json, logging, asyncio
|
| 27 |
-
from typing import Dict, Any, List, Optional
|
| 28 |
from datetime import datetime, timezone
|
| 29 |
from collections import deque
|
| 30 |
import numpy as np
|
| 31 |
import pandas as pd
|
|
|
|
| 32 |
import httpx
|
| 33 |
from fastapi import FastAPI, Query
|
| 34 |
|
|
@@ -42,23 +44,48 @@ SYMBOLS = ["XAU/USD", "ETH/USD", "SOL/USD"]
|
|
| 42 |
|
| 43 |
HUB_URL = os.getenv("HUB_URL", "https://pro-3-tomiris-hub.hf.space")
|
| 44 |
HUB_SECRET = os.getenv("HUB_SECRET", "TomyrisUltraSecret2026!")
|
| 45 |
-
TWELVE_DATA_KEY = os.getenv("TWELVE_DATA_KEY", "
|
| 46 |
-
FRED_KEY = os.getenv("FRED_KEY", "
|
|
|
|
| 47 |
|
| 48 |
-
|
| 49 |
-
|
| 50 |
|
| 51 |
-
|
| 52 |
-
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
HISTORY_FILE = "options_history.json"
|
| 55 |
-
CACHE_TTL = {"funding": 60, "oi": 60, "lsr": 120, "deribit": 300, "fred": 3600, "vix": 60
|
| 56 |
|
| 57 |
http_client = httpx.AsyncClient(timeout=15.0)
|
| 58 |
|
| 59 |
def hub_headers():
|
| 60 |
return {"X-Hub-Secret": HUB_SECRET, "Content-Type": "application/json"}
|
| 61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
cache_store = {}
|
| 63 |
cache_times = {}
|
| 64 |
CIRCUIT_BREAKER = {}
|
|
@@ -92,12 +119,13 @@ def save_history():
|
|
| 92 |
# ================= ЗАГРУЗКА ДАННЫХ =================
|
| 93 |
async def fetch_vix() -> Dict[str, Any]:
|
| 94 |
if breaker_open("vix"): return {"vix": 20.0, "level": "NORMAL", "signal": "NEUTRAL"}
|
|
|
|
| 95 |
try:
|
| 96 |
r = await http_client.get(f"https://api.twelvedata.com/quote?symbol=VIX&apikey={TWELVE_DATA_KEY}")
|
| 97 |
if r.status_code == 200:
|
| 98 |
data = r.json(); vix_val = float(data.get("close", 20))
|
| 99 |
-
level = "HIGH" if vix_val >
|
| 100 |
-
signal = "
|
| 101 |
breaker_record("vix", True)
|
| 102 |
return {"vix": vix_val, "level": level, "signal": signal}
|
| 103 |
except: breaker_record("vix", False)
|
|
@@ -106,14 +134,30 @@ async def fetch_vix() -> Dict[str, Any]:
|
|
| 106 |
async def fetch_funding_rate(symbol: str) -> Dict[str, Any]:
|
| 107 |
if breaker_open(f"funding_{symbol}"): return {"funding_rate": 0, "signal": "NEUTRAL"}
|
| 108 |
try:
|
| 109 |
-
r = await http_client.get("https://fapi.binance.com/fapi/v1/premiumIndex")
|
| 110 |
if r.status_code == 200:
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
except: breaker_record(f"funding_{symbol}", False)
|
| 118 |
return {"funding_rate": 0, "signal": "NEUTRAL"}
|
| 119 |
|
|
@@ -126,9 +170,18 @@ async def fetch_open_interest(symbol: str) -> Dict[str, Any]:
|
|
| 126 |
prev_key = f"oi_{symbol}_prev"; prev_oi = cache_store.get(prev_key, oi)
|
| 127 |
change = ((oi - prev_oi) / prev_oi * 100) if prev_oi > 0 else 0
|
| 128 |
cache_store[prev_key] = oi
|
| 129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
breaker_record(f"oi_{symbol}", True)
|
| 131 |
-
return {"open_interest": oi, "change_pct": round(change, 2), "signal": signal}
|
| 132 |
except: breaker_record(f"oi_{symbol}", False)
|
| 133 |
return {"open_interest": 0, "change_pct": 0, "signal": "NEUTRAL"}
|
| 134 |
|
|
@@ -139,7 +192,9 @@ async def fetch_long_short_ratio(symbol: str) -> Dict[str, Any]:
|
|
| 139 |
if r.status_code == 200:
|
| 140 |
lsr = float(r.json().get("longShortRatio", 1))
|
| 141 |
long_pct = lsr / (1 + lsr) * 100; short_pct = 100 - long_pct
|
| 142 |
-
if lsr >
|
|
|
|
|
|
|
| 143 |
elif lsr < 0.5: signal = "BULLISH"
|
| 144 |
else: signal = "NEUTRAL"
|
| 145 |
breaker_record(f"lsr_{symbol}", True)
|
|
@@ -155,46 +210,76 @@ async def fetch_deribit_options(coin: str = "ETH") -> Dict[str, Any]:
|
|
| 155 |
instruments = r.json().get('result', [])
|
| 156 |
call_vol = sum(float(i.get('volume', 0)) for i in instruments if i.get('option_type') == 'call')
|
| 157 |
put_vol = sum(float(i.get('volume', 0)) for i in instruments if i.get('option_type') == 'put')
|
| 158 |
-
|
| 159 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
breaker_record("deribit", True)
|
| 161 |
-
return {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
except: breaker_record("deribit", False)
|
| 163 |
-
return {"put_call_ratio_volume": 1.0, "signal": "NEUTRAL"}
|
| 164 |
|
| 165 |
async def fetch_gold_derivatives() -> Dict[str, Any]:
|
| 166 |
if breaker_open("fred"): return {"signal": "NEUTRAL"}
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
return {"signal": "NEUTRAL"}
|
| 179 |
|
| 180 |
async def get_current_price(symbol: str) -> float:
|
| 181 |
try:
|
| 182 |
r = await http_client.get(f"{HUB_URL}/price/{symbol}", headers=hub_headers())
|
| 183 |
-
if r.status_code == 200:
|
| 184 |
data = r.json()
|
| 185 |
return float(data.get("price", data.get("mid", 0)))
|
| 186 |
except: pass
|
| 187 |
-
bin_symbol = symbol.replace("/", "")
|
| 188 |
-
try:
|
| 189 |
-
r = await http_client.get(f"https://fapi.binance.com/fapi/v1/ticker/price?symbol={bin_symbol}")
|
| 190 |
-
if r.status_code == 200: return float(r.json()["price"])
|
| 191 |
-
except: pass
|
| 192 |
return 0.0
|
| 193 |
|
| 194 |
def calculate_max_pain(current_price: float) -> float:
|
| 195 |
return round(current_price * 0.98, 2) if current_price > 0 else 0.0
|
| 196 |
|
| 197 |
-
# ================= АНАЛИЗ =================
|
| 198 |
async def analyze_derivatives(symbol: str) -> Dict[str, Any]:
|
| 199 |
native = "XAU" if "XAU" in symbol else ("ETH" if "ETH" in symbol else "SOL")
|
| 200 |
bin_sym = "" if native == "XAU" else (native + "USDT")
|
|
@@ -207,6 +292,7 @@ async def analyze_derivatives(symbol: str) -> Dict[str, Any]:
|
|
| 207 |
|
| 208 |
results = await asyncio.gather(*tasks)
|
| 209 |
idx = 0; metrics = {}
|
|
|
|
| 210 |
if bin_sym:
|
| 211 |
metrics['funding_rate'] = results[idx]; idx += 1
|
| 212 |
metrics['open_interest'] = results[idx]; idx += 1
|
|
@@ -215,103 +301,154 @@ async def analyze_derivatives(symbol: str) -> Dict[str, Any]:
|
|
| 215 |
if native == "XAU": metrics['gold_derivatives'] = results[idx]; idx += 1
|
| 216 |
vix_data = results[idx]
|
| 217 |
|
| 218 |
-
signals =
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
|
|
|
|
| 225 |
fund = metrics.get('funding_rate', {})
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
|
|
|
|
| 231 |
oi = metrics.get('open_interest', {})
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
|
|
|
|
| 237 |
lsr = metrics.get('long_short_ratio', {})
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
|
|
|
|
| 243 |
options = metrics.get('options', {})
|
| 244 |
-
if options.get('signal') == '
|
| 245 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 246 |
elif options.get('signal') == 'BULLISH':
|
| 247 |
-
|
|
|
|
|
|
|
| 248 |
|
|
|
|
| 249 |
gold = metrics.get('gold_derivatives', {})
|
| 250 |
-
if gold.get('signal') == '
|
| 251 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 252 |
elif gold.get('signal') == 'BEARISH':
|
| 253 |
-
|
|
|
|
|
|
|
| 254 |
|
|
|
|
| 255 |
price = await get_current_price(symbol) if native != "XAU" else 0
|
| 256 |
max_pain = calculate_max_pain(price) if price > 0 else 0
|
| 257 |
-
if max_pain > 0 and price < max_pain:
|
| 258 |
-
|
| 259 |
-
elif max_pain > 0 and price > max_pain:
|
| 260 |
-
|
| 261 |
-
|
| 262 |
-
score = max(0, min(100, score))
|
| 263 |
-
|
| 264 |
-
# 🔥 BUY/SELL вместо LONG/SHORT
|
| 265 |
-
if score > 60:
|
| 266 |
-
signal = "BUY"
|
| 267 |
-
confidence = score / 100
|
| 268 |
-
elif score < 40:
|
| 269 |
-
signal = "SELL"
|
| 270 |
-
confidence = (100 - score) / 100
|
| 271 |
else:
|
| 272 |
-
|
| 273 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
|
| 275 |
-
analysis = {
|
| 276 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
save_history()
|
|
|
|
| 278 |
return analysis
|
| 279 |
|
| 280 |
-
# ================= ОТПРАВКА В HUB
|
| 281 |
async def send_signal_to_hub(symbol: str, signal: str, confidence: float, features: Dict = None):
|
| 282 |
-
if features is None:
|
| 283 |
-
features = {}
|
| 284 |
-
|
| 285 |
payload = {
|
| 286 |
-
"space_id": SPACE_ID,
|
| 287 |
-
"
|
| 288 |
-
"
|
| 289 |
-
"
|
| 290 |
-
"confidence": round(confidence, 4),
|
| 291 |
-
"features": features,
|
| 292 |
-
"metadata": {"version": "3.1"},
|
| 293 |
-
"timestamp": datetime.now().isoformat()
|
| 294 |
}
|
| 295 |
-
|
| 296 |
for attempt in range(3):
|
| 297 |
try:
|
| 298 |
-
|
| 299 |
-
r = await http_client.post(f"{HUB_URL}/signals", json=payload, timeout=15, headers=headers)
|
| 300 |
if r.status_code == 200:
|
| 301 |
logger.info(f"📤 {symbol}: {signal} conf={confidence:.3f}")
|
| 302 |
-
return
|
| 303 |
-
|
| 304 |
-
wait = 3 * (attempt + 1)
|
| 305 |
-
logger.warning(f"⏳ 429 для {symbol}, жду {wait}с...")
|
| 306 |
-
await asyncio.sleep(wait)
|
| 307 |
-
else:
|
| 308 |
-
logger.warning(f"Попытка {attempt+1}: HTTP {r.status_code}")
|
| 309 |
-
await asyncio.sleep(2)
|
| 310 |
except Exception as e:
|
| 311 |
logger.warning(f"Попытка {attempt+1}: {e}")
|
| 312 |
await asyncio.sleep(2)
|
| 313 |
-
|
| 314 |
-
logger.error(f"❌ Не удалось отправить {symbol}")
|
| 315 |
|
| 316 |
# ================= ГЛАВНЫЙ СИГНАЛ =================
|
| 317 |
async def get_derivative_signal(symbol: str = "ETH/USD") -> Dict[str, Any]:
|
|
@@ -319,24 +456,25 @@ async def get_derivative_signal(symbol: str = "ETH/USD") -> Dict[str, Any]:
|
|
| 319 |
analysis = await analyze_derivatives(symbol)
|
| 320 |
latency = int((time.time() - start) * 1000)
|
| 321 |
|
| 322 |
-
features = {
|
|
|
|
|
|
|
|
|
|
| 323 |
await send_signal_to_hub(symbol, analysis['signal'], analysis['confidence'], features)
|
| 324 |
|
| 325 |
-
logger.info(f"📊 Options {symbol}: {analysis['signal']} conf={analysis['confidence']:.3f} | {latency}ms")
|
| 326 |
return {
|
| 327 |
-
"space_id": SPACE_ID,
|
| 328 |
-
"
|
| 329 |
-
"
|
| 330 |
-
"signal": analysis['signal'],
|
| 331 |
-
"confidence": analysis['confidence'],
|
| 332 |
-
"derivative_analysis": analysis
|
| 333 |
}
|
| 334 |
|
| 335 |
# ================= АВТО-ОТПРАВКА =================
|
| 336 |
async def auto_send_loop():
|
| 337 |
logger.info(f"⏳ Стартовый сон {STARTUP_SLEEP}с...")
|
|
|
|
| 338 |
await asyncio.sleep(STARTUP_SLEEP)
|
| 339 |
-
logger.info(f"🔄 Options Engine
|
| 340 |
while True:
|
| 341 |
try:
|
| 342 |
for symbol in SYMBOLS:
|
|
@@ -344,28 +482,25 @@ async def auto_send_loop():
|
|
| 344 |
await asyncio.sleep(2)
|
| 345 |
logger.info("✅ Options Engine цикл завершён")
|
| 346 |
except Exception as e:
|
| 347 |
-
logger.error(f"Ошибка
|
|
|
|
| 348 |
await asyncio.sleep(AUTO_SEND_INTERVAL)
|
| 349 |
|
| 350 |
# ================= FASTAPI =================
|
| 351 |
-
app = FastAPI(title="Tomiris Space 26
|
| 352 |
|
| 353 |
@app.on_event("startup")
|
| 354 |
async def startup():
|
| 355 |
asyncio.create_task(auto_send_loop())
|
| 356 |
-
logger.info(f"🚀 Space 26
|
| 357 |
|
| 358 |
@app.on_event("shutdown")
|
| 359 |
-
async def shutdown():
|
| 360 |
-
await http_client.aclose()
|
| 361 |
|
| 362 |
@app.get("/health")
|
| 363 |
-
async def health():
|
| 364 |
-
return {"space_id": SPACE_ID, "status": "operational", "version": "3.1"}
|
| 365 |
-
|
| 366 |
@app.head("/health")
|
| 367 |
-
async def health_head():
|
| 368 |
-
return {}
|
| 369 |
|
| 370 |
@app.get("/consilium")
|
| 371 |
async def consilium(symbol: str = Query("ETH/USD")):
|
|
@@ -373,23 +508,40 @@ async def consilium(symbol: str = Query("ETH/USD")):
|
|
| 373 |
return await get_derivative_signal(symbol)
|
| 374 |
|
| 375 |
@app.get("/funding/{symbol}")
|
| 376 |
-
async def funding(symbol: str):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 377 |
@app.get("/oi/{symbol}")
|
| 378 |
-
async def oi(symbol: str):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 379 |
@app.get("/lsr/{symbol}")
|
| 380 |
-
async def lsr(symbol: str):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 381 |
@app.get("/options")
|
| 382 |
async def options(): return await fetch_deribit_options("ETH")
|
|
|
|
| 383 |
@app.get("/gold")
|
| 384 |
async def gold(): return await fetch_gold_derivatives()
|
|
|
|
| 385 |
@app.get("/vix")
|
| 386 |
async def vix(): return await fetch_vix()
|
|
|
|
| 387 |
@app.get("/maxpain/{symbol}")
|
| 388 |
async def maxpain(symbol: str):
|
| 389 |
price = await get_current_price(symbol); mp = calculate_max_pain(price)
|
| 390 |
return {"symbol": symbol, "current_price": price, "max_pain": mp}
|
|
|
|
| 391 |
@app.get("/history")
|
| 392 |
async def history(limit: int = 50): return list(OPTIONS_HISTORY)[-limit:]
|
|
|
|
| 393 |
@app.get("/send_now")
|
| 394 |
async def send_now():
|
| 395 |
results = {}
|
|
@@ -399,11 +551,10 @@ async def send_now():
|
|
| 399 |
return {"status": "sent", "results": results}
|
| 400 |
|
| 401 |
@app.get("/")
|
| 402 |
-
async def root():
|
| 403 |
-
return {"name": "Options Engine v3.1", "space_id": SPACE_ID, "hub": HUB_URL}
|
| 404 |
|
| 405 |
if __name__ == "__main__":
|
| 406 |
import uvicorn
|
| 407 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
| 408 |
|
| 409 |
-
print("🚀 SPACE 26
|
|
|
|
| 7 |
'numpy': 'numpy',
|
| 8 |
'pandas': 'pandas',
|
| 9 |
'httpx': 'httpx',
|
| 10 |
+
'scipy': 'scipy',
|
| 11 |
'fastapi': 'fastapi',
|
| 12 |
'uvicorn': 'uvicorn',
|
| 13 |
'requests': 'requests'
|
|
|
|
| 22 |
print(f"✅ {pip_name} установлен!")
|
| 23 |
|
| 24 |
# ============================================
|
| 25 |
+
# 👑 TOMIRIS SPACE 26 v4.0 — OPTIONS & DERIVATIVES ENGINE (УСИЛЕННЫЙ)
|
| 26 |
# ============================================
|
| 27 |
import os, time, json, logging, asyncio
|
| 28 |
+
from typing import Dict, Any, List, Optional, Tuple
|
| 29 |
from datetime import datetime, timezone
|
| 30 |
from collections import deque
|
| 31 |
import numpy as np
|
| 32 |
import pandas as pd
|
| 33 |
+
from scipy import stats
|
| 34 |
import httpx
|
| 35 |
from fastapi import FastAPI, Query
|
| 36 |
|
|
|
|
| 44 |
|
| 45 |
HUB_URL = os.getenv("HUB_URL", "https://pro-3-tomiris-hub.hf.space")
|
| 46 |
HUB_SECRET = os.getenv("HUB_SECRET", "TomyrisUltraSecret2026!")
|
| 47 |
+
TWELVE_DATA_KEY = os.getenv("TWELVE_DATA_KEY", "")
|
| 48 |
+
FRED_KEY = os.getenv("FRED_KEY", "")
|
| 49 |
+
FRED_KEY_2 = os.getenv("FRED_KEY_2", "")
|
| 50 |
|
| 51 |
+
FRED_KEYS = [k for k in [FRED_KEY, FRED_KEY_2] if k]
|
| 52 |
+
if not FRED_KEYS: FRED_KEYS = ["no_key"]
|
| 53 |
|
| 54 |
+
STARTUP_SLEEP = int(os.getenv("STARTUP_SLEEP", "120"))
|
| 55 |
+
AUTO_SEND_INTERVAL = int(os.getenv("AUTO_SEND_INTERVAL", "300"))
|
| 56 |
+
|
| 57 |
+
logger.info(f"🔗 Хаб: {HUB_URL} | Старт: {STARTUP_SLEEP}с | Интервал: {AUTO_SEND_INTERVAL}с")
|
| 58 |
+
logger.info(f"🔑 FRED: {len(FRED_KEYS)} | TwelveData: {'✓' if TWELVE_DATA_KEY else '✗'}")
|
| 59 |
|
| 60 |
HISTORY_FILE = "options_history.json"
|
| 61 |
+
CACHE_TTL = {"funding": 60, "oi": 60, "lsr": 120, "deribit": 300, "fred": 3600, "vix": 60}
|
| 62 |
|
| 63 |
http_client = httpx.AsyncClient(timeout=15.0)
|
| 64 |
|
| 65 |
def hub_headers():
|
| 66 |
return {"X-Hub-Secret": HUB_SECRET, "Content-Type": "application/json"}
|
| 67 |
|
| 68 |
+
async def log_to_hub(event_type: str, message: str, details: dict = None):
|
| 69 |
+
try:
|
| 70 |
+
await http_client.post(
|
| 71 |
+
f"{HUB_URL}/log",
|
| 72 |
+
json={"space_id": str(SPACE_ID), "event_type": event_type, "message": message, "details": details or {}},
|
| 73 |
+
headers=hub_headers(), timeout=5
|
| 74 |
+
)
|
| 75 |
+
except: pass
|
| 76 |
+
|
| 77 |
+
# ================= ИСТОРИЯ ДЛЯ Z-SCORE =================
|
| 78 |
+
SCORE_HISTORY = {sym: deque(maxlen=200) for sym in SYMBOLS}
|
| 79 |
+
FUNDING_HISTORY = {sym: deque(maxlen=200) for sym in SYMBOLS}
|
| 80 |
+
OI_HISTORY = {sym: deque(maxlen=200) for sym in SYMBOLS}
|
| 81 |
+
|
| 82 |
+
def calculate_zscore(current: float, history: deque) -> float:
|
| 83 |
+
if len(history) < 10: return 0.0
|
| 84 |
+
arr = np.array(list(history))
|
| 85 |
+
mean, std = arr.mean(), arr.std()
|
| 86 |
+
if std == 0: return 0.0
|
| 87 |
+
return (current - mean) / std
|
| 88 |
+
|
| 89 |
cache_store = {}
|
| 90 |
cache_times = {}
|
| 91 |
CIRCUIT_BREAKER = {}
|
|
|
|
| 119 |
# ================= ЗАГРУЗКА ДАННЫХ =================
|
| 120 |
async def fetch_vix() -> Dict[str, Any]:
|
| 121 |
if breaker_open("vix"): return {"vix": 20.0, "level": "NORMAL", "signal": "NEUTRAL"}
|
| 122 |
+
if not TWELVE_DATA_KEY: return {"vix": 20.0, "level": "NORMAL", "signal": "NEUTRAL"}
|
| 123 |
try:
|
| 124 |
r = await http_client.get(f"https://api.twelvedata.com/quote?symbol=VIX&apikey={TWELVE_DATA_KEY}")
|
| 125 |
if r.status_code == 200:
|
| 126 |
data = r.json(); vix_val = float(data.get("close", 20))
|
| 127 |
+
level = "CRITICAL" if vix_val > 35 else "HIGH" if vix_val > 28 else "ELEVATED" if vix_val > 22 else "NORMAL"
|
| 128 |
+
signal = "STRONG_SELL" if vix_val > 35 else "SELL" if vix_val > 28 else "CAUTION" if vix_val > 22 else "NEUTRAL"
|
| 129 |
breaker_record("vix", True)
|
| 130 |
return {"vix": vix_val, "level": level, "signal": signal}
|
| 131 |
except: breaker_record("vix", False)
|
|
|
|
| 134 |
async def fetch_funding_rate(symbol: str) -> Dict[str, Any]:
|
| 135 |
if breaker_open(f"funding_{symbol}"): return {"funding_rate": 0, "signal": "NEUTRAL"}
|
| 136 |
try:
|
| 137 |
+
r = await http_client.get(f"https://fapi.binance.com/fapi/v1/premiumIndex?symbol={symbol}")
|
| 138 |
if r.status_code == 200:
|
| 139 |
+
data = r.json()
|
| 140 |
+
if isinstance(data, list):
|
| 141 |
+
for item in data:
|
| 142 |
+
if item.get('symbol') == symbol:
|
| 143 |
+
fr = float(item.get('lastFundingRate', 0))
|
| 144 |
+
break
|
| 145 |
+
elif isinstance(data, dict):
|
| 146 |
+
fr = float(data.get('lastFundingRate', 0))
|
| 147 |
+
else:
|
| 148 |
+
return {"funding_rate": 0, "signal": "NEUTRAL"}
|
| 149 |
+
|
| 150 |
+
FUNDING_HISTORY[symbol].append(fr)
|
| 151 |
+
fr_z = calculate_zscore(fr, FUNDING_HISTORY[symbol])
|
| 152 |
+
|
| 153 |
+
if fr > 0.005: signal = "STRONG_BEARISH"
|
| 154 |
+
elif fr > 0.001: signal = "BEARISH"
|
| 155 |
+
elif fr < -0.005: signal = "STRONG_BULLISH"
|
| 156 |
+
elif fr < -0.001: signal = "BULLISH"
|
| 157 |
+
else: signal = "NEUTRAL"
|
| 158 |
+
|
| 159 |
+
breaker_record(f"funding_{symbol}", True)
|
| 160 |
+
return {"funding_rate": fr, "funding_rate_pct": round(fr * 100, 4), "zscore": round(fr_z, 2), "signal": signal}
|
| 161 |
except: breaker_record(f"funding_{symbol}", False)
|
| 162 |
return {"funding_rate": 0, "signal": "NEUTRAL"}
|
| 163 |
|
|
|
|
| 170 |
prev_key = f"oi_{symbol}_prev"; prev_oi = cache_store.get(prev_key, oi)
|
| 171 |
change = ((oi - prev_oi) / prev_oi * 100) if prev_oi > 0 else 0
|
| 172 |
cache_store[prev_key] = oi
|
| 173 |
+
|
| 174 |
+
OI_HISTORY[symbol].append(change)
|
| 175 |
+
oi_z = calculate_zscore(change, OI_HISTORY[symbol])
|
| 176 |
+
|
| 177 |
+
if change > 10: signal = "STRONG_BULLISH"
|
| 178 |
+
elif change > 3: signal = "BULLISH"
|
| 179 |
+
elif change < -10: signal = "STRONG_BEARISH"
|
| 180 |
+
elif change < -3: signal = "BEARISH"
|
| 181 |
+
else: signal = "NEUTRAL"
|
| 182 |
+
|
| 183 |
breaker_record(f"oi_{symbol}", True)
|
| 184 |
+
return {"open_interest": oi, "change_pct": round(change, 2), "zscore": round(oi_z, 2), "signal": signal}
|
| 185 |
except: breaker_record(f"oi_{symbol}", False)
|
| 186 |
return {"open_interest": 0, "change_pct": 0, "signal": "NEUTRAL"}
|
| 187 |
|
|
|
|
| 192 |
if r.status_code == 200:
|
| 193 |
lsr = float(r.json().get("longShortRatio", 1))
|
| 194 |
long_pct = lsr / (1 + lsr) * 100; short_pct = 100 - long_pct
|
| 195 |
+
if lsr > 3.0: signal = "STRONG_BEARISH"
|
| 196 |
+
elif lsr > 2.0: signal = "BEARISH"
|
| 197 |
+
elif lsr < 0.33: signal = "STRONG_BULLISH"
|
| 198 |
elif lsr < 0.5: signal = "BULLISH"
|
| 199 |
else: signal = "NEUTRAL"
|
| 200 |
breaker_record(f"lsr_{symbol}", True)
|
|
|
|
| 210 |
instruments = r.json().get('result', [])
|
| 211 |
call_vol = sum(float(i.get('volume', 0)) for i in instruments if i.get('option_type') == 'call')
|
| 212 |
put_vol = sum(float(i.get('volume', 0)) for i in instruments if i.get('option_type') == 'put')
|
| 213 |
+
|
| 214 |
+
# Open Interest тоже
|
| 215 |
+
call_oi = sum(float(i.get('open_interest', 0)) for i in instruments if i.get('option_type') == 'call')
|
| 216 |
+
put_oi = sum(float(i.get('open_interest', 0)) for i in instruments if i.get('option_type') == 'put')
|
| 217 |
+
|
| 218 |
+
pcr_volume = put_vol / call_vol if call_vol > 0 else 1.0
|
| 219 |
+
pcr_oi = put_oi / call_oi if call_oi > 0 else 1.0
|
| 220 |
+
|
| 221 |
+
if pcr_volume > 1.5: signal = "STRONG_BEARISH"
|
| 222 |
+
elif pcr_volume > 1.2: signal = "BEARISH"
|
| 223 |
+
elif pcr_volume < 0.6: signal = "STRONG_BULLISH"
|
| 224 |
+
elif pcr_volume < 0.8: signal = "BULLISH"
|
| 225 |
+
else: signal = "NEUTRAL"
|
| 226 |
+
|
| 227 |
breaker_record("deribit", True)
|
| 228 |
+
return {
|
| 229 |
+
"put_call_ratio_volume": round(pcr_volume, 4),
|
| 230 |
+
"put_call_ratio_oi": round(pcr_oi, 4),
|
| 231 |
+
"signal": signal
|
| 232 |
+
}
|
| 233 |
except: breaker_record("deribit", False)
|
| 234 |
+
return {"put_call_ratio_volume": 1.0, "put_call_ratio_oi": 1.0, "signal": "NEUTRAL"}
|
| 235 |
|
| 236 |
async def fetch_gold_derivatives() -> Dict[str, Any]:
|
| 237 |
if breaker_open("fred"): return {"signal": "NEUTRAL"}
|
| 238 |
+
if not FRED_KEYS or FRED_KEYS == ["no_key"]: return {"signal": "NEUTRAL"}
|
| 239 |
+
|
| 240 |
+
for key in FRED_KEYS:
|
| 241 |
+
try:
|
| 242 |
+
dxy_r = await http_client.get(f"https://api.stlouisfed.org/fred/series/observations?series_id=DTWEXBGS&api_key={key}&file_type=json&limit=30&sort_order=desc")
|
| 243 |
+
tips_r = await http_client.get(f"https://api.stlouisfed.org/fred/series/observations?series_id=DFII10&api_key={key}&file_type=json&limit=30&sort_order=desc")
|
| 244 |
+
|
| 245 |
+
if dxy_r.status_code == 200 and tips_r.status_code == 200:
|
| 246 |
+
dxy_vals = [float(o['value']) for o in dxy_r.json().get('observations', []) if o['value'] != '.']
|
| 247 |
+
tips_vals = [float(o['value']) for o in tips_r.json().get('observations', []) if o['value'] != '.']
|
| 248 |
+
|
| 249 |
+
dxy_change = ((dxy_vals[0] - dxy_vals[-1]) / dxy_vals[-1] * 100) if len(dxy_vals) >= 2 else 0
|
| 250 |
+
tips_current = tips_vals[0] if tips_vals else 0.5
|
| 251 |
+
|
| 252 |
+
if dxy_change < -2 and tips_current < 0: signal = "STRONG_BULLISH"
|
| 253 |
+
elif dxy_change < 0 and tips_current < 0: signal = "BULLISH"
|
| 254 |
+
elif dxy_change > 2 and tips_current > 0: signal = "STRONG_BEARISH"
|
| 255 |
+
elif dxy_change > 0 and tips_current > 0: signal = "BEARISH"
|
| 256 |
+
else: signal = "NEUTRAL"
|
| 257 |
+
|
| 258 |
+
breaker_record("fred", True)
|
| 259 |
+
return {
|
| 260 |
+
"dxy": dxy_vals[0] if dxy_vals else 104.5,
|
| 261 |
+
"dxy_change_pct": round(dxy_change, 2),
|
| 262 |
+
"tips_yield": tips_current,
|
| 263 |
+
"signal": signal
|
| 264 |
+
}
|
| 265 |
+
except: continue
|
| 266 |
+
|
| 267 |
+
breaker_record("fred", False)
|
| 268 |
return {"signal": "NEUTRAL"}
|
| 269 |
|
| 270 |
async def get_current_price(symbol: str) -> float:
|
| 271 |
try:
|
| 272 |
r = await http_client.get(f"{HUB_URL}/price/{symbol}", headers=hub_headers())
|
| 273 |
+
if r.status_code == 200:
|
| 274 |
data = r.json()
|
| 275 |
return float(data.get("price", data.get("mid", 0)))
|
| 276 |
except: pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 277 |
return 0.0
|
| 278 |
|
| 279 |
def calculate_max_pain(current_price: float) -> float:
|
| 280 |
return round(current_price * 0.98, 2) if current_price > 0 else 0.0
|
| 281 |
|
| 282 |
+
# ================= 🔥 АНАЛИЗ =================
|
| 283 |
async def analyze_derivatives(symbol: str) -> Dict[str, Any]:
|
| 284 |
native = "XAU" if "XAU" in symbol else ("ETH" if "ETH" in symbol else "SOL")
|
| 285 |
bin_sym = "" if native == "XAU" else (native + "USDT")
|
|
|
|
| 292 |
|
| 293 |
results = await asyncio.gather(*tasks)
|
| 294 |
idx = 0; metrics = {}
|
| 295 |
+
|
| 296 |
if bin_sym:
|
| 297 |
metrics['funding_rate'] = results[idx]; idx += 1
|
| 298 |
metrics['open_interest'] = results[idx]; idx += 1
|
|
|
|
| 301 |
if native == "XAU": metrics['gold_derivatives'] = results[idx]; idx += 1
|
| 302 |
vix_data = results[idx]
|
| 303 |
|
| 304 |
+
signals = {}
|
| 305 |
+
score = 50.0
|
| 306 |
+
|
| 307 |
+
# VIX (вес 15%)
|
| 308 |
+
vix_val = vix_data.get("vix", 20)
|
| 309 |
+
if vix_val > 35:
|
| 310 |
+
signals["vix"] = ("STRONG_SELL", 18) if native != "XAU" else ("STRONG_BUY", 18)
|
| 311 |
+
score += 18 if native == "XAU" else -18
|
| 312 |
+
elif vix_val > 28:
|
| 313 |
+
signals["vix"] = ("SELL", 12) if native != "XAU" else ("BUY", 12)
|
| 314 |
+
score += 12 if native == "XAU" else -12
|
| 315 |
+
elif vix_val > 22:
|
| 316 |
+
score += 6 if native == "XAU" else -6
|
| 317 |
+
signals["vix"] = ("SLIGHT_BUY", 6) if native == "XAU" else ("SLIGHT_SELL", 6)
|
| 318 |
+
else:
|
| 319 |
+
signals["vix"] = ("NEUTRAL", 0)
|
| 320 |
|
| 321 |
+
# Funding Rate (вес 20%)
|
| 322 |
fund = metrics.get('funding_rate', {})
|
| 323 |
+
fund_signal = fund.get('signal', 'NEUTRAL')
|
| 324 |
+
fund_z = fund.get('zscore', 0)
|
| 325 |
+
if fund_signal == 'STRONG_BEARISH':
|
| 326 |
+
score -= 20; signals["funding"] = ("STRONG_SELL", 20)
|
| 327 |
+
elif fund_signal == 'BEARISH':
|
| 328 |
+
score -= 14; signals["funding"] = ("SELL", 14)
|
| 329 |
+
elif fund_signal == 'STRONG_BULLISH':
|
| 330 |
+
score += 20; signals["funding"] = ("STRONG_BUY", 20)
|
| 331 |
+
elif fund_signal == 'BULLISH':
|
| 332 |
+
score += 14; signals["funding"] = ("BUY", 14)
|
| 333 |
+
else:
|
| 334 |
+
signals["funding"] = ("NEUTRAL", 0)
|
| 335 |
+
|
| 336 |
+
if abs(fund_z) > 2.0:
|
| 337 |
+
score += 8 if fund_z < 0 else -8
|
| 338 |
|
| 339 |
+
# Open Interest (вес 15%)
|
| 340 |
oi = metrics.get('open_interest', {})
|
| 341 |
+
oi_signal = oi.get('signal', 'NEUTRAL')
|
| 342 |
+
if oi_signal == 'STRONG_BULLISH':
|
| 343 |
+
score += 12; signals["oi"] = ("BUY", 12)
|
| 344 |
+
elif oi_signal == 'BULLISH':
|
| 345 |
+
score += 8; signals["oi"] = ("SLIGHT_BUY", 8)
|
| 346 |
+
elif oi_signal == 'STRONG_BEARISH':
|
| 347 |
+
score -= 12; signals["oi"] = ("SELL", 12)
|
| 348 |
+
elif oi_signal == 'BEARISH':
|
| 349 |
+
score -= 8; signals["oi"] = ("SLIGHT_SELL", 8)
|
| 350 |
+
else:
|
| 351 |
+
signals["oi"] = ("NEUTRAL", 0)
|
| 352 |
|
| 353 |
+
# Long/Short Ratio (вес 15%)
|
| 354 |
lsr = metrics.get('long_short_ratio', {})
|
| 355 |
+
lsr_signal = lsr.get('signal', 'NEUTRAL')
|
| 356 |
+
if lsr_signal == 'STRONG_BEARISH':
|
| 357 |
+
score -= 15; signals["lsr"] = ("STRONG_SELL", 15)
|
| 358 |
+
elif lsr_signal == 'BEARISH':
|
| 359 |
+
score -= 10; signals["lsr"] = ("SELL", 10)
|
| 360 |
+
elif lsr_signal == 'STRONG_BULLISH':
|
| 361 |
+
score += 15; signals["lsr"] = ("STRONG_BUY", 15)
|
| 362 |
+
elif lsr_signal == 'BULLISH':
|
| 363 |
+
score += 10; signals["lsr"] = ("BUY", 10)
|
| 364 |
+
else:
|
| 365 |
+
signals["lsr"] = ("NEUTRAL", 0)
|
| 366 |
|
| 367 |
+
# Options PCR (вес 15%)
|
| 368 |
options = metrics.get('options', {})
|
| 369 |
+
if options.get('signal') == 'STRONG_BEARISH':
|
| 370 |
+
score -= 12; signals["options"] = ("SELL", 12)
|
| 371 |
+
elif options.get('signal') == 'BEARISH':
|
| 372 |
+
score -= 8; signals["options"] = ("SLIGHT_SELL", 8)
|
| 373 |
+
elif options.get('signal') == 'STRONG_BULLISH':
|
| 374 |
+
score += 12; signals["options"] = ("BUY", 12)
|
| 375 |
elif options.get('signal') == 'BULLISH':
|
| 376 |
+
score += 8; signals["options"] = ("SLIGHT_BUY", 8)
|
| 377 |
+
else:
|
| 378 |
+
signals["options"] = ("NEUTRAL", 0)
|
| 379 |
|
| 380 |
+
# Gold Derivatives (вес 10%)
|
| 381 |
gold = metrics.get('gold_derivatives', {})
|
| 382 |
+
if gold.get('signal') == 'STRONG_BULLISH':
|
| 383 |
+
score += 12; signals["gold"] = ("STRONG_BUY", 12)
|
| 384 |
+
elif gold.get('signal') == 'BULLISH':
|
| 385 |
+
score += 8; signals["gold"] = ("BUY", 8)
|
| 386 |
+
elif gold.get('signal') == 'STRONG_BEARISH':
|
| 387 |
+
score -= 12; signals["gold"] = ("STRONG_SELL", 12)
|
| 388 |
elif gold.get('signal') == 'BEARISH':
|
| 389 |
+
score -= 8; signals["gold"] = ("SELL", 8)
|
| 390 |
+
else:
|
| 391 |
+
signals["gold"] = ("NEUTRAL", 0)
|
| 392 |
|
| 393 |
+
# Max Pain (вес 10%)
|
| 394 |
price = await get_current_price(symbol) if native != "XAU" else 0
|
| 395 |
max_pain = calculate_max_pain(price) if price > 0 else 0
|
| 396 |
+
if max_pain > 0 and price < max_pain * 0.97:
|
| 397 |
+
score += 8; signals["maxpain"] = ("BUY", 8)
|
| 398 |
+
elif max_pain > 0 and price > max_pain * 1.03:
|
| 399 |
+
score -= 8; signals["maxpain"] = ("SELL", 8)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 400 |
else:
|
| 401 |
+
signals["maxpain"] = ("NEUTRAL", 0)
|
| 402 |
+
|
| 403 |
+
score = round(max(3, min(97, score)), 1)
|
| 404 |
+
|
| 405 |
+
# Z-score
|
| 406 |
+
SCORE_HISTORY[symbol].append(score)
|
| 407 |
+
score_z = calculate_zscore(score, SCORE_HISTORY[symbol])
|
| 408 |
+
|
| 409 |
+
if score > 62: signal, confidence = "BUY", min(0.92, score / 100)
|
| 410 |
+
elif score > 54: signal, confidence = "BUY", min(0.68, (score - 50) / 50)
|
| 411 |
+
elif score < 38: signal, confidence = "SELL", min(0.92, (100 - score) / 100)
|
| 412 |
+
elif score < 46: signal, confidence = "SELL", min(0.68, (50 - score) / 50)
|
| 413 |
+
else: signal, confidence = "WAIT", 0.0
|
| 414 |
|
| 415 |
+
analysis = {
|
| 416 |
+
"derivative_score": score,
|
| 417 |
+
"score_zscore": round(score_z, 2),
|
| 418 |
+
"signal": signal,
|
| 419 |
+
"confidence": round(confidence, 4),
|
| 420 |
+
"signals_breakdown": {k: v[0] for k, v in signals.items()},
|
| 421 |
+
"metrics": {**metrics, "vix": vix_data, "max_pain": max_pain}
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
OPTIONS_HISTORY.append({
|
| 425 |
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
| 426 |
+
"symbol": symbol, "signal": signal, "score": score
|
| 427 |
+
})
|
| 428 |
save_history()
|
| 429 |
+
|
| 430 |
return analysis
|
| 431 |
|
| 432 |
+
# ================= ОТПРАВКА В HUB =================
|
| 433 |
async def send_signal_to_hub(symbol: str, signal: str, confidence: float, features: Dict = None):
|
| 434 |
+
if features is None: features = {}
|
|
|
|
|
|
|
| 435 |
payload = {
|
| 436 |
+
"space_id": SPACE_ID, "space_name": SPACE_NAME,
|
| 437 |
+
"symbol": symbol, "signal": signal, "confidence": round(confidence, 4),
|
| 438 |
+
"features": features, "metadata": {"version": "4.0"},
|
| 439 |
+
"timestamp": datetime.now(timezone.utc).isoformat()
|
|
|
|
|
|
|
|
|
|
|
|
|
| 440 |
}
|
|
|
|
| 441 |
for attempt in range(3):
|
| 442 |
try:
|
| 443 |
+
r = await http_client.post(f"{HUB_URL}/signals", json=payload, timeout=15, headers=hub_headers())
|
|
|
|
| 444 |
if r.status_code == 200:
|
| 445 |
logger.info(f"📤 {symbol}: {signal} conf={confidence:.3f}")
|
| 446 |
+
return True
|
| 447 |
+
await asyncio.sleep(2)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 448 |
except Exception as e:
|
| 449 |
logger.warning(f"Попытка {attempt+1}: {e}")
|
| 450 |
await asyncio.sleep(2)
|
| 451 |
+
return False
|
|
|
|
| 452 |
|
| 453 |
# ================= ГЛАВНЫЙ СИГНАЛ =================
|
| 454 |
async def get_derivative_signal(symbol: str = "ETH/USD") -> Dict[str, Any]:
|
|
|
|
| 456 |
analysis = await analyze_derivatives(symbol)
|
| 457 |
latency = int((time.time() - start) * 1000)
|
| 458 |
|
| 459 |
+
features = {
|
| 460 |
+
"derivative_score": analysis['derivative_score'],
|
| 461 |
+
"score_zscore": analysis['score_zscore']
|
| 462 |
+
}
|
| 463 |
await send_signal_to_hub(symbol, analysis['signal'], analysis['confidence'], features)
|
| 464 |
|
| 465 |
+
logger.info(f"📊 Options {symbol}: {analysis['signal']} conf={analysis['confidence']:.3f} score={analysis['derivative_score']} | {latency}ms")
|
| 466 |
return {
|
| 467 |
+
"space_id": SPACE_ID, "timestamp": int(time.time()),
|
| 468 |
+
"symbol": symbol, "signal": analysis['signal'],
|
| 469 |
+
"confidence": analysis['confidence'], "derivative_analysis": analysis
|
|
|
|
|
|
|
|
|
|
| 470 |
}
|
| 471 |
|
| 472 |
# ================= АВТО-ОТПРАВКА =================
|
| 473 |
async def auto_send_loop():
|
| 474 |
logger.info(f"⏳ Стартовый сон {STARTUP_SLEEP}с...")
|
| 475 |
+
await log_to_hub("STARTUP", f"Options Engine v4.0 запущен, жду {STARTUP_SLEEP}с")
|
| 476 |
await asyncio.sleep(STARTUP_SLEEP)
|
| 477 |
+
logger.info(f"🔄 Options Engine [интервал={AUTO_SEND_INTERVAL}с]")
|
| 478 |
while True:
|
| 479 |
try:
|
| 480 |
for symbol in SYMBOLS:
|
|
|
|
| 482 |
await asyncio.sleep(2)
|
| 483 |
logger.info("✅ Options Engine цикл завершён")
|
| 484 |
except Exception as e:
|
| 485 |
+
logger.error(f"Ошибка: {e}")
|
| 486 |
+
await log_to_hub("ERROR", f"Ошибка: {str(e)[:200]}")
|
| 487 |
await asyncio.sleep(AUTO_SEND_INTERVAL)
|
| 488 |
|
| 489 |
# ================= FASTAPI =================
|
| 490 |
+
app = FastAPI(title="Tomiris Space 26 v4.0 — Options & Derivatives")
|
| 491 |
|
| 492 |
@app.on_event("startup")
|
| 493 |
async def startup():
|
| 494 |
asyncio.create_task(auto_send_loop())
|
| 495 |
+
logger.info(f"🚀 Space 26 v4.0 | Хаб: {HUB_URL}")
|
| 496 |
|
| 497 |
@app.on_event("shutdown")
|
| 498 |
+
async def shutdown(): await http_client.aclose()
|
|
|
|
| 499 |
|
| 500 |
@app.get("/health")
|
| 501 |
+
async def health(): return {"space_id": SPACE_ID, "status": "operational", "version": "4.0"}
|
|
|
|
|
|
|
| 502 |
@app.head("/health")
|
| 503 |
+
async def health_head(): return {}
|
|
|
|
| 504 |
|
| 505 |
@app.get("/consilium")
|
| 506 |
async def consilium(symbol: str = Query("ETH/USD")):
|
|
|
|
| 508 |
return await get_derivative_signal(symbol)
|
| 509 |
|
| 510 |
@app.get("/funding/{symbol}")
|
| 511 |
+
async def funding(symbol: str):
|
| 512 |
+
native = "XAU" if "XAU" in symbol.upper() else ("ETH" if "ETH" in symbol.upper() else "SOL")
|
| 513 |
+
bin_sym = native + "USDT"
|
| 514 |
+
return await fetch_funding_rate(bin_sym)
|
| 515 |
+
|
| 516 |
@app.get("/oi/{symbol}")
|
| 517 |
+
async def oi(symbol: str):
|
| 518 |
+
native = "XAU" if "XAU" in symbol.upper() else ("ETH" if "ETH" in symbol.upper() else "SOL")
|
| 519 |
+
bin_sym = native + "USDT"
|
| 520 |
+
return await fetch_open_interest(bin_sym)
|
| 521 |
+
|
| 522 |
@app.get("/lsr/{symbol}")
|
| 523 |
+
async def lsr(symbol: str):
|
| 524 |
+
native = "XAU" if "XAU" in symbol.upper() else ("ETH" if "ETH" in symbol.upper() else "SOL")
|
| 525 |
+
bin_sym = native + "USDT"
|
| 526 |
+
return await fetch_long_short_ratio(bin_sym)
|
| 527 |
+
|
| 528 |
@app.get("/options")
|
| 529 |
async def options(): return await fetch_deribit_options("ETH")
|
| 530 |
+
|
| 531 |
@app.get("/gold")
|
| 532 |
async def gold(): return await fetch_gold_derivatives()
|
| 533 |
+
|
| 534 |
@app.get("/vix")
|
| 535 |
async def vix(): return await fetch_vix()
|
| 536 |
+
|
| 537 |
@app.get("/maxpain/{symbol}")
|
| 538 |
async def maxpain(symbol: str):
|
| 539 |
price = await get_current_price(symbol); mp = calculate_max_pain(price)
|
| 540 |
return {"symbol": symbol, "current_price": price, "max_pain": mp}
|
| 541 |
+
|
| 542 |
@app.get("/history")
|
| 543 |
async def history(limit: int = 50): return list(OPTIONS_HISTORY)[-limit:]
|
| 544 |
+
|
| 545 |
@app.get("/send_now")
|
| 546 |
async def send_now():
|
| 547 |
results = {}
|
|
|
|
| 551 |
return {"status": "sent", "results": results}
|
| 552 |
|
| 553 |
@app.get("/")
|
| 554 |
+
async def root(): return {"name": "Options Engine v4.0", "space_id": SPACE_ID, "hub": HUB_URL}
|
|
|
|
| 555 |
|
| 556 |
if __name__ == "__main__":
|
| 557 |
import uvicorn
|
| 558 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
| 559 |
|
| 560 |
+
print("🚀 SPACE 26 v4.0 — OPTIONS & DERIVATIVES ENGINE ГОТОВ!")
|