Spaces:
Sleeping
Sleeping
| # ============================================ | |
| # АВТО-УСТАНОВКА ПАКЕТОВ | |
| # ============================================ | |
| import subprocess | |
| import sys | |
| import 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 20 v2.0 — ETHEREUM L2 & DEFI PULSE (УСИЛЕННЫЙ) | |
| # ============================================ | |
| from fastapi import FastAPI, Query | |
| from typing import Optional, Dict, Any, List, Tuple | |
| import time | |
| import threading | |
| import asyncio | |
| import numpy as np | |
| from datetime import datetime, timezone | |
| from collections import deque | |
| import warnings | |
| import os | |
| import json | |
| import logging | |
| import httpx | |
| warnings.filterwarnings('ignore') | |
| logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") | |
| logger = logging.getLogger("Space20_L2_DeFi") | |
| # ================= КОНФИГУРАЦИЯ ================= | |
| SPACE_ID = 20 | |
| SPACE_NAME = "L2 & DeFi Pulse" | |
| SYMBOL = "ETH/USD" | |
| HUB_URL = "https://TOMI-HUB-HUB-FINAL.hf.space" | |
| HUB_SECRET = os.getenv("HUB_SECRET", "TomyrisUltraSecret2026!") | |
| 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}с") | |
| L2_CHAINS: Dict[str, str] = { | |
| "Arbitrum": "Arbitrum", | |
| "Optimism": "Optimism", | |
| "Base": "Base", | |
| "Polygon ZkEVM": "Polygon ZkEVM", | |
| "Linea": "Linea", | |
| "Scroll": "Scroll" | |
| } | |
| DEFI_PROTOCOLS: List[str] = [ | |
| "Lido", "MakerDAO", "Aave", "Uniswap", "Curve", | |
| "EigenLayer", "Rocket Pool" | |
| ] | |
| # ================= HTTP КЛИЕНТ ================= | |
| 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 = deque(maxlen=200) | |
| L2_TVL_HISTORY = deque(maxlen=200) | |
| 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: Dict[str, Dict[str, Any]] = {} | |
| CACHE_TIMES: Dict[str, float] = {} | |
| # ================= ЗАГРУЗКА ДАННЫХ DeFiLlama ================= | |
| async def fetch_chain_tvl(chain: str) -> Optional[Dict[str, Any]]: | |
| cache_key = f"tvl_{chain}" | |
| if cache_key in CACHE and time.time() - CACHE_TIMES.get(cache_key, 0) < 300: | |
| return CACHE[cache_key] | |
| try: | |
| r = await http_client.get(f"https://api.llama.fi/v2/chains/{chain}", timeout=10) | |
| if r.status_code == 200: | |
| text = r.text.strip() | |
| if not text or text == "0" or text == "0.0": | |
| return None | |
| try: | |
| data = r.json() | |
| except: | |
| return None | |
| if isinstance(data, (int, float)): | |
| tvl = float(data) | |
| if tvl == 0: return None | |
| change_24h = 0; change_7d = 0 | |
| elif isinstance(data, dict): | |
| tvl = data.get('tvl', 0) | |
| if tvl == 0: return None | |
| change_24h = data.get('change_1d', 0) or data.get('change_24h', 0) or 0 | |
| change_7d = data.get('change_7d', 0) or 0 | |
| else: | |
| return None | |
| result = { | |
| 'chain': chain, | |
| 'tvl': tvl, | |
| 'tvl_billions': round(tvl / 1e9, 2), | |
| 'change_24h': change_24h, | |
| 'change_7d': change_7d, | |
| 'trend': 'UP' if change_24h > 0 else 'DOWN' | |
| } | |
| CACHE[cache_key] = result | |
| CACHE_TIMES[cache_key] = time.time() | |
| return result | |
| except Exception as e: | |
| logger.warning(f"⚠️ TVL {chain}: {e}") | |
| return None | |
| async def fetch_all_l2_tvl() -> Dict[str, Any]: | |
| l2_data: Dict[str, Any] = {} | |
| total_tvl = 0.0 | |
| total_change = 0.0 | |
| active_chains = 0 | |
| tasks = [fetch_chain_tvl(chain) for chain in L2_CHAINS] | |
| results = await asyncio.gather(*tasks) | |
| for chain, data in zip(L2_CHAINS.keys(), results): | |
| if data and data.get('tvl', 0) > 0: | |
| l2_data[chain] = data | |
| total_tvl += data['tvl'] | |
| total_change += data.get('change_24h', 0) | |
| active_chains += 1 | |
| else: | |
| l2_data[chain] = {"error": "no_data", "tvl": 0} | |
| # Прокси если все L2 недоступны | |
| if total_tvl == 0: | |
| logger.info("🔄 Все L2 TVL = 0, использую Ethereum как прокси...") | |
| eth_data = await fetch_chain_tvl("ethereum") | |
| if eth_data and eth_data.get('tvl', 0) > 0: | |
| total_tvl = eth_data['tvl'] * 0.25 # ~25% TVL ETH на L2 | |
| total_change = eth_data.get('change_24h', 0) | |
| active_chains = 1 | |
| avg_change = total_change / active_chains if active_chains > 0 else 0 | |
| # Z-score L2 TVL | |
| L2_TVL_HISTORY.append(total_tvl) | |
| tvl_z = calculate_zscore(total_tvl, L2_TVL_HISTORY) | |
| result = { | |
| 'chains': l2_data, | |
| 'total_l2_tvl': round(total_tvl, 2), | |
| 'total_l2_tvl_billions': round(total_tvl / 1e9, 2), | |
| 'avg_change_24h': round(avg_change, 2), | |
| 'tvl_zscore': round(tvl_z, 2), | |
| 'trend': 'GROWING' if avg_change > 2 else 'SHRINKING' if avg_change < -2 else 'STABLE', | |
| 'signal': 'BULLISH' if avg_change > 2 else 'BEARISH' if avg_change < -2 else 'NEUTRAL' | |
| } | |
| logger.info(f"✅ L2 TVL: ${result['total_l2_tvl_billions']}B (z={tvl_z:.1f})") | |
| return result | |
| async def fetch_ethereum_tvl() -> Dict[str, Any]: | |
| cache_key = "tvl_ethereum" | |
| if cache_key in CACHE and time.time() - CACHE_TIMES.get(cache_key, 0) < 300: | |
| return CACHE[cache_key] | |
| data = await fetch_chain_tvl("ethereum") | |
| if data: | |
| CACHE[cache_key] = data | |
| CACHE_TIMES[cache_key] = time.time() | |
| return data | |
| return {'tvl': 0, 'tvl_billions': 0, 'change_24h': 0, 'trend': 'STABLE'} | |
| # ================= СТЕЙКИНГ ================= | |
| async def fetch_staking_metrics() -> Dict[str, Any]: | |
| cache_key = "staking" | |
| if cache_key in CACHE and time.time() - CACHE_TIMES.get(cache_key, 0) < 300: | |
| return CACHE[cache_key] | |
| result: Dict[str, Any] = {"lido_tvl": 0, "eigenlayer_tvl": 0} | |
| # Lido + EigenLayer параллельно | |
| tasks = [fetch_chain_tvl("lido"), fetch_chain_tvl("eigenlayer")] | |
| lido_data, eigen_data = await asyncio.gather(*tasks) | |
| if lido_data and lido_data.get('tvl', 0) > 0: | |
| result['lido_tvl'] = lido_data['tvl'] | |
| result['lido_change_24h'] = lido_data.get('change_24h', 0) | |
| result['lido_tvl_billions'] = round(lido_data['tvl'] / 1e9, 2) | |
| if eigen_data and eigen_data.get('tvl', 0) > 0: | |
| result['eigenlayer_tvl'] = eigen_data['tvl'] | |
| result['eigenlayer_change_24h'] = eigen_data.get('change_24h', 0) | |
| eigen_change = result.get('eigenlayer_change_24h', 0) or 0 | |
| if eigen_change > 5: result['restaking_growth'] = 'HIGH' | |
| elif eigen_change > 0: result['restaking_growth'] = 'MODERATE' | |
| else: result['restaking_growth'] = 'DECLINING' | |
| lido_change = result.get('lido_change_24h', 0) or 0 | |
| result['signal'] = 'BULLISH' if lido_change > 0 else 'NEUTRAL' | |
| CACHE[cache_key] = result | |
| CACHE_TIMES[cache_key] = time.time() | |
| return result | |
| # ================= DEX ОБЪЁМЫ ================= | |
| async def fetch_dex_volumes() -> Dict[str, Any]: | |
| cache_key = "dex_eth" | |
| if cache_key in CACHE and time.time() - CACHE_TIMES.get(cache_key, 0) < 300: | |
| return CACHE[cache_key] | |
| try: | |
| r = await http_client.get( | |
| "https://api.llama.fi/overview/dexs/ethereum?excludeTotalDataChart=true&excludeTotalDataChartBreakdown=true", | |
| timeout=10 | |
| ) | |
| if r.status_code == 200: | |
| data = r.json() | |
| total24h = data.get('total24h', 0) | |
| change24h = data.get('change_1d', 0) or data.get('dailyChange', 0) or 0 | |
| result = { | |
| 'total_volume_24h': total24h, | |
| 'volume_billions': round(total24h / 1e9, 2) if total24h else 0, | |
| 'change_24h': change24h, | |
| 'trend': 'UP' if change24h > 0 else 'DOWN', | |
| 'signal': 'BULLISH' if change24h > 10 else 'BEARISH' if change24h < -10 else 'NEUTRAL' | |
| } | |
| CACHE[cache_key] = result | |
| CACHE_TIMES[cache_key] = time.time() | |
| return result | |
| except Exception as e: | |
| logger.warning(f"DEX error: {e}") | |
| return {'total_volume_24h': 0, 'volume_billions': 0, 'signal': 'NEUTRAL'} | |
| # ================= СЖИГАНИЕ ETH ================= | |
| async def fetch_ultrasound_money() -> Dict[str, Any]: | |
| cache_key = "ultrasound" | |
| if cache_key in CACHE and time.time() - CACHE_TIMES.get(cache_key, 0) < 300: | |
| return CACHE[cache_key] | |
| try: | |
| r = await http_client.get("https://ultrasound.money/api/overview", timeout=10) | |
| if r.status_code == 200: | |
| data = r.json() | |
| result = { | |
| 'burn_24h': data.get("burn24h", 0), | |
| 'supply_growth_24h': data.get("supplyGrowth24h", 0), | |
| 'is_deflationary': data.get("supplyGrowth24h", 0) < 0, | |
| 'signal': 'BULLISH' if data.get("supplyGrowth24h", 0) < 0 else 'BEARISH' | |
| } | |
| CACHE[cache_key] = result | |
| CACHE_TIMES[cache_key] = time.time() | |
| return result | |
| except Exception as e: | |
| logger.warning(f"Ultrasound error: {e}") | |
| return {'is_deflationary': False, 'burn_24h': 0, 'supply_growth_24h': 0, 'signal': 'NEUTRAL'} | |
| # ================= РАЗРАБОТЧИКИ ================= | |
| async def fetch_developer_activity() -> Dict[str, Any]: | |
| try: | |
| r = await http_client.get("https://api.coingecko.com/api/v3/coins/ethereum?developer_data=true", timeout=10) | |
| if r.status_code == 200: | |
| dev = r.json().get('developer_data', {}) | |
| commits = dev.get('commit_count_4_weeks', 0) | |
| return { | |
| 'stars': dev.get('stars', 0), | |
| 'commits_4w': commits, | |
| 'activity': 'HIGH' if commits > 150 else 'MODERATE' if commits > 50 else 'LOW' | |
| } | |
| except: pass | |
| return {'stars': 0, 'commits_4w': 0, 'activity': 'MODERATE'} | |
| # ================= ОТПРАВКА В 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": "2.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 analyze_ecosystem() -> Dict[str, Any]: | |
| l2, eth_tvl, staking, dex, ultrasound, dev = await asyncio.gather( | |
| fetch_all_l2_tvl(), fetch_ethereum_tvl(), fetch_staking_metrics(), | |
| fetch_dex_volumes(), fetch_ultrasound_money(), fetch_developer_activity() | |
| ) | |
| signals = {} | |
| score = 50.0 | |
| # 1. L2 TVL (вес 25%) | |
| if l2.get('trend') == 'GROWING': | |
| score += 15; signals["l2"] = ("BUY", 15) | |
| elif l2.get('trend') == 'SHRINKING': | |
| score -= 15; signals["l2"] = ("SELL", 15) | |
| else: | |
| signals["l2"] = ("NEUTRAL", 0) | |
| # Z-score L2 TVL | |
| l2_z = l2.get('tvl_zscore', 0) | |
| if l2_z > 2.0: score += 8; signals["l2_z"] = ("BUY", 8) | |
| elif l2_z < -2.0: score -= 8; signals["l2_z"] = ("SELL", 8) | |
| # 2. ETH TVL (вес 20%) | |
| eth_change = eth_tvl.get('change_24h', 0) or 0 | |
| if eth_change > 3: | |
| score += 12; signals["eth_tvl"] = ("BUY", 12) | |
| elif eth_change > 0: | |
| score += 6; signals["eth_tvl"] = ("SLIGHT_BUY", 6) | |
| elif eth_change < -3: | |
| score -= 12; signals["eth_tvl"] = ("SELL", 12) | |
| elif eth_change < 0: | |
| score -= 6; signals["eth_tvl"] = ("SLIGHT_SELL", 6) | |
| else: | |
| signals["eth_tvl"] = ("NEUTRAL", 0) | |
| # 3. DEX (вес 20%) | |
| dex_signal = dex.get('signal', 'NEUTRAL') | |
| if dex_signal == 'BULLISH': | |
| score += 12; signals["dex"] = ("BUY", 12) | |
| elif dex_signal == 'BEARISH': | |
| score -= 12; signals["dex"] = ("SELL", 12) | |
| else: | |
| signals["dex"] = ("NEUTRAL", 0) | |
| # 4. Ultrasound/Дефляция (вес 15%) | |
| if ultrasound.get('is_deflationary'): | |
| score += 10; signals["ultrasound"] = ("BUY", 10) | |
| elif ultrasound.get('supply_growth_24h', 0) > 0.1: | |
| score -= 8; signals["ultrasound"] = ("SELL", 8) | |
| else: | |
| signals["ultrasound"] = ("NEUTRAL", 0) | |
| # 5. Стейкинг (вес 10%) | |
| staking_signal = staking.get('signal', 'NEUTRAL') | |
| if staking_signal == 'BULLISH': | |
| score += 6; signals["staking"] = ("SLIGHT_BUY", 6) | |
| elif staking_signal == 'BEARISH': | |
| score -= 4; signals["staking"] = ("SLIGHT_SELL", 4) | |
| else: | |
| signals["staking"] = ("NEUTRAL", 0) | |
| if staking.get('restaking_growth') == 'HIGH': | |
| score += 4; signals["restaking"] = ("SLIGHT_BUY", 4) | |
| # 6. Разработчики (вес 10%) | |
| dev_activity = dev.get('activity', 'MODERATE') | |
| if dev_activity == 'HIGH': | |
| score += 5; signals["dev"] = ("SLIGHT_BUY", 5) | |
| elif dev_activity == 'LOW': | |
| score -= 3; signals["dev"] = ("SLIGHT_SELL", 3) | |
| else: | |
| signals["dev"] = ("NEUTRAL", 0) | |
| score = round(max(3, min(97, score)), 1) | |
| # Z-score | |
| SCORE_HISTORY.append(score) | |
| score_z = calculate_zscore(score, SCORE_HISTORY) | |
| 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 | |
| return { | |
| "ecosystem_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": { | |
| "l2": {k: v for k, v in l2.items() if k != 'chains'}, | |
| "l2_chains": l2.get('chains', {}), | |
| "ethereum_tvl": eth_tvl, | |
| "staking": staking, | |
| "dex": dex, | |
| "ultrasound": ultrasound, | |
| "developer_activity": dev | |
| } | |
| } | |
| # ================= ГЛАВНЫЙ СИГНАЛ ================= | |
| async def get_ecosystem_signal() -> Dict[str, Any]: | |
| start = time.time() | |
| analysis = await analyze_ecosystem() | |
| features = { | |
| "ecosystem_score": analysis['ecosystem_score'], | |
| "score_zscore": analysis['score_zscore'], | |
| "l2_tvl": analysis['metrics'].get('l2', {}).get('total_l2_tvl_billions', 0), | |
| "eth_tvl_change": analysis['metrics'].get('ethereum_tvl', {}).get('change_24h', 0) | |
| } | |
| await send_signal_to_hub(SYMBOL, analysis['signal'], analysis['confidence'], features) | |
| elapsed = int((time.time() - start) * 1000) | |
| logger.info(f"🔷 L2+DeFi: {analysis['signal']} conf={analysis['confidence']:.3f} score={analysis['ecosystem_score']} z={analysis['score_zscore']:.1f} | {elapsed}ms") | |
| return { | |
| "space_id": SPACE_ID, "symbol": SYMBOL, | |
| "signal": analysis['signal'], "confidence": analysis['confidence'], | |
| "ecosystem_score": analysis['ecosystem_score'] | |
| } | |
| # ================= АВТО-ОТПРАВКА ================= | |
| async def auto_send_loop(): | |
| logger.info(f"⏳ Стартовый сон {STARTUP_SLEEP}с...") | |
| await log_to_hub("STARTUP", f"L2 DeFi Pulse v2.0 запущен, жду {STARTUP_SLEEP}с") | |
| await asyncio.sleep(STARTUP_SLEEP) | |
| logger.info(f"🔄 L2/DeFi (интервал {AUTO_SEND_INTERVAL}с)") | |
| while True: | |
| try: await get_ecosystem_signal() | |
| except Exception as e: | |
| logger.error(f"Ошибка: {e}") | |
| await log_to_hub("ERROR", f"Ошибка: {str(e)[:200]}") | |
| await asyncio.sleep(AUTO_SEND_INTERVAL) | |
| # ================= KEEP-ALIVE ================= | |
| def keep_alive(): | |
| time.sleep(STARTUP_SLEEP) | |
| while True: | |
| time.sleep(840) | |
| try: | |
| import requests as req | |
| req.get("http://localhost:7860/health", timeout=5) | |
| except: pass | |
| threading.Thread(target=keep_alive, daemon=True).start() | |
| # ================= FASTAPI ================= | |
| app = FastAPI(title="TOMIRIS SPACE 20 v2.0 — L2 & DEFI PULSE") | |
| async def startup(): | |
| asyncio.create_task(auto_send_loop()) | |
| logger.info(f"🚀 Space 20 v2.0 | Хаб: {HUB_URL}") | |
| async def health(): | |
| return {"space_id": SPACE_ID, "status": "operational", "version": "2.0", "l2_chains": list(L2_CHAINS.keys())} | |
| async def health_head(): return {} | |
| async def consilium(): return await get_ecosystem_signal() | |
| async def l2_status(): return await fetch_all_l2_tvl() | |
| async def tvl(): return await fetch_ethereum_tvl() | |
| async def staking(): return await fetch_staking_metrics() | |
| async def dex(): return await fetch_dex_volumes() | |
| async def ultrasound(): return await fetch_ultrasound_money() | |
| async def developers(): return await fetch_developer_activity() | |
| async def full_analysis(): return await analyze_ecosystem() | |
| async def send_now(): return await get_ecosystem_signal() | |
| async def root(): | |
| return {"name": "L2 & DeFi Pulse v2.0", "space_id": SPACE_ID, "hub": HUB_URL} | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=7860) | |
| print(f"🚀 SPACE 20 v2.0 — L2 & DEFI PULSE ЗАПУЩЕН!") | |
| print(f"🔷 Мониторинг: {', '.join(L2_CHAINS.keys())} | DEX | Staking | Ultrasound") |