"""Trading 212 read-only client. Ported from /home/gg/ownCloud/Family/Finances/Wealth/trading212.py — same Basic auth scheme, same endpoints, async via httpx. Live endpoint only (demo would need a separate key pair). """ from __future__ import annotations import asyncio import base64 import time import httpx from app.config import get_settings LIVE_BASE = "https://live.trading212.com/api/v0" class Trading212: def __init__(self, api_key: str | None = None, secret_key: str | None = None): s = get_settings() api_key = api_key or s.API_KEY secret_key = secret_key or s.SECRET_KEY if not api_key or not secret_key: raise RuntimeError("Trading 212 API_KEY/SECRET_KEY missing in env") token = base64.b64encode(f"{api_key}:{secret_key}".encode()).decode() self.headers = { "Authorization": f"Basic {token}", "Accept": "application/json", "User-Agent": "cassandra/0.1", } async def _request( self, client: httpx.AsyncClient, method: str, path: str, **kwargs ): url = f"{LIVE_BASE}{path}" r = await client.request(method, url, headers=self.headers, timeout=30, **kwargs) if r.status_code == 429: reset = float(r.headers.get("x-ratelimit-reset", "1")) wait = max(1.0, reset - time.time()) await asyncio.sleep(wait) r = await client.request(method, url, headers=self.headers, timeout=30, **kwargs) r.raise_for_status() if not r.content: return None ctype = r.headers.get("content-type", "") return r.json() if "json" in ctype else r.text async def summary(self, client: httpx.AsyncClient): return await self._request(client, "GET", "/equity/account/summary") async def cash(self, client: httpx.AsyncClient): return await self._request(client, "GET", "/equity/account/cash") async def positions(self, client: httpx.AsyncClient): return await self._request(client, "GET", "/equity/portfolio") async def position(self, client: httpx.AsyncClient, ticker: str): return await self._request(client, "GET", f"/equity/portfolio/{ticker}") async def orders(self, client: httpx.AsyncClient): return await self._request(client, "GET", "/equity/orders") async def instruments(self, client: httpx.AsyncClient): """Full catalogue of tradable instruments. Used to enrich position rows with human-readable names.""" return await self._request(client, "GET", "/equity/metadata/instruments")