Spaces:
Running
Running
Update app/app.py
Browse files- app/app.py +62 -76
app/app.py
CHANGED
@@ -1,104 +1,90 @@
|
|
1 |
"""
|
2 |
-
Sentinel Arbitrage Engine -
|
3 |
|
4 |
-
|
5 |
-
|
6 |
"""
|
7 |
import asyncio
|
8 |
import os
|
9 |
from contextlib import asynccontextmanager
|
10 |
from datetime import datetime, timezone
|
11 |
-
import json
|
12 |
import httpx
|
13 |
-
|
14 |
-
from fastapi
|
15 |
-
from fastapi.
|
16 |
|
17 |
-
|
18 |
-
from
|
19 |
-
from .arbitrage_analyzer import ArbitrageAnalyzer
|
20 |
-
from .broker import signal_broker
|
21 |
|
22 |
OPPORTUNITY_THRESHOLD = 0.001
|
23 |
|
|
|
|
|
|
|
|
|
|
|
24 |
@asynccontextmanager
|
25 |
async def lifespan(app: FastAPI):
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
app.state.arbitrage_analyzer = ArbitrageAnalyzer(httpx.AsyncClient())
|
30 |
|
31 |
-
|
|
|
|
|
|
|
32 |
|
33 |
-
print("
|
34 |
yield
|
35 |
|
36 |
print("β³ Shutting down engine...")
|
37 |
arbitrage_task.cancel()
|
38 |
try: await arbitrage_task
|
39 |
-
except asyncio.CancelledError: print("Engine shut down.")
|
40 |
|
41 |
-
async def run_arbitrage_detector(
|
42 |
-
|
43 |
while True:
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
|
|
|
|
|
|
|
|
64 |
|
65 |
-
await asyncio.sleep(15)
|
66 |
|
67 |
-
|
68 |
-
|
69 |
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
return f"""
|
78 |
-
<tr id="trade-row-{s['id']}" hx-swap-oob="afterbegin:#opportunities-table">
|
79 |
-
<td><strong>BTC/USD</strong></td>
|
80 |
-
<td><span class="{pyth_class}">Pyth Network</span><br>${s['pyth_price']:,.2f}</td>
|
81 |
-
<td><span class="{chainlink_class}">Chainlink Agg.</span><br>${s['chainlink_price']:,.2f}</td>
|
82 |
-
<td><strong>{s['spread_pct']:.3f}%</strong></td>
|
83 |
-
<td><span class="risk-{s.get('risk', 'low').lower()}">{s.get('risk', 'N/A')}</span></td>
|
84 |
-
<td>{s.get('rationale', 'N/A')}</td>
|
85 |
-
<td><button class="trade-btn">{s.get('strategy', 'N/A')}</button></td>
|
86 |
-
</tr>
|
87 |
-
<div id="last-update-time" hx-swap-oob="true">{time_str}</div>
|
88 |
-
"""
|
89 |
|
90 |
-
@
|
91 |
-
async def
|
92 |
-
|
93 |
|
94 |
-
|
95 |
-
|
96 |
-
# This endpoint now also imports and uses the singleton broker
|
97 |
-
async def event_generator():
|
98 |
-
while True:
|
99 |
-
# Get the signal from the singleton broker's queue
|
100 |
-
payload = await signal_broker.queue.get()
|
101 |
-
html_card = render_signal_card(payload)
|
102 |
-
data_payload = html_card.replace('\n', ' ').strip()
|
103 |
-
yield f"event: message\ndata: {data_payload}\n\n"
|
104 |
-
return StreamingResponse(event_generator(), media_type="text/event-stream")
|
|
|
1 |
"""
|
2 |
+
Sentinel Arbitrage Engine - v10.0 OMEGA
|
3 |
|
4 |
+
A complete architectural overhaul using FastAPI-SocketIO for guaranteed,
|
5 |
+
real-time signal delivery. This is the definitive money-spinning engine.
|
6 |
"""
|
7 |
import asyncio
|
8 |
import os
|
9 |
from contextlib import asynccontextmanager
|
10 |
from datetime import datetime, timezone
|
|
|
11 |
import httpx
|
12 |
+
import socketio
|
13 |
+
from fastapi import FastAPI
|
14 |
+
from fastapi.staticfiles import StaticFiles
|
15 |
|
16 |
+
from price_fetcher import PriceFetcher
|
17 |
+
from arbitrage_analyzer import ArbitrageAnalyzer
|
|
|
|
|
18 |
|
19 |
OPPORTUNITY_THRESHOLD = 0.001
|
20 |
|
21 |
+
# --- Socket.IO Server Setup ---
|
22 |
+
sio = socketio.AsyncServer(async_mode='asgi', cors_allowed_origins='*')
|
23 |
+
socket_app = socketio.ASGIApp(sio)
|
24 |
+
|
25 |
+
# --- Application Lifespan ---
|
26 |
@asynccontextmanager
|
27 |
async def lifespan(app: FastAPI):
|
28 |
+
print("π Initializing Sentinel Arbitrage Engine v10.0...")
|
29 |
+
price_fetcher = PriceFetcher(httpx.AsyncClient())
|
30 |
+
arbitrage_analyzer = ArbitrageAnalyzer(httpx.AsyncClient())
|
|
|
31 |
|
32 |
+
# Launch the engine as a background task, passing the Socket.IO server instance
|
33 |
+
arbitrage_task = asyncio.create_task(
|
34 |
+
run_arbitrage_detector(price_fetcher, arbitrage_analyzer)
|
35 |
+
)
|
36 |
|
37 |
+
print("β
Engine is online and hunting for opportunities.")
|
38 |
yield
|
39 |
|
40 |
print("β³ Shutting down engine...")
|
41 |
arbitrage_task.cancel()
|
42 |
try: await arbitrage_task
|
43 |
+
except asyncio.CancelledError: print("Engine shut down gracefully.")
|
44 |
|
45 |
+
async def run_arbitrage_detector(price_fetcher, analyzer):
|
46 |
+
"""The core engine loop. Now directly emits events via Socket.IO."""
|
47 |
while True:
|
48 |
+
try:
|
49 |
+
await price_fetcher.update_prices_async()
|
50 |
+
prices = price_fetcher.get_current_prices()
|
51 |
+
|
52 |
+
pyth_price = prices.get("pyth")
|
53 |
+
chainlink_price = prices.get("chainlink_agg")
|
54 |
+
|
55 |
+
if pyth_price and chainlink_price:
|
56 |
+
spread = abs(pyth_price - chainlink_price) / chainlink_price
|
57 |
+
if spread > OPPORTUNITY_THRESHOLD:
|
58 |
+
opportunity = {
|
59 |
+
"pyth_price": pyth_price,
|
60 |
+
"chainlink_price": chainlink_price,
|
61 |
+
"spread_pct": spread * 100
|
62 |
+
}
|
63 |
+
print(f"β‘οΈ Discrepancy Detected: {opportunity['spread_pct']:.3f}%")
|
64 |
+
briefing = await analyzer.get_alpha_briefing(opportunity)
|
65 |
+
if briefing:
|
66 |
+
signal = {**opportunity, **briefing, "timestamp": datetime.now(timezone.utc).isoformat()}
|
67 |
+
# --- THE FIX: Directly emit to all connected clients ---
|
68 |
+
await sio.emit('new_signal', signal)
|
69 |
+
print(f"β
Signal Emitted: {signal['strategy']}")
|
70 |
+
except Exception as e:
|
71 |
+
print(f"β ERROR in engine loop: {e}")
|
72 |
|
73 |
+
await asyncio.sleep(15)
|
74 |
|
75 |
+
# --- FastAPI App Setup ---
|
76 |
+
app = FastAPI(lifespan=lifespan)
|
77 |
|
78 |
+
# This serves the index.html and any other static files (like JS or CSS)
|
79 |
+
app.mount("/", StaticFiles(directory="static", html=True), name="static")
|
80 |
+
|
81 |
+
@sio.event
|
82 |
+
async def connect(sid, environ):
|
83 |
+
print(f"β
Client connected: {sid}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
84 |
|
85 |
+
@sio.event
|
86 |
+
async def disconnect(sid):
|
87 |
+
print(f"π₯ Client disconnected: {sid}")
|
88 |
|
89 |
+
# Mount the Socket.IO app on top of the FastAPI app
|
90 |
+
app.mount('/socket.io', socket_app)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|