Spaces:
Running
Running
Update app/app.py
Browse files- app/app.py +16 -9
app/app.py
CHANGED
@@ -12,17 +12,18 @@ import json
|
|
12 |
import httpx
|
13 |
from fastapi import FastAPI, Request
|
14 |
from fastapi.responses import HTMLResponse, StreamingResponse
|
15 |
-
from fastapi.
|
16 |
|
|
|
17 |
from .price_fetcher import PriceFetcher
|
18 |
from .arbitrage_analyzer import ArbitrageAnalyzer
|
19 |
from .broker import signal_broker
|
20 |
|
21 |
-
OPPORTUNITY_THRESHOLD = 0.0015 # 0.15% price difference
|
22 |
|
23 |
@asynccontextmanager
|
24 |
async def lifespan(app: FastAPI):
|
25 |
-
|
26 |
async with httpx.AsyncClient() as client:
|
27 |
app.state.price_fetcher = PriceFetcher(client)
|
28 |
app.state.arbitrage_analyzer = ArbitrageAnalyzer(client)
|
@@ -35,6 +36,7 @@ async def lifespan(app: FastAPI):
|
|
35 |
except asyncio.CancelledError: print("Engine shut down.")
|
36 |
|
37 |
async def run_arbitrage_detector(app: FastAPI):
|
|
|
38 |
while True:
|
39 |
await app.state.price_fetcher.update_prices_async()
|
40 |
all_prices = app.state.price_fetcher.get_all_prices()
|
@@ -59,10 +61,12 @@ async def run_arbitrage_detector(app: FastAPI):
|
|
59 |
|
60 |
await asyncio.sleep(15)
|
61 |
|
|
|
62 |
app = FastAPI(title="Sentinel Arbitrage Engine", lifespan=lifespan)
|
63 |
-
templates = Jinja2Templates(directory="templates")
|
64 |
|
|
|
65 |
def render_signal_card(payload: dict) -> str:
|
|
|
66 |
s = payload
|
67 |
time_str = datetime.fromisoformat(s['timestamp']).strftime('%H:%M:%S UTC')
|
68 |
pyth_class = "buy" if s['pyth_price'] < s['chainlink_price'] else "sell"
|
@@ -81,16 +85,19 @@ def render_signal_card(payload: dict) -> str:
|
|
81 |
<div id="last-update-time" hx-swap-oob="true">{time_str}</div>
|
82 |
"""
|
83 |
|
84 |
-
|
85 |
-
async def serve_dashboard(request: Request):
|
86 |
-
return templates.TemplateResponse("index.html", {"request": request})
|
87 |
-
|
88 |
@app.get("/api/signals/stream")
|
89 |
async def signal_stream(request: Request):
|
|
|
90 |
async def event_generator():
|
91 |
while True:
|
92 |
payload = await signal_broker.queue.get()
|
93 |
html_card = render_signal_card(payload)
|
94 |
data_payload = html_card.replace('\n', ' ').strip()
|
95 |
yield f"event: message\ndata: {data_payload}\n\n"
|
96 |
-
return StreamingResponse(event_generator(), media_type="text/event-stream")
|
|
|
|
|
|
|
|
|
|
|
|
12 |
import httpx
|
13 |
from fastapi import FastAPI, Request
|
14 |
from fastapi.responses import HTMLResponse, StreamingResponse
|
15 |
+
from fastapi.staticfiles import StaticFiles # <-- We use this instead of Jinja2
|
16 |
|
17 |
+
# Relative imports for package structure
|
18 |
from .price_fetcher import PriceFetcher
|
19 |
from .arbitrage_analyzer import ArbitrageAnalyzer
|
20 |
from .broker import signal_broker
|
21 |
|
22 |
+
OPPORTUNITY_THRESHOLD = 0.0015 # 0.15% price difference
|
23 |
|
24 |
@asynccontextmanager
|
25 |
async def lifespan(app: FastAPI):
|
26 |
+
"""Manages application startup and shutdown events."""
|
27 |
async with httpx.AsyncClient() as client:
|
28 |
app.state.price_fetcher = PriceFetcher(client)
|
29 |
app.state.arbitrage_analyzer = ArbitrageAnalyzer(client)
|
|
|
36 |
except asyncio.CancelledError: print("Engine shut down.")
|
37 |
|
38 |
async def run_arbitrage_detector(app: FastAPI):
|
39 |
+
"""The core engine loop. Checks for opportunities and queues them."""
|
40 |
while True:
|
41 |
await app.state.price_fetcher.update_prices_async()
|
42 |
all_prices = app.state.price_fetcher.get_all_prices()
|
|
|
61 |
|
62 |
await asyncio.sleep(15)
|
63 |
|
64 |
+
# --- FastAPI App Initialization ---
|
65 |
app = FastAPI(title="Sentinel Arbitrage Engine", lifespan=lifespan)
|
|
|
66 |
|
67 |
+
# --- HTML Rendering Helper ---
|
68 |
def render_signal_card(payload: dict) -> str:
|
69 |
+
"""Renders a dictionary of analysis into a styled HTML table row."""
|
70 |
s = payload
|
71 |
time_str = datetime.fromisoformat(s['timestamp']).strftime('%H:%M:%S UTC')
|
72 |
pyth_class = "buy" if s['pyth_price'] < s['chainlink_price'] else "sell"
|
|
|
85 |
<div id="last-update-time" hx-swap-oob="true">{time_str}</div>
|
86 |
"""
|
87 |
|
88 |
+
# --- API Endpoints ---
|
|
|
|
|
|
|
89 |
@app.get("/api/signals/stream")
|
90 |
async def signal_stream(request: Request):
|
91 |
+
"""SSE stream for the automated Signal Stream."""
|
92 |
async def event_generator():
|
93 |
while True:
|
94 |
payload = await signal_broker.queue.get()
|
95 |
html_card = render_signal_card(payload)
|
96 |
data_payload = html_card.replace('\n', ' ').strip()
|
97 |
yield f"event: message\ndata: {data_payload}\n\n"
|
98 |
+
return StreamingResponse(event_generator(), media_type="text/event-stream")
|
99 |
+
|
100 |
+
# --- Static File Server ---
|
101 |
+
# This single mount point serves index.html for the root path "/"
|
102 |
+
# and any other files like CSS or JS from the "static" directory.
|
103 |
+
app.mount("/", StaticFiles(directory="static", html=True), name="static")
|