SPB Git forge

spb/market-atlas

Public
12commits 1branches 0releases
1.1 MBsize
maindefault branch
10 days agolast push
TypeScript 96.7% SQL 1.6% CSS 0.8% JavaScript 0.5%
3.8 KB · 78 lines typescript
Raw Blame History
1import { defineConnector, raw, type NormalizedBatch, type ProposedInstrument } from "@market-atlas/connector-sdk";23const URL = "https://www.sec.gov/files/company_tickers_exchange.json";4const EXCHANGE_MAP: Record<string, { exchangeId: string; mic: string } | null> = {5  Nasdaq: { exchangeId: "xnas", mic: "XNAS" },6  NYSE: { exchangeId: "xnys", mic: "XNYS" },7  CBOE: { exchangeId: "bats", mic: "BATS" },8  OTC: null,9};1011/** SEC company ↔ ticker ↔ exchange directory (bulk JSON, refreshed daily). Enriches instruments with CIK + company; creates listed US equities. */12export const secCompanyTickers = defineConnector({13  metadata: {14    id: "sec-company-tickers",15    name: "SEC — company tickers & exchanges directory",16    version: "1.0.0",17    sourceId: "sec-edgar",18    organization: "U.S. Securities and Exchange Commission",19    sourceType: "BULK_FILE",20    jurisdiction: "US",21    rightsStatus: "OFFICIAL_OPEN_DATA",22    realtimeStatus: "END_OF_DAY",23    expectedLatencyMs: null,24    supportsStreaming: false,25    supportsHistorical: false,26    assetClasses: ["EQUITY"],27    exchanges: ["xnas", "xnys", "bats"],28    homepage: "https://www.sec.gov/files/company_tickers_exchange.json",29    description: "Bulk directory of ~10,000 SEC registrants with ticker, listing exchange and CIK. Used to resolve filings to instruments and to attach companies (CIK) to US equities.",30    rightsNotes: "U.S. government data (public domain).",31    termsUrl: "https://www.sec.gov/os/accessing-edgar-data",32    sourceFamily: "sec",33    enabled: true,34  },35  rateLimits: { "www.sec.gov": 2 },36  schedule: { intervalMs: 24 * 60 * 60_000 },37  async poll(ctx) {38    const res = await ctx.http.getText(URL, { timeoutMs: 60_000, headers: { accept: "application/json" } });39    if (res.notModified) return [];40    return [raw("sec-company-tickers", "sec-edgar", "directory", JSON.parse(res.text))];41  },42  normalize(r): NormalizedBatch {43    const p = r.payload as { fields?: string[]; data?: Array<[number, string, string, string | null]> };44    if (r.kind !== "directory" || !Array.isArray(p.data) || !Array.isArray(p.fields)) return { observations: [] };45    const idx = (n: string) => p.fields!.indexOf(n);46    const iCik = idx("cik"), iName = idx("name"), iTicker = idx("ticker"), iEx = idx("exchange");47    if ([iCik, iName, iTicker, iEx].some((i) => i < 0)) return { observations: [] };48    const instruments: ProposedInstrument[] = [];49    for (const row of p.data) {50      const ticker = String(row[iTicker] ?? "").trim();51      const name = String(row[iName] ?? "").trim();52      const cik = String(row[iCik] ?? "").trim();53      const ex = row[iEx] ? EXCHANGE_MAP[String(row[iEx])] : null;54      if (!ticker || !name || !cik) continue;55      const listed = ex != null;56      instruments.push({57        symbol: ticker.replace("-", "."),58        aliases: ticker.includes("-") ? [ticker] : [],59        // Listed names may be created (id derives from the exchange); OTC/unknown venues only enrich existing rows.60        createIfMissing: listed,61        hint: { assetClass: "EQUITY", name: titleCase(name), exchangeId: ex?.exchangeId ?? null, mic: ex?.mic ?? null, currency: "USD", country: "US", securityType: "COMMON_STOCK", companyName: titleCase(name), cik },62      });63    }64    return { observations: [], instruments, stats: { rows: p.data.length } };65  },66  fixturesDir: "fixtures",67});6869/** "MICROSOFT CORP" → "Microsoft Corp" while preserving already mixed-case names like "Apple Inc.". */70export function titleCase(s: string): string {71  if (s !== s.toUpperCase()) return s;72  return s73    .toLowerCase()74    .replace(/(^|[\s\-/(&])([a-z])/g, (m, pre: string, c: string) => pre + c.toUpperCase())75    .replace(/\b(Llc|Inc|Corp|Ltd|Plc|Co|Sa|Nv|Ag|Lp)\b/g, (w) => w)76    .replace(/\b(Etf|Reit|Spac|Usa|Us|Adr|Ii|Iii|Iv)\b/g, (w) => w.toUpperCase());77}78