Spaces:
Sleeping
Sleeping
| # ============================================ | |
| # АВТО-УСТАНОВКА ПАКЕТОВ | |
| # ============================================ | |
| import subprocess, sys, importlib | |
| REQUIRED_PACKAGES = { | |
| 'numpy': 'numpy', | |
| 'pandas': 'pandas', | |
| 'httpx': 'httpx', | |
| 'scipy': 'scipy', | |
| 'fastapi': 'fastapi', | |
| 'uvicorn': 'uvicorn', | |
| 'requests': 'requests' | |
| } | |
| for module_name, pip_name in REQUIRED_PACKAGES.items(): | |
| try: | |
| importlib.import_module(module_name) | |
| except ImportError: | |
| print(f"📦 Устанавливаю {pip_name}...") | |
| subprocess.check_call([sys.executable, "-m", "pip", "install", pip_name]) | |
| print(f"✅ {pip_name} установлен!") | |
| # ============================================ | |
| # 👑 TOMIRIS SPACE 26 v4.0 — OPTIONS & DERIVATIVES ENGINE (УСИЛЕННЫЙ) | |
| # ============================================ | |
| import os, time, json, logging, asyncio | |
| from typing import Dict, Any, List, Optional, Tuple | |
| from datetime import datetime, timezone | |
| from collections import deque | |
| import numpy as np | |
| import pandas as pd | |
| from scipy import stats | |
| import httpx | |
| from fastapi import FastAPI, Query | |
| logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") | |
| logger = logging.getLogger("Space26_Options") | |
| # ================= КОНФИГУРАЦИЯ ================= | |
| SPACE_ID = 26 | |
| SPACE_NAME = "Options & Derivatives" | |
| SYMBOLS = ["XAU/USD", "ETH/USD", "SOL/USD"] | |
| HUB_URL = "https://TOMI-HUB-HUB-FINAL.hf.space" | |
| HUB_SECRET = os.getenv("HUB_SECRET", "TomyrisUltraSecret2026!") | |
| TWELVE_DATA_KEY = os.getenv("TWELVE_DATA_KEY", "") | |
| FRED_KEY = os.getenv("FRED_KEY", "") | |
| FRED_KEY_2 = os.getenv("FRED_KEY_2", "") | |
| FRED_KEYS = [k for k in [FRED_KEY, FRED_KEY_2] if k] | |
| if not FRED_KEYS: FRED_KEYS = ["no_key"] | |
| STARTUP_SLEEP = int(os.getenv("STARTUP_SLEEP", "120")) | |
| AUTO_SEND_INTERVAL = int(os.getenv("AUTO_SEND_INTERVAL", "300")) | |
| logger.info(f"🔗 Хаб: {HUB_URL} | Старт: {STARTUP_SLEEP}с | Интервал: {AUTO_SEND_INTERVAL}с") | |
| logger.info(f"🔑 FRED: {len(FRED_KEYS)} | TwelveData: {'✓' if TWELVE_DATA_KEY else '✗'}") | |
| HISTORY_FILE = "options_history.json" | |
| CACHE_TTL = {"funding": 60, "oi": 60, "lsr": 120, "deribit": 300, "fred": 3600, "vix": 60} | |
| http_client = httpx.AsyncClient(timeout=15.0) | |
| def hub_headers(): | |
| return {"X-Hub-Secret": HUB_SECRET, "Content-Type": "application/json"} | |
| async def log_to_hub(event_type: str, message: str, details: dict = None): | |
| try: | |
| await http_client.post( | |
| f"{HUB_URL}/log", | |
| json={"space_id": str(SPACE_ID), "event_type": event_type, "message": message, "details": details or {}}, | |
| headers=hub_headers(), timeout=5 | |
| ) | |
| except: pass | |
| # ================= ИСТОРИЯ ДЛЯ Z-SCORE ================= | |
| SCORE_HISTORY = {sym: deque(maxlen=200) for sym in SYMBOLS} | |
| FUNDING_HISTORY = {sym: deque(maxlen=200) for sym in SYMBOLS} | |
| OI_HISTORY = {sym: deque(maxlen=200) for sym in SYMBOLS} | |
| def calculate_zscore(current: float, history: deque) -> float: | |
| if len(history) < 10: return 0.0 | |
| arr = np.array(list(history)) | |
| mean, std = arr.mean(), arr.std() | |
| if std == 0: return 0.0 | |
| return (current - mean) / std | |
| cache_store = {} | |
| cache_times = {} | |
| CIRCUIT_BREAKER = {} | |
| def breaker_open(name: str) -> bool: | |
| info = CIRCUIT_BREAKER.get(name) | |
| if not info: return False | |
| if info["fails"] < 3: return False | |
| if time.time() - info["last_fail"] > 600: | |
| CIRCUIT_BREAKER[name] = {"fails": 0, "last_fail": 0} | |
| return False | |
| return True | |
| def breaker_record(name: str, success: bool): | |
| info = CIRCUIT_BREAKER.get(name, {"fails": 0, "last_fail": 0}) | |
| if success: info["fails"] = 0 | |
| else: info["fails"] += 1; info["last_fail"] = time.time() | |
| CIRCUIT_BREAKER[name] = info | |
| if os.path.exists(HISTORY_FILE): | |
| try: | |
| with open(HISTORY_FILE) as f: OPTIONS_HISTORY = deque(json.load(f), maxlen=500) | |
| except: OPTIONS_HISTORY = deque(maxlen=500) | |
| else: OPTIONS_HISTORY = deque(maxlen=500) | |
| def save_history(): | |
| try: | |
| with open(HISTORY_FILE, 'w') as f: json.dump(list(OPTIONS_HISTORY), f) | |
| except: pass | |
| # ================= ЗАГРУЗКА ДАННЫХ ================= | |
| async def fetch_vix() -> Dict[str, Any]: | |
| if breaker_open("vix"): return {"vix": 20.0, "level": "NORMAL", "signal": "NEUTRAL"} | |
| if not TWELVE_DATA_KEY: return {"vix": 20.0, "level": "NORMAL", "signal": "NEUTRAL"} | |
| try: | |
| r = await http_client.get(f"https://api.twelvedata.com/quote?symbol=VIX&apikey={TWELVE_DATA_KEY}") | |
| if r.status_code == 200: | |
| data = r.json(); vix_val = float(data.get("close", 20)) | |
| level = "CRITICAL" if vix_val > 35 else "HIGH" if vix_val > 28 else "ELEVATED" if vix_val > 22 else "NORMAL" | |
| signal = "STRONG_SELL" if vix_val > 35 else "SELL" if vix_val > 28 else "CAUTION" if vix_val > 22 else "NEUTRAL" | |
| breaker_record("vix", True) | |
| return {"vix": vix_val, "level": level, "signal": signal} | |
| except: breaker_record("vix", False) | |
| return {"vix": 20.0, "level": "NORMAL", "signal": "NEUTRAL"} | |
| async def fetch_funding_rate(symbol: str) -> Dict[str, Any]: | |
| if breaker_open(f"funding_{symbol}"): return {"funding_rate": 0, "signal": "NEUTRAL"} | |
| try: | |
| r = await http_client.get(f"https://fapi.binance.com/fapi/v1/premiumIndex?symbol={symbol}") | |
| if r.status_code == 200: | |
| data = r.json() | |
| if isinstance(data, list): | |
| for item in data: | |
| if item.get('symbol') == symbol: | |
| fr = float(item.get('lastFundingRate', 0)) | |
| break | |
| elif isinstance(data, dict): | |
| fr = float(data.get('lastFundingRate', 0)) | |
| else: | |
| return {"funding_rate": 0, "signal": "NEUTRAL"} | |
| FUNDING_HISTORY[symbol].append(fr) | |
| fr_z = calculate_zscore(fr, FUNDING_HISTORY[symbol]) | |
| if fr > 0.005: signal = "STRONG_BEARISH" | |
| elif fr > 0.001: signal = "BEARISH" | |
| elif fr < -0.005: signal = "STRONG_BULLISH" | |
| elif fr < -0.001: signal = "BULLISH" | |
| else: signal = "NEUTRAL" | |
| breaker_record(f"funding_{symbol}", True) | |
| return {"funding_rate": fr, "funding_rate_pct": round(fr * 100, 4), "zscore": round(fr_z, 2), "signal": signal} | |
| except: breaker_record(f"funding_{symbol}", False) | |
| return {"funding_rate": 0, "signal": "NEUTRAL"} | |
| async def fetch_open_interest(symbol: str) -> Dict[str, Any]: | |
| if breaker_open(f"oi_{symbol}"): return {"open_interest": 0, "change_pct": 0, "signal": "NEUTRAL"} | |
| try: | |
| r = await http_client.get(f"https://fapi.binance.com/fapi/v1/openInterest?symbol={symbol}") | |
| if r.status_code == 200: | |
| oi = float(r.json().get("openInterest", 0)) | |
| prev_key = f"oi_{symbol}_prev"; prev_oi = cache_store.get(prev_key, oi) | |
| change = ((oi - prev_oi) / prev_oi * 100) if prev_oi > 0 else 0 | |
| cache_store[prev_key] = oi | |
| OI_HISTORY[symbol].append(change) | |
| oi_z = calculate_zscore(change, OI_HISTORY[symbol]) | |
| if change > 10: signal = "STRONG_BULLISH" | |
| elif change > 3: signal = "BULLISH" | |
| elif change < -10: signal = "STRONG_BEARISH" | |
| elif change < -3: signal = "BEARISH" | |
| else: signal = "NEUTRAL" | |
| breaker_record(f"oi_{symbol}", True) | |
| return {"open_interest": oi, "change_pct": round(change, 2), "zscore": round(oi_z, 2), "signal": signal} | |
| except: breaker_record(f"oi_{symbol}", False) | |
| return {"open_interest": 0, "change_pct": 0, "signal": "NEUTRAL"} | |
| async def fetch_long_short_ratio(symbol: str) -> Dict[str, Any]: | |
| if breaker_open(f"lsr_{symbol}"): return {"long_short_ratio": 1, "signal": "NEUTRAL"} | |
| try: | |
| r = await http_client.get(f"https://fapi.binance.com/fapi/v1/globalLongShortAccountRatio?symbol={symbol}&period=5m") | |
| if r.status_code == 200: | |
| lsr = float(r.json().get("longShortRatio", 1)) | |
| long_pct = lsr / (1 + lsr) * 100; short_pct = 100 - long_pct | |
| if lsr > 3.0: signal = "STRONG_BEARISH" | |
| elif lsr > 2.0: signal = "BEARISH" | |
| elif lsr < 0.33: signal = "STRONG_BULLISH" | |
| elif lsr < 0.5: signal = "BULLISH" | |
| else: signal = "NEUTRAL" | |
| breaker_record(f"lsr_{symbol}", True) | |
| return {"long_short_ratio": round(lsr, 4), "long_pct": round(long_pct, 1), "short_pct": round(short_pct, 1), "signal": signal} | |
| except: breaker_record(f"lsr_{symbol}", False) | |
| return {"long_short_ratio": 1, "signal": "NEUTRAL"} | |
| async def fetch_deribit_options(coin: str = "ETH") -> Dict[str, Any]: | |
| if breaker_open("deribit"): return {"put_call_ratio_volume": 1.0, "signal": "NEUTRAL"} | |
| try: | |
| r = await http_client.get(f"https://www.deribit.com/api/v2/public/get_book_summary_by_currency?currency={coin}&kind=option") | |
| if r.status_code == 200: | |
| instruments = r.json().get('result', []) | |
| call_vol = sum(float(i.get('volume', 0)) for i in instruments if i.get('option_type') == 'call') | |
| put_vol = sum(float(i.get('volume', 0)) for i in instruments if i.get('option_type') == 'put') | |
| # Open Interest тоже | |
| call_oi = sum(float(i.get('open_interest', 0)) for i in instruments if i.get('option_type') == 'call') | |
| put_oi = sum(float(i.get('open_interest', 0)) for i in instruments if i.get('option_type') == 'put') | |
| pcr_volume = put_vol / call_vol if call_vol > 0 else 1.0 | |
| pcr_oi = put_oi / call_oi if call_oi > 0 else 1.0 | |
| if pcr_volume > 1.5: signal = "STRONG_BEARISH" | |
| elif pcr_volume > 1.2: signal = "BEARISH" | |
| elif pcr_volume < 0.6: signal = "STRONG_BULLISH" | |
| elif pcr_volume < 0.8: signal = "BULLISH" | |
| else: signal = "NEUTRAL" | |
| breaker_record("deribit", True) | |
| return { | |
| "put_call_ratio_volume": round(pcr_volume, 4), | |
| "put_call_ratio_oi": round(pcr_oi, 4), | |
| "signal": signal | |
| } | |
| except: breaker_record("deribit", False) | |
| return {"put_call_ratio_volume": 1.0, "put_call_ratio_oi": 1.0, "signal": "NEUTRAL"} | |
| async def fetch_gold_derivatives() -> Dict[str, Any]: | |
| if breaker_open("fred"): return {"signal": "NEUTRAL"} | |
| if not FRED_KEYS or FRED_KEYS == ["no_key"]: return {"signal": "NEUTRAL"} | |
| for key in FRED_KEYS: | |
| try: | |
| 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") | |
| 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") | |
| if dxy_r.status_code == 200 and tips_r.status_code == 200: | |
| dxy_vals = [float(o['value']) for o in dxy_r.json().get('observations', []) if o['value'] != '.'] | |
| tips_vals = [float(o['value']) for o in tips_r.json().get('observations', []) if o['value'] != '.'] | |
| dxy_change = ((dxy_vals[0] - dxy_vals[-1]) / dxy_vals[-1] * 100) if len(dxy_vals) >= 2 else 0 | |
| tips_current = tips_vals[0] if tips_vals else 0.5 | |
| if dxy_change < -2 and tips_current < 0: signal = "STRONG_BULLISH" | |
| elif dxy_change < 0 and tips_current < 0: signal = "BULLISH" | |
| elif dxy_change > 2 and tips_current > 0: signal = "STRONG_BEARISH" | |
| elif dxy_change > 0 and tips_current > 0: signal = "BEARISH" | |
| else: signal = "NEUTRAL" | |
| breaker_record("fred", True) | |
| return { | |
| "dxy": dxy_vals[0] if dxy_vals else 104.5, | |
| "dxy_change_pct": round(dxy_change, 2), | |
| "tips_yield": tips_current, | |
| "signal": signal | |
| } | |
| except: continue | |
| breaker_record("fred", False) | |
| return {"signal": "NEUTRAL"} | |
| async def get_current_price(symbol: str) -> float: | |
| try: | |
| r = await http_client.get(f"{HUB_URL}/price/{symbol}", headers=hub_headers()) | |
| if r.status_code == 200: | |
| data = r.json() | |
| return float(data.get("price", data.get("mid", 0))) | |
| except: pass | |
| return 0.0 | |
| def calculate_max_pain(current_price: float) -> float: | |
| return round(current_price * 0.98, 2) if current_price > 0 else 0.0 | |
| # ================= 🔥 АНАЛИЗ ================= | |
| async def analyze_derivatives(symbol: str) -> Dict[str, Any]: | |
| native = "XAU" if "XAU" in symbol else ("ETH" if "ETH" in symbol else "SOL") | |
| bin_sym = "" if native == "XAU" else (native + "USDT") | |
| tasks = [] | |
| if bin_sym: tasks.extend([fetch_funding_rate(bin_sym), fetch_open_interest(bin_sym), fetch_long_short_ratio(bin_sym)]) | |
| if native == "ETH": tasks.append(fetch_deribit_options("ETH")) | |
| if native == "XAU": tasks.append(fetch_gold_derivatives()) | |
| tasks.append(fetch_vix()) | |
| results = await asyncio.gather(*tasks) | |
| idx = 0; metrics = {} | |
| if bin_sym: | |
| metrics['funding_rate'] = results[idx]; idx += 1 | |
| metrics['open_interest'] = results[idx]; idx += 1 | |
| metrics['long_short_ratio'] = results[idx]; idx += 1 | |
| if native == "ETH": metrics['options'] = results[idx]; idx += 1 | |
| if native == "XAU": metrics['gold_derivatives'] = results[idx]; idx += 1 | |
| vix_data = results[idx] | |
| signals = {} | |
| score = 50.0 | |
| # VIX (вес 15%) | |
| vix_val = vix_data.get("vix", 20) | |
| if vix_val > 35: | |
| signals["vix"] = ("STRONG_SELL", 18) if native != "XAU" else ("STRONG_BUY", 18) | |
| score += 18 if native == "XAU" else -18 | |
| elif vix_val > 28: | |
| signals["vix"] = ("SELL", 12) if native != "XAU" else ("BUY", 12) | |
| score += 12 if native == "XAU" else -12 | |
| elif vix_val > 22: | |
| score += 6 if native == "XAU" else -6 | |
| signals["vix"] = ("SLIGHT_BUY", 6) if native == "XAU" else ("SLIGHT_SELL", 6) | |
| else: | |
| signals["vix"] = ("NEUTRAL", 0) | |
| # Funding Rate (вес 20%) | |
| fund = metrics.get('funding_rate', {}) | |
| fund_signal = fund.get('signal', 'NEUTRAL') | |
| fund_z = fund.get('zscore', 0) | |
| if fund_signal == 'STRONG_BEARISH': | |
| score -= 20; signals["funding"] = ("STRONG_SELL", 20) | |
| elif fund_signal == 'BEARISH': | |
| score -= 14; signals["funding"] = ("SELL", 14) | |
| elif fund_signal == 'STRONG_BULLISH': | |
| score += 20; signals["funding"] = ("STRONG_BUY", 20) | |
| elif fund_signal == 'BULLISH': | |
| score += 14; signals["funding"] = ("BUY", 14) | |
| else: | |
| signals["funding"] = ("NEUTRAL", 0) | |
| if abs(fund_z) > 2.0: | |
| score += 8 if fund_z < 0 else -8 | |
| # Open Interest (вес 15%) | |
| oi = metrics.get('open_interest', {}) | |
| oi_signal = oi.get('signal', 'NEUTRAL') | |
| if oi_signal == 'STRONG_BULLISH': | |
| score += 12; signals["oi"] = ("BUY", 12) | |
| elif oi_signal == 'BULLISH': | |
| score += 8; signals["oi"] = ("SLIGHT_BUY", 8) | |
| elif oi_signal == 'STRONG_BEARISH': | |
| score -= 12; signals["oi"] = ("SELL", 12) | |
| elif oi_signal == 'BEARISH': | |
| score -= 8; signals["oi"] = ("SLIGHT_SELL", 8) | |
| else: | |
| signals["oi"] = ("NEUTRAL", 0) | |
| # Long/Short Ratio (вес 15%) | |
| lsr = metrics.get('long_short_ratio', {}) | |
| lsr_signal = lsr.get('signal', 'NEUTRAL') | |
| if lsr_signal == 'STRONG_BEARISH': | |
| score -= 15; signals["lsr"] = ("STRONG_SELL", 15) | |
| elif lsr_signal == 'BEARISH': | |
| score -= 10; signals["lsr"] = ("SELL", 10) | |
| elif lsr_signal == 'STRONG_BULLISH': | |
| score += 15; signals["lsr"] = ("STRONG_BUY", 15) | |
| elif lsr_signal == 'BULLISH': | |
| score += 10; signals["lsr"] = ("BUY", 10) | |
| else: | |
| signals["lsr"] = ("NEUTRAL", 0) | |
| # Options PCR (вес 15%) | |
| options = metrics.get('options', {}) | |
| if options.get('signal') == 'STRONG_BEARISH': | |
| score -= 12; signals["options"] = ("SELL", 12) | |
| elif options.get('signal') == 'BEARISH': | |
| score -= 8; signals["options"] = ("SLIGHT_SELL", 8) | |
| elif options.get('signal') == 'STRONG_BULLISH': | |
| score += 12; signals["options"] = ("BUY", 12) | |
| elif options.get('signal') == 'BULLISH': | |
| score += 8; signals["options"] = ("SLIGHT_BUY", 8) | |
| else: | |
| signals["options"] = ("NEUTRAL", 0) | |
| # Gold Derivatives (вес 10%) | |
| gold = metrics.get('gold_derivatives', {}) | |
| if gold.get('signal') == 'STRONG_BULLISH': | |
| score += 12; signals["gold"] = ("STRONG_BUY", 12) | |
| elif gold.get('signal') == 'BULLISH': | |
| score += 8; signals["gold"] = ("BUY", 8) | |
| elif gold.get('signal') == 'STRONG_BEARISH': | |
| score -= 12; signals["gold"] = ("STRONG_SELL", 12) | |
| elif gold.get('signal') == 'BEARISH': | |
| score -= 8; signals["gold"] = ("SELL", 8) | |
| else: | |
| signals["gold"] = ("NEUTRAL", 0) | |
| # Max Pain (вес 10%) | |
| price = await get_current_price(symbol) if native != "XAU" else 0 | |
| max_pain = calculate_max_pain(price) if price > 0 else 0 | |
| if max_pain > 0 and price < max_pain * 0.97: | |
| score += 8; signals["maxpain"] = ("BUY", 8) | |
| elif max_pain > 0 and price > max_pain * 1.03: | |
| score -= 8; signals["maxpain"] = ("SELL", 8) | |
| else: | |
| signals["maxpain"] = ("NEUTRAL", 0) | |
| score = round(max(3, min(97, score)), 1) | |
| # Z-score | |
| SCORE_HISTORY[symbol].append(score) | |
| score_z = calculate_zscore(score, SCORE_HISTORY[symbol]) | |
| if score > 62: signal, confidence = "BUY", min(0.92, score / 100) | |
| elif score > 54: signal, confidence = "BUY", min(0.68, (score - 50) / 50) | |
| elif score < 38: signal, confidence = "SELL", min(0.92, (100 - score) / 100) | |
| elif score < 46: signal, confidence = "SELL", min(0.68, (50 - score) / 50) | |
| else: signal, confidence = "WAIT", 0.0 | |
| analysis = { | |
| "derivative_score": score, | |
| "score_zscore": round(score_z, 2), | |
| "signal": signal, | |
| "confidence": round(confidence, 4), | |
| "signals_breakdown": {k: v[0] for k, v in signals.items()}, | |
| "metrics": {**metrics, "vix": vix_data, "max_pain": max_pain} | |
| } | |
| OPTIONS_HISTORY.append({ | |
| "timestamp": datetime.now(timezone.utc).isoformat(), | |
| "symbol": symbol, "signal": signal, "score": score | |
| }) | |
| save_history() | |
| return analysis | |
| # ================= ОТПРАВКА В HUB ================= | |
| async def send_signal_to_hub(symbol: str, signal: str, confidence: float, features: Dict = None): | |
| if features is None: features = {} | |
| payload = { | |
| "space_id": SPACE_ID, "space_name": SPACE_NAME, | |
| "symbol": symbol, "signal": signal, "confidence": round(confidence, 4), | |
| "features": features, "metadata": {"version": "4.0"}, | |
| "timestamp": datetime.now(timezone.utc).isoformat() | |
| } | |
| for attempt in range(3): | |
| try: | |
| r = await http_client.post(f"{HUB_URL}/signals", json=payload, timeout=15, headers=hub_headers()) | |
| if r.status_code == 200: | |
| logger.info(f"📤 {symbol}: {signal} conf={confidence:.3f}") | |
| return True | |
| await asyncio.sleep(2) | |
| except Exception as e: | |
| logger.warning(f"Попытка {attempt+1}: {e}") | |
| await asyncio.sleep(2) | |
| return False | |
| # ================= ГЛАВНЫЙ СИГНАЛ ================= | |
| async def get_derivative_signal(symbol: str = "ETH/USD") -> Dict[str, Any]: | |
| start = time.time() | |
| analysis = await analyze_derivatives(symbol) | |
| latency = int((time.time() - start) * 1000) | |
| features = { | |
| "derivative_score": analysis['derivative_score'], | |
| "score_zscore": analysis['score_zscore'] | |
| } | |
| await send_signal_to_hub(symbol, analysis['signal'], analysis['confidence'], features) | |
| logger.info(f"📊 Options {symbol}: {analysis['signal']} conf={analysis['confidence']:.3f} score={analysis['derivative_score']} | {latency}ms") | |
| return { | |
| "space_id": SPACE_ID, "timestamp": int(time.time()), | |
| "symbol": symbol, "signal": analysis['signal'], | |
| "confidence": analysis['confidence'], "derivative_analysis": analysis | |
| } | |
| # ================= АВТО-ОТПРАВКА ================= | |
| async def auto_send_loop(): | |
| logger.info(f"⏳ Стартовый сон {STARTUP_SLEEP}с...") | |
| await log_to_hub("STARTUP", f"Options Engine v4.0 запущен, жду {STARTUP_SLEEP}с") | |
| await asyncio.sleep(STARTUP_SLEEP) | |
| logger.info(f"🔄 Options Engine [интервал={AUTO_SEND_INTERVAL}с]") | |
| while True: | |
| try: | |
| for symbol in SYMBOLS: | |
| await get_derivative_signal(symbol) | |
| await asyncio.sleep(2) | |
| logger.info("✅ Options Engine цикл завершён") | |
| except Exception as e: | |
| logger.error(f"Ошибка: {e}") | |
| await log_to_hub("ERROR", f"Ошибка: {str(e)[:200]}") | |
| await asyncio.sleep(AUTO_SEND_INTERVAL) | |
| # ================= FASTAPI ================= | |
| app = FastAPI(title="Tomiris Space 26 v4.0 — Options & Derivatives") | |
| async def startup(): | |
| asyncio.create_task(auto_send_loop()) | |
| logger.info(f"🚀 Space 26 v4.0 | Хаб: {HUB_URL}") | |
| async def shutdown(): await http_client.aclose() | |
| async def health(): return {"space_id": SPACE_ID, "status": "operational", "version": "4.0"} | |
| async def health_head(): return {} | |
| async def consilium(symbol: str = Query("ETH/USD")): | |
| if symbol not in SYMBOLS: return {"error": "Invalid symbol"} | |
| return await get_derivative_signal(symbol) | |
| async def funding(symbol: str): | |
| native = "XAU" if "XAU" in symbol.upper() else ("ETH" if "ETH" in symbol.upper() else "SOL") | |
| bin_sym = native + "USDT" | |
| return await fetch_funding_rate(bin_sym) | |
| async def oi(symbol: str): | |
| native = "XAU" if "XAU" in symbol.upper() else ("ETH" if "ETH" in symbol.upper() else "SOL") | |
| bin_sym = native + "USDT" | |
| return await fetch_open_interest(bin_sym) | |
| async def lsr(symbol: str): | |
| native = "XAU" if "XAU" in symbol.upper() else ("ETH" if "ETH" in symbol.upper() else "SOL") | |
| bin_sym = native + "USDT" | |
| return await fetch_long_short_ratio(bin_sym) | |
| async def options(): return await fetch_deribit_options("ETH") | |
| async def gold(): return await fetch_gold_derivatives() | |
| async def vix(): return await fetch_vix() | |
| async def maxpain(symbol: str): | |
| price = await get_current_price(symbol); mp = calculate_max_pain(price) | |
| return {"symbol": symbol, "current_price": price, "max_pain": mp} | |
| async def history(limit: int = 50): return list(OPTIONS_HISTORY)[-limit:] | |
| async def send_now(): | |
| results = {} | |
| for symbol in SYMBOLS: | |
| sig = await get_derivative_signal(symbol) | |
| results[symbol] = sig.get("signal", "WAIT") | |
| return {"status": "sent", "results": results} | |
| async def root(): return {"name": "Options Engine v4.0", "space_id": SPACE_ID, "hub": HUB_URL} | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=7860) | |
| print("🚀 SPACE 26 v4.0 — OPTIONS & DERIVATIVES ENGINE ГОТОВ!") |