market-feed 1.1.0 → 1.2.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.
Files changed (99) hide show
  1. package/CHANGELOG.md +213 -137
  2. package/README.md +300 -40
  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 +178 -42
  28. package/dist/cli.js.map +1 -1
  29. package/dist/{client-CRK3kKjp.d.cts → client-BWZ-PieL.d.cts} +6 -46
  30. package/dist/{client-CX5RGcWX.d.ts → client-CtXeYQ1h.d.ts} +6 -46
  31. package/dist/consensus.d.cts +5 -4
  32. package/dist/consensus.d.ts +5 -4
  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 +284 -45
  38. package/dist/index.cjs.map +1 -1
  39. package/dist/index.d.cts +132 -20
  40. package/dist/index.d.ts +132 -20
  41. package/dist/index.js +283 -46
  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 +3 -1
  50. package/dist/macro.cjs.map +1 -1
  51. package/dist/macro.js +3 -1
  52. package/dist/macro.js.map +1 -1
  53. package/dist/memory-CG6fw-mp.d.ts +19 -0
  54. package/dist/memory-dGCjMfCu.d.cts +19 -0
  55. package/dist/orderbook-B6vFsskE.d.ts +115 -0
  56. package/dist/orderbook-Cxp95hhW.d.cts +115 -0
  57. package/dist/portfolio.cjs.map +1 -1
  58. package/dist/portfolio.d.cts +1 -1
  59. package/dist/portfolio.d.ts +1 -1
  60. package/dist/portfolio.js.map +1 -1
  61. package/dist/provider-CitIZBZX.d.ts +73 -0
  62. package/dist/provider-DQGRULh_.d.cts +73 -0
  63. package/dist/{quote-Cfh_7Cgg.d.cts → quote-BpPnhI4K.d.cts} +1 -1
  64. package/dist/{quote-Cfh_7Cgg.d.ts → quote-BpPnhI4K.d.ts} +1 -1
  65. package/dist/react.cjs +1021 -4
  66. package/dist/react.cjs.map +1 -1
  67. package/dist/react.d.cts +54 -10
  68. package/dist/react.d.ts +54 -10
  69. package/dist/react.js +1020 -5
  70. package/dist/react.js.map +1 -1
  71. package/dist/screener.d.cts +1 -1
  72. package/dist/screener.d.ts +1 -1
  73. package/dist/{provider-B3PfcWLV.d.ts → splits-Y1u7sJgO.d.cts} +19 -72
  74. package/dist/{provider-DROKdbpH.d.cts → splits-Y1u7sJgO.d.ts} +19 -72
  75. package/dist/stream.cjs +30 -0
  76. package/dist/stream.cjs.map +1 -1
  77. package/dist/stream.d.cts +9 -7
  78. package/dist/stream.d.ts +9 -7
  79. package/dist/stream.js +30 -0
  80. package/dist/stream.js.map +1 -1
  81. package/dist/trpc.cjs +97 -0
  82. package/dist/trpc.cjs.map +1 -0
  83. package/dist/trpc.d.cts +175 -0
  84. package/dist/trpc.d.ts +175 -0
  85. package/dist/trpc.js +94 -0
  86. package/dist/trpc.js.map +1 -0
  87. package/dist/{types-hzOO9Cst.d.cts → types-BpKjqHAF.d.cts} +1 -1
  88. package/dist/{types-C8OPQ-Yj.d.cts → types-BziYJD5M.d.cts} +24 -3
  89. package/dist/{types-D90J5xwU.d.ts → types-C9q2uBkf.d.ts} +1 -1
  90. package/dist/{types-B5oHwrPy.d.ts → types-DXjo6Y4W.d.ts} +24 -3
  91. package/dist/types-Dz0yKXpx.d.cts +43 -0
  92. package/dist/types-Dz0yKXpx.d.ts +43 -0
  93. package/dist/ws.cjs +606 -44
  94. package/dist/ws.cjs.map +1 -1
  95. package/dist/ws.d.cts +11 -67
  96. package/dist/ws.d.ts +11 -67
  97. package/dist/ws.js +606 -45
  98. package/dist/ws.js.map +1 -1
  99. package/package.json +30 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
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
+
3
9
  ## 1.1.0 — 2026-03-12
