market-feed 1.0.0 → 1.1.1

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.
Files changed (113) hide show
  1. package/CHANGELOG.md +280 -133
  2. package/README.md +519 -39
  3. package/dist/alerts.cjs +8 -4
  4. package/dist/alerts.cjs.map +1 -1
  5. package/dist/alerts.d.cts +3 -3
  6. package/dist/alerts.d.ts +3 -3
  7. package/dist/alerts.js +8 -4
  8. package/dist/alerts.js.map +1 -1
  9. package/dist/backtest.cjs +205 -2
  10. package/dist/backtest.cjs.map +1 -1
  11. package/dist/backtest.d.cts +92 -2
  12. package/dist/backtest.d.ts +92 -2
  13. package/dist/backtest.js +205 -3
  14. package/dist/backtest.js.map +1 -1
  15. package/dist/browser.cjs +3766 -0
  16. package/dist/browser.cjs.map +1 -0
  17. package/dist/browser.d.cts +85 -0
  18. package/dist/browser.d.ts +85 -0
  19. package/dist/browser.js +3735 -0
  20. package/dist/browser.js.map +1 -0
  21. package/dist/cache.cjs +186 -0
  22. package/dist/cache.cjs.map +1 -0
  23. package/dist/cache.d.cts +144 -0
  24. package/dist/cache.d.ts +144 -0
  25. package/dist/cache.js +181 -0
  26. package/dist/cache.js.map +1 -0
  27. package/dist/cli.js +469 -34
  28. package/dist/cli.js.map +1 -1
  29. package/dist/{client-ADDEqqP4.d.cts → client-BWZ-PieL.d.cts} +8 -46
  30. package/dist/{client-B_oIoAtz.d.ts → client-CtXeYQ1h.d.ts} +8 -46
  31. package/dist/consensus.d.cts +5 -3
  32. package/dist/consensus.d.ts +5 -3
  33. package/dist/fundamentals.cjs.map +1 -1
  34. package/dist/fundamentals.js.map +1 -1
  35. package/dist/{historical-BbCuwqyZ.d.cts → historical-C2E1zFA_.d.cts} +1 -1
  36. package/dist/{historical-BbCuwqyZ.d.ts → historical-C2E1zFA_.d.ts} +1 -1
  37. package/dist/index.cjs +569 -31
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +142 -20
  40. package/dist/index.d.ts +142 -20
  41. package/dist/index.js +568 -32
  42. package/dist/index.js.map +1 -1
  43. package/dist/indicators.cjs +6 -2
  44. package/dist/indicators.cjs.map +1 -1
  45. package/dist/indicators.d.cts +1 -1
  46. package/dist/indicators.d.ts +1 -1
  47. package/dist/indicators.js +6 -2
  48. package/dist/indicators.js.map +1 -1
  49. package/dist/macro.cjs +235 -0
  50. package/dist/macro.cjs.map +1 -0
  51. package/dist/macro.d.cts +126 -0
  52. package/dist/macro.d.ts +126 -0
  53. package/dist/macro.js +231 -0
  54. package/dist/macro.js.map +1 -0
  55. package/dist/memory-CG6fw-mp.d.ts +19 -0
  56. package/dist/memory-dGCjMfCu.d.cts +19 -0
  57. package/dist/options-CyRqRiFk.d.cts +59 -0
  58. package/dist/options-CyRqRiFk.d.ts +59 -0
  59. package/dist/options.cjs +13 -0
  60. package/dist/options.cjs.map +1 -0
  61. package/dist/options.d.cts +45 -0
  62. package/dist/options.d.ts +45 -0
  63. package/dist/options.js +11 -0
  64. package/dist/options.js.map +1 -0
  65. package/dist/orderbook-B6vFsskE.d.ts +115 -0
  66. package/dist/orderbook-Cxp95hhW.d.cts +115 -0
  67. package/dist/portfolio.cjs.map +1 -1
  68. package/dist/portfolio.d.cts +1 -1
  69. package/dist/portfolio.d.ts +1 -1
  70. package/dist/portfolio.js.map +1 -1
  71. package/dist/provider-CitIZBZX.d.ts +73 -0
  72. package/dist/provider-DQGRULh_.d.cts +73 -0
  73. package/dist/{quote-Cfh_7Cgg.d.cts → quote-BpPnhI4K.d.cts} +1 -1
  74. package/dist/{quote-Cfh_7Cgg.d.ts → quote-BpPnhI4K.d.ts} +1 -1
  75. package/dist/react.cjs +1021 -4
  76. package/dist/react.cjs.map +1 -1
  77. package/dist/react.d.cts +54 -9
  78. package/dist/react.d.ts +54 -9
  79. package/dist/react.js +1020 -5
  80. package/dist/react.js.map +1 -1
  81. package/dist/screener.cjs +6 -0
  82. package/dist/screener.cjs.map +1 -1
  83. package/dist/screener.d.cts +7 -1
  84. package/dist/screener.d.ts +7 -1
  85. package/dist/screener.js +6 -0
  86. package/dist/screener.js.map +1 -1
  87. package/dist/{provider-CnWUOTf9.d.ts → splits-Y1u7sJgO.d.cts} +19 -67
  88. package/dist/{provider-DC4ydTdu.d.cts → splits-Y1u7sJgO.d.ts} +19 -67
  89. package/dist/stream.cjs +30 -0
  90. package/dist/stream.cjs.map +1 -1
  91. package/dist/stream.d.cts +9 -6
  92. package/dist/stream.d.ts +9 -6
  93. package/dist/stream.js +30 -0
  94. package/dist/stream.js.map +1 -1
  95. package/dist/trpc.cjs +97 -0
  96. package/dist/trpc.cjs.map +1 -0
  97. package/dist/trpc.d.cts +175 -0
  98. package/dist/trpc.d.ts +175 -0
  99. package/dist/trpc.js +94 -0
  100. package/dist/trpc.js.map +1 -0
  101. package/dist/{types-hzOO9Cst.d.cts → types-BpKjqHAF.d.cts} +1 -1
  102. package/dist/{types-C8OPQ-Yj.d.cts → types-BziYJD5M.d.cts} +24 -3
  103. package/dist/{types-D90J5xwU.d.ts → types-C9q2uBkf.d.ts} +1 -1
  104. package/dist/{types-B5oHwrPy.d.ts → types-DXjo6Y4W.d.ts} +24 -3
  105. package/dist/types-Dz0yKXpx.d.cts +43 -0
  106. package/dist/types-Dz0yKXpx.d.ts +43 -0
  107. package/dist/ws.cjs +606 -44
  108. package/dist/ws.cjs.map +1 -1
  109. package/dist/ws.d.cts +11 -66
  110. package/dist/ws.d.ts +11 -66
  111. package/dist/ws.js +606 -45
  112. package/dist/ws.js.map +1 -1
  113. package/package.json +44 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,86 @@
