market-feed 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,137 @@
1
1
  # market-feed Changelog
2
2
 
3
+ ## 0.8.0 — 2026-03-12
4
+
5
+ ### New module
6
+
7
+ **`market-feed/fundamentals`** — Financial statement types and a `getFundamentals()` convenience function.
8
+
9
+ ```ts
10
+ import { getFundamentals } from "market-feed/fundamentals";
11
+ import { MarketFeed } from "market-feed";
12
+
13
+ const feed = new MarketFeed();
14
+ const { incomeStatements, balanceSheets, cashFlows } = await getFundamentals(feed, "AAPL");
15
+
16
+ console.log(`Revenue: $${(incomeStatements[0]!.revenue! / 1e9).toFixed(1)}B`);
17
+ console.log(`Total assets: $${(balanceSheets[0]!.totalAssets! / 1e9).toFixed(1)}B`);
18
+ console.log(`Free cash flow: $${(cashFlows[0]!.freeCashFlow! / 1e9).toFixed(1)}B`);
19
+ ```
20
+
21
+ `getFundamentals()` fetches all three statements in parallel via `Promise.allSettled` — a failure on one statement still returns the others.
22
+
23
+ #### `IncomeStatement`
24
+
25
+ | Field | Description |
26
+ |-------|-------------|
27
+ | `revenue` | Total revenue |
28
+ | `grossProfit` | Revenue - cost of revenue |
29
+ | `operatingIncome` | EBIT (operating) |
30
+ | `netIncome` | Bottom-line net income |
31
+ | `ebitda` | EBITDA when available |
32
+ | `dilutedEps` | Diluted EPS |
33
+
34
+ #### `BalanceSheet`
35
+
36
+ | Field | Description |
37
+ |-------|-------------|
38
+ | `totalAssets` | Total assets |
39
+ | `totalLiabilities` | Total liabilities |
40
+ | `totalStockholdersEquity` | Book value of equity |
41
+ | `cashAndCashEquivalents` | Cash + cash equivalents |
42
+ | `totalDebt` | Short + long-term debt |
43
+
44
+ #### `CashFlowStatement`
45
+
46
+ | Field | Description |
47
+ |-------|-------------|
48
+ | `operatingCashFlow` | Cash from operations |
49
+ | `capitalExpenditures` | CapEx (negative = outflow) |
50
+ | `freeCashFlow` | operatingCashFlow + capitalExpenditures |
51
+ | `investingCashFlow` | Cash from investing |
52
+ | `financingCashFlow` | Cash from financing |
53
+
54
+ All three types include `symbol`, `date` (period end), `periodType` (`"annual"` | `"quarterly"`), and `provider`.
55
+
56
+ ### New methods on `MarketFeed`
57
+
58
+ ```ts
59
+ const feed = new MarketFeed();
60
+
61
+ const income = await feed.incomeStatements("AAPL", { limit: 4 });
62
+ const balance = await feed.balanceSheets("AAPL", { quarterly: true });
63
+ const cash = await feed.cashFlows("AAPL");
64
+ ```
65
+
66
+ `FundamentalsOptions`: `quarterly?` (default `false`), `limit?` (default `4`), `raw?`.
67
+
68
+ ### Provider support
69
+
70
+ | Method | `YahooProvider` | others |
71
+ |--------|-----------------|--------|
72
+ | `incomeStatements` | ✓ `incomeStatementHistory` / `incomeStatementHistoryQuarterly` | — |
73
+ | `balanceSheets` | ✓ `balanceSheetHistory` / `balanceSheetHistoryQuarterly` | — |
74
+ | `cashFlows` | ✓ `cashflowStatementHistory` / `cashflowStatementHistoryQuarterly` | — |
75
+
76
+ ### Breaking changes
77
+
78
+ None. All v0.7.x imports continue to work unchanged.
79
+
80
+ ### Other changes
81
+
82
+ - New types `IncomeStatement`, `BalanceSheet`, `CashFlowStatement`, `FundamentalsOptions` exported from main `market-feed` entry point
83
+ - `CacheMethod` extended with `"incomeStatements" | "balanceSheets" | "cashFlows"` (TTL: 24 h each)
84
+ - 13 new unit tests (460 total across 24 test files)
85
+ - 10 tsup library entry points + 1 CLI binary
86
+
87
+ ---
88
+
89
+ ## 0.7.0 — 2026-03-12
90
+
91
+ ### New provider
92
+
93
+ **`TiingoProvider`** — Tiingo (free tier: EOD prices, real-time IEX quotes, news).
94
+
95
+ ```ts
96
+ import { MarketFeed, TiingoProvider } from "market-feed";
97
+
98
+ const feed = new MarketFeed([
99
+ new TiingoProvider({ apiKey: process.env.TIINGO_KEY! }),
100
+ ]);
101
+
102
+ const quote = await feed.quote(["AAPL"]);
103
+ const bars = await feed.historical("AAPL", { period1: "2024-01-01" });
104
+ const results = await feed.search("apple");
105
+ const profile = await feed.company("AAPL");
106
+ const news = await feed.news("AAPL", { limit: 5 });
107
+ ```
108
+
109
+ Supports: `quote`, `historical`, `search`, `company`, `news`.
110
+
111
+ | Feature | Detail |
112
+ |---------|--------|
113
+ | Real-time quotes | IEX endpoint — US equities, intraday |
114
+ | Historical | EOD daily bars, includes `adjClose` |
115
+ | Rate limit | ~50 calls/hour (free plan) |
116
+ | Auth | `Authorization: Token KEY` header |
117
+
118
+ Free plan sign-up: https://www.tiingo.com
119
+
120
+ ### CLI
121
+
122
+ `--tiingo-key <key>` flag adds `TiingoProvider` to the CLI provider chain.
123
+
124
+ ### Breaking changes
125
+
126
+ None. All v0.6.x imports continue to work unchanged.
127
+
128
+ ### Other changes
129
+
130
+ - `TiingoProvider` and `TiingoProviderOptions` exported from main `market-feed` entry point
131
+ - 21 new unit tests (447 total across 23 test files)
132
+
133
+ ---
134
+
3
135
  ## 0.6.0 — 2026-03-12
