Spaces:
Paused
Paused
Update app.py
Browse files
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 29 v2.
|
| 25 |
# ============================================
|
| 26 |
import os, time, json, logging, asyncio
|
| 27 |
from typing import Dict, Any, List, Optional
|
|
@@ -37,7 +37,7 @@ logger = logging.getLogger("Space29_MacroSurprise")
|
|
| 37 |
|
| 38 |
# ================= КОНФИГУРАЦИЯ =================
|
| 39 |
SYMBOLS = ["XAU/USD", "ETH/USD", "SOL/USD"]
|
| 40 |
-
|
| 41 |
FRED_KEY = os.getenv("FRED_KEY", "faa11c8e2e4beee08c5b966e8b63a513")
|
| 42 |
NEWSAPI_KEY = os.getenv("NEWSAPI_KEY", "948c7816beea47baa23b054592472d0e")
|
| 43 |
|
|
@@ -91,15 +91,12 @@ async def fetch_fred_series(series_id: str, months: int = 13) -> List[Dict]:
|
|
| 91 |
return []
|
| 92 |
|
| 93 |
def get_yoy_change(data: List[Dict], current_month: str) -> Optional[float]:
|
| 94 |
-
"""Считает YoY изменение: текущее значение vs значение 12 месяцев назад (тот же месяц)."""
|
| 95 |
-
# Ищем записи с нужным месяцем
|
| 96 |
current_val = None
|
| 97 |
prev_val = None
|
| 98 |
for item in data:
|
| 99 |
date = item['date']
|
| 100 |
if date == current_month:
|
| 101 |
current_val = item['value']
|
| 102 |
-
# Тот же месяц, год назад (YYYY-1)
|
| 103 |
year_ago = str(int(date[:4]) - 1) + date[4:]
|
| 104 |
if date == year_ago and date[:7] == current_month[:7]:
|
| 105 |
prev_val = item['value']
|
|
@@ -149,46 +146,41 @@ async def analyze_macro_surprises() -> Dict:
|
|
| 149 |
today = datetime.now(timezone.utc)
|
| 150 |
current_month_str = today.strftime("%Y-%m")
|
| 151 |
|
| 152 |
-
# Загружаем FRED параллельно
|
| 153 |
cpi_data = await fetch_fred_series("CPIAUCSL", 13)
|
| 154 |
core_cpi_data = await fetch_fred_series("CPILFESL", 13)
|
| 155 |
unemp_data = await fetch_fred_series("UNRATE", 6)
|
| 156 |
-
nfp_data = await fetch_fred_series("PAYEMS", 3)
|
| 157 |
-
gdp_data = await fetch_fred_series("GDP", 3)
|
| 158 |
-
ism_data = await fetch_fred_series("NAPM", 3)
|
| 159 |
-
retail_data = await fetch_fred_series("RSAFS", 3)
|
| 160 |
-
durable_data = await fetch_fred_series("DGORDER", 3)
|
| 161 |
fomc_surprise = await fetch_fomc_surprise()
|
| 162 |
|
| 163 |
surprises = []
|
| 164 |
total_score = 0.0
|
| 165 |
weights = {"CPI_YOY": 0.30, "NFP": 0.25, "FOMC": 0.20, "ISM_MANUF": 0.10, "GDP_QOQ": 0.10, "RETAIL_SALES": 0.05}
|
| 166 |
|
| 167 |
-
# CPI YoY (корректный)
|
| 168 |
cpi_yoy = get_yoy_change(cpi_data, current_month_str)
|
| 169 |
if cpi_yoy is not None:
|
| 170 |
s = calc_surprise(cpi_yoy, CONSENSUS["CPI_YOY"])
|
| 171 |
-
impact = s['impact'] * (1 if s['direction'] == 'NEGATIVE' else -0.5)
|
| 172 |
total_score += impact * weights["CPI_YOY"]
|
| 173 |
surprises.append({"indicator": "CPI_YOY", "actual": round(cpi_yoy, 2), "consensus": CONSENSUS["CPI_YOY"], "surprise": s})
|
| 174 |
|
| 175 |
-
# NFP (последний месяц)
|
| 176 |
if nfp_data:
|
| 177 |
-
nfp_actual = nfp_data[0]['value']
|
| 178 |
s = calc_surprise(nfp_actual, CONSENSUS["NFP"])
|
| 179 |
-
impact = s['impact'] * (1 if s['direction'] == 'POSITIVE' else -1)
|
| 180 |
total_score += impact * weights["NFP"]
|
| 181 |
surprises.append({"indicator": "NFP", "actual": int(nfp_actual), "consensus": CONSENSUS["NFP"], "surprise": s})
|
| 182 |
|
| 183 |
-
# GDP (квартальный, берём последний)
|
| 184 |
if gdp_data:
|
| 185 |
-
gdp_actual = gdp_data[0]['value']
|
| 186 |
s = calc_surprise(gdp_actual, CONSENSUS["GDP_QOQ"])
|
| 187 |
impact = s['impact'] * (1 if s['direction'] == 'POSITIVE' else -1)
|
| 188 |
total_score += impact * weights["GDP_QOQ"]
|
| 189 |
surprises.append({"indicator": "GDP_QOQ", "actual": round(gdp_actual, 2), "consensus": CONSENSUS["GDP_QOQ"], "surprise": s})
|
| 190 |
|
| 191 |
-
# ISM Manufacturing
|
| 192 |
if ism_data:
|
| 193 |
ism_actual = ism_data[0]['value']
|
| 194 |
s = calc_surprise(ism_actual, CONSENSUS["ISM_MANUF"])
|
|
@@ -196,7 +188,6 @@ async def analyze_macro_surprises() -> Dict:
|
|
| 196 |
total_score += impact * weights["ISM_MANUF"]
|
| 197 |
surprises.append({"indicator": "ISM_MANUF", "actual": round(ism_actual, 2), "consensus": CONSENSUS["ISM_MANUF"], "surprise": s})
|
| 198 |
|
| 199 |
-
# Retail Sales
|
| 200 |
if retail_data:
|
| 201 |
retail_actual = retail_data[0]['value']
|
| 202 |
s = calc_surprise(retail_actual, CONSENSUS["RETAIL_SALES"])
|
|
@@ -204,7 +195,6 @@ async def analyze_macro_surprises() -> Dict:
|
|
| 204 |
total_score += impact * weights["RETAIL_SALES"]
|
| 205 |
surprises.append({"indicator": "RETAIL_SALES", "actual": round(retail_actual, 2), "consensus": CONSENSUS["RETAIL_SALES"], "surprise": s})
|
| 206 |
|
| 207 |
-
# FOMC
|
| 208 |
if fomc_surprise['impact'] != 0:
|
| 209 |
total_score += fomc_surprise['impact'] * weights["FOMC"]
|
| 210 |
surprises.append({"indicator": "FOMC", "signal": fomc_surprise['direction'], "impact": fomc_surprise['impact']})
|
|
@@ -223,7 +213,6 @@ async def analyze_macro_surprises() -> Dict:
|
|
| 223 |
regime, direction = "NEUTRAL", "WAIT"
|
| 224 |
confidence = 0.0
|
| 225 |
|
| 226 |
-
# Сохраняем историю
|
| 227 |
SURPRISE_HISTORY.append({
|
| 228 |
"timestamp": today.isoformat(),
|
| 229 |
"surprise_index": round(surprise_index, 2),
|
|
@@ -241,12 +230,30 @@ async def analyze_macro_surprises() -> Dict:
|
|
| 241 |
"total_score": round(total_score, 2)
|
| 242 |
}
|
| 243 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 244 |
# ================= ГЛАВНЫЙ СИГНАЛ =================
|
| 245 |
async def get_macro_surprise_signal() -> Dict[str, Any]:
|
| 246 |
start = time.time()
|
| 247 |
analysis = await analyze_macro_surprises()
|
| 248 |
latency = int((time.time() - start) * 1000)
|
| 249 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
result = {
|
| 251 |
"space": "space_29_macro_surprise",
|
| 252 |
"timestamp": int(time.time()),
|
|
@@ -255,22 +262,11 @@ async def get_macro_surprise_signal() -> Dict[str, Any]:
|
|
| 255 |
"latency_ms": latency
|
| 256 |
}
|
| 257 |
|
| 258 |
-
# Отправка в Arbiter
|
| 259 |
-
try:
|
| 260 |
-
await http_client.post(f"{ARBITER_URL}/log_signal", json={
|
| 261 |
-
"space": "space_29_macro_surprise",
|
| 262 |
-
"symbol": "XAU/USD",
|
| 263 |
-
"signal": result["signals"]["XAU/USD"],
|
| 264 |
-
"surprise_details": analysis
|
| 265 |
-
})
|
| 266 |
-
except:
|
| 267 |
-
pass
|
| 268 |
-
|
| 269 |
logger.info(f"📈 Macro Surprise: Index={analysis['surprise_index']:.1f} Regime={analysis['market_regime']}")
|
| 270 |
return result
|
| 271 |
|
| 272 |
# ================= FASTAPI =================
|
| 273 |
-
app = FastAPI(title="Tomiris Space 29 v2.
|
| 274 |
|
| 275 |
@app.on_event("startup")
|
| 276 |
async def startup(): pass
|
|
@@ -280,7 +276,7 @@ async def shutdown(): await http_client.aclose()
|
|
| 280 |
|
| 281 |
@app.get("/health")
|
| 282 |
async def health():
|
| 283 |
-
return {"status": "operational", "version": "2.
|
| 284 |
"indicators": list(CONSENSUS.keys()), "history_length": len(SURPRISE_HISTORY)}
|
| 285 |
|
| 286 |
@app.get("/consilium")
|
|
@@ -303,4 +299,4 @@ if __name__ == "__main__":
|
|
| 303 |
import uvicorn
|
| 304 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
| 305 |
|
| 306 |
-
print("🚀 SPACE 29 v2.
|
|
|
|
| 21 |
print(f"✅ {pip_name} установлен!")
|
| 22 |
|
| 23 |
# ============================================
|
| 24 |
+
# 👑 TOMIRIS SPACE 29 v2.1 — MACRO SURPRISE ENGINE (Hub-Connected)
|
| 25 |
# ============================================
|
| 26 |
import os, time, json, logging, asyncio
|
| 27 |
from typing import Dict, Any, List, Optional
|
|
|
|
| 37 |
|
| 38 |
# ================= КОНФИГУРАЦИЯ =================
|
| 39 |
SYMBOLS = ["XAU/USD", "ETH/USD", "SOL/USD"]
|
| 40 |
+
HUB_URL = os.getenv("SPACE17_URL", "https://tomiris-ai-name5-5.hf.space")
|
| 41 |
FRED_KEY = os.getenv("FRED_KEY", "faa11c8e2e4beee08c5b966e8b63a513")
|
| 42 |
NEWSAPI_KEY = os.getenv("NEWSAPI_KEY", "948c7816beea47baa23b054592472d0e")
|
| 43 |
|
|
|
|
| 91 |
return []
|
| 92 |
|
| 93 |
def get_yoy_change(data: List[Dict], current_month: str) -> Optional[float]:
|
|
|
|
|
|
|
| 94 |
current_val = None
|
| 95 |
prev_val = None
|
| 96 |
for item in data:
|
| 97 |
date = item['date']
|
| 98 |
if date == current_month:
|
| 99 |
current_val = item['value']
|
|
|
|
| 100 |
year_ago = str(int(date[:4]) - 1) + date[4:]
|
| 101 |
if date == year_ago and date[:7] == current_month[:7]:
|
| 102 |
prev_val = item['value']
|
|
|
|
| 146 |
today = datetime.now(timezone.utc)
|
| 147 |
current_month_str = today.strftime("%Y-%m")
|
| 148 |
|
|
|
|
| 149 |
cpi_data = await fetch_fred_series("CPIAUCSL", 13)
|
| 150 |
core_cpi_data = await fetch_fred_series("CPILFESL", 13)
|
| 151 |
unemp_data = await fetch_fred_series("UNRATE", 6)
|
| 152 |
+
nfp_data = await fetch_fred_series("PAYEMS", 3)
|
| 153 |
+
gdp_data = await fetch_fred_series("GDP", 3)
|
| 154 |
+
ism_data = await fetch_fred_series("NAPM", 3)
|
| 155 |
+
retail_data = await fetch_fred_series("RSAFS", 3)
|
| 156 |
+
durable_data = await fetch_fred_series("DGORDER", 3)
|
| 157 |
fomc_surprise = await fetch_fomc_surprise()
|
| 158 |
|
| 159 |
surprises = []
|
| 160 |
total_score = 0.0
|
| 161 |
weights = {"CPI_YOY": 0.30, "NFP": 0.25, "FOMC": 0.20, "ISM_MANUF": 0.10, "GDP_QOQ": 0.10, "RETAIL_SALES": 0.05}
|
| 162 |
|
|
|
|
| 163 |
cpi_yoy = get_yoy_change(cpi_data, current_month_str)
|
| 164 |
if cpi_yoy is not None:
|
| 165 |
s = calc_surprise(cpi_yoy, CONSENSUS["CPI_YOY"])
|
| 166 |
+
impact = s['impact'] * (1 if s['direction'] == 'NEGATIVE' else -0.5)
|
| 167 |
total_score += impact * weights["CPI_YOY"]
|
| 168 |
surprises.append({"indicator": "CPI_YOY", "actual": round(cpi_yoy, 2), "consensus": CONSENSUS["CPI_YOY"], "surprise": s})
|
| 169 |
|
|
|
|
| 170 |
if nfp_data:
|
| 171 |
+
nfp_actual = nfp_data[0]['value']
|
| 172 |
s = calc_surprise(nfp_actual, CONSENSUS["NFP"])
|
| 173 |
+
impact = s['impact'] * (1 if s['direction'] == 'POSITIVE' else -1)
|
| 174 |
total_score += impact * weights["NFP"]
|
| 175 |
surprises.append({"indicator": "NFP", "actual": int(nfp_actual), "consensus": CONSENSUS["NFP"], "surprise": s})
|
| 176 |
|
|
|
|
| 177 |
if gdp_data:
|
| 178 |
+
gdp_actual = gdp_data[0]['value']
|
| 179 |
s = calc_surprise(gdp_actual, CONSENSUS["GDP_QOQ"])
|
| 180 |
impact = s['impact'] * (1 if s['direction'] == 'POSITIVE' else -1)
|
| 181 |
total_score += impact * weights["GDP_QOQ"]
|
| 182 |
surprises.append({"indicator": "GDP_QOQ", "actual": round(gdp_actual, 2), "consensus": CONSENSUS["GDP_QOQ"], "surprise": s})
|
| 183 |
|
|
|
|
| 184 |
if ism_data:
|
| 185 |
ism_actual = ism_data[0]['value']
|
| 186 |
s = calc_surprise(ism_actual, CONSENSUS["ISM_MANUF"])
|
|
|
|
| 188 |
total_score += impact * weights["ISM_MANUF"]
|
| 189 |
surprises.append({"indicator": "ISM_MANUF", "actual": round(ism_actual, 2), "consensus": CONSENSUS["ISM_MANUF"], "surprise": s})
|
| 190 |
|
|
|
|
| 191 |
if retail_data:
|
| 192 |
retail_actual = retail_data[0]['value']
|
| 193 |
s = calc_surprise(retail_actual, CONSENSUS["RETAIL_SALES"])
|
|
|
|
| 195 |
total_score += impact * weights["RETAIL_SALES"]
|
| 196 |
surprises.append({"indicator": "RETAIL_SALES", "actual": round(retail_actual, 2), "consensus": CONSENSUS["RETAIL_SALES"], "surprise": s})
|
| 197 |
|
|
|
|
| 198 |
if fomc_surprise['impact'] != 0:
|
| 199 |
total_score += fomc_surprise['impact'] * weights["FOMC"]
|
| 200 |
surprises.append({"indicator": "FOMC", "signal": fomc_surprise['direction'], "impact": fomc_surprise['impact']})
|
|
|
|
| 213 |
regime, direction = "NEUTRAL", "WAIT"
|
| 214 |
confidence = 0.0
|
| 215 |
|
|
|
|
| 216 |
SURPRISE_HISTORY.append({
|
| 217 |
"timestamp": today.isoformat(),
|
| 218 |
"surprise_index": round(surprise_index, 2),
|
|
|
|
| 230 |
"total_score": round(total_score, 2)
|
| 231 |
}
|
| 232 |
|
| 233 |
+
# ================= ОТПРАВКА В HUB =================
|
| 234 |
+
async def send_signal_to_hub(symbol: str, direction: str, confidence: float):
|
| 235 |
+
try:
|
| 236 |
+
await http_client.post(f"{HUB_URL}/signal", json={
|
| 237 |
+
"space": "space_29_macro_surprise",
|
| 238 |
+
"symbol": symbol,
|
| 239 |
+
"direction": direction,
|
| 240 |
+
"confidence": confidence,
|
| 241 |
+
"raw": json.dumps({"source": "space_29_macro_surprise"})
|
| 242 |
+
})
|
| 243 |
+
logger.info(f"📤 {symbol}: {direction} conf={confidence:.3f} отправлен в Hub")
|
| 244 |
+
except Exception as e:
|
| 245 |
+
logger.error(f"Ошибка отправки в Hub: {e}")
|
| 246 |
+
|
| 247 |
# ================= ГЛАВНЫЙ СИГНАЛ =================
|
| 248 |
async def get_macro_surprise_signal() -> Dict[str, Any]:
|
| 249 |
start = time.time()
|
| 250 |
analysis = await analyze_macro_surprises()
|
| 251 |
latency = int((time.time() - start) * 1000)
|
| 252 |
|
| 253 |
+
# Отправка сигналов для всех трёх символов в Hub
|
| 254 |
+
for sym in SYMBOLS:
|
| 255 |
+
await send_signal_to_hub(sym, analysis['direction'], analysis['confidence'])
|
| 256 |
+
|
| 257 |
result = {
|
| 258 |
"space": "space_29_macro_surprise",
|
| 259 |
"timestamp": int(time.time()),
|
|
|
|
| 262 |
"latency_ms": latency
|
| 263 |
}
|
| 264 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 265 |
logger.info(f"📈 Macro Surprise: Index={analysis['surprise_index']:.1f} Regime={analysis['market_regime']}")
|
| 266 |
return result
|
| 267 |
|
| 268 |
# ================= FASTAPI =================
|
| 269 |
+
app = FastAPI(title="Tomiris Space 29 v2.1 — Macro Surprise Engine (Hub)")
|
| 270 |
|
| 271 |
@app.on_event("startup")
|
| 272 |
async def startup(): pass
|
|
|
|
| 276 |
|
| 277 |
@app.get("/health")
|
| 278 |
async def health():
|
| 279 |
+
return {"status": "operational", "version": "2.1", "hub_connected": True,
|
| 280 |
"indicators": list(CONSENSUS.keys()), "history_length": len(SURPRISE_HISTORY)}
|
| 281 |
|
| 282 |
@app.get("/consilium")
|
|
|
|
| 299 |
import uvicorn
|
| 300 |
uvicorn.run(app, host="0.0.0.0", port=7860)
|
| 301 |
|
| 302 |
+
print("🚀 SPACE 29 v2.1 — MACRO SURPRISE ENGINE (Hub-Connected) ЗАПУЩЕН!")
|