@lyniaca/lynstock 0.1.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/LICENSE +21 -0
- package/README.md +197 -0
- package/dist/cryptoSymbols.d.ts +9 -0
- package/dist/cryptoSymbols.d.ts.map +1 -0
- package/dist/cryptoSymbols.js +23 -0
- package/dist/cryptoSymbols.js.map +1 -0
- package/dist/errors.d.ts +28 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +50 -0
- package/dist/errors.js.map +1 -0
- package/dist/historicalCache.d.ts +23 -0
- package/dist/historicalCache.d.ts.map +1 -0
- package/dist/historicalCache.js +33 -0
- package/dist/historicalCache.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/lynstock.d.ts +45 -0
- package/dist/lynstock.d.ts.map +1 -0
- package/dist/lynstock.js +131 -0
- package/dist/lynstock.js.map +1 -0
- package/dist/sources/alphaVantage.d.ts +3 -0
- package/dist/sources/alphaVantage.d.ts.map +1 -0
- package/dist/sources/alphaVantage.js +79 -0
- package/dist/sources/alphaVantage.js.map +1 -0
- package/dist/sources/binance.d.ts +3 -0
- package/dist/sources/binance.d.ts.map +1 -0
- package/dist/sources/binance.js +68 -0
- package/dist/sources/binance.js.map +1 -0
- package/dist/sources/coinCap.d.ts +3 -0
- package/dist/sources/coinCap.d.ts.map +1 -0
- package/dist/sources/coinCap.js +37 -0
- package/dist/sources/coinCap.js.map +1 -0
- package/dist/sources/coinGecko.d.ts +3 -0
- package/dist/sources/coinGecko.d.ts.map +1 -0
- package/dist/sources/coinGecko.js +76 -0
- package/dist/sources/coinGecko.js.map +1 -0
- package/dist/sources/coinbase.d.ts +3 -0
- package/dist/sources/coinbase.d.ts.map +1 -0
- package/dist/sources/coinbase.js +71 -0
- package/dist/sources/coinbase.js.map +1 -0
- package/dist/sources/finnhub.d.ts +3 -0
- package/dist/sources/finnhub.d.ts.map +1 -0
- package/dist/sources/finnhub.js +35 -0
- package/dist/sources/finnhub.js.map +1 -0
- package/dist/sources/kraken.d.ts +3 -0
- package/dist/sources/kraken.d.ts.map +1 -0
- package/dist/sources/kraken.js +79 -0
- package/dist/sources/kraken.js.map +1 -0
- package/dist/sources/stooq.d.ts +3 -0
- package/dist/sources/stooq.d.ts.map +1 -0
- package/dist/sources/stooq.js +83 -0
- package/dist/sources/stooq.js.map +1 -0
- package/dist/sources/twelveData.d.ts +3 -0
- package/dist/sources/twelveData.d.ts.map +1 -0
- package/dist/sources/twelveData.js +80 -0
- package/dist/sources/twelveData.js.map +1 -0
- package/dist/sources/yahoo.d.ts +3 -0
- package/dist/sources/yahoo.d.ts.map +1 -0
- package/dist/sources/yahoo.js +65 -0
- package/dist/sources/yahoo.js.map +1 -0
- package/dist/time.d.ts +4 -0
- package/dist/time.d.ts.map +1 -0
- package/dist/time.js +10 -0
- package/dist/time.js.map +1 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lyniaca
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# @lyniaca/lynstock
|
|
2
|
+
|
|
3
|
+
Normalized, multi-source market data for stocks and crypto. Ask for a quote or
|
|
4
|
+
some history and get the same shape back no matter which upstream API
|
|
5
|
+
actually answered — LynStock tries sources in priority order and falls
|
|
6
|
+
through automatically on a rate limit, an outage, or a missing API key.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { LynStock } from "@lyniaca/lynstock";
|
|
10
|
+
|
|
11
|
+
const lynstock = new LynStock();
|
|
12
|
+
|
|
13
|
+
const quote = await lynstock.getQuote("AAPL", "stock");
|
|
14
|
+
// { symbol: "AAPL", assetClass: "stock", price: 227.4, currency: "USD", timestamp: 1735689600000, source: "yahoo" }
|
|
15
|
+
|
|
16
|
+
const btc = await lynstock.getQuote("BTC", "crypto");
|
|
17
|
+
// { symbol: "BTC", assetClass: "crypto", price: 98213, currency: "USD", timestamp: 1735689600000, source: "coingecko" }
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
npm install @lyniaca/lynstock
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
ESM only (`"type": "module"`). Node 18+ for global `fetch`.
|
|
27
|
+
|
|
28
|
+
## Why
|
|
29
|
+
|
|
30
|
+
Free/cheap market data APIs (Alpha Vantage, Yahoo, IEX, CoinGecko, etc.) each
|
|
31
|
+
have their own rate limits, outages, and response shapes. LynStock is a thin
|
|
32
|
+
layer that hides all of that: your code asks one interface for a quote or a
|
|
33
|
+
history window, and LynStock handles trying sources in order, skipping ones
|
|
34
|
+
that are rate-limited or missing a required API key, and normalizing whatever
|
|
35
|
+
comes back into one schema.
|
|
36
|
+
|
|
37
|
+
## Quotes
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
const quote = await lynstock.getQuote(symbol, assetClass);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Every source normalizes into:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
interface Quote {
|
|
47
|
+
symbol: string;
|
|
48
|
+
assetClass: "stock" | "crypto";
|
|
49
|
+
price: number;
|
|
50
|
+
currency: string;
|
|
51
|
+
timestamp: number; // ms since epoch, when LynStock received it
|
|
52
|
+
source: string; // which upstream actually answered, e.g. "yahoo"
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Historical data
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
const history = await lynstock.getHistorical("AAPL", "stock", {
|
|
60
|
+
from: new Date("2024-01-01"),
|
|
61
|
+
to: new Date("2024-02-01"),
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Returns daily OHLCV candles:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
interface HistoricalResult {
|
|
69
|
+
symbol: string;
|
|
70
|
+
assetClass: "stock" | "crypto";
|
|
71
|
+
source: string;
|
|
72
|
+
candles: Candle[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface Candle {
|
|
76
|
+
timestamp: number; // start of the day, ms since epoch
|
|
77
|
+
open: number;
|
|
78
|
+
high: number;
|
|
79
|
+
low: number;
|
|
80
|
+
close: number;
|
|
81
|
+
volume?: number; // not every free-tier source exposes volume (e.g. CoinGecko's OHLC endpoint)
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Only daily granularity is supported for now.
|
|
86
|
+
|
|
87
|
+
### Caching
|
|
88
|
+
|
|
89
|
+
A historical range that ends before today is fully elapsed — none of these
|
|
90
|
+
providers revise a past day's close after the fact — so LynStock caches that
|
|
91
|
+
result in memory and never re-fetches it. A range that reaches into today is
|
|
92
|
+
never cached, since today's candle is still moving.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
// First call hits the network.
|
|
96
|
+
await lynstock.getHistorical("AAPL", "stock", { from: jan1, to: jan31 });
|
|
97
|
+
// Second call for the exact same symbol/range/asset class returns instantly, no network call.
|
|
98
|
+
await lynstock.getHistorical("AAPL", "stock", { from: jan1, to: jan31 });
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The default cache is an unbounded in-memory `Map`, fine for a single process.
|
|
102
|
+
Pass your own implementation of the `HistoricalCache` interface (e.g. backed
|
|
103
|
+
by Redis) via the `historicalCache` option if you need it shared across
|
|
104
|
+
processes or persisted across restarts.
|
|
105
|
+
|
|
106
|
+
## Supported sources
|
|
107
|
+
|
|
108
|
+
| Source | Asset class | API key | Historical | Notes |
|
|
109
|
+
|---|---|---|---|---|
|
|
110
|
+
| Yahoo Finance | stock | no | yes | Default first choice for stocks. |
|
|
111
|
+
| Alpha Vantage | stock | yes (free signup) | yes | Free tier: 25 requests/day. |
|
|
112
|
+
| Finnhub | stock | yes (free signup) | no | Free tier no longer includes historical candles. |
|
|
113
|
+
| Twelve Data | stock | yes (free signup) | yes | |
|
|
114
|
+
| CoinGecko | crypto | no | yes | Default first choice for crypto. Historical has no volume on the free tier. |
|
|
115
|
+
| Binance | crypto | no | yes | Blocked (HTTP 451) from some regions, including the US, at the network level — not something this library can work around. |
|
|
116
|
+
| Coinbase | crypto | no | yes | Uses the public Exchange candles endpoint for history, not the spot-price endpoint. |
|
|
117
|
+
| Kraken | crypto | no | yes | |
|
|
118
|
+
| Stooq | stock | no | yes | **Not in the default priority.** Its quote endpoint currently 404s and its historical endpoint serves a JS anti-bot challenge instead of CSV. The adapter is implemented and opt-in via `setPriority` in case that changes. |
|
|
119
|
+
| CoinCap | crypto | no | no (price-only, no OHLC) | **Not in the default priority.** `api.coincap.io` no longer resolves — the free v2 API was shut down. |
|
|
120
|
+
|
|
121
|
+
Default priority order:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
DEFAULT_STOCK_PRIORITY = ["yahoo", "alphavantage", "finnhub", "twelvedata"];
|
|
125
|
+
DEFAULT_CRYPTO_PRIORITY = ["coingecko", "binance", "coinbase", "kraken"];
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Configuration
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
const lynstock = new LynStock({
|
|
132
|
+
apiKeys: {
|
|
133
|
+
alphavantage: process.env.ALPHA_VANTAGE_KEY,
|
|
134
|
+
finnhub: process.env.FINNHUB_KEY,
|
|
135
|
+
twelvedata: process.env.TWELVE_DATA_KEY,
|
|
136
|
+
},
|
|
137
|
+
priorities: {
|
|
138
|
+
stock: ["yahoo", "twelvedata", "alphavantage"],
|
|
139
|
+
crypto: ["kraken", "coingecko", "binance"],
|
|
140
|
+
},
|
|
141
|
+
disabledSources: ["binance"], // e.g. you know you're on a blocked region
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
All of it can also change after construction:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
lynstock.setApiKey("finnhub", "...");
|
|
149
|
+
lynstock.setPriority("stock", ["yahoo"]);
|
|
150
|
+
lynstock.disableSource("binance");
|
|
151
|
+
lynstock.enableSource("binance");
|
|
152
|
+
lynstock.getPriority("crypto"); // ["coingecko", "binance", "coinbase", "kraken"]
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
A source that requires a key you haven't set is skipped automatically (it
|
|
156
|
+
throws internally and LynStock moves on to the next one in priority order) —
|
|
157
|
+
you don't need to filter it out of the priority list yourself.
|
|
158
|
+
|
|
159
|
+
## Error handling
|
|
160
|
+
|
|
161
|
+
If every source in the priority list fails, `getQuote`/`getHistorical` throws
|
|
162
|
+
`AllSourcesFailedError`, which carries the per-source failure reasons:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
try {
|
|
166
|
+
await lynstock.getQuote("AAPL", "stock");
|
|
167
|
+
} catch (error) {
|
|
168
|
+
if (error instanceof AllSourcesFailedError) {
|
|
169
|
+
for (const { source, error: cause } of error.causes) {
|
|
170
|
+
console.log(source, cause.message);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Other exported error types, mainly useful if you're writing your own source
|
|
177
|
+
adapter: `RateLimitError`, `SourceError`, `MissingApiKeyError`,
|
|
178
|
+
`UnsupportedSymbolError` (thrown for a crypto symbol with no known mapping to
|
|
179
|
+
a given provider's id format — see `CRYPTO_SYMBOLS`).
|
|
180
|
+
|
|
181
|
+
## Development
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
npm install
|
|
185
|
+
npm run build # tsc -> dist/
|
|
186
|
+
npm test # vitest, all mocked — no network calls
|
|
187
|
+
npm run live-check # hits real upstream APIs, prints pass/fail per source
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`live-check` is not part of the automated test suite on purpose — it depends
|
|
191
|
+
on the real internet and on providers' current rate limits/regional blocks,
|
|
192
|
+
so its output will vary run to run. Run it by hand before a release to catch
|
|
193
|
+
response-shape drift the mocked unit tests can't see.
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cryptoSymbols.d.ts","sourceRoot":"","sources":["../src/cryptoSymbols.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAqB1D,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const CRYPTO_SYMBOLS = {
|
|
2
|
+
BTC: { coingecko: "bitcoin", binance: "BTCUSDT", coinbase: "BTC-USD", coincap: "bitcoin", kraken: "XBTUSD" },
|
|
3
|
+
ETH: { coingecko: "ethereum", binance: "ETHUSDT", coinbase: "ETH-USD", coincap: "ethereum", kraken: "ETHUSD" },
|
|
4
|
+
SOL: { coingecko: "solana", binance: "SOLUSDT", coinbase: "SOL-USD", coincap: "solana", kraken: "SOLUSD" },
|
|
5
|
+
ADA: { coingecko: "cardano", binance: "ADAUSDT", coinbase: "ADA-USD", coincap: "cardano", kraken: "ADAUSD" },
|
|
6
|
+
XRP: { coingecko: "ripple", binance: "XRPUSDT", coinbase: "XRP-USD", coincap: "xrp", kraken: "XRPUSD" },
|
|
7
|
+
DOGE: { coingecko: "dogecoin", binance: "DOGEUSDT", coinbase: "DOGE-USD", coincap: "dogecoin", kraken: "DOGEUSD" },
|
|
8
|
+
LTC: { coingecko: "litecoin", binance: "LTCUSDT", coinbase: "LTC-USD", coincap: "litecoin", kraken: "LTCUSD" },
|
|
9
|
+
BCH: { coingecko: "bitcoin-cash", binance: "BCHUSDT", coinbase: "BCH-USD", coincap: "bitcoin-cash", kraken: "BCHUSD" },
|
|
10
|
+
DOT: { coingecko: "polkadot", binance: "DOTUSDT", coinbase: "DOT-USD", coincap: "polkadot", kraken: "DOTUSD" },
|
|
11
|
+
MATIC: { coingecko: "matic-network", binance: "MATICUSDT", coinbase: "MATIC-USD", coincap: "polygon", kraken: "MATICUSD" },
|
|
12
|
+
AVAX: { coingecko: "avalanche-2", binance: "AVAXUSDT", coinbase: "AVAX-USD", coincap: "avalanche", kraken: "AVAXUSD" },
|
|
13
|
+
LINK: { coingecko: "chainlink", binance: "LINKUSDT", coinbase: "LINK-USD", coincap: "chainlink", kraken: "LINKUSD" },
|
|
14
|
+
UNI: { coingecko: "uniswap", binance: "UNIUSDT", coinbase: "UNI-USD", coincap: "uniswap", kraken: "UNIUSD" },
|
|
15
|
+
ATOM: { coingecko: "cosmos", binance: "ATOMUSDT", coinbase: "ATOM-USD", coincap: "cosmos", kraken: "ATOMUSD" },
|
|
16
|
+
XLM: { coingecko: "stellar", binance: "XLMUSDT", coinbase: "XLM-USD", coincap: "stellar", kraken: "XLMUSD" },
|
|
17
|
+
ETC: { coingecko: "ethereum-classic", binance: "ETCUSDT", coinbase: "ETC-USD", coincap: "ethereum-classic", kraken: "ETCUSD" },
|
|
18
|
+
FIL: { coingecko: "filecoin", binance: "FILUSDT", coinbase: "FIL-USD", coincap: "filecoin", kraken: "FILUSD" },
|
|
19
|
+
ARB: { coingecko: "arbitrum", binance: "ARBUSDT", coinbase: "ARB-USD", coincap: "arbitrum", kraken: "ARBUSD" },
|
|
20
|
+
OP: { coingecko: "optimism", binance: "OPUSDT", coinbase: "OP-USD", coincap: "optimism", kraken: "OPUSD" },
|
|
21
|
+
APT: { coingecko: "aptos", binance: "APTUSDT", coinbase: "APT-USD", coincap: "aptos", kraken: "APTUSD" },
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=cryptoSymbols.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cryptoSymbols.js","sourceRoot":"","sources":["../src/cryptoSymbols.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare class RateLimitError extends Error {
|
|
2
|
+
readonly source: string;
|
|
3
|
+
constructor(source: string, message?: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class SourceError extends Error {
|
|
6
|
+
readonly source: string;
|
|
7
|
+
constructor(source: string, message: string);
|
|
8
|
+
}
|
|
9
|
+
export declare class UnsupportedSymbolError extends Error {
|
|
10
|
+
readonly source: string;
|
|
11
|
+
readonly symbol: string;
|
|
12
|
+
constructor(source: string, symbol: string);
|
|
13
|
+
}
|
|
14
|
+
export declare class MissingApiKeyError extends Error {
|
|
15
|
+
readonly source: string;
|
|
16
|
+
constructor(source: string);
|
|
17
|
+
}
|
|
18
|
+
export interface SourceFailure {
|
|
19
|
+
source: string;
|
|
20
|
+
error: Error;
|
|
21
|
+
}
|
|
22
|
+
export declare class AllSourcesFailedError extends Error {
|
|
23
|
+
readonly symbol: string;
|
|
24
|
+
readonly assetClass: string;
|
|
25
|
+
readonly causes: SourceFailure[];
|
|
26
|
+
constructor(symbol: string, assetClass: string, causes: SourceFailure[]);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,cAAe,SAAQ,KAAK;aACX,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;CAI7D;AAED,qBAAa,WAAY,SAAQ,KAAK;aACR,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAI5D;AAED,qBAAa,sBAAuB,SAAQ,KAAK;aACnB,MAAM,EAAE,MAAM;aAAkB,MAAM,EAAE,MAAM;gBAA9C,MAAM,EAAE,MAAM,EAAkB,MAAM,EAAE,MAAM;CAI3E;AAED,qBAAa,kBAAmB,SAAQ,KAAK;aACf,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM;CAI3C;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,KAAK,CAAC;CACd;AAED,qBAAa,qBAAsB,SAAQ,KAAK;aAE5B,MAAM,EAAE,MAAM;aACd,UAAU,EAAE,MAAM;aAClB,MAAM,EAAE,aAAa,EAAE;gBAFvB,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,aAAa,EAAE;CAU1C"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export class RateLimitError extends Error {
|
|
2
|
+
source;
|
|
3
|
+
constructor(source, message) {
|
|
4
|
+
super(message ?? `${source} rate limit exceeded`);
|
|
5
|
+
this.source = source;
|
|
6
|
+
this.name = "RateLimitError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class SourceError extends Error {
|
|
10
|
+
source;
|
|
11
|
+
constructor(source, message) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.source = source;
|
|
14
|
+
this.name = "SourceError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class UnsupportedSymbolError extends Error {
|
|
18
|
+
source;
|
|
19
|
+
symbol;
|
|
20
|
+
constructor(source, symbol) {
|
|
21
|
+
super(`${source} does not support symbol "${symbol}"`);
|
|
22
|
+
this.source = source;
|
|
23
|
+
this.symbol = symbol;
|
|
24
|
+
this.name = "UnsupportedSymbolError";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class MissingApiKeyError extends Error {
|
|
28
|
+
source;
|
|
29
|
+
constructor(source) {
|
|
30
|
+
super(`${source} requires an API key`);
|
|
31
|
+
this.source = source;
|
|
32
|
+
this.name = "MissingApiKeyError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export class AllSourcesFailedError extends Error {
|
|
36
|
+
symbol;
|
|
37
|
+
assetClass;
|
|
38
|
+
causes;
|
|
39
|
+
constructor(symbol, assetClass, causes) {
|
|
40
|
+
super(`All sources failed for ${symbol} (${assetClass}): ` +
|
|
41
|
+
(causes.length === 0
|
|
42
|
+
? "no sources configured for this asset class"
|
|
43
|
+
: causes.map((c) => `${c.source}: ${c.error.message}`).join("; ")));
|
|
44
|
+
this.symbol = symbol;
|
|
45
|
+
this.assetClass = assetClass;
|
|
46
|
+
this.causes = causes;
|
|
47
|
+
this.name = "AllSourcesFailedError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,cAAe,SAAQ,KAAK;IACX;IAA5B,YAA4B,MAAc,EAAE,OAAgB;QAC1D,KAAK,CAAC,OAAO,IAAI,GAAG,MAAM,sBAAsB,CAAC,CAAC;QADxB,WAAM,GAAN,MAAM,CAAQ;QAExC,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED,MAAM,OAAO,WAAY,SAAQ,KAAK;IACR;IAA5B,YAA4B,MAAc,EAAE,OAAe;QACzD,KAAK,CAAC,OAAO,CAAC,CAAC;QADW,WAAM,GAAN,MAAM,CAAQ;QAExC,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAED,MAAM,OAAO,sBAAuB,SAAQ,KAAK;IACnB;IAAgC;IAA5D,YAA4B,MAAc,EAAkB,MAAc;QACxE,KAAK,CAAC,GAAG,MAAM,6BAA6B,MAAM,GAAG,CAAC,CAAC;QAD7B,WAAM,GAAN,MAAM,CAAQ;QAAkB,WAAM,GAAN,MAAM,CAAQ;QAExE,IAAI,CAAC,IAAI,GAAG,wBAAwB,CAAC;IACvC,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IACf;IAA5B,YAA4B,MAAc;QACxC,KAAK,CAAC,GAAG,MAAM,sBAAsB,CAAC,CAAC;QADb,WAAM,GAAN,MAAM,CAAQ;QAExC,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF;AAOD,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAE5B;IACA;IACA;IAHlB,YACkB,MAAc,EACd,UAAkB,EAClB,MAAuB;QAEvC,KAAK,CACH,0BAA0B,MAAM,KAAK,UAAU,KAAK;YAClD,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC;gBAClB,CAAC,CAAC,4CAA4C;gBAC9C,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CACvE,CAAC;QATc,WAAM,GAAN,MAAM,CAAQ;QACd,eAAU,GAAV,UAAU,CAAQ;QAClB,WAAM,GAAN,MAAM,CAAiB;QAQvC,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { AssetClass, HistoricalQuery, HistoricalResult } from "./types.js";
|
|
2
|
+
export interface HistoricalCache {
|
|
3
|
+
get(key: string): HistoricalResult | undefined;
|
|
4
|
+
set(key: string, value: HistoricalResult): void;
|
|
5
|
+
}
|
|
6
|
+
/** Simple unbounded in-memory cache. Fine for a single process; swap in a real
|
|
7
|
+
* cache (Redis, etc.) via the LynStock `historicalCache` option for anything
|
|
8
|
+
* longer-lived than one process. */
|
|
9
|
+
export declare class MemoryHistoricalCache implements HistoricalCache {
|
|
10
|
+
private readonly store;
|
|
11
|
+
get(key: string): HistoricalResult | undefined;
|
|
12
|
+
set(key: string, value: HistoricalResult): void;
|
|
13
|
+
clear(): void;
|
|
14
|
+
get size(): number;
|
|
15
|
+
}
|
|
16
|
+
export declare function buildHistoricalCacheKey(symbol: string, assetClass: AssetClass, query: HistoricalQuery): string;
|
|
17
|
+
/**
|
|
18
|
+
* A range ending before the start of today is fully elapsed: none of the
|
|
19
|
+
* providers revise past daily closes, so a result for that range is safe to
|
|
20
|
+
* cache indefinitely instead of re-fetching the same history on every call.
|
|
21
|
+
*/
|
|
22
|
+
export declare function isFullyElapsed(query: HistoricalQuery): boolean;
|
|
23
|
+
//# sourceMappingURL=historicalCache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"historicalCache.d.ts","sourceRoot":"","sources":["../src/historicalCache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGhF,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAAC;IAC/C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACjD;AAED;;qCAEqC;AACrC,qBAAa,qBAAsB,YAAW,eAAe;IAC3D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAuC;IAE7D,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAI9C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAI/C,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,eAAe,GACrB,MAAM,CAER;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAI9D"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { toMs } from "./time.js";
|
|
2
|
+
/** Simple unbounded in-memory cache. Fine for a single process; swap in a real
|
|
3
|
+
* cache (Redis, etc.) via the LynStock `historicalCache` option for anything
|
|
4
|
+
* longer-lived than one process. */
|
|
5
|
+
export class MemoryHistoricalCache {
|
|
6
|
+
store = new Map();
|
|
7
|
+
get(key) {
|
|
8
|
+
return this.store.get(key);
|
|
9
|
+
}
|
|
10
|
+
set(key, value) {
|
|
11
|
+
this.store.set(key, value);
|
|
12
|
+
}
|
|
13
|
+
clear() {
|
|
14
|
+
this.store.clear();
|
|
15
|
+
}
|
|
16
|
+
get size() {
|
|
17
|
+
return this.store.size;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function buildHistoricalCacheKey(symbol, assetClass, query) {
|
|
21
|
+
return `${assetClass}:${symbol.toUpperCase()}:${toMs(query.from)}:${toMs(query.to)}`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A range ending before the start of today is fully elapsed: none of the
|
|
25
|
+
* providers revise past daily closes, so a result for that range is safe to
|
|
26
|
+
* cache indefinitely instead of re-fetching the same history on every call.
|
|
27
|
+
*/
|
|
28
|
+
export function isFullyElapsed(query) {
|
|
29
|
+
const startOfToday = new Date();
|
|
30
|
+
startOfToday.setUTCHours(0, 0, 0, 0);
|
|
31
|
+
return toMs(query.to) < startOfToday.getTime();
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=historicalCache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"historicalCache.js","sourceRoot":"","sources":["../src/historicalCache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAOjC;;qCAEqC;AACrC,MAAM,OAAO,qBAAqB;IACf,KAAK,GAAG,IAAI,GAAG,EAA4B,CAAC;IAE7D,GAAG,CAAC,GAAW;QACb,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,KAAuB;QACtC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;CACF;AAED,MAAM,UAAU,uBAAuB,CACrC,MAAc,EACd,UAAsB,EACtB,KAAsB;IAEtB,OAAO,GAAG,UAAU,IAAI,MAAM,CAAC,WAAW,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC;AACvF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,KAAsB;IACnD,MAAM,YAAY,GAAG,IAAI,IAAI,EAAE,CAAC;IAChC,YAAY,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACrC,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,OAAO,EAAE,CAAC;AACjD,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from "./types.js";
|
|
2
|
+
export * from "./errors.js";
|
|
3
|
+
export * from "./lynstock.js";
|
|
4
|
+
export { CRYPTO_SYMBOLS, type CryptoSymbolMap } from "./cryptoSymbols.js";
|
|
5
|
+
export { MemoryHistoricalCache, buildHistoricalCacheKey, isFullyElapsed, type HistoricalCache, } from "./historicalCache.js";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,cAAc,EACd,KAAK,eAAe,GACrB,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export * from "./types.js";
|
|
2
|
+
export * from "./errors.js";
|
|
3
|
+
export * from "./lynstock.js";
|
|
4
|
+
export { CRYPTO_SYMBOLS } from "./cryptoSymbols.js";
|
|
5
|
+
export { MemoryHistoricalCache, buildHistoricalCacheKey, isFullyElapsed, } from "./historicalCache.js";
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAwB,MAAM,oBAAoB,CAAC;AAC1E,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,cAAc,GAEf,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { AssetClass, FetchLike, HistoricalQuery, HistoricalResult, Quote } from "./types.js";
|
|
2
|
+
import { type HistoricalCache } from "./historicalCache.js";
|
|
3
|
+
export type SourceName = "yahoo" | "stooq" | "alphavantage" | "finnhub" | "twelvedata" | "coingecko" | "binance" | "coinbase" | "coincap" | "kraken";
|
|
4
|
+
export declare const DEFAULT_STOCK_PRIORITY: SourceName[];
|
|
5
|
+
export declare const DEFAULT_CRYPTO_PRIORITY: SourceName[];
|
|
6
|
+
export interface LynStockOptions {
|
|
7
|
+
/** API keys for sources that require one (alphavantage, finnhub, twelvedata). */
|
|
8
|
+
apiKeys?: Partial<Record<SourceName, string>>;
|
|
9
|
+
/** Override the fallback order tried for a given asset class. */
|
|
10
|
+
priorities?: Partial<Record<AssetClass, SourceName[]>>;
|
|
11
|
+
/** Sources to skip entirely, regardless of priority. */
|
|
12
|
+
disabledSources?: SourceName[];
|
|
13
|
+
/** Inject a custom fetch implementation (used for testing). */
|
|
14
|
+
fetchImpl?: FetchLike;
|
|
15
|
+
/** Defaults to an in-memory cache; pass your own (e.g. Redis-backed) for multi-process reuse. */
|
|
16
|
+
historicalCache?: HistoricalCache;
|
|
17
|
+
}
|
|
18
|
+
export declare class LynStock {
|
|
19
|
+
private readonly sources;
|
|
20
|
+
private readonly apiKeys;
|
|
21
|
+
private readonly disabled;
|
|
22
|
+
private readonly priorities;
|
|
23
|
+
private readonly historicalCache;
|
|
24
|
+
constructor(options?: LynStockOptions);
|
|
25
|
+
/** Reorder (or restrict) which sources are tried, and in what order, for an asset class. */
|
|
26
|
+
setPriority(assetClass: AssetClass, order: SourceName[]): void;
|
|
27
|
+
getPriority(assetClass: AssetClass): SourceName[];
|
|
28
|
+
setApiKey(source: SourceName, apiKey: string): void;
|
|
29
|
+
disableSource(source: SourceName): void;
|
|
30
|
+
enableSource(source: SourceName): void;
|
|
31
|
+
/**
|
|
32
|
+
* Fetches a quote, walking the configured priority list for the asset class and
|
|
33
|
+
* falling through to the next source whenever the current one errors (including
|
|
34
|
+
* on rate limits) so a single degraded upstream never fails the whole call.
|
|
35
|
+
*/
|
|
36
|
+
getQuote(symbol: string, assetClass: AssetClass): Promise<Quote>;
|
|
37
|
+
/**
|
|
38
|
+
* Fetches daily OHLCV history, same priority/fallback behavior as getQuote.
|
|
39
|
+
* Results for a range that's fully in the past are cached indefinitely, since
|
|
40
|
+
* none of the upstream providers revise a closed day's candle after the fact —
|
|
41
|
+
* repeatedly asking for the same historical window never has to hit the network twice.
|
|
42
|
+
*/
|
|
43
|
+
getHistorical(symbol: string, assetClass: AssetClass, query: HistoricalQuery): Promise<HistoricalResult>;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=lynstock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lynstock.d.ts","sourceRoot":"","sources":["../src/lynstock.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,eAAe,EAAE,gBAAgB,EAAE,KAAK,EAAe,MAAM,YAAY,CAAC;AAE/G,OAAO,EAIL,KAAK,eAAe,EACrB,MAAM,sBAAsB,CAAC;AAY9B,MAAM,MAAM,UAAU,GAClB,OAAO,GACP,OAAO,GACP,cAAc,GACd,SAAS,GACT,YAAY,GACZ,WAAW,GACX,SAAS,GACT,UAAU,GACV,SAAS,GACT,QAAQ,CAAC;AAMb,eAAO,MAAM,sBAAsB,EAAE,UAAU,EAK9C,CAAC;AAIF,eAAO,MAAM,uBAAuB,EAAE,UAAU,EAAmD,CAAC;AAEpG,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,OAAO,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9C,iEAAiE;IACjE,UAAU,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC;IACvD,wDAAwD;IACxD,eAAe,CAAC,EAAE,UAAU,EAAE,CAAC;IAC/B,+DAA+D;IAC/D,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,iGAAiG;IACjG,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IACvD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmC;IAC9D,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;gBAEtC,OAAO,GAAE,eAAoB;IAyBzC,4FAA4F;IAC5F,WAAW,CAAC,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,IAAI;IAI9D,WAAW,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,EAAE;IAIjD,SAAS,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAInD,aAAa,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAIvC,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAItC;;;;OAIG;IACG,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC;IAuBtE;;;;;OAKG;IACG,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAgC/G"}
|
package/dist/lynstock.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { AllSourcesFailedError } from "./errors.js";
|
|
2
|
+
import { buildHistoricalCacheKey, isFullyElapsed, MemoryHistoricalCache, } from "./historicalCache.js";
|
|
3
|
+
import { createYahooSource } from "./sources/yahoo.js";
|
|
4
|
+
import { createStooqSource } from "./sources/stooq.js";
|
|
5
|
+
import { createAlphaVantageSource } from "./sources/alphaVantage.js";
|
|
6
|
+
import { createFinnhubSource } from "./sources/finnhub.js";
|
|
7
|
+
import { createTwelveDataSource } from "./sources/twelveData.js";
|
|
8
|
+
import { createCoinGeckoSource } from "./sources/coinGecko.js";
|
|
9
|
+
import { createBinanceSource } from "./sources/binance.js";
|
|
10
|
+
import { createCoinbaseSource } from "./sources/coinbase.js";
|
|
11
|
+
import { createCoinCapSource } from "./sources/coinCap.js";
|
|
12
|
+
import { createKrakenSource } from "./sources/kraken.js";
|
|
13
|
+
// Stooq is excluded by default: its quote endpoint currently 404s and its
|
|
14
|
+
// historical endpoint serves a JS proof-of-work challenge a plain fetch can't
|
|
15
|
+
// pass. The adapter is still exported for anyone who wants to opt in manually
|
|
16
|
+
// via setPriority, in case that changes.
|
|
17
|
+
export const DEFAULT_STOCK_PRIORITY = [
|
|
18
|
+
"yahoo",
|
|
19
|
+
"alphavantage",
|
|
20
|
+
"finnhub",
|
|
21
|
+
"twelvedata",
|
|
22
|
+
];
|
|
23
|
+
// CoinCap is excluded by default: api.coincap.io no longer resolves (the free
|
|
24
|
+
// v2 API was shut down). Same opt-in-if-it-comes-back reasoning as Stooq above.
|
|
25
|
+
export const DEFAULT_CRYPTO_PRIORITY = ["coingecko", "binance", "coinbase", "kraken"];
|
|
26
|
+
export class LynStock {
|
|
27
|
+
sources;
|
|
28
|
+
apiKeys;
|
|
29
|
+
disabled;
|
|
30
|
+
priorities;
|
|
31
|
+
historicalCache;
|
|
32
|
+
constructor(options = {}) {
|
|
33
|
+
const fetchImpl = options.fetchImpl ?? fetch;
|
|
34
|
+
this.sources = new Map([
|
|
35
|
+
["yahoo", createYahooSource(fetchImpl)],
|
|
36
|
+
["stooq", createStooqSource(fetchImpl)],
|
|
37
|
+
["alphavantage", createAlphaVantageSource(fetchImpl)],
|
|
38
|
+
["finnhub", createFinnhubSource(fetchImpl)],
|
|
39
|
+
["twelvedata", createTwelveDataSource(fetchImpl)],
|
|
40
|
+
["coingecko", createCoinGeckoSource(fetchImpl)],
|
|
41
|
+
["binance", createBinanceSource(fetchImpl)],
|
|
42
|
+
["coinbase", createCoinbaseSource(fetchImpl)],
|
|
43
|
+
["coincap", createCoinCapSource(fetchImpl)],
|
|
44
|
+
["kraken", createKrakenSource(fetchImpl)],
|
|
45
|
+
]);
|
|
46
|
+
this.apiKeys = { ...options.apiKeys };
|
|
47
|
+
this.disabled = new Set(options.disabledSources ?? []);
|
|
48
|
+
this.priorities = {
|
|
49
|
+
stock: options.priorities?.stock ?? [...DEFAULT_STOCK_PRIORITY],
|
|
50
|
+
crypto: options.priorities?.crypto ?? [...DEFAULT_CRYPTO_PRIORITY],
|
|
51
|
+
};
|
|
52
|
+
this.historicalCache = options.historicalCache ?? new MemoryHistoricalCache();
|
|
53
|
+
}
|
|
54
|
+
/** Reorder (or restrict) which sources are tried, and in what order, for an asset class. */
|
|
55
|
+
setPriority(assetClass, order) {
|
|
56
|
+
this.priorities[assetClass] = [...order];
|
|
57
|
+
}
|
|
58
|
+
getPriority(assetClass) {
|
|
59
|
+
return [...this.priorities[assetClass]];
|
|
60
|
+
}
|
|
61
|
+
setApiKey(source, apiKey) {
|
|
62
|
+
this.apiKeys[source] = apiKey;
|
|
63
|
+
}
|
|
64
|
+
disableSource(source) {
|
|
65
|
+
this.disabled.add(source);
|
|
66
|
+
}
|
|
67
|
+
enableSource(source) {
|
|
68
|
+
this.disabled.delete(source);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Fetches a quote, walking the configured priority list for the asset class and
|
|
72
|
+
* falling through to the next source whenever the current one errors (including
|
|
73
|
+
* on rate limits) so a single degraded upstream never fails the whole call.
|
|
74
|
+
*/
|
|
75
|
+
async getQuote(symbol, assetClass) {
|
|
76
|
+
const order = this.priorities[assetClass];
|
|
77
|
+
const causes = [];
|
|
78
|
+
for (const sourceName of order) {
|
|
79
|
+
if (this.disabled.has(sourceName)) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const source = this.sources.get(sourceName);
|
|
83
|
+
if (!source || !source.assetClasses.includes(assetClass)) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
return await source.getQuote(symbol, this.apiKeys[sourceName]);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
causes.push({ source: sourceName, error: error });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
throw new AllSourcesFailedError(symbol, assetClass, causes);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Fetches daily OHLCV history, same priority/fallback behavior as getQuote.
|
|
97
|
+
* Results for a range that's fully in the past are cached indefinitely, since
|
|
98
|
+
* none of the upstream providers revise a closed day's candle after the fact —
|
|
99
|
+
* repeatedly asking for the same historical window never has to hit the network twice.
|
|
100
|
+
*/
|
|
101
|
+
async getHistorical(symbol, assetClass, query) {
|
|
102
|
+
const cacheKey = buildHistoricalCacheKey(symbol, assetClass, query);
|
|
103
|
+
const cached = this.historicalCache.get(cacheKey);
|
|
104
|
+
if (cached) {
|
|
105
|
+
return cached;
|
|
106
|
+
}
|
|
107
|
+
const order = this.priorities[assetClass];
|
|
108
|
+
const causes = [];
|
|
109
|
+
for (const sourceName of order) {
|
|
110
|
+
if (this.disabled.has(sourceName)) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const source = this.sources.get(sourceName);
|
|
114
|
+
if (!source || !source.assetClasses.includes(assetClass) || !source.getHistorical) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
try {
|
|
118
|
+
const result = await source.getHistorical(symbol, query, this.apiKeys[sourceName]);
|
|
119
|
+
if (isFullyElapsed(query)) {
|
|
120
|
+
this.historicalCache.set(cacheKey, result);
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
causes.push({ source: sourceName, error: error });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
throw new AllSourcesFailedError(symbol, assetClass, causes);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=lynstock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lynstock.js","sourceRoot":"","sources":["../src/lynstock.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAsB,MAAM,aAAa,CAAC;AACxE,OAAO,EACL,uBAAuB,EACvB,cAAc,EACd,qBAAqB,GAEtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAczD,0EAA0E;AAC1E,8EAA8E;AAC9E,8EAA8E;AAC9E,yCAAyC;AACzC,MAAM,CAAC,MAAM,sBAAsB,GAAiB;IAClD,OAAO;IACP,cAAc;IACd,SAAS;IACT,YAAY;CACb,CAAC;AAEF,8EAA8E;AAC9E,gFAAgF;AAChF,MAAM,CAAC,MAAM,uBAAuB,GAAiB,CAAC,WAAW,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;AAepG,MAAM,OAAO,QAAQ;IACF,OAAO,CAA+B;IACtC,OAAO,CAAsC;IAC7C,QAAQ,CAAkB;IAC1B,UAAU,CAAmC;IAC7C,eAAe,CAAkB;IAElD,YAAY,UAA2B,EAAE;QACvC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC;QAE7C,IAAI,CAAC,OAAO,GAAG,IAAI,GAAG,CAA0B;YAC9C,CAAC,OAAO,EAAE,iBAAiB,CAAC,SAAS,CAAC,CAAC;YACvC,CAAC,OAAO,EAAE,iBAAiB,CAAC,SAAS,CAAC,CAAC;YACvC,CAAC,cAAc,EAAE,wBAAwB,CAAC,SAAS,CAAC,CAAC;YACrD,CAAC,SAAS,EAAE,mBAAmB,CAAC,SAAS,CAAC,CAAC;YAC3C,CAAC,YAAY,EAAE,sBAAsB,CAAC,SAAS,CAAC,CAAC;YACjD,CAAC,WAAW,EAAE,qBAAqB,CAAC,SAAS,CAAC,CAAC;YAC/C,CAAC,SAAS,EAAE,mBAAmB,CAAC,SAAS,CAAC,CAAC;YAC3C,CAAC,UAAU,EAAE,oBAAoB,CAAC,SAAS,CAAC,CAAC;YAC7C,CAAC,SAAS,EAAE,mBAAmB,CAAC,SAAS,CAAC,CAAC;YAC3C,CAAC,QAAQ,EAAE,kBAAkB,CAAC,SAAS,CAAC,CAAC;SAC1C,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,GAAG,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;QACtC,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,eAAe,IAAI,EAAE,CAAC,CAAC;QACvD,IAAI,CAAC,UAAU,GAAG;YAChB,KAAK,EAAE,OAAO,CAAC,UAAU,EAAE,KAAK,IAAI,CAAC,GAAG,sBAAsB,CAAC;YAC/D,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE,MAAM,IAAI,CAAC,GAAG,uBAAuB,CAAC;SACnE,CAAC;QACF,IAAI,CAAC,eAAe,GAAG,OAAO,CAAC,eAAe,IAAI,IAAI,qBAAqB,EAAE,CAAC;IAChF,CAAC;IAED,4FAA4F;IAC5F,WAAW,CAAC,UAAsB,EAAE,KAAmB;QACrD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC;IAC3C,CAAC;IAED,WAAW,CAAC,UAAsB;QAChC,OAAO,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;IAC1C,CAAC;IAED,SAAS,CAAC,MAAkB,EAAE,MAAc;QAC1C,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IAChC,CAAC;IAED,aAAa,CAAC,MAAkB;QAC9B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;IAED,YAAY,CAAC,MAAkB;QAC7B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,QAAQ,CAAC,MAAc,EAAE,UAAsB;QACnD,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAoB,EAAE,CAAC;QAEnC,KAAK,MAAM,UAAU,IAAI,KAAK,EAAE,CAAC;YAC/B,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;gBAClC,SAAS;YACX,CAAC;YACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAC5C,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;gBACzD,SAAS;YACX,CAAC;YAED,IAAI,CAAC;gBACH,OAAO,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;YACjE,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,KAAc,EAAE,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;QAED,MAAM,IAAI,qBAAqB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;IAC9D,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,aAAa,CAAC,MAAc,EAAE,UAAsB,EAAE,KAAsB;QAChF,MAAM,QAAQ,GAAG,uBAAuB,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;QACpE,MAAM,MAAM,GAAG,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAClD,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAoB,EAAE,CAAC;QAEnC,KAAK,MAAM,UAAU,IAAI,KAAK,EAAE,CAAC;YAC/B,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;gBAClC,SAAS;YACX,CAAC;YACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YAC5C,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;gBAClF,SAAS;YACX,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC;gBACnF,IAAI,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC1B,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;gBAC7C,CAAC;gBACD,OAAO,MAAM,CAAC;YAChB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,KAAc,EAAE,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC;QAED,MAAM,IAAI,qBAAqB,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;IAC9D,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"alphaVantage.d.ts","sourceRoot":"","sources":["../../src/sources/alphaVantage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAU,MAAM,aAAa,CAAC;AAIlE,wBAAgB,wBAAwB,CAAC,SAAS,GAAE,SAAiB,GAAG,WAAW,CA6ElF"}
|