import type { AssetClass, InstrumentHint } from "./index.js"; const slug = (s: string) => s .toLowerCase() .normalize("NFKD") .replace(/[̀-ͯ]/g, "") .replace(/[^a-z0-9]+/g, "_") .replace(/^_+|_+$/g, ""); /** * Canonical symbol form used inside instrument ids and for alias matching. * "BRK.B", "BRK-B", "BRK/B", "BRK B" → "brk_b". */ export function canonicalSymbol(symbol: string): string { return slug(symbol.trim()); } /** Deterministic stable ids; never database sequences. */ export function makeInstrumentId(assetClass: AssetClass, hint: { symbol: string } & Partial): string { const sym = canonicalSymbol(hint.symbol); switch (assetClass) { case "CRYPTO": return `crypto_${slug(hint.base ?? sym)}_${slug(hint.quote ?? "usd")}`; case "FOREX": return `fx_${slug(hint.base ?? sym.slice(0, 3))}_${slug(hint.quote ?? sym.slice(3, 6))}`; case "INDEX": return `index_${slug(hint.country ?? "xx")}_${sym.replace(/^[_^.]+/, "")}`; case "TREASURY": case "INTEREST_RATE": case "BOND": return `rate_${slug(hint.country ?? "xx")}_${sym}`; case "COMMODITY": case "FUTURE": return `${assetClass === "FUTURE" ? "fut" : "cmd"}_${slug(hint.exchangeId ?? "xx")}_${sym}`; case "ETF": case "ETN": case "REIT": case "ADR": case "GDR": case "EQUITY": default: { const prefix = assetClass === "ETF" || assetClass === "ETN" ? "etf" : assetClass === "MUTUAL_FUND" ? "fund" : assetClass === "OPTION" ? "opt" : "eq"; const venue = slug(hint.mic ?? hint.exchangeId ?? "xxxx"); return `${prefix}_${slug(hint.country ?? "xx")}_${venue}_${sym}`; } } } export function makeCompanyId(name: string): string { return slug(name).replace(/_/g, "-").slice(0, 80); } export function makeEventId(ts: number, fingerprint: string): string { return `evt_${ts.toString(36)}_${fingerprint.slice(0, 12)}`; } /** Split "BTC-USD" / "BTC/USD" / "BTCUSD" (6 letters) into base and quote. */ export function splitPair(symbol: string): { base: string; quote: string } | null { const m = symbol.toUpperCase().match(/^([A-Z0-9]{2,10})[-_/]([A-Z0-9]{2,6})$/); if (m) return { base: m[1]!, quote: m[2]!, }; const compact = symbol.toUpperCase(); if (/^[A-Z]{6}$/.test(compact)) return { base: compact.slice(0, 3), quote: compact.slice(3) }; return null; }