File size: 3,558 Bytes
b159555
2ca2f4e
ea758f6
2ca2f4e
 
 
b159555
 
c6f94f2
2ca2f4e
b8d0337
cfa3962
 
2ca2f4e
b159555
2ca2f4e
 
cfa3962
b159555
6a1e668
 
510ee6f
cfa3962
2ca2f4e
 
 
 
b349d30
0e87c05
 
2ca2f4e
6a1e668
 
 
cfa3962
 
 
 
 
2ca2f4e
259af0a
cfa3962
6a1e668
 
 
cfa3962
073930c
cfa3962
2ca2f4e
073930c
cfa3962
 
 
1690b33
cfa3962
 
 
 
 
 
 
2ca2f4e
 
 
 
 
 
 
 
 
 
 
 
 
cfa3962
 
510ee6f
1690b33
073930c
2ca2f4e
cfa3962
 
2ca2f4e
 
 
b159555
2ca2f4e
 
 
7ab0ea7
2ca2f4e
259af0a
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
91
92
"""
Sentinel Arbitrage Engine - v14.0 FINAL (Socket.IO Multi-Asset)

The definitive version, combining a robust multi-asset detection engine
with a high-performance Socket.IO backend for guaranteed, real-time
signal delivery and a professional UI.
"""
import asyncio
import os
from contextlib import asynccontextmanager
import json
import time
from datetime import datetime, timezone

import httpx
import socketio
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles

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='*')
socket_app = socketio.ASGIApp(sio)

@asynccontextmanager
async def lifespan(app: FastAPI):
    print("πŸš€ Initializing Sentinel Arbitrage Engine v14.0...")
    async with httpx.AsyncClient() as client:
        app.state.price_fetcher = PriceFetcher(client)
        app.state.arbitrage_analyzer = ArbitrageAnalyzer(client)
        
        arbitrage_task = asyncio.create_task(
            run_arbitrage_detector(app.state.price_fetcher, app.state.arbitrage_analyzer)
        )
        
        print("βœ… Engine is online and hunting for opportunities.")
        yield
        
        print("⏳ Shutting down engine...")
        arbitrage_task.cancel()
        try: await arbitrage_task
        except asyncio.CancelledError: print("Engine shut down gracefully.")

async def run_arbitrage_detector(price_fetcher, analyzer):
    last_opportunity_time = 0
    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 = prices.get("pyth")
                chainlink_price = 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 current_time - last_opportunity_time > 20: # Throttle Gemini calls
                            last_opportunity_time = 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 & Socket.IO Setup ---
app = FastAPI(lifespan=lifespan)

@sio.event
async def connect(sid, environ):
    print(f"βœ… Client connected: {sid}")

@sio.event
async def disconnect(sid):
    print(f"πŸ”₯ Client disconnected: {sid}")

app.mount('/socket.io', socket_app)
app.mount("/", StaticFiles(directory="static", html=True), name="static")