4
10
 
5
11
  ### New modules
@@ -19,7 +25,11 @@ const chain = await getOptionChain(polygon, "AAPL", {
19
25
  });
20
26
 
21
27
  for (const c of chain.calls) {
22
- console.log(`Strike $${c.strike} IV ${(c.impliedVolatility! * 100).toFixed(1)}% Δ ${c.delta?.toFixed(3)}`);
28
+ console.log(
29
+ `Strike $${c.strike} IV ${(c.impliedVolatility! * 100).toFixed(
30
+ 1
31
+ )}% Δ ${c.delta?.toFixed(3)}`
32
+ );
23
33
  }
24
34
  ```
25
35
 
@@ -52,10 +62,10 @@ for (const obs of cpi.observations) {
52
62
 
53
63
  Two new criteria in `market-feed/screener`:
54
64
 
55
- | Criterion | Passes when |
56
- |-----------|-------------|
65
+ | Criterion | Passes when |
66
+ | --------------------- | ------------------------------------------------------------------------------ |
57
67
  | `volume_vs_avg_above` | `quote.volume > quote.avgVolume * value` — e.g. `value: 2` = 2× average volume |
58
- | `volume_vs_avg_below` | `quote.volume < quote.avgVolume * value` |
68
+ | `volume_vs_avg_below` | `quote.volume < quote.avgVolume * value` |
59
69
 
60
70
  Both pass-through when `avgVolume` is `undefined` (consistent with 52-week criteria).
61
71
 
@@ -88,24 +98,40 @@ function StockPrice({ symbol }: { symbol: string }) {
88
98
  const { data, loading, error } = useQuote(feed, symbol);
89
99
  if (loading) return <span>…</span>;
90
100
  if (error) return <span>Error: {error.message}</span>;
91
- return <span>{symbol}: ${data?.price.toFixed(2)}</span>;
101
+ return (
102
+ <span>
103
+ {symbol}: ${data?.price.toFixed(2)}
104
+ </span>
105
+ );
92
106
  }
93
107
 
94
108
  // Subscribe to a live stream
95
109
  function LiveFeed() {
96
110
  const { event } = useStream(feed, ["AAPL", "MSFT", "GOOGL"]);
97
111
  if (!event || event.type !== "quote") return null;
98
- return <p>{event.symbol}: ${event.quote.price}</p>;
112
+ return (
113
+ <p>
114
+ {event.symbol}: ${event.quote.price}
115
+ </p>
116
+ );
99
117
  }
100
118
 
101
119
  // Collect price alerts
102
120
  function AlertLog() {
103
121
  const { events, clearEvents } = useAlerts(feed, [
104
- { 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
+ },
105
127
  ]);
106
128
  return (
107
129
  <ul>
108
- {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
+ ))}
109
135
  <button onClick={clearEvents}>Clear</button>
110
136
  </ul>
111
137
  );
@@ -116,10 +142,10 @@ function AlertLog() {
116
142
 
117
143
  **`useQuote(source, symbol, options?)`**
118
144
 
119
- | Option | Default | Description |
120
- |--------|---------|-------------|
121
- | `intervalMs` | `5000` | Poll interval in milliseconds |
122
- | `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 |
123
149
 
124
150
  Returns `{ data: Quote \| null, loading: boolean, error: Error \| null, refetch() }`.
125
151
 
@@ -172,25 +198,25 @@ console.log(results.map((r) => `${r.symbol} @ ${r.quote.price}`));
172
198
 
173
199
  #### Criterion types
174
200
 
175
- | Type | Description |
176
- |------|-------------|
177
- | `price_above` / `price_below` | Filter by current price |
178
- | `change_pct_above` / `change_pct_below` | Filter by daily % change |
179
- | `volume_above` / `volume_below` | Filter by trading volume |
180
- | `market_cap_above` / `market_cap_below` | Filter by market cap |
181
- | `52w_high_pct_below` | Price is within N% of the 52-week high |
182
- | `52w_low_pct_above` | Price is at least N% above the 52-week low |
183
- | `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 }` |
184
210
 
