tomirisai80 commited on
Commit
eb452cb
·
verified ·
1 Parent(s): 5b4220e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +254 -525
app.py CHANGED
@@ -1,5 +1,8 @@
1
  # ============================================
2
- # 👑 TOMIRIS SPACE 19 v9.1 — SOL/USD MASTER (Hub‑Connected + Auto‑Retrain + Trigger)
 
 
 
3
  # ============================================
4
  import os, time, threading, warnings, json, asyncio
5
  from typing import Dict, Any, Optional, List, Tuple
@@ -13,7 +16,6 @@ warnings.filterwarnings('ignore')
13
  # ================= БЕЗОПАСНЫЙ ИМПОРТ =================
14
  HAS_JOBLIB = False
15
  HAS_FIREBASE = False
16
- HAS_YFINANCE = False
17
 
18
  try:
19
  import joblib
@@ -28,15 +30,9 @@ try:
28
  except:
29
  print("⚠️ firebase_admin не установлен")
30
 
31
- try:
32
- import yfinance as yf
33
- HAS_YFINANCE = True
34
- except:
35
- print("⚠️ yfinance не установлен")
36
-
37
- # ================= FIREBASE =================
38
  db = None
39
- if HAS_FIREBASE:
40
  try:
41
  cred = credentials.Certificate("firebase-key.json")
42
  firebase_admin.initialize_app(cred)
@@ -45,13 +41,16 @@ if HAS_FIREBASE:
45
  except Exception as e:
46
  print(f"⚠️ Firebase: {e}")
47
 
48
- # ================= URL'ы СМЕЖНЫХ SPACE'ов =================
49
- SPACE_URLS: Dict[str, str] = {
50
- "space_17_hub": os.getenv("SPACE17_URL", "https://tomiris-ai-name5-5.hf.space"),
51
- "space_18_arbiter": os.getenv("SPACE18_URL", "https://tomiris-ai-name6-6.hf.space"),
52
- }
 
53
 
54
- HUB_URL = SPACE_URLS["space_17_hub"]
 
 
55
 
56
  # ================= API КЛЮЧИ =================
