tomirisai80 commited on
Commit
68dc3cf
·
verified ·
1 Parent(s): da06ca2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +207 -290
app.py CHANGED
@@ -19,15 +19,14 @@ for module_name, pip_name in REQUIRED_PACKAGES.items():
19
  print(f"✅ {pip_name} установлен!")
20
 
21
  # ============================================
22
- # 👑 TOMIRIS SPACE 23 v1.0 — ON-CHAIN ANOMALY DETECTOR
23
  # ============================================
24
- # Детектирует аномалии на блокчейне:
25
- # Китовые транзакции, притоктток на биржи, скачки газа,
26
- # нетипичная активность, сливы/пампы.
27
  # ============================================
28
 
29
  from fastapi import FastAPI, Query
30
- from typing import Optional, Dict, Any, List
31
  import time
32
  import requests
33
  import threading
@@ -38,284 +37,211 @@ import warnings
38
  warnings.filterwarnings('ignore')
39
 
40
  # ================= КОНФИГУРАЦИЯ =================
41
- TRACKED_SYMBOLS: List[str] = ["ETH/USD", "SOL/USD"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
  SPACE_18_ARBITER: str = "https://tomiris-ai-name6-6.hf.space"
44
 
45
- # API ключи
46
- ETHERSCAN_KEYS: List[str] = [
47
- "TZGVD58I2HZ9G4BBD548KX11E4JFH4J5GX",
48
- "NMK1GC2S8RKVQZ6YKV7CIVZGGAKS1XYWSI"
49
- ]
50
-
51
  CACHE: Dict[str, Dict[str, Any]] = {}
52
  CACHE_TIMES: Dict[str, float] = {}
53
  FEATURES_STORE: Dict[str, Dict[str, Any]] = {}
54
  MT5_MAX_AGE_SEC: int = 300
55
- ANOMALY_HISTORY: deque = deque(maxlen=200)
56
-
57
- etherscan_counter: int = 0
58
- api_lock = threading.Lock()
59
-
60
- def get_next_etherscan_key() -> str:
61
- global etherscan_counter
62
- with api_lock:
63
- key = ETHERSCAN_KEYS[etherscan_counter % len(ETHERSCAN_KEYS)]
64
- etherscan_counter += 1
65
- return key
66
 
67
  def send_to_arbiter(signal_data: Dict[str, Any]) -> None:
68
  try:
69
  requests.post(
70
  f"{SPACE_18_ARBITER}/log_signal",
71
- json={'space': 'space_23_anomaly', 'symbol': 'ETH/USD', 'signal': signal_data.get('signal', {})},
72
  timeout=5
73
  )
74
  except:
75
  pass
76
 
77
- # ================= ETHERSCAN =================
78
- def fetch_gas_oracle() -> Dict[str, Any]:
79
- cache_key = "gas_oracle"
80
  if cache_key in CACHE and time.time() - CACHE_TIMES.get(cache_key, 0) < 60:
81
  return CACHE[cache_key]
82
 
83
  try:
84
- key = get_next_etherscan_key()
85
- r = requests.get(
86
- f"https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey={key}",
87
- timeout=10
88
- )
89
- if r.status_code == 200 and r.json().get('status') == '1':
90
- result = r.json()['result']
91
- avg = int(result['ProposeGasPrice'])
92
- data = {
93
- 'avg_gas': avg,
94
- 'gas_level': 'LOW' if avg < 30 else 'NORMAL' if avg < 80 else 'HIGH' if avg < 150 else 'EXTREME',
95
- 'is_anomaly': avg > 200
96
- }
97
  CACHE[cache_key] = data
98
  CACHE_TIMES[cache_key] = time.time()
99
  return data
100
- except:
101
- pass
102
- return {'avg_gas': 45, 'gas_level': 'NORMAL', 'is_anomaly': False}
103
 
104
- def fetch_large_transactions() -> Dict[str, Any]:
105
- """Китовые транзакции (>100 ETH) через Etherscan."""
106
- cache_key = "large_tx"
107
- if cache_key in CACHE and time.time() - CACHE_TIMES.get(cache_key, 0) < 300:
108
- return CACHE[cache_key]
109
 
110
- try:
111
- key = get_next_etherscan_key()
112
- r = requests.get(
113
- f"https://api.etherscan.io/api?module=account&action=txlist&address=0x00000000219ab540356cBB839Cbe05303d7705Fa&page=1&offset=10&sort=desc&apikey={key}",
114
- timeout=10
115
- )
116
- if r.status_code == 200 and r.json().get('status') == '1':
117
- txs = r.json()['result']
118
- large_txs = []
119
- for tx in txs:
120
- value_eth = float(tx.get('value', 0)) / 1e18
121
- if value_eth > 100:
122
- large_txs.append({
123
- 'hash': tx.get('hash', '')[:10],
124
- 'value_eth': round(value_eth, 2),
125
- 'from': tx.get('from', '')[:10],
126
- 'to': tx.get('to', '')[:10]
127
- })
128
-
129
- is_anomaly = len(large_txs) > 3
130
-
131
- result = {
132
- 'large_transactions': large_txs[:5],
133
- 'count': len(large_txs),
134
- 'is_anomaly': is_anomaly,
135
- 'signal': 'WHALE_ACTIVE' if is_anomaly else 'NORMAL'
136
- }
137
- CACHE[cache_key] = result
138
- CACHE_TIMES[cache_key] = time.time()
139
- return result
140
- except:
141
- pass
142
 
143
- return {'large_transactions': [], 'count': 0, 'is_anomaly': False, 'signal': 'NORMAL'}
144
-
145
- # ================= BINANCE ПРИТОКИ/ОТТОКИ =================
146
- def fetch_exchange_flows(symbol: str = "ETH") -> Dict[str, Any]:
147
- """Приток/отток на биржи (упрощённо через Binance данные)."""
148
- cache_key = f"exchange_flows_{symbol}"
149
- if cache_key in CACHE and time.time() - CACHE_TIMES.get(cache_key, 0) < 300:
150
- return CACHE[cache_key]
151
 
152
- try:
153
- binance_symbol = "ETHUSDT" if symbol == "ETH" else "SOLUSDT"
154
 
155
- # Оцениваем через large trades на Binance
156
- r = requests.get(f"https://fapi.binance.com/fapi/v1/trades?symbol={binance_symbol}&limit=50", timeout=10)
157
- if r.status_code == 200:
158
- trades = r.json()
159
- large_trades = [t for t in trades if float(t.get('qty', 0)) > 100]
160
- buy_large = sum(1 for t in large_trades if t.get('isBuyerMaker') == False)
161
- sell_large = sum(1 for t in large_trades if t.get('isBuyerMaker') == True)
162
-
163
- net_flow = buy_large - sell_large
164
- is_anomaly = abs(net_flow) > 10
165
-
166
- result = {
167
- 'symbol': symbol,
168
- 'large_buys': buy_large,
169
- 'large_sells': sell_large,
170
- 'net_flow': net_flow,
171
- 'signal': 'INFLOW' if net_flow > 10 else 'OUTFLOW' if net_flow < -10 else 'BALANCED',
172
- 'is_anomaly': is_anomaly
173
- }
174
- CACHE[cache_key] = result
175
- CACHE_TIMES[cache_key] = time.time()
176
- return result
177
- except:
178
- pass
179
 
180
- return {'signal': 'BALANCED', 'is_anomaly': False}
 
 
 
 
 
 
 
 
 
181
 
182
- # ================= АКТИВНЫЕ АДРЕСА (через DeFiLlama) =================
183
- def fetch_active_addresses(chain: str = "ethereum") -> Dict[str, Any]:
184
- cache_key = f"active_addresses_{chain}"
185
- if cache_key in CACHE and time.time() - CACHE_TIMES.get(cache_key, 0) < 600:
186
- return CACHE[cache_key]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
 
188
- try:
189
- r = requests.get(
190
- f"https://api.llama.fi/overview/{chain}?excludeTotalDataChart=true&excludeTotalDataChartBreakdown=true",
191
- timeout=10
192
- )
193
- if r.status_code == 200:
194
- data = r.json()
195
- active_users = data.get('activeUsers', 0)
196
-
197
- prev = CACHE.get(f"{cache_key}_prev", active_users)
198
- change = ((active_users - prev) / prev * 100) if prev > 0 else 0
199
- CACHE[f"{cache_key}_prev"] = active_users
200
-
201
- is_anomaly = abs(change) > 20 # Резкий скачок >20% = аномалия
202
-
203
- result = {
204
- 'chain': chain,
205
- 'active_users': active_users,
206
- 'change_pct': round(change, 2),
207
- 'is_anomaly': is_anomaly,
208
- 'signal': 'SPIKE_UP' if change > 20 else 'SPIKE_DOWN' if change < -20 else 'NORMAL'
209
- }
210
- CACHE[cache_key] = result
211
- CACHE_TIMES[cache_key] = time.time()
212
- return result
213
- except:
214
- pass
215
 
216
- return {'active_users': 0, 'is_anomaly': False, 'signal': 'NORMAL'}
217
-
218
- # ================= ОБЩИЙ ДЕТЕКТОР АНОМАЛИЙ =================
219
- def detect_anomalies(symbol: str) -> Dict[str, Any]:
220
- chain = "ethereum" if "ETH" in symbol else "solana"
221
- native = "ETH" if "ETH" in symbol else "SOL"
222
-
223
- gas = fetch_gas_oracle() if chain == "ethereum" else {'is_anomaly': False}
224
- large_tx = fetch_large_transactions() if chain == "ethereum" else {'is_anomaly': False, 'signal': 'NORMAL'}
225
- flows = fetch_exchange_flows(native)
226
- active = fetch_active_addresses(chain)
227
-
228
- anomalies = []
229
- anomaly_score = 0
230
-
231
- if gas.get('is_anomaly'):
232
- anomalies.append({"type": "GAS_SPIKE", "severity": "HIGH", "reason": f"Газ: {gas.get('avg_gas')} gwei"})
233
- anomaly_score += 30
234
-
235
- if large_tx.get('is_anomaly'):
236
- anomalies.append({"type": "WHALE_MOVEMENT", "severity": "HIGH", "reason": f"Китовых TX: {large_tx.get('count')}"})
237
- anomaly_score += 25
238
-
239
- if flows.get('is_anomaly'):
240
- direction = flows.get('signal', '')
241
- if direction == 'OUTFLOW':
242
- anomalies.append({"type": "EXCHANGE_OUTFLOW", "severity": "HIGH", "reason": "Крупный отток с бирж"})
243
- anomaly_score += 20
244
- elif direction == 'INFLOW':
245
- anomalies.append({"type": "EXCHANGE_INFLOW", "severity": "MEDIUM", "reason": "Крупный приток на биржи"})
246
- anomaly_score += 15
247
-
248
- if active.get('is_anomaly'):
249
- anomalies.append({"type": "ADDRESS_SPIKE", "severity": "MEDIUM", "reason": f"Адреса: {active.get('signal')} ({active.get('change_pct')}%)"})
250
- anomaly_score += 15
251
-
252
- anomaly_score = min(100, anomaly_score)
253
-
254
- if anomaly_score >= 60:
255
- level = "CRITICAL"
256
- signal = "WAIT" # При критических аномалиях — не торгуем
257
- elif anomaly_score >= 30:
258
- level = "ELEVATED"
259
- signal = "CAUTION"
260
  else:
261
- level = "NORMAL"
262
- signal = "NEUTRAL"
263
 
264
- if anomalies:
265
- ANOMALY_HISTORY.append({
266
- "timestamp": datetime.utcnow().isoformat(),
267
- "symbol": symbol,
268
- "anomalies": anomalies,
269
- "score": anomaly_score
270
- })
271
 
272
  return {
273
- "anomaly_score": anomaly_score,
274
- "anomaly_level": level,
275
- "signal": signal,
276
- "anomalies": anomalies,
277
- "metrics": {
278
- "gas": gas,
279
- "large_transactions": large_tx,
280
- "exchange_flows": flows,
281
- "active_addresses": active
282
  }
283
  }
284
 
285
  # ================= АНАЛИЗ ИЗ MT5 =================
286
  def analyze_from_mt5(mt5_features: Dict[str, Any]) -> Optional[Dict[str, Any]]:
287
  try:
288
- anomalies = []
289
- score = 0
290
-
291
- atr_pct = mt5_features.get('H1_atr_pct', 1)
292
- if isinstance(atr_pct, (int, float)) and atr_pct > 3.0:
293
- anomalies.append({"type": "VOLATILITY_SPIKE", "severity": "HIGH", "reason": f"ATR {atr_pct}%"})
294
- score += 25
295
-
296
- volume = mt5_features.get('H1_volume', 0)
297
- if isinstance(volume, (int, float)) and volume > 5000:
298
- anomalies.append({"type": "VOLUME_SPIKE", "severity": "MEDIUM", "reason": f"Объём {volume}"})
299
- score += 15
300
-
301
- if score >= 30:
302
- signal = "WAIT"
303
- elif score >= 15:
304
- signal = "CAUTION"
 
 
 
 
 
305
  else:
306
- signal = "NEUTRAL"
307
 
308
  return {
309
- "anomaly_score": score,
310
- "signal": signal,
311
- "anomalies": anomalies,
312
  "source": "MT5"
313
  }
314
  except:
315
  return None
316
 
317
  # ================= ГЛАВНЫЙ СИГНАЛ =================
318
- def get_anomaly_signal(symbol: str = "ETH/USD") -> Dict[str, Any]:
319
  start = time.time()
320
 
321
  # Проверка MT5
@@ -326,51 +252,50 @@ def get_anomaly_signal(symbol: str = "ETH/USD") -> Dict[str, Any]:
326
  mt5_features = fs.get("features", {})
327
  if mt5_features:
328
  mt5_result = analyze_from_mt5(mt5_features)
329
- if mt5_result and mt5_result["anomaly_score"] > 0:
330
  result = {
331
- "space": "space_23_anomaly",
332
  "timestamp": int(time.time()),
333
  "symbol": symbol,
334
- "signal": {"direction": mt5_result["signal"], "confidence": min(0.9, mt5_result["anomaly_score"] / 50)},
335
- "anomaly_analysis": {
336
- "score": mt5_result["anomaly_score"],
337
- "anomalies": mt5_result["anomalies"]
 
 
 
 
338
  },
339
- "data_source": "MT5",
340
  "meta": {"latency_ms": int((time.time() - start) * 1000)}
341
  }
342
  send_to_arbiter(result)
343
- print(f"🔍 ANOMALY {symbol}: Score={mt5_result['anomaly_score']} (MT5)")
344
  return result
345
 
346
- # Fallback
347
- analysis = detect_anomalies(symbol)
348
  latency = int((time.time() - start) * 1000)
349
 
350
- direction = "WAIT" if analysis['anomaly_level'] in ['CRITICAL', 'ELEVATED'] else "NEUTRAL"
351
- confidence = analysis['anomaly_score'] / 100 if analysis['anomaly_score'] > 0 else 0.0
352
-
353
  result = {
354
- "space": "space_23_anomaly",
355
  "timestamp": int(time.time()),
356
  "symbol": symbol,
357
  "signal": {
358
- "direction": direction,
359
- "confidence": round(confidence, 4)
360
  },
361
- "anomaly_analysis": {
362
- "score": analysis['anomaly_score'],
363
- "level": analysis['anomaly_level'],
364
- "anomalies": analysis['anomalies'],
365
- "metrics": analysis['metrics']
 
366
  },
367
- "data_source": "ONCHAIN",
368
  "meta": {"latency_ms": latency}
369
  }
370
 
371
  send_to_arbiter(result)
372
- if analysis['anomalies']:
373
- print(f"🔍 ANOMALY {symbol}: {analysis['anomaly_level']} | Score={analysis['anomaly_score']} | {len(analysis['anomalies'])} аномалий")
374
  return result
375
 
376
  # ================= KEEP-ALIVE =================
@@ -385,54 +310,46 @@ def keep_alive():
385
  threading.Thread(target=keep_alive, daemon=True).start()
386
 
387
  # ================= FASTAPI =================
388
- app = FastAPI(title="TOMIRIS SPACE 23 v1.0 — ON-CHAIN ANOMALY DETECTOR")
389
 
390
  @app.get("/health")
391
  @app.head("/health")
392
  async def health():
393
  return {
394
- "space": "Space 23 - On-Chain Anomaly Detector v1.0",
395
  "status": "operational",
396
- "symbols": TRACKED_SYMBOLS,
397
- "features": ["Gas Spike", "Whale TX", "Exchange Flows", "Active Addresses"],
398
- "anomaly_history": len(ANOMALY_HISTORY)
399
  }
400
 
401
  @app.get("/consilium")
402
- async def consilium(symbol: str = Query("ETH/USD")):
403
- if symbol not in TRACKED_SYMBOLS:
404
- return {"error": f"Unsupported: {symbol}"}
405
- return get_anomaly_signal(symbol)
406
-
407
- @app.get("/scan/{symbol}")
408
- async def scan(symbol: str):
409
- if symbol not in TRACKED_SYMBOLS:
410
  return {"error": f"Unsupported: {symbol}"}
411
- return detect_anomalies(symbol)
412
 
413
- @app.get("/gas")
414
- async def gas():
415
- return fetch_gas_oracle()
416
-
417
- @app.get("/whales")
418
- async def whales():
419
- return fetch_large_transactions()
420
-
421
- @app.get("/flows/{symbol}")
422
- async def flows(symbol: str = "ETH"):
423
- return fetch_exchange_flows(symbol)
424
-
425
- @app.get("/active/{chain}")
426
- async def active(chain: str = "ethereum"):
427
- return fetch_active_addresses(chain)
428
 
429
- @app.get("/history")
430
- async def history(limit: int = Query(20)):
431
- return list(ANOMALY_HISTORY)[-limit:]
 
 
 
 
432
 
433
  @app.post("/features")
434
  async def receive_features(data: Dict[str, Any]):
435
- symbol = data.get("symbol", "ETH/USD")
436
  FEATURES_STORE[symbol] = {
437
  "features": data.get("features", {}),
438
  "price": data.get("price", 0.0),
@@ -441,6 +358,6 @@ async def receive_features(data: Dict[str, Any]):
441
  print(f"📥 MT5 {symbol}: {len(data.get('features', {}))} признаков")
442
  return {"status": "ok"}
443
 
444
- print(f"🚀 SPACE 23 v1.0 — ON-CHAIN ANOMALY DETECTOR ЗАПУЩЕН!")
445
- print(f"🔍 Детектор: Gas Spike | Whale TX | Exchange Flows | Active Addresses")
446
  print(f"✅ Готов к бою!")
 
19
  print(f"✅ {pip_name} установлен!")
20
 
21
  # ============================================
22
+ # 👑 TOMIRIS SPACE 24 v1.0 — ECOSYSTEM AGGREGATOR
23
  # ============================================
24
+ # Агрегирует сигналы от Space 19-23 и выдаёт единый
25
+ # экосистемный вердикт для XAU/USD, ETH/USD, SOL/USD.
 
26
  # ============================================
27
 
28
  from fastapi import FastAPI, Query
29
+ from typing import Dict, Any, List, Optional, Tuple
30
  import time
31
  import requests
32
  import threading
 
37
  warnings.filterwarnings('ignore')
38
 
39
  # ================= КОНФИГУРАЦИЯ =================
40
+ SYMBOLS: List[str] = ["XAU/USD", "ETH/USD", "SOL/USD"]
41
+
42
+ # URL'ы экосистемных Space'ов (19-23)
43
+ ECOSYSTEM_SPACES: Dict[str, Dict[str, Any]] = {
44
+ "space_19_sol": {
45
+ "url": "https://tomiris-ecosystem-sol-master.hf.space",
46
+ "weight": 0.25,
47
+ "symbols": ["SOL/USD"],
48
+ "description": "SOL Master"
49
+ },
50
+ "space_20_l2_defi": {
51
+ "url": "https://tomiris-ecosystem-l2-defi.hf.space",
52
+ "weight": 0.25,
53
+ "symbols": ["ETH/USD"],
54
+ "description": "L2 & DeFi Pulse"
55
+ },
56
+ "space_21_gold_macro": {
57
+ "url": "https://tomiris-ecosystem-gold-macro.hf.space",
58
+ "weight": 0.25,
59
+ "symbols": ["XAU/USD"],
60
+ "description": "Gold Macro & Flow"
61
+ },
62
+ "space_22_sentiment": {
63
+ "url": "https://tomiris-ecosystem-sentiment.hf.space",
64
+ "weight": 0.15,
65
+ "symbols": ["XAU/USD", "ETH/USD", "SOL/USD"],
66
+ "description": "Cross-Asset Sentiment"
67
+ },
68
+ "space_23_anomaly": {
69
+ "url": "https://tomiris-ecosystem-anomaly.hf.space",
70
+ "weight": 0.10,
71
+ "symbols": ["ETH/USD", "SOL/USD"],
72
+ "description": "On-Chain Anomaly"
73
+ }
74
+ }
75
 
76
  SPACE_18_ARBITER: str = "https://tomiris-ai-name6-6.hf.space"
77
 
 
 
 
 
 
 
78
  CACHE: Dict[str, Dict[str, Any]] = {}
79
  CACHE_TIMES: Dict[str, float] = {}
80
  FEATURES_STORE: Dict[str, Dict[str, Any]] = {}
81
  MT5_MAX_AGE_SEC: int = 300
 
 
 
 
 
 
 
 
 
 
 
82
 
83
  def send_to_arbiter(signal_data: Dict[str, Any]) -> None:
84
  try:
85
  requests.post(
86
  f"{SPACE_18_ARBITER}/log_signal",
87
+ json={'space': 'space_24_ecosystem', 'symbol': 'ALL', 'signal': signal_data.get('signal', {})},
88
  timeout=5
89
  )
90
  except:
91
  pass
92
 
93
+ # ================= ЗАГРУЗКА СИГНАЛОВ =================
94
+ def fetch_space_signal(space_name: str, url: str, symbol: str) -> Optional[Dict[str, Any]]:
95
+ cache_key = f"{space_name}_{symbol}"
96
  if cache_key in CACHE and time.time() - CACHE_TIMES.get(cache_key, 0) < 60:
97
  return CACHE[cache_key]
98
 
99
  try:
100
+ r = requests.get(f"{url}/consilium?symbol={symbol}", timeout=10)
101
+ if r.status_code == 200:
102
+ data = r.json()
 
 
 
 
 
 
 
 
 
 
103
  CACHE[cache_key] = data
104
  CACHE_TIMES[cache_key] = time.time()
105
  return data
106
+ except Exception as e:
107
+ print(f"⚠️ {space_name}: {e}")
108
+ return None
109
 
110
+ def fetch_all_ecosystem_signals(symbol: str) -> Dict[str, Dict[str, Any]]:
111
+ signals = {}
112
+ errors = []
 
 
113
 
114
+ for space_name, config in ECOSYSTEM_SPACES.items():
115
+ if symbol not in config['symbols']:
116
+ continue
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
 
118
+ signal = fetch_space_signal(space_name, config['url'], symbol)
119
+ if signal:
120
+ signals[space_name] = signal
121
+ else:
122
+ errors.append(space_name)
 
 
 
123
 
124
+ return signals, errors
 
125
 
126
+ # ================= АГРЕГАЦИЯ =================
127
+ def aggregate_ecosystem(symbol: str) -> Dict[str, Any]:
128
+ signals, errors = fetch_all_ecosystem_signals(symbol)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
+ if not signals:
131
+ return {
132
+ "ecosystem_score": 50.0,
133
+ "direction": "WAIT",
134
+ "confidence": 0.0,
135
+ "active_spaces": 0,
136
+ "error_spaces": len(errors),
137
+ "signals_detail": {},
138
+ "votes": {"LONG": 0.0, "SHORT": 0.0}
139
+ }
140
 
141
+ total_weight = 0.0
142
+ long_votes = 0.0
143
+ short_votes = 0.0
144
+ details = {}
145
+
146
+ for space_name, data in signals.items():
147
+ config = ECOSYSTEM_SPACES.get(space_name, {})
148
+ weight = config.get('weight', 0.1)
149
+ sig = data.get('signal', {})
150
+ direction = sig.get('direction', 'WAIT')
151
+ confidence = sig.get('confidence', 0)
152
+
153
+ # Взвешенное голосование
154
+ if direction == 'LONG':
155
+ long_votes += weight * confidence
156
+ elif direction == 'SHORT':
157
+ short_votes += weight * confidence
158
+
159
+ total_weight += weight
160
+ details[space_name] = {
161
+ "direction": direction,
162
+ "confidence": confidence,
163
+ "weight": weight,
164
+ "status": "active",
165
+ "description": config.get('description', '')
166
+ }
167
 
168
+ for name in errors:
169
+ config = ECOSYSTEM_SPACES.get(name, {})
170
+ details[name] = {
171
+ "direction": "ERROR",
172
+ "confidence": 0,
173
+ "weight": config.get('weight', 0),
174
+ "status": "error",
175
+ "description": config.get('description', '')
176
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
 
178
+ # Определяем направление
179
+ if long_votes > short_votes * 1.3:
180
+ direction = "LONG"
181
+ confidence = min(0.85, long_votes / max(total_weight, 0.1))
182
+ elif short_votes > long_votes * 1.3:
183
+ direction = "SHORT"
184
+ confidence = min(0.85, short_votes / max(total_weight, 0.1))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  else:
186
+ direction = "WAIT"
187
+ confidence = max(long_votes, short_votes) / max(total_weight, 0.1)
188
 
189
+ # Экосистемный скор (0-100)
190
+ ecosystem_score = ((long_votes + short_votes) / max(total_weight, 0.1)) * 50
191
+ ecosystem_score = min(100, max(0, ecosystem_score))
 
 
 
 
192
 
193
  return {
194
+ "ecosystem_score": round(ecosystem_score, 2),
195
+ "direction": direction,
196
+ "confidence": round(confidence, 4),
197
+ "active_spaces": len(signals),
198
+ "error_spaces": len(errors),
199
+ "signals_detail": details,
200
+ "votes": {
201
+ "LONG": round(long_votes, 4),
202
+ "SHORT": round(short_votes, 4)
203
  }
204
  }
205
 
206
  # ================= АНАЛИЗ ИЗ MT5 =================
207
  def analyze_from_mt5(mt5_features: Dict[str, Any]) -> Optional[Dict[str, Any]]:
208
  try:
209
+ score = 50.0
210
+
211
+ rsi = mt5_features.get('H1_rsi', 50)
212
+ if isinstance(rsi, (int, float)):
213
+ if rsi > 70:
214
+ score -= 10
215
+ elif rsi < 30:
216
+ score += 10
217
+
218
+ ema = mt5_features.get('H1_price_vs_ema_21', 0)
219
+ if isinstance(ema, (int, float)):
220
+ if ema > 1.0:
221
+ score += 10
222
+ elif ema < -1.0:
223
+ score -= 10
224
+
225
+ score = max(0, min(100, score))
226
+
227
+ if score > 60:
228
+ direction, confidence = "LONG", score / 100
229
+ elif score < 40:
230
+ direction, confidence = "SHORT", (100 - score) / 100
231
  else:
232
+ direction, confidence = "WAIT", 0.0
233
 
234
  return {
235
+ "direction": direction,
236
+ "confidence": round(confidence, 4),
237
+ "score": score,
238
  "source": "MT5"
239
  }
240
  except:
241
  return None
242
 
243
  # ================= ГЛАВНЫЙ СИГНАЛ =================
244
+ def get_ecosystem_aggregate(symbol: str = "XAU/USD") -> Dict[str, Any]:
245
  start = time.time()
246
 
247
  # Проверка MT5
 
252
  mt5_features = fs.get("features", {})
253
  if mt5_features:
254
  mt5_result = analyze_from_mt5(mt5_features)
255
+ if mt5_result:
256
  result = {
257
+ "space": "space_24_ecosystem_aggregator",
258
  "timestamp": int(time.time()),
259
  "symbol": symbol,
260
+ "signal": {
261
+ "direction": mt5_result["direction"],
262
+ "confidence": mt5_result["confidence"]
263
+ },
264
+ "ecosystem_analysis": {
265
+ "score": mt5_result["score"],
266
+ "active_spaces": 0,
267
+ "data_source": "MT5"
268
  },
 
269
  "meta": {"latency_ms": int((time.time() - start) * 1000)}
270
  }
271
  send_to_arbiter(result)
272
+ print(f"🌐 ECO AGG: {mt5_result['direction']} | Score={mt5_result['score']} (MT5)")
273
  return result
274
 
275
+ # Fallback: агрегация экосистемных Space'ов
276
+ agg = aggregate_ecosystem(symbol)
277
  latency = int((time.time() - start) * 1000)
278
 
 
 
 
279
  result = {
280
+ "space": "space_24_ecosystem_aggregator",
281
  "timestamp": int(time.time()),
282
  "symbol": symbol,
283
  "signal": {
284
+ "direction": agg['direction'],
285
+ "confidence": agg['confidence']
286
  },
287
+ "ecosystem_analysis": {
288
+ "score": agg['ecosystem_score'],
289
+ "active_spaces": agg['active_spaces'],
290
+ "error_spaces": agg['error_spaces'],
291
+ "votes": agg['votes'],
292
+ "details": agg['signals_detail']
293
  },
 
294
  "meta": {"latency_ms": latency}
295
  }
296
 
297
  send_to_arbiter(result)
298
+ print(f"🌐 ECO AGG v1: {symbol} | {agg['direction']} | Active={agg['active_spaces']}/{len(ECOSYSTEM_SPACES)}")
 
299
  return result
300
 
301
  # ================= KEEP-ALIVE =================
 
310
  threading.Thread(target=keep_alive, daemon=True).start()
311
 
312
  # ================= FASTAPI =================
313
+ app = FastAPI(title="TOMIRIS SPACE 24 v1.0 — ECOSYSTEM AGGREGATOR")
314
 
315
  @app.get("/health")
316
  @app.head("/health")
317
  async def health():
318
  return {
319
+ "space": "Space 24 - Ecosystem Aggregator v1.0",
320
  "status": "operational",
321
+ "symbols": SYMBOLS,
322
+ "ecosystem_spaces": {k: v['description'] for k, v in ECOSYSTEM_SPACES.items()},
323
+ "total_spaces": len(ECOSYSTEM_SPACES)
324
  }
325
 
326
  @app.get("/consilium")
327
+ async def consilium(symbol: str = Query("XAU/USD")):
328
+ if symbol not in SYMBOLS:
 
 
 
 
 
 
329
  return {"error": f"Unsupported: {symbol}"}
330
+ return get_ecosystem_aggregate(symbol)
331
 
332
+ @app.get("/breakdown/{symbol}")
333
+ async def breakdown(symbol: str):
334
+ """Детальный разбор всех экосистемных Space'ов."""
335
+ agg = aggregate_ecosystem(symbol)
336
+ return {
337
+ "symbol": symbol,
338
+ "aggregate": agg,
339
+ "spaces_detail": agg.get('signals_detail', {})
340
+ }
 
 
 
 
 
 
341
 
342
+ @app.get("/all")
343
+ async def all_signals():
344
+ """Сигналы по всем трём активам."""
345
+ results = {}
346
+ for sym in SYMBOLS:
347
+ results[sym] = aggregate_ecosystem(sym)
348
+ return results
349
 
350
  @app.post("/features")
351
  async def receive_features(data: Dict[str, Any]):
352
+ symbol = data.get("symbol", "XAU/USD")
353
  FEATURES_STORE[symbol] = {
354
  "features": data.get("features", {}),
355
  "price": data.get("price", 0.0),
 
358
  print(f"📥 MT5 {symbol}: {len(data.get('features', {}))} признаков")
359
  return {"status": "ok"}
360
 
361
+ print(f"🚀 SPACE 24 v1.0 — ECOSYSTEM AGGREGATOR ЗАПУЩЕН!")
362
+ print(f"🌐 Агрегирует Space 19-23 для XAU, ETH, SOL")
363
  print(f"✅ Готов к бою!")