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
+15
View File
@@ -0,0 +1,15 @@
from __future__ import annotations
import abc
from typing import AsyncIterator
from .typing import Kline
class BaseProvider(abc.ABC):
@abc.abstractmethod
async def stream_klines(self, symbol: str, timeframe: str) -> AsyncIterator["Kline"]:
...
@abc.abstractmethod
async def get_historical_ohlcv(self, symbol: str, timeframe: str, start=None, end=None):
...
@@ -0,0 +1,50 @@
from __future__ import annotations
import asyncio
import json
import websockets
from typing import AsyncIterator
from datetime import datetime
from ..typing import Kline
import os
class BinanceWSProvider:
def __init__(self, base_url: str | None = None):
self.base_url = base_url or os.getenv("BINANCE_WS_URL", "wss://stream.binance.com:9443/ws")
async def stream_klines(self, symbol: str, timeframe: str) -> AsyncIterator[Kline]:
# Binance expects lowercase, no slash: BTCUSDT -> btcusdt
stream = f"{symbol.lower()}@kline_{timeframe}"
url = self.base_url.rstrip("/").replace("/ws", "/stream") + f"?streams={stream}"
async for msg in self._ws_loop(url):
try:
data = json.loads(msg)
k = data.get("data", {}).get("k", {})
if not k:
continue
yield Kline(
symbol=symbol,
timeframe=timeframe,
open_time=datetime.fromtimestamp(k["t"] / 1000.0),
close_time=datetime.fromtimestamp(k["T"] / 1000.0),
open=float(k["o"]),
high=float(k["h"]),
low=float(k["l"]),
close=float(k["c"]),
volume=float(k.get("v", 0.0)),
is_closed=bool(k.get("x", False)),
source="binance",
)
except Exception:
continue
async def _ws_loop(self, url: str):
while True:
try:
async with websockets.connect(url, ping_interval=20, ping_timeout=20) as ws:
async for message in ws:
yield message
except Exception:
await asyncio.sleep(2)
@@ -0,0 +1,60 @@
from __future__ import annotations
import asyncio
from datetime import datetime
from typing import AsyncIterator
import httpx
from ..typing import Kline
from app.config import settings
ALPHA_BASE = "https://www.alphavantage.co/query"
class AlphaVantageXAUProvider:
def __init__(self, api_key: str | None = None):
self.api_key = api_key or settings.alpha_vantage_api_key
async def stream_klines(self, symbol: str, timeframe: str) -> AsyncIterator[Kline]:
# Poll once per minute due to AV rate limits
assert symbol.upper() in {"XAUUSD", "XAU/USD"}
from_symbol = "XAU"
to_symbol = "USD"
interval = "1min" if timeframe == "1m" else "5min"
async with httpx.AsyncClient(timeout=30) as client:
while True:
params = {
"function": "FX_INTRADAY",
"from_symbol": from_symbol,
"to_symbol": to_symbol,
"interval": interval,
"outputsize": "compact",
"apikey": self.api_key or "demo",
}
try:
r = await client.get(ALPHA_BASE, params=params)
r.raise_for_status()
js = r.json()
# Pick the latest candle
key = f"Time Series FX ({interval})"
series = js.get(key) or {}
if series:
ts, row = next(iter(series.items()))
dt = datetime.fromisoformat(ts)
yield Kline(
symbol="XAUUSD",
timeframe=timeframe,
open_time=dt,
close_time=dt,
open=float(row["1. open"]),
high=float(row["2. high"]),
low=float(row["3. low"]),
close=float(row["4. close"]),
volume=float(row.get("5. volume", 0.0)),
is_closed=True,
source="alpha_vantage",
)
except Exception:
pass
await asyncio.sleep(60)
+18
View File
@@ -0,0 +1,18 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
@dataclass
class Kline:
symbol: str
timeframe: str
open_time: datetime
close_time: datetime
open: float
high: float
low: float
close: float
volume: float = 0.0
is_closed: bool = True
source: str = "other"