Spaces:
Running
Running
Update app/app.py
Browse files- app/app.py +35 -46
app/app.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1 |
"""
|
2 |
-
Sentinel Arbitrage Engine -
|
3 |
|
4 |
-
|
5 |
-
|
6 |
"""
|
7 |
import asyncio
|
8 |
import os
|
@@ -16,41 +16,32 @@ import socketio
|
|
16 |
from fastapi import FastAPI
|
17 |
from fastapi.staticfiles import StaticFiles
|
18 |
|
19 |
-
#
|
20 |
-
from
|
21 |
-
from
|
22 |
|
23 |
OPPORTUNITY_THRESHOLD = 0.0015
|
24 |
|
25 |
# --- Socket.IO Server Setup ---
|
26 |
-
# This creates the server instance that will handle all real-time communication.
|
27 |
sio = socketio.AsyncServer(async_mode='asgi', cors_allowed_origins='*')
|
28 |
|
29 |
# --- Background Engine ---
|
30 |
async def run_arbitrage_detector(price_fetcher, analyzer):
|
31 |
-
|
32 |
while True:
|
33 |
try:
|
34 |
await price_fetcher.update_prices_async()
|
35 |
all_prices = price_fetcher.get_all_prices()
|
36 |
-
|
37 |
for asset, prices in all_prices.items():
|
38 |
-
pyth_price = prices.get("pyth")
|
39 |
-
chainlink_price = prices.get("chainlink_agg")
|
40 |
-
|
41 |
if pyth_price and chainlink_price and pyth_price > 0:
|
42 |
spread = abs(pyth_price - chainlink_price) / chainlink_price
|
43 |
if spread > OPPORTUNITY_THRESHOLD:
|
44 |
current_time = time.time()
|
45 |
-
# Simple throttle to avoid spamming Gemini for the same opportunity
|
46 |
if not hasattr(analyzer, 'last_call') or current_time - analyzer.last_call.get(asset, 0) > 60:
|
47 |
analyzer.last_call = getattr(analyzer, 'last_call', {})
|
48 |
analyzer.last_call[asset] = current_time
|
49 |
-
|
50 |
-
opportunity = {
|
51 |
-
"asset": asset, "pyth_price": pyth_price,
|
52 |
-
"chainlink_price": chainlink_price, "spread_pct": spread * 100
|
53 |
-
}
|
54 |
print(f"β‘οΈ Dislocation for {asset}: {opportunity['spread_pct']:.3f}%")
|
55 |
briefing = await analyzer.get_alpha_briefing(asset, opportunity)
|
56 |
if briefing:
|
@@ -59,37 +50,29 @@ async def run_arbitrage_detector(price_fetcher, analyzer):
|
|
59 |
print(f"β
Signal Emitted for {asset}: {signal['strategy']}")
|
60 |
except Exception as e:
|
61 |
print(f"β ERROR in engine loop: {e}")
|
62 |
-
|
63 |
await asyncio.sleep(15)
|
64 |
|
65 |
-
# --- FastAPI
|
66 |
-
|
67 |
-
|
68 |
-
print("π Initializing Sentinel Arbitrage Engine v16.0...")
|
69 |
-
async with httpx.AsyncClient() as client:
|
70 |
-
# The background task is started using the Socket.IO server's robust method
|
71 |
-
sio.start_background_task(
|
72 |
-
run_arbitrage_detector,
|
73 |
-
PriceFetcher(client),
|
74 |
-
ArbitrageAnalyzer(client)
|
75 |
-
)
|
76 |
-
print("β
Engine is online and hunting for opportunities.")
|
77 |
-
yield
|
78 |
-
# No explicit shutdown needed for the background task here,
|
79 |
-
# as it's tied to the server process.
|
80 |
-
|
81 |
-
# --- FastAPI App & Final ASGI App ---
|
82 |
-
# We define the FastAPI app instance first, including its lifespan.
|
83 |
-
app = FastAPI(lifespan=lifespan)
|
84 |
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
|
|
|
|
|
|
92 |
|
|
|
|
|
|
|
|
|
|
|
|
|
93 |
|
94 |
# --- Socket.IO Event Handlers ---
|
95 |
@sio.event
|
@@ -98,4 +81,10 @@ async def connect(sid, environ):
|
|
98 |
|
99 |
@sio.event
|
100 |
async def disconnect(sid):
|
101 |
-
print(f"π₯ Client disconnected: {sid}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
"""
|
2 |
+
Sentinel Arbitrage Engine - v17.0 FINAL (Flat Structure)
|
3 |
|
4 |
+
This definitive version uses a flat project structure and direct imports
|
5 |
+
to conform to the environment's startup command, ensuring a successful launch.
|
6 |
"""
|
7 |
import asyncio
|
8 |
import os
|
|
|
16 |
from fastapi import FastAPI
|
17 |
from fastapi.staticfiles import StaticFiles
|
18 |
|
19 |
+
# --- DIRECT IMPORTS FOR FLAT STRUCTURE ---
|
20 |
+
from price_fetcher import PriceFetcher
|
21 |
+
from arbitrage_analyzer import ArbitrageAnalyzer
|
22 |
|
23 |
OPPORTUNITY_THRESHOLD = 0.0015
|
24 |
|
25 |
# --- Socket.IO Server Setup ---
|
|
|
26 |
sio = socketio.AsyncServer(async_mode='asgi', cors_allowed_origins='*')
|
27 |
|
28 |
# --- Background Engine ---
|
29 |
async def run_arbitrage_detector(price_fetcher, analyzer):
|
30 |
+
# This logic is correct and does not need to change.
|
31 |
while True:
|
32 |
try:
|
33 |
await price_fetcher.update_prices_async()
|
34 |
all_prices = price_fetcher.get_all_prices()
|
|
|
35 |
for asset, prices in all_prices.items():
|
36 |
+
pyth_price, chainlink_price = prices.get("pyth"), prices.get("chainlink_agg")
|
|
|
|
|
37 |
if pyth_price and chainlink_price and pyth_price > 0:
|
38 |
spread = abs(pyth_price - chainlink_price) / chainlink_price
|
39 |
if spread > OPPORTUNITY_THRESHOLD:
|
40 |
current_time = time.time()
|
|
|
41 |
if not hasattr(analyzer, 'last_call') or current_time - analyzer.last_call.get(asset, 0) > 60:
|
42 |
analyzer.last_call = getattr(analyzer, 'last_call', {})
|
43 |
analyzer.last_call[asset] = current_time
|
44 |
+
opportunity = {"asset": asset, "pyth_price": pyth_price, "chainlink_price": chainlink_price, "spread_pct": spread * 100}
|
|
|
|
|
|
|
|
|
45 |
print(f"β‘οΈ Dislocation for {asset}: {opportunity['spread_pct']:.3f}%")
|
46 |
briefing = await analyzer.get_alpha_briefing(asset, opportunity)
|
47 |
if briefing:
|
|
|
50 |
print(f"β
Signal Emitted for {asset}: {signal['strategy']}")
|
51 |
except Exception as e:
|
52 |
print(f"β ERROR in engine loop: {e}")
|
|
|
53 |
await asyncio.sleep(15)
|
54 |
|
55 |
+
# --- FastAPI App & Lifespan ---
|
56 |
+
# We now define the main 'app' object first.
|
57 |
+
app = FastAPI()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
58 |
|
59 |
+
@app.on_event("startup")
|
60 |
+
async def startup_event():
|
61 |
+
print("π Initializing Sentinel Arbitrage Engine v17.0...")
|
62 |
+
# Using the older startup event which is more compatible with some environments
|
63 |
+
app.state.client = httpx.AsyncClient()
|
64 |
+
price_fetcher = PriceFetcher(app.state.client)
|
65 |
+
arbitrage_analyzer = ArbitrageAnalyzer(app.state.client)
|
66 |
+
# Start the background task
|
67 |
+
sio.background_task = sio.start_background_task(run_arbitrage_detector, price_fetcher, arbitrage_analyzer)
|
68 |
+
print("β
Engine is online and hunting for opportunities.")
|
69 |
|
70 |
+
@app.on_event("shutdown")
|
71 |
+
async def shutdown_event():
|
72 |
+
print("β³ Shutting down engine...")
|
73 |
+
sio.background_task.cancel()
|
74 |
+
await app.state.client.aclose()
|
75 |
+
print("Engine shut down gracefully.")
|
76 |
|
77 |
# --- Socket.IO Event Handlers ---
|
78 |
@sio.event
|
|
|
81 |
|
82 |
@sio.event
|
83 |
async def disconnect(sid):
|
84 |
+
print(f"π₯ Client disconnected: {sid}")
|
85 |
+
|
86 |
+
# --- Mount the apps ---
|
87 |
+
# The primary app is the Socket.IO server, which wraps our FastAPI app.
|
88 |
+
app = socketio.ASGIApp(sio, other_asgi_app=app)
|
89 |
+
# Serve the static files.
|
90 |
+
sio.mount("/", StaticFiles(directory="static", html=True), name="static")
|