Spaces:
Running
Running
Update app/app.py
Browse files- app/app.py +28 -42
app/app.py
CHANGED
@@ -1,15 +1,13 @@
|
|
1 |
"""
|
2 |
-
Sentinel Arbitrage Engine -
|
3 |
|
4 |
-
|
5 |
"""
|
6 |
import asyncio
|
7 |
import os
|
8 |
from contextlib import asynccontextmanager
|
9 |
from datetime import datetime, timezone
|
10 |
-
from typing import Dict, Optional
|
11 |
import json
|
12 |
-
|
13 |
import httpx
|
14 |
from fastapi import FastAPI, Request
|
15 |
from fastapi.responses import HTMLResponse, StreamingResponse
|
@@ -18,7 +16,7 @@ from fastapi.templating import Jinja2Templates
|
|
18 |
from .price_fetcher import PriceFetcher
|
19 |
from .arbitrage_analyzer import ArbitrageAnalyzer
|
20 |
|
21 |
-
OPPORTUNITY_THRESHOLD = 0.
|
22 |
|
23 |
@asynccontextmanager
|
24 |
async def lifespan(app: FastAPI):
|
@@ -26,51 +24,38 @@ async def lifespan(app: FastAPI):
|
|
26 |
app.state.price_fetcher = PriceFetcher(client=client)
|
27 |
app.state.arbitrage_analyzer = ArbitrageAnalyzer(client=client)
|
28 |
app.state.signal_queue: asyncio.Queue = asyncio.Queue()
|
29 |
-
|
30 |
-
arbitrage_task = asyncio.create_task(run_arbitrage_detector(app, 5)) # Run every 5 seconds
|
31 |
|
32 |
-
print("🚀 Sentinel Arbitrage Engine started successfully.")
|
33 |
yield
|
34 |
|
35 |
-
print("⏳ Shutting down
|
36 |
arbitrage_task.cancel()
|
37 |
-
try:
|
38 |
-
|
39 |
-
except asyncio.CancelledError:
|
40 |
-
print("Engine shut down.")
|
41 |
|
42 |
async def run_arbitrage_detector(app: FastAPI, interval_seconds: int):
|
43 |
-
last_opportunity = {}
|
44 |
while True:
|
45 |
await app.state.price_fetcher.update_prices_async()
|
46 |
prices = app.state.price_fetcher.get_current_prices()
|
47 |
|
48 |
-
|
49 |
-
|
50 |
-
await asyncio.sleep(interval_seconds)
|
51 |
-
continue
|
52 |
-
|
53 |
-
min_exchange = min(valid_prices, key=valid_prices.get)
|
54 |
-
max_exchange = max(valid_prices, key=valid_prices.get)
|
55 |
-
|
56 |
-
buy_price = valid_prices[min_exchange]
|
57 |
-
sell_price = valid_prices[max_exchange]
|
58 |
|
59 |
-
if
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
print(f"⚡️
|
69 |
briefing = await app.state.arbitrage_analyzer.get_alpha_briefing(opportunity)
|
70 |
if briefing:
|
71 |
signal = {**opportunity, **briefing, "timestamp": datetime.now(timezone.utc).isoformat()}
|
72 |
await app.state.signal_queue.put(signal)
|
73 |
-
last_opportunity = opportunity
|
74 |
|
75 |
await asyncio.sleep(interval_seconds)
|
76 |
|
@@ -80,17 +65,18 @@ templates = Jinja2Templates(directory="templates")
|
|
80 |
def render_signal_card(payload: dict) -> str:
|
81 |
s = payload
|
82 |
time_str = datetime.fromisoformat(s['timestamp']).strftime('%H:%M:%S UTC')
|
|
|
|
|
83 |
|
84 |
return f"""
|
85 |
-
<tr id="trade-row-{s['id']}" hx-swap-oob="
|
86 |
-
<td><strong>BTC</strong></td>
|
87 |
-
<td><span class="
|
88 |
-
<td><span class="
|
89 |
<td><strong>{s['spread_pct']:.3f}%</strong></td>
|
90 |
<td><span class="risk-{s.get('risk', 'low').lower()}">{s.get('risk', 'N/A')}</span></td>
|
91 |
-
<td>{s.get('
|
92 |
-
<td><
|
93 |
-
<td><button class="trade-btn">Simulate Trade</button></td>
|
94 |
</tr>
|
95 |
<div id="last-update-time" hx-swap-oob="true">{time_str}</div>
|
96 |
"""
|
|
|
1 |
"""
|
2 |
+
Sentinel Arbitrage Engine - v5.0 FINAL (Anti-Geoblock)
|
3 |
|
4 |
+
Detects on-chain vs. off-chain price discrepancies and provides AI-powered analysis.
|
5 |
"""
|
6 |
import asyncio
|
7 |
import os
|
8 |
from contextlib import asynccontextmanager
|
9 |
from datetime import datetime, timezone
|
|
|
10 |
import json
|
|
|
11 |
import httpx
|
12 |
from fastapi import FastAPI, Request
|
13 |
from fastapi.responses import HTMLResponse, StreamingResponse
|
|
|
16 |
from .price_fetcher import PriceFetcher
|
17 |
from .arbitrage_analyzer import ArbitrageAnalyzer
|
18 |
|
19 |
+
OPPORTUNITY_THRESHOLD = 0.001 # 0.1% price difference to trigger a signal
|
20 |
|
21 |
@asynccontextmanager
|
22 |
async def lifespan(app: FastAPI):
|
|
|
24 |
app.state.price_fetcher = PriceFetcher(client=client)
|
25 |
app.state.arbitrage_analyzer = ArbitrageAnalyzer(client=client)
|
26 |
app.state.signal_queue: asyncio.Queue = asyncio.Queue()
|
27 |
+
arbitrage_task = asyncio.create_task(run_arbitrage_detector(app, 10))
|
|
|
28 |
|
29 |
+
print("🚀 Sentinel Arbitrage Engine v5.0 started successfully.")
|
30 |
yield
|
31 |
|
32 |
+
print("⏳ Shutting down engine...")
|
33 |
arbitrage_task.cancel()
|
34 |
+
try: await arbitrage_task
|
35 |
+
except asyncio.CancelledError: print("Engine shut down.")
|
|
|
|
|
36 |
|
37 |
async def run_arbitrage_detector(app: FastAPI, interval_seconds: int):
|
|
|
38 |
while True:
|
39 |
await app.state.price_fetcher.update_prices_async()
|
40 |
prices = app.state.price_fetcher.get_current_prices()
|
41 |
|
42 |
+
on_chain = prices.get("on_chain_pyth")
|
43 |
+
off_chain = prices.get("off_chain_agg")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
|
45 |
+
if on_chain and off_chain:
|
46 |
+
spread = abs(on_chain - off_chain) / off_chain
|
47 |
+
if spread > OPPORTUNITY_THRESHOLD:
|
48 |
+
opportunity = {
|
49 |
+
"id": f"{int(datetime.now().timestamp())}",
|
50 |
+
"on_chain_price": on_chain,
|
51 |
+
"off_chain_price": off_chain,
|
52 |
+
"spread_pct": spread * 100
|
53 |
+
}
|
54 |
+
print(f"⚡️ Discrepancy Detected: {opportunity['spread_pct']:.3f}%")
|
55 |
briefing = await app.state.arbitrage_analyzer.get_alpha_briefing(opportunity)
|
56 |
if briefing:
|
57 |
signal = {**opportunity, **briefing, "timestamp": datetime.now(timezone.utc).isoformat()}
|
58 |
await app.state.signal_queue.put(signal)
|
|
|
59 |
|
60 |
await asyncio.sleep(interval_seconds)
|
61 |
|
|
|
65 |
def render_signal_card(payload: dict) -> str:
|
66 |
s = payload
|
67 |
time_str = datetime.fromisoformat(s['timestamp']).strftime('%H:%M:%S UTC')
|
68 |
+
on_chain_class = "buy" if s['on_chain_price'] < s['off_chain_price'] else "sell"
|
69 |
+
off_chain_class = "sell" if s['on_chain_price'] < s['off_chain_price'] else "buy"
|
70 |
|
71 |
return f"""
|
72 |
+
<tr id="trade-row-{s['id']}" hx-swap-oob="afterbegin:#opportunities-table">
|
73 |
+
<td><strong>BTC/USD</strong></td>
|
74 |
+
<td><span class="{on_chain_class}">On-Chain (Pyth)</span><br>${s['on_chain_price']:,.2f}</td>
|
75 |
+
<td><span class="{off_chain_class}">Off-Chain (Agg)</span><br>${s['off_chain_price']:,.2f}</td>
|
76 |
<td><strong>{s['spread_pct']:.3f}%</strong></td>
|
77 |
<td><span class="risk-{s.get('risk', 'low').lower()}">{s.get('risk', 'N/A')}</span></td>
|
78 |
+
<td>{s.get('rationale', 'N/A')}</td>
|
79 |
+
<td><button class="trade-btn">{s.get('strategy', 'N/A')}</button></td>
|
|
|
80 |
</tr>
|
81 |
<div id="last-update-time" hx-swap-oob="true">{time_str}</div>
|
82 |
"""
|