spb/market-atlas
Public
TypeScript 96.7%
SQL 1.6%
CSS 0.8%
JavaScript 0.5%
1import type { AssetClass, InstrumentHint } from "./index.js";23const slug = (s: string) =>4 s5 .toLowerCase()6 .normalize("NFKD")7 .replace(/[̀-ͯ]/g, "")8 .replace(/[^a-z0-9]+/g, "_")9 .replace(/^_+|_+$/g, "");1011/**12 * Canonical symbol form used inside instrument ids and for alias matching.13 * "BRK.B", "BRK-B", "BRK/B", "BRK B" → "brk_b".14 */15export function canonicalSymbol(symbol: string): string {16 return slug(symbol.trim());17}1819/** Deterministic stable ids; never database sequences. */20export function makeInstrumentId(assetClass: AssetClass, hint: { symbol: string } & Partial<InstrumentHint>): string {21 const sym = canonicalSymbol(hint.symbol);22 switch (assetClass) {23 case "CRYPTO":24 return `crypto_${slug(hint.base ?? sym)}_${slug(hint.quote ?? "usd")}`;25 case "FOREX":26 return `fx_${slug(hint.base ?? sym.slice(0, 3))}_${slug(hint.quote ?? sym.slice(3, 6))}`;27 case "INDEX":28 return `index_${slug(hint.country ?? "xx")}_${sym.replace(/^[_^.]+/, "")}`;29 case "TREASURY":30 case "INTEREST_RATE":31 case "BOND":32 return `rate_${slug(hint.country ?? "xx")}_${sym}`;33 case "COMMODITY":34 case "FUTURE":35 return `${assetClass === "FUTURE" ? "fut" : "cmd"}_${slug(hint.exchangeId ?? "xx")}_${sym}`;36 case "ETF":37 case "ETN":38 case "REIT":39 case "ADR":40 case "GDR":41 case "EQUITY":42 default: {43 const prefix =44 assetClass === "ETF" || assetClass === "ETN"45 ? "etf"46 : assetClass === "MUTUAL_FUND"47 ? "fund"48 : assetClass === "OPTION"49 ? "opt"50 : "eq";51 const venue = slug(hint.mic ?? hint.exchangeId ?? "xxxx");52 return `${prefix}_${slug(hint.country ?? "xx")}_${venue}_${sym}`;53 }54 }55}5657export function makeCompanyId(name: string): string {58 return slug(name).replace(/_/g, "-").slice(0, 80);59}6061export function makeEventId(ts: number, fingerprint: string): string {62 return `evt_${ts.toString(36)}_${fingerprint.slice(0, 12)}`;63}6465/** Split "BTC-USD" / "BTC/USD" / "BTCUSD" (6 letters) into base and quote. */66export function splitPair(symbol: string): { base: string; quote: string } | null {67 const m = symbol.toUpperCase().match(/^([A-Z0-9]{2,10})[-_/]([A-Z0-9]{2,6})$/);68 if (m) return { base: m[1]!, quote: m[2]!, };69 const compact = symbol.toUpperCase();70 if (/^[A-Z]{6}$/.test(compact)) return { base: compact.slice(0, 3), quote: compact.slice(3) };71 return null;72}73