File size: 3,790 Bytes
b159555
07fe08e
ea758f6
07fe08e
 
b159555
 
c6f94f2
b8d0337
acb6f17
3b67fa5
cfa3962
acb6f17
b159555
acb6f17
 
cfa3962
b159555
07fe08e
 
 
510ee6f
acb6f17
2ca2f4e
acb6f17
 
b349d30
acb6f17
 
07fe08e
073930c
cfa3962
 
acb6f17
 
07fe08e
acb6f17
 
cfa3962
acb6f17
 
 
 
07fe08e
acb6f17
 
 
 
 
 
cfa3962
 
1690b33
073930c
07fe08e
 
 
7ab0ea7
07fe08e
 
 
 
 
 
 
 
 
 
d501c8b
07fe08e
 
 
 
 
 
d501c8b
d944095
d501c8b
 
 
3b67fa5
d501c8b
 
07fe08e
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
"""
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")