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 24 v2.2 — ECOSYSTEM AGGREGATOR PRO (АВТО-ОТПРАВКА) | |
| # ============================================ | |
| 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("Space24_EcosystemAggregator") | |
| # ================= КОНФИГУРАЦИЯ ================= | |
| SYMBOLS = ["XAU/USD", "ETH/USD", "SOL/USD"] | |
| ECOSYSTEM_SPACES = { | |
| "space_19_sol": { | |
| "url": "https://tomirisai80-tomirisanal.hf.space", | |
| "weight": 0.25, | |
| "symbols": ["SOL/USD"], | |
| "description": "SOL Master", | |
| "cache_ttl": 300 | |
| }, | |
| "space_20_l2_defi": { | |
| "url": "https://tomirisai80-tomirisanal2.hf.space", | |
| "weight": 0.25, | |
| "symbols": ["ETH/USD"], | |
| "description": "L2 & DeFi Pulse", | |
| "cache_ttl": 300 | |
| }, | |
| "space_21_gold_macro": { | |
| "url": "https://tomirisai80-tomirisanal3.hf.space", | |
| "weight": 0.25, | |
| "symbols": ["XAU/USD"], | |
| "description": "Gold Macro & Flow", | |
| "cache_ttl": 600 | |
| }, | |
| "space_22_sentiment": { | |
| "url": "https://tomirisai80-tomirisanal4.hf.space", | |
| "weight": 0.15, | |
| "symbols": ["XAU/USD", "ETH/USD", "SOL/USD"], | |
| "description": "Cross-Asset Sentiment", | |
| "cache_ttl": 300 | |
| }, | |
| "space_23_anomaly": { | |
| "url": "https://tomirisai80-tomirisai5.hf.space", | |
| "weight": 0.10, | |
| "symbols": ["ETH/USD", "SOL/USD"], | |
| "description": "On-Chain Anomaly", | |
| "cache_ttl": 60 | |
| } | |
| } | |
| HUB_URL = os.getenv("SPACE17_URL", "https://tomiris-ai-name5-5.hf.space") | |
| SPACE31_URL = os.getenv("SPACE31_URL", "https://nuxotetotmailsvoboden-tomiris-perf.hf.space") | |
| # Интервал авто-отправки | |
| AUTO_SEND_INTERVAL = int(os.getenv("AUTO_SEND_INTERVAL", "300")) | |
| # ================= HTTP КЛИЕНТ ================= | |
| http_client = httpx.AsyncClient(timeout=15.0) | |
| # ================= ГЛОБАЛЬНЫЙ КЭШ И BREAKER ================= | |
| 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 | |
| # ================= ДИНАМИЧЕСКИЕ ВЕСА ================= | |
| async def fetch_dynamic_weights() -> Dict[str, float]: | |
| if SPACE31_URL: | |
| try: | |
| r = await http_client.get(f"{SPACE31_URL}/weights", timeout=8) | |
| if r.status_code == 200: | |
| data = r.json() | |
| if data: | |
| return {k: v.get("weight", 0.1) for k, v in data.items() if k in ECOSYSTEM_SPACES} | |
| except: | |
| pass | |
| try: | |
| r = await http_client.get(f"{HUB_URL}/metrics", timeout=8) | |
| if r.status_code == 200: | |
| metrics = r.json() | |
| weights = {} | |
| for m in metrics: | |
| name = m.get("space_name", "") | |
| if name in ECOSYSTEM_SPACES: | |
| acc = m.get("accuracy", 0.5) | |
| pf = m.get("profit_factor", 1.0) | |
| weights[name] = acc * min(pf, 3.0) / 3.0 | |
| return weights | |
| except: | |
| pass | |
| return {name: cfg["weight"] for name, cfg in ECOSYSTEM_SPACES.items()} | |
| # ================= ОПРОС ОДНОГО SPACE ================= | |
| async def fetch_space_signal(name: str, config: Dict, symbol: str) -> Optional[Dict]: | |
| if breaker_open(name): | |
| return None | |
| cache_key = f"{name}_{symbol}" | |
| ttl = config.get("cache_ttl", 60) | |
| now = time.time() | |
| if cache_key in cache_store and (now - cache_times.get(cache_key, 0)) < ttl: | |
| return cache_store[cache_key] | |
| try: | |
| r = await http_client.get(f"{config['url']}/consilium?symbol={symbol}", timeout=10) | |
| if r.status_code == 200: | |
| data = r.json() | |
| breaker_record(name, True) | |
| cache_store[cache_key] = data | |
| cache_times[cache_key] = time.time() | |
| return data | |
| except: | |
| breaker_record(name, False) | |
| return None | |
| # ================= АГРЕГАЦИЯ ================= | |
| async def aggregate_ecosystem(symbol: str) -> Dict[str, Any]: | |
| dynamic_weights = await fetch_dynamic_weights() | |
| tasks = [] | |
| space_names = [] | |
| for name, cfg in ECOSYSTEM_SPACES.items(): | |
| if symbol in cfg["symbols"]: | |
| tasks.append(fetch_space_signal(name, cfg, symbol)) | |
| space_names.append(name) | |
| results = await asyncio.gather(*tasks) | |
| signals = {} | |
| errors = [] | |
| for name, res in zip(space_names, results): | |
| if res and isinstance(res, dict): | |
| signals[name] = res | |
| else: | |
| errors.append(name) | |
| total_weight = 0.0 | |
| long_votes = 0.0 | |
| short_votes = 0.0 | |
| wait_votes = 0.0 | |
| details = {} | |
| for name, data in signals.items(): | |
| weight = dynamic_weights.get(name, ECOSYSTEM_SPACES[name]["weight"]) | |
| sig = data.get("signal", {}) | |
| direction = sig.get("direction", "WAIT") | |
| confidence = sig.get("confidence", 0) | |
| if direction == "LONG": | |
| long_votes += weight * confidence | |
| elif direction == "SHORT": | |
| short_votes += weight * confidence | |
| else: | |
| wait_votes += weight * confidence | |
| total_weight += weight | |
| details[name] = { | |
| "direction": direction, | |
| "confidence": confidence, | |
| "weight": weight, | |
| "status": "active", | |
| "description": ECOSYSTEM_SPACES[name]["description"] | |
| } | |
| for name in errors: | |
| weight = dynamic_weights.get(name, ECOSYSTEM_SPACES[name]["weight"]) | |
| details[name] = { | |
| "direction": "ERROR", | |
| "confidence": 0, | |
| "weight": weight, | |
| "status": "error", | |
| "description": ECOSYSTEM_SPACES[name]["description"] | |
| } | |
| if total_weight == 0: | |
| return { | |
| "ecosystem_score": 50.0, | |
| "direction": "WAIT", | |
| "confidence": 0.0, | |
| "active_spaces": 0, | |
| "error_spaces": len(errors), | |
| "signals_detail": details, | |
| "votes": {"LONG": 0.0, "SHORT": 0.0, "WAIT": 0.0} | |
| } | |
| bias = (long_votes - short_votes) / total_weight | |
| ecosystem_score = 50.0 + bias * 50.0 | |
| ecosystem_score = max(0, min(100, ecosystem_score)) | |
| wait_ratio = wait_votes / total_weight | |
| if wait_ratio > 0.6: | |
| direction = "WAIT" | |
| confidence = wait_ratio | |
| elif long_votes > short_votes * 1.3: | |
| direction = "LONG" | |
| confidence = min(0.9, long_votes / total_weight) | |
| elif short_votes > long_votes * 1.3: | |
| direction = "SHORT" | |
| confidence = min(0.9, short_votes / total_weight) | |
| else: | |
| direction = "WAIT" | |
| confidence = max(long_votes, short_votes) / total_weight | |
| active_ratio = len(signals) / len(space_names) if space_names else 0 | |
| confidence *= 0.5 + 0.5 * active_ratio | |
| return { | |
| "ecosystem_score": round(ecosystem_score, 2), | |
| "direction": direction, | |
| "confidence": round(confidence, 4), | |
| "active_spaces": len(signals), | |
| "error_spaces": len(errors), | |
| "signals_detail": details, | |
| "votes": { | |
| "LONG": round(long_votes, 4), | |
| "SHORT": round(short_votes, 4), | |
| "WAIT": round(wait_votes, 4) | |
| } | |
| } | |
| # ================= ОТПРАВКА В 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_24_eco_agg", | |
| "symbol": symbol, | |
| "direction": direction, | |
| "confidence": confidence, | |
| "raw": json.dumps({"source": "space_24_eco_agg"}) | |
| }, 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_ecosystem_aggregate(symbol: str = "XAU/USD") -> Dict[str, Any]: | |
| start = time.time() | |
| agg = await aggregate_ecosystem(symbol) | |
| latency = int((time.time() - start) * 1000) | |
| await send_signal_to_hub(symbol, agg['direction'], agg['confidence']) | |
| result = { | |
| "space": "space_24_ecosystem_aggregator", | |
| "timestamp": int(time.time()), | |
| "symbol": symbol, | |
| "signal": { | |
| "direction": agg['direction'], | |
| "confidence": agg['confidence'] | |
| }, | |
| "ecosystem_analysis": { | |
| "score": agg['ecosystem_score'], | |
| "active_spaces": agg['active_spaces'], | |
| "error_spaces": agg['error_spaces'], | |
| "votes": agg['votes'], | |
| "details": agg['signals_detail'] | |
| }, | |
| "latency_ms": latency | |
| } | |
| logger.info(f"🌐 Ecosystem Agg: {symbol} {agg['direction']} conf={agg['confidence']:.3f}") | |
| return result | |
| # ================= АВТО-ОТПРАВКА ================= | |
| async def auto_send_loop(): | |
| logger.info(f"🔄 Авто-отправка Ecosystem Aggregator запущена (интервал {AUTO_SEND_INTERVAL}с)") | |
| await asyncio.sleep(30) | |
| while True: | |
| try: | |
| for symbol in SYMBOLS: | |
| await get_ecosystem_aggregate(symbol) | |
| await asyncio.sleep(1) | |
| logger.info("✅ Ecosystem Aggregator авто-отправка завершена") | |
| except Exception as e: | |
| logger.error(f"Ошибка авто-отправки: {e}") | |
| await asyncio.sleep(AUTO_SEND_INTERVAL) | |
| # ================= FASTAPI ================= | |
| app = FastAPI(title="Tomiris Space 24 v2.2 — Ecosystem Aggregator Pro (Auto-Hub)") | |
| async def startup(): | |
| asyncio.create_task(auto_send_loop()) | |
| logger.info("🚀 Space 24 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": SYMBOLS, | |
| "ecosystem_spaces": list(ECOSYSTEM_SPACES.keys()) | |
| } | |
| async def consilium(symbol: str = Query("XAU/USD")): | |
| if symbol not in SYMBOLS: | |
| return {"error": "Invalid symbol"} | |
| return await get_ecosystem_aggregate(symbol) | |
| async def breakdown(symbol: str): | |
| agg = await aggregate_ecosystem(symbol) | |
| return {"symbol": symbol, "aggregate": agg, "spaces_detail": agg.get('signals_detail', {})} | |
| async def all_signals(): | |
| results = {} | |
| for sym in SYMBOLS: | |
| results[sym] = await aggregate_ecosystem(sym) | |
| return results | |
| async def send_now(): | |
| results = {} | |
| for symbol in SYMBOLS: | |
| analysis = await get_ecosystem_aggregate(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 24 v2.2 — ECOSYSTEM AGGREGATOR PRO (АВТО-ОТПРАВКА) ЗАПУЩЕН!") |