# ============================================ # АВТО-УСТАНОВКА ПАКЕТОВ # ============================================ import subprocess, sys, importlib REQUIRED_PACKAGES = { 'numpy': 'numpy', 'httpx': 'httpx', '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 23 v2.2 — ON-CHAIN ANOMALY SENTINEL (АВТО-ОТПРАВКА) # ============================================ import os, time, json, logging, asyncio from typing import Dict, Any, List, Optional from datetime import datetime, timezone from collections import deque import numpy as np import httpx from fastapi import FastAPI, Query logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") logger = logging.getLogger("Space23_AnomalySentinel") # ================= КОНФИГУРАЦИЯ ================= TRACKED_SYMBOLS = ["ETH/USD", "SOL/USD"] HUB_URL = os.getenv("SPACE17_URL", "https://tomiris-ai-name5-5.hf.space") ETHERSCAN_KEY = os.getenv("ETHERSCAN_KEY", "TZGVD58I2HZ9G4BBD548KX11E4JFH4J5GX") SOLSCAN_KEY = os.getenv("SOLSCAN_KEY", "") # Интервал авто-отправки AUTO_SEND_INTERVAL = int(os.getenv("AUTO_SEND_INTERVAL", "300")) ETH_WHALE_WALLETS = [ "0x28C6c06298d514089D2Bd15f6A8a38c5d8d3a6B2", "0xA274C1e7D16E7e6F7C0e68b9f5A1a1BCc6E39509", "0xBE0eB53F46cDf7F88F0E6F9D4D1D08B42DAbcD17" ] SOL_WHALE_WALLETS = [ "9L4rU4mZJ2o8H6h2Xw3V3vJ2x9f7DqFtRt3y5j7", "8k3WkRLGkR9mZ9z3X3a3o5v3h5p7u5j3n4b5v6c" ] CACHE_TTL = {"gas": 60, "whales": 300, "stablecoin": 300, "active": 600} HISTORY_FILE = "anomaly_history.json" ACTIVE_HISTORY_FILE = "active_history.json" CIRCUIT_BREAKER = {} def breaker_open(name: str) -> bool: if name not in CIRCUIT_BREAKER: return False fails, last = CIRCUIT_BREAKER[name] if fails >= 3 and time.time() - last < 600: return True return False def breaker_record(name: str, success: bool): if success: CIRCUIT_BREAKER[name] = (0, 0) else: fails, _ = CIRCUIT_BREAKER.get(name, (0, 0)) CIRCUIT_BREAKER[name] = (fails + 1, time.time()) # ================= HTTP КЛИЕНТ ================= http_client = httpx.AsyncClient(timeout=15.0) # ================= ЗАГРУЗКА ИСТОРИИ ================= if os.path.exists(HISTORY_FILE): try: with open(HISTORY_FILE) as f: ANOMALY_HISTORY = deque(json.load(f), maxlen=500) except: ANOMALY_HISTORY = deque(maxlen=500) else: ANOMALY_HISTORY = deque(maxlen=500) if os.path.exists(ACTIVE_HISTORY_FILE): with open(ACTIVE_HISTORY_FILE) as f: active_history = json.load(f) else: active_history = {} # ================= ЗАГРУЗКА ДАННЫХ ================= async def fetch_gas_oracle() -> Dict[str, Any]: if breaker_open("etherscan"): return {'avg_gas': 45, 'gas_level': 'NORMAL', 'is_anomaly': False} try: r = await http_client.get( f"https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey={ETHERSCAN_KEY}") if r.status_code == 200 and r.json().get('status') == '1': result = r.json()['result'] avg = int(result['ProposeGasPrice']) breaker_record("etherscan", True) return { 'avg_gas': avg, 'gas_level': 'LOW' if avg < 30 else 'NORMAL' if avg < 80 else 'HIGH' if avg < 150 else 'EXTREME', 'is_anomaly': avg > 200 } except: breaker_record("etherscan", False) return {'avg_gas': 45, 'gas_level': 'NORMAL', 'is_anomaly': False} async def fetch_large_transactions(network: str) -> Dict[str, Any]: if network == "ethereum": wallets = ETH_WHALE_WALLETS api_url = f"https://api.etherscan.io/api?module=account&action=txlist&apikey={ETHERSCAN_KEY}" else: wallets = SOL_WHALE_WALLETS api_url = f"https://api.solscan.io/api?module=account&action=txlist" if breaker_open(f"whale_{network}"): return {'large_transactions': [], 'count': 0, 'is_anomaly': False, 'signal': 'NORMAL'} large_txs = [] for wallet in wallets: try: r = await http_client.get(f"{api_url}&address={wallet}&page=1&offset=5&sort=desc") if r.status_code == 200 and r.json().get('status') == '1': txs = r.json().get('result', []) for tx in txs: value = float(tx.get('value', 0)) if network == "ethereum" and value > 100e18: large_txs.append({ 'hash': tx.get('hash', '')[:10], 'value_eth': round(value / 1e18, 2), 'from': tx.get('from', '')[:10], 'to': tx.get('to', '')[:10] }) except: continue is_anomaly = len(large_txs) > 3 breaker_record(f"whale_{network}", len(large_txs) > 0) return { 'large_transactions': large_txs[:5], 'count': len(large_txs), 'is_anomaly': is_anomaly, 'signal': 'WHALE_ACTIVE' if is_anomaly else 'NORMAL' } async def fetch_stablecoin_flows(network: str) -> Dict[str, Any]: if breaker_open(f"stablecoin_{network}"): return {'signal': 'BALANCED', 'is_anomaly': False} if network == "ethereum": usdt_url = f"https://api.etherscan.io/api?module=account&action=tokentx&address=0xdAC17F958D2ee523a2206206994597C13D831ec7&page=1&offset=10&sort=desc&apikey={ETHERSCAN_KEY}" try: r = await http_client.get(usdt_url) if r.status_code == 200 and r.json().get('status') == '1': txs = r.json().get('result', []) total_value = sum(int(tx['value']) for tx in txs) / 1e6 anomaly = total_value > 10_000_000 breaker_record("stablecoin_ethereum", True) return { 'token': 'USDT', 'recent_volume': round(total_value, 2), 'is_anomaly': anomaly, 'signal': 'INFLOW' if anomaly else 'BALANCED' } except: breaker_record("stablecoin_ethereum", False) return {'signal': 'BALANCED', 'is_anomaly': False} async def fetch_active_addresses(chain: str) -> Dict[str, Any]: if breaker_open(f"active_{chain}"): return {'active_users': 0, 'change_pct': 0, 'is_anomaly': False, 'signal': 'NORMAL'} try: r = await http_client.get(f"https://api.llama.fi/overview/{chain}?excludeTotalDataChart=true&excludeTotalDataChartBreakdown=true") if r.status_code == 200: data = r.json() current = data.get('activeUsers', 0) prev = active_history.get(chain, current) change = ((current - prev) / prev * 100) if prev > 0 else 0 active_history[chain] = current with open(ACTIVE_HISTORY_FILE, 'w') as f: json.dump(active_history, f) is_anomaly = abs(change) > 20 breaker_record(f"active_{chain}", True) return { 'chain': chain, 'active_users': current, 'change_pct': round(change, 2), 'is_anomaly': is_anomaly, 'signal': 'SPIKE_UP' if change > 20 else 'SPIKE_DOWN' if change < -20 else 'NORMAL' } except: breaker_record(f"active_{chain}", False) return {'active_users': 0, 'is_anomaly': False, 'signal': 'NORMAL'} # ================= ДЕТЕКТОР АНОМАЛИЙ ================= async def detect_anomalies(symbol: str) -> Dict[str, Any]: chain = "ethereum" if "ETH" in symbol else "solana" gas = await fetch_gas_oracle() if chain == "ethereum" else {'is_anomaly': False} whales = await fetch_large_transactions(chain) stablecoin = await fetch_stablecoin_flows(chain) active = await fetch_active_addresses(chain) anomalies = [] score = 0 if gas.get('is_anomaly'): anomalies.append({"type": "GAS_SPIKE", "severity": "HIGH", "reason": f"Газ: {gas['avg_gas']} gwei"}) score += 30 if whales.get('is_anomaly'): anomalies.append({"type": "WHALE_MOVEMENT", "severity": "HIGH", "reason": f"Китовых TX: {whales['count']}"}) score += 25 if stablecoin.get('is_anomaly'): anomalies.append({"type": "STABLECOIN_FLOW", "severity": "MEDIUM", "reason": f"USDT/объём {stablecoin.get('recent_volume', 0):.0f}"}) score += 20 if active.get('is_anomaly'): anomalies.append({"type": "ADDRESS_SPIKE", "severity": "MEDIUM", "reason": f"Адреса: {active['signal']} ({active['change_pct']}%)"}) score += 15 score = min(100, score) if score >= 60: level, direction = "CRITICAL", "WAIT" elif score >= 30: level, direction = "ELEVATED", "WAIT" else: level, direction = "NORMAL", "LONG" record = { "timestamp": datetime.now(timezone.utc).isoformat(), "symbol": symbol, "score": score, "level": level, "anomalies": anomalies } ANOMALY_HISTORY.append(record) with open(HISTORY_FILE, 'w') as f: json.dump(list(ANOMALY_HISTORY), f) try: await http_client.post(f"{HUB_URL}/anomaly", json=record, timeout=5) except: pass return { "anomaly_score": score, "anomaly_level": level, "signal": direction, "anomalies": anomalies, "metrics": { "gas": gas, "whale_transactions": whales, "stablecoin_flows": stablecoin, "active_addresses": active } } # ================= ОТПРАВКА В HUB ================= async def send_signal_to_hub(symbol: str, direction: str, confidence: float): try: resp = await http_client.post(f"{HUB_URL}/signal", json={ "space": "space_23_anomaly", "symbol": symbol, "direction": direction, "confidence": confidence, "raw": json.dumps({"source": "space_23_anomaly"}) }, timeout=10) if resp.status_code == 200: logger.info(f"📤 {symbol}: {direction} conf={confidence:.3f} отправлен в Hub") else: logger.warning(f"Hub вернул {resp.status_code}") except Exception as e: logger.error(f"Ошибка отправки в Hub: {e}") # ================= ГЛАВНЫЙ СИГНАЛ ================= async def get_anomaly_signal(symbol: str = "ETH/USD") -> Dict[str, Any]: start = time.time() analysis = await detect_anomalies(symbol) latency = int((time.time() - start) * 1000) direction = analysis['signal'] confidence = round(analysis['anomaly_score'] / 100, 4) if analysis['anomaly_score'] > 0 else 0.0 await send_signal_to_hub(symbol, direction, confidence) result = { "space": "space_23_anomaly", "timestamp": int(time.time()), "symbol": symbol, "signal": { "direction": direction, "confidence": confidence }, "anomaly_analysis": analysis, "latency_ms": latency } logger.info(f"🔍 Anomaly {symbol}: {analysis['anomaly_level']} | Score={analysis['anomaly_score']}") return result # ================= АВТО-ОТПРАВКА ================= async def auto_send_loop(): logger.info(f"🔄 Авто-отправка Anomaly Sentinel запущена (интервал {AUTO_SEND_INTERVAL}с)") await asyncio.sleep(30) while True: try: for symbol in TRACKED_SYMBOLS: await get_anomaly_signal(symbol) await asyncio.sleep(2) logger.info("✅ Anomaly Sentinel авто-отправка завершена") except Exception as e: logger.error(f"Ошибка авто-отправки: {e}") await asyncio.sleep(AUTO_SEND_INTERVAL) # ================= FASTAPI ================= app = FastAPI(title="Tomiris Space 23 v2.2 — On-Chain Anomaly Sentinel (Auto-Hub)") @app.on_event("startup") async def startup(): asyncio.create_task(auto_send_loop()) logger.info("🚀 Space 23 v2.2 запущен с авто-отправкой в Hub") @app.on_event("shutdown") async def shutdown(): await http_client.aclose() @app.get("/health") async def health(): return { "status": "operational", "version": "2.2", "hub_url": HUB_URL, "auto_send_interval": AUTO_SEND_INTERVAL, "symbols": TRACKED_SYMBOLS, "anomaly_history": len(ANOMALY_HISTORY) } @app.get("/consilium") async def consilium(symbol: str = Query("ETH/USD")): if symbol not in TRACKED_SYMBOLS: return {"error": "Invalid symbol"} return await get_anomaly_signal(symbol) @app.get("/scan/{symbol}") async def scan(symbol: str): if symbol not in TRACKED_SYMBOLS: return {"error": "Invalid symbol"} return await detect_anomalies(symbol) @app.get("/gas") async def gas(): return await fetch_gas_oracle() @app.get("/whales/{symbol}") async def whales(symbol: str): chain = "ethereum" if "ETH" in symbol else "solana" return await fetch_large_transactions(chain) @app.get("/stablecoin/{symbol}") async def stablecoin(symbol: str): chain = "ethereum" if "ETH" in symbol else "solana" return await fetch_stablecoin_flows(chain) @app.get("/active/{chain}") async def active(chain: str = "ethereum"): return await fetch_active_addresses(chain) @app.get("/history") async def history(limit: int = 50): return list(ANOMALY_HISTORY)[-limit:] @app.get("/send_now") async def send_now(): results = {} for symbol in TRACKED_SYMBOLS: analysis = await get_anomaly_signal(symbol) results[symbol] = analysis.get("signal", {}).get("direction", "WAIT") return {"status": "sent", "results": results} if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=7860) print("🚀 SPACE 23 v2.2 — ON-CHAIN ANOMALY SENTINEL (АВТО-ОТПРАВКА) ЗАПУЩЕН!")