tomirisai80 commited on
Commit
db22eaa
·
verified ·
1 Parent(s): ab90134

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +214 -76
app.py CHANGED
@@ -1,5 +1,5 @@
1
  # ============================================
2
- # 👑 TOMIRIS SPACE 19 v10.1 — SOL/USD MASTER (ФИКС)
3
  # ============================================
4
  import os, time, threading, warnings, json, asyncio
5
  from typing import Dict, Any, Optional, List, Tuple
@@ -8,17 +8,24 @@ import requests
8
  from datetime import datetime, timedelta
9
  from collections import deque
10
  from fastapi import FastAPI, Query
 
11
  warnings.filterwarnings('ignore')
12
 
 
 
 
13
  # ================= БЕЗОПАСНЫЙ ИМПОРТ =================
14
  HAS_JOBLIB = False
15
  try:
16
  import joblib
17
  HAS_JOBLIB = True
18
  except:
19
- print("⚠️ joblib не установлен")
20
 
21
  # ================= КОНФИГУРАЦИЯ =================
 
 
 
22
  HUB_URL = os.getenv("HUB_URL", "https://pro-3-tomiris-hub.hf.space")
23
  HUB_SECRET = os.getenv("HUB_SECRET", "TomyrisUltraSecret2026!")
24
  TWELVE_DATA_KEY = os.getenv("TWELVE_DATA_KEY", "")
@@ -30,8 +37,13 @@ TIMEFRAMES = ["15min", "1h", "4h"]
30
 
31
  SOL_THRESHOLD = 0.52
32
 
 
 
33
  def hub_headers():
34
- return {"X-Hub-Secret": HUB_SECRET, "Content-Type": "application/json"}
 
 
 
35
 
36
  CACHE_TTL = 300
37
  DATA_CACHE: Dict[str, Dict[str, Any]] = {}
@@ -52,7 +64,7 @@ REGIME_WEIGHTS = {
52
  }
53
 
54
  # ================= ЗАГРУЗКА МОДЕЛЕЙ =================
55
- print(f"🔥 SPACE 19 v10.1: Загрузка моделей для {SYMBOL}...")
56
  MODELS: Dict[str, Optional[Any]] = {"xgb_daily": None, "xgb_4h": None, "lgb": None}
57
 
58
  if HAS_JOBLIB:
@@ -62,13 +74,13 @@ if HAS_JOBLIB:
62
  if os.path.exists(fname):
63
  try:
64
  MODELS[key] = joblib.load(fname)
65
- print(f"✅ {fname} загружен")
66
  except Exception as e:
67
- print(f"⚠️ {fname}: {e}")
68
 
69
  # ================= HTTP СЕССИЯ =================
70
  session = requests.Session()
71
- session.headers.update({"User-Agent": "Tomiris-Space19-v10.1"})
72
 
73
  # ================= УТИЛИТЫ =================
74
  def safe_float(value, default=0.0):
@@ -76,27 +88,36 @@ def safe_float(value, default=0.0):
76
  if isinstance(value, (pd.Series, pd.DataFrame)):
77
  return float(value.iloc[-1]) if len(value) > 0 else default
78
  return float(value) if not pd.isna(float(value)) else default
79
- except: return default
 
80
 
81
  def safe_rsi(close, period=14):
82
  try:
83
- delta = close.diff(); gain = delta.clip(lower=0).rolling(period, min_periods=period).mean()
 
84
  loss = (-delta.clip(upper=0)).rolling(period, min_periods=period).mean()
85
  g_val, l_val = gain.iloc[-1], loss.iloc[-1]
86
- if pd.notna(g_val) and pd.notna(l_val) and l_val > 0: return float(100 - (100 / (1 + g_val/l_val)))
 
 
 
87
  return 50.0
88
- except: return 50.0
89
 
90
  def safe_ema(close, span):
91
- try: return float(close.ewm(span=span, adjust=False).mean().iloc[-1])
92
- except: return float(close.iloc[-1])
 
 
93
 
94
  def hurst_exponent(series, lags=20):
95
- if len(series) < lags * 2: return 0.5
 
