tomirisg25 commited on
Commit
10ff5cf
·
verified ·
1 Parent(s): 524e174

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +84 -132
app.py CHANGED
@@ -21,7 +21,7 @@ for module_name, pip_name in REQUIRED_PACKAGES.items():
21
  print(f"✅ {pip_name} установлен!")
22
 
23
  # ============================================
24
- # 👑 TOMIRIS SPACE 26 v2.3 — OPTIONS & DERIVATIVES ENGINE (Hub-Connected)
25
  # ============================================
26
  import os, time, json, logging, asyncio
27
  from typing import Dict, Any, List, Optional
@@ -41,6 +41,9 @@ HUB_URL = os.getenv("SPACE17_URL", "https://tomiris-ai-name5-5.hf.space")
41
  TWELVE_DATA_KEY = os.getenv("TWELVE_DATA_KEY", "58e67e0008e24161ac9b1671b7c2d2d0")
42
  FRED_KEY = os.getenv("FRED_KEY", "faa11c8e2e4beee08c5b966e8b63a513")
43
 
 
 
 
44
  HISTORY_FILE = "options_history.json"
45
  CACHE_TTL = {
46
  "funding": 60, "oi": 60, "lsr": 120, "deribit": 300,
@@ -66,40 +69,31 @@ def breaker_open(name: str) -> bool:
66
 
67
  def breaker_record(name: str, success: bool):
68
  info = CIRCUIT_BREAKER.get(name, {"fails": 0, "last_fail": 0})
69
- if success:
70
- info["fails"] = 0
71
- else:
72
- info["fails"] += 1
73
- info["last_fail"] = time.time()
74
  CIRCUIT_BREAKER[name] = info
75
 
76
  if os.path.exists(HISTORY_FILE):
77
  try:
78
- with open(HISTORY_FILE) as f:
79
- OPTIONS_HISTORY = deque(json.load(f), maxlen=500)
80
- except:
81
- OPTIONS_HISTORY = deque(maxlen=500)
82
- else:
83
- OPTIONS_HISTORY = deque(maxlen=500)
84
 
85
  def save_history():
86
- with open(HISTORY_FILE, 'w') as f:
87
- json.dump(list(OPTIONS_HISTORY), f)
88
 
89
- # ================= ЗАГРУЗКА ДАННЫХ (без изменений) =================
90
  async def fetch_vix() -> Dict[str, Any]:
91
  if breaker_open("vix"): return {"vix": 20.0, "level": "NORMAL", "signal": "NEUTRAL"}
92
  try:
93
  r = await http_client.get(f"https://api.twelvedata.com/quote?symbol=VIX&apikey={TWELVE_DATA_KEY}")
94
  if r.status_code == 200:
95
- data = r.json()
96
- vix_val = float(data.get("close", 20))
97
  level = "HIGH" if vix_val > 30 else "ELEVATED" if vix_val > 25 else "NORMAL"
98
  signal = "BEARISH_RISK" if vix_val > 30 else "NEUTRAL"
99
  breaker_record("vix", True)
100
  return {"vix": vix_val, "level": level, "signal": signal}
101
- except:
102
- breaker_record("vix", False)
103
  return {"vix": 20.0, "level": "NORMAL", "signal": "NEUTRAL"}
104
 
105
  async def fetch_funding_rate(symbol: str) -> Dict[str, Any]:
@@ -113,8 +107,7 @@ async def fetch_funding_rate(symbol: str) -> Dict[str, Any]:
113
  signal = "BEARISH" if fr > 0.001 else "BULLISH" if fr < -0.001 else "NEUTRAL"
114
  breaker_record(f"funding_{symbol}", True)
115
  return {"funding_rate": fr, "funding_rate_pct": round(fr * 100, 4), "signal": signal}
116
- except:
117
- breaker_record(f"funding_{symbol}", False)
118
  return {"funding_rate": 0, "signal": "NEUTRAL"}
119
 
120
  async def fetch_open_interest(symbol: str) -> Dict[str, Any]:
@@ -123,15 +116,13 @@ async def fetch_open_interest(symbol: str) -> Dict[str, Any]:
123
  r = await http_client.get(f"https://fapi.binance.com/fapi/v1/openInterest?symbol={symbol}")
124
  if r.status_code == 200:
125
  oi = float(r.json().get("openInterest", 0))
126
- prev_key = f"oi_{symbol}_prev"
127
- prev_oi = cache_store.get(prev_key, oi)
128
  change = ((oi - prev_oi) / prev_oi * 100) if prev_oi > 0 else 0
129
  cache_store[prev_key] = oi
130
  signal = "BULLISH" if change > 3 else "BEARISH" if change < -3 else "NEUTRAL"
131
  breaker_record(f"oi_{symbol}", True)
132
  return {"open_interest": oi, "change_pct": round(change, 2), "signal": signal}
133
- except:
134
- breaker_record(f"oi_{symbol}", False)
135
  return {"open_interest": 0, "change_pct": 0, "signal": "NEUTRAL"}
136
 
137
  async def fetch_long_short_ratio(symbol: str) -> Dict[str, Any]:
@@ -140,15 +131,13 @@ async def fetch_long_short_ratio(symbol: str) -> Dict[str, Any]:
140
  r = await http_client.get(f"https://fapi.binance.com/fapi/v1/globalLongShortAccountRatio?symbol={symbol}&period=5m")
141
  if r.status_code == 200:
142
  lsr = float(r.json().get("longShortRatio", 1))
143
- long_pct = lsr / (1 + lsr) * 100
144
- short_pct = 100 - long_pct
145
  if lsr > 2.0: signal = "BEARISH"
146
  elif lsr < 0.5: signal = "BULLISH"
147
  else: signal = "NEUTRAL"
148
  breaker_record(f"lsr_{symbol}", True)
149
  return {"long_short_ratio": round(lsr, 4), "long_pct": round(long_pct, 1), "short_pct": round(short_pct, 1), "signal": signal}
150
- except:
151
- breaker_record(f"lsr_{symbol}", False)
152
  return {"long_short_ratio": 1, "signal": "NEUTRAL"}
153
 
154
  async def fetch_deribit_options(coin: str = "ETH") -> Dict[str, Any]:
@@ -163,8 +152,7 @@ async def fetch_deribit_options(coin: str = "ETH") -> Dict[str, Any]:
163
  signal = "BEARISH" if pcr > 1.3 else "BULLISH" if pcr < 0.7 else "NEUTRAL"
164
  breaker_record("deribit", True)
165
  return {"put_call_ratio_volume": round(pcr, 4), "signal": signal}
166
- except:
167
- breaker_record("deribit", False)
168
  return {"put_call_ratio_volume": 1.0, "signal": "NEUTRAL"}
169
 
170
  async def fetch_gold_derivatives() -> Dict[str, Any]:
@@ -179,24 +167,19 @@ async def fetch_gold_derivatives() -> Dict[str, Any]:
179
  signal = "BULLISH" if dxy_change < -2 and tips_current < 0 else "BEARISH" if dxy_change > 2 and tips_current > 0 else "NEUTRAL"
180
  breaker_record("fred", True)
181
  return {"dxy": dxy_vals[0] if dxy_vals else 104.5, "dxy_change_pct": round(dxy_change, 2), "tips_yield": tips_current, "signal": signal}
182
- except:
183
- breaker_record("fred", False)
184
  return {"signal": "NEUTRAL"}
185
 
186
  async def get_current_price(symbol: str) -> float:
187
  try:
188
  r = await http_client.get(f"{HUB_URL}/price/{symbol}")
189
- if r.status_code == 200:
190
- return float(r.json().get("mid", 0))
191
- except:
192
- pass
193
  bin_symbol = symbol.replace("/", "")
194
  try:
195
  r = await http_client.get(f"https://fapi.binance.com/fapi/v1/ticker/price?symbol={bin_symbol}")
196
- if r.status_code == 200:
197
- return float(r.json()["price"])
198
- except:
199
- pass
200
  return 0.0
201
 
202
  def calculate_max_pain(current_price: float) -> float:
@@ -208,101 +191,71 @@ async def analyze_derivatives(symbol: str) -> Dict[str, Any]:
208
  bin_sym = "" if native == "XAU" else (native + "USDT")
209
 
210
  tasks = []
211
- if bin_sym:
212
- tasks.extend([fetch_funding_rate(bin_sym), fetch_open_interest(bin_sym), fetch_long_short_ratio(bin_sym)])
213
- if native == "ETH":
214
- tasks.append(fetch_deribit_options("ETH"))
215
- if native == "XAU":
216
- tasks.append(fetch_gold_derivatives())
217
  tasks.append(fetch_vix())
218
 
219
  results = await asyncio.gather(*tasks)
220
- idx = 0
221
- metrics = {}
222
  if bin_sym:
223
  metrics['funding_rate'] = results[idx]; idx += 1
224
  metrics['open_interest'] = results[idx]; idx += 1
225
  metrics['long_short_ratio'] = results[idx]; idx += 1
226
- if native == "ETH":
227
- metrics['options'] = results[idx]; idx += 1
228
- if native == "XAU":
229
- metrics['gold_derivatives'] = results[idx]; idx += 1
230
  vix_data = results[idx]
231
 
232
- signals = []
233
- score = 50.0
234
 
235
  if vix_data["vix"] > 30:
236
  signals.append({"source": "VIX", "signal": "BEARISH_RISK", "reason": f"VIX={vix_data['vix']:.1f}"})
237
  score += 15 if native == "XAU" else -10
238
- elif vix_data["vix"] > 25:
239
- score += 5
240
 
241
  fund = metrics.get('funding_rate', {})
242
  if fund.get('signal') == 'BEARISH':
243
- signals.append({"source": "FUNDING", "signal": "BEARISH", "reason": f"Перегретый лонг ({fund.get('funding_rate_pct')}%)"})
244
- score -= 15
245
  elif fund.get('signal') == 'BULLISH':
246
- signals.append({"source": "FUNDING", "signal": "BULLISH", "reason": f"Перегретый шорт ({fund.get('funding_rate_pct')}%)"})
247
- score += 15
248
 
249
  oi = metrics.get('open_interest', {})
250
  if oi.get('signal') == 'BEARISH':
251
- signals.append({"source": "OI", "signal": "BEARISH", "reason": f"OI перегрет ({oi.get('change_pct')}%)"})
252
- score -= 10
253
  elif oi.get('signal') == 'BULLISH':
254
- signals.append({"source": "OI", "signal": "BULLISH", "reason": f"OI растёт ({oi.get('change_pct')}%)"})
255
- score += 10
256
 
257
  lsr = metrics.get('long_short_ratio', {})
258
  if lsr.get('signal') == 'BEARISH':
259
- signals.append({"source": "L/S", "signal": "BEARISH", "reason": f"Слишком много лонгов ({lsr.get('long_pct')}%)"})
260
- score -= 10
261
  elif lsr.get('signal') == 'BULLISH':
262
- signals.append({"source": "L/S", "signal": "BULLISH", "reason": f"Много шортов ({lsr.get('short_pct')}%)"})
263
- score += 10
264
 
265
  options = metrics.get('options', {})
266
  if options.get('signal') == 'BEARISH':
267
- signals.append({"source": "OPTIONS", "signal": "BEARISH", "reason": f"PCR={options.get('put_call_ratio_volume')}"})
268
- score -= 8
269
  elif options.get('signal') == 'BULLISH':
270
- signals.append({"source": "OPTIONS", "signal": "BULLISH", "reason": f"PCR={options.get('put_call_ratio_volume')}"})
271
- score += 8
272
 
273
  gold = metrics.get('gold_derivatives', {})
274
  if gold.get('signal') == 'BULLISH':
275
- signals.append({"source": "COT/DXY/TIPS", "signal": "BULLISH", "reason": "Макро-факторы за золото"})
276
- score += 12
277
  elif gold.get('signal') == 'BEARISH':
278
- signals.append({"source": "COT/DXY/TIPS", "signal": "BEARISH", "reason": "Макро-факторы против золота"})
279
- score -= 12
280
 
281
  price = await get_current_price(symbol) if native != "XAU" else 0
282
  max_pain = calculate_max_pain(price) if price > 0 else 0
283
  if max_pain > 0 and price < max_pain:
284
- signals.append({"source": "MAX_PAIN", "signal": "BULLISH", "reason": f"Цена ниже Max Pain ({max_pain})"})
285
- score += 8
286
  elif max_pain > 0 and price > max_pain:
287
- signals.append({"source": "MAX_PAIN", "signal": "BEARISH", "reason": f"Цена выше Max Pain ({max_pain})"})
288
- score -= 8
289
 
290
  score = max(0, min(100, score))
291
- if score > 60:
292
- direction, confidence = "LONG", score / 100
293
- elif score < 40:
294
- direction, confidence = "SHORT", (100 - score) / 100
295
- else:
296
- direction, confidence = "WAIT", 0.0
297
-
298
- analysis = {
299
- "derivative_score": score,
300
- "direction": direction,
301
- "confidence": round(confidence, 4),
302
- "signals": signals,
303
- "metrics": {**metrics, "vix": vix_data, "max_pain": max_pain}
304
- }
305
 
 
306
  OPTIONS_HISTORY.append({"timestamp": datetime.now(timezone.utc).isoformat(), "symbol": symbol, "direction": direction, "score": score})
307
  save_history()
308
  return analysis
@@ -310,50 +263,50 @@ async def analyze_derivatives(symbol: str) -> Dict[str, Any]:
310
  # ================= ОТПРАВКА В HUB =================
311
  async def send_signal_to_hub(symbol: str, direction: str, confidence: float):
312
  try:
313
- await http_client.post(f"{HUB_URL}/signal", json={
314
- "space": "space_26_options",
315
- "symbol": symbol,
316
- "direction": direction,
317
- "confidence": confidence,
318
  "raw": json.dumps({"source": "space_26_options"})
319
- })
320
- logger.info(f"📤 {symbol}: {direction} conf={confidence:.3f} отправлен в Hub")
321
- except Exception as e:
322
- logger.error(f"Ошибка отправки в Hub: {e}")
323
 
324
  # ================= ГЛАВНЫЙ СИГНАЛ =================
325
  async def get_derivative_signal(symbol: str = "ETH/USD") -> Dict[str, Any]:
326
- start = time.time()
327
- analysis = await analyze_derivatives(symbol)
328
  latency = int((time.time() - start) * 1000)
329
-
330
- # Отправка в Hub
331
  await send_signal_to_hub(symbol, analysis['direction'], analysis['confidence'])
332
-
333
- result = {
334
- "space": "space_26_options",
335
- "timestamp": int(time.time()),
336
- "symbol": symbol,
337
- "signal": {"direction": analysis['direction'], "confidence": analysis['confidence']},
338
- "derivative_analysis": analysis,
339
- "latency_ms": latency
340
- }
341
-
342
- logger.info(f"📊 Options {symbol}: {analysis['direction']} conf={analysis['confidence']:.3f} score={analysis['derivative_score']}")
343
  return result
344
 
 
 
 
 
 
 
 
 
 
 
 
 
345
  # ================= FASTAPI =================
346
- app = FastAPI(title="Tomiris Space 26 v2.3 — Options & Derivatives Engine (Hub)")
347
 
348
  @app.on_event("startup")
349
- async def startup(): pass
 
 
350
 
351
  @app.on_event("shutdown")
352
  async def shutdown(): await http_client.aclose()
353
 
354
  @app.get("/health")
355
  async def health():
356
- return {"status": "operational", "version": "2.3", "hub_connected": True, "features": ["VIX", "Funding", "OI", "L/S", "Options PCR", "COT/DXY/TIPS", "Max Pain"]}
357
 
358
  @app.get("/consilium")
359
  async def consilium(symbol: str = Query("ETH/USD")):
@@ -362,33 +315,32 @@ async def consilium(symbol: str = Query("ETH/USD")):
362
 
363
  @app.get("/funding/{symbol}")
364
  async def funding(symbol: str): return await fetch_funding_rate(symbol.upper())
365
-
366
  @app.get("/oi/{symbol}")
367
  async def oi(symbol: str): return await fetch_open_interest(symbol.upper())
368
-
369
  @app.get("/lsr/{symbol}")
370
  async def lsr(symbol: str): return await fetch_long_short_ratio(symbol.upper())
371
-
372
  @app.get("/options")
373
  async def options(): return await fetch_deribit_options("ETH")
374
-
375
  @app.get("/gold")
376
  async def gold(): return await fetch_gold_derivatives()
377
-
378
  @app.get("/vix")
379
  async def vix(): return await fetch_vix()
380
-
381
  @app.get("/maxpain/{symbol}")
382
  async def maxpain(symbol: str):
383
- price = await get_current_price(symbol)
384
- mp = calculate_max_pain(price)
385
  return {"symbol": symbol, "current_price": price, "max_pain": mp}
386
-
387
  @app.get("/history")
388
  async def history(limit: int = 50): return list(OPTIONS_HISTORY)[-limit:]
 
 
 
 
 
 
 
389
 
390
  if __name__ == "__main__":
391
  import uvicorn
392
  uvicorn.run(app, host="0.0.0.0", port=7860)
393
 
394
- print("🚀 SPACE 26 v2.3 — OPTIONS & DERIVATIVES ENGINE (Hub-Connected) ЗАПУЩЕН!")
 
21
  print(f"✅ {pip_name} установлен!")
22
 
23
  # ============================================
24
+ # 👑 TOMIRIS SPACE 26 v2.4 — OPTIONS & DERIVATIVES ENGINE (АВТО-ОТПРАВКА)
25
  # ============================================
26
  import os, time, json, logging, asyncio
27
  from typing import Dict, Any, List, Optional
 
41
  TWELVE_DATA_KEY = os.getenv("TWELVE_DATA_KEY", "58e67e0008e24161ac9b1671b7c2d2d0")
42
  FRED_KEY = os.getenv("FRED_KEY", "faa11c8e2e4beee08c5b966e8b63a513")
43
 
44
+ # Интервал авто-отправки
45
+ AUTO_SEND_INTERVAL = int(os.getenv("AUTO_SEND_INTERVAL", "300"))
46
+
47
  HISTORY_FILE = "options_history.json"
48
  CACHE_TTL = {
49
  "funding": 60, "oi": 60, "lsr": 120, "deribit": 300,
 
69
 
70
  def breaker_record(name: str, success: bool):
71
  info = CIRCUIT_BREAKER.get(name, {"fails": 0, "last_fail": 0})
72
+ if success: info["fails"] = 0
73
+ else: info["fails"] += 1; info["last_fail"] = time.time()
 
 
 
74
  CIRCUIT_BREAKER[name] = info
75
 
76
  if os.path.exists(HISTORY_FILE):
77
  try:
78
+ with open(HISTORY_FILE) as f: OPTIONS_HISTORY = deque(json.load(f), maxlen=500)
79
+ except: OPTIONS_HISTORY = deque(maxlen=500)
80
+ else: OPTIONS_HISTORY = deque(maxlen=500)
 
 
 
81
 
82
  def save_history():
83
+ with open(HISTORY_FILE, 'w') as f: json.dump(list(OPTIONS_HISTORY), f)
 
84
 
85
+ # ================= ЗАГРУЗКА ДАННЫХ =================
86
  async def fetch_vix() -> Dict[str, Any]:
87
  if breaker_open("vix"): return {"vix": 20.0, "level": "NORMAL", "signal": "NEUTRAL"}
88
  try:
89
  r = await http_client.get(f"https://api.twelvedata.com/quote?symbol=VIX&apikey={TWELVE_DATA_KEY}")
90
  if r.status_code == 200:
91
+ data = r.json(); vix_val = float(data.get("close", 20))
 
92
  level = "HIGH" if vix_val > 30 else "ELEVATED" if vix_val > 25 else "NORMAL"
93
  signal = "BEARISH_RISK" if vix_val > 30 else "NEUTRAL"
94
  breaker_record("vix", True)
95
  return {"vix": vix_val, "level": level, "signal": signal}
96
+ except: breaker_record("vix", False)
 
97
  return {"vix": 20.0, "level": "NORMAL", "signal": "NEUTRAL"}
98
 
99
  async def fetch_funding_rate(symbol: str) -> Dict[str, Any]:
 
107
  signal = "BEARISH" if fr > 0.001 else "BULLISH" if fr < -0.001 else "NEUTRAL"
108
  breaker_record(f"funding_{symbol}", True)
109
  return {"funding_rate": fr, "funding_rate_pct": round(fr * 100, 4), "signal": signal}
110
+ except: breaker_record(f"funding_{symbol}", False)
 
111
  return {"funding_rate": 0, "signal": "NEUTRAL"}
112
 
113
  async def fetch_open_interest(symbol: str) -> Dict[str, Any]:
 
116
  r = await http_client.get(f"https://fapi.binance.com/fapi/v1/openInterest?symbol={symbol}")
117
  if r.status_code == 200:
118
  oi = float(r.json().get("openInterest", 0))
119
+ prev_key = f"oi_{symbol}_prev"; prev_oi = cache_store.get(prev_key, oi)
 
120
  change = ((oi - prev_oi) / prev_oi * 100) if prev_oi > 0 else 0
121
  cache_store[prev_key] = oi
122
  signal = "BULLISH" if change > 3 else "BEARISH" if change < -3 else "NEUTRAL"
123
  breaker_record(f"oi_{symbol}", True)
124
  return {"open_interest": oi, "change_pct": round(change, 2), "signal": signal}
125
+ except: breaker_record(f"oi_{symbol}", False)
 
126
  return {"open_interest": 0, "change_pct": 0, "signal": "NEUTRAL"}
127
 
128
  async def fetch_long_short_ratio(symbol: str) -> Dict[str, Any]:
 
131
  r = await http_client.get(f"https://fapi.binance.com/fapi/v1/globalLongShortAccountRatio?symbol={symbol}&period=5m")
132
  if r.status_code == 200:
133
  lsr = float(r.json().get("longShortRatio", 1))
134
+ long_pct = lsr / (1 + lsr) * 100; short_pct = 100 - long_pct
 
135
  if lsr > 2.0: signal = "BEARISH"
136
  elif lsr < 0.5: signal = "BULLISH"
137
  else: signal = "NEUTRAL"
138
  breaker_record(f"lsr_{symbol}", True)
139
  return {"long_short_ratio": round(lsr, 4), "long_pct": round(long_pct, 1), "short_pct": round(short_pct, 1), "signal": signal}
140
+ except: breaker_record(f"lsr_{symbol}", False)
 
141
  return {"long_short_ratio": 1, "signal": "NEUTRAL"}
142
 
143
  async def fetch_deribit_options(coin: str = "ETH") -> Dict[str, Any]:
 
152
  signal = "BEARISH" if pcr > 1.3 else "BULLISH" if pcr < 0.7 else "NEUTRAL"
153
  breaker_record("deribit", True)
154
  return {"put_call_ratio_volume": round(pcr, 4), "signal": signal}
155
+ except: breaker_record("deribit", False)
 
156
  return {"put_call_ratio_volume": 1.0, "signal": "NEUTRAL"}
157
 
158
  async def fetch_gold_derivatives() -> Dict[str, Any]:
 
167
  signal = "BULLISH" if dxy_change < -2 and tips_current < 0 else "BEARISH" if dxy_change > 2 and tips_current > 0 else "NEUTRAL"
168
  breaker_record("fred", True)
169
  return {"dxy": dxy_vals[0] if dxy_vals else 104.5, "dxy_change_pct": round(dxy_change, 2), "tips_yield": tips_current, "signal": signal}
170
+ except: breaker_record("fred", False)
 
171
  return {"signal": "NEUTRAL"}
172
 
173
  async def get_current_price(symbol: str) -> float:
174
  try:
175
  r = await http_client.get(f"{HUB_URL}/price/{symbol}")
176
+ if r.status_code == 200: return float(r.json().get("mid", 0))
177
+ except: pass
 
 
178
  bin_symbol = symbol.replace("/", "")
179
  try:
180
  r = await http_client.get(f"https://fapi.binance.com/fapi/v1/ticker/price?symbol={bin_symbol}")
181
+ if r.status_code == 200: return float(r.json()["price"])
182
+ except: pass
 
 
183
  return 0.0
184
 
185
  def calculate_max_pain(current_price: float) -> float:
 
191
  bin_sym = "" if native == "XAU" else (native + "USDT")
192
 
193
  tasks = []
194
+ if bin_sym: tasks.extend([fetch_funding_rate(bin_sym), fetch_open_interest(bin_sym), fetch_long_short_ratio(bin_sym)])
195
+ if native == "ETH": tasks.append(fetch_deribit_options("ETH"))
196
+ if native == "XAU": tasks.append(fetch_gold_derivatives())
 
 
 
197
  tasks.append(fetch_vix())
198
 
199
  results = await asyncio.gather(*tasks)
200
+ idx = 0; metrics = {}
 
201
  if bin_sym:
202
  metrics['funding_rate'] = results[idx]; idx += 1
203
  metrics['open_interest'] = results[idx]; idx += 1
204
  metrics['long_short_ratio'] = results[idx]; idx += 1
205
+ if native == "ETH": metrics['options'] = results[idx]; idx += 1
206
+ if native == "XAU": metrics['gold_derivatives'] = results[idx]; idx += 1
 
 
207
  vix_data = results[idx]
208
 
209
+ signals = []; score = 50.0
 
210
 
211
  if vix_data["vix"] > 30:
212
  signals.append({"source": "VIX", "signal": "BEARISH_RISK", "reason": f"VIX={vix_data['vix']:.1f}"})
213
  score += 15 if native == "XAU" else -10
214
+ elif vix_data["vix"] > 25: score += 5
 
215
 
216
  fund = metrics.get('funding_rate', {})
217
  if fund.get('signal') == 'BEARISH':
218
+ signals.append({"source": "FUNDING", "signal": "BEARISH", "reason": f"Перегретый лонг ({fund.get('funding_rate_pct')}%)"}); score -= 15
 
219
  elif fund.get('signal') == 'BULLISH':
220
+ signals.append({"source": "FUNDING", "signal": "BULLISH", "reason": f"Перегретый шорт ({fund.get('funding_rate_pct')}%)"}); score += 15
 
221
 
222
  oi = metrics.get('open_interest', {})
223
  if oi.get('signal') == 'BEARISH':
224
+ signals.append({"source": "OI", "signal": "BEARISH", "reason": f"OI перегрет ({oi.get('change_pct')}%)"}); score -= 10
 
225
  elif oi.get('signal') == 'BULLISH':
226
+ signals.append({"source": "OI", "signal": "BULLISH", "reason": f"OI растёт ({oi.get('change_pct')}%)"}); score += 10
 
227
 
228
  lsr = metrics.get('long_short_ratio', {})
229
  if lsr.get('signal') == 'BEARISH':
230
+ signals.append({"source": "L/S", "signal": "BEARISH", "reason": f"Слишком много лонгов ({lsr.get('long_pct')}%)"}); score -= 10
 
231
  elif lsr.get('signal') == 'BULLISH':
232
+ signals.append({"source": "L/S", "signal": "BULLISH", "reason": f"Много шортов ({lsr.get('short_pct')}%)"}); score += 10
 
233
 
234
  options = metrics.get('options', {})
235
  if options.get('signal') == 'BEARISH':
236
+ signals.append({"source": "OPTIONS", "signal": "BEARISH", "reason": f"PCR={options.get('put_call_ratio_volume')}"}); score -= 8
 
237
  elif options.get('signal') == 'BULLISH':
238
+ signals.append({"source": "OPTIONS", "signal": "BULLISH", "reason": f"PCR={options.get('put_call_ratio_volume')}"}); score += 8
 
239
 
240
  gold = metrics.get('gold_derivatives', {})
241
  if gold.get('signal') == 'BULLISH':
242
+ signals.append({"source": "COT/DXY/TIPS", "signal": "BULLISH", "reason": "Макро-факторы за золото"}); score += 12
 
243
  elif gold.get('signal') == 'BEARISH':
244
+ signals.append({"source": "COT/DXY/TIPS", "signal": "BEARISH", "reason": "Макро-факторы против золота"}); score -= 12
 
245
 
246
  price = await get_current_price(symbol) if native != "XAU" else 0
247
  max_pain = calculate_max_pain(price) if price > 0 else 0
248
  if max_pain > 0 and price < max_pain:
249
+ signals.append({"source": "MAX_PAIN", "signal": "BULLISH", "reason": f"Цена ниже Max Pain ({max_pain})"}); score += 8
 
250
  elif max_pain > 0 and price > max_pain:
251
+ signals.append({"source": "MAX_PAIN", "signal": "BEARISH", "reason": f"Цена выше Max Pain ({max_pain})"}); score -= 8
 
252
 
253
  score = max(0, min(100, score))
254
+ if score > 60: direction, confidence = "LONG", score / 100
255
+ elif score < 40: direction, confidence = "SHORT", (100 - score) / 100
256
+ else: direction, confidence = "WAIT", 0.0
 
 
 
 
 
 
 
 
 
 
 
257
 
258
+ analysis = {"derivative_score": score, "direction": direction, "confidence": round(confidence, 4), "signals": signals, "metrics": {**metrics, "vix": vix_data, "max_pain": max_pain}}
259
  OPTIONS_HISTORY.append({"timestamp": datetime.now(timezone.utc).isoformat(), "symbol": symbol, "direction": direction, "score": score})
260
  save_history()
261
  return analysis
 
263
  # ================= ОТПРАВКА В HUB =================
264
  async def send_signal_to_hub(symbol: str, direction: str, confidence: float):
265
  try:
266
+ resp = await http_client.post(f"{HUB_URL}/signal", json={
267
+ "space": "space_26_options", "symbol": symbol,
268
+ "direction": direction, "confidence": confidence,
 
 
269
  "raw": json.dumps({"source": "space_26_options"})
270
+ }, timeout=10)
271
+ if resp.status_code == 200: logger.info(f"📤 {symbol}: {direction} conf={confidence:.3f} отправлен в Hub")
272
+ else: logger.warning(f"Hub вернул {resp.status_code}")
273
+ except Exception as e: logger.error(f"Ошибка отправки в Hub: {e}")
274
 
275
  # ================= ГЛАВНЫЙ СИГНАЛ =================
276
  async def get_derivative_signal(symbol: str = "ETH/USD") -> Dict[str, Any]:
277
+ start = time.time(); analysis = await analyze_derivatives(symbol)
 
278
  latency = int((time.time() - start) * 1000)
 
 
279
  await send_signal_to_hub(symbol, analysis['direction'], analysis['confidence'])
280
+ result = {"space": "space_26_options", "timestamp": int(time.time()), "symbol": symbol, "signal": {"direction": analysis['direction'], "confidence": analysis['confidence']}, "derivative_analysis": analysis, "latency_ms": latency}
281
+ logger.info(f"📊 Options {symbol}: {analysis['direction']} conf={analysis['confidence']:.3f}")
 
 
 
 
 
 
 
 
 
282
  return result
283
 
284
+ # ================= АВТО-ОТПРАВКА =================
285
+ async def auto_send_loop():
286
+ logger.info(f"🔄 Авто-отправка Options Engine запущена (интервал {AUTO_SEND_INTERVAL}с)")
287
+ await asyncio.sleep(30)
288
+ while True:
289
+ try:
290
+ for symbol in SYMBOLS:
291
+ await get_derivative_signal(symbol); await asyncio.sleep(2)
292
+ logger.info("✅ Options Engine авто-отправка завершена")
293
+ except Exception as e: logger.error(f"Ошибка авто-отправки: {e}")
294
+ await asyncio.sleep(AUTO_SEND_INTERVAL)
295
+
296
  # ================= FASTAPI =================
297
+ app = FastAPI(title="Tomiris Space 26 v2.4 — Options & Derivatives Engine (Auto-Hub)")
298
 
299
  @app.on_event("startup")
300
+ async def startup():
301
+ asyncio.create_task(auto_send_loop())
302
+ logger.info("🚀 Space 26 v2.4 запущен с авто-отправкой в Hub")
303
 
304
  @app.on_event("shutdown")
305
  async def shutdown(): await http_client.aclose()
306
 
307
  @app.get("/health")
308
  async def health():
309
+ return {"status": "operational", "version": "2.4", "hub_url": HUB_URL, "auto_send_interval": AUTO_SEND_INTERVAL, "features": ["VIX", "Funding", "OI", "L/S", "Options PCR", "COT/DXY/TIPS", "Max Pain"]}
310
 
311
  @app.get("/consilium")
312
  async def consilium(symbol: str = Query("ETH/USD")):
 
315
 
316
  @app.get("/funding/{symbol}")
317
  async def funding(symbol: str): return await fetch_funding_rate(symbol.upper())
 
318
  @app.get("/oi/{symbol}")
319
  async def oi(symbol: str): return await fetch_open_interest(symbol.upper())
 
320
  @app.get("/lsr/{symbol}")
321
  async def lsr(symbol: str): return await fetch_long_short_ratio(symbol.upper())
 
322
  @app.get("/options")
323
  async def options(): return await fetch_deribit_options("ETH")
 
324
  @app.get("/gold")
325
  async def gold(): return await fetch_gold_derivatives()
 
326
  @app.get("/vix")
327
  async def vix(): return await fetch_vix()
 
328
  @app.get("/maxpain/{symbol}")
329
  async def maxpain(symbol: str):
330
+ price = await get_current_price(symbol); mp = calculate_max_pain(price)
 
331
  return {"symbol": symbol, "current_price": price, "max_pain": mp}
 
332
  @app.get("/history")
333
  async def history(limit: int = 50): return list(OPTIONS_HISTORY)[-limit:]
334
+ @app.get("/send_now")
335
+ async def send_now():
336
+ results = {}
337
+ for symbol in SYMBOLS:
338
+ analysis = await get_derivative_signal(symbol)
339
+ results[symbol] = analysis.get("signal", {}).get("direction", "WAIT")
340
+ return {"status": "sent", "results": results}
341
 
342
  if __name__ == "__main__":
343
  import uvicorn
344
  uvicorn.run(app, host="0.0.0.0", port=7860)
345
 
346
+ print("🚀 SPACE 26 v2.4 — OPTIONS & DERIVATIVES ENGINE (АВТ��-ОТПРАВКА) ЗАПУЩЕН!")