mgbam commited on
Commit
2ca2f4e
Β·
verified Β·
1 Parent(s): 04c9c82

Update app/app.py

Browse files
Files changed (1) hide show
  1. app/app.py +36 -86
app/app.py CHANGED
@@ -1,32 +1,34 @@
1
  """
2
- Sentinel Arbitrage Engine - v13.1 FINAL (UI Fix)
3
 
4
- This version uses a file-based log for absolute signal persistence and
5
- a high-frequency polling mechanism for guaranteed data delivery.
6
- It includes the corrected HTML rendering for a professional UI.
7
  """
8
  import asyncio
9
  import os
 
10
  import json
11
  import time
12
- from contextlib import asynccontextmanager
13
  from datetime import datetime, timezone
 
14
  import httpx
15
- from fastapi import FastAPI, Request
16
- from fastapi.responses import FileResponse, HTMLResponse
17
  from fastapi.staticfiles import StaticFiles
18
 
19
  from .price_fetcher import PriceFetcher
20
  from .arbitrage_analyzer import ArbitrageAnalyzer
21
 
22
  OPPORTUNITY_THRESHOLD = 0.0015
23
- SIGNALS_FILE = "signals.json"
 
 
 
24
 
25
  @asynccontextmanager
26
  async def lifespan(app: FastAPI):
27
- if os.path.exists(SIGNALS_FILE):
28
- os.remove(SIGNALS_FILE)
29
-
30
  async with httpx.AsyncClient() as client:
31
  app.state.price_fetcher = PriceFetcher(client)
32
  app.state.arbitrage_analyzer = ArbitrageAnalyzer(client)
@@ -35,7 +37,7 @@ async def lifespan(app: FastAPI):
35
  run_arbitrage_detector(app.state.price_fetcher, app.state.arbitrage_analyzer)
36
  )
37
 
38
- print("πŸš€ Sentinel Arbitrage Engine v13.1 (UI Fix) started.")
39
  yield
40
 
41
  print("⏳ Shutting down engine...")
@@ -44,6 +46,7 @@ async def lifespan(app: FastAPI):
44
  except asyncio.CancelledError: print("Engine shut down gracefully.")
45
 
46
  async def run_arbitrage_detector(price_fetcher, analyzer):
 
47
  while True:
48
  try:
49
  await price_fetcher.update_prices_async()
@@ -56,87 +59,34 @@ async def run_arbitrage_detector(price_fetcher, analyzer):
56
  if pyth_price and chainlink_price and pyth_price > 0:
57
  spread = abs(pyth_price - chainlink_price) / chainlink_price
58
  if spread > OPPORTUNITY_THRESHOLD:
59
- opportunity = {
60
- "asset": asset, "pyth_price": pyth_price,
61
- "chainlink_price": chainlink_price, "spread_pct": spread * 100
62
- }
63
- briefing = await analyzer.get_alpha_briefing(asset, opportunity)
64
- if briefing:
65
- signal = {**opportunity, **briefing, "timestamp": datetime.now(timezone.utc).isoformat()}
66
- try:
67
- data = []
68
- if os.path.exists(SIGNALS_FILE):
69
- with open(SIGNALS_FILE, 'r') as f:
70
- data = json.load(f)
71
- data.insert(0, signal)
72
- with open(SIGNALS_FILE, 'w') as f:
73
- json.dump(data, f)
74
- except (FileNotFoundError, json.JSONDecodeError):
75
- with open(SIGNALS_FILE, 'w') as f:
76
- json.dump([signal], f)
77
-
78
- print(f"βœ… Signal LOGGED for {asset}: {signal['spread_pct']:.3f}%")
79
  except Exception as e:
80
  print(f"❌ ERROR in engine loop: {e}")
81
 
82
  await asyncio.sleep(15)
83
 
 
84
  app = FastAPI(lifespan=lifespan)
85
 
