Spaces:
Sleeping
Sleeping
File size: 4,497 Bytes
b159555 c3b443a ea758f6 c3b443a b159555 c6f94f2 b159555 56eb560 510ee6f b159555 431c9a5 b159555 01e217d 510ee6f c3b443a b349d30 0e87c05 510ee6f 431c9a5 c3b443a 6ca5793 a17b947 c3b443a c6f94f2 0e87c05 6ca5793 510ee6f 6ca5793 073930c 510ee6f c3b443a 073930c 510ee6f 6ca5793 510ee6f 6ca5793 510ee6f 073930c 510ee6f b159555 c3b443a 431c9a5 073930c ea758f6 6ca5793 510ee6f a17b947 6ca5793 510ee6f 6ca5793 510ee6f a17b947 b159555 073930c b159555 431c9a5 24a8706 c6f94f2 ea758f6 a17b947 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
"""
Sentinel Arbitrage Engine - v6.0 FINAL (Rate-Limit Proof)
Detects on-chain vs. off-chain price discrepancies using a fault-tolerant,
multi-source data aggregator.
"""
import asyncio
import os
from contextlib import asynccontextmanager
from datetime import datetime, timezone
import json
import httpx
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse, StreamingResponse
from fastapi.templating import Jinja2Templates
from .price_fetcher import PriceFetcher
from .arbitrage_analyzer import ArbitrageAnalyzer
OPPORTUNITY_THRESHOLD = 0.001 # 0.1% price difference
@asynccontextmanager
async def lifespan(app: FastAPI):
async with httpx.AsyncClient() as client:
app.state.price_fetcher = PriceFetcher(client=client)
app.state.arbitrage_analyzer = ArbitrageAnalyzer(client=client)
app.state.signal_queue: asyncio.Queue = asyncio.Queue()
# Slowing down the loop slightly to be respectful to APIs
arbitrage_task = asyncio.create_task(run_arbitrage_detector(app, 10))
print("🚀 Sentinel Arbitrage Engine v6.0 started successfully.")
yield
print("⏳ Shutting down engine...")
arbitrage_task.cancel()
try: await arbitrage_task
except asyncio.CancelledError: print("Engine shut down.")
async def run_arbitrage_detector(app: FastAPI, interval_seconds: int):
# This loop logic remains the same, but it's now fed by a more reliable PriceFetcher
while True:
await app.state.price_fetcher.update_prices_async()
prices = app.state.price_fetcher.get_current_prices()
on_chain = prices.get("on_chain_pyth")
off_chain = prices.get("off_chain_agg")
if on_chain and off_chain:
spread = abs(on_chain - off_chain) / off_chain
if spread > OPPORTUNITY_THRESHOLD:
opportunity = {
"id": f"{int(datetime.now().timestamp())}",
"on_chain_price": on_chain,
"off_chain_price": off_chain,
"spread_pct": spread * 100
}
print(f"⚡️ Discrepancy Detected: {opportunity['spread_pct']:.3f}%")
briefing = await app.state.arbitrage_analyzer.get_alpha_briefing(opportunity)
if briefing:
signal = {**opportunity, **briefing, "timestamp": datetime.now(timezone.utc).isoformat()}
await app.state.signal_queue.put(signal)
await asyncio.sleep(interval_seconds)
app = FastAPI(title="Sentinel Arbitrage Engine", lifespan=lifespan)
templates = Jinja2Templates(directory="templates")
# No changes needed to render_signal_card, GET /, or the SSE stream endpoint.
# The code below this line is the same as the previous version.
def render_signal_card(payload: dict) -> str:
s = payload
time_str = datetime.fromisoformat(s['timestamp']).strftime('%H:%M:%S UTC')
on_chain_class = "buy" if s['on_chain_price'] < s['off_chain_price'] else "sell"
off_chain_class = "sell" if s['on_chain_price'] < s['off_chain_price'] else "buy"
return f"""
<tr id="trade-row-{s['id']}" hx-swap-oob="afterbegin:#opportunities-table">
<td><strong>BTC/USD</strong></td>
<td><span class="{on_chain_class}">On-Chain (Pyth)</span><br>${s['on_chain_price']:,.2f}</td>
<td><span class="{off_chain_class}">Off-Chain (Agg)</span><br>${s['off_chain_price']:,.2f}</td>
<td><strong>{s['spread_pct']:.3f}%</strong></td>
<td><span class="risk-{s.get('risk', 'low').lower()}">{s.get('risk', 'N/A')}</span></td>
<td>{s.get('rationale', 'N/A')}</td>
<td><button class="trade-btn">{s.get('strategy', 'N/A')}</button></td>
</tr>
<div id="last-update-time" hx-swap-oob="true">{time_str}</div>
"""
@app.get("/", response_class=HTMLResponse)
async def serve_dashboard(request: Request):
return templates.TemplateResponse("index.html", {"request": request})
@app.get("/api/signals/stream")
async def signal_stream(request: Request):
queue: asyncio.Queue = request.app.state.signal_queue
async def event_generator():
while True:
payload = await queue.get()
html_card = render_signal_card(payload)
data_payload = html_card.replace('\n', ' ').strip()
yield f"event: message\ndata: {data_payload}\n\n"
return StreamingResponse(event_generator(), media_type="text/event-stream") |