Spaces:
Running
Running
""" | |
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() | |
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.") | |
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 --- | |
async def connect(sid, environ): | |
print(f"β Client connected: {sid}") | |
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") |