185
211
  All criteria are evaluated with **AND logic** — a symbol must pass every criterion to be included.
186
212
 
187
213
  #### Options
188
214
 
189
- | Option | Description |
190
- |--------|-------------|
191
- | `criteria` | Array of `ScreenerCriterion` (required) |
215
+ | Option | Description |
216
+ | ----------- | ------------------------------------------------------- |
217
+ | `criteria` | Array of `ScreenerCriterion` (required) |
192
218
  | `batchSize` | Max symbols per quote fetch call (default: all at once) |
193
- | `limit` | Max number of results to return |
219
+ | `limit` | Max number of results to return |
194
220
 
195
221
  #### Result shape
196
222
 
@@ -217,45 +243,52 @@ import { getFundamentals } from "market-feed/fundamentals";
217
243
  import { MarketFeed } from "market-feed";
218
244
 
219
245
  const feed = new MarketFeed();
220
- const { incomeStatements, balanceSheets, cashFlows } = await getFundamentals(feed, "AAPL");
246
+ const { incomeStatements, balanceSheets, cashFlows } = await getFundamentals(
247
+ feed,
248
+ "AAPL"
249
+ );
221
250
 
222
251
  console.log(`Revenue: $${(incomeStatements[0]!.revenue! / 1e9).toFixed(1)}B`);
223
- console.log(`Total assets: $${(balanceSheets[0]!.totalAssets! / 1e9).toFixed(1)}B`);
224
- 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
+ );
225
258
  ```
226
259
 
227
260
  `getFundamentals()` fetches all three statements in parallel via `Promise.allSettled` — a failure on one statement still returns the others.
228
261
 
229
262
  #### `IncomeStatement`
230
263
 
231
- | Field | Description |
232
- |-------|-------------|
233
- | `revenue` | Total revenue |
234
- | `grossProfit` | Revenue - cost of revenue |
235
- | `operatingIncome` | EBIT (operating) |
236
- | `netIncome` | Bottom-line net income |
237
- | `ebitda` | EBITDA when available |
238
- | `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 |
239
272
 
240
273
  #### `BalanceSheet`
241
274
 
242
- | Field | Description |
243
- |-------|-------------|
244
- | `totalAssets` | Total assets |
245
- | `totalLiabilities` | Total liabilities |
246
- | `totalStockholdersEquity` | Book value of equity |
247
- | `cashAndCashEquivalents` | Cash + cash equivalents |
248
- | `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 |
249
282
 
250
283
  #### `CashFlowStatement`
251
284
 
252
- | Field | Description |
253
- |-------|-------------|
254
- | `operatingCashFlow` | Cash from operations |
255
- | `capitalExpenditures` | CapEx (negative = outflow) |
256
- | `freeCashFlow` | operatingCashFlow + capitalExpenditures |
257
- | `investingCashFlow` | Cash from investing |
258
- | `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 |
259
292
 
260
293
  All three types include `symbol`, `date` (period end), `periodType` (`"annual"` | `"quarterly"`), and `provider`.
261
294
 
