Spaces:
Sleeping
Sleeping
| # ============================================ | |
| # АВТО-УСТАНОВКА ПАКЕТОВ | |
| # ============================================ | |
| 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") | |
| async def startup(): | |
| asyncio.create_task(auto_send_loop()) | |
| logger.info(f"🚀 Space 27 v6.0 | Хаб: {HUB_URL}") | |
| async def shutdown(): await http_client.aclose() | |
| async def health(): return {"space_id": SPACE_ID, "status": "operational", "version": "6.0"} | |
| async def health_head(): return {} | |
| async def consilium(symbol: str = Query("XAU/USD")): | |
| if symbol not in SYMBOLS: return {"error": "Invalid symbol"} | |
| return await get_seasonality_signal(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} | |
| 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} | |
| async def expiry(): return {"expiry_dates": await fetch_expiry_dates()} | |
| 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) | |
| } | |
| 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} | |
| 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 ГОТОВ!") |