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 +132 -0
- package/README.md +2 -2
- package/dist/cli.js +418 -11
- package/dist/cli.js.map +1 -1
- package/dist/{client-B8eMDynL.d.cts → client-ADDEqqP4.d.cts} +6 -2
- package/dist/{client-DFXMg2UE.d.ts → client-B_oIoAtz.d.ts} +6 -2
- package/dist/consensus.d.cts +2 -1
- package/dist/consensus.d.ts +2 -1
- package/dist/fundamentals-ChmjgT1d.d.cts +73 -0
- package/dist/fundamentals-ChmjgT1d.d.ts +73 -0
- package/dist/fundamentals.cjs +23 -0
- package/dist/fundamentals.cjs.map +1 -0
- package/dist/fundamentals.d.cts +49 -0
- package/dist/fundamentals.d.ts +49 -0
- package/dist/fundamentals.js +21 -0
- package/dist/fundamentals.js.map +1 -0
- package/dist/index.cjs +404 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -5
- package/dist/index.d.ts +43 -5
- package/dist/index.js +404 -2
- package/dist/index.js.map +1 -1
- package/dist/{provider-CC1CWzj-.d.ts → provider-CnWUOTf9.d.ts} +13 -0
- package/dist/{provider-C6qYyVGJ.d.cts → provider-DC4ydTdu.d.cts} +13 -0
- package/dist/stream.d.cts +3 -2
- package/dist/stream.d.ts +3 -2
- package/dist/ws.d.cts +2 -1
- package/dist/ws.d.ts +2 -1
- package/package.json +6 -1
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,
|
|
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
|
[](https://github.com/piyushgupta344/market-feed/actions/workflows/ci.yml)
|
|
7
7
|
[](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.
|
|
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
|
|
1590
|
-
function
|
|
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:
|
|
2013
|
+
provider: PROVIDER6,
|
|
1612
2014
|
...raw !== void 0 ? { raw } : {}
|
|
1613
2015
|
};
|
|
1614
2016
|
}
|
|
1615
|
-
function
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
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) =>
|
|
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) =>
|
|
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) {
|