@@ -264,20 +297,20 @@ All three types include `symbol`, `date` (period end), `periodType` (`"annual"`
264
297
  ```ts
265
298
  const feed = new MarketFeed();
266
299
 
267
- const income = await feed.incomeStatements("AAPL", { limit: 4 });
300
+ const income = await feed.incomeStatements("AAPL", { limit: 4 });
268
301
  const balance = await feed.balanceSheets("AAPL", { quarterly: true });
269
- const cash = await feed.cashFlows("AAPL");
302
+ const cash = await feed.cashFlows("AAPL");
270
303
  ```
271
304
 
272
305
  `FundamentalsOptions`: `quarterly?` (default `false`), `limit?` (default `4`), `raw?`.
273
306
 
274
307
  ### Provider support
275
308
 
276
- | Method | `YahooProvider` | others |
277
- |--------|-----------------|--------|
278
- | `incomeStatements` | ✓ `incomeStatementHistory` / `incomeStatementHistoryQuarterly` | — |
279
- | `balanceSheets` | ✓ `balanceSheetHistory` / `balanceSheetHistoryQuarterly` | — |
280
- | `cashFlows` | ✓ `cashflowStatementHistory` / `cashflowStatementHistoryQuarterly` | — |
309
+ | Method | `YahooProvider` | others |
310
+ | ------------------ | ------------------------------------------------------------------ | ------ |
311
+ | `incomeStatements` | ✓ `incomeStatementHistory` / `incomeStatementHistoryQuarterly` | — |
312
+ | `balanceSheets` | ✓ `balanceSheetHistory` / `balanceSheetHistoryQuarterly` | — |
313
+ | `cashFlows` | ✓ `cashflowStatementHistory` / `cashflowStatementHistoryQuarterly` | — |
281
314
 
282
315
  ### Breaking changes
283
316
 
@@ -305,21 +338,21 @@ const feed = new MarketFeed([
305
338
  new TiingoProvider({ apiKey: process.env.TIINGO_KEY! }),
306
339
  ]);
307
340
 
308
- const quote = await feed.quote(["AAPL"]);
309
- 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" });
310
343
  const results = await feed.search("apple");
311
344
  const profile = await feed.company("AAPL");
312
- const news = await feed.news("AAPL", { limit: 5 });
345
+ const news = await feed.news("AAPL", { limit: 5 });
313
346
  ```
314
347
 
315
348
  Supports: `quote`, `historical`, `search`, `company`, `news`.
316
349
 
317
- | Feature | Detail |
318
- |---------|--------|
350
+ | Feature | Detail |
351
+ | ---------------- | ------------------------------------ |
319
352
  | Real-time quotes | IEX endpoint — US equities, intraday |
320
- | Historical | EOD daily bars, includes `adjClose` |
321
- | Rate limit | ~50 calls/hour (free plan) |
322
- | 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 |
323
356
 
324
357
  Free plan sign-up: https://www.tiingo.com
325
358
 
@@ -351,8 +384,8 @@ const feed = new MarketFeed([
351
384
  new TwelveDataProvider({ apiKey: process.env.TWELVE_DATA_KEY! }),
352
385
  ]);
353
386
 
354
- const quote = await feed.quote(["AAPL", "BTC/USD", "EUR/USD"]);
355
- 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" });
356
389
  const results = await feed.search("apple");
357
390
  const profile = await feed.company("AAPL");
358
391
  ```
@@ -360,6 +393,7 @@ const profile = await feed.company("AAPL");
360
393
  Supports: `quote`, `historical`, `search`, `company`.
361
394
 
362
395
  Strong coverage for global equities, forex, and crypto pairs. Symbol normalisation handles all common formats:
396
+
363
397
  - US stocks: `AAPL` (unchanged)
364
398
  - Crypto: `BTC-USD` / `BTC/USD` / `X:BTCUSD` → `BTC/USD`
365
399
  - Forex: `EURUSD=X` / `C:EURUSD` → `EUR/USD`
@@ -369,15 +403,15 @@ Free plan sign-up: https://twelvedata.com
369
403
  #### Interval mapping
370
404
 
371
405
  | market-feed | Twelve Data |
372
- |-------------|-------------|
373
- | `1m` | `1min` |
374
- | `5m` | `5min` |
375
- | `15m` | `15min` |
376
- | `30m` | `30min` |
377
- | `1h` | `1h` |
378
- | `1d` | `1day` |
379
- | `1wk` | `1week` |
380
- | `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` |
381
415
 
382
416
  ### New utility
383
417
 
@@ -429,8 +463,10 @@ None.
429
463
  import { backtest } from "market-feed/backtest";
430
464
  import type { EntrySignal, ExitSignal } from "market-feed/backtest";
431
465
 
432
- const entry: EntrySignal = (bars, i) => i > 0 && bars[i]!.close > bars[i - 1]!.close;
433
- 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;
434
470
 
435
471
  const result = backtest("AAPL", bars, entry, exit, { initialCapital: 10_000 });
436
472
  console.log(`Total return: ${(result.totalReturn * 100).toFixed(2)}%`);
@@ -438,16 +474,16 @@ console.log(`Sharpe ratio: ${result.sharpeRatio.toFixed(2)}`);
438
474
  console.log(`Max drawdown: ${(result.maxDrawdown * 100).toFixed(2)}%`);
439
475
  ```
440
476
 
441
- | Field | Description |
442
- |-------|-------------|
443
- | `totalReturn` | Fraction, e.g. 0.25 = 25% |
444
- | `annualizedReturn` | CAGR as a fraction |
445
- | `sharpeRatio` | Annualised Sharpe (risk-free rate = 0) |
446
- | `maxDrawdown` | Peak-to-trough as a positive fraction |
447
- | `winRate` | Fraction of profitable trades |
448
- | `profitFactor` | Gross profit / gross loss (`Infinity` when no losses) |
449
- | `totalTrades` | Number of completed round-trip trades |
450
- | `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 |
451
487
 
452
488
  **`market-feed/alerts`** — Poll a feed and yield `AlertEvent` when conditions are met.
453
489
 
@@ -458,21 +494,33 @@ import { MarketFeed } from "market-feed";
458
494
  const feed = new MarketFeed();
459
495
  const controller = new AbortController();
460
496
 
461
- for await (const event of watchAlerts(feed, [
462
- { symbol: "AAPL", condition: { type: "price_above", threshold: 200 }, once: true },
463
- { symbol: "TSLA", condition: { type: "change_pct_below", threshold: -5 }, debounceMs: 60_000 },
464
- ], { 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
+ )) {
465
513
  console.log(`${event.alert.symbol} triggered: $${event.quote.price}`);
466
514
  }
467
515
  ```
468
516
 
469
- | Condition type | Description |
470
- |----------------|-------------|
471
- | `price_above` | `quote.price > threshold` |
472
- | `price_below` | `quote.price < threshold` |
473
- | `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 |
474
522
  | `change_pct_below` | Daily `%` change falls below threshold |
475
- | `volume_above` | `quote.volume > threshold` |
523
+ | `volume_above` | `quote.volume > threshold` |
476
524
 
477
525
  `AlertConfig` options: `once` (fire at most once), `debounceMs` (suppress re-fires within window).
478
526
 
@@ -483,27 +531,33 @@ Three new methods on `MarketFeed` (and on individual providers):
483
531
  ```ts
484
532
  const feed = new MarketFeed([new PolygonProvider({ apiKey: "..." })]);
485
533
 
486
- const earnings = await feed.earnings("AAPL", { limit: 8 });
534
+ const earnings = await feed.earnings("AAPL", { limit: 8 });
487
535
  const dividends = await feed.dividends("AAPL");
488
- const splits = await feed.splits("AAPL");
536
+ const splits = await feed.splits("AAPL");
489
537
  ```
490
538
 
491
539
  #### Provider support
492
540
 
493
- | Method | `YahooProvider` | `PolygonProvider` | `FinnhubProvider` | `AlphaVantageProvider` |
494
- |--------|-----------------|-------------------|-------------------|------------------------|
495
- | `earnings` | ✓ quoteSummary `earningsHistory` | — | ✓ `/stock/earnings` | — |
496
- | `dividends` | ✓ chart `events=div` | ✓ `/v3/reference/dividends` | — | — |
497
- | `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` | — | — |
498
546
 
499
547
  #### `EarningsEvent`
500
548
 
501
549
  ```ts
502
550
  interface EarningsEvent {
503
- symbol: string; date: Date; period?: string;
504
- epsActual?: number; epsEstimate?: number; epsSurprisePct?: number;
505
- revenueActual?: number; revenueEstimate?: number;
506
- 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;
507
561
  }
508
562
  ```
509
563
 
@@ -511,10 +565,15 @@ interface EarningsEvent {
511
565
 
512
566
  ```ts
513
567
  interface DividendEvent {
514
- symbol: string; exDate: Date; payDate?: Date; declaredDate?: Date;
515
- amount: number; currency: string;
568
+ symbol: string;
569
+ exDate: Date;
570
+ payDate?: Date;
571
+ declaredDate?: Date;
572
+ amount: number;
573
+ currency: string;
516
574
  frequency?: "annual" | "semi-annual" | "quarterly" | "monthly" | "irregular";
517
- provider: string; raw?: unknown;
575
+ provider: string;
576
+ raw?: unknown;
518
577
  }
519
578
  ```
520
579
 
@@ -522,10 +581,12 @@ interface DividendEvent {
522
581
 
523
582
  ```ts
524
583
  interface SplitEvent {
525
- symbol: string; date: Date;
526
- 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
527
587
  description?: string;
528
- provider: string; raw?: unknown;
588
+ provider: string;
589
+ raw?: unknown;
529
590
  }
530
591
  ```
531
592
 
@@ -558,10 +619,14 @@ import { FinnhubProvider } from "market-feed";
558
619
  const provider = new FinnhubProvider({ apiKey: process.env.FINNHUB_KEY! });
559
620
  const controller = new AbortController();
560
621
 
561
- 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
+ })) {
562
625
  switch (event.type) {
563
626
  case "trade":
564
- 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
+ );
565
630
  break;
566
631
  case "connected":
567
632
  console.log(`Connected to ${event.provider}`);
@@ -578,23 +643,23 @@ for await (const event of connect(provider, ["AAPL", "MSFT"], { signal: controll
578
643
 
579
644
  #### Provider support
580
645
 
581
- | Provider | WebSocket | Notes |
582
- |----------|-----------|-------|
583
- | **PolygonProvider** | Native WS | `wss://socket.polygon.io/stocks` — auth via JSON handshake, subscribes to `T.*` trade channel |
584
- | **FinnhubProvider** | Native WS | `wss://ws.finnhub.io?token=KEY` — per-symbol subscribe, batched trade messages |
585
- | **YahooProvider** | Polling fallback | Polls `provider.quote()` every 5 s; emits `WsTrade` from quote data |
586
- | **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 |
587
652
 
588
653
  The `connect()` function detects provider capability automatically — no configuration required.
589
654
 
590
655
  #### `WsEvent` union
591
656
 
592
- | `type` | Payload | When |
593
- |--------|---------|------|
594
- | `"connected"` | `provider: string` | WS opened (and after each reconnect) |
595
- | `"trade"` | `trade: WsTrade` | Each trade tick |
596
- | `"disconnected"` | `provider`, `reconnecting`, `attempt` | WS closed unexpectedly |
597
- | `"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 |
598
663
 
599
664
  #### `WsTrade`
600
665
 
@@ -602,7 +667,7 @@ The `connect()` function detects provider capability automatically — no config
602
667
  interface WsTrade {
603
668
  symbol: string;
604
669
  price: number;
605
- size: number; // shares / units
670
+ size: number; // shares / units
606
671
  timestamp: Date;
607
672
  conditions?: number[]; // provider-specific condition codes
608
673
  }
@@ -610,12 +675,12 @@ interface WsTrade {
610
675
 
611
676
  #### `WsOptions`
612
677
 
613
- | Option | Type | Default | Description |
614
- |--------|------|---------|-------------|
615
- | `wsImpl` | `typeof globalThis.WebSocket` | `globalThis.WebSocket` | Custom WS constructor for Node 18–20 |
616
- | `maxReconnectAttempts` | `number` | `10` | Reconnect attempts before closing |
617
- | `reconnectDelayMs` | `number` | `1000` | Base delay (doubles per attempt, max 30 s) |
618
- | `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 |
619
684
 
620
685
  #### Node 18–20 compatibility
621
686
 
@@ -623,7 +688,9 @@ Node 21+ exposes `WebSocket` globally. For Node 18/20, install the `ws` package
623
688
 
624
689
  ```ts
625
690
  import WebSocket from "ws";
626
- 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
+ });
627
694
  ```
628
695
 
629
696
  ### Other changes
@@ -644,6 +711,7 @@ None. All v0.3.0 imports continue to work unchanged.
644
711
  ### New provider
645
712
 
646
713
  **`FinnhubProvider`** — Finnhub.io (free tier: real-time US stock data, 60 calls/minute).
714
+
647
715
  - `quote`, `historical` (candles), `search`, `company`, `news`
648
716
  - API key required — get one free at https://finnhub.io
649
717
  - Uses `X-Finnhub-Token` header; rate-limited client-side at 60 req/min
@@ -653,6 +721,7 @@ None. All v0.3.0 imports continue to work unchanged.
653
721
  ### New modules
654
722
 
655
723
  **`market-feed/indicators`** — Technical indicators as pure functions over `HistoricalBar[]`.
724
+
656
725
  - `sma(bars, period)` — Simple Moving Average (O(1) sliding window)
657
726
  - `ema(bars, period)` — Exponential Moving Average (k = 2/(period+1), SMA-seeded)
658
727
  - `rsi(bars, period?)` — Relative Strength Index via Wilder's smoothing (default period: 14)
@@ -665,6 +734,7 @@ None. All v0.3.0 imports continue to work unchanged.
665
734
  - All functions return typed result arrays (`IndicatorPoint[]`, `MACDPoint[]`, `BollingerPoint[]`, `StochasticPoint[]`)
666
735
 
667
736
  **`market-feed/portfolio`** — Track positions and compute live P&L.
737
+
668
738
  - `new Portfolio(positions?)` — construct with an array of `Position` objects
669
739
  - `portfolio.add(position)` / `portfolio.remove(symbol)` — mutable, chainable
670
740
  - `portfolio.snapshot(feed)` — fetches live quotes and returns `PortfolioSnapshot` with per-position and aggregate P&L
@@ -717,6 +787,7 @@ None. All v0.2.0 imports continue to work unchanged.
717
787
  ### New modules
718
788
 
719
789
  **`market-feed/calendar`** — Synchronous exchange calendar. No network required.
790
+
720
791
  - `isMarketOpen(exchange, at?)` — boolean, DST-correct via `Intl`
721
792
  - `getSession(exchange, at?)` — `"pre" | "regular" | "post" | "closed"`
722
793
  - `nextSessionOpen(exchange, from?)` / `nextSessionClose(exchange, from?)` — next UTC Date
@@ -728,6 +799,7 @@ None. All v0.2.0 imports continue to work unchanged.
728
799
  - Early-close days (NYSE: day before Thanksgiving, Independence Day, Christmas Eve)
729
800
 
730
801
  **`market-feed/stream`** — Market-hours-aware observable quote stream.
802
+
731
803
  - `watch(feed, symbols, options)` — async generator yielding typed `StreamEvent` union
732
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
733
805
  - Emits `market-open` / `market-close` events at session transitions
@@ -736,6 +808,7 @@ None. All v0.2.0 imports continue to work unchanged.
736
808
  - Configurable `maxErrors` before the generator throws
737
809
 
738
810
  **`market-feed/consensus`** — Multi-provider parallel price consensus.
811
+
739
812
  - `consensus(providers, symbol, options)` — queries all providers simultaneously via `Promise.allSettled`
740
813
  - Median-based outlier detection (avoids the all-outlier edge case of mean-based approaches)
741
814
  - Staleness detection: providers with quotes older than `stalenessThreshold` receive half weight
@@ -760,11 +833,13 @@ None. All v0.1.0 imports continue to work unchanged.
760
833
  ### Initial release
761
834
 
762
835
  **Providers**
836
+
763
837
  - `YahooProvider` — Yahoo Finance (no API key required): quote, historical, search, company
764
838
  - `AlphaVantageProvider` — Alpha Vantage (free: 25/day): quote, historical, search, company
765
839
  - `PolygonProvider` — Polygon.io (free: 15-min delayed): quote, historical, search, company, news
766
840
 
767
841
  **Core**
842
+
768
843
  - Unified `Quote`, `HistoricalBar`, `CompanyProfile`, `NewsItem`, `SearchResult`, `MarketStatus` types
769
844
  - `MarketFeed` client with provider chain, automatic fallback, and LRU caching
770
845
  - `MemoryCacheDriver` — zero-dependency LRU cache with TTL and configurable max size
@@ -775,6 +850,7 @@ None. All v0.1.0 imports continue to work unchanged.
775
850
  - Error hierarchy: `MarketFeedError`, `ProviderError`, `RateLimitError`, `UnsupportedOperationError`, `AllProvidersFailedError`
776
851
 
777
852
  **DX**
853
+
778
854
  - Zero production dependencies (native `fetch` only)
779
855
  - Strict TypeScript — `strict: true`, `noUncheckedIndexedAccess`, `noImplicitOverride`
780
856
  - ESM + CJS + `.d.ts` dual build