tomirisai80 commited on
Commit
38cef9a
·
verified ·
1 Parent(s): 5cb303b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +179 -96
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 21 v3.2 — GOLD MACRO & FLOW ENGINE (АВТО-ОТПРАВКА)
25
  # ============================================
26
  import os, time, json, logging, asyncio
27
  from typing import Dict, Any, List, Optional
@@ -36,47 +36,96 @@ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(name)s - %(level
36
  logger = logging.getLogger("Space21_GoldMacro")
37
 
38
  # ================= КОНФИГУРАЦИЯ =================
 
 
39
  SYMBOL = "XAU/USD"
40
- HUB_URL = os.getenv("SPACE17_URL", "https://tomiris-ai-name5-5.hf.space")
41
- TWELVE_DATA_KEY = os.getenv("TWELVE_DATA_KEY", "f33e660d8c1945d19e3c4ded72a2875e")
 
 
 
 
42
  FRED_KEY = os.getenv("FRED_KEY", "faa11c8e2e4beee08c5b966e8b63a513")
43
- NEWSAPI_KEY = os.getenv("NEWSAPI_KEY", "948c7816beea47baa23b054592472d0e")
 
 
 
44
 
45
- # Интервал авто-отправки
46
- AUTO_SEND_INTERVAL = int(os.getenv("AUTO_SEND_INTERVAL", "300"))
 
47
 
48
  GOLD_ETFS = ["GLD", "IAU", "GDX"]
49
  CACHE_TTL = {"fred": 3600, "etf_aum": 600, "cot": 86400, "gpr": 900}
50
  CACHE: Dict[str, Any] = {}
51
 
52
  # ================= HTTP КЛИЕНТ =================
53
- http_client = httpx.AsyncClient(timeout=15.0)
 
54
 
55
- # ================= ОТПРАВКА В HUB =================
56
- async def send_signal_to_hub(symbol: str, direction: str, confidence: float):
57
- try:
58
- resp = await http_client.post(f"{HUB_URL}/signal", json={
59
- "space": "space_21_gold_macro",
60
- "symbol": symbol,
61
- "direction": direction,
62
- "confidence": confidence,
63
- "raw": json.dumps({"source": "space_21_gold_macro"})
64
- }, timeout=10)
65
- if resp.status_code == 200:
66
- logger.info(f"📤 {symbol}: {direction} conf={confidence:.3f} отправлен в Hub")
67
- else:
68
- logger.warning(f"Hub вернул {resp.status_code}")
69
- except Exception as e:
70
- logger.error(f"Ошибка отправки в Hub: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
  # ================= ЗАГРУЗКА ДАННЫХ =================
73
  async def fetch_fred_series(series_id: str, days: int = 30) -> List[Dict]:
 
 
74
  try:
75
  r = await http_client.get(
76
- f"https://api.stlouisfed.org/fred/series/observations?series_id={series_id}&api_key={FRED_KEY}&file_type=json&sort_order=desc&limit={days}")
 
77
  if r.status_code == 200:
78
  data = r.json()
79
- return [{'date': obs['date'], 'value': float(obs['value'])} for obs in data.get('observations', []) if obs['value'] != '.']
 
 
 
 
 
 
 
 
 
80
  except Exception as e:
81
  logger.warning(f"FRED {series_id}: {e}")
82
  return []
@@ -86,13 +135,14 @@ async def fetch_dxy() -> Dict[str, Any]:
86
  if len(values) >= 2:
87
  current = values[0]['value']
88
  month_ago = values[-1]['value']
89
- change = ((current - month_ago) / month_ago) * 100
90
- return {
91
- 'dxy': round(current, 2),
92
- 'change_1m': round(change, 2),
93
- 'trend': 'STRENGTHENING' if change > 2 else 'WEAKENING' if change < -2 else 'STABLE',
94
- 'gold_signal': 'BULLISH' if change < -2 else 'BEARISH' if change > 2 else 'NEUTRAL'
95
- }
 
96
  return {'dxy': 104.5, 'gold_signal': 'NEUTRAL'}
97
 
98
  async def fetch_tips_yield() -> Dict[str, Any]:
@@ -107,6 +157,8 @@ async def fetch_tips_yield() -> Dict[str, Any]:
107
  return {'tips_yield': 0.5, 'gold_signal': 'NEUTRAL'}
108
 
109
  async def fetch_etf_aum(ticker: str) -> Optional[float]:
 
 
110
  try:
111
  r = await http_client.get(f"https://api.twelvedata.com/statistics?symbol={ticker}&apikey={TWELVE_DATA_KEY}")
112
  if r.status_code == 200:
@@ -121,14 +173,17 @@ async def analyze_etf_flows() -> Dict[str, Any]:
121
  etf_data = {}
122
  total_change = 0.0
123
  count = 0
 
124
  for ticker in GOLD_ETFS:
125
  aum_now = await fetch_etf_aum(ticker)
126
  if aum_now is None:
127
  continue
 
128
  prev_key = f"aum_{ticker}_prev"
129
  prev_aum = CACHE.get(prev_key, aum_now)
130
  change = aum_now - prev_aum
131
  change_pct = (change / prev_aum * 100) if prev_aum > 0 else 0
 
132
  etf_data[ticker] = {
133
  "aum": aum_now,
134
  "change_24h": round(change_pct, 2),
@@ -139,6 +194,7 @@ async def analyze_etf_flows() -> Dict[str, Any]:
139
  CACHE[prev_key] = aum_now
140
 
141
  avg_change = total_change / count if count else 0
 
142
  if avg_change > 1:
143
  flow_signal, gold_signal = "STRONG_INFLOW", "BULLISH"
144
  elif avg_change > 0:
@@ -148,6 +204,7 @@ async def analyze_etf_flows() -> Dict[str, Any]:
148
  else:
149
  flow_signal, gold_signal = "NEUTRAL", "NEUTRAL"
150
 
 
151
  return {
152
  "etfs": etf_data,
153
  "avg_change_pct": round(avg_change, 2),
@@ -157,56 +214,74 @@ async def analyze_etf_flows() -> Dict[str, Any]:
157
 
158
  async def fetch_cot_report() -> Dict[str, Any]:
159
  try:
160
- df = pd.read_csv("https://raw.githubusercontent.com/datasets/cftc-commitment-of-traders/main/data/gold.csv")
161
- if not df.empty:
162
- noncomm_net = df['Noncommercial_Long'] - df['Noncommercial_Short']
163
- latest_net = int(noncomm_net.iloc[-1])
164
- lookback = min(156, len(noncomm_net))
165
- recent = noncomm_net.iloc[-lookback:]
166
- percentile = (recent < latest_net).mean() * 100
167
- if percentile > 80:
168
- cot_signal = "BULLISH_EXTREME"
169
- elif percentile > 60:
170
- cot_signal = "BULLISH"
171
- elif percentile < 20:
172
- cot_signal = "BEARISH_EXTREME"
173
- elif percentile < 40:
174
- cot_signal = "BEARISH"
175
- else:
176
- cot_signal = "NEUTRAL"
177
-
178
- return {
179
- 'report_date': str(df['Date'].iloc[-1]),
180
- 'noncommercial_net': latest_net,
181
- 'percentile': round(percentile, 1),
182
- 'cot_signal': cot_signal,
183
- 'gold_signal': 'BULLISH' if 'BULLISH' in cot_signal else 'BEARISH' if 'BEARISH' in cot_signal else 'NEUTRAL'
184
- }
 
 
 
 
 
 
 
185
  except Exception as e:
186
  logger.warning(f"COT error: {e}")
187
  return {'cot_signal': 'NEUTRAL', 'gold_signal': 'NEUTRAL'}
188
 
189
  async def fetch_gpr() -> Dict[str, Any]:
 
 
190
  try:
191
- r = await http_client.get(f"https://newsapi.org/v2/everything?q=geopolitical+war+sanctions&pageSize=5&apiKey={NEWSAPI_KEY}")
 
 
192
  if r.status_code == 200:
193
  total = r.json().get('totalResults', 0)
194
- r_all = await http_client.get(f"https://newsapi.org/v2/everything?q=all&pageSize=5&apiKey={NEWSAPI_KEY}")
 
 
195
  total_all = r_all.json().get('totalResults', 1) if r_all.status_code == 200 else 1000
196
  ratio = total / max(total_all, 1)
 
197
  if ratio > 0.15:
198
  level, gold_signal = "HIGH", "BULLISH"
199
  elif ratio > 0.08:
200
  level, gold_signal = "ELEVATED", "SLIGHTLY_BULLISH"
201
  else:
202
  level, gold_signal = "LOW", "NEUTRAL"
 
 
203
  return {'gpr_level': level, 'mentions_share': round(ratio, 4), 'gold_signal': gold_signal}
204
- except:
205
- pass
206
- return {'gpr_level': 'LOW', 'gold_signal': 'NEUTRAL'}
207
 
208
  # ================= ГЛАВНЫЙ МАКРО‑АНАЛИЗ =================
209
  async def analyze_gold_macro() -> Dict[str, Any]:
 
 
210
  dxy, tips, etf, cot, gpr = await asyncio.gather(
211
  fetch_dxy(), fetch_tips_yield(), analyze_etf_flows(), fetch_cot_report(), fetch_gpr()
212
  )
@@ -250,16 +325,21 @@ async def analyze_gold_macro() -> Dict[str, Any]:
250
 
251
  score = max(0, min(100, score))
252
 
 
253
  if score > 60:
254
- direction, confidence = "LONG", score / 100
 
255
  elif score < 40:
256
- direction, confidence = "SHORT", (100 - score) / 100
 
257
  else:
258
- direction, confidence = "WAIT", 0.0
 
259
 
 
260
  return {
261
  "macro_score": score,
262
- "direction": direction,
263
  "confidence": round(confidence, 4),
264
  "signals": signals,
265
  "metrics": {
@@ -275,48 +355,44 @@ async def analyze_gold_macro() -> Dict[str, Any]:
275
  async def get_gold_macro_signal() -> Dict[str, Any]:
276
  start = time.time()
277
  analysis = await analyze_gold_macro()
278
- latency = int((time.time() - start) * 1000)
279
-
280
- await send_signal_to_hub(SYMBOL, analysis['direction'], analysis['confidence'])
281
-
282
- result = {
283
- "space": "space_21_gold_macro",
284
- "timestamp": int(time.time()),
 
 
 
 
 
285
  "symbol": SYMBOL,
286
- "signal": {
287
- "direction": analysis['direction'],
288
- "confidence": analysis['confidence']
289
- },
290
- "macro_analysis": {
291
- "score": analysis['macro_score'],
292
- "signals": analysis['signals'],
293
- "metrics": analysis['metrics']
294
- },
295
- "latency_ms": latency
296
  }
297
 
298
- logger.info(f"🥇 Gold Macro: {analysis['direction']} | Score={analysis['macro_score']}")
299
- return result
300
-
301
  # ================= АВТО-ОТПРАВКА =================
302
  async def auto_send_loop():
303
- logger.info(f"🔄 Авто-отправка Gold Macro запущена (интервал {AUTO_SEND_INTERVAL)")
304
- await asyncio.sleep(30)
 
305
  while True:
306
  try:
307
  await get_gold_macro_signal()
308
- logger.info("✅ Gold Macro авто-отправка завершена")
309
  except Exception as e:
310
  logger.error(f"Ошибка авто-отправки: {e}")
311
  await asyncio.sleep(AUTO_SEND_INTERVAL)
312
 
313
  # ================= FASTAPI =================
314
- app = FastAPI(title="Tomiris Space 21 v3.2 — Gold Macro & Flow Engine (Auto-Hub)")
315
 
316
  @app.on_event("startup")
317
  async def startup():
318
  asyncio.create_task(auto_send_loop())
319
- logger.info("🚀 Space 21 v3.2 запущен с авто-отправкой в Hub")
320
 
321
  @app.on_event("shutdown")
322
  async def shutdown():
@@ -325,13 +401,16 @@ async def shutdown():
325
  @app.get("/health")
326
  async def health():
327
  return {
 
328
  "status": "operational",
329
- "version": "3.2",
330
- "hub_url": HUB_URL,
331
- "auto_send_interval": AUTO_SEND_INTERVAL,
332
- "async": True
333
  }
334
 
 
 
 
 
335
  @app.get("/consilium")
336
  async def consilium():
337
  return await get_gold_macro_signal()
@@ -364,8 +443,12 @@ async def full():
364
  async def send_now():
365
  return await get_gold_macro_signal()
366
 
 
 
 
 
367
  if __name__ == "__main__":
368
  import uvicorn
369
  uvicorn.run(app, host="0.0.0.0", port=7860)
370
 
371
- print("🚀 SPACE 21 v3.2 — GOLD MACRO & FLOW ENGINE (АВТОТПРАВКА) ЗАПУЩЕН!")
 
21
  print(f"✅ {pip_name} установлен!")
22
 
23
  # ============================================
24
+ # 👑 TOMIRIS SPACE 21 v3.3 — GOLD MACRO & FLOW ENGINE (FIXED)
25
  # ============================================
26
  import os, time, json, logging, asyncio
27
  from typing import Dict, Any, List, Optional
 
36
  logger = logging.getLogger("Space21_GoldMacro")
37
 
38
  # ================= КОНФИГУРАЦИЯ =================
39
+ SPACE_ID = 21
40
+ SPACE_NAME = "Gold Macro & Flow"
41
  SYMBOL = "XAU/USD"
42
+
43
+ # 🔥 ПРАВИЛЬНЫЙ URL ХАБА
44
+ HUB_URL = os.getenv("HUB_URL", "https://pro-3-tomiris-hub.hf.space")
45
+ HUB_SECRET = os.getenv("HUB_SECRET", "TomyrisUltraSecret2026!")
46
+
47
+ TWELVE_DATA_KEY = os.getenv("TWELVE_DATA_KEY", "763fbc9719484fca9863f6c2b5c337f5")
48
  FRED_KEY = os.getenv("FRED_KEY", "faa11c8e2e4beee08c5b966e8b63a513")
49
+ NEWSAPI_KEY = os.getenv("NEWSAPI_KEY", "4742c7ca753049b7bef3e3f23d3f5aaa")
50
+
51
+ STARTUP_SLEEP = int(os.getenv("STARTUP_SLEEP", "600"))
52
+ AUTO_SEND_INTERVAL = int(os.getenv("AUTO_SEND_INTERVAL", "900"))
53
 
54
+ logger.info(f"🔗 Хаб: {HUB_URL}")
55
+ logger.info(f"⏱️ Стартовый сон: {STARTUP_SLEEP}с | Интервал: {AUTO_SEND_INTERVAL")
56
+ logger.info(f"🔑 FRED: {'✅' if FRED_KEY else '❌'} | NewsAPI: {'✅' if NEWSAPI_KEY else '❌'} | TwelveData: {'✅' if TWELVE_DATA_KEY else '❌'}")
57
 
58
  GOLD_ETFS = ["GLD", "IAU", "GDX"]
59
  CACHE_TTL = {"fred": 3600, "etf_aum": 600, "cot": 86400, "gpr": 900}
60
  CACHE: Dict[str, Any] = {}
61
 
62
  # ================= HTTP КЛИЕНТ =================
63
+ limits = httpx.Limits(max_connections=10, max_keepalive_connections=5)
64
+ http_client = httpx.AsyncClient(timeout=httpx.Timeout(20.0, connect=5.0), limits=limits)
65
 
66
+ def hub_headers():
67
+ return {
68
+ "X-Hub-Secret": HUB_SECRET,
69
+ "Content-Type": "application/json"
70
+ }
71
+
72
+ # ================= ОТПРАВКА В HUB (ИСПРАВЛЕНО) =================
73
+ async def send_signal_to_hub(symbol: str, signal: str, confidence: float, features: Dict = None):
74
+ if features is None:
75
+ features = {}
76
+
77
+ payload = {
78
+ "space_id": SPACE_ID,
79
+ "space_name": SPACE_NAME,
80
+ "symbol": symbol,
81
+ "signal": signal, # BUY/SELL/WAIT
82
+ "confidence": round(confidence, 4),
83
+ "features": features,
84
+ "metadata": {"version": "3.3"},
85
+ "timestamp": datetime.now().isoformat()
86
+ }
87
+
88
+ for attempt in range(3):
89
+ try:
90
+ headers = hub_headers()
91
+ r = await http_client.post(f"{HUB_URL}/signals", json=payload, timeout=15, headers=headers)
92
+ if r.status_code == 200:
93
+ logger.info(f"📤 {symbol}: {signal} conf={confidence:.3f}")
94
+ return True
95
+ elif r.status_code == 429:
96
+ wait = 3 * (attempt + 1)
97
+ logger.warning(f"⏳ 429 для {symbol}, жду {wait}с...")
98
+ await asyncio.sleep(wait)
99
+ else:
100
+ logger.warning(f"Попытка {attempt+1}: HTTP {r.status_code}")
101
+ await asyncio.sleep(2)
102
+ except Exception as e:
103
+ logger.warning(f"Попытка {attempt+1}: {e}")
104
+ await asyncio.sleep(2)
105
+
106
+ logger.error(f"❌ Не удалось отправить {symbol}")
107
+ return False
108
 
109
  # ================= ЗАГРУЗКА ДАННЫХ =================
110
  async def fetch_fred_series(series_id: str, days: int = 30) -> List[Dict]:
111
+ if not FRED_KEY:
112
+ return []
113
  try:
114
  r = await http_client.get(
115
+ f"https://api.stlouisfed.org/fred/series/observations?series_id={series_id}&api_key={FRED_KEY}&file_type=json&sort_order=desc&limit={days}"
116
+ )
117
  if r.status_code == 200:
118
  data = r.json()
119
+ values = []
120
+ for obs in data.get('observations', []):
121
+ if obs['value'] != '.':
122
+ try:
123
+ values.append({'date': obs['date'], 'value': float(obs['value'])})
124
+ except ValueError:
125
+ continue
126
+ if values:
127
+ logger.info(f"✅ FRED {series_id}: {len(values)} записей")
128
+ return values
129
  except Exception as e:
130
  logger.warning(f"FRED {series_id}: {e}")
131
  return []
 
135
  if len(values) >= 2:
136
  current = values[0]['value']
137
  month_ago = values[-1]['value']
138
+ if month_ago > 0:
139
+ change = ((current - month_ago) / month_ago) * 100
140
+ return {
141
+ 'dxy': round(current, 2),
142
+ 'change_1m': round(change, 2),
143
+ 'trend': 'STRENGTHENING' if change > 2 else 'WEAKENING' if change < -2 else 'STABLE',
144
+ 'gold_signal': 'BULLISH' if change < -2 else 'BEARISH' if change > 2 else 'NEUTRAL'
145
+ }
146
  return {'dxy': 104.5, 'gold_signal': 'NEUTRAL'}
147
 
148
  async def fetch_tips_yield() -> Dict[str, Any]:
 
157
  return {'tips_yield': 0.5, 'gold_signal': 'NEUTRAL'}
158
 
159
  async def fetch_etf_aum(ticker: str) -> Optional[float]:
160
+ if not TWELVE_DATA_KEY:
161
+ return None
162
  try:
163
  r = await http_client.get(f"https://api.twelvedata.com/statistics?symbol={ticker}&apikey={TWELVE_DATA_KEY}")
164
  if r.status_code == 200:
 
173
  etf_data = {}
174
  total_change = 0.0
175
  count = 0
176
+
177
  for ticker in GOLD_ETFS:
178
  aum_now = await fetch_etf_aum(ticker)
179
  if aum_now is None:
180
  continue
181
+
182
  prev_key = f"aum_{ticker}_prev"
183
  prev_aum = CACHE.get(prev_key, aum_now)
184
  change = aum_now - prev_aum
185
  change_pct = (change / prev_aum * 100) if prev_aum > 0 else 0
186
+
187
  etf_data[ticker] = {
188
  "aum": aum_now,
189
  "change_24h": round(change_pct, 2),
 
194
  CACHE[prev_key] = aum_now
195
 
196
  avg_change = total_change / count if count else 0
197
+
198
  if avg_change > 1:
199
  flow_signal, gold_signal = "STRONG_INFLOW", "BULLISH"
200
  elif avg_change > 0:
 
204
  else:
205
  flow_signal, gold_signal = "NEUTRAL", "NEUTRAL"
206
 
207
+ logger.info(f"✅ ETF Flow: {flow_signal} ({round(avg_change, 2)}%)")
208
  return {
209
  "etfs": etf_data,
210
  "avg_change_pct": round(avg_change, 2),
 
214
 
215
  async def fetch_cot_report() -> Dict[str, Any]:
216
  try:
217
+ r = await http_client.get(
218
+ "https://raw.githubusercontent.com/datasets/cftc-commitment-of-traders/main/data/gold.csv",
219
+ timeout=15
220
+ )
221
+ if r.status_code == 200:
222
+ df = pd.read_csv(io.StringIO(r.text))
223
+ if not df.empty:
224
+ noncomm_net = df['Noncommercial_Long'] - df['Noncommercial_Short']
225
+ latest_net = int(noncomm_net.iloc[-1])
226
+ lookback = min(156, len(noncomm_net))
227
+ recent = noncomm_net.iloc[-lookback:]
228
+ percentile = (recent < latest_net).mean() * 100
229
+
230
+ if percentile > 80:
231
+ cot_signal = "BULLISH_EXTREME"
232
+ elif percentile > 60:
233
+ cot_signal = "BULLISH"
234
+ elif percentile < 20:
235
+ cot_signal = "BEARISH_EXTREME"
236
+ elif percentile < 40:
237
+ cot_signal = "BEARISH"
238
+ else:
239
+ cot_signal = "NEUTRAL"
240
+
241
+ logger.info(f"✅ COT: {cot_signal} (percentile={round(percentile, 1)}%)")
242
+ return {
243
+ 'report_date': str(df['Date'].iloc[-1]),
244
+ 'noncommercial_net': latest_net,
245
+ 'percentile': round(percentile, 1),
246
+ 'cot_signal': cot_signal,
247
+ 'gold_signal': 'BULLISH' if 'BULLISH' in cot_signal else 'BEARISH' if 'BEARISH' in cot_signal else 'NEUTRAL'
248
+ }
249
  except Exception as e:
250
  logger.warning(f"COT error: {e}")
251
  return {'cot_signal': 'NEUTRAL', 'gold_signal': 'NEUTRAL'}
252
 
253
  async def fetch_gpr() -> Dict[str, Any]:
254
+ if not NEWSAPI_KEY:
255
+ return {'gpr_level': 'UNAVAILABLE', 'gold_signal': 'NEUTRAL'}
256
  try:
257
+ r = await http_client.get(
258
+ f"https://newsapi.org/v2/everything?q=geopolitical+war+sanctions&pageSize=5&apiKey={NEWSAPI_KEY}"
259
+ )
260
  if r.status_code == 200:
261
  total = r.json().get('totalResults', 0)
262
+ r_all = await http_client.get(
263
+ f"https://newsapi.org/v2/everything?q=all&pageSize=5&apiKey={NEWSAPI_KEY}"
264
+ )
265
  total_all = r_all.json().get('totalResults', 1) if r_all.status_code == 200 else 1000
266
  ratio = total / max(total_all, 1)
267
+
268
  if ratio > 0.15:
269
  level, gold_signal = "HIGH", "BULLISH"
270
  elif ratio > 0.08:
271
  level, gold_signal = "ELEVATED", "SLIGHTLY_BULLISH"
272
  else:
273
  level, gold_signal = "LOW", "NEUTRAL"
274
+
275
+ logger.info(f"✅ GPR: {level} (ratio={round(ratio, 4)})")
276
  return {'gpr_level': level, 'mentions_share': round(ratio, 4), 'gold_signal': gold_signal}
277
+ except Exception as e:
278
+ logger.warning(f"GPR error: {e}")
279
+ return {'gpr_level': 'UNAVAILABLE', 'gold_signal': 'NEUTRAL'}
280
 
281
  # ================= ГЛАВНЫЙ МАКРО‑АНАЛИЗ =================
282
  async def analyze_gold_macro() -> Dict[str, Any]:
283
+ logger.info("🥇 Запуск Gold Macro анализа...")
284
+
285
  dxy, tips, etf, cot, gpr = await asyncio.gather(
286
  fetch_dxy(), fetch_tips_yield(), analyze_etf_flows(), fetch_cot_report(), fetch_gpr()
287
  )
 
325
 
326
  score = max(0, min(100, score))
327
 
328
+ # 🔥 BUY/SELL вместо LONG/SHORT
329
  if score > 60:
330
+ signal = "BUY"
331
+ confidence = score / 100
332
  elif score < 40:
333
+ signal = "SELL"
334
+ confidence = (100 - score) / 100
335
  else:
336
+ signal = "WAIT"
337
+ confidence = 0.0
338
 
339
+ logger.info(f"🥇 Gold Macro Score: {score} → {signal} (conf={confidence:.3f})")
340
  return {
341
  "macro_score": score,
342
+ "signal": signal,
343
  "confidence": round(confidence, 4),
344
  "signals": signals,
345
  "metrics": {
 
355
  async def get_gold_macro_signal() -> Dict[str, Any]:
356
  start = time.time()
357
  analysis = await analyze_gold_macro()
358
+
359
+ features = {
360
+ "macro_score": analysis['macro_score'],
361
+ "dxy_change": analysis['metrics'].get('dxy', {}).get('change_1m', 0),
362
+ "etf_flow": analysis['metrics'].get('etf_flows', {}).get('avg_change_pct', 0)
363
+ }
364
+ await send_signal_to_hub(SYMBOL, analysis['signal'], analysis['confidence'], features)
365
+
366
+ elapsed = int((time.time() - start) * 1000)
367
+
368
+ return {
369
+ "space_id": SPACE_ID,
370
  "symbol": SYMBOL,
371
+ "signal": analysis['signal'],
372
+ "confidence": analysis['confidence'],
373
+ "macro_score": analysis['macro_score'],
374
+ "latency_ms": elapsed
 
 
 
 
 
 
375
  }
376
 
 
 
 
377
  # ================= АВТО-ОТПРАВКА =================
378
  async def auto_send_loop():
379
+ logger.info(f" Стартовый сон {STARTUP_SLEEP...")
380
+ await asyncio.sleep(STARTUP_SLEEP)
381
+ logger.info(f"🔄 Авто-отправка Gold Macro (интервал {AUTO_SEND_INTERVAL}с)")
382
  while True:
383
  try:
384
  await get_gold_macro_signal()
 
385
  except Exception as e:
386
  logger.error(f"Ошибка авто-отправки: {e}")
387
  await asyncio.sleep(AUTO_SEND_INTERVAL)
388
 
389
  # ================= FASTAPI =================
390
+ app = FastAPI(title="Tomiris Space 21 v3.3 — Gold Macro & Flow Engine")
391
 
392
  @app.on_event("startup")
393
  async def startup():
394
  asyncio.create_task(auto_send_loop())
395
+ logger.info(f"🚀 Space 21 v3.3 ЗАПУЩЕН | Хаб: {HUB_URL}")
396
 
397
  @app.on_event("shutdown")
398
  async def shutdown():
 
401
  @app.get("/health")
402
  async def health():
403
  return {
404
+ "space_id": SPACE_ID,
405
  "status": "operational",
406
+ "version": "3.3",
407
+ "hub_url": HUB_URL
 
 
408
  }
409
 
410
+ @app.head("/health")
411
+ async def health_head():
412
+ return {}
413
+
414
  @app.get("/consilium")
415
  async def consilium():
416
  return await get_gold_macro_signal()
 
443
  async def send_now():
444
  return await get_gold_macro_signal()
445
 
446
+ @app.get("/")
447
+ async def root():
448
+ return {"name": "Gold Macro & Flow v3.3", "space_id": SPACE_ID, "hub": HUB_URL}
449
+
450
  if __name__ == "__main__":
451
  import uvicorn
452
  uvicorn.run(app, host="0.0.0.0", port=7860)
453
 
454
+ print("🚀 SPACE 21 v3.3 — GOLD MACRO & FLOW ENGINE ГОТОВ К РАБОТЕ!")