mgbam commited on
Commit
1690b33
Β·
verified Β·
1 Parent(s): faf277f

Update app/app.py

Browse files
Files changed (1) hide show
  1. app/app.py +62 -76
app/app.py CHANGED
@@ -1,104 +1,90 @@
1
  """
2
- Sentinel Arbitrage Engine - v9.0 FINAL (Singleton Broker)
3
 
4
- This version uses a singleton message broker to ensure robust, stateful
5
- communication between the background engine and the API endpoints.
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
- from fastapi import FastAPI, Request
14
- from fastapi.responses import HTMLResponse, StreamingResponse
15
- from fastapi.templating import Jinja2Templates
16
 
17
- # Import our local modules AND the new singleton broker
18
- from .price_fetcher import PriceFetcher
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
- # We no longer need to manage the queue in the app state.
27
- # The broker exists independently.
28
- app.state.price_fetcher = PriceFetcher(httpx.AsyncClient())
29
- app.state.arbitrage_analyzer = ArbitrageAnalyzer(httpx.AsyncClient())
30
 
31
- arbitrage_task = asyncio.create_task(run_arbitrage_detector(app))
 
 
 
32
 
33
- print("πŸš€ Sentinel Arbitrage Engine v9.0 (Singleton Broker) started.")
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(app: FastAPI):
42
- # Now this function uses the globally imported signal_broker
43
  while True:
44
- await app.state.price_fetcher.update_prices_async()
45
- prices = app.state.price_fetcher.get_current_prices()
46
-
47
- pyth_price = prices.get("pyth")
48
- chainlink_price = prices.get("chainlink_agg")
49
-
50
- if pyth_price and chainlink_price:
51
- spread = abs(pyth_price - chainlink_price) / chainlink_price
52
- if spread > OPPORTUNITY_THRESHOLD:
53
- opportunity = {
54
- "id": f"{int(datetime.now().timestamp())}",
55
- "pyth_price": pyth_price, "chainlink_price": chainlink_price,
56
- "spread_pct": spread * 100
57
- }
58
- briefing = await app.state.arbitrage_analyzer.get_alpha_briefing(opportunity)
59
- if briefing:
60
- signal = {**opportunity, **briefing, "timestamp": datetime.now(timezone.utc).isoformat()}
61
- # Put the signal into the singleton broker's queue
62
- await signal_broker.queue.put(signal)
63
- print(f"βœ… Signal Queued: {signal['spread_pct']:.3f}% Discrepancy")
 
 
 
 
64
 
65
- await asyncio.sleep(15) # Check every 15 seconds
66
 
67
- app = FastAPI(title="Sentinel Arbitrage Engine", lifespan=lifespan)
68
- templates = Jinja2Templates(directory="templates")
69
 
70
- def render_signal_card(payload: dict) -> str:
71
- # This rendering function remains the same
72
- s = payload
73
- time_str = datetime.fromisoformat(s['timestamp']).strftime('%H:%M:%S UTC')
74
- pyth_class = "buy" if s['pyth_price'] < s['chainlink_price'] else "sell"
75
- chainlink_class = "sell" if s['pyth_price'] < s['chainlink_price'] else "buy"
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
- @app.get("/", response_class=HTMLResponse)
91
- async def serve_dashboard(request: Request):
92
- return templates.TemplateResponse("index.html", {"request": request})
93
 
94
- @app.get("/api/signals/stream")
95
- async def signal_stream(request: Request):
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)