TomirisGold6.1 / app.py
tomirisg25's picture
Update app.py
e9cd28b verified
Raw
History Blame Contribute Delete
18.9 kB
# ============================================
# АВТО-УСТАНОВКА ПАКЕТОВ
# ============================================
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 ГОТОВ!")