mgbam commited on
Commit
243f098
Β·
verified Β·
1 Parent(s): 259af0a

Update app/app.py

Browse files
Files changed (1) hide show
  1. app/app.py +51 -39
app/app.py CHANGED
@@ -1,8 +1,9 @@
1
  """
2
- Sentinel Arbitrage Engine - v13.0 OMEGA (File-Based & Unbreakable)
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
  """
7
  import asyncio
8
  import os
@@ -15,7 +16,6 @@ from fastapi import FastAPI, Request
15
  from fastapi.responses import FileResponse, HTMLResponse
16
  from fastapi.staticfiles import StaticFiles
17
 
18
- # --- RELATIVE IMPORTS ---
19
  from .price_fetcher import PriceFetcher
20
  from .arbitrage_analyzer import ArbitrageAnalyzer
21
 
@@ -24,7 +24,6 @@ SIGNALS_FILE = "signals.json"
24
 
25
  @asynccontextmanager
26
  async def lifespan(app: FastAPI):
27
- # Clear the log on startup
28
  if os.path.exists(SIGNALS_FILE):
29
  os.remove(SIGNALS_FILE)
30
 
@@ -36,7 +35,7 @@ async def lifespan(app: FastAPI):
36
  run_arbitrage_detector(app.state.price_fetcher, app.state.arbitrage_analyzer)
37
  )
38
 
39
- print("πŸš€ Sentinel Arbitrage Engine v13.0 (File-Based) started.")
40
  yield
41
 
42
  print("⏳ Shutting down engine...")
@@ -45,7 +44,6 @@ async def lifespan(app: FastAPI):
45
  except asyncio.CancelledError: print("Engine shut down gracefully.")
46
 
47
  async def run_arbitrage_detector(price_fetcher, analyzer):
48
- """The core engine loop. Detects opportunities and writes them to a file."""
49
  while True:
50
  try:
51
  await price_fetcher.update_prices_async()
@@ -65,17 +63,17 @@ async def run_arbitrage_detector(price_fetcher, analyzer):
65
  briefing = await analyzer.get_alpha_briefing(asset, opportunity)
66
  if briefing:
67
  signal = {**opportunity, **briefing, "timestamp": datetime.now(timezone.utc).isoformat()}
68
-
69
- # --- THE FIX: Write signal directly to the JSON file ---
70
  try:
71
- with open(SIGNALS_FILE, 'r+') as f:
72
- data = json.load(f)
73
- data.insert(0, signal) # Prepend new signal
74
- f.seek(0)
75
- json.dump(data, f, indent=4)
 
 
76
  except (FileNotFoundError, json.JSONDecodeError):
77
  with open(SIGNALS_FILE, 'w') as f:
78
- json.dump([signal], f, indent=4)
79
 
80
  print(f"βœ… Signal LOGGED for {asset}: {signal['spread_pct']:.3f}%")
81
  except Exception as e:
@@ -83,13 +81,39 @@ async def run_arbitrage_detector(price_fetcher, analyzer):
83
 
84
  await asyncio.sleep(15)
85
 
86
- # --- FastAPI App Setup ---
87
  app = FastAPI(lifespan=lifespan)
88
 
89
- # --- API Endpoints ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  @app.get("/api/signals", response_class=HTMLResponse)
91
  async def get_signals_table(request: Request):
92
- """Reads the signals file and renders the table body."""
93
  try:
94
  with open(SIGNALS_FILE, 'r') as f:
95
  signals = json.load(f)
@@ -97,34 +121,22 @@ async def get_signals_table(request: Request):
97
  signals = []
98
 
99
  if not signals:
100
- return HTMLResponse('<tr id="placeholder-row"><td colspan="7" style="text-align:center;">Monitoring for arbitrage opportunities...</td></tr>')
101
 
102
- table_rows = []
 
 
 
103
  total_profit = 0
104
  for s in signals:
105
- pyth_class = "buy" if s['pyth_price'] < s['chainlink_price'] else "sell"
106
- chainlink_class = "sell" if s['pyth_price'] < s['chainlink_price'] else "buy"
107
- profit = (s['chainlink_price'] - s['pyth_price']) if pyth_class == 'buy' else (s['pyth_price'] - s['chainlink_price'])
108
- profit_after_fees = profit * (1 - 0.002) # Assume 0.2% total fees
109
- total_profit += profit_after_fees
110
 
111
- table_rows.append(f"""
112
- <tr>
113
- <td>{datetime.fromisoformat(s['timestamp']).strftime('%H:%M:%S')}</td>
114
- <td><strong>{s['asset']}/USD</strong></td>
115
- <td><span class="{pyth_class}">Pyth</span><br>${s['pyth_price']:,.2f}</td>
116
- <td><span class="{chainlink_class}">Agg.</span><br>${s['chainlink_price']:,.2f}</td>
117
- <td><strong>{s['spread_pct']:.3f}%</strong></td>
118
- <td><span class="risk-{s.get('risk', 'low').lower()}">{s.get('risk', 'N/A')}</span></td>
119
- <td>{s.get('strategy', 'N/A')}</td>
120
- </tr>
121
- """)
122
-
123
- # Use OOB swap to update the P/L ticker
124
  profit_html = f'<span id="pnl-ticker" hx-swap-oob="true">Simulated P/L: <span style="color: #34D399;">${total_profit:,.2f}</span></span>'
125
 
126
- return HTMLResponse(profit_html + "".join(table_rows))
127
-
128
 
129
- # Serve the static files (like index.html) from the 'static' directory
130
  app.mount("/", StaticFiles(directory="static", html=True), name="static")
 
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
 
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
 
 
24
 
25
  @asynccontextmanager
26
  async def lifespan(app: FastAPI):
 
27
  if os.path.exists(SIGNALS_FILE):
28
  os.remove(SIGNALS_FILE)
29
 
 
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
  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()
 
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:
 
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)
 
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")