# ============================================ # АВТО-УСТАНОВКА ПАКЕТОВ # ============================================ 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 27 v6.0 — ADAPTIVE SEASONALITY ENGINE (УСИЛЕННЫЙ) # ============================================ import os, time, json, logging, asyncio from typing import Dict, Any, List, Optional, Tuple from datetime import datetime, timedelta, 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("Space27_Seasonality") # ================= КОНФИГУРАЦИЯ ================= SPACE_ID = 27 SPACE_NAME = "Seasonality Engine" SYMBOLS = ["XAU/USD", "ETH/USD", "SOL/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", "1800")) logger.info(f"🔗 Хаб: {HUB_URL} | Старт: {STARTUP_SLEEP}с | Интервал: {AUTO_SEND_INTERVAL}с") FOMC_DATES = ["2026-06-12", "2026-07-31", "2026-09-18", "2026-11-06", "2026-12-18"] CPI_DATES = ["2026-06-10", "2026-07-14", "2026-08-12", "2026-09-15", "2026-10-13", "2026-11-10", "2026-12-11"] NFP_DATES = ["2026-06-05", "2026-07-03", "2026-08-07", "2026-09-04", "2026-10-02", "2026-11-06", "2026-12-04"] OPEC_DATES = ["2026-06-03", "2026-08-26", "2026-11-12"] G20_DATES = ["2026-07-15", "2026-11-20"] http_client = httpx.AsyncClient(timeout=20.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} 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 season_cache = {} season_ttl = 86400 # ================= ЗАГРУЗКА ДАННЫХ ================= async def fetch_historical_prices(symbol: str, years: int = 5) -> Optional[pd.DataFrame]: cache_key = f"hist_daily_{symbol}_{years}" if cache_key in season_cache and time.time() - season_cache[cache_key]["ts"] < season_ttl: return season_cache[cache_key]["data"] try: limit = years * 365 r = await http_client.get( f"{HUB_URL}/candles", params={"symbol": symbol, "interval": "1d", "limit": limit}, headers=hub_headers() ) if r.status_code == 200: data = r.json().get("candles", []) if data: df = pd.DataFrame(data) if "o" in df.columns: df.rename(columns={"o": "open", "h": "high", "l": "low", "c": "close", "v": "volume", "t": "timestamp"}, inplace=True) df["close"] = pd.to_numeric(df["close"], errors="coerce") if "timestamp" in df.columns: df["timestamp"] = pd.to_datetime(df["timestamp"], unit="ms") df.set_index("timestamp", inplace=True) season_cache[cache_key] = {"data": df, "ts": time.time()} return df except Exception as e: logger.warning(f"История {symbol}: {e}") return None def compute_monthly_returns(df: pd.DataFrame) -> Dict[int, Dict[str, float]]: if df is None or df.empty: return {} monthly = df["close"].resample("M").last().pct_change().dropna() * 100 monthly.index = monthly.index.month result = {} for month in range(1, 13): month_data = monthly[monthly.index == month] if len(month_data) > 0: result[month] = { "avg_return": round(float(month_data.mean()), 2), "win_rate": round(float((month_data > 0).mean()) * 100, 1), "count": len(month_data) } else: result[month] = {"avg_return": 0.0, "win_rate": 50.0, "count": 0} return result def compute_daily_returns(df: pd.DataFrame) -> Dict[int, float]: if df is None or df.empty: return {} df["weekday"] = df.index.weekday daily = df.groupby("weekday")["close"].last().pct_change().dropna() * 100 daily_avg = daily.groupby(df["weekday"]).mean() return daily_avg.to_dict() # 🔥 Квартальная сезонность def compute_quarterly_returns(df: pd.DataFrame) -> Dict[int, float]: if df is None or df.empty: return {} quarterly = df["close"].resample("Q").last().pct_change().dropna() * 100 quarterly.index = quarterly.index.quarter return quarterly.groupby(level=0).mean().to_dict() async def fetch_expiry_dates() -> List[str]: try: r = await http_client.get("https://www.deribit.com/api/v2/public/get_instruments?currency=ETH&kind=option&expired=false") if r.status_code == 200: instruments = r.json().get("result", []) expiries = list(set(instr["expiration_timestamp"] for instr in instruments)) expiries.sort() now = datetime.now(timezone.utc) return [datetime.fromtimestamp(ts/1000, tz=timezone.utc).strftime("%Y-%m-%d") for ts in expiries if ts/1000 > now.timestamp()][:3] except: pass today = datetime.now(timezone.utc) expiries = [] for _ in range(3): days_until_friday = (4 - today.weekday()) % 7 if days_until_friday == 0: days_until_friday = 7 friday = today + timedelta(days=days_until_friday) expiries.append(friday.strftime("%Y-%m-%d")) today = friday + timedelta(days=1) return expiries def is_macro_week(date: datetime, dates_list: List[str], window: int = 3) -> bool: for d_str in dates_list: try: event_date = datetime.strptime(d_str, "%Y-%m-%d").replace(tzinfo=timezone.utc) if abs((date - event_date).days) <= window: return True except: pass return False # ================= 🔥 АНАЛИЗ СЕЗОННОСТИ ================= async def analyze_seasonality(symbol: str) -> Dict[str, Any]: now = datetime.now(timezone.utc) df = await fetch_historical_prices(symbol, 5) monthly_returns = compute_monthly_returns(df) current_month = now.month month_data = monthly_returns.get(current_month, {"avg_return": 0, "win_rate": 50}) monthly_ret = month_data["avg_return"] monthly_winrate = month_data["win_rate"] # 🔥 Месячный сигнал с учётом winrate if monthly_ret > 1.5 and monthly_winrate > 55: monthly_signal, monthly_bias = "STRONG_BULLISH", 0.30 elif monthly_ret > 0.5 and monthly_winrate > 50: monthly_signal, monthly_bias = "BULLISH", 0.18 elif monthly_ret < -1.5 and monthly_winrate < 45: monthly_signal, monthly_bias = "STRONG_BEARISH", -0.30 elif monthly_ret < -0.5 and monthly_winrate < 50: monthly_signal, monthly_bias = "BEARISH", -0.18 else: monthly_signal, monthly_bias = "NEUTRAL", 0.0 daily_returns = compute_daily_returns(df) weekday = now.weekday() daily_ret = daily_returns.get(weekday, 0) if "XAU" in symbol and weekday >= 5: daily_bias = -0.05 elif daily_ret > 0.1: daily_bias = 0.03 elif daily_ret < -0.1: daily_bias = -0.03 else: daily_bias = 0.0 # 🔥 Квартальная сезонность quarterly_returns = compute_quarterly_returns(df) current_quarter = (now.month - 1) // 3 + 1 quarter_ret = quarterly_returns.get(current_quarter, 0) if quarter_ret > 2: quarter_bias = 0.10 elif quarter_ret > 0.5: quarter_bias = 0.05 elif quarter_ret < -2: quarter_bias = -0.10 elif quarter_ret < -0.5: quarter_bias = -0.05 else: quarter_bias = 0.0 expiry_dates = await fetch_expiry_dates() nearest_expiry = expiry_dates[0] if expiry_dates else None days_to_expiry = (datetime.strptime(nearest_expiry, "%Y-%m-%d").replace(tzinfo=timezone.utc) - now).days if nearest_expiry else None if days_to_expiry is not None and days_to_expiry <= 1: expiry_bias = -0.12 elif days_to_expiry is not None and days_to_expiry <= 3: expiry_bias = -0.06 elif days_to_expiry is not None and days_to_expiry <= 7: expiry_bias = 0.0 else: expiry_bias = 0.0 # 🔥 Расширенные специальные события specials = [] special_bias = 0.0 if "XAU" in symbol and now.month in [9, 10, 11, 12]: specials.append({"event": "INDIAN_WEDDING_SEASON", "impact": "BULLISH", "note": "Спрос на золото"}) special_bias += 0.12 if "XAU" in symbol and now.month == 1 and now.day >= 20 or now.month == 2 and now.day <= 15: specials.append({"event": "CHINESE_NEW_YEAR", "impact": "BULLISH", "note": "Праздничный спрос"}) special_bias += 0.08 if now.month == 12 and now.day >= 20: specials.append({"event": "SANTA_CLAUS_RALLY", "impact": "BULLISH", "note": "Исторически позитивный период"}) special_bias += 0.08 if now.month == 5: specials.append({"event": "SELL_IN_MAY", "impact": "BEARISH", "note": "Сезонное снижение"}) special_bias -= 0.05 if now.month == 1: specials.append({"event": "JANUARY_EFFECT", "impact": "BULLISH", "note": "Эффект января"}) special_bias += 0.05 if is_macro_week(now, FOMC_DATES, window=2): specials.append({"event": "FOMC_WEEK", "impact": "CAUTION", "note": "Заседание ФРС"}) special_bias -= 0.06 if is_macro_week(now, CPI_DATES, window=1): specials.append({"event": "CPI_WEEK", "impact": "CAUTION", "note": "Данные по инфляции"}) special_bias -= 0.03 if is_macro_week(now, NFP_DATES, window=1): specials.append({"event": "NFP_WEEK", "impact": "CAUTION", "note": "Данные по занятости"}) special_bias -= 0.03 if is_macro_week(now, OPEC_DATES, window=2): specials.append({"event": "OPEC_MEETING", "impact": "CAUTION", "note": "Заседание ОПЕК"}) if is_macro_week(now, G20_DATES, window=3): specials.append({"event": "G20_MEETING", "impact": "CAUTION", "note": "Саммит G20"}) # 🔥 Веса: Месяц 45%, Квартал 15%, Экспирация 20%, События 15%, День 5% total_bias = ( monthly_bias * 0.45 + quarter_bias * 0.15 + expiry_bias * 0.20 + special_bias * 0.15 + daily_bias * 0.05 ) score = 50.0 + total_bias * 100 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.85, score / 100) elif score > 54: signal, confidence = "BUY", min(0.62, (score - 50) / 50) elif score < 38: signal, confidence = "SELL", min(0.85, (100 - score) / 100) elif score < 46: signal, confidence = "SELL", min(0.62, (50 - score) / 50) else: signal, confidence = "WAIT", 0.0 components = { "monthly": {"return_pct": round(monthly_ret, 2), "win_rate": monthly_winrate, "bias": monthly_bias, "signal": monthly_signal}, "quarterly": {"return_pct": round(quarter_ret, 2), "bias": quarter_bias}, "daily": {"return_pct": round(daily_ret, 3), "bias": daily_bias}, "expiry": {"nearest": nearest_expiry, "days": days_to_expiry, "bias": expiry_bias}, "specials": {"events": specials, "bias": special_bias} } return { "seasonality_score": score, "score_zscore": round(score_z, 2), "signal": signal, "confidence": round(confidence, 4), "total_bias": round(total_bias, 4), "signals": [ {"factor": "MONTHLY", "signal": monthly_signal, "reason": f"Ср.доходность {monthly_ret:+.2f}% (WR={monthly_winrate:.0f}%)"}, {"factor": "QUARTERLY", "signal": "BULLISH" if quarter_bias > 0 else "BEARISH" if quarter_bias < 0 else "NEUTRAL", "reason": f"Квартал {current_quarter}: {quarter_ret:+.2f}%"}, {"factor": "DAILY", "signal": "BULLISH" if daily_bias > 0 else "BEARISH" if daily_bias < 0 else "NEUTRAL", "reason": f"День недели: {daily_ret:+.3f}%"}, {"factor": "EXPIRY", "signal": "EXPIRY_NEAR" if expiry_bias != 0 else "NORMAL", "reason": f"Экспирация через {days_to_expiry} дн." if days_to_expiry else "Нет данных"}, *[{"factor": s["event"], "signal": s["impact"], "reason": s["note"]} for s in specials] ], "components": components } # ================= ОТПРАВКА В 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": "6.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_seasonality_signal(symbol: str = "XAU/USD") -> Dict[str, Any]: start = time.time() analysis = await analyze_seasonality(symbol) latency = int((time.time() - start) * 1000) features = { "seasonality_score": analysis['seasonality_score'], "score_zscore": analysis['score_zscore'], "monthly_ret": analysis['components']['monthly']['return_pct'] } await send_signal_to_hub(symbol, analysis['signal'], analysis['confidence'], features) logger.info(f"📅 Seasonality {symbol}: {analysis['signal']} conf={analysis['confidence']:.3f} score={analysis['seasonality_score']} | {latency}ms") return { "space_id": SPACE_ID, "timestamp": int(time.time()), "symbol": symbol, "signal": analysis['signal'], "confidence": analysis['confidence'], "seasonality_analysis": analysis } # ================= АВТО-ОТПРАВКА ================= async def auto_send_loop(): logger.info(f"⏳ Стартовый сон {STARTUP_SLEEP}с...") await log_to_hub("STARTUP", f"Seasonality v6.0 запущен, жду {STARTUP_SLEEP}с") await asyncio.sleep(STARTUP_SLEEP) logger.info(f"🔄 Seasonality [интервал={AUTO_SEND_INTERVAL}с]") while True: try: for symbol in SYMBOLS: await get_seasonality_signal(symbol) await asyncio.sleep(2) logger.info("✅ Seasonality цикл завершён") 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 27 v6.0 — Seasonality Engine") @app.on_event("startup") async def startup(): asyncio.create_task(auto_send_loop()) logger.info(f"🚀 Space 27 v6.0 | Хаб: {HUB_URL}") @app.on_event("shutdown") async def shutdown(): await http_client.aclose() @app.get("/health") async def health(): return {"space_id": SPACE_ID, "status": "operational", "version": "6.0"} @app.head("/health") async def health_head(): return {} @app.get("/consilium") async def consilium(symbol: str = Query("XAU/USD")): if symbol not in SYMBOLS: return {"error": "Invalid symbol"} return await get_seasonality_signal(symbol) @app.get("/monthly/{symbol}") async def monthly(symbol: str): if symbol not in SYMBOLS: return {"error": "Invalid symbol"} df = await fetch_historical_prices(symbol, 5) returns = compute_monthly_returns(df) return {"symbol": symbol, "monthly_returns": returns} @app.get("/daily/{symbol}") async def daily(symbol: str): if symbol not in SYMBOLS: return {"error": "Invalid symbol"} df = await fetch_historical_prices(symbol, 5) returns = compute_daily_returns(df) return {"symbol": symbol, "daily_returns": returns} @app.get("/expiry") async def expiry(): return {"expiry_dates": await fetch_expiry_dates()} @app.get("/specials") async def specials(): now = datetime.now(timezone.utc) return { "fomc_week": is_macro_week(now, FOMC_DATES, 2), "cpi_week": is_macro_week(now, CPI_DATES, 1), "nfp_week": is_macro_week(now, NFP_DATES, 1), "opec_week": is_macro_week(now, OPEC_DATES, 2), "g20_week": is_macro_week(now, G20_DATES, 3) } @app.get("/send_now") async def send_now(): results = {} for symbol in SYMBOLS: sig = await get_seasonality_signal(symbol) results[symbol] = sig.get("signal", "WAIT") return {"status": "sent", "results": results} @app.get("/") async def root(): return {"name": "Seasonality Engine v6.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 27 v6.0 — SEASONALITY ENGINE ГОТОВ!")