Initial commit: Gold Trading Simulator with AI-powered analysis

This commit is contained in:
Krikorios
2025-11-16 00:50:04 +02:00
commit 72c1d3adb7
128 changed files with 16232 additions and 0 deletions
+115
View File
@@ -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))