Spaces:
Paused
Paused
| # ============================================ | |
| # АВТО-УСТАНОВКА ПАКЕТОВ | |
| # ============================================ | |
| 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)") | |
| async def startup(): | |
| asyncio.create_task(auto_send_loop()) | |
| logger.info("🚀 Space 23 v2.2 запущен с авто-отправкой в Hub") | |
| async def shutdown(): | |
| await http_client.aclose() | |
| 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) | |
| } | |
| async def consilium(symbol: str = Query("ETH/USD")): | |
| if symbol not in TRACKED_SYMBOLS: | |
| return {"error": "Invalid symbol"} | |
| return await get_anomaly_signal(symbol) | |
| async def scan(symbol: str): | |
| if symbol not in TRACKED_SYMBOLS: | |
| return {"error": "Invalid symbol"} | |
| return await detect_anomalies(symbol) | |
| async def gas(): | |
| return await fetch_gas_oracle() | |
| async def whales(symbol: str): | |
| chain = "ethereum" if "ETH" in symbol else "solana" | |
| return await fetch_large_transactions(chain) | |
| async def stablecoin(symbol: str): | |
| chain = "ethereum" if "ETH" in symbol else "solana" | |
| return await fetch_stablecoin_flows(chain) | |
| async def active(chain: str = "ethereum"): | |
| return await fetch_active_addresses(chain) | |
| async def history(limit: int = 50): | |
| return list(ANOMALY_HISTORY)[-limit:] | |
| 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 (АВТО-ОТПРАВКА) ЗАПУЩЕН!") |