96
  lags_range = range(2, min(lags, len(series)//2))
97
  tau = [np.std(np.subtract(series.values[lag:], series.values[:-lag])) for lag in lags_range]
98
- try: return float(np.polyfit(np.log(list(lags_range)), np.log(tau), 1)[0] * 2.0)
99
- except: return 0.5
 
 
100
 
101
  def smooth_confidence(current):
102
  global LAST_CONFIDENCE
@@ -109,8 +130,10 @@ def detect_market_regime(features):
109
  adx = features.get("adx", 20.0)
110
  volatility = features.get("volatility_1h", 0.0)
111
  hurst = features.get("hurst_exponent", 0.5)
112
- if adx > 30 and hurst > 0.55: return "TREND"
113
- if volatility > 0.04: return "VOLATILE"
 
 
114
  return "RANGE"
115
 
116
  # ================= ЗАГРУЗКА ДАННЫХ =================
@@ -120,23 +143,31 @@ def fetch_ohlc_hub(symbol, tf, limit=200):
120
  return DATA_CACHE[cache_key]["df"]
121
 
122
  try:
123
- r = session.get(f"{HUB_URL}/candles", params={"symbol": symbol, "interval": tf, "limit": limit}, timeout=20, headers=hub_headers())
 
 
 
 
 
124
  if r.status_code == 200:
125
  candles = r.json().get("candles", [])
126
  if candles:
127
  df = pd.DataFrame(candles)
128
- if "o" in df.columns: df.rename(columns={"o":"open","h":"high","l":"low","c":"close","v":"volume"}, inplace=True)
129
- for col in ["open","high","low","close"]: df[col] = pd.to_numeric(df[col], errors="coerce")
 
 
130
  df["volume"] = pd.to_numeric(df.get("volume", 0), errors="coerce").fillna(0)
131
  if len(df) >= 30:
132
  DATA_CACHE[cache_key] = {"df": df, "timestamp": time.time()}
133
  return df
134
  except Exception as e:
135
- print(f"Hub {symbol} {tf}: {e}")
136
  return None
137
 
138
  def fetch_twelvedata_sol(tf="1h"):
139
- if not TWELVE_DATA_KEY: return None
 
140
  cache_key = f"td_sol_{tf}"
141
  if cache_key in DATA_CACHE and time.time() - DATA_CACHE[cache_key].get("timestamp", 0) < CACHE_TTL:
142
  return DATA_CACHE[cache_key]["df"]
@@ -146,41 +177,75 @@ def fetch_twelvedata_sol(tf="1h"):
146
  r = session.get(url, timeout=15)
147
  if r.status_code == 200 and "values" in r.json():
148
  df = pd.DataFrame(r.json()["values"]).iloc[::-1].reset_index(drop=True)
149
- for col in ["close","high","low","open"]: df[col] = pd.to_numeric(df[col], errors="coerce")
 
150
  df["volume"] = pd.to_numeric(df.get("volume", 0), errors="coerce").fillna(0)
151
  if len(df) >= 30:
152
  DATA_CACHE[cache_key] = {"df": df, "timestamp": time.time()}
153
  return df
154
- except: pass
 
155
  return None
156
 
157
  def fetch_ohlc(symbol, tf="1h"):
158
  df = fetch_ohlc_hub(symbol, tf)
159
  if df is None:
160
- print(f" 🔄 Хаб недоступен для {symbol} {tf}, пробую Twelve Data...")
161
  df = fetch_twelvedata_sol(tf)
162
  return df
163
 
164
  def fetch_solana_onchain():
 
165
  cache_key = "solana_onchain"
166
  if cache_key in DATA_CACHE and time.time() - DATA_CACHE[cache_key].get("timestamp", 0) < 300:
167
  return DATA_CACHE[cache_key]["data"]
 
168
  result = {}
 
 
169
  try:
170
- r = session.get("https://api.llama.fi/v2/tvl/solana", timeout=10)
171
  if r.status_code == 200:
172
- data = r.json()
173
- result['tvl'] = data.get('tvl', 0)
174
- result['tvl_change_24h'] = data.get('change_1d', 0)
175
- result['tvl_trend'] = 'UP' if data.get('change_1d', 0) > 0 else 'DOWN'
176
- except: pass
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  try:
178
- r = session.get("https://api.llama.fi/overview/dexs/solana?excludeTotalDataChart=true&excludeTotalDataChartBreakdown=true", timeout=10)
 
 
 
179
  if r.status_code == 200:
180
  data = r.json()
181
  result['dex_volume_24h'] = data.get('total24h', 0)
182
  result['dex_change_24h'] = data.get('change_1d', 0)
183
- except: pass
 
 
 
 
 
 
 
184
  DATA_CACHE[cache_key] = {"data": result, "timestamp": time.time()}
185
  return result
186
 
@@ -195,16 +260,20 @@ def fetch_binance_sol():
195
  result['funding_rate'] = fr
196
  result['funding_signal'] = 'BEARISH' if fr > 0.001 else 'BULLISH' if fr < -0.001 else 'NEUTRAL'
197
  break
198
- except: pass
 
199
  try:
200
  r = session.get("https://fapi.binance.com/fapi/v1/openInterest?symbol=SOLUSDT", timeout=10)
201
- if r.status_code == 200: result['open_interest'] = float(r.json().get('openInterest', 0))
202
- except: pass
 
 
203
  return result
204
 
205
- # ================= ПОСТРОЕНИЕ ПРИЗНАКОВ (ФИКС — БЕЗ ПАДДИНГА) =================
206
  def build_sol_features(df, onchain_data=None, derivatives=None):
207
- if df is None or len(df) < 20: return {}
 
208
 
209
  close = df["close"].astype(float)
210
  high = df["high"].astype(float)
@@ -239,10 +308,17 @@ def build_sol_features(df, onchain_data=None, derivatives=None):
239
 
240
  if len(close) >= 14:
241
  prev_close = close.shift(1)
242
- tr = pd.DataFrame({"tr1": high-low, "tr2": (high-prev_close).abs(), "tr3": (low-prev_close).abs()}).max(axis=1)
 
 
 
 
243
  features["atr_14"] = safe_float(tr.rolling(14, min_periods=14).mean().iloc[-1])
244
  features["atr_pct"] = (features["atr_14"] / (close.iloc[-1] + 1e-10)) * 100
245
 
 
 
 
246
  if onchain_data:
247
  features["tvl"] = onchain_data.get("tvl", 0)
248
  features["tvl_trend"] = 1 if onchain_data.get("tvl_trend") == "UP" else -1
@@ -258,9 +334,46 @@ def build_sol_features(df, onchain_data=None, derivatives=None):
258
  features["is_weekend"] = 1 if now.weekday() >= 5 else 0
259
  features["hour"] = now.hour
260
 
261
- # 🔥 ФИКС: БЕЗ ПАДДИНГА! Модель сама разберётся
262
  return features
263
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  # ================= СИГНАЛ =================
265
  def get_sol_signal():
266
  global LAST_CONFIDENCE
@@ -274,16 +387,17 @@ def get_sol_signal():
274
  df = fetch_ohlc(SYMBOL, tf)
275
  if df is not None and len(df) >= 30:
276
  feats = build_sol_features(df, onchain_data, binance_data)
277
- if feats: all_features[tf] = feats
 
278
 
279
  if not all_features:
280
- send_signal_to_hub("WAIT", 0.0)
281
  return None
282
 
283
  h1_features = all_features.get("1h", list(all_features.values())[0])
284
  price = h1_features.get("price", 0)
285
  if price == 0:
286
- send_signal_to_hub("WAIT", 0.0)
287
  return None
288
 
289
  regime = detect_market_regime(h1_features)
@@ -306,7 +420,7 @@ def get_sol_signal():
306
  probs.append(float(proba[1] if len(proba) > 1 else proba[0]))
307
  models_used += 1
308
  except Exception as e:
309
- print(f" ⚠️ Ошибка {mk}: {e}")
310
 
311
  if MODELS.get("lgb"):
312
  try:
@@ -314,11 +428,12 @@ def get_sol_signal():
314
  lgb_prob = float(proba[1] if len(proba) > 1 else proba[0])
315
  xgb_prob = (sum(probs)/len(probs) * 0.6 + lgb_prob * 0.4) if probs else lgb_prob
316
  models_used += 1
317
- except: pass
 
318
  elif probs:
319
  xgb_prob = sum(probs) / len(probs)
320
  except Exception as e:
321
- print(f" ⚠️ Ошибка предсказания: {e}")
322
 
323
  # Мульти-ТФ
324
  confirmations, total_tf = 0, 0
@@ -327,8 +442,10 @@ def get_sol_signal():
327
  ema_score = feats.get("price_vs_ema_21", 0)
328
  rsi_val = feats.get("rsi_14", 50)
329
  macd_hist = feats.get("macd_hist", 0)
330
- if ema_score > 0 and rsi_val > 50 and macd_hist > 0: confirmations += 1
331
- elif ema_score < 0 and rsi_val < 50 and macd_hist < 0: confirmations -= 1
 
 
332
  tf_norm = ((confirmations / max(total_tf, 1)) + 1) / 2
333
 
334
  # Meta SOL Score
@@ -343,59 +460,80 @@ def get_sol_signal():
343
  model_w = base["model"] * (COMPONENT_PERF["model"]["correct"] / max(COMPONENT_PERF["model"]["total"], 1)) * COMPONENT_PERF["model"]["sharpe"]
344
  tf_w = base["tf"] * (COMPONENT_PERF["tf"]["correct"] / max(COMPONENT_PERF["tf"]["total"], 1)) * COMPONENT_PERF["tf"]["sharpe"]
345
  remaining = 1.0 - (model_w + tf_w)
346
- onchain_w = remaining * 0.6; deriv_w = remaining * 0.4
 
347
  total_w = model_w + tf_w + onchain_w + deriv_w
348
- if total_w > 0: model_w /= total_w; tf_w /= total_w; onchain_w /= total_w; deriv_w /= total_w
 
 
 
 
349
 
350
  final_score = xgb_prob * model_w + tf_norm * tf_w + meta_sol_score * onchain_w + deriv_norm * deriv_w
351
  confidence = smooth_confidence(final_score)
352
 
353
- if confidence > SOL_THRESHOLD + 0.08: direction = "LONG"
354
- elif confidence < SOL_THRESHOLD - 0.08: direction = "SHORT"
355
- else: direction = "WAIT"
 
 
 
 
356
 
357
- send_signal_to_hub(direction, confidence)
 
 
 
 
 
 
 
358
 
359
- print(f"🥉 SOL/USD: {direction} | conf={confidence:.3f} | models={models_used} | regime={regime}")
360
- return {"direction": direction, "confidence": confidence}
361
-
362
- # ================= ОТПРАВКА =================
363
- def send_signal_to_hub(direction, confidence):
364
- try:
365
- payload = {"space": "space_19_sol_master", "space_name": "space_19_sol_master", "symbol": SYMBOL, "direction": direction, "confidence": confidence, "features": {}, "metadata": {"version": "10.1"}}
366
- r = session.post(f"{HUB_URL}/signals", json=payload, timeout=10, headers=hub_headers())
367
- if r.status_code != 200:
368
- session.post(f"{HUB_URL}/signal", json={"space": "space_19_sol_master", "symbol": SYMBOL, "direction": direction, "confidence": confidence}, timeout=10, headers=hub_headers())
369
- print(f"📤 {SYMBOL}: {direction} conf={confidence:.3f}")
370
- except Exception as e: print(f"Ошибка: {e}")
371
 
372
  def auto_report():
373
- print(f"⏳ Стартовый сон {STARTUP_SLEEP}с...")
374
  time.sleep(STARTUP_SLEEP)
375
- print("✅ SOL Master — начинаю авто-отправку!")
376
  while True:
377
  time.sleep(AUTO_REPORT_INTERVAL)
378
- try: get_sol_signal()
379
- except Exception as e: print(f"Ошибка: {e}")
 
 
380
 
381
  threading.Thread(target=auto_report, daemon=True).start()
382
 
383
- app = FastAPI(title="SOL Master v10.1 FIX")
384
 
385
  @app.get("/health")
386
  async def health():
387
- return {"status": "ok", "version": "10.1", "symbol": SYMBOL, "models": sum(1 for m in MODELS.values() if m is not None)}
 
 
 
 
 
 
 
 
 
 
388
 
389
  @app.get("/consilium")
390
  async def consilium():
391
  result = get_sol_signal()
392
- return {"signal": {"direction": result["direction"], "confidence": result["confidence"]} if result else {"direction": "WAIT", "confidence": 0.0}}
 
 
393
 
394
  @app.get("/")
395
- async def root(): return {"name": "SOL Master v10.1 FIX"}
 
396
 
397
  if __name__ == "__main__":
398
  import uvicorn
399
  uvicorn.run(app, host="0.0.0.0", port=7860)
400
 
401
- print("🚀 SPACE 19 v10.1 — SOL/USD MASTER FIX ЗАПУЩЕН!")
 
1
  # ============================================
2
+ # 👑 TOMIRIS SPACE 19 v10.2 — SOL/USD MASTER (FULL FIX)
3
  # ============================================
4
  import os, time, threading, warnings, json, asyncio
5
  from typing import Dict, Any, Optional, List, Tuple
 
8
  from datetime import datetime, timedelta
9
  from collections import deque
10
  from fastapi import FastAPI, Query
11
+ import logging
12
  warnings.filterwarnings('ignore')
13
 
14
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
15
+ logger = logging.getLogger("Space19_SOL_Master")
16
+
17
  # ================= БЕЗОПАСНЫЙ ИМПОРТ =================
18
  HAS_JOBLIB = False
19
  try:
20
  import joblib
21
  HAS_JOBLIB = True
22
  except:
23
+ logger.warning("⚠️ joblib не установлен")
24
 
25
  # ================= КОНФИГУРАЦИЯ =================
26
+ SPACE_ID = 19
27
+ SPACE_NAME = "SOL Master"
28
+
29
  HUB_URL = os.getenv("HUB_URL", "https://pro-3-tomiris-hub.hf.space")
30
  HUB_SECRET = os.getenv("HUB_SECRET", "TomyrisUltraSecret2026!")
31
  TWELVE_DATA_KEY = os.getenv("TWELVE_DATA_KEY", "")
 
37
 
38
  SOL_THRESHOLD = 0.52
39
 
40
+ logger.info(f"🔗 Хаб: {HUB_URL}")
41
+
42
  def hub_headers():
43
+ return {
44
+ "X-Hub-Secret": HUB_SECRET,
45
+ "Content-Type": "application/json"
46
+ }
47
 
48
  CACHE_TTL = 300
49
  DATA_CACHE: Dict[str, Dict[str, Any]] = {}
 
64
  }
65
 
66
  # ================= ЗАГРУЗКА МОДЕЛЕЙ =================
67
+ logger.info(f"🔥 Загрузка моделей для {SYMBOL}...")
68
  MODELS: Dict[str, Optional[Any]] = {"xgb_daily": None, "xgb_4h": None, "lgb": None}
69
 
70
  if HAS_JOBLIB:
 
74
  if os.path.exists(fname):
75
  try:
76
  MODELS[key] = joblib.load(fname)
77
+ logger.info(f"✅ {fname} загружен")
78
  except Exception as e:
79
+ logger.warning(f"⚠️ {fname}: {e}")
80
 
81
  # ================= HTTP СЕССИЯ =================
82
  session = requests.Session()
83
+ session.headers.update({"User-Agent": "Tomiris-Space19-v10.2"})
84
 
85
  # ================= УТИЛИТЫ =================
86
  def safe_float(value, default=0.0):
 
88
  if isinstance(value, (pd.Series, pd.DataFrame)):
89
  return float(value.iloc[-1]) if len(value) > 0 else default
90
  return float(value) if not pd.isna(float(value)) else default
91
+ except:
92
+ return default
93
 
94
  def safe_rsi(close, period=14):
95
  try:
96
+ delta = close.diff()
97
+ gain = delta.clip(lower=0).rolling(period, min_periods=period).mean()
98
  loss = (-delta.clip(upper=0)).rolling(period, min_periods=period).mean()
99
  g_val, l_val = gain.iloc[-1], loss.iloc[-1]
100
+ if pd.notna(g_val) and pd.notna(l_val) and l_val > 0:
101
+ return float(100 - (100 / (1 + g_val/l_val)))
102
+ return 50.0
103
+ except:
104
  return 50.0
 
105
 
106
  def safe_ema(close, span):
107
+ try:
108
+ return float(close.ewm(span=span, adjust=False).mean().iloc[-1])
109
+ except:
110
+ return float(close.iloc[-1])
111
 
112
  def hurst_exponent(series, lags=20):
113
+ if len(series) < lags * 2:
114
+ return 0.5
115
  lags_range = range(2, min(lags, len(series)//2))
116
  tau = [np.std(np.subtract(series.values[lag:], series.values[:-lag])) for lag in lags_range]
117
+ try:
118
+ return float(np.polyfit(np.log(list(lags_range)), np.log(tau), 1)[0] * 2.0)
119
+ except:
120
+ return 0.5
121
 
122
  def smooth_confidence(current):
123
  global LAST_CONFIDENCE
 
130
  adx = features.get("adx", 20.0)
131
  volatility = features.get("volatility_1h", 0.0)
132
  hurst = features.get("hurst_exponent", 0.5)
133
+ if adx > 30 and hurst > 0.55:
134
+ return "TREND"
135
+ if volatility > 0.04:
136
+ return "VOLATILE"
137
  return "RANGE"
138
 
139
  # ================= ЗАГРУЗКА ДАННЫХ =================
 
143
  return DATA_CACHE[cache_key]["df"]
144
 
145
  try:
146
+ r = session.get(
147
+ f"{HUB_URL}/candles",
148
+ params={"symbol": symbol, "interval": tf, "limit": limit},
149
+ timeout=20,
150
+ headers=hub_headers()
151
+ )
152
  if r.status_code == 200:
153
  candles = r.json().get("candles", [])
154
  if candles:
155
  df = pd.DataFrame(candles)
156
+ if "o" in df.columns:
157
+ df.rename(columns={"o":"open","h":"high","l":"low","c":"close","v":"volume"}, inplace=True)
158
+ for col in ["open","high","low","close"]:
159
+ df[col] = pd.to_numeric(df[col], errors="coerce")
160
  df["volume"] = pd.to_numeric(df.get("volume", 0), errors="coerce").fillna(0)
161
  if len(df) >= 30:
162
  DATA_CACHE[cache_key] = {"df": df, "timestamp": time.time()}
163
  return df
164
  except Exception as e:
165
+ logger.warning(f"Hub {symbol} {tf}: {e}")
166
  return None
167
 
168
  def fetch_twelvedata_sol(tf="1h"):
169
+ if not TWELVE_DATA_KEY:
170
+ return None
171
  cache_key = f"td_sol_{tf}"
172
  if cache_key in DATA_CACHE and time.time() - DATA_CACHE[cache_key].get("timestamp", 0) < CACHE_TTL:
173
  return DATA_CACHE[cache_key]["df"]
 
177
  r = session.get(url, timeout=15)
178
  if r.status_code == 200 and "values" in r.json():
179
  df = pd.DataFrame(r.json()["values"]).iloc[::-1].reset_index(drop=True)
180
+ for col in ["close","high","low","open"]:
181
+ df[col] = pd.to_numeric(df[col], errors="coerce")
182
  df["volume"] = pd.to_numeric(df.get("volume", 0), errors="coerce").fillna(0)
183
  if len(df) >= 30:
184
  DATA_CACHE[cache_key] = {"df": df, "timestamp": time.time()}
185
  return df
186
+ except:
187
+ pass
188
  return None
189
 
190
  def fetch_ohlc(symbol, tf="1h"):
191
  df = fetch_ohlc_hub(symbol, tf)
192
  if df is None:
193
+ logger.info(f" 🔄 Хаб недоступен для {symbol} {tf}, пробую Twelve Data...")
194
  df = fetch_twelvedata_sol(tf)
195
  return df
196
 
197
  def fetch_solana_onchain():
198
+ """🔥 ФИКС: Правильные эндпоинты DefiLlama"""
199
  cache_key = "solana_onchain"
200
  if cache_key in DATA_CACHE and time.time() - DATA_CACHE[cache_key].get("timestamp", 0) < 300:
201
  return DATA_CACHE[cache_key]["data"]
202
+
203
  result = {}
204
+
205
+ # TVL — правильный эндпоинт
206
  try:
207
+ r = session.get("https://api.llama.fi/tvl/solana", timeout=10)
208
  if r.status_code == 200:
209
+ text = r.text.strip()
210
+ if text:
211
+ try:
212
+ data = r.json()
213
+ if isinstance(data, (int, float)):
214
+ result['tvl'] = float(data)
215
+ result['tvl_change_24h'] = 0
216
+ elif isinstance(data, dict):
217
+ result['tvl'] = data.get('tvl', 0)
218
+ result['tvl_change_24h'] = data.get('change_1d', 0)
219
+ except:
220
+ pass
221
+ if 'tvl' not in result:
222
+ result['tvl'] = 0
223
+ result['tvl_change_24h'] = 0
224
+ except Exception as e:
225
+ logger.warning(f"Solana TVL error: {e}")
226
+ result['tvl'] = 0
227
+ result['tvl_change_24h'] = 0
228
+
229
+ result['tvl_trend'] = 'UP' if result.get('tvl_change_24h', 0) > 0 else 'DOWN'
230
+
231
+ # DEX volumes — правильный эндпоинт
232
  try:
233
+ r = session.get(
234
+ "https://api.llama.fi/overview/dexs/solana?excludeTotalDataChart=true&excludeTotalDataChartBreakdown=true",
235
+ timeout=10
236
+ )
237
  if r.status_code == 200:
238
  data = r.json()
239
  result['dex_volume_24h'] = data.get('total24h', 0)
240
  result['dex_change_24h'] = data.get('change_1d', 0)
241
+ else:
242
+ result['dex_volume_24h'] = 0
243
+ result['dex_change_24h'] = 0
244
+ except Exception as e:
245
+ logger.warning(f"Solana DEX error: {e}")
246
+ result['dex_volume_24h'] = 0
247
+ result['dex_change_24h'] = 0
248
+
249
  DATA_CACHE[cache_key] = {"data": result, "timestamp": time.time()}
250
  return result
251
 
 
260
  result['funding_rate'] = fr
261
  result['funding_signal'] = 'BEARISH' if fr > 0.001 else 'BULLISH' if fr < -0.001 else 'NEUTRAL'
262
  break
263
+ except:
264
+ pass
265
  try:
266
  r = session.get("https://fapi.binance.com/fapi/v1/openInterest?symbol=SOLUSDT", timeout=10)
267
+ if r.status_code == 200:
268
+ result['open_interest'] = float(r.json().get('openInterest', 0))
269
+ except:
270
+ pass
271
  return result
272
 
273
+ # ================= ПОСТРОЕНИЕ ПРИЗНАКОВ =================
274
  def build_sol_features(df, onchain_data=None, derivatives=None):
275
+ if df is None or len(df) < 20:
276
+ return {}
277
 
278
  close = df["close"].astype(float)
279
  high = df["high"].astype(float)
 
308
 
309
  if len(close) >= 14:
310
  prev_close = close.shift(1)
311
+ tr = pd.DataFrame({
312
+ "tr1": high-low,
313
+ "tr2": (high-prev_close).abs(),
314
+ "tr3": (low-prev_close).abs()
315
+ }).max(axis=1)
316
  features["atr_14"] = safe_float(tr.rolling(14, min_periods=14).mean().iloc[-1])
317
  features["atr_pct"] = (features["atr_14"] / (close.iloc[-1] + 1e-10)) * 100
318
 
319
+ # ADX
320
+ features["adx"] = 20.0
321
+
322
  if onchain_data:
323
  features["tvl"] = onchain_data.get("tvl", 0)
324
  features["tvl_trend"] = 1 if onchain_data.get("tvl_trend") == "UP" else -1
 
334
  features["is_weekend"] = 1 if now.weekday() >= 5 else 0
335
  features["hour"] = now.hour
336
 
 
337
  return features
338
 
339
+ # ================= ОТПРАВКА СИГНАЛА (ИСПРАВЛЕНО) =================
340
+ def send_signal_to_hub(signal: str, confidence: float, features: Dict = None):
341
+ """🔥 ИСПРАВЛЕНО: signal вместо direction, space_id вместо space"""
342
+ if features is None:
343
+ features = {}
344
+
345
+ payload = {
346
+ "space_id": SPACE_ID,
347
+ "space_name": SPACE_NAME,
348
+ "symbol": SYMBOL,
349
+ "signal": signal, # ✅ BUY/SELL/WAIT
350
+ "confidence": round(confidence, 4),
351
+ "features": features,
352
+ "metadata": {"version": "10.2"},
353
+ "timestamp": datetime.now().isoformat()
354
+ }
355
+
356
+ for attempt in range(3):
357
+ try:
358
+ headers = hub_headers()
359
+ r = session.post(f"{HUB_URL}/signals", json=payload, timeout=15, headers=headers)
360
+ if r.status_code == 200:
361
+ logger.info(f"📤 {SYMBOL}: {signal} conf={confidence:.3f}")
362
+ return True
363
+ elif r.status_code == 429:
364
+ wait = 3 * (attempt + 1)
365
+ logger.warning(f"⏳ 429, жду {wait}с...")
366
+ time.sleep(wait)
367
+ else:
368
+ logger.warning(f"Попытка {attempt+1}: HTTP {r.status_code}")
369
+ time.sleep(2)
370
+ except Exception as e:
371
+ logger.warning(f"Попытка {attempt+1}: {e}")
372
+ time.sleep(2)
373
+
374
+ logger.error("❌ Не удалось отправить сигнал после 3 попыток")
375
+ return False
376
+
377
  # ================= СИГНАЛ =================
378
  def get_sol_signal():
379
  global LAST_CONFIDENCE
 
387
  df = fetch_ohlc(SYMBOL, tf)
388
  if df is not None and len(df) >= 30:
389
  feats = build_sol_features(df, onchain_data, binance_data)
390
+ if feats:
391
+ all_features[tf] = feats
392
 
393
  if not all_features:
394
+ send_signal_to_hub("WAIT", 0.0, {"reason": "no_data"})
395
  return None
396
 
397
  h1_features = all_features.get("1h", list(all_features.values())[0])
398
  price = h1_features.get("price", 0)
399
  if price == 0:
400
+ send_signal_to_hub("WAIT", 0.0, {"reason": "no_price"})
401
  return None
402
 
403
  regime = detect_market_regime(h1_features)
 
420
  probs.append(float(proba[1] if len(proba) > 1 else proba[0]))
421
  models_used += 1
422
  except Exception as e:
423
+ logger.warning(f" ⚠️ Ошибка {mk}: {e}")
424
 
425
  if MODELS.get("lgb"):
426
  try:
 
428
  lgb_prob = float(proba[1] if len(proba) > 1 else proba[0])
429
  xgb_prob = (sum(probs)/len(probs) * 0.6 + lgb_prob * 0.4) if probs else lgb_prob
430
  models_used += 1
431
+ except:
432
+ pass
433
  elif probs:
434
  xgb_prob = sum(probs) / len(probs)
435
  except Exception as e:
436
+ logger.warning(f" ⚠️ Ошибка предсказания: {e}")
437
 
438
  # Мульти-ТФ
439
  confirmations, total_tf = 0, 0
 
442
  ema_score = feats.get("price_vs_ema_21", 0)
443
  rsi_val = feats.get("rsi_14", 50)
444
  macd_hist = feats.get("macd_hist", 0)
445
+ if ema_score > 0 and rsi_val > 50 and macd_hist > 0:
446
+ confirmations += 1
447
+ elif ema_score < 0 and rsi_val < 50 and macd_hist < 0:
448
+ confirmations -= 1
449
  tf_norm = ((confirmations / max(total_tf, 1)) + 1) / 2
450
 
451
  # Meta SOL Score
 
460
  model_w = base["model"] * (COMPONENT_PERF["model"]["correct"] / max(COMPONENT_PERF["model"]["total"], 1)) * COMPONENT_PERF["model"]["sharpe"]
461
  tf_w = base["tf"] * (COMPONENT_PERF["tf"]["correct"] / max(COMPONENT_PERF["tf"]["total"], 1)) * COMPONENT_PERF["tf"]["sharpe"]
462
  remaining = 1.0 - (model_w + tf_w)
463
+ onchain_w = remaining * 0.6
464
+ deriv_w = remaining * 0.4
465
  total_w = model_w + tf_w + onchain_w + deriv_w
466
+ if total_w > 0:
467
+ model_w /= total_w
468
+ tf_w /= total_w
469
+ onchain_w /= total_w
470
+ deriv_w /= total_w
471
 
472
  final_score = xgb_prob * model_w + tf_norm * tf_w + meta_sol_score * onchain_w + deriv_norm * deriv_w
473
  confidence = smooth_confidence(final_score)
474
 
475
+ # 🔥 BUY/SELL вместо LONG/SHORT
476
+ if confidence > SOL_THRESHOLD + 0.08:
477
+ signal = "BUY"
478
+ elif confidence < SOL_THRESHOLD - 0.08:
479
+ signal = "SELL"
480
+ else:
481
+ signal = "WAIT"
482
 
483
+ features_out = {
484
+ "ml_prob": xgb_prob,
485
+ "tf_norm": tf_norm,
486
+ "meta_sol_score": meta_sol_score,
487
+ "regime": regime,
488
+ "models_used": models_used
489
+ }
490
+ send_signal_to_hub(signal, confidence, features_out)
491
 
492
+ logger.info(f"🥉 SOL/USD: {signal} | conf={confidence:.3f} | models={models_used} | regime={regime}")
493
+ return {"signal": signal, "confidence": confidence}
 
 
 
 
 
 
 
 
 
 
494
 
495
  def auto_report():
496
+ logger.info(f"⏳ Стартовый сон {STARTUP_SLEEP}с...")
497
  time.sleep(STARTUP_SLEEP)
498
+ logger.info("✅ SOL Master — начинаю авто-отправку!")
499
  while True:
500
  time.sleep(AUTO_REPORT_INTERVAL)
501
+ try:
502
+ get_sol_signal()
503
+ except Exception as e:
504
+ logger.error(f"Ошибка: {e}")
505
 
506
  threading.Thread(target=auto_report, daemon=True).start()
507
 
508
+ app = FastAPI(title="SOL Master v10.2")
509
 
510
  @app.get("/health")
511
  async def health():
512
+ return {
513
+ "space_id": SPACE_ID,
514
+ "status": "ok",
515
+ "version": "10.2",
516
+ "symbol": SYMBOL,
517
+ "models": sum(1 for m in MODELS.values() if m is not None)
518
+ }
519
+
520
+ @app.head("/health")
521
+ async def health_head():
522
+ return {}
523
 
524
  @app.get("/consilium")
525
  async def consilium():
526
  result = get_sol_signal()
527
+ if result:
528
+ return {"signal": result["signal"], "confidence": result["confidence"]}
529
+ return {"signal": "WAIT", "confidence": 0.0}
530
 
531
  @app.get("/")
532
+ async def root():
533
+ return {"name": "SOL Master v10.2", "space_id": SPACE_ID, "hub": HUB_URL}
534
 
535
  if __name__ == "__main__":
536
  import uvicorn
537
  uvicorn.run(app, host="0.0.0.0", port=7860)
538
 
539
+ print("🚀 SPACE 19 v10.2 — SOL/USD MASTER ГОТОВ К РАБОТЕ!")