57
  TWELVE_KEYS: List[str] = [
@@ -61,47 +60,32 @@ TWELVE_KEYS: List[str] = [
61
 
62
  # ================= КОНФИГУРАЦИЯ =================
63
  SYMBOL: str = "SOL/USD"
64
- MT5_SYMBOL: str = "SOLUSD"
65
  TIMEFRAMES: List[str] = ["15min", "1h", "4h"]
66
- AUTO_REPORT_INTERVAL = 300 # секунд между авто‑отправками
67
  RETRAIN_INTERVAL = 14 * 86400 # 14 дней
68
 
69
- try:
70
- with open("best_config.json", "r") as f:
71
- config = json.load(f)
72
- SOL_THRESHOLD: float = config.get("sol", {}).get("threshold", 0.52)
73
- TRADING_RULES: Dict[str, Any] = config.get("trading_rules", {})
74
- except:
75
- SOL_THRESHOLD: float = 0.52
76
- TRADING_RULES: Dict[str, Any] = {
77
- "sl_atr_multiplier": 2.0,
78
- "tp_atr_multiplier": 4.0,
79
- "trailing_stop_activation": 0.005,
80
- "trailing_stop_distance": 0.003,
81
- "breakeven_at": 0.005
82
- }
83
 
84
- CACHE_TTL: int = 900
85
- MT5_MAX_AGE_SEC: int = 300
86
- HUB_CACHE_TTL: float = 5.0
87
 
88
- # Адаптивные веса (начальные)
89
  REGIME_WEIGHTS: Dict[str, Dict[str, float]] = {
90
  "TREND": {"model": 0.70, "tf": 0.30},
91
  "VOLATILE": {"model": 0.50, "tf": 0.50},
92
- "CONGESTED": {"model": 0.45, "tf": 0.55},
93
  "RANGE": {"model": 0.60, "tf": 0.40}
94
  }
95
 
96
- # Хранилища
97
- FEATURES_STORE: Dict[str, Any] = {}
98
  DATA_CACHE: Dict[str, Dict[str, Any]] = {}
99
  PREDICTION_HISTORY = deque(maxlen=500)
100
- HUB_CACHE: Dict[str, Any] = {"price": 0.0, "timestamp": 0.0, "fresh": False}
101
  CIRCUIT_BREAKERS: Dict[str, Dict[str, int]] = {}
102
  LAST_CONFIDENCE: float = 0.5
103
 
104
- # История точности компонентов
105
  COMPONENT_PERF: Dict[str, Dict[str, float]] = {
106
  "model": {"correct": 0, "total": 1, "sharpe": 1.0},
107
  "tf": {"correct": 0, "total": 1, "sharpe": 1.0},
@@ -109,43 +93,26 @@ COMPONENT_PERF: Dict[str, Dict[str, float]] = {
109
  "derivatives": {"correct": 0, "total": 1, "sharpe": 1.0},
110
  }
111
 
112
- YAHOO_INTERVAL_MAP: Dict[str, str] = {"15min": "15m", "1h": "60m", "4h": "4h"}
113
-
114
  # ================= ЗАГРУЗКА МОДЕЛЕЙ =================
115
- print(f"🔥 SPACE 19 v9.1: Загрузка моделей для {SYMBOL}...")
116
  MODELS: Dict[str, Optional[Any]] = {"xgb_daily": None, "xgb_4h": None, "lgb": None}
117
 
118
  if HAS_JOBLIB:
119
- try:
120
- MODELS["xgb_daily"] = joblib.load("xgboost_sol_daily.joblib")
121
- print(" XGBoost SOL daily загружен")
122
- except Exception as e:
123
- print(f"⚠️ XGBoost SOL daily: {e}")
124
- try:
125
- MODELS["xgb_4h"] = joblib.load("xgboost_sol_4h.joblib")
126
- print("✅ XGBoost SOL 4h загружен")
127
- except Exception as e:
128
- print(f"⚠️ XGBoost SOL 4h: {e}")
129
- try:
130
- MODELS["lgb"] = joblib.load("lgb_sol.joblib")
131
- print("✅ LightGBM SOL загружен")
132
- except:
133
- print("⚠️ LightGBM SOL не найден")
134
-
135
- # ================= УТИЛИТЫ =================
136
- api_lock = threading.Lock()
137
- twelve_counter: int = 0
138
-
139
- def get_next_twelve_key() -> str:
140
- global twelve_counter
141
- with api_lock:
142
- key = TWELVE_KEYS[twelve_counter % len(TWELVE_KEYS)]
143
- twelve_counter += 1
144
- return key
145
-
146
  session = requests.Session()
147
- session.headers.update({"User-Agent": "Tomiris-Space19-v9.1"})
148
 
 
149
  def safe_float(value: Any, default: float = 0.0) -> float:
150
  try:
151
  if isinstance(value, (pd.Series, pd.DataFrame)):
@@ -178,9 +145,9 @@ def safe_ema(close_series: pd.Series, span: int) -> Tuple[Optional[pd.Series], f
178
  return None, 0.0
179
 
180
  class KalmanFilter:
181
- def __init__(self, process_noise: float = 1e-5, measurement_noise: float = 1e-4):
182
- self.q = process_noise
183
- self.r = measurement_noise
184
  self.x = 0.0
185
  self.p = 1.0
186
  def update(self, z: float) -> float:
@@ -201,63 +168,6 @@ def hurst_exponent(series: pd.Series, lags: int = 20) -> float:
201
  except:
202
  return 0.5
203
 
204
- def fetch_google_trends_index(keyword: str) -> float:
205
- return 50.0
206
-
207
- # ================= ОТПРАВКА В HUB =================
208
- def send_signal_to_hub(direction, confidence):
209
- try:
210
- session.post(f"{HUB_URL}/signal", json={
211
- "space": "space_19_sol_master",
212
- "symbol": SYMBOL,
213
- "direction": direction,
214
- "confidence": confidence,
215
- "raw": json.dumps({"source": "space_19_sol_master"})
216
- }, timeout=5)
217
- print(f"📤 {SYMBOL}: {direction} conf={confidence:.3f} отправлен в Hub")
218
- except Exception as e:
219
- print(f"Ошибка отправки в Hub: {e}")
220
-
221
- # ================= DATA HUB =================
222
- def get_mt5_price_from_hub() -> Dict[str, Any]:
223
- global HUB_CACHE
224
- if time.time() - HUB_CACHE.get("timestamp", 0) < HUB_CACHE_TTL:
225
- if HUB_CACHE.get("fresh"):
226
- return HUB_CACHE
227
- try:
228
- r = requests.get(f"{SPACE_URLS['space_17_hub']}/price/{SYMBOL}", timeout=3)
229
- if r.status_code == 200:
230
- data = r.json()
231
- fresh = data.get("fresh", False)
232
- mid = data.get("mid", 0)
233
- if fresh and mid > 0:
234
- HUB_CACHE = {
235
- "price": mid, "bid": data.get("bid",0), "ask": data.get("ask",0),
236
- "spread_pct": data.get("spread_pct",0), "timestamp": time.time(),
237
- "fresh": True, "source": "MT5_LIVE"
238
- }
239
- return HUB_CACHE
240
- except:
241
- pass
242
- return {"price":0.0, "timestamp":time.time(), "fresh":False, "source":"UNAVAILABLE"}
243
-
244
- # ================= CIRCUIT BREAKER =================
245
- def breaker_open(name: str) -> bool:
246
- info = CIRCUIT_BREAKERS.get(name)
247
- if not info: return False
248
- if info["fails"] < 5: return False
249
- if time.time() - info["last_fail"] > 300:
250
- CIRCUIT_BREAKERS[name] = {"fails":0, "last_fail":0}
251
- return False
252
- return True
253
-
254
- def breaker_fail(name: str) -> None:
255
- info = CIRCUIT_BREAKERS.get(name, {"fails":0, "last_fail":0})
256
- info["fails"] += 1
257
- info["last_fail"] = time.time()
258
- CIRCUIT_BREAKERS[name] = info
259
-
260
- # ================= СГЛАЖИВАНИЕ УВЕРЕННОСТИ =================
261
  def smooth_confidence(current: float) -> float:
262
  global LAST_CONFIDENCE
263
  current = max(0.0, min(1.0, current))
@@ -282,76 +192,127 @@ def stress_test(features: Dict[str, Any]) -> Optional[str]:
282
  return "WAIT"
283
  return None
284
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  # ================= ЗАГРУЗКА ДАННЫХ =================
286
- def fetch_twelvedata_sol(tf: str = "1h") -> Tuple[Optional[pd.DataFrame], Optional[str]]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  cache_key = f"td_sol_{tf}"
288
  if cache_key in DATA_CACHE:
289
- age = time.time() - DATA_CACHE[cache_key].get("timestamp",0)
290
  if age < CACHE_TTL:
291
- return DATA_CACHE[cache_key]["df"], DATA_CACHE[cache_key]["source"]
292
- for key_idx, key in enumerate(TWELVE_KEYS):
 
293
  try:
294
  url = f"https://api.twelvedata.com/time_series?symbol=SOL/USD&interval={tf}&outputsize=200&apikey={key}"
295
  r = session.get(url, timeout=10)
296
- if r.status_code == 200:
297
- data = r.json()
298
- if "values" in data:
299
- df = pd.DataFrame(data["values"]).iloc[::-1].reset_index(drop=True)
300
- for col in ["close","high","low","open"]:
301
- df[col] = pd.to_numeric(df[col], errors="coerce")
302
- df["volume"] = pd.to_numeric(df.get("volume",0), errors="coerce").fillna(0)
303
- df = df.dropna(subset=["close","high","low","open"])
304
- if len(df) >= 30:
305
- DATA_CACHE[cache_key] = {
306
- "df":df,
307
- "source":f"TwelveData-Key{key_idx+1}",
308
- "timestamp":time.time()
309
- }
310
- return df, f"TwelveData-Key{key_idx+1}"
311
- elif r.status_code == 429:
312
- continue
313
- except:
314
- continue
315
- if HAS_YFINANCE:
316
- try:
317
- yf_interval = YAHOO_INTERVAL_MAP.get(tf, "60m")
318
- period_map = {"15min":"7d", "1h":"60d", "4h":"60d"}
319
- yf_data = yf.download("SOL-USD", period=period_map.get(tf,"60d"), interval=yf_interval, progress=False)
320
- if not yf_data.empty:
321
- df = pd.DataFrame({
322
- 'close': yf_data['Close'].values.flatten(),
323
- 'high': yf_data['High'].values.flatten(),
324
- 'low': yf_data['Low'].values.flatten(),
325
- 'open': yf_data['Open'].values.flatten(),
326
- 'volume': yf_data['Volume'].values.flatten()
327
- }).dropna()
328
  if len(df) >= 30:
329
- DATA_CACHE[cache_key] = {"df":df, "source":"YahooFinance", "timestamp":time.time()}
330
- return df, "YahooFinance"
331
  except:
332
- pass
333
- return None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
 
335
  def fetch_solana_onchain() -> Dict[str, Any]:
336
  cache_key = "solana_onchain"
337
- if cache_key in DATA_CACHE and time.time() - DATA_CACHE[cache_key].get("timestamp",0) < 300:
338
  return DATA_CACHE[cache_key]["data"]
339
  result = {}
340
  try:
341
  r = session.get("https://api.llama.fi/v2/tvl/solana", timeout=10)
342
  if r.status_code == 200:
343
  data = r.json()
344
- result['tvl'] = data.get('tvl',0)
345
- result['tvl_change_24h'] = data.get('change_1d',0)
346
- result['tvl_trend'] = 'UP' if data.get('change_1d',0) > 0 else 'DOWN'
347
  except:
348
  pass
349
  try:
350
  r = session.get("https://api.llama.fi/overview/dexs/solana?excludeTotalDataChart=true&excludeTotalDataChartBreakdown=true", timeout=10)
351
  if r.status_code == 200:
352
  data = r.json()
353
- result['dex_volume_24h'] = data.get('total24h',0)
354
- result['dex_change_24h'] = data.get('change_1d',0)
355
  except:
356
  pass
357
  result['active_users'] = result.get('tvl', 0) / 100
@@ -365,7 +326,7 @@ def fetch_solana_dev_activity() -> Dict[str, Any]:
365
  dev = r.json().get('developer_data', {})
366
  return {
367
  'developer_score': dev.get('developer_score', 0),
368
- 'developer_activity': 'HIGH' if dev.get('developer_score',0) > 80 else 'MODERATE' if dev.get('developer_score',0) > 50 else 'LOW'
369
  }
370
  except:
371
  pass
@@ -378,7 +339,7 @@ def fetch_binance_sol() -> Dict[str, Any]:
378
  if r.status_code == 200:
379
  for item in r.json():
380
  if item.get('symbol') == 'SOLUSDT':
381
- fr = float(item.get('lastFundingRate',0))
382
  result['funding_rate'] = fr
383
  result['funding_signal'] = 'BEARISH' if fr > 0.001 else 'BULLISH' if fr < -0.001 else 'NEUTRAL'
384
  break
@@ -387,7 +348,7 @@ def fetch_binance_sol() -> Dict[str, Any]:
387
  try:
388
  r = session.get("https://fapi.binance.com/fapi/v1/openInterest?symbol=SOLUSDT", timeout=10)
389
  if r.status_code == 200:
390
- result['open_interest'] = float(r.json().get('openInterest',0))
391
  except:
392
  pass
393
  return result
@@ -398,56 +359,24 @@ def fetch_coingecko_sol() -> Dict[str, Any]:
398
  if r.status_code == 200:
399
  md = r.json().get('market_data', {})
400
  return {
401
- 'market_cap': md.get('market_cap',{}).get('usd',0),
402
- 'total_volume': md.get('total_volume',{}).get('usd',0),
403
- 'price_change_24h': md.get('price_change_percentage_24h',0)
404
  }
405
  except:
406
  pass
407
- return {'market_cap':0, 'total_volume':0, 'price_change_24h':0}
408
-
409
- def fetch_space_signal(name: str, url: str, endpoint: str = "/consilium") -> Dict[str, Any]:
410
- if breaker_open(name):
411
- return {"active": False, "reason": "circuit_breaker"}
412
- try:
413
- r = session.get(f"{url}{endpoint}", timeout=8)
414
- if r.status_code == 200:
415
- return {"active": True, "data": r.json()}
416
- else:
417
- breaker_fail(name)
418
- return {"active": False, "reason": f"status_{r.status_code}"}
419
- except Exception as e:
420
- breaker_fail(name)
421
- return {"active": False, "reason": str(e)[:50]}
422
 
423
  # ================= ПОСТРОЕНИЕ ПРИЗНАКОВ =================
424
- def build_features_from_mt5(mt5_features: Dict[str, Any]) -> Dict[str, Any]:
425
- features: Dict[str, Any] = {}
426
- for k, v in mt5_features.items():
427
- if isinstance(v, (int, float, np.floating, np.integer)):
428
- features[k] = float(v)
429
- elif isinstance(v, np.bool_):
430
- features[k] = bool(v)
431
- else:
432
- features[k] = v
433
- while len(features) < 200:
434
- features[f"mt5_pad_{len(features)}"] = 0.0
435
- return features
436
-
437
  def build_sol_features(df: pd.DataFrame, onchain_data: Optional[Dict[str, Any]] = None,
438
  dev_data: Optional[Dict[str, Any]] = None,
439
  derivatives: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
440
  if df is None or len(df) < 20:
441
  return {}
442
 
443
- try:
444
- close = df["close"].astype(float)
445
- high = df["high"].astype(float)
446
- low = df["low"].astype(float)
447
- open_p = df["open"].astype(float) if "open" in df.columns else close
448
- volume = df["volume"].astype(float) if "volume" in df.columns else pd.Series([0.0]*len(df))
449
- except:
450
- return {}
451
 
452
  features: Dict[str, Any] = {}
453
  features["price"] = safe_float(close.iloc[-1])
@@ -506,221 +435,130 @@ def build_sol_features(df: pd.DataFrame, onchain_data: Optional[Dict[str, Any]]
506
  features["active_users"] = onchain_data.get("active_users", 0)
507
  if dev_data:
508
  features["developer_score"] = dev_data.get("developer_score", 50)
509
- features["developer_activity"] = dev_data.get("developer_activity", "MODERATE")
510
  if derivatives:
511
  features["funding_rate"] = derivatives.get("funding_rate", 0)
512
  features["open_interest"] = derivatives.get("open_interest", 0)
513
  features["funding_bullish"] = 1 if derivatives.get("funding_signal") == "BULLISH" else 0
514
  features["funding_bearish"] = 1 if derivatives.get("funding_signal") == "BEARISH" else 0
515
 
516
- for kw in ["solana", "memecoin", "pump_fun", "firedancer"]:
517
- features[f"trends_{kw}"] = fetch_google_trends_index(kw)
518
-
519
  now = datetime.utcnow()
520
  features["is_weekend"] = 1 if now.weekday() >= 5 else 0
521
  features["hour"] = now.hour
522
 
523
- while len(features) < 200:
524
- features[f"pad_{len(features)}"] = 0.0
525
-
526
  return features
527
 
528
- def get_multi_tf_features(onchain_data: Dict[str, Any], dev_data: Dict[str, Any],
529
- derivatives: Dict[str, Any]) -> Tuple[Dict[str, Dict[str, Any]], List[str]]:
530
- all_features: Dict[str, Dict[str, Any]] = {}
531
- sources: List[str] = []
532
- for tf in TIMEFRAMES:
533
- df, source = fetch_twelvedata_sol(tf)
534
- if df is not None and len(df) >= 30:
535
- feats = build_sol_features(df, onchain_data, dev_data, derivatives)
536
- if feats:
537
- all_features[tf] = feats
538
- sources.append(source or "Unknown")
539
- return all_features, sources
540
-
541
  # ================= ДИНАМИЧЕСКИЕ ВЕСА =================
542
- def update_component_perf(component: str, success: bool):
543
- c = COMPONENT_PERF[component]
544
- c["total"] += 1
545
- if success:
546
- c["correct"] += 1
547
- if success:
548
- c["sharpe"] = min(3.0, c["sharpe"] + 0.1)
549
- else:
550
- c["sharpe"] = max(0.1, c["sharpe"] - 0.1)
551
-
552
  def get_dynamic_component_weights(regime: str) -> Dict[str, float]:
553
  base = REGIME_WEIGHTS.get(regime, REGIME_WEIGHTS["RANGE"])
554
  model_acc = COMPONENT_PERF["model"]["correct"] / max(COMPONENT_PERF["model"]["total"], 1)
555
  model_sharpe = COMPONENT_PERF["model"]["sharpe"]
556
  model_w = base["model"] * model_acc * model_sharpe
557
-
558
  tf_acc = COMPONENT_PERF["tf"]["correct"] / max(COMPONENT_PERF["tf"]["total"], 1)
559
  tf_sharpe = COMPONENT_PERF["tf"]["sharpe"]
560
  tf_w = base["tf"] * tf_acc * tf_sharpe
561
-
562
  remaining = 1.0 - (model_w + tf_w)
563
  onchain_w = remaining * 0.6
564
  deriv_w = remaining * 0.4
565
-
566
  weights = {"model": model_w, "tf": tf_w, "onchain": onchain_w, "derivatives": deriv_w}
567
  norm = sum(weights.values())
568
  if norm > 0:
569
  weights = {k: v/norm for k, v in weights.items()}
570
  return weights
571
 
572
- # ================= ГЛАВНЫЙ СИГНАЛ (АНСАМБЛЬ С DYNAMIC WEIGHTS) =================
573
  def get_sol_signal() -> Optional[Dict[str, Any]]:
574
  global LAST_CONFIDENCE
575
  start = time.time()
576
 
577
  onchain_data = fetch_solana_onchain()
578
  dev_data = fetch_solana_dev_activity()
579
- coingecko = fetch_coingecko_sol()
580
  binance_data = fetch_binance_sol()
581
 
582
- mt5_features: Optional[Dict[str, Any]] = None
583
- mt5_price: Optional[float] = None
584
- data_source: str = "UNKNOWN"
585
-
586
- fs = FEATURES_STORE.get(SYMBOL, {})
587
- age = time.time() - fs.get("timestamp", 0)
588
- if age < MT5_MAX_AGE_SEC:
589
- mt5_features = fs.get("features", {})
590
- mt5_price = fs.get("price")
591
- data_source = "MT5"
592
- print(f"📡 Используем MT5 данные озраст {age:.0f}с)")
593
-
594
- mtf_features: Dict[str, Dict[str, Any]] = {}
595
- sources: List[str] = []
596
-
597
- if mt5_features and len(mt5_features) >= 50:
598
- model_features = build_features_from_mt5(mt5_features)
599
- model_features["tvl"] = onchain_data.get("tvl", 0)
600
- model_features["tvl_trend"] = 1 if onchain_data.get("tvl_trend") == "UP" else -1
601
- model_features["active_users"] = onchain_data.get("active_users", 0)
602
- model_features["developer_score"] = dev_data.get("developer_score", 50)
603
- model_features["funding_rate"] = binance_data.get("funding_rate", 0)
604
- model_features["open_interest"] = binance_data.get("open_interest", 0)
605
- model_features["funding_bullish"] = 1 if binance_data.get("funding_signal") == "BULLISH" else 0
606
- model_features["funding_bearish"] = 1 if binance_data.get("funding_signal") == "BEARISH" else 0
607
- price = mt5_price or model_features.get("H1_price", model_features.get("price", 0))
608
- sources = ["MT5"]
609
- else:
610
- print(" ⚠️ MT5 данные недоступны, перехожу на API...")
611
- mtf_features, sources = get_multi_tf_features(onchain_data, dev_data, binance_data)
612
- if not mtf_features:
613
- print("❌ Нет данных")
614
- return None
615
- h1_features = mtf_features.get("1h", list(mtf_features.values())[0])
616
- model_features = h1_features
617
- price = h1_features.get("price", 0)
618
- data_source = "+".join(sources) if sources else "API"
619
 
620
  if price == 0:
621
  return None
622
 
623
- stress = stress_test(model_features)
 
624
  if stress == "WAIT":
625
  print("🛑 СТРЕСС-ТЕСТ: рынок слишком опасен")
626
- result = {
 
627
  "space": "space_19_sol_master",
628
  "symbol": SYMBOL,
629
  "signal": {"direction": "WAIT", "confidence": 0.0},
630
- "reason": "stress_test_black_swan"
631
  }
632
- send_signal_to_hub("WAIT", 0.0)
633
- return result
634
 
635
- regime = detect_market_regime(model_features)
636
- print(f"📊 Режим: {regime} | Цена: ${price:.2f} | TVL: ${onchain_data.get('tvl',0)/1e6:.1f}M | Данные: {data_source}")
637
 
638
- # === ПРЕДСКАЗАНИЕ МОДЕЛИ (АНСАМБЛЬ daily + 4h) ===
639
  xgb_prob = 0.5
640
  models_used = 0
641
- if model_features:
642
- try:
643
- fv = list(model_features.values())[:200]
644
- while len(fv) < 200:
645
- fv.append(0.0)
646
- X = np.nan_to_num(np.array(fv, dtype=np.float64).reshape(1, -1))
647
-
648
- probs = []
649
- if MODELS.get("xgb_daily"):
650
- proba = MODELS["xgb_daily"].predict_proba(X)[0]
651
- probs.append(float(proba[1] if len(proba) > 1 else proba[0]))
652
- models_used += 1
653
- if MODELS.get("xgb_4h"):
654
- proba = MODELS["xgb_4h"].predict_proba(X)[0]
655
  probs.append(float(proba[1] if len(proba) > 1 else proba[0]))
656
  models_used += 1
657
- if MODELS.get("lgb"):
658
- proba = MODELS["lgb"].predict_proba(X)[0]
659
- lgb_prob = float(proba[1] if len(proba) > 1 else proba[0])
660
- if probs:
661
- xgb_prob = sum(probs)/len(probs) * 0.6 + lgb_prob * 0.4
662
- else:
663
- xgb_prob = lgb_prob
664
- models_used += 1
665
- elif probs:
666
- xgb_prob = sum(probs) / len(probs)
667
- xgb_prob = max(0.0, min(1.0, xgb_prob))
668
- except Exception as e:
669
- print(f" ⚠️ Ошибка предсказания: {e}")
670
 
671
  # Мульти-ТФ подтверждение
672
- if data_source == "MT5":
673
- confirmations, total_tf = 0, 0
674
- for tf_key in ["M15", "H1", "H4"]:
675
- ema_key = f"{tf_key}_price_vs_ema_21"
676
- if ema_key in model_features:
677
- total_tf += 1
678
- if model_features.get(ema_key, 0) > 0:
679
- confirmations += 1
680
- else:
681
- confirmations -= 1
682
- tf_score = confirmations / max(total_tf, 1)
683
- tf_norm = (tf_score + 1) / 2
684
- else:
685
- confirmations, total_tf = 0, 0
686
- for tf_key in ["15min", "1h", "4h"]:
687
- tf_feats = mtf_features.get(tf_key, {})
688
- if not tf_feats:
689
- continue
690
- total_tf += 1
691
- ema_score = tf_feats.get("price_vs_ema_21", 0)
692
- rsi_val = tf_feats.get("rsi_14", 50)
693
- macd_hist = tf_feats.get("macd_hist", 0)
694
- if ema_score > 0 and rsi_val > 50 and macd_hist > 0:
695
- confirmations += 1
696
- elif ema_score < 0 and rsi_val < 50 and macd_hist < 0:
697
- confirmations -= 1
698
- tf_score = confirmations / max(total_tf, 1)
699
- tf_norm = (tf_score + 1) / 2
700
-
701
- # Meta SOL Score: TVL, DEX Volume, Active Addresses, Developer Activity
702
- tvl_norm = 0.5
703
- if onchain_data.get("tvl", 0) > 0:
704
- tvl_norm = min(1.0, max(0.0, 0.5 + onchain_data.get("tvl_change_24h", 0) / 20))
705
- dex_norm = 0.5
706
- if onchain_data.get("dex_volume_24h", 0) > 0:
707
- dex_norm = min(1.0, max(0.0, 0.5 + onchain_data.get("dex_change_24h", 0) / 20))
708
- dev_norm = 0.5
709
- if dev_data.get("developer_score", 50) > 50:
710
- dev_norm = 0.7
711
- active_norm = 0.5
712
- meta_sol_score = (tvl_norm + dex_norm + dev_norm + active_norm) / 4
713
-
714
- # Компонент деривативов
715
- deriv_norm = 0.5
716
- if binance_data.get("funding_signal") == "BULLISH":
717
- deriv_norm = 0.7
718
- elif binance_data.get("funding_signal") == "BEARISH":
719
- deriv_norm = 0.3
720
 
721
  # Динамические веса
722
  comp_weights = get_dynamic_component_weights(regime)
723
- print(f" ⚖️ Веса: {comp_weights}")
724
 
725
  final_score = (
726
  xgb_prob * comp_weights["model"] +
@@ -738,19 +576,11 @@ def get_sol_signal() -> Optional[Dict[str, Any]]:
738
  direction = "WAIT"
739
 
740
  # SL/TP
741
- atr = model_features.get("atr_14", price * 0.02)
742
- sl_mult = TRADING_RULES.get("sl_atr_multiplier", 2.0)
743
- tp_mult = TRADING_RULES.get("tp_atr_multiplier", 4.0)
744
- sl_dist = atr * sl_mult
745
- tp_dist = atr * tp_mult
746
- if direction == "LONG":
747
- sl = round(price - sl_dist, 2)
748
- tp = round(price + tp_dist, 2)
749
- elif direction == "SHORT":
750
- sl = round(price + sl_dist, 2)
751
- tp = round(price - tp_dist, 2)
752
- else:
753
- sl = tp = 0
754
 
755
  PREDICTION_HISTORY.append({
756
  "timestamp": datetime.utcnow().isoformat(),
@@ -759,59 +589,39 @@ def get_sol_signal() -> Optional[Dict[str, Any]]:
759
  "price": price
760
  })
761
 
762
- if db:
763
- try:
764
- db.collection("space19_sol_signals").add({
765
- "direction": direction,
766
- "confidence": confidence,
767
- "price": price,
768
- "regime": regime,
769
- "data_source": data_source,
770
- "timestamp": firestore.SERVER_TIMESTAMP
771
- })
772
- except:
773
- pass
774
 
775
  latency = int((time.time() - start) * 1000)
776
  result = {
777
  "space": "space_19_sol_master",
778
  "timestamp": int(time.time()),
779
  "symbol": SYMBOL,
780
- "signal": {
781
- "direction": direction,
782
- "confidence": round(confidence, 4),
783
- "strength": round(confidence * (1 + abs(tf_score)), 4)
784
- },
785
  "analysis": {
786
  "xgb_probability": round(xgb_prob, 4),
787
  "models_used": models_used,
788
  "multi_tf_score": round(tf_score, 4),
789
  "meta_sol_score": round(meta_sol_score, 4),
790
  "market_regime": regime,
791
- "data_source": data_source
792
  },
793
  "onchain": {
794
  "tvl": onchain_data.get("tvl", 0),
795
  "dex_volume_24h": onchain_data.get("dex_volume_24h", 0),
796
- "active_users": onchain_data.get("active_users", 0),
797
- "developer_score": dev_data.get("developer_score", 50),
798
  "funding_rate": binance_data.get("funding_rate", 0)
799
  },
800
- "risk": {"sl": sl, "tp": tp, "rr": round(tp_dist/(sl_dist+1e-10), 2) if direction != "WAIT" else 0},
801
- "meta": {
802
- "latency_ms": latency,
803
- "model_version": "v9.1_auto_retrain",
804
- "features_used": len(model_features) if model_features else 0,
805
- "dynamic_weights": comp_weights
806
- }
807
  }
808
 
809
- send_signal_to_hub(result["signal"]["direction"], result["signal"]["confidence"])
810
- print(f"🥉 SOL/USD: {direction} | conf={confidence:.3f} | ensemble={xgb_prob:.3f} | models={models_used} | regime={regime} | latency={latency}ms")
811
  return result
812
 
813
  # ================= АВТО-ОТПРАВКА =================
814
  def auto_report():
 
 
 
815
  while True:
816
  time.sleep(AUTO_REPORT_INTERVAL)
817
  try:
@@ -822,76 +632,18 @@ def auto_report():
822
  # ================= АВТО-ДООБУЧЕНИЕ =================
823
  def retrain_models():
824
  print("🔄 Запуск дообучения моделей SOL...")
825
- try:
826
- import joblib
827
- df = None
828
- if HAS_YFINANCE:
829
- yf_data = yf.download("SOL-USD", period="1y", interval="1d", progress=False)
830
- if not yf_data.empty:
831
- df = pd.DataFrame({
832
- 'close': yf_data['Close'].values.flatten(),
833
- 'high': yf_data['High'].values.flatten(),
834
- 'low': yf_data['Low'].values.flatten(),
835
- 'open': yf_data['Open'].values.flatten(),
836
- 'volume': yf_data['Volume'].values.flatten()
837
- }).dropna()
838
- if df is None or len(df) < 200:
839
- print("Недостаточно данных для дообучения")
840
- return
841
-
842
- features_list = []
843
- targets = []
844
- for i in range(100, len(df)-24):
845
- sub_df = df.iloc[:i+1]
846
- feats = build_sol_features(sub_df)
847
- if not feats:
848
- continue
849
- features_list.append(feats)
850
- target = 1 if df["close"].iloc[i+24] > df["close"].iloc[i] else 0
851
- targets.append(target)
852
-
853
- if not features_list:
854
- return
855
- X = np.array([list(f.values())[:200] + [0.0]*(200 - len(f)) for f in features_list])
856
- y = np.array(targets)
857
-
858
- for model_name in ["xgb_daily", "xgb_4h"]:
859
- model = MODELS.get(model_name)
860
- if model and hasattr(model, 'fit'):
861
- model.fit(X, y)
862
- joblib.dump(model, f"{model_name}_retrained.joblib")
863
- print(f"✅ {model_name} дообучена")
864
- if MODELS.get("lgb") and hasattr(MODELS["lgb"], 'fit'):
865
- MODELS["lgb"].fit(X, y)
866
- joblib.dump(MODELS["lgb"], "lgb_retrained.joblib")
867
- print("✅ LightGBM дообучена")
868
- except Exception as e:
869
- print(f"Ошибка дообуче��ия: {e}")
870
 
871
  def auto_retrain():
 
872
  while True:
873
- # Проверяем винрейт каждый час
874
- time.sleep(3600)
875
- try:
876
- resp = session.get(f"{HUB_URL}/metrics", params={"space": "space_19_sol"}, timeout=10)
877
- if resp.status_code == 200:
878
- data = resp.json()
879
- if data and len(data) > 0:
880
- acc = data[0].get("accuracy", 0.5)
881
- total = data[0].get("total_trades", 0)
882
- if total >= 20 and acc < 0.4:
883
- print(f"Точность SOL Master упала до {acc:.2f}, экстренное дообучение!")
884
- retrain_models()
885
- time.sleep(RETRAIN_INTERVAL)
886
- continue
887
- except Exception as e:
888
- print(f"Ошибка проверки винрейта: {e}")
889
- # Ждём оставшееся время до планового дообучения
890
- time.sleep(RETRAIN_INTERVAL - 3600)
891
  retrain_models()
892
 
893
  # ================= KEEP-ALIVE =================
894
  def keep_alive():
 
895
  while True:
896
  time.sleep(840)
897
  try:
@@ -904,30 +656,27 @@ threading.Thread(target=auto_report, daemon=True).start()
904
  threading.Thread(target=auto_retrain, daemon=True).start()
905
 
906
  # ================= FASTAPI =================
907
- app = FastAPI(title="TOMIRIS SOL/USD MASTER v9.1 Auto-Retrain + Trigger")
908
 
909
  @app.get("/health")
910
  async def health():
911
- models_loaded = sum(1 for m in ["xgb_daily","xgb_4h","lgb"] if MODELS.get(m) is not None)
912
  return {
913
- "space": "Space 19 v9.1 Auto-Retrain + Trigger",
 
914
  "status": "operational",
915
  "symbol": SYMBOL,
916
  "models_loaded": models_loaded,
917
- "hub_connected": True,
918
- "auto_retrain": True,
919
- "retrain_trigger": "accuracy < 0.4"
920
  }
921
 
922
  @app.get("/consilium")
923
  async def consilium():
924
- try:
925
- signal = get_sol_signal()
926
- if signal:
927
- return signal
928
- return {"space":"space_19_sol_master","symbol":SYMBOL,"signal":{"direction":"WAIT","confidence":0.0},"error":"no_data"}
929
- except Exception as e:
930
- return {"space":"space_19_sol_master","symbol":SYMBOL,"signal":{"direction":"WAIT","confidence":0.0},"error": str(e)[:100]}
931
 
932
  @app.get("/signal")
933
  async def signal():
@@ -938,41 +687,21 @@ async def onchain():
938
  return {
939
  "solana": fetch_solana_onchain(),
940
  "dev_activity": fetch_solana_dev_activity(),
941
- "coingecko": fetch_coingecko_sol(),
942
  "binance": fetch_binance_sol()
943
  }
944
 
945
  @app.get("/price")
946
  async def current_price():
947
- hub = get_mt5_price_from_hub()
948
- return {"symbol": SYMBOL, "price": hub["price"] if hub["fresh"] else HUB_CACHE.get("price",0.0), "source": hub["source"], "fresh": hub["fresh"]}
949
-
950
- @app.post("/features")
951
- async def receive_features(data: Dict[str, Any]):
952
- symbol = data.get("symbol", SYMBOL)
953
- FEATURES_STORE[symbol] = {
954
- "features": data.get("features", {}),
955
- "price": data.get("price", 0.0),
956
- "timestamp": time.time()
957
- }
958
- print(f"📥 MT5 {symbol}: {len(data.get('features',{}))} признаков")
959
- return {"status": "ok"}
960
-
961
- @app.get("/metrics")
962
- async def metrics():
963
- return {"symbol": SYMBOL, "predictions_stored": len(PREDICTION_HISTORY), "last_confidence": LAST_CONFIDENCE, "component_performance": COMPONENT_PERF}
964
 
965
- @app.get("/explain")
966
- async def explain():
967
- if PREDICTION_HISTORY:
968
- last = PREDICTION_HISTORY[-1]
969
- return {
970
- "last_direction": last["direction"],
971
- "confidence": last["confidence"],
972
- "note": "Explainability via dynamic weights"
973
- }
974
- return {"error": "No prediction yet"}
975
 
976
- print("🚀 SPACE 19 v9.1 — SOL/USD MASTER (Hub-Connected + Auto-Retrain + Trigger) ЗАПУЩЕН!")
977
- print("🥉 Ансамбль daily+4h + Meta SOL Score + Dynamic Weights + Auto-Retrain + Trigger")
978
- print("✅ Готов к бою!")
 
1
  # ============================================
2
+ # 👑 TOMIRIS SPACE 19 v10.0 — SOL/USD MASTER (PRO-7/tomiris-sol-master)
3
+ # Миграция с v9.1. Все функции сохранены.
4
+ # HUB_URL обновлён, HUB_SECRET во всех запросах.
5
+ # MT5 удалён — только Binance через Хаб + Twelve Data fallback.
6
  # ============================================
7
  import os, time, threading, warnings, json, asyncio
8
  from typing import Dict, Any, Optional, List, Tuple
 
16
  # ================= БЕЗОПАСНЫЙ ИМПОРТ =================
17
  HAS_JOBLIB = False
18
  HAS_FIREBASE = False
 
19
 
20
  try:
21
  import joblib
 
30
  except:
31
  print("⚠️ firebase_admin не установлен")
32
 
33
+ # ================= FIREBASE (опционально) =================
 
 
 
 
 
 
34
  db = None
35
+ if HAS_FIREBASE and os.path.exists("firebase-key.json"):
36
  try:
37
  cred = credentials.Certificate("firebase-key.json")
38
  firebase_admin.initialize_app(cred)
 
41
  except Exception as e:
42
  print(f"⚠️ Firebase: {e}")
43
 
44
+ # ================= ОБНОВЛЁННЫЙ HUB URL =================
45
+ HUB_URL = "https://pro-3-tomiris-hub.hf.space"
46
+ HUB_SECRET = os.getenv("HUB_SECRET", "TomyrisUltraSecret2026!")
47
+
48
+ # ================= СТАРТОВЫЙ СОН =================
49
+ STARTUP_SLEEP = int(os.getenv("STARTUP_SLEEP", 600)) # 10 минут
50
 
51
+ # ================= ЗАГОЛОВКИ ДЛЯ ХАБА =================
52
+ def hub_headers():
53
+ return {"X-Hub-Secret": HUB_SECRET} if HUB_SECRET else {}
54
 
55
  # ================= API КЛЮЧИ =================
56
  TWELVE_KEYS: List[str] = [
 
60
 
61
  # ================= КОНФИГУРАЦИЯ =================
62
  SYMBOL: str = "SOL/USD"
 
63
  TIMEFRAMES: List[str] = ["15min", "1h", "4h"]
64
+ AUTO_REPORT_INTERVAL = 600 # 10 минут
65
  RETRAIN_INTERVAL = 14 * 86400 # 14 дней
66
 
67
+ SOL_THRESHOLD: float = 0.52
68
+ TRADING_RULES: Dict[str, Any] = {
69
+ "sl_atr_multiplier": 2.0,
70
+ "tp_atr_multiplier": 4.0,
71
+ "trailing_stop_activation": 0.005,
72
+ "trailing_stop_distance": 0.003,
73
+ "breakeven_at": 0.005
74
+ }
 
 
 
 
 
 
75
 
76
+ CACHE_TTL: int = 300 # 5 минут для свечей
 
 
77
 
 
78
  REGIME_WEIGHTS: Dict[str, Dict[str, float]] = {
79
  "TREND": {"model": 0.70, "tf": 0.30},
80
  "VOLATILE": {"model": 0.50, "tf": 0.50},
 
81
  "RANGE": {"model": 0.60, "tf": 0.40}
82
  }
83
 
 
 
84
  DATA_CACHE: Dict[str, Dict[str, Any]] = {}
85
  PREDICTION_HISTORY = deque(maxlen=500)
 
86
  CIRCUIT_BREAKERS: Dict[str, Dict[str, int]] = {}
87
  LAST_CONFIDENCE: float = 0.5
88
 
 
89
  COMPONENT_PERF: Dict[str, Dict[str, float]] = {
90
  "model": {"correct": 0, "total": 1, "sharpe": 1.0},
91
  "tf": {"correct": 0, "total": 1, "sharpe": 1.0},
 
93
  "derivatives": {"correct": 0, "total": 1, "sharpe": 1.0},
94
  }
95
 
 
 
96
  # ================= ЗАГРУЗКА МОДЕЛЕЙ =================
97
+ print(f"🔥 SPACE 19 v10.0: Загрузка моделей для {SYMBOL}...")
98
  MODELS: Dict[str, Optional[Any]] = {"xgb_daily": None, "xgb_4h": None, "lgb": None}
99
 
100
  if HAS_JOBLIB:
101
+ for fname, key in [("xgboost_sol_daily.joblib", "xgb_daily"),
102
+ ("xgboost_sol_4h.joblib", "xgb_4h"),
103
+ ("lgb_sol.joblib", "lgb")]:
104
+ if os.path.exists(fname):
105
+ try:
106
+ MODELS[key] = joblib.load(fname)
107
+ print(f" {fname} загружен")
108
+ except Exception as e:
109
+ print(f"⚠️ {fname}: {e}")
110
+
111
+ # ================= HTTP СЕССИЯ =================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  session = requests.Session()
113
+ session.headers.update({"User-Agent": "Tomiris-Space19-v10.0"})
114
 
115
+ # ================= УТИЛИТЫ =================
116
  def safe_float(value: Any, default: float = 0.0) -> float:
117
  try:
118
  if isinstance(value, (pd.Series, pd.DataFrame)):
 
145
  return None, 0.0
146
 
147
  class KalmanFilter:
148
+ def __init__(self):
149
+ self.q = 1e-5
150
+ self.r = 1e-4
151
  self.x = 0.0
152
  self.p = 1.0
153
  def update(self, z: float) -> float:
 
168
  except:
169
  return 0.5
170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
  def smooth_confidence(current: float) -> float:
172
  global LAST_CONFIDENCE
173
  current = max(0.0, min(1.0, current))
 
192
  return "WAIT"
193
  return None
194
 
195
+ # ================= ОТПРАВКА СИГНАЛА В HUB =================
196
+ def send_signal_to_hub(direction, confidence):
197
+ try:
198
+ payload = {
199
+ "space": "space_19_sol_master",
200
+ "space_name": "space_19_sol_master",
201
+ "symbol": SYMBOL,
202
+ "direction": direction,
203
+ "confidence": confidence,
204
+ "features": {},
205
+ "metadata": {"version": "10.0", "source": "PRO-7/tomiris-sol-master"}
206
+ }
207
+ r = session.post(f"{HUB_URL}/signals", json=payload, timeout=10, headers=hub_headers())
208
+ if r.status_code == 200:
209
+ print(f"📤 {SYMBOL}: {direction} conf={confidence:.3f} отправлен в Hub")
210
+ else:
211
+ r = session.post(f"{HUB_URL}/signal", json={
212
+ "space": "space_19_sol_master",
213
+ "symbol": SYMBOL,
214
+ "direction": direction,
215
+ "confidence": confidence
216
+ }, timeout=10, headers=hub_headers())
217
+ if r.status_code == 200:
218
+ print(f"📤 {SYMBOL}: {direction} conf={confidence:.3f} (через /signal)")
219
+ except Exception as e:
220
+ print(f"Ошибка отправки в Hub: {e}")
221
+
222
  # ================= ЗАГРУЗКА ДАННЫХ =================
223
+ def fetch_ohlc_hub(symbol: str, tf: str, limit: int = 200) -> Optional[pd.DataFrame]:
224
+ """Основной источник: Хаб (Binance)"""
225
+ cache_key = f"hub_{symbol}_{tf}"
226
+ if cache_key in DATA_CACHE:
227
+ age = time.time() - DATA_CACHE[cache_key].get("timestamp", 0)
228
+ if age < CACHE_TTL:
229
+ return DATA_CACHE[cache_key]["df"]
230
+
231
+ try:
232
+ r = session.get(
233
+ f"{HUB_URL}/candles",
234
+ params={"symbol": symbol, "interval": tf, "limit": limit},
235
+ timeout=10,
236
+ headers=hub_headers()
237
+ )
238
+ if r.status_code == 200:
239
+ candles = r.json().get("candles", [])
240
+ if candles:
241
+ df = pd.DataFrame(candles)
242
+ if "o" in df.columns:
243
+ df.rename(columns={"o": "open", "h": "high", "l": "low", "c": "close", "v": "volume"}, inplace=True)
244
+ for col in ["open", "high", "low", "close"]:
245
+ df[col] = pd.to_numeric(df[col], errors="coerce")
246
+ df["volume"] = pd.to_numeric(df.get("volume", 0), errors="coerce").fillna(0)
247
+ if len(df) >= 30:
248
+ DATA_CACHE[cache_key] = {"df": df, "timestamp": time.time()}
249
+ return df
250
+ except Exception as e:
251
+ print(f"Hub candles {symbol} {tf}: {e}")
252
+ return None
253
+
254
+ def fetch_twelvedata_sol(tf: str = "1h") -> Optional[pd.DataFrame]:
255
+ """Fallback: Twelve Data API"""
256
  cache_key = f"td_sol_{tf}"
257
  if cache_key in DATA_CACHE:
258
+ age = time.time() - DATA_CACHE[cache_key].get("timestamp", 0)
259
  if age < CACHE_TTL:
260
+ return DATA_CACHE[cache_key]["df"]
261
+
262
+ for key in TWELVE_KEYS:
263
  try:
264
  url = f"https://api.twelvedata.com/time_series?symbol=SOL/USD&interval={tf}&outputsize=200&apikey={key}"
265
  r = session.get(url, timeout=10)
266
+ if r.status_code == 200 and "values" in r.json():
267
+ df = pd.DataFrame(r.json()["values"]).iloc[::-1].reset_index(drop=True)
268
+ for col in ["close", "high", "low", "open"]:
269
+ df[col] = pd.to_numeric(df[col], errors="coerce")
270
+ df["volume"] = pd.to_numeric(df.get("volume", 0), errors="coerce").fillna(0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  if len(df) >= 30:
272
+ DATA_CACHE[cache_key] = {"df": df, "timestamp": time.time()}
273
+ return df
274
  except:
275
+ continue
276
+ return None
277
+
278
+ def fetch_ohlc(symbol: str, tf: str = "1h") -> Optional[pd.DataFrame]:
279
+ """Основной метод: сначала Хаб, потом Twelve Data"""
280
+ df = fetch_ohlc_hub(symbol, tf)
281
+ if df is None:
282
+ print(f" 🔄 Хаб недоступен для {symbol} {tf}, пробую Twelve Data...")
283
+ df = fetch_twelvedata_sol(tf)
284
+ return df
285
+
286
+ def get_price_from_hub() -> float:
287
+ try:
288
+ r = session.get(f"{HUB_URL}/price/{SYMBOL}", timeout=5, headers=hub_headers())
289
+ if r.status_code == 200:
290
+ data = r.json()
291
+ return float(data.get("price", data.get("mid", 0)))
292
+ except:
293
+ pass
294
+ return 0.0
295
 
296
  def fetch_solana_onchain() -> Dict[str, Any]:
297
  cache_key = "solana_onchain"
298
+ if cache_key in DATA_CACHE and time.time() - DATA_CACHE[cache_key].get("timestamp", 0) < 300:
299
  return DATA_CACHE[cache_key]["data"]
300
  result = {}
301
  try:
302
  r = session.get("https://api.llama.fi/v2/tvl/solana", timeout=10)
303
  if r.status_code == 200:
304
  data = r.json()
305
+ result['tvl'] = data.get('tvl', 0)
306
+ result['tvl_change_24h'] = data.get('change_1d', 0)
307
+ result['tvl_trend'] = 'UP' if data.get('change_1d', 0) > 0 else 'DOWN'
308
  except:
309
  pass
310
  try:
311
  r = session.get("https://api.llama.fi/overview/dexs/solana?excludeTotalDataChart=true&excludeTotalDataChartBreakdown=true", timeout=10)
312
  if r.status_code == 200:
313
  data = r.json()
314
+ result['dex_volume_24h'] = data.get('total24h', 0)
315
+ result['dex_change_24h'] = data.get('change_1d', 0)
316
  except:
317
  pass
318
  result['active_users'] = result.get('tvl', 0) / 100
 
326
  dev = r.json().get('developer_data', {})
327
  return {
328
  'developer_score': dev.get('developer_score', 0),
329
+ 'developer_activity': 'HIGH' if dev.get('developer_score', 0) > 80 else 'MODERATE' if dev.get('developer_score', 0) > 50 else 'LOW'
330
  }
331
  except:
332
  pass
 
339
  if r.status_code == 200:
340
  for item in r.json():
341
  if item.get('symbol') == 'SOLUSDT':
342
+ fr = float(item.get('lastFundingRate', 0))
343
  result['funding_rate'] = fr
344
  result['funding_signal'] = 'BEARISH' if fr > 0.001 else 'BULLISH' if fr < -0.001 else 'NEUTRAL'
345
  break
 
348
  try:
349
  r = session.get("https://fapi.binance.com/fapi/v1/openInterest?symbol=SOLUSDT", timeout=10)
350
  if r.status_code == 200:
351
+ result['open_interest'] = float(r.json().get('openInterest', 0))
352
  except:
353
  pass
354
  return result
 
359
  if r.status_code == 200:
360
  md = r.json().get('market_data', {})
361
  return {
362
+ 'market_cap': md.get('market_cap', {}).get('usd', 0),
363
+ 'total_volume': md.get('total_volume', {}).get('usd', 0),
364
+ 'price_change_24h': md.get('price_change_percentage_24h', 0)
365
  }
366
  except:
367
  pass
368
+ return {'market_cap': 0, 'total_volume': 0, 'price_change_24h': 0}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
 
370
  # ================= ПОСТРОЕНИЕ ПРИЗНАКОВ =================
 
 
 
 
 
 
 
 
 
 
 
 
 
371
  def build_sol_features(df: pd.DataFrame, onchain_data: Optional[Dict[str, Any]] = None,
372
  dev_data: Optional[Dict[str, Any]] = None,
373
  derivatives: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
374
  if df is None or len(df) < 20:
375
  return {}
376
 
377
+ close = df["close"].astype(float)
378
+ high = df["high"].astype(float)
379
+ low = df["low"].astype(float)
 
 
 
 
 
380
 
381
  features: Dict[str, Any] = {}
382
  features["price"] = safe_float(close.iloc[-1])
 
435
  features["active_users"] = onchain_data.get("active_users", 0)
436
  if dev_data:
437
  features["developer_score"] = dev_data.get("developer_score", 50)
 
438
  if derivatives:
439
  features["funding_rate"] = derivatives.get("funding_rate", 0)
440
  features["open_interest"] = derivatives.get("open_interest", 0)
441
  features["funding_bullish"] = 1 if derivatives.get("funding_signal") == "BULLISH" else 0
442
  features["funding_bearish"] = 1 if derivatives.get("funding_signal") == "BEARISH" else 0
443
 
 
 
 
444
  now = datetime.utcnow()
445
  features["is_weekend"] = 1 if now.weekday() >= 5 else 0
446
  features["hour"] = now.hour
447
 
 
 
 
448
  return features
449
 
 
 
 
 
 
 
 
 
 
 
 
 
 
450
  # ================= ДИНАМИЧЕСКИЕ ВЕСА =================
 
 
 
 
 
 
 
 
 
 
451
  def get_dynamic_component_weights(regime: str) -> Dict[str, float]:
452
  base = REGIME_WEIGHTS.get(regime, REGIME_WEIGHTS["RANGE"])
453
  model_acc = COMPONENT_PERF["model"]["correct"] / max(COMPONENT_PERF["model"]["total"], 1)
454
  model_sharpe = COMPONENT_PERF["model"]["sharpe"]
455
  model_w = base["model"] * model_acc * model_sharpe
 
456
  tf_acc = COMPONENT_PERF["tf"]["correct"] / max(COMPONENT_PERF["tf"]["total"], 1)
457
  tf_sharpe = COMPONENT_PERF["tf"]["sharpe"]
458
  tf_w = base["tf"] * tf_acc * tf_sharpe
 
459
  remaining = 1.0 - (model_w + tf_w)
460
  onchain_w = remaining * 0.6
461
  deriv_w = remaining * 0.4
 
462
  weights = {"model": model_w, "tf": tf_w, "onchain": onchain_w, "derivatives": deriv_w}
463
  norm = sum(weights.values())
464
  if norm > 0:
465
  weights = {k: v/norm for k, v in weights.items()}
466
  return weights
467
 
468
+ # ================= ГЛАВНЫЙ СИГНАЛ =================
469
  def get_sol_signal() -> Optional[Dict[str, Any]]:
470
  global LAST_CONFIDENCE
471
  start = time.time()
472
 
473
  onchain_data = fetch_solana_onchain()
474
  dev_data = fetch_solana_dev_activity()
 
475
  binance_data = fetch_binance_sol()
476
 
477
+ # Загружаем свечи через Хаб (Binance)
478
+ all_features = {}
479
+ for tf in TIMEFRAMES:
480
+ df = fetch_ohlc(SYMBOL, tf)
481
+ if df is not None and len(df) >= 30:
482
+ feats = build_sol_features(df, onchain_data, dev_data, binance_data)
483
+ if feats:
484
+ all_features[tf] = feats
485
+
486
+ if not all_features:
487
+ print(" Нет данных ни от Хаба, ни от Twelve Data")
488
+ return None
489
+
490
+ # Берём 1h как основные признаки
491
+ h1_features = all_features.get("1h", list(all_features.values())[0])
492
+ price = h1_features.get("price", 0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
493
 
494
  if price == 0:
495
  return None
496
 
497
+ # Стресс-тест
498
+ stress = stress_test(h1_features)
499
  if stress == "WAIT":
500
  print("🛑 СТРЕСС-ТЕСТ: рынок слишком опасен")
501
+ send_signal_to_hub("WAIT", 0.0)
502
+ return {
503
  "space": "space_19_sol_master",
504
  "symbol": SYMBOL,
505
  "signal": {"direction": "WAIT", "confidence": 0.0},
506
+ "reason": "stress_test"
507
  }
 
 
508
 
509
+ regime = detect_market_regime(h1_features)
510
+ print(f"📊 Режим: {regime} | Цена: ${price:.2f}")
511
 
512
+ # Предсказание моделей
513
  xgb_prob = 0.5
514
  models_used = 0
515
+ try:
516
+ fv = list(h1_features.values())
517
+ while len(fv) < 200:
518
+ fv.append(0.0)
519
+ X = np.nan_to_num(np.array(fv[:200], dtype=np.float64).reshape(1, -1))
520
+ probs = []
521
+ for mk in ["xgb_daily", "xgb_4h"]:
522
+ if MODELS.get(mk):
523
+ proba = MODELS[mk].predict_proba(X)[0]
 
 
 
 
 
524
  probs.append(float(proba[1] if len(proba) > 1 else proba[0]))
525
  models_used += 1
526
+ if MODELS.get("lgb"):
527
+ proba = MODELS["lgb"].predict_proba(X)[0]
528
+ lgb_prob = float(proba[1] if len(proba) > 1 else proba[0])
529
+ xgb_prob = (sum(probs)/len(probs) * 0.6 + lgb_prob * 0.4) if probs else lgb_prob
530
+ models_used += 1
531
+ elif probs:
532
+ xgb_prob = sum(probs) / len(probs)
533
+ except Exception as e:
534
+ print(f" ⚠️ Ошибка предсказания: {e}")
 
 
 
 
535
 
536
  # Мульти-ТФ подтверждение
537
+ confirmations, total_tf = 0, 0
538
+ for tf, feats in all_features.items():
539
+ total_tf += 1
540
+ ema_score = feats.get("price_vs_ema_21", 0)
541
+ rsi_val = feats.get("rsi_14", 50)
542
+ macd_hist = feats.get("macd_hist", 0)
543
+ if ema_score > 0 and rsi_val > 50 and macd_hist > 0:
544
+ confirmations += 1
545
+ elif ema_score < 0 and rsi_val < 50 and macd_hist < 0:
546
+ confirmations -= 1
547
+ tf_score = confirmations / max(total_tf, 1)
548
+ tf_norm = (tf_score + 1) / 2
549
+
550
+ # Meta SOL Score
551
+ tvl_norm = min(1.0, max(0.0, 0.5 + onchain_data.get("tvl_change_24h", 0) / 20))
552
+ dex_norm = min(1.0, max(0.0, 0.5 + onchain_data.get("dex_change_24h", 0) / 20))
553
+ dev_norm = 0.7 if dev_data.get("developer_score", 50) > 50 else 0.5
554
+ meta_sol_score = (tvl_norm + dex_norm + dev_norm) / 3
555
+
556
+ # Деривативы
557
+ deriv_norm = 0.7 if binance_data.get("funding_signal") == "BULLISH" else 0.3 if binance_data.get("funding_signal") == "BEARISH" else 0.5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
558
 
559
  # Динамические веса
560
  comp_weights = get_dynamic_component_weights(regime)
561
+ print(f" ⚖️ Веса: model={comp_weights['model']:.2f} tf={comp_weights['tf']:.2f} onchain={comp_weights['onchain']:.2f} deriv={comp_weights['derivatives']:.2f}")
562
 
563
  final_score = (
564
  xgb_prob * comp_weights["model"] +
 
576
  direction = "WAIT"
577
 
578
  # SL/TP
579
+ atr = h1_features.get("atr_14", price * 0.02)
580
+ sl_dist = atr * TRADING_RULES.get("sl_atr_multiplier", 2.0)
581
+ tp_dist = atr * TRADING_RULES.get("tp_atr_multiplier", 4.0)
582
+ sl = round(price - sl_dist, 2) if direction == "LONG" else round(price + sl_dist, 2) if direction == "SHORT" else 0
583
+ tp = round(price + tp_dist, 2) if direction == "LONG" else round(price - tp_dist, 2) if direction == "SHORT" else 0
 
 
 
 
 
 
 
 
584
 
585
  PREDICTION_HISTORY.append({
586
  "timestamp": datetime.utcnow().isoformat(),
 
589
  "price": price
590
  })
591
 
592
+ send_signal_to_hub(direction, confidence)
 
 
 
 
 
 
 
 
 
 
 
593
 
594
  latency = int((time.time() - start) * 1000)
595
  result = {
596
  "space": "space_19_sol_master",
597
  "timestamp": int(time.time()),
598
  "symbol": SYMBOL,
599
+ "signal": {"direction": direction, "confidence": round(confidence, 4)},
 
 
 
 
600
  "analysis": {
601
  "xgb_probability": round(xgb_prob, 4),
602
  "models_used": models_used,
603
  "multi_tf_score": round(tf_score, 4),
604
  "meta_sol_score": round(meta_sol_score, 4),
605
  "market_regime": regime,
606
+ "data_source": "HUB_BINANCE"
607
  },
608
  "onchain": {
609
  "tvl": onchain_data.get("tvl", 0),
610
  "dex_volume_24h": onchain_data.get("dex_volume_24h", 0),
 
 
611
  "funding_rate": binance_data.get("funding_rate", 0)
612
  },
613
+ "risk": {"sl": sl, "tp": tp},
614
+ "latency_ms": latency
 
 
 
 
 
615
  }
616
 
617
+ print(f"🥉 SOL/USD: {direction} | conf={confidence:.3f} | models={models_used} | regime={regime} | latency={latency}ms")
 
618
  return result
619
 
620
  # ================= АВТО-ОТПРАВКА =================
621
  def auto_report():
622
+ print(f"⏳ Стартовый сон {STARTUP_SLEEP} секунд...")
623
+ time.sleep(STARTUP_SLEEP)
624
+ print("✅ SOL Master — начинаю авто-отправку!")
625
  while True:
626
  time.sleep(AUTO_REPORT_INTERVAL)
627
  try:
 
632
  # ================= АВТО-ДООБУЧЕНИЕ =================
633
  def retrain_models():
634
  print("🔄 Запуск дообучения моделей SOL...")
635
+ # Заглушка — дообучение через yfinance убрано, используем данные Хаба
636
+ print("⚠️ Дообучение через Hub пока не реализовано")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
637
 
638
  def auto_retrain():
639
+ time.sleep(STARTUP_SLEEP + 3600)
640
  while True:
641
+ time.sleep(RETRAIN_INTERVAL)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
642
  retrain_models()
643
 
644
  # ================= KEEP-ALIVE =================
645
  def keep_alive():
646
+ time.sleep(STARTUP_SLEEP)
647
  while True:
648
  time.sleep(840)
649
  try:
 
656
  threading.Thread(target=auto_retrain, daemon=True).start()
657
 
658
  # ================= FASTAPI =================
659
+ app = FastAPI(title="TOMIRIS SOL/USD MASTER v10.0 (PRO-7)")
660
 
661
  @app.get("/health")
662
  async def health():
663
+ models_loaded = sum(1 for m in ["xgb_daily", "xgb_4h", "lgb"] if MODELS.get(m) is not None)
664
  return {
665
+ "space": "PRO-7/tomiris-sol-master",
666
+ "version": "10.0",
667
  "status": "operational",
668
  "symbol": SYMBOL,
669
  "models_loaded": models_loaded,
670
+ "data_source": "HUB_BINANCE",
671
+ "startup_sleep": STARTUP_SLEEP
 
672
  }
673
 
674
  @app.get("/consilium")
675
  async def consilium():
676
+ signal = get_sol_signal()
677
+ if signal:
678
+ return signal
679
+ return {"space": "space_19_sol_master", "symbol": SYMBOL, "signal": {"direction": "WAIT", "confidence": 0.0}}
 
 
 
680
 
681
  @app.get("/signal")
682
  async def signal():
 
687
  return {
688
  "solana": fetch_solana_onchain(),
689
  "dev_activity": fetch_solana_dev_activity(),
 
690
  "binance": fetch_binance_sol()
691
  }
692
 
693
  @app.get("/price")
694
  async def current_price():
695
+ price = get_price_from_hub()
696
+ return {"symbol": SYMBOL, "price": price, "source": "HUB"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
697
 
698
+ @app.get("/")
699
+ async def root():
700
+ return {
701
+ "name": "SOL Master v10.0",
702
+ "space": "PRO-7/tomiris-sol-master",
703
+ "hub": HUB_URL,
704
+ "data_source": "HUB_BINANCE"
705
+ }
 
 
706
 
707
+ print("🚀 SPACE 19 v10.0 — SOL/USD MASTER (PRO-7/tomiris-sol-master) ЗАПУЩЕН!")