Files
trumpsignal-backend/app/api/prices.py
T

84 lines
2.7 KiB
Python

import logging
from typing import List
from fastapi import APIRouter, HTTPException, Query, Request
from fastapi.responses import Response
from app.ratelimit import limiter
from app.config import settings
from app.schemas import Candle
from app.services.price_store import price_store
router = APIRouter()
logger = logging.getLogger(__name__)
VALID_ASSETS = {"BTC", "ETH"}
VALID_TIMEFRAMES = {"5m", "15m", "1H", "4H", "1D", "1W"}
BINANCE_INTERVAL = {
"5m": "5m",
"15m": "15m",
"1H": "1h",
"4H": "4h",
"1D": "1d",
"1W": "1w",
}
SYMBOL_MAP = {"BTC": "BTCUSDT", "ETH": "ETHUSDT"}
async def fetch_binance_candles(asset: str, tf: str, limit: int) -> List[Candle]:
symbol = SYMBOL_MAP[asset]
interval = BINANCE_INTERVAL[tf]
url = f"{settings.binance_rest_url}/api/v3/klines?symbol={symbol}&interval={interval}&limit={limit}"
from app.services.http_client import get_client
resp = await get_client().get(url, timeout=15)
resp.raise_for_status()
rows = resp.json()
return [
Candle(
time=row[0] // 1000, # ms → seconds
open=float(row[1]),
high=float(row[2]),
low=float(row[3]),
close=float(row[4]),
volume=float(row[5]),
)
for row in rows
]
@router.get("/prices/{asset}", response_model=List[Candle])
@limiter.limit("30/minute")
async def get_prices(
request: Request,
asset: str,
tf: str = Query(default="4H"),
limit: int = Query(default=200, ge=1, le=1000),
response: Response = None,
):
asset = asset.upper()
if asset not in VALID_ASSETS:
raise HTTPException(status_code=400, detail=f"Asset must be one of {VALID_ASSETS}")
if tf not in VALID_TIMEFRAMES:
raise HTTPException(status_code=400, detail=f"Timeframe must be one of {VALID_TIMEFRAMES}")
# 1m: use in-memory store (updated in real-time) — don't cache at CDN level
if tf == "1m":
candles = price_store.get_candles(asset, "1m", limit)
if response is not None:
response.headers["Cache-Control"] = "public, max-age=5, stale-while-revalidate=10"
return [Candle(**c) for c in candles]
# Larger timeframes change infrequently — safe to cache longer
if response is not None:
ttl = 60 if tf in ("5m", "15m") else 300 # 1m for short TFs, 5m for 1H+
response.headers["Cache-Control"] = f"public, max-age={ttl}, stale-while-revalidate={ttl * 2}"
try:
return await fetch_binance_candles(asset, tf, limit)
except Exception as exc:
logger.error("Binance REST fetch failed for %s %s: %s", asset, tf, exc)
raise HTTPException(status_code=502, detail="Failed to fetch price data")