"""Pair metadata discovery (AssetPairs) → `pairs` table. Capabilities are read from the exchange, never assumed.""" from __future__ import annotations import asyncpg from ktrade.kraken.models import PairMeta from ktrade.kraken.rest import KrakenREST from ktrade.logging import get_logger log = get_logger("ingest.pairs") async def refresh_pairs(rest: KrakenREST, pool: asyncpg.Pool, universe: list[str]) -> dict[str, PairMeta]: pairs = await rest.asset_pairs() uni = set(universe) async with pool.acquire() as conn, conn.transaction(): for k, p in pairs.items(): await conn.execute( """INSERT INTO pairs (pair_id, altname, wsname, base, quote, status, ordermin, costmin, tick_size, pair_decimals, lot_decimals, cost_decimals, leverage_buy, leverage_sell, in_universe, raw, updated_at) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16, now()) ON CONFLICT (pair_id) DO UPDATE SET altname=EXCLUDED.altname, wsname=EXCLUDED.wsname, base=EXCLUDED.base, quote=EXCLUDED.quote, status=EXCLUDED.status, ordermin=EXCLUDED.ordermin, costmin=EXCLUDED.costmin, tick_size=EXCLUDED.tick_size, pair_decimals=EXCLUDED.pair_decimals, lot_decimals=EXCLUDED.lot_decimals, cost_decimals=EXCLUDED.cost_decimals, leverage_buy=EXCLUDED.leverage_buy, leverage_sell=EXCLUDED.leverage_sell, in_universe=EXCLUDED.in_universe, raw=EXCLUDED.raw, updated_at=now()""", k, p.altname, p.wsname, p.base, p.quote, p.status, p.ordermin, p.costmin, p.tick_size, p.pair_decimals, p.lot_decimals, p.cost_decimals, p.leverage_buy, p.leverage_sell, p.wsname in uni, p.model_dump(mode="json"), ) missing = uni - {p.wsname for p in pairs.values()} if missing: log.warning("ingest.pairs.universe_missing", symbols=sorted(missing)) log.info("ingest.pairs.refreshed", count=len(pairs), universe=len(uni - missing)) return {p.wsname: p for p in pairs.values() if p.wsname in uni}