""" Sentinel Arbitrage Engine - v17.0 FINAL (Flat Structure) This definitive version uses a flat project structure and direct imports to conform to the environment's startup command, ensuring a successful launch. """ import asyncio import os import json import time from contextlib import asynccontextmanager from datetime import datetime, timezone import httpx import socketio from fastapi import FastAPI from fastapi.staticfiles import StaticFiles # --- DIRECT IMPORTS FOR FLAT STRUCTURE --- from price_fetcher import PriceFetcher from arbitrage_analyzer import ArbitrageAnalyzer OPPORTUNITY_THRESHOLD = 0.0015 # --- Socket.IO Server Setup --- sio = socketio.AsyncServer(async_mode='asgi', cors_allowed_origins='*') # --- Background Engine --- async def run_arbitrage_detector(price_fetcher, analyzer): # This logic is correct and does not need to change. while True: try: await price_fetcher.update_prices_async() all_prices = price_fetcher.get_all_prices() for asset, prices in all_prices.items(): pyth_price, chainlink_price = prices.get("pyth"), prices.get("chainlink_agg") if pyth_price and chainlink_price and pyth_price > 0: spread = abs(pyth_price - chainlink_price) / chainlink_price if spread > OPPORTUNITY_THRESHOLD: current_time = time.time() if not hasattr(analyzer, 'last_call') or current_time - analyzer.last_call.get(asset, 0) > 60: analyzer.last_call = getattr(analyzer, 'last_call', {}) analyzer.last_call[asset] = current_time opportunity = {"asset": asset, "pyth_price": pyth_price, "chainlink_price": chainlink_price, "spread_pct": spread * 100} print(f"⚡️ Dislocation for {asset}: {opportunity['spread_pct']:.3f}%") briefing = await analyzer.get_alpha_briefing(asset, opportunity) if briefing: signal = {**opportunity, **briefing, "timestamp": datetime.now(timezone.utc).isoformat()} await sio.emit('new_signal', signal) print(f"✅ Signal Emitted for {asset}: {signal['strategy']}") except Exception as e: print(f"❌ ERROR in engine loop: {e}") await asyncio.sleep(15) # --- FastAPI App & Lifespan --- # We now define the main 'app' object first. app = FastAPI() @app.on_event("startup") async def startup_event(): print("🚀 Initializing Sentinel Arbitrage Engine v17.0...") # Using the older startup event which is more compatible with some environments app.state.client = httpx.AsyncClient() price_fetcher = PriceFetcher(app.state.client) arbitrage_analyzer = ArbitrageAnalyzer(app.state.client) # Start the background task sio.background_task = sio.start_background_task(run_arbitrage_detector, price_fetcher, arbitrage_analyzer) print("✅ Engine is online and hunting for opportunities.") @app.on_event("shutdown") async def shutdown_event(): print("⏳ Shutting down engine...") sio.background_task.cancel() await app.state.client.aclose() print("Engine shut down gracefully.") # --- Socket.IO Event Handlers --- @sio.event async def connect(sid, environ): print(f"✅ Client connected: {sid}") @sio.event async def disconnect(sid): print(f"🔥 Client disconnected: {sid}") # --- Mount the apps --- # The primary app is the Socket.IO server, which wraps our FastAPI app. app = socketio.ASGIApp(sio, other_asgi_app=app) # Serve the static files. sio.mount("/", StaticFiles(directory="static", html=True), name="static")