1
1
  # market-feed Changelog
2
2
 
3
+ ## 1.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Maintenance: update dev tooling to vitest 4 / vite 6, add @types/node, patch devDep vulnerabilities, fix docs logo and Node 20+ references
8
+
9
+ ## 1.1.0 — 2026-03-12
10
+
11
+ ### New modules
12
+
13
+ **`market-feed/options`** — Options chains with full Greeks from Polygon.io.
14
+
15
+ ```ts
16
+ import { getOptionChain } from "market-feed/options";
17
+ import { PolygonProvider } from "market-feed";
18
+
19
+ const polygon = new PolygonProvider({ apiKey: process.env.POLYGON_KEY! });
20
+ const chain = await getOptionChain(polygon, "AAPL", {
21
+ expiry: "2024-07-19",
22
+ type: "call",
23
+ strikeLow: 170,
24
+ strikeHigh: 210,
25
+ });
26
+
27
+ for (const c of chain.calls) {
28
+ console.log(
29
+ `Strike $${c.strike} IV ${(c.impliedVolatility! * 100).toFixed(
30
+ 1
31
+ )}% Δ ${c.delta?.toFixed(3)}`
32
+ );
33
+ }
34
+ ```
35
+
36
+ Fields: `ticker`, `type`, `strike`, `expiry`, `style`, `sharesPerContract`, `bid`, `ask`, `midpoint`, `lastPrice`, `volume`, `openInterest`, `impliedVolatility`, `delta`, `gamma`, `theta`, `vega`, OHLCV.
37
+
38
+ ---
39
+
40
+ **`market-feed/macro`** — Macroeconomic time-series data from the [FRED API](https://fred.stlouisfed.org/).
41
+
42
+ ```ts
43
+ import { FredProvider, getIndicator, INDICATORS } from "market-feed/macro";
44
+
45
+ const fred = new FredProvider({ apiKey: process.env.FRED_KEY! });
46
+ const cpi = await getIndicator(fred, INDICATORS.CPI, { limit: 12 });
47
+
48
+ for (const obs of cpi.observations) {
49
+ console.log(`${obs.date.toISOString().slice(0, 7)}: ${obs.value}`);
50
+ }
51
+ ```
52
+
53
+ 15 named constants in `INDICATORS`: `CPI`, `FED_FUNDS`, `UNEMPLOYMENT`, `GDP`, `M2`, `T10Y`, `T2Y`, `MORTGAGE_30Y`, `PCE`, `PPI`, `INDUSTRIAL_PRODUCTION`, `RETAIL_SALES`, `OIL_WTI`, `HOUSING_STARTS`, `CONSUMER_SENTIMENT`. Any FRED series ID string also accepted.
54
+
55
+ ### Provider enhancements
56
+
57
+ **Polygon.io** — added `incomeStatements()`, `balanceSheets()`, `cashFlows()` via `/vX/reference/financials`.
58
+
59
+ **Tiingo** — added `incomeStatements()`, `balanceSheets()`, `cashFlows()` via `/tiingo/fundamentals/{ticker}/statements`.
60
+
61
+ ### Screener additions
62
+
63
+ Two new criteria in `market-feed/screener`:
64
+
65
+ | Criterion | Passes when |
66
+ | --------------------- | ------------------------------------------------------------------------------ |
67
+ | `volume_vs_avg_above` | `quote.volume > quote.avgVolume * value` — e.g. `value: 2` = 2× average volume |
68
+ | `volume_vs_avg_below` | `quote.volume < quote.avgVolume * value` |
69
+
70
+ Both pass-through when `avgVolume` is `undefined` (consistent with 52-week criteria).
71
+
72
+ ### Client
73
+
74
+ - `MarketFeed.optionChain(symbol, options?)` — delegates to first provider supporting `optionChain`. Cache TTL: 60s.
75
+ - `CacheMethod` union extended with `"optionChain"`.
76
+
77
+ ### Package
78
+
79
+ - `market-feed/options` and `market-feed/macro` added to `exports` and `keywords`.
80
+ - Total subpath modules: **14** (`market-feed`, `/calendar`, `/stream`, `/ws`, `/consensus`, `/indicators`, `/portfolio`, `/backtest`, `/alerts`, `/fundamentals`, `/screener`, `/options`, `/macro`, `/react`).
81
+
82
+ ---
83
+
3
84
  ## 1.0.0 — 2026-03-12
4
85
 
5
86
  ### New module
@@ -17,24 +98,40 @@ function StockPrice({ symbol }: { symbol: string }) {
17
98
  const { data, loading, error } = useQuote(feed, symbol);
18
99
  if (loading) return <span>…</span>;
19
100
  if (error) return <span>Error: {error.message}</span>;
20
- return <span>{symbol}: ${data?.price.toFixed(2)}</span>;
101
+ return (
102
+ <span>
103
+ {symbol}: ${data?.price.toFixed(2)}
104
+ </span>
105
+ );
21
106
  }
22
107
 
23
108
  // Subscribe to a live stream
24
109
  function LiveFeed() {
25
110
  const { event } = useStream(feed, ["AAPL", "MSFT", "GOOGL"]);
26
111
  if (!event || event.type !== "quote") return null;
27
- return <p>{event.symbol}: ${event.quote.price}</p>;
112
+ return (
113
+ <p>
114
+ {event.symbol}: ${event.quote.price}
115
+ </p>
116
+ );
28
117
  }
29
118
 
30
119
  // Collect price alerts
31
120
  function AlertLog() {
32
121
  const { events, clearEvents } = useAlerts(feed, [
33
- { symbol: "AAPL", condition: { type: "price_above", threshold: 200 }, once: false },
122
+ {
123
+ symbol: "AAPL",
124
+ condition: { type: "price_above", threshold: 200 },
125
+ once: false,
126
+ },
34
127
  ]);
35
128
  return (
36
129
  <ul>
37
- {events.map((e, i) => <li key={i}>{e.alert.symbol} triggered @ ${e.quote.price}</li>)}
130
+ {events.map((e, i) => (
131
+ <li key={i}>
132
+ {e.alert.symbol} triggered @ ${e.quote.price}
133
+ </li>
134
+ ))}
38
135
  <button onClick={clearEvents}>Clear</button>
39
136
  </ul>
40
137
  );
@@ -45,10 +142,10 @@ function AlertLog() {
45
142
 
46
143
  **`useQuote(source, symbol, options?)`**
47
144
 
48
- | Option | Default | Description |
49
- |--------|---------|-------------|
50
- | `intervalMs` | `5000` | Poll interval in milliseconds |
51
- | `enabled` | `true` | Set to `false` to suspend polling |
145
+ | Option | Default | Description |
146
+ | ------------ | ------- | --------------------------------- |
147
+ | `intervalMs` | `5000` | Poll interval in milliseconds |
148
+ | `enabled` | `true` | Set to `false` to suspend polling |
52
149
 
53
150
  Returns `{ data: Quote \| null, loading: boolean, error: Error \| null, refetch() }`.
54
151
 
@@ -101,25 +198,25 @@ console.log(results.map((r) => `${r.symbol} @ ${r.quote.price}`));
101
198
 
102
199
  #### Criterion types
103
200
 
104
- | Type | Description |
105
- |------|-------------|
106
- | `price_above` / `price_below` | Filter by current price |
107
- | `change_pct_above` / `change_pct_below` | Filter by daily % change |
108
- | `volume_above` / `volume_below` | Filter by trading volume |
109
- | `market_cap_above` / `market_cap_below` | Filter by market cap |
110
- | `52w_high_pct_below` | Price is within N% of the 52-week high |
111
- | `52w_low_pct_above` | Price is at least N% above the 52-week low |
112
- | `custom` | Arbitrary predicate: `{ type: "custom", fn: (quote) => boolean }` |
201
+ | Type | Description |
202
+ | --------------------------------------- | ----------------------------------------------------------------- |
203
+ | `price_above` / `price_below` | Filter by current price |
204
+ | `change_pct_above` / `change_pct_below` | Filter by daily % change |
205
+ | `volume_above` / `volume_below` | Filter by trading volume |
206
+ | `market_cap_above` / `market_cap_below` | Filter by market cap |
207
+ | `52w_high_pct_below` | Price is within N% of the 52-week high |
208
+ | `52w_low_pct_above` | Price is at least N% above the 52-week low |
209
+ | `custom` | Arbitrary predicate: `{ type: "custom", fn: (quote) => boolean }` |
113
210
 
114
211
  All criteria are evaluated with **AND logic** — a symbol must pass every criterion to be included.
115
212
 
116
213
  #### Options
117
214
 
118
- | Option | Description |
119
- |--------|-------------|
120
- | `criteria` | Array of `ScreenerCriterion` (required) |
215
+ | Option | Description |
216
+ | ----------- | ------------------------------------------------------- |
217
+ | `criteria` | Array of `ScreenerCriterion` (required) |
121
218
  | `batchSize` | Max symbols per quote fetch call (default: all at once) |
122
- | `limit` | Max number of results to return |
219
+ | `limit` | Max number of results to return |
123
220
 
124
221
  #### Result shape
125
222
 
@@ -146,45 +243,52 @@ import { getFundamentals } from "market-feed/fundamentals";
146
243
  import { MarketFeed } from "market-feed";
147
244
 
148
245
  const feed = new MarketFeed();
149
- const { incomeStatements, balanceSheets, cashFlows } = await getFundamentals(feed, "AAPL");
246
+ const { incomeStatements, balanceSheets, cashFlows } = await getFundamentals(
247
+ feed,
248
+ "AAPL"
249
+ );
150
250
 
151
251
  console.log(`Revenue: $${(incomeStatements[0]!.revenue! / 1e9).toFixed(1)}B`);
152
- console.log(`Total assets: $${(balanceSheets[0]!.totalAssets! / 1e9).toFixed(1)}B`);
153
- console.log(`Free cash flow: $${(cashFlows[0]!.freeCashFlow! / 1e9).toFixed(1)}B`);
252
+ console.log(
253
+ `Total assets: $${(balanceSheets[0]!.totalAssets! / 1e9).toFixed(1)}B`
254
+ );
255
+ console.log(
256
+ `Free cash flow: $${(cashFlows[0]!.freeCashFlow! / 1e9).toFixed(1)}B`
257
+ );
154
258
  ```
155
259
 
156
260
  `getFundamentals()` fetches all three statements in parallel via `Promise.allSettled` — a failure on one statement still returns the others.
157
261
 
158
262
  #### `IncomeStatement`
159
263
 
160
- | Field | Description |
161
- |-------|-------------|
162
- | `revenue` | Total revenue |
163
- | `grossProfit` | Revenue - cost of revenue |
164
- | `operatingIncome` | EBIT (operating) |
165
- | `netIncome` | Bottom-line net income |
166
- | `ebitda` | EBITDA when available |
167
- | `dilutedEps` | Diluted EPS |
264
+ | Field | Description |
265
+ | ----------------- | ------------------------- |
266
+ | `revenue` | Total revenue |
267
+ | `grossProfit` | Revenue - cost of revenue |
268
+ | `operatingIncome` | EBIT (operating) |
269
+ | `netIncome` | Bottom-line net income |
270
+ | `ebitda` | EBITDA when available |
271
+ | `dilutedEps` | Diluted EPS |
168
272
 
169
273
  #### `BalanceSheet`
170
274
 
171
- | Field | Description |
172
- |-------|-------------|
173
- | `totalAssets` | Total assets |
174
- | `totalLiabilities` | Total liabilities |
175
- | `totalStockholdersEquity` | Book value of equity |
176
- | `cashAndCashEquivalents` | Cash + cash equivalents |
177
- | `totalDebt` | Short + long-term debt |
275
+ | Field | Description |
276
+ | ------------------------- | ----------------------- |
277
+ | `totalAssets` | Total assets |
278
+ | `totalLiabilities` | Total liabilities |
279
+ | `totalStockholdersEquity` | Book value of equity |
280
+ | `cashAndCashEquivalents` | Cash + cash equivalents |
281
+ | `totalDebt` | Short + long-term debt |
178
282
 
179
283
  #### `CashFlowStatement`
180
284
 
181
- | Field | Description |
182
- |-------|-------------|
183
- | `operatingCashFlow` | Cash from operations |
184
- | `capitalExpenditures` | CapEx (negative = outflow) |
185
- | `freeCashFlow` | operatingCashFlow + capitalExpenditures |
186
- | `investingCashFlow` | Cash from investing |
187
- | `financingCashFlow` | Cash from financing |
285
+ | Field | Description |
286
+ | --------------------- | --------------------------------------- |
287
+ | `operatingCashFlow` | Cash from operations |
288
+ | `capitalExpenditures` | CapEx (negative = outflow) |
289
+ | `freeCashFlow` | operatingCashFlow + capitalExpenditures |
290
+ | `investingCashFlow` | Cash from investing |
291
+ | `financingCashFlow` | Cash from financing |
188
292
 
189
293
  All three types include `symbol`, `date` (period end), `periodType` (`"annual"` | `"quarterly"`), and `provider`.
190
294
 
@@ -193,20 +297,20 @@ All three types include `symbol`, `date` (period end), `periodType` (`"annual"`
193
297
  ```ts
194
298
  const feed = new MarketFeed();
195
299
 
196
- const income = await feed.incomeStatements("AAPL", { limit: 4 });
300
+ const income = await feed.incomeStatements("AAPL", { limit: 4 });
197
301
  const balance = await feed.balanceSheets("AAPL", { quarterly: true });
198
- const cash = await feed.cashFlows("AAPL");
302
+ const cash = await feed.cashFlows("AAPL");
199
303
  ```
200
304
 
201
305
  `FundamentalsOptions`: `quarterly?` (default `false`), `limit?` (default `4`), `raw?`.
202
306
 
203
307
  ### Provider support
204
308
 
205
- | Method | `YahooProvider` | others |
206
- |--------|-----------------|--------|
207
- | `incomeStatements` | ✓ `incomeStatementHistory` / `incomeStatementHistoryQuarterly` | — |
208
- | `balanceSheets` | ✓ `balanceSheetHistory` / `balanceSheetHistoryQuarterly` | — |
209
- | `cashFlows` | ✓ `cashflowStatementHistory` / `cashflowStatementHistoryQuarterly` | — |
309
+ | Method | `YahooProvider` | others |
310
+ | ------------------ | ------------------------------------------------------------------ | ------ |
311
+ | `incomeStatements` | ✓ `incomeStatementHistory` / `incomeStatementHistoryQuarterly` | — |
312
+ | `balanceSheets` | ✓ `balanceSheetHistory` / `balanceSheetHistoryQuarterly` | — |
313
+ | `cashFlows` | ✓ `cashflowStatementHistory` / `cashflowStatementHistoryQuarterly` | — |
210
314
 
211
315
  ### Breaking changes
212
316
 
@@ -234,21 +338,21 @@ const feed = new MarketFeed([
234
338
  new TiingoProvider({ apiKey: process.env.TIINGO_KEY! }),
235
339
  ]);
236
340
 
237
- const quote = await feed.quote(["AAPL"]);
238
- const bars = await feed.historical("AAPL", { period1: "2024-01-01" });
341
+ const quote = await feed.quote(["AAPL"]);
342
+ const bars = await feed.historical("AAPL", { period1: "2024-01-01" });
239
343
  const results = await feed.search("apple");
240
344
  const profile = await feed.company("AAPL");
241
- const news = await feed.news("AAPL", { limit: 5 });
345
+ const news = await feed.news("AAPL", { limit: 5 });
242
346
  ```
243
347
 
244
348
  Supports: `quote`, `historical`, `search`, `company`, `news`.
245
349
 
246
- | Feature | Detail |
247
- |---------|--------|
350
+ | Feature | Detail |
351
+ | ---------------- | ------------------------------------ |
248
352
  | Real-time quotes | IEX endpoint — US equities, intraday |
249
- | Historical | EOD daily bars, includes `adjClose` |
250
- | Rate limit | ~50 calls/hour (free plan) |
251
- | Auth | `Authorization: Token KEY` header |
353
+ | Historical | EOD daily bars, includes `adjClose` |
354
+ | Rate limit | ~50 calls/hour (free plan) |
355
+ | Auth | `Authorization: Token KEY` header |
252
356
 
253
357
  Free plan sign-up: https://www.tiingo.com
254
358
 
@@ -280,8 +384,8 @@ const feed = new MarketFeed([
280
384
  new TwelveDataProvider({ apiKey: process.env.TWELVE_DATA_KEY! }),
281
385
  ]);
282
386
 
283
- const quote = await feed.quote(["AAPL", "BTC/USD", "EUR/USD"]);
284
- const bars = await feed.historical("AAPL", { interval: "1wk" });
387
+ const quote = await feed.quote(["AAPL", "BTC/USD", "EUR/USD"]);
388
+ const bars = await feed.historical("AAPL", { interval: "1wk" });
285
389
  const results = await feed.search("apple");
286
390
  const profile = await feed.company("AAPL");
287
391
  ```
@@ -289,6 +393,7 @@ const profile = await feed.company("AAPL");
289
393
  Supports: `quote`, `historical`, `search`, `company`.
290
394
 
291
395
  Strong coverage for global equities, forex, and crypto pairs. Symbol normalisation handles all common formats:
396
+
292
397
  - US stocks: `AAPL` (unchanged)
293
398
  - Crypto: `BTC-USD` / `BTC/USD` / `X:BTCUSD` → `BTC/USD`
294
399
  - Forex: `EURUSD=X` / `C:EURUSD` → `EUR/USD`
@@ -298,15 +403,15 @@ Free plan sign-up: https://twelvedata.com
298
403
  #### Interval mapping
299
404
 
300
405
  | market-feed | Twelve Data |
301
- |-------------|-------------|
302
- | `1m` | `1min` |
303
- | `5m` | `5min` |
304
- | `15m` | `15min` |
305
- | `30m` | `30min` |
306
- | `1h` | `1h` |
307
- | `1d` | `1day` |
308
- | `1wk` | `1week` |
309
- | `1mo` | `1month` |
406
+ | ----------- | ----------- |
407
+ | `1m` | `1min` |
408
+ | `5m` | `5min` |
409
+ | `15m` | `15min` |
410
+ | `30m` | `30min` |
411
+ | `1h` | `1h` |
412
+ | `1d` | `1day` |
413
+ | `1wk` | `1week` |
414
+ | `1mo` | `1month` |
310
415
 
311
416
  ### New utility
312
417
 
@@ -358,8 +463,10 @@ None.
358
463
  import { backtest } from "market-feed/backtest";
359
464
  import type { EntrySignal, ExitSignal } from "market-feed/backtest";
360
465
 
361
- const entry: EntrySignal = (bars, i) => i > 0 && bars[i]!.close > bars[i - 1]!.close;
362
- const exit: ExitSignal = (bars, i) => i > 0 && bars[i]!.close < bars[i - 1]!.close;
466
+ const entry: EntrySignal = (bars, i) =>
467
+ i > 0 && bars[i]!.close > bars[i - 1]!.close;
468
+ const exit: ExitSignal = (bars, i) =>
469
+ i > 0 && bars[i]!.close < bars[i - 1]!.close;
363
470
 
364
471
  const result = backtest("AAPL", bars, entry, exit, { initialCapital: 10_000 });
365
472
  console.log(`Total return: ${(result.totalReturn * 100).toFixed(2)}%`);
@@ -367,16 +474,16 @@ console.log(`Sharpe ratio: ${result.sharpeRatio.toFixed(2)}`);
367
474
  console.log(`Max drawdown: ${(result.maxDrawdown * 100).toFixed(2)}%`);
368
475
  ```
369
476
 
370
- | Field | Description |
371
- |-------|-------------|
372
- | `totalReturn` | Fraction, e.g. 0.25 = 25% |
373
- | `annualizedReturn` | CAGR as a fraction |
374
- | `sharpeRatio` | Annualised Sharpe (risk-free rate = 0) |
375
- | `maxDrawdown` | Peak-to-trough as a positive fraction |
376
- | `winRate` | Fraction of profitable trades |
377
- | `profitFactor` | Gross profit / gross loss (`Infinity` when no losses) |
378
- | `totalTrades` | Number of completed round-trip trades |
379
- | `trades` | Full `BacktestTrade[]` ledger |
477
+ | Field | Description |
478
+ | ------------------ | ----------------------------------------------------- |
479
+ | `totalReturn` | Fraction, e.g. 0.25 = 25% |
480
+ | `annualizedReturn` | CAGR as a fraction |
481
+ | `sharpeRatio` | Annualised Sharpe (risk-free rate = 0) |
482
+ | `maxDrawdown` | Peak-to-trough as a positive fraction |
483
+ | `winRate` | Fraction of profitable trades |
484
+ | `profitFactor` | Gross profit / gross loss (`Infinity` when no losses) |
485
+ | `totalTrades` | Number of completed round-trip trades |
486
+ | `trades` | Full `BacktestTrade[]` ledger |
380
487
 
381
488
  **`market-feed/alerts`** — Poll a feed and yield `AlertEvent` when conditions are met.
382
489
 
@@ -387,21 +494,33 @@ import { MarketFeed } from "market-feed";
387
494
  const feed = new MarketFeed();
388
495
  const controller = new AbortController();
389
496
 
390
- for await (const event of watchAlerts(feed, [
391
- { symbol: "AAPL", condition: { type: "price_above", threshold: 200 }, once: true },
392
- { symbol: "TSLA", condition: { type: "change_pct_below", threshold: -5 }, debounceMs: 60_000 },
393
- ], { signal: controller.signal })) {
497
+ for await (const event of watchAlerts(
498
+ feed,
499
+ [
500
+ {
501
+ symbol: "AAPL",
502
+ condition: { type: "price_above", threshold: 200 },
503
+ once: true,
504
+ },
505
+ {
506
+ symbol: "TSLA",
507
+ condition: { type: "change_pct_below", threshold: -5 },
508
+ debounceMs: 60_000,
509
+ },
510
+ ],
511
+ { signal: controller.signal }
512
+ )) {
394
513
  console.log(`${event.alert.symbol} triggered: $${event.quote.price}`);
395
514
  }
396
515
  ```
397
516
 
398
- | Condition type | Description |
399
- |----------------|-------------|
400
- | `price_above` | `quote.price > threshold` |
401
- | `price_below` | `quote.price < threshold` |
402
- | `change_pct_above` | Daily `%` change exceeds threshold |
517
+ | Condition type | Description |
518
+ | ------------------ | -------------------------------------- |
519
+ | `price_above` | `quote.price > threshold` |
520
+ | `price_below` | `quote.price < threshold` |
521
+ | `change_pct_above` | Daily `%` change exceeds threshold |
403
522
  | `change_pct_below` | Daily `%` change falls below threshold |
404
- | `volume_above` | `quote.volume > threshold` |
523
+ | `volume_above` | `quote.volume > threshold` |
405
524
 
406
525
  `AlertConfig` options: `once` (fire at most once), `debounceMs` (suppress re-fires within window).
407
526
 
@@ -412,27 +531,33 @@ Three new methods on `MarketFeed` (and on individual providers):
412
531
  ```ts
413
532
  const feed = new MarketFeed([new PolygonProvider({ apiKey: "..." })]);
414
533
 
415
- const earnings = await feed.earnings("AAPL", { limit: 8 });
534
+ const earnings = await feed.earnings("AAPL", { limit: 8 });
416
535
  const dividends = await feed.dividends("AAPL");
417
- const splits = await feed.splits("AAPL");
536
+ const splits = await feed.splits("AAPL");
418
537
  ```
419
538
 
420
539
  #### Provider support
421
540
 
422
- | Method | `YahooProvider` | `PolygonProvider` | `FinnhubProvider` | `AlphaVantageProvider` |
423
- |--------|-----------------|-------------------|-------------------|------------------------|
424
- | `earnings` | ✓ quoteSummary `earningsHistory` | — | ✓ `/stock/earnings` | — |
425
- | `dividends` | ✓ chart `events=div` | ✓ `/v3/reference/dividends` | — | — |
426
- | `splits` | ✓ chart `events=split` | ✓ `/v3/reference/splits` | — | — |
541
+ | Method | `YahooProvider` | `PolygonProvider` | `FinnhubProvider` | `AlphaVantageProvider` |
542
+ | ----------- | -------------------------------- | --------------------------- | ------------------- | ---------------------- |
543
+ | `earnings` | ✓ quoteSummary `earningsHistory` | — | ✓ `/stock/earnings` | — |
544
+ | `dividends` | ✓ chart `events=div` | ✓ `/v3/reference/dividends` | — | — |
545
+ | `splits` | ✓ chart `events=split` | ✓ `/v3/reference/splits` | — | — |
427
546
 
428
547
  #### `EarningsEvent`
429
548
 
430
549
  ```ts
431
550
  interface EarningsEvent {
432
- symbol: string; date: Date; period?: string;
433
- epsActual?: number; epsEstimate?: number; epsSurprisePct?: number;
434
- revenueActual?: number; revenueEstimate?: number;
435
- provider: string; raw?: unknown;
551
+ symbol: string;
552
+ date: Date;
553
+ period?: string;
554
+ epsActual?: number;
555
+ epsEstimate?: number;
556
+ epsSurprisePct?: number;
557
+ revenueActual?: number;
558
+ revenueEstimate?: number;
559
+ provider: string;
560
+ raw?: unknown;
436
561
  }
437
562
  ```
438
563
 
@@ -440,10 +565,15 @@ interface EarningsEvent {
440
565
 
441
566
  ```ts
442
567
  interface DividendEvent {
443
- symbol: string; exDate: Date; payDate?: Date; declaredDate?: Date;
444
- amount: number; currency: string;
568
+ symbol: string;
569
+ exDate: Date;
570
+ payDate?: Date;
571
+ declaredDate?: Date;
572
+ amount: number;
573
+ currency: string;
445
574
  frequency?: "annual" | "semi-annual" | "quarterly" | "monthly" | "irregular";
446
- provider: string; raw?: unknown;
575
+ provider: string;
576
+ raw?: unknown;
447
577
  }
448
578
  ```
449
579
 
@@ -451,10 +581,12 @@ interface DividendEvent {
451
581
 
452
582
  ```ts
453
583
  interface SplitEvent {
454
- symbol: string; date: Date;
455
- ratio: number; // 4-for-1 forward split → 4; 1-for-10 reverse → 0.1
584
+ symbol: string;
585
+ date: Date;
586
+ ratio: number; // 4-for-1 forward split → 4; 1-for-10 reverse → 0.1
456
587
  description?: string;
457
- provider: string; raw?: unknown;
588
+ provider: string;
589
+ raw?: unknown;
458
590
  }
459
591
  ```
460
592
 
@@ -487,10 +619,14 @@ import { FinnhubProvider } from "market-feed";
487
619
  const provider = new FinnhubProvider({ apiKey: process.env.FINNHUB_KEY! });
488
620
  const controller = new AbortController();
489
621
 
490
- for await (const event of connect(provider, ["AAPL", "MSFT"], { signal: controller.signal })) {
622
+ for await (const event of connect(provider, ["AAPL", "MSFT"], {
623
+ signal: controller.signal,
624
+ })) {
491
625
  switch (event.type) {
492
626
  case "trade":
493
- console.log(`${event.trade.symbol}: $${event.trade.price} × ${event.trade.size}`);
627
+ console.log(
628
+ `${event.trade.symbol}: $${event.trade.price} × ${event.trade.size}`
629
+ );
494
630
  break;
495
631
  case "connected":
496
632
  console.log(`Connected to ${event.provider}`);
@@ -507,23 +643,23 @@ for await (const event of connect(provider, ["AAPL", "MSFT"], { signal: controll
507
643
 
508
644
  #### Provider support
509
645
 
510
- | Provider | WebSocket | Notes |
511
- |----------|-----------|-------|
512
- | **PolygonProvider** | Native WS | `wss://socket.polygon.io/stocks` — auth via JSON handshake, subscribes to `T.*` trade channel |
513
- | **FinnhubProvider** | Native WS | `wss://ws.finnhub.io?token=KEY` — per-symbol subscribe, batched trade messages |
514
- | **YahooProvider** | Polling fallback | Polls `provider.quote()` every 5 s; emits `WsTrade` from quote data |
515
- | **AlphaVantageProvider** | Polling fallback | Same as Yahoo |
646
+ | Provider | WebSocket | Notes |
647
+ | ------------------------ | ---------------- | --------------------------------------------------------------------------------------------- |
648
+ | **PolygonProvider** | Native WS | `wss://socket.polygon.io/stocks` — auth via JSON handshake, subscribes to `T.*` trade channel |
649
+ | **FinnhubProvider** | Native WS | `wss://ws.finnhub.io?token=KEY` — per-symbol subscribe, batched trade messages |
650
+ | **YahooProvider** | Polling fallback | Polls `provider.quote()` every 5 s; emits `WsTrade` from quote data |
651
+ | **AlphaVantageProvider** | Polling fallback | Same as Yahoo |
516
652
 
517
653
  The `connect()` function detects provider capability automatically — no configuration required.
518
654
 
519
655
  #### `WsEvent` union
520
656
 
521
- | `type` | Payload | When |
522
- |--------|---------|------|
523
- | `"connected"` | `provider: string` | WS opened (and after each reconnect) |
524
- | `"trade"` | `trade: WsTrade` | Each trade tick |
525
- | `"disconnected"` | `provider`, `reconnecting`, `attempt` | WS closed unexpectedly |
526
- | `"error"` | `error`, `recoverable` | Protocol or network error |
657
+ | `type` | Payload | When |
658
+ | ---------------- | ------------------------------------- | ------------------------------------ |
659
+ | `"connected"` | `provider: string` | WS opened (and after each reconnect) |
660
+ | `"trade"` | `trade: WsTrade` | Each trade tick |
661
+ | `"disconnected"` | `provider`, `reconnecting`, `attempt` | WS closed unexpectedly |
662
+ | `"error"` | `error`, `recoverable` | Protocol or network error |
527
663
 
528
664
  #### `WsTrade`
529
665
 
@@ -531,7 +667,7 @@ The `connect()` function detects provider capability automatically — no config
531
667
  interface WsTrade {
532
668
  symbol: string;
533
669
  price: number;
534
- size: number; // shares / units
670
+ size: number; // shares / units
535
671
  timestamp: Date;
536
672
  conditions?: number[]; // provider-specific condition codes
537
673
  }
@@ -539,12 +675,12 @@ interface WsTrade {
539
675
 
540
676
  #### `WsOptions`
541
677
 
542
- | Option | Type | Default | Description |
543
- |--------|------|---------|-------------|
544
- | `wsImpl` | `typeof globalThis.WebSocket` | `globalThis.WebSocket` | Custom WS constructor for Node 18–20 |
545
- | `maxReconnectAttempts` | `number` | `10` | Reconnect attempts before closing |
546
- | `reconnectDelayMs` | `number` | `1000` | Base delay (doubles per attempt, max 30 s) |
547
- | `signal` | `AbortSignal` | — | Stop the stream |
678
+ | Option | Type | Default | Description |
679
+ | ---------------------- | ----------------------------- | ---------------------- | ------------------------------------------ |
680
+ | `wsImpl` | `typeof globalThis.WebSocket` | `globalThis.WebSocket` | Custom WS constructor for Node 18–20 |
681
+ | `maxReconnectAttempts` | `number` | `10` | Reconnect attempts before closing |
682
+ | `reconnectDelayMs` | `number` | `1000` | Base delay (doubles per attempt, max 30 s) |
683
+ | `signal` | `AbortSignal` | — | Stop the stream |
548
684
 
549
685
  #### Node 18–20 compatibility
550
686
 
@@ -552,7 +688,9 @@ Node 21+ exposes `WebSocket` globally. For Node 18/20, install the `ws` package
552
688
 
553
689
  ```ts
554
690
  import WebSocket from "ws";
555
- connect(provider, ["AAPL"], { wsImpl: WebSocket as unknown as typeof globalThis.WebSocket })
691
+ connect(provider, ["AAPL"], {
692
+ wsImpl: WebSocket as unknown as typeof globalThis.WebSocket,
693
+ });
556
694
  ```
557
695
 
558
696
  ### Other changes
@@ -573,6 +711,7 @@ None. All v0.3.0 imports continue to work unchanged.
573
711
  ### New provider
574
712
 
575
713
  **`FinnhubProvider`** — Finnhub.io (free tier: real-time US stock data, 60 calls/minute).
714
+
576
715
  - `quote`, `historical` (candles), `search`, `company`, `news`
577
716
  - API key required — get one free at https://finnhub.io
578
717
  - Uses `X-Finnhub-Token` header; rate-limited client-side at 60 req/min
@@ -582,6 +721,7 @@ None. All v0.3.0 imports continue to work unchanged.
582
721
  ### New modules
583
722
 
584
723
  **`market-feed/indicators`** — Technical indicators as pure functions over `HistoricalBar[]`.
724
+
585
725
  - `sma(bars, period)` — Simple Moving Average (O(1) sliding window)
586
726
  - `ema(bars, period)` — Exponential Moving Average (k = 2/(period+1), SMA-seeded)
587
727
  - `rsi(bars, period?)` — Relative Strength Index via Wilder's smoothing (default period: 14)
@@ -594,6 +734,7 @@ None. All v0.3.0 imports continue to work unchanged.
594
734
  - All functions return typed result arrays (`IndicatorPoint[]`, `MACDPoint[]`, `BollingerPoint[]`, `StochasticPoint[]`)
595
735
 
596
736
  **`market-feed/portfolio`** — Track positions and compute live P&L.
737
+
597
738
  - `new Portfolio(positions?)` — construct with an array of `Position` objects
598
739
  - `portfolio.add(position)` / `portfolio.remove(symbol)` — mutable, chainable
599
740
  - `portfolio.snapshot(feed)` — fetches live quotes and returns `PortfolioSnapshot` with per-position and aggregate P&L
@@ -646,6 +787,7 @@ None. All v0.2.0 imports continue to work unchanged.
646
787
  ### New modules
647
788
 
648
789
  **`market-feed/calendar`** — Synchronous exchange calendar. No network required.
790
+
649
791
  - `isMarketOpen(exchange, at?)` — boolean, DST-correct via `Intl`
650
792
  - `getSession(exchange, at?)` — `"pre" | "regular" | "post" | "closed"`
651
793
  - `nextSessionOpen(exchange, from?)` / `nextSessionClose(exchange, from?)` — next UTC Date
@@ -657,6 +799,7 @@ None. All v0.2.0 imports continue to work unchanged.
657
799
  - Early-close days (NYSE: day before Thanksgiving, Independence Day, Christmas Eve)
658
800
 
659
801
  **`market-feed/stream`** — Market-hours-aware observable quote stream.
802
+
660
803
  - `watch(feed, symbols, options)` — async generator yielding typed `StreamEvent` union
661
804
  - Polls at `interval.open` (default 5s) during regular hours, `interval.prepost` (default 30s) pre/post, pauses at `interval.closed` (default 60s) when closed — saves API quota overnight and on weekends
662
805
  - Emits `market-open` / `market-close` events at session transitions
@@ -665,6 +808,7 @@ None. All v0.2.0 imports continue to work unchanged.
665
808
  - Configurable `maxErrors` before the generator throws
666
809
 
667
810
  **`market-feed/consensus`** — Multi-provider parallel price consensus.
811
+
668
812
  - `consensus(providers, symbol, options)` — queries all providers simultaneously via `Promise.allSettled`
669
813
  - Median-based outlier detection (avoids the all-outlier edge case of mean-based approaches)
670
814
  - Staleness detection: providers with quotes older than `stalenessThreshold` receive half weight
@@ -689,11 +833,13 @@ None. All v0.1.0 imports continue to work unchanged.
689
833
  ### Initial release
690
834
 
691
835
  **Providers**
836
+
692
837
  - `YahooProvider` — Yahoo Finance (no API key required): quote, historical, search, company
693
838
  - `AlphaVantageProvider` — Alpha Vantage (free: 25/day): quote, historical, search, company
694
839
  - `PolygonProvider` — Polygon.io (free: 15-min delayed): quote, historical, search, company, news
695
840
 
696
841
  **Core**
842
+
697
843
  - Unified `Quote`, `HistoricalBar`, `CompanyProfile`, `NewsItem`, `SearchResult`, `MarketStatus` types
698
844
  - `MarketFeed` client with provider chain, automatic fallback, and LRU caching
699
845
  - `MemoryCacheDriver` — zero-dependency LRU cache with TTL and configurable max size
@@ -704,6 +850,7 @@ None. All v0.1.0 imports continue to work unchanged.
704
850
  - Error hierarchy: `MarketFeedError`, `ProviderError`, `RateLimitError`, `UnsupportedOperationError`, `AllProvidersFailedError`
705
851
 
706
852
  **DX**
853
+
707
854
  - Zero production dependencies (native `fetch` only)
708
855
  - Strict TypeScript — `strict: true`, `noUncheckedIndexedAccess`, `noImplicitOverride`
709
856
  - ESM + CJS + `.d.ts` dual build