spb/market-atlas
Public
TypeScript 96.7%
SQL 1.6%
CSS 0.8%
JavaScript 0.5%
1import type { InstrumentHint, NormalizedObservation, RealtimeStatus, RightsStatus, TimestampTrust } from "@market-atlas/market-model";2import type { ProposedInstrument } from "@market-atlas/connector-sdk";34export const CRYPTO_NAMES: Record<string, string> = {5 BTC: "Bitcoin",6 ETH: "Ether",7 SOL: "Solana",8 XRP: "XRP",9 ADA: "Cardano",10 DOGE: "Dogecoin",11 LTC: "Litecoin",12 LINK: "Chainlink",13 AVAX: "Avalanche",14 DOT: "Polkadot",15 BNB: "BNB",16 TRX: "TRON",17 MATIC: "Polygon",18 POL: "Polygon",19 UNI: "Uniswap",20 ATOM: "Cosmos",21 XLM: "Stellar",22 BCH: "Bitcoin Cash",23 ETC: "Ethereum Classic",24 AAVE: "Aave",25 NEAR: "NEAR Protocol",26 SUI: "Sui",27 TON: "Toncoin",28 SHIB: "Shiba Inu",29 PAXG: "PAX Gold",30};3132/** Major coins observed across venues. */33export const MAJOR_BASES = ["BTC", "ETH", "SOL", "XRP", "ADA", "DOGE", "LTC", "LINK", "AVAX", "DOT", "BCH", "XLM", "UNI", "AAVE", "NEAR", "SUI", "PAXG"];3435export function cryptoHint(base: string, quote: string, exchangeId: string): InstrumentHint {36 const b = base.toUpperCase();37 const q = quote.toUpperCase();38 return {39 assetClass: "CRYPTO",40 name: `${CRYPTO_NAMES[b] ?? b} / ${q}`,41 exchangeId,42 currency: q,43 country: "XX",44 base: b,45 quote: q,46 securityType: "SPOT",47 metadata: { featured: ["BTC", "ETH", "SOL", "XRP"].includes(b) && (q === "USD" || q === "USDT") },48 };49}5051export function cryptoSeeds(pairs: Array<[string, string]>, exchangeId: string, toSymbol: (b: string, q: string) => string): ProposedInstrument[] {52 return pairs.map(([b, q]) => ({ symbol: toSymbol(b, q), hint: cryptoHint(b, q, exchangeId), aliases: [`${b}-${q}`, `${b}/${q}`, `${b}${q}`] }));53}5455export interface TickerFields {56 last?: unknown;57 open?: unknown;58 high?: unknown;59 low?: unknown;60 bid?: unknown;61 ask?: unknown;62 bidSize?: unknown;63 askSize?: unknown;64 volume?: unknown;65 vwap?: unknown;66}6768const num = (v: unknown): number | null => {69 if (v == null || v === "") return null;70 const n = typeof v === "number" ? v : Number(v);71 return Number.isFinite(n) ? n : null;72};7374/** Expand a venue ticker into field observations, skipping missing/invalid values. */75export function tickerObservations(76 symbol: string,77 base: string,78 quote: string,79 exchangeId: string,80 f: TickerFields,81 meta: { sourceTimestamp: number | null; timestampTrust: TimestampTrust; sequence?: number | string | null; rightsStatus: RightsStatus; realtimeStatus: RealtimeStatus },82): NormalizedObservation[] {83 const hint = cryptoHint(base, quote, exchangeId);84 const out: NormalizedObservation[] = [];85 const push = (field: NormalizedObservation["field"], v: unknown) => {86 const n = num(v);87 if (n == null) return;88 if (field !== "VOLUME" && field !== "BID_SIZE" && field !== "ASK_SIZE" && n <= 0) return;89 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 });90 };91 push("LAST_PRICE", f.last);92 push("OPEN", f.open);93 push("HIGH", f.high);94 push("LOW", f.low);95 push("BID", f.bid);96 push("ASK", f.ask);97 push("BID_SIZE", f.bidSize);98 push("ASK_SIZE", f.askSize);99 push("VOLUME", f.volume);100 push("VWAP", f.vwap);101 return out;102}103