import type { InstrumentHint, NormalizedObservation, RealtimeStatus, RightsStatus, TimestampTrust } from "@market-atlas/market-model"; import type { ProposedInstrument } from "@market-atlas/connector-sdk"; export const CRYPTO_NAMES: Record = { BTC: "Bitcoin", ETH: "Ether", SOL: "Solana", XRP: "XRP", ADA: "Cardano", DOGE: "Dogecoin", LTC: "Litecoin", LINK: "Chainlink", AVAX: "Avalanche", DOT: "Polkadot", BNB: "BNB", TRX: "TRON", MATIC: "Polygon", POL: "Polygon", UNI: "Uniswap", ATOM: "Cosmos", XLM: "Stellar", BCH: "Bitcoin Cash", ETC: "Ethereum Classic", AAVE: "Aave", NEAR: "NEAR Protocol", SUI: "Sui", TON: "Toncoin", SHIB: "Shiba Inu", PAXG: "PAX Gold", }; /** Major coins observed across venues. */ export const MAJOR_BASES = ["BTC", "ETH", "SOL", "XRP", "ADA", "DOGE", "LTC", "LINK", "AVAX", "DOT", "BCH", "XLM", "UNI", "AAVE", "NEAR", "SUI", "PAXG"]; export function cryptoHint(base: string, quote: string, exchangeId: string): InstrumentHint { const b = base.toUpperCase(); const q = quote.toUpperCase(); return { assetClass: "CRYPTO", name: `${CRYPTO_NAMES[b] ?? b} / ${q}`, exchangeId, currency: q, country: "XX", base: b, quote: q, securityType: "SPOT", metadata: { featured: ["BTC", "ETH", "SOL", "XRP"].includes(b) && (q === "USD" || q === "USDT") }, }; } export function cryptoSeeds(pairs: Array<[string, string]>, exchangeId: string, toSymbol: (b: string, q: string) => string): ProposedInstrument[] { return pairs.map(([b, q]) => ({ symbol: toSymbol(b, q), hint: cryptoHint(b, q, exchangeId), aliases: [`${b}-${q}`, `${b}/${q}`, `${b}${q}`] })); } export interface TickerFields { last?: unknown; open?: unknown; high?: unknown; low?: unknown; bid?: unknown; ask?: unknown; bidSize?: unknown; askSize?: unknown; volume?: unknown; vwap?: unknown; } const num = (v: unknown): number | null => { if (v == null || v === "") return null; const n = typeof v === "number" ? v : Number(v); return Number.isFinite(n) ? n : null; }; /** Expand a venue ticker into field observations, skipping missing/invalid values. */ export function tickerObservations( symbol: string, base: string, quote: string, exchangeId: string, f: TickerFields, meta: { sourceTimestamp: number | null; timestampTrust: TimestampTrust; sequence?: number | string | null; rightsStatus: RightsStatus; realtimeStatus: RealtimeStatus }, ): NormalizedObservation[] { const hint = cryptoHint(base, quote, exchangeId); const out: NormalizedObservation[] = []; const push = (field: NormalizedObservation["field"], v: unknown) => { const n = num(v); if (n == null) return; if (field !== "VOLUME" && field !== "BID_SIZE" && field !== "ASK_SIZE" && n <= 0) return; out.push({ symbol, instrumentHint: hint, field, value: n, currency: quote.toUpperCase(), observationType: field === "BID" || field === "ASK" || field === "BID_SIZE" || field === "ASK_SIZE" ? "QUOTE" : "TRADE", sourceTimestamp: meta.sourceTimestamp, timestampTrust: meta.timestampTrust, sequence: meta.sequence ?? null, rightsStatus: meta.rightsStatus, realtimeStatus: meta.realtimeStatus }); }; push("LAST_PRICE", f.last); push("OPEN", f.open); push("HIGH", f.high); push("LOW", f.low); push("BID", f.bid); push("ASK", f.ask); push("BID_SIZE", f.bidSize); push("ASK_SIZE", f.askSize); push("VOLUME", f.volume); push("VWAP", f.vwap); return out; }