86
- # ====================================================================
87
- # THE CRITICAL FIX IS HERE
88
- # ====================================================================
89
- def render_signal_card(signal: dict) -> str:
90
- """Renders a single signal dictionary into a clean HTML table row."""
91
- s = signal
92
- time_str = datetime.fromisoformat(s['timestamp']).strftime('%H:%M:%S')
93
-
94
- # Determine which price is higher/lower for coloring
95
- is_pyth_cheaper = s['pyth_price'] < s['chainlink_price']
96
-
97
- pyth_price_html = f'<span class="{"buy" if is_pyth_cheaper else "sell"}">${s["pyth_price"]:,.2f}</span>'
98
- chainlink_price_html = f'<span class="{"sell" if is_pyth_cheaper else "buy"}">${s["chainlink_price"]:,.2f}</span>'
99
-
100
- # Build the complete table row with 7 cells
101
- return f"""
102
- <tr>
103
- <td>{time_str}</td>
104
- <td><strong>{s['asset']}/USD</strong></td>
105
- <td>{pyth_price_html}</td>
106
- <td>{chainlink_price_html}</td>
107
- <td><strong class="buy">{s['spread_pct']:.3f}%</strong></td>
108
- <td><span class="risk-{s.get('risk', 'low').lower()}">{s.get('risk', 'N/A')}</span></td>
109
- <td>{s.get('strategy', 'N/A')}</td>
110
- </tr>
111
- """
112
- # ====================================================================
113
-
114
- @app.get("/api/signals", response_class=HTMLResponse)
115
- async def get_signals_table(request: Request):
116
- """Reads the signals file and renders the entire table body."""
117
- try:
118
- with open(SIGNALS_FILE, 'r') as f:
119
- signals = json.load(f)
120
- except (FileNotFoundError, json.JSONDecodeError):
121
- signals = []
122
-
123
- if not signals:
124
- return HTMLResponse('<tr><td colspan="7" style="text-align:center;">Monitoring for arbitrage opportunities...</td></tr>')
125
-
126
- # Generate all table rows
127
- table_rows_html = "".join([render_signal_card(s) for s in signals])
128
-
129
- # Calculate total simulated profit
130
- total_profit = 0
131
- for s in signals:
132
- profit = abs(s['chainlink_price'] - s['pyth_price'])
133
- total_profit += profit * (1 - 0.002) # Assume 0.2% total fees
134
-
135
- # Create the P/L ticker with an OOB swap attribute
136
- profit_html = f'<span id="pnl-ticker" hx-swap-oob="true">Simulated P/L: <span style="color: #34D399;">${total_profit:,.2f}</span></span>'
137
 
138
- # Return the P/L ticker and the table rows together
139
- return HTMLResponse(profit_html + table_rows_html)
 
140
 
141
- # Serve the static files (like index.html)
142
  app.mount("/", StaticFiles(directory="static", html=True), name="static")
 
1
  """
2
+ Sentinel Arbitrage Engine - v14.0 FINAL (Socket.IO Multi-Asset)
3
 
4
+ The definitive version, combining a robust multi-asset detection engine
5
+ with a high-performance Socket.IO backend for guaranteed, real-time
6
+ signal delivery and a professional UI.
7
  """
8
  import asyncio
9
  import os
10
+ from contextlib import asynccontextmanager
11
  import json
12
  import time
 
13
  from datetime import datetime, timezone
14
+
15
  import httpx
16
+ import socketio
17
+ from fastapi import FastAPI
18
  from fastapi.staticfiles import StaticFiles
19
 
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
+ socket_app = socketio.ASGIApp(sio)
28
 
29
  @asynccontextmanager
30
  async def lifespan(app: FastAPI):
31
+ print("πŸš€ Initializing Sentinel Arbitrage Engine v14.0...")
 
 
32
  async with httpx.AsyncClient() as client:
33
  app.state.price_fetcher = PriceFetcher(client)
34
  app.state.arbitrage_analyzer = ArbitrageAnalyzer(client)
 
37
  run_arbitrage_detector(app.state.price_fetcher, app.state.arbitrage_analyzer)
38
  )
39
 
40
+ print("βœ… Engine is online and hunting for opportunities.")
41
  yield
42
 
43
  print("⏳ Shutting down engine...")
 
46
  except asyncio.CancelledError: print("Engine shut down gracefully.")
47
 
48
  async def run_arbitrage_detector(price_fetcher, analyzer):
49
+ last_opportunity_time = 0
50
  while True:
51
  try:
52
  await price_fetcher.update_prices_async()
 
59
  if pyth_price and chainlink_price and pyth_price > 0:
60
  spread = abs(pyth_price - chainlink_price) / chainlink_price
61
  if spread > OPPORTUNITY_THRESHOLD:
62
+ current_time = time.time()
63
+ if current_time - last_opportunity_time > 20: # Throttle Gemini calls
64
+ last_opportunity_time = current_time
65
+ opportunity = {
66
+ "asset": asset, "pyth_price": pyth_price,
67
+ "chainlink_price": chainlink_price, "spread_pct": spread * 100
68
+ }
69
+ print(f"⚑️ Dislocation for {asset}: {opportunity['spread_pct']:.3f}%")
70
+ briefing = await analyzer.get_alpha_briefing(asset, opportunity)
71
+ if briefing:
72
+ signal = {**opportunity, **briefing, "timestamp": datetime.now(timezone.utc).isoformat()}
73
+ await sio.emit('new_signal', signal)
74
+ print(f"βœ… Signal Emitted for {asset}: {signal['strategy']}")
 
 
 
 
 
 
 
75
  except Exception as e:
76
  print(f"❌ ERROR in engine loop: {e}")
77
 
78
  await asyncio.sleep(15)
79
 
80
+ # --- FastAPI App & Socket.IO Setup ---
81
  app = FastAPI(lifespan=lifespan)
82
 
83
+ @sio.event
84
+ async def connect(sid, environ):
85
+ print(f"βœ… Client connected: {sid}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
+ @sio.event
88
+ async def disconnect(sid):
89
+ print(f"πŸ”₯ Client disconnected: {sid}")
90
 
91
+ app.mount('/socket.io', socket_app)
92
  app.mount("/", StaticFiles(directory="static", html=True), name="static")