Initial commit: Gold Trading Simulator with AI-powered analysis
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Query, HTTPException
|
||||
from typing import List, Dict, Any
|
||||
|
||||
from app.services.crypto.binance_rest import fetch_klines as binance_klines
|
||||
from app.services.metals.alpha_fx import fetch_fx_intraday, fetch_fx_daily
|
||||
from app.utils.cache import TTLCache
|
||||
from app.streaming.live_store import live_store
|
||||
|
||||
router = APIRouter(prefix="/ohlcv", tags=["OHLCV"])
|
||||
|
||||
_cache = TTLCache(default_ttl=60, maxsize=128)
|
||||
|
||||
|
||||
def _resample(data: List[Dict[str, Any]], timeframe: str) -> List[Dict[str, Any]]:
|
||||
# data is ascending, 1m or 5m depending on source
|
||||
import math
|
||||
seconds_map = {"1m": 60, "5m": 300, "1h": 3600, "4h": 14400, "1d": 86400}
|
||||
tf_sec = seconds_map.get(timeframe, 60)
|
||||
buckets: Dict[int, Dict[str, Any]] = {}
|
||||
for d in data:
|
||||
b = (d["time"] // tf_sec) * tf_sec
|
||||
cur = buckets.get(b)
|
||||
if cur is None:
|
||||
buckets[b] = {
|
||||
"time": b,
|
||||
"open": d["open"],
|
||||
"high": d["high"],
|
||||
"low": d["low"],
|
||||
"close": d["close"],
|
||||
"volume": d.get("volume", 0.0),
|
||||
}
|
||||
else:
|
||||
cur["high"] = max(cur["high"], d["high"])
|
||||
cur["low"] = min(cur["low"], d["low"])
|
||||
cur["close"] = d["close"]
|
||||
cur["volume"] = cur.get("volume", 0.0) + d.get("volume", 0.0)
|
||||
out = list(buckets.values())
|
||||
out.sort(key=lambda x: x["time"])
|
||||
return out
|
||||
|
||||
|
||||
def _ttl_for(sym: str, timeframe: str) -> int:
|
||||
# Tune TTL based on timeframe and provider characteristics
|
||||
if sym.startswith("XAU"):
|
||||
# Alpha Vantage free tier ~ 1/min practical cadence
|
||||
if timeframe in ("1m", "5m"): return 60
|
||||
if timeframe in ("1h", "4h"): return 300
|
||||
return 3600
|
||||
else:
|
||||
# Binance updates are frequent; cache briefly
|
||||
if timeframe == "1m": return 10
|
||||
if timeframe in ("5m",): return 20
|
||||
if timeframe in ("1h", "4h"): return 120
|
||||
return 900
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def get_ohlcv(
|
||||
symbol: str = Query(..., description="e.g., BTCUSDT, ETHUSDT, XAUUSD"),
|
||||
timeframe: str = Query("1m", description="1m,5m,1h,4h,1d"),
|
||||
limit: int = Query(500, ge=10, le=1000),
|
||||
) -> List[Dict[str, Any]]:
|
||||
try:
|
||||
sym = symbol.upper().replace("/", "")
|
||||
key = (sym, timeframe)
|
||||
cached = _cache.get(key)
|
||||
if cached is not None:
|
||||
return cached[-limit:]
|
||||
|
||||
if sym.startswith("XAU"):
|
||||
# Prefer live store 1m if available (ingested by alpha_hub)
|
||||
live_1m = live_store.get_history(sym, "1m")
|
||||
if live_1m:
|
||||
if timeframe == "1m":
|
||||
return live_1m[-limit:]
|
||||
data = _resample(live_1m, timeframe)
|
||||
return data[-limit:]
|
||||
# Fallback to Alpha Vantage REST
|
||||
if timeframe in ("1m", "5m"):
|
||||
base_tf = timeframe
|
||||
data = await fetch_fx_intraday(sym, interval="1min" if timeframe == "1m" else "5min")
|
||||
elif timeframe in ("1h", "4h"):
|
||||
base_tf = "5m"
|
||||
data = await fetch_fx_intraday(sym, interval="5min")
|
||||
else: # daily
|
||||
base_tf = "1d"
|
||||
data = await fetch_fx_daily(sym)
|
||||
if timeframe != base_tf:
|
||||
data = _resample(data, timeframe)
|
||||
ttl = _ttl_for(sym, timeframe)
|
||||
_cache.set(key, data, ttl=ttl)
|
||||
return data[-limit:]
|
||||
else:
|
||||
# Binance
|
||||
if timeframe not in ("1m", "5m", "1h", "4h", "1d"):
|
||||
raise HTTPException(status_code=400, detail="Unsupported timeframe")
|
||||
# Prefer live store for 1m data if available
|
||||
live_1m = live_store.get_history(sym, "1m")
|
||||
if live_1m:
|
||||
if timeframe == "1m":
|
||||
return live_1m[-limit:]
|
||||
# Resample from 1m to requested timeframe
|
||||
data = _resample(live_1m, timeframe)
|
||||
return data[-limit:]
|
||||
# Fallback to REST
|
||||
data = await binance_klines(sym, interval=timeframe, limit=1000)
|
||||
ttl = _ttl_for(sym, timeframe)
|
||||
_cache.set(key, data, ttl=ttl)
|
||||
return data[-limit:]
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
Reference in New Issue
Block a user