4
136
 
5
137
  ### New provider
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # market-feed
2
2
 
3
3
  > Unified TypeScript client for financial market data.
4
- > Wraps Yahoo Finance, Alpha Vantage, Polygon.io, Finnhub, and Twelve Data under one consistent interface — with caching and automatic fallback built in.
4
+ > Wraps Yahoo Finance, Alpha Vantage, Polygon.io, Finnhub, Twelve Data, and Tiingo under one consistent interface — with caching and automatic fallback built in.
5
5
 
6
6
  [![CI](https://github.com/piyushgupta344/market-feed/actions/workflows/ci.yml/badge.svg)](https://github.com/piyushgupta344/market-feed/actions/workflows/ci.yml)
7
7
  [![npm version](https://badge.fury.io/js/market-feed.svg)](https://www.npmjs.com/package/market-feed)
@@ -39,7 +39,7 @@ const quote = await feed.quote("AAPL");
39
39
  console.log(quote.price); // always a number, always the same key
40
40
  ```
41
41
 
42
- One interface. Five providers. Zero API key required for Yahoo Finance.
42
+ One interface. Six providers. Zero API key required for Yahoo Finance.
43
43
 
44
44
  ---
45
45
 
package/dist/cli.js CHANGED
@@ -348,6 +348,70 @@ function transformSearch(quote, raw) {
348
348
  ...raw !== void 0 ? { raw } : {}
349
349
  };
350
350
  }
351
+ function transformIncomeStatement(symbol, entry, periodType, raw) {
352
+ const rawDate = entry.endDate?.raw;
353
+ return {
354
+ symbol: symbol.toUpperCase(),
355
+ date: rawDate ? new Date(rawDate * 1e3) : /* @__PURE__ */ new Date(0),
356
+ periodType,
357
+ ...entry.totalRevenue?.raw !== void 0 ? { revenue: entry.totalRevenue.raw } : {},
358
+ ...entry.costOfRevenue?.raw !== void 0 ? { costOfRevenue: entry.costOfRevenue.raw } : {},
359
+ ...entry.grossProfit?.raw !== void 0 ? { grossProfit: entry.grossProfit.raw } : {},
360
+ ...entry.researchDevelopment?.raw !== void 0 ? { researchAndDevelopment: entry.researchDevelopment.raw } : {},
361
+ ...entry.sellingGeneralAdministrative?.raw !== void 0 ? { sellingGeneralAdministrative: entry.sellingGeneralAdministrative.raw } : {},
362
+ ...entry.totalOperatingExpenses?.raw !== void 0 ? { totalOperatingExpenses: entry.totalOperatingExpenses.raw } : {},
363
+ ...entry.operatingIncome?.raw !== void 0 ? { operatingIncome: entry.operatingIncome.raw } : {},
364
+ ...entry.netIncome?.raw !== void 0 ? { netIncome: entry.netIncome.raw } : {},
365
+ ...entry.ebit?.raw !== void 0 ? { ebit: entry.ebit.raw } : {},
366
+ ...entry.ebitda?.raw !== void 0 ? { ebitda: entry.ebitda.raw } : {},
367
+ ...entry.dilutedEps?.raw !== void 0 ? { dilutedEps: entry.dilutedEps.raw } : {},
368
+ provider: PROVIDER,
369
+ ...raw !== void 0 ? { raw } : {}
370
+ };
371
+ }
372
+ function transformBalanceSheet(symbol, entry, periodType, raw) {
373
+ const rawDate = entry.endDate?.raw;
374
+ return {
375
+ symbol: symbol.toUpperCase(),
376
+ date: rawDate ? new Date(rawDate * 1e3) : /* @__PURE__ */ new Date(0),
377
+ periodType,
378
+ ...entry.totalAssets?.raw !== void 0 ? { totalAssets: entry.totalAssets.raw } : {},
379
+ ...entry.totalCurrentAssets?.raw !== void 0 ? { totalCurrentAssets: entry.totalCurrentAssets.raw } : {},
380
+ ...entry.totalLiab?.raw !== void 0 ? { totalLiabilities: entry.totalLiab.raw } : {},
381
+ ...entry.totalCurrentLiabilities?.raw !== void 0 ? { totalCurrentLiabilities: entry.totalCurrentLiabilities.raw } : {},
382
+ ...entry.totalStockholderEquity?.raw !== void 0 ? { totalStockholdersEquity: entry.totalStockholderEquity.raw } : {},
383
+ ...entry.cash?.raw !== void 0 ? { cashAndCashEquivalents: entry.cash.raw } : {},
384
+ ...entry.shortTermInvestments?.raw !== void 0 ? { shortTermInvestments: entry.shortTermInvestments.raw } : {},
385
+ ...entry.netReceivables?.raw !== void 0 ? { netReceivables: entry.netReceivables.raw } : {},
386
+ ...entry.inventory?.raw !== void 0 ? { inventory: entry.inventory.raw } : {},
387
+ ...entry.shortLongTermDebt?.raw !== void 0 ? { shortTermDebt: entry.shortLongTermDebt.raw } : {},
388
+ ...entry.longTermDebt?.raw !== void 0 ? { longTermDebt: entry.longTermDebt.raw } : {},
389
+ ...entry.totalDebt?.raw !== void 0 ? { totalDebt: entry.totalDebt.raw } : {},
390
+ ...entry.retainedEarnings?.raw !== void 0 ? { retainedEarnings: entry.retainedEarnings.raw } : {},
391
+ provider: PROVIDER,
392
+ ...raw !== void 0 ? { raw } : {}
393
+ };
394
+ }
395
+ function transformCashFlowStatement(symbol, entry, periodType, raw) {
396
+ const rawDate = entry.endDate?.raw;
397
+ const operatingCF = entry.totalCashFromOperatingActivities?.raw;
398
+ const capex = entry.capitalExpenditures?.raw;
399
+ const freeCashFlow = operatingCF !== void 0 && capex !== void 0 ? operatingCF + capex : void 0;
400
+ return {
401
+ symbol: symbol.toUpperCase(),
402
+ date: rawDate ? new Date(rawDate * 1e3) : /* @__PURE__ */ new Date(0),
403
+ periodType,
404
+ ...operatingCF !== void 0 ? { operatingCashFlow: operatingCF } : {},
405
+ ...entry.totalCashflowsFromInvestingActivities?.raw !== void 0 ? { investingCashFlow: entry.totalCashflowsFromInvestingActivities.raw } : {},
406
+ ...entry.totalCashFromFinancingActivities?.raw !== void 0 ? { financingCashFlow: entry.totalCashFromFinancingActivities.raw } : {},
407
+ ...entry.changeInCash?.raw !== void 0 ? { netChangeInCash: entry.changeInCash.raw } : {},
408
+ ...capex !== void 0 ? { capitalExpenditures: capex } : {},
409
+ ...freeCashFlow !== void 0 ? { freeCashFlow } : {},
410
+ ...entry.depreciation?.raw !== void 0 ? { depreciation: entry.depreciation.raw } : {},
411
+ provider: PROVIDER,
412
+ ...raw !== void 0 ? { raw } : {}
413
+ };
414
+ }
351
415
 
352
416
  // src/providers/yahoo/index.ts
353
417
  var YahooProvider = class {
@@ -498,6 +562,93 @@ var YahooProvider = class {
498
562
  return transformDividends(symbol, result, options?.raw ? data : void 0).slice(0, limit);
499
563
  }
500
564
  // ---------------------------------------------------------------------------
565
+ // Fundamentals: income statements
566
+ // ---------------------------------------------------------------------------
567
+ async incomeStatements(symbol, options) {
568
+ const s = toYahooSymbol(symbol);
569
+ const limit = options?.limit ?? 4;
570
+ const quarterly = options?.quarterly ?? false;
571
+ const module = quarterly ? "incomeStatementHistoryQuarterly" : "incomeStatementHistory";
572
+ const data = await this.http2.get(`/v10/finance/quoteSummary/${s}`, {
573
+ params: { modules: module }
574
+ });
575
+ const result = data.quoteSummary.result?.[0];
576
+ if (!result) {
577
+ const err = data.quoteSummary.error;
578
+ throw new ProviderError(
579
+ err?.description ?? `No income statement data for symbol "${s}"`,
580
+ this.name
581
+ );
582
+ }
583
+ const history = quarterly ? result.incomeStatementHistoryQuarterly?.incomeStatementHistory : result.incomeStatementHistory?.incomeStatementHistory;
584
+ return (history ?? []).slice(0, limit).map(
585
+ (entry) => transformIncomeStatement(
586
+ symbol,
587
+ entry,
588
+ quarterly ? "quarterly" : "annual",
589
+ options?.raw ? entry : void 0
590
+ )
591
+ );
592
+ }
593
+ // ---------------------------------------------------------------------------
594
+ // Fundamentals: balance sheets
595
+ // ---------------------------------------------------------------------------
596
+ async balanceSheets(symbol, options) {
597
+ const s = toYahooSymbol(symbol);
598
+ const limit = options?.limit ?? 4;
599
+ const quarterly = options?.quarterly ?? false;
600
+ const module = quarterly ? "balanceSheetHistoryQuarterly" : "balanceSheetHistory";
601
+ const data = await this.http2.get(`/v10/finance/quoteSummary/${s}`, {
602
+ params: { modules: module }
603
+ });
604
+ const result = data.quoteSummary.result?.[0];
605
+ if (!result) {
606
+ const err = data.quoteSummary.error;
607
+ throw new ProviderError(
608
+ err?.description ?? `No balance sheet data for symbol "${s}"`,
609
+ this.name
610
+ );
611
+ }
612
+ const statements = quarterly ? result.balanceSheetHistoryQuarterly?.balanceSheetStatements : result.balanceSheetHistory?.balanceSheetStatements;
613
+ return (statements ?? []).slice(0, limit).map(
614
+ (entry) => transformBalanceSheet(
615
+ symbol,
616
+ entry,
617
+ quarterly ? "quarterly" : "annual",
618
+ options?.raw ? entry : void 0
619
+ )
620
+ );
621
+ }
622
+ // ---------------------------------------------------------------------------
623
+ // Fundamentals: cash flow statements
624
+ // ---------------------------------------------------------------------------
625
+ async cashFlows(symbol, options) {
626
+ const s = toYahooSymbol(symbol);
627
+ const limit = options?.limit ?? 4;
628
+ const quarterly = options?.quarterly ?? false;
629
+ const module = quarterly ? "cashflowStatementHistoryQuarterly" : "cashflowStatementHistory";
630
+ const data = await this.http2.get(`/v10/finance/quoteSummary/${s}`, {
631
+ params: { modules: module }
632
+ });
633
+ const result = data.quoteSummary.result?.[0];
634
+ if (!result) {
635
+ const err = data.quoteSummary.error;
636
+ throw new ProviderError(
637
+ err?.description ?? `No cash flow data for symbol "${s}"`,
638
+ this.name
639
+ );
640
+ }
641
+ const statements = quarterly ? result.cashflowStatementHistoryQuarterly?.cashflowStatements : result.cashflowStatementHistory?.cashflowStatements;
642
+ return (statements ?? []).slice(0, limit).map(
643
+ (entry) => transformCashFlowStatement(
644
+ symbol,
645
+ entry,
646
+ quarterly ? "quarterly" : "annual",
647
+ options?.raw ? entry : void 0
648
+ )
649
+ );
650
+ }
651
+ // ---------------------------------------------------------------------------
501
652
  // Splits
502
653
  // ---------------------------------------------------------------------------
503
654
  async splits(symbol, options) {
@@ -544,7 +695,10 @@ var DEFAULT_TTLS = {
544
695
  marketStatus: 60,
545
696
  earnings: 3600,
546
697
  dividends: 86400,
547
- splits: 86400
698
+ splits: 86400,
699
+ incomeStatements: 86400,
700
+ balanceSheets: 86400,
701
+ cashFlows: 86400
548
702
  };
549
703
  var MarketFeed = class {
550
704
  _providers;
@@ -697,6 +851,50 @@ var MarketFeed = class {
697
851
  return result;
698
852
  }
699
853
  // ---------------------------------------------------------------------------
854
+ // incomeStatements
855
+ // ---------------------------------------------------------------------------
856
+ async incomeStatements(symbol, options) {
857
+ const cacheKey = `incomeStatements:${symbol}:${options?.quarterly ?? false}:${options?.limit ?? 4}`;
858
+ const cached = await this.getCache(cacheKey);
859
+ if (cached) return cached;
860
+ const result = await this.withFallback("incomeStatements", (provider) => {
861
+ if (!provider.incomeStatements)
862
+ throw new UnsupportedOperationError(provider.name, "incomeStatements");
863
+ return provider.incomeStatements(symbol, options);
864
+ });
865
+ await this.setCache(cacheKey, result, "incomeStatements");
866
+ return result;
867
+ }
868
+ // ---------------------------------------------------------------------------
869
+ // balanceSheets
870
+ // ---------------------------------------------------------------------------
871
+ async balanceSheets(symbol, options) {
872
+ const cacheKey = `balanceSheets:${symbol}:${options?.quarterly ?? false}:${options?.limit ?? 4}`;
873
+ const cached = await this.getCache(cacheKey);
874
+ if (cached) return cached;
875
+ const result = await this.withFallback("balanceSheets", (provider) => {
876
+ if (!provider.balanceSheets)
877
+ throw new UnsupportedOperationError(provider.name, "balanceSheets");
878
+ return provider.balanceSheets(symbol, options);
879
+ });
880
+ await this.setCache(cacheKey, result, "balanceSheets");
881
+ return result;
882
+ }
883
+ // ---------------------------------------------------------------------------
884
+ // cashFlows
885
+ // ---------------------------------------------------------------------------
886
+ async cashFlows(symbol, options) {
887
+ const cacheKey = `cashFlows:${symbol}:${options?.quarterly ?? false}:${options?.limit ?? 4}`;
888
+ const cached = await this.getCache(cacheKey);
889
+ if (cached) return cached;
890
+ const result = await this.withFallback("cashFlows", (provider) => {
891
+ if (!provider.cashFlows) throw new UnsupportedOperationError(provider.name, "cashFlows");
892
+ return provider.cashFlows(symbol, options);
893
+ });
894
+ await this.setCache(cacheKey, result, "cashFlows");
895
+ return result;
896
+ }
897
+ // ---------------------------------------------------------------------------
700
898
  // Cache management
701
899
  // ---------------------------------------------------------------------------
702
900
  /** Invalidate all cached entries. */
@@ -1585,9 +1783,213 @@ function subtractOneYear3() {
1585
1783
  return d;
1586
1784
  }
1587
1785
 
1786
+ // src/providers/tiingo/transform.ts
1787
+ var PROVIDER5 = "tiingo";
1788
+ function transformQuote5(iex, meta, raw) {
1789
+ const price = iex.last ?? iex.tngoLast;
1790
+ const prevClose = iex.prevClose;
1791
+ const change = price - prevClose;
1792
+ const changePercent = prevClose !== 0 ? change / prevClose * 100 : 0;
1793
+ return {
1794
+ symbol: iex.ticker.toUpperCase(),
1795
+ name: meta?.name ?? iex.ticker.toUpperCase(),
1796
+ price,
1797
+ change,
1798
+ changePercent,
1799
+ open: iex.open ?? price,
1800
+ high: iex.high ?? price,
1801
+ low: iex.low ?? price,
1802
+ close: price,
1803
+ previousClose: prevClose,
1804
+ volume: iex.volume,
1805
+ currency: "USD",
1806
+ exchange: meta?.exchangeCode ?? "",
1807
+ timestamp: new Date(iex.timestamp),
1808
+ provider: PROVIDER5,
1809
+ ...raw !== void 0 ? { raw } : {}
1810
+ };
1811
+ }
1812
+ function transformHistoricalBar2(bar, raw) {
1813
+ return {
1814
+ date: new Date(bar.date),
1815
+ open: bar.open,
1816
+ high: bar.high,
1817
+ low: bar.low,
1818
+ close: bar.close,
1819
+ adjClose: bar.adjClose,
1820
+ volume: bar.volume,
1821
+ ...raw !== void 0 ? { raw } : {}
1822
+ };
1823
+ }
1824
+ function transformSearch5(item, raw) {
1825
+ return {
1826
+ symbol: item.ticker.toUpperCase(),
1827
+ name: item.name,
1828
+ type: mapAssetType(item.assetType),
1829
+ exchange: item.exchangeCode || void 0,
1830
+ provider: PROVIDER5,
1831
+ ...raw !== void 0 ? { raw } : {}
1832
+ };
1833
+ }
1834
+ function transformCompany5(meta, raw) {
1835
+ return {
1836
+ symbol: meta.ticker.toUpperCase(),
1837
+ name: meta.name,
1838
+ description: meta.description || void 0,
1839
+ exchange: meta.exchangeCode || void 0,
1840
+ provider: PROVIDER5,
1841
+ ...raw !== void 0 ? { raw } : {}
1842
+ };
1843
+ }
1844
+ function transformNews3(article, raw) {
1845
+ return {
1846
+ id: String(article.id),
1847
+ title: article.title,
1848
+ summary: article.description || void 0,
1849
+ url: article.url,
1850
+ source: article.source,
1851
+ publishedAt: new Date(article.publishedDate),
1852
+ symbols: article.tickers.map((t) => t.toUpperCase()),
1853
+ provider: PROVIDER5,
1854
+ ...raw !== void 0 ? { raw } : {}
1855
+ };
1856
+ }
1857
+ function mapAssetType(type) {
1858
+ if (!type) return "stock";
1859
+ const t = type.toLowerCase();
1860
+ if (t === "stock" || t === "equity") return "stock";
1861
+ if (t === "etf") return "etf";
1862
+ if (t === "mutualfund" || t === "mutual fund") return "mutual-fund";
1863
+ if (t === "crypto") return "crypto";
1864
+ if (t === "forex") return "forex";
1865
+ return "unknown";
1866
+ }
1867
+
1868
+ // src/providers/tiingo/index.ts
1869
+ var TiingoProvider = class {
1870
+ constructor(options) {
1871
+ this.options = options;
1872
+ this.http = new HttpClient("tiingo", {
1873
+ baseUrl: "https://api.tiingo.com",
1874
+ headers: {
1875
+ Authorization: `Token ${options.apiKey}`,
1876
+ "Content-Type": "application/json"
1877
+ },
1878
+ ...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {},
1879
+ ...options.retries !== void 0 ? { retries: options.retries } : {}
1880
+ });
1881
+ this.limiter = options.rateLimiter ?? new RateLimiter("tiingo", 50, 50 / 3600);
1882
+ }
1883
+ name = "tiingo";
1884
+ http;
1885
+ limiter;
1886
+ // ---------------------------------------------------------------------------
1887
+ // Quote (IEX real-time)
1888
+ // ---------------------------------------------------------------------------
1889
+ async quote(symbols, options) {
1890
+ return Promise.all(symbols.map((s) => this.fetchSingleQuote(s, options)));
1891
+ }
1892
+ async fetchSingleQuote(symbol, options) {
1893
+ this.limiter.consume();
1894
+ const s = normalise(symbol);
1895
+ const [iexData, metaData] = await Promise.allSettled([
1896
+ this.http.get(`/iex/${s}`),
1897
+ this.http.get(`/tiingo/daily/${s}`)
1898
+ ]);
1899
+ if (iexData.status === "rejected") {
1900
+ throw new ProviderError(`No quote data for symbol "${s}"`, this.name);
1901
+ }
1902
+ const iexRaw = iexData.value;
1903
+ const iex = Array.isArray(iexRaw) ? iexRaw[0] : null;
1904
+ if (!iex) {
1905
+ throw new ProviderError(`No quote data for symbol "${s}"`, this.name);
1906
+ }
1907
+ const meta = metaData.status === "fulfilled" && !("detail" in metaData.value) ? metaData.value : void 0;
1908
+ return transformQuote5(iex, meta, options?.raw ? { iex, meta } : void 0);
1909
+ }
1910
+ // ---------------------------------------------------------------------------
1911
+ // Historical (EOD daily)
1912
+ // ---------------------------------------------------------------------------
1913
+ async historical(symbol, options) {
1914
+ this.limiter.consume();
1915
+ const s = normalise(symbol);
1916
+ const params = {};
1917
+ if (options?.period1 !== void 0) {
1918
+ params["startDate"] = typeof options.period1 === "string" ? options.period1 : options.period1.toISOString().slice(0, 10);
1919
+ }
1920
+ if (options?.period2 !== void 0) {
1921
+ params["endDate"] = typeof options.period2 === "string" ? options.period2 : options.period2.toISOString().slice(0, 10);
1922
+ }
1923
+ const data = await this.http.get(
1924
+ `/tiingo/daily/${s}/prices`,
1925
+ { params }
1926
+ );
1927
+ if (!Array.isArray(data)) {
1928
+ throw new ProviderError(
1929
+ data.detail ?? `No historical data for symbol "${s}"`,
1930
+ this.name
1931
+ );
1932
+ }
1933
+ if (data.length === 0) {
1934
+ throw new ProviderError(`No historical data for symbol "${s}"`, this.name);
1935
+ }
1936
+ return data.map((bar) => transformHistoricalBar2(bar, options?.raw ? bar : void 0));
1937
+ }
1938
+ // ---------------------------------------------------------------------------
1939
+ // Search
1940
+ // ---------------------------------------------------------------------------
1941
+ async search(query, options) {
1942
+ this.limiter.consume();
1943
+ const limit = options?.limit ?? 10;
1944
+ const data = await this.http.get(
1945
+ "/tiingo/utilities/search",
1946
+ { params: { query, limit } }
1947
+ );
1948
+ if (!Array.isArray(data)) {
1949
+ return [];
1950
+ }
1951
+ return data.slice(0, limit).map((item) => transformSearch5(item, options?.raw ? item : void 0));
1952
+ }
1953
+ // ---------------------------------------------------------------------------
1954
+ // Company (metadata)
1955
+ // ---------------------------------------------------------------------------
1956
+ async company(symbol, options) {
1957
+ this.limiter.consume();
1958
+ const s = normalise(symbol);
1959
+ const data = await this.http.get(
1960
+ `/tiingo/daily/${s}`
1961
+ );
1962
+ if ("detail" in data || !("name" in data) || !data.name) {
1963
+ throw new ProviderError(
1964
+ ("detail" in data ? data.detail : void 0) ?? `No company data for symbol "${s}"`,
1965
+ this.name
1966
+ );
1967
+ }
1968
+ return transformCompany5(data, options?.raw ? data : void 0);
1969
+ }
1970
+ // ---------------------------------------------------------------------------
1971
+ // News
1972
+ // ---------------------------------------------------------------------------
1973
+ async news(symbol, options) {
1974
+ this.limiter.consume();
1975
+ const s = normalise(symbol);
1976
+ const limit = options?.limit ?? 10;
1977
+ const data = await this.http.get("/tiingo/news", {
1978
+ params: { tickers: s, limit }
1979
+ });
1980
+ if (!Array.isArray(data)) {
1981
+ throw new ProviderError(
1982
+ data.detail ?? `No news data for symbol "${s}"`,
1983
+ this.name
1984
+ );
1985
+ }
1986
+ return data.slice(0, limit).map((article) => transformNews3(article, options?.raw ? article : void 0));
1987
+ }
1988
+ };
1989
+
1588
1990
  // src/providers/twelve-data/transform.ts
1589
- var PROVIDER5 = "twelve-data";
1590
- function transformQuote5(data, raw) {
1991
+ var PROVIDER6 = "twelve-data";
1992
+ function transformQuote6(data, raw) {
1591
1993
  return {
1592
1994
  symbol: data.symbol,
1593
1995
  name: data.name,
@@ -1608,11 +2010,11 @@ function transformQuote5(data, raw) {
1608
2010
  currency: data.currency,
1609
2011
  exchange: data.exchange,
1610
2012
  timestamp: new Date(data.timestamp * 1e3),
1611
- provider: PROVIDER5,
2013
+ provider: PROVIDER6,
1612
2014
  ...raw !== void 0 ? { raw } : {}
1613
2015
  };
1614
2016
  }
1615
- function transformHistoricalBar2(bar, raw) {
2017
+ function transformHistoricalBar3(bar, raw) {
1616
2018
  return {
1617
2019
  date: new Date(bar.datetime),
1618
2020
  open: Number(bar.open),
@@ -1623,13 +2025,13 @@ function transformHistoricalBar2(bar, raw) {
1623
2025
  ...raw !== void 0 ? { raw } : {}
1624
2026
  };
1625
2027
  }
1626
- function transformSearch5(result, raw) {
2028
+ function transformSearch6(result, raw) {
1627
2029
  return {
1628
2030
  symbol: result.symbol,
1629
2031
  name: result.instrument_name,
1630
2032
  type: mapInstrumentType(result.instrument_type),
1631
2033
  exchange: result.exchange || void 0,
1632
- provider: PROVIDER5,
2034
+ provider: PROVIDER6,
1633
2035
  ...raw !== void 0 ? { raw } : {}
1634
2036
  };
1635
2037
  }
@@ -1645,7 +2047,7 @@ function transformProfile(data, raw) {
1645
2047
  website: data.website || void 0,
1646
2048
  ceo: data.CEO || void 0,
1647
2049
  exchange: data.exchange || void 0,
1648
- provider: PROVIDER5,
2050
+ provider: PROVIDER6,
1649
2051
  ...raw !== void 0 ? { raw } : {}
1650
2052
  };
1651
2053
  }
@@ -1696,7 +2098,7 @@ var TwelveDataProvider = class {
1696
2098
  data.code
1697
2099
  );
1698
2100
  }
1699
- return transformQuote5(data, options?.raw ? data : void 0);
2101
+ return transformQuote6(data, options?.raw ? data : void 0);
1700
2102
  }
1701
2103
  // ---------------------------------------------------------------------------
1702
2104
  // Historical (time series)
@@ -1728,7 +2130,7 @@ var TwelveDataProvider = class {
1728
2130
  if (!Array.isArray(data.values) || data.values.length === 0) {
1729
2131
  throw new ProviderError(`No historical data for symbol "${s}"`, this.name);
1730
2132
  }
1731
- return [...data.values].reverse().map((bar) => transformHistoricalBar2(bar, options?.raw ? bar : void 0));
2133
+ return [...data.values].reverse().map((bar) => transformHistoricalBar3(bar, options?.raw ? bar : void 0));
1732
2134
  }
1733
2135
  // ---------------------------------------------------------------------------
1734
2136
  // Search
@@ -1742,7 +2144,7 @@ var TwelveDataProvider = class {
1742
2144
  if (data.code !== void 0 || data.status === "error") {
1743
2145
  return [];
1744
2146
  }
1745
- return (data.data ?? []).slice(0, limit).map((r) => transformSearch5(r, options?.raw ? r : void 0));
2147
+ return (data.data ?? []).slice(0, limit).map((r) => transformSearch6(r, options?.raw ? r : void 0));
1746
2148
  }
1747
2149
  // ---------------------------------------------------------------------------
1748
2150
  // Company (profile)
@@ -1804,6 +2206,7 @@ Options:
1804
2206
  --polygon-key <key> Polygon.io API key
1805
2207
  --finnhub-key <key> Finnhub API key
1806
2208
  --td-key <key> Twelve Data API key
2209
+ --tiingo-key <key> Tiingo API key
1807
2210
  --json Output raw JSON
1808
2211
  --limit <n> Limit results (default: 10)
1809
2212
  --interval <i> Historical interval: 1m 5m 15m 30m 1h 1d 1wk 1mo (default: 1d)
@@ -1853,6 +2256,9 @@ function parseArgs(argv) {
1853
2256
  } else if (arg === "--td-key") {
1854
2257
  result.tdKey = args[++i];
1855
2258
  i++;
2259
+ } else if (arg === "--tiingo-key") {
2260
+ result.tiingoKey = args[++i];
2261
+ i++;
1856
2262
  } else if (arg === "--limit") {
1857
2263
  result.limit = Number(args[++i]) || 10;
1858
2264
  i++;
@@ -1889,6 +2295,7 @@ function buildFeed(args) {
1889
2295
  if (args.polygonKey) providers.push(new PolygonProvider({ apiKey: args.polygonKey }));
1890
2296
  if (args.finnhubKey) providers.push(new FinnhubProvider({ apiKey: args.finnhubKey }));
1891
2297
  if (args.tdKey) providers.push(new TwelveDataProvider({ apiKey: args.tdKey }));
2298
+ if (args.tiingoKey) providers.push(new TiingoProvider({ apiKey: args.tiingoKey }));
1892
2299
  return new MarketFeed({ providers });
1893
2300
  }
1894
2301
  function pad(s, n) {