Initial commit: Gold Trading Simulator with AI-powered analysis
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import json
|
||||
from typing import List, Callable, Awaitable
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
from app.streaming.binance_hub import hub as binance_hub
|
||||
from app.streaming.alpha_hub import alpha_hub
|
||||
|
||||
router = APIRouter(prefix="/stream", tags=["Stream"])
|
||||
|
||||
|
||||
@router.get("/sse")
|
||||
async def stream_sse(symbols: str = "BTCUSDT,XAUUSD", timeframe: str = "1m"):
|
||||
"""
|
||||
Server-Sent Events (SSE) multiplexer for multiple symbols over a single connection.
|
||||
- Supports 1m timeframe (server streams 1m updates; clients can resample locally).
|
||||
- symbols: comma-separated list (e.g., BTCUSDT,ETHUSDT,XAUUSD)
|
||||
"""
|
||||
if not symbols:
|
||||
raise HTTPException(status_code=400, detail="symbols must not be empty")
|
||||
if timeframe != "1m":
|
||||
raise HTTPException(status_code=400, detail="Only timeframe=1m is supported")
|
||||
|
||||
syms: List[str] = [s.strip().upper().replace("/", "") for s in symbols.split(",") if s.strip()]
|
||||
if not syms:
|
||||
raise HTTPException(status_code=400, detail="No valid symbols provided")
|
||||
|
||||
out_queue: asyncio.Queue = asyncio.Queue(maxsize=1000)
|
||||
tasks: List[asyncio.Task] = []
|
||||
unsubscribers: List[Callable[[], Awaitable[None]]] = []
|
||||
|
||||
async def add_subscription(sym: str):
|
||||
if sym.startswith("XAU"):
|
||||
q, unsubscribe = await alpha_hub.subscribe(sym, timeframe="1m")
|
||||
else:
|
||||
q, unsubscribe = await binance_hub.subscribe(sym, timeframe="1m")
|
||||
unsubscribers.append(unsubscribe)
|
||||
|
||||
async def worker():
|
||||
try:
|
||||
while True:
|
||||
evt = await q.get()
|
||||
if evt is None:
|
||||
break
|
||||
try:
|
||||
await out_queue.put(evt)
|
||||
except Exception:
|
||||
await asyncio.sleep(0)
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
tasks.append(asyncio.create_task(worker()))
|
||||
|
||||
for s in syms:
|
||||
await add_subscription(s)
|
||||
|
||||
async def event_generator():
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
evt = await asyncio.wait_for(out_queue.get(), timeout=15.0)
|
||||
data = json.dumps(evt, separators=(",", ":"))
|
||||
yield f"event: kline\n".encode("utf-8")
|
||||
yield f"data: {data}\n\n".encode("utf-8")
|
||||
except asyncio.TimeoutError:
|
||||
# Keep-alive comment
|
||||
yield b": ping\n\n"
|
||||
finally:
|
||||
for t in tasks:
|
||||
t.cancel()
|
||||
for t in tasks:
|
||||
with contextlib.suppress(Exception):
|
||||
await t
|
||||
for u in unsubscribers:
|
||||
with contextlib.suppress(Exception):
|
||||
await u()
|
||||
|
||||
headers = {
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"X-Accel-Buffering": "no",
|
||||
}
|
||||
return StreamingResponse(event_generator(), media_type="text/event-stream", headers=headers)
|
||||
Reference in New Issue
Block a user