@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.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +197 -0
  3. package/dist/cryptoSymbols.d.ts +9 -0
  4. package/dist/cryptoSymbols.d.ts.map +1 -0
  5. package/dist/cryptoSymbols.js +23 -0
  6. package/dist/cryptoSymbols.js.map +1 -0
  7. package/dist/errors.d.ts +28 -0
  8. package/dist/errors.d.ts.map +1 -0
  9. package/dist/errors.js +50 -0
  10. package/dist/errors.js.map +1 -0
  11. package/dist/historicalCache.d.ts +23 -0
  12. package/dist/historicalCache.d.ts.map +1 -0
  13. package/dist/historicalCache.js +33 -0
  14. package/dist/historicalCache.js.map +1 -0
  15. package/dist/index.d.ts +6 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +6 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/lynstock.d.ts +45 -0
  20. package/dist/lynstock.d.ts.map +1 -0
  21. package/dist/lynstock.js +131 -0
  22. package/dist/lynstock.js.map +1 -0
  23. package/dist/sources/alphaVantage.d.ts +3 -0
  24. package/dist/sources/alphaVantage.d.ts.map +1 -0
  25. package/dist/sources/alphaVantage.js +79 -0
  26. package/dist/sources/alphaVantage.js.map +1 -0
  27. package/dist/sources/binance.d.ts +3 -0
  28. package/dist/sources/binance.d.ts.map +1 -0
  29. package/dist/sources/binance.js +68 -0
  30. package/dist/sources/binance.js.map +1 -0
  31. package/dist/sources/coinCap.d.ts +3 -0
  32. package/dist/sources/coinCap.d.ts.map +1 -0
  33. package/dist/sources/coinCap.js +37 -0
  34. package/dist/sources/coinCap.js.map +1 -0
  35. package/dist/sources/coinGecko.d.ts +3 -0
  36. package/dist/sources/coinGecko.d.ts.map +1 -0
  37. package/dist/sources/coinGecko.js +76 -0
  38. package/dist/sources/coinGecko.js.map +1 -0
  39. package/dist/sources/coinbase.d.ts +3 -0
  40. package/dist/sources/coinbase.d.ts.map +1 -0
  41. package/dist/sources/coinbase.js +71 -0
  42. package/dist/sources/coinbase.js.map +1 -0
  43. package/dist/sources/finnhub.d.ts +3 -0
  44. package/dist/sources/finnhub.d.ts.map +1 -0
  45. package/dist/sources/finnhub.js +35 -0
  46. package/dist/sources/finnhub.js.map +1 -0
  47. package/dist/sources/kraken.d.ts +3 -0
  48. package/dist/sources/kraken.d.ts.map +1 -0
  49. package/dist/sources/kraken.js +79 -0
  50. package/dist/sources/kraken.js.map +1 -0
  51. package/dist/sources/stooq.d.ts +3 -0
  52. package/dist/sources/stooq.d.ts.map +1 -0
  53. package/dist/sources/stooq.js +83 -0
  54. package/dist/sources/stooq.js.map +1 -0
  55. package/dist/sources/twelveData.d.ts +3 -0
  56. package/dist/sources/twelveData.d.ts.map +1 -0
  57. package/dist/sources/twelveData.js +80 -0
  58. package/dist/sources/twelveData.js.map +1 -0
  59. package/dist/sources/yahoo.d.ts +3 -0
  60. package/dist/sources/yahoo.d.ts.map +1 -0
  61. package/dist/sources/yahoo.js +65 -0
  62. package/dist/sources/yahoo.js.map +1 -0
  63. package/dist/time.d.ts +4 -0
  64. package/dist/time.d.ts.map +1 -0
  65. package/dist/time.js +10 -0
  66. package/dist/time.js.map +1 -0
  67. package/dist/types.d.ts +47 -0
  68. package/dist/types.d.ts.map +1 -0
  69. package/dist/types.js +2 -0
  70. package/dist/types.js.map +1 -0
  71. package/package.json +43 -0
@@ -0,0 +1,79 @@
1
+ import { MissingApiKeyError, RateLimitError, SourceError } from "../errors.js";
2
+ import { toMs } from "../time.js";
3
+ export function createAlphaVantageSource(fetchImpl = fetch) {
4
+ return {
5
+ name: "alphavantage",
6
+ assetClasses: ["stock"],
7
+ requiresApiKey: true,
8
+ async getQuote(symbol, apiKey) {
9
+ if (!apiKey) {
10
+ throw new MissingApiKeyError("alphavantage");
11
+ }
12
+ const url = `https://www.alphavantage.co/query?function=GLOBAL_QUOTE&symbol=${encodeURIComponent(symbol)}&apikey=${apiKey}`;
13
+ const response = await fetchImpl(url);
14
+ if (response.status === 429) {
15
+ throw new RateLimitError("alphavantage");
16
+ }
17
+ if (!response.ok) {
18
+ throw new SourceError("alphavantage", `HTTP ${response.status}`);
19
+ }
20
+ const data = await response.json();
21
+ // Alpha Vantage returns HTTP 200 even when the free-tier rate limit is hit,
22
+ // signaling it through a "Note" or "Information" field instead of a status code.
23
+ if (data.Note || data.Information) {
24
+ throw new RateLimitError("alphavantage", data.Note ?? data.Information);
25
+ }
26
+ const quote = data["Global Quote"];
27
+ const price = quote ? Number(quote["05. price"]) : NaN;
28
+ if (!Number.isFinite(price)) {
29
+ throw new SourceError("alphavantage", `No data for symbol "${symbol}"`);
30
+ }
31
+ return {
32
+ symbol: symbol.toUpperCase(),
33
+ assetClass: "stock",
34
+ price,
35
+ currency: "USD",
36
+ timestamp: Date.now(),
37
+ source: "alphavantage",
38
+ };
39
+ },
40
+ async getHistorical(symbol, query, apiKey) {
41
+ if (!apiKey) {
42
+ throw new MissingApiKeyError("alphavantage");
43
+ }
44
+ const fromMs = toMs(query.from);
45
+ const toMsValue = toMs(query.to);
46
+ // The free tier only distinguishes compact (last 100 days) vs full history.
47
+ const outputsize = Date.now() - fromMs > 100 * 24 * 60 * 60 * 1000 ? "full" : "compact";
48
+ const url = `https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=${encodeURIComponent(symbol)}&outputsize=${outputsize}&apikey=${apiKey}`;
49
+ const response = await fetchImpl(url);
50
+ if (response.status === 429) {
51
+ throw new RateLimitError("alphavantage");
52
+ }
53
+ if (!response.ok) {
54
+ throw new SourceError("alphavantage", `HTTP ${response.status}`);
55
+ }
56
+ const data = await response.json();
57
+ if (data.Note || data.Information) {
58
+ throw new RateLimitError("alphavantage", data.Note ?? data.Information);
59
+ }
60
+ const series = data["Time Series (Daily)"];
61
+ if (!series) {
62
+ throw new SourceError("alphavantage", `No historical data for symbol "${symbol}"`);
63
+ }
64
+ const candles = Object.entries(series)
65
+ .map(([date, values]) => ({
66
+ timestamp: Date.parse(`${date}T00:00:00Z`),
67
+ open: Number(values["1. open"]),
68
+ high: Number(values["2. high"]),
69
+ low: Number(values["3. low"]),
70
+ close: Number(values["4. close"]),
71
+ volume: Number(values["5. volume"]),
72
+ }))
73
+ .filter((candle) => candle.timestamp >= fromMs && candle.timestamp <= toMsValue)
74
+ .sort((a, b) => a.timestamp - b.timestamp);
75
+ return { symbol: symbol.toUpperCase(), assetClass: "stock", source: "alphavantage", candles };
76
+ },
77
+ };
78
+ }
79
+ //# sourceMappingURL=alphaVantage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alphaVantage.js","sourceRoot":"","sources":["../../src/sources/alphaVantage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { FetchLike, QuoteSource } from "../types.js";
2
+ export declare function createBinanceSource(fetchImpl?: FetchLike): QuoteSource;
3
+ //# sourceMappingURL=binance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binance.d.ts","sourceRoot":"","sources":["../../src/sources/binance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAU,MAAM,aAAa,CAAC;AAKlE,wBAAgB,mBAAmB,CAAC,SAAS,GAAE,SAAiB,GAAG,WAAW,CA+D7E"}
@@ -0,0 +1,68 @@
1
+ import { RateLimitError, SourceError, UnsupportedSymbolError } from "../errors.js";
2
+ import { CRYPTO_SYMBOLS } from "../cryptoSymbols.js";
3
+ import { toMs } from "../time.js";
4
+ export function createBinanceSource(fetchImpl = fetch) {
5
+ return {
6
+ name: "binance",
7
+ assetClasses: ["crypto"],
8
+ requiresApiKey: false,
9
+ async getQuote(symbol) {
10
+ const pair = CRYPTO_SYMBOLS[symbol.toUpperCase()]?.binance;
11
+ if (!pair) {
12
+ throw new UnsupportedSymbolError("binance", symbol);
13
+ }
14
+ const url = `https://api.binance.com/api/v3/ticker/price?symbol=${pair}`;
15
+ const response = await fetchImpl(url);
16
+ // 418 is Binance's "IP auto-banned for repeated rate-limit violations" status.
17
+ if (response.status === 429 || response.status === 418) {
18
+ throw new RateLimitError("binance");
19
+ }
20
+ if (!response.ok) {
21
+ throw new SourceError("binance", `HTTP ${response.status}`);
22
+ }
23
+ const data = await response.json();
24
+ const price = Number(data?.price);
25
+ if (!Number.isFinite(price)) {
26
+ throw new SourceError("binance", `No data for symbol "${symbol}"`);
27
+ }
28
+ return {
29
+ symbol: symbol.toUpperCase(),
30
+ assetClass: "crypto",
31
+ price,
32
+ currency: "USD",
33
+ timestamp: Date.now(),
34
+ source: "binance",
35
+ };
36
+ },
37
+ async getHistorical(symbol, query) {
38
+ const pair = CRYPTO_SYMBOLS[symbol.toUpperCase()]?.binance;
39
+ if (!pair) {
40
+ throw new UnsupportedSymbolError("binance", symbol);
41
+ }
42
+ const startTime = toMs(query.from);
43
+ const endTime = toMs(query.to);
44
+ const url = `https://api.binance.com/api/v3/klines?symbol=${pair}&interval=1d&startTime=${startTime}&endTime=${endTime}&limit=1000`;
45
+ const response = await fetchImpl(url);
46
+ if (response.status === 429 || response.status === 418) {
47
+ throw new RateLimitError("binance");
48
+ }
49
+ if (!response.ok) {
50
+ throw new SourceError("binance", `HTTP ${response.status}`);
51
+ }
52
+ const rows = await response.json();
53
+ if (!Array.isArray(rows) || rows.length === 0) {
54
+ throw new SourceError("binance", `No historical data for symbol "${symbol}"`);
55
+ }
56
+ const candles = rows.map((row) => ({
57
+ timestamp: Number(row[0]),
58
+ open: Number(row[1]),
59
+ high: Number(row[2]),
60
+ low: Number(row[3]),
61
+ close: Number(row[4]),
62
+ volume: Number(row[5]),
63
+ }));
64
+ return { symbol: symbol.toUpperCase(), assetClass: "crypto", source: "binance", candles };
65
+ },
66
+ };
67
+ }
68
+ //# sourceMappingURL=binance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binance.js","sourceRoot":"","sources":["../../src/sources/binance.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { FetchLike, QuoteSource } from "../types.js";
2
+ export declare function createCoinCapSource(fetchImpl?: FetchLike): QuoteSource;
3
+ //# sourceMappingURL=coinCap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coinCap.d.ts","sourceRoot":"","sources":["../../src/sources/coinCap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI1D,wBAAgB,mBAAmB,CAAC,SAAS,GAAE,SAAiB,GAAG,WAAW,CAiC7E"}
@@ -0,0 +1,37 @@
1
+ import { RateLimitError, SourceError, UnsupportedSymbolError } from "../errors.js";
2
+ import { CRYPTO_SYMBOLS } from "../cryptoSymbols.js";
3
+ export function createCoinCapSource(fetchImpl = fetch) {
4
+ return {
5
+ name: "coincap",
6
+ assetClasses: ["crypto"],
7
+ requiresApiKey: false,
8
+ async getQuote(symbol) {
9
+ const id = CRYPTO_SYMBOLS[symbol.toUpperCase()]?.coincap;
10
+ if (!id) {
11
+ throw new UnsupportedSymbolError("coincap", symbol);
12
+ }
13
+ const url = `https://api.coincap.io/v2/assets/${id}`;
14
+ const response = await fetchImpl(url);
15
+ if (response.status === 429) {
16
+ throw new RateLimitError("coincap");
17
+ }
18
+ if (!response.ok) {
19
+ throw new SourceError("coincap", `HTTP ${response.status}`);
20
+ }
21
+ const data = await response.json();
22
+ const price = Number(data?.data?.priceUsd);
23
+ if (!Number.isFinite(price)) {
24
+ throw new SourceError("coincap", `No data for symbol "${symbol}"`);
25
+ }
26
+ return {
27
+ symbol: symbol.toUpperCase(),
28
+ assetClass: "crypto",
29
+ price,
30
+ currency: "USD",
31
+ timestamp: Date.now(),
32
+ source: "coincap",
33
+ };
34
+ },
35
+ };
36
+ }
37
+ //# sourceMappingURL=coinCap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coinCap.js","sourceRoot":"","sources":["../../src/sources/coinCap.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,MAAM,UAAU,mBAAmB,CAAC,YAAuB,KAAK;IAC9D,OAAO;QACL,IAAI,EAAE,SAAS;QACf,YAAY,EAAE,CAAC,QAAQ,CAAC;QACxB,cAAc,EAAE,KAAK;QACrB,KAAK,CAAC,QAAQ,CAAC,MAAM;YACnB,MAAM,EAAE,GAAG,cAAc,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,EAAE,OAAO,CAAC;YACzD,IAAI,CAAC,EAAE,EAAE,CAAC;gBACR,MAAM,IAAI,sBAAsB,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;YACtD,CAAC;YACD,MAAM,GAAG,GAAG,oCAAoC,EAAE,EAAE,CAAC;YACrD,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;YACtC,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAC5B,MAAM,IAAI,cAAc,CAAC,SAAS,CAAC,CAAC;YACtC,CAAC;YACD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,WAAW,CAAC,SAAS,EAAE,QAAQ,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;YAC9D,CAAC;YACD,MAAM,IAAI,GAAQ,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACxC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;YAC3C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC5B,MAAM,IAAI,WAAW,CAAC,SAAS,EAAE,uBAAuB,MAAM,GAAG,CAAC,CAAC;YACrE,CAAC;YACD,OAAO;gBACL,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE;gBAC5B,UAAU,EAAE,QAAQ;gBACpB,KAAK;gBACL,QAAQ,EAAE,KAAK;gBACf,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;gBACrB,MAAM,EAAE,SAAS;aAClB,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { FetchLike, QuoteSource } from "../types.js";
2
+ export declare function createCoinGeckoSource(fetchImpl?: FetchLike): QuoteSource;
3
+ //# sourceMappingURL=coinGecko.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coinGecko.d.ts","sourceRoot":"","sources":["../../src/sources/coinGecko.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAU,MAAM,aAAa,CAAC;AAalE,wBAAgB,qBAAqB,CAAC,SAAS,GAAE,SAAiB,GAAG,WAAW,CAiE/E"}
@@ -0,0 +1,76 @@
1
+ import { RateLimitError, SourceError, UnsupportedSymbolError } from "../errors.js";
2
+ import { CRYPTO_SYMBOLS } from "../cryptoSymbols.js";
3
+ import { toMs } from "../time.js";
4
+ // CoinGecko's free-tier /ohlc endpoint only accepts these fixed lookback windows.
5
+ const ALLOWED_DAYS = [1, 7, 14, 30, 90, 180, 365];
6
+ function pickDaysWindow(fromMs) {
7
+ const requestedDays = Math.ceil((Date.now() - fromMs) / (24 * 60 * 60 * 1000));
8
+ return ALLOWED_DAYS.find((days) => days >= requestedDays) ?? "max";
9
+ }
10
+ export function createCoinGeckoSource(fetchImpl = fetch) {
11
+ return {
12
+ name: "coingecko",
13
+ assetClasses: ["crypto"],
14
+ requiresApiKey: false,
15
+ async getQuote(symbol) {
16
+ const id = CRYPTO_SYMBOLS[symbol.toUpperCase()]?.coingecko;
17
+ if (!id) {
18
+ throw new UnsupportedSymbolError("coingecko", symbol);
19
+ }
20
+ const url = `https://api.coingecko.com/api/v3/simple/price?ids=${id}&vs_currencies=usd`;
21
+ const response = await fetchImpl(url);
22
+ if (response.status === 429) {
23
+ throw new RateLimitError("coingecko");
24
+ }
25
+ if (!response.ok) {
26
+ throw new SourceError("coingecko", `HTTP ${response.status}`);
27
+ }
28
+ const data = await response.json();
29
+ const price = data?.[id]?.usd;
30
+ if (typeof price !== "number") {
31
+ throw new SourceError("coingecko", `No data for symbol "${symbol}"`);
32
+ }
33
+ return {
34
+ symbol: symbol.toUpperCase(),
35
+ assetClass: "crypto",
36
+ price,
37
+ currency: "USD",
38
+ timestamp: Date.now(),
39
+ source: "coingecko",
40
+ };
41
+ },
42
+ async getHistorical(symbol, query) {
43
+ const id = CRYPTO_SYMBOLS[symbol.toUpperCase()]?.coingecko;
44
+ if (!id) {
45
+ throw new UnsupportedSymbolError("coingecko", symbol);
46
+ }
47
+ const days = pickDaysWindow(toMs(query.from));
48
+ const url = `https://api.coingecko.com/api/v3/coins/${id}/ohlc?vs_currency=usd&days=${days}`;
49
+ const response = await fetchImpl(url);
50
+ if (response.status === 429) {
51
+ throw new RateLimitError("coingecko");
52
+ }
53
+ if (!response.ok) {
54
+ throw new SourceError("coingecko", `HTTP ${response.status}`);
55
+ }
56
+ const rows = await response.json();
57
+ if (!Array.isArray(rows) || rows.length === 0) {
58
+ throw new SourceError("coingecko", `No historical data for symbol "${symbol}"`);
59
+ }
60
+ const fromMs = toMs(query.from);
61
+ const toMsValue = toMs(query.to);
62
+ // CoinGecko's free OHLC endpoint doesn't expose volume, unlike its exchange-native counterparts.
63
+ const candles = rows
64
+ .map((row) => ({
65
+ timestamp: Number(row[0]),
66
+ open: Number(row[1]),
67
+ high: Number(row[2]),
68
+ low: Number(row[3]),
69
+ close: Number(row[4]),
70
+ }))
71
+ .filter((candle) => candle.timestamp >= fromMs && candle.timestamp <= toMsValue);
72
+ return { symbol: symbol.toUpperCase(), assetClass: "crypto", source: "coingecko", candles };
73
+ },
74
+ };
75
+ }
76
+ //# sourceMappingURL=coinGecko.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coinGecko.js","sourceRoot":"","sources":["../../src/sources/coinGecko.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { FetchLike, QuoteSource } from "../types.js";
2
+ export declare function createCoinbaseSource(fetchImpl?: FetchLike): QuoteSource;
3
+ //# sourceMappingURL=coinbase.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coinbase.d.ts","sourceRoot":"","sources":["../../src/sources/coinbase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAU,MAAM,aAAa,CAAC;AAKlE,wBAAgB,oBAAoB,CAAC,SAAS,GAAE,SAAiB,GAAG,WAAW,CAkE9E"}
@@ -0,0 +1,71 @@
1
+ import { RateLimitError, SourceError, UnsupportedSymbolError } from "../errors.js";
2
+ import { CRYPTO_SYMBOLS } from "../cryptoSymbols.js";
3
+ import { toMs } from "../time.js";
4
+ export function createCoinbaseSource(fetchImpl = fetch) {
5
+ return {
6
+ name: "coinbase",
7
+ assetClasses: ["crypto"],
8
+ requiresApiKey: false,
9
+ async getQuote(symbol) {
10
+ const pair = CRYPTO_SYMBOLS[symbol.toUpperCase()]?.coinbase;
11
+ if (!pair) {
12
+ throw new UnsupportedSymbolError("coinbase", symbol);
13
+ }
14
+ const url = `https://api.coinbase.com/v2/prices/${pair}/spot`;
15
+ const response = await fetchImpl(url);
16
+ if (response.status === 429) {
17
+ throw new RateLimitError("coinbase");
18
+ }
19
+ if (!response.ok) {
20
+ throw new SourceError("coinbase", `HTTP ${response.status}`);
21
+ }
22
+ const data = await response.json();
23
+ const price = Number(data?.data?.amount);
24
+ if (!Number.isFinite(price)) {
25
+ throw new SourceError("coinbase", `No data for symbol "${symbol}"`);
26
+ }
27
+ return {
28
+ symbol: symbol.toUpperCase(),
29
+ assetClass: "crypto",
30
+ price,
31
+ currency: "USD",
32
+ timestamp: Date.now(),
33
+ source: "coinbase",
34
+ };
35
+ },
36
+ async getHistorical(symbol, query) {
37
+ const pair = CRYPTO_SYMBOLS[symbol.toUpperCase()]?.coinbase;
38
+ if (!pair) {
39
+ throw new UnsupportedSymbolError("coinbase", symbol);
40
+ }
41
+ const start = new Date(toMs(query.from)).toISOString();
42
+ const end = new Date(toMs(query.to)).toISOString();
43
+ // Public product-candles endpoint on Coinbase's Exchange API, not the spot-price endpoint.
44
+ const url = `https://api.exchange.coinbase.com/products/${pair}/candles?start=${start}&end=${end}&granularity=86400`;
45
+ const response = await fetchImpl(url);
46
+ if (response.status === 429) {
47
+ throw new RateLimitError("coinbase");
48
+ }
49
+ if (!response.ok) {
50
+ throw new SourceError("coinbase", `HTTP ${response.status}`);
51
+ }
52
+ const rows = await response.json();
53
+ if (!Array.isArray(rows) || rows.length === 0) {
54
+ throw new SourceError("coinbase", `No historical data for symbol "${symbol}"`);
55
+ }
56
+ // Rows come back as [time, low, high, open, close, volume], newest first.
57
+ const candles = rows
58
+ .map((row) => ({
59
+ timestamp: Number(row[0]) * 1000,
60
+ low: Number(row[1]),
61
+ high: Number(row[2]),
62
+ open: Number(row[3]),
63
+ close: Number(row[4]),
64
+ volume: Number(row[5]),
65
+ }))
66
+ .sort((a, b) => a.timestamp - b.timestamp);
67
+ return { symbol: symbol.toUpperCase(), assetClass: "crypto", source: "coinbase", candles };
68
+ },
69
+ };
70
+ }
71
+ //# sourceMappingURL=coinbase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coinbase.js","sourceRoot":"","sources":["../../src/sources/coinbase.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { FetchLike, QuoteSource } from "../types.js";
2
+ export declare function createFinnhubSource(fetchImpl?: FetchLike): QuoteSource;
3
+ //# sourceMappingURL=finnhub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finnhub.d.ts","sourceRoot":"","sources":["../../src/sources/finnhub.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1D,wBAAgB,mBAAmB,CAAC,SAAS,GAAE,SAAiB,GAAG,WAAW,CAgC7E"}
@@ -0,0 +1,35 @@
1
+ import { MissingApiKeyError, RateLimitError, SourceError } from "../errors.js";
2
+ export function createFinnhubSource(fetchImpl = fetch) {
3
+ return {
4
+ name: "finnhub",
5
+ assetClasses: ["stock"],
6
+ requiresApiKey: true,
7
+ async getQuote(symbol, apiKey) {
8
+ if (!apiKey) {
9
+ throw new MissingApiKeyError("finnhub");
10
+ }
11
+ const url = `https://finnhub.io/api/v1/quote?symbol=${encodeURIComponent(symbol)}&token=${apiKey}`;
12
+ const response = await fetchImpl(url);
13
+ if (response.status === 429) {
14
+ throw new RateLimitError("finnhub");
15
+ }
16
+ if (!response.ok) {
17
+ throw new SourceError("finnhub", `HTTP ${response.status}`);
18
+ }
19
+ const data = await response.json();
20
+ const price = data?.c;
21
+ if (typeof price !== "number" || price === 0) {
22
+ throw new SourceError("finnhub", `No data for symbol "${symbol}"`);
23
+ }
24
+ return {
25
+ symbol: symbol.toUpperCase(),
26
+ assetClass: "stock",
27
+ price,
28
+ currency: "USD",
29
+ timestamp: Date.now(),
30
+ source: "finnhub",
31
+ };
32
+ },
33
+ };
34
+ }
35
+ //# sourceMappingURL=finnhub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finnhub.js","sourceRoot":"","sources":["../../src/sources/finnhub.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE/E,MAAM,UAAU,mBAAmB,CAAC,YAAuB,KAAK;IAC9D,OAAO;QACL,IAAI,EAAE,SAAS;QACf,YAAY,EAAE,CAAC,OAAO,CAAC;QACvB,cAAc,EAAE,IAAI;QACpB,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM;YAC3B,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,MAAM,IAAI,kBAAkB,CAAC,SAAS,CAAC,CAAC;YAC1C,CAAC;YACD,MAAM,GAAG,GAAG,0CAA0C,kBAAkB,CAAC,MAAM,CAAC,UAAU,MAAM,EAAE,CAAC;YACnG,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;YACtC,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAC5B,MAAM,IAAI,cAAc,CAAC,SAAS,CAAC,CAAC;YACtC,CAAC;YACD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,WAAW,CAAC,SAAS,EAAE,QAAQ,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;YAC9D,CAAC;YACD,MAAM,IAAI,GAAQ,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACxC,MAAM,KAAK,GAAG,IAAI,EAAE,CAAC,CAAC;YACtB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBAC7C,MAAM,IAAI,WAAW,CAAC,SAAS,EAAE,uBAAuB,MAAM,GAAG,CAAC,CAAC;YACrE,CAAC;YACD,OAAO;gBACL,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE;gBAC5B,UAAU,EAAE,OAAO;gBACnB,KAAK;gBACL,QAAQ,EAAE,KAAK;gBACf,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;gBACrB,MAAM,EAAE,SAAS;aAClB,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { FetchLike, QuoteSource } from "../types.js";
2
+ export declare function createKrakenSource(fetchImpl?: FetchLike): QuoteSource;
3
+ //# sourceMappingURL=kraken.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kraken.d.ts","sourceRoot":"","sources":["../../src/sources/kraken.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAU,MAAM,aAAa,CAAC;AAKlE,wBAAgB,kBAAkB,CAAC,SAAS,GAAE,SAAiB,GAAG,WAAW,CA0E5E"}
@@ -0,0 +1,79 @@
1
+ import { RateLimitError, SourceError, UnsupportedSymbolError } from "../errors.js";
2
+ import { CRYPTO_SYMBOLS } from "../cryptoSymbols.js";
3
+ import { toMs, toSeconds } from "../time.js";
4
+ export function createKrakenSource(fetchImpl = fetch) {
5
+ return {
6
+ name: "kraken",
7
+ assetClasses: ["crypto"],
8
+ requiresApiKey: false,
9
+ async getQuote(symbol) {
10
+ const pair = CRYPTO_SYMBOLS[symbol.toUpperCase()]?.kraken;
11
+ if (!pair) {
12
+ throw new UnsupportedSymbolError("kraken", symbol);
13
+ }
14
+ const url = `https://api.kraken.com/0/public/Ticker?pair=${pair}`;
15
+ const response = await fetchImpl(url);
16
+ if (response.status === 429) {
17
+ throw new RateLimitError("kraken");
18
+ }
19
+ if (!response.ok) {
20
+ throw new SourceError("kraken", `HTTP ${response.status}`);
21
+ }
22
+ const data = await response.json();
23
+ if (Array.isArray(data?.error) && data.error.length > 0) {
24
+ throw new SourceError("kraken", data.error.join("; "));
25
+ }
26
+ const result = data?.result ? Object.values(data.result)[0] : undefined;
27
+ const price = result ? Number(result.c?.[0]) : NaN;
28
+ if (!Number.isFinite(price)) {
29
+ throw new SourceError("kraken", `No data for symbol "${symbol}"`);
30
+ }
31
+ return {
32
+ symbol: symbol.toUpperCase(),
33
+ assetClass: "crypto",
34
+ price,
35
+ currency: "USD",
36
+ timestamp: Date.now(),
37
+ source: "kraken",
38
+ };
39
+ },
40
+ async getHistorical(symbol, query) {
41
+ const pair = CRYPTO_SYMBOLS[symbol.toUpperCase()]?.kraken;
42
+ if (!pair) {
43
+ throw new UnsupportedSymbolError("kraken", symbol);
44
+ }
45
+ const since = toSeconds(query.from);
46
+ // Kraken's OHLC endpoint has no "until" param; interval=1440 is daily candles.
47
+ const url = `https://api.kraken.com/0/public/OHLC?pair=${pair}&interval=1440&since=${since}`;
48
+ const response = await fetchImpl(url);
49
+ if (response.status === 429) {
50
+ throw new RateLimitError("kraken");
51
+ }
52
+ if (!response.ok) {
53
+ throw new SourceError("kraken", `HTTP ${response.status}`);
54
+ }
55
+ const data = await response.json();
56
+ if (Array.isArray(data?.error) && data.error.length > 0) {
57
+ throw new SourceError("kraken", data.error.join("; "));
58
+ }
59
+ const resultKey = data?.result ? Object.keys(data.result).find((key) => key !== "last") : undefined;
60
+ const rows = resultKey ? data.result[resultKey] : [];
61
+ if (rows.length === 0) {
62
+ throw new SourceError("kraken", `No historical data for symbol "${symbol}"`);
63
+ }
64
+ const toMsValue = toMs(query.to);
65
+ const candles = rows
66
+ .map((row) => ({
67
+ timestamp: Number(row[0]) * 1000,
68
+ open: Number(row[1]),
69
+ high: Number(row[2]),
70
+ low: Number(row[3]),
71
+ close: Number(row[4]),
72
+ volume: Number(row[6]),
73
+ }))
74
+ .filter((candle) => candle.timestamp <= toMsValue);
75
+ return { symbol: symbol.toUpperCase(), assetClass: "crypto", source: "kraken", candles };
76
+ },
77
+ };
78
+ }
79
+ //# sourceMappingURL=kraken.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kraken.js","sourceRoot":"","sources":["../../src/sources/kraken.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { FetchLike, QuoteSource } from "../types.js";
2
+ export declare function createStooqSource(fetchImpl?: FetchLike): QuoteSource;
3
+ //# sourceMappingURL=stooq.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stooq.d.ts","sourceRoot":"","sources":["../../src/sources/stooq.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAU,MAAM,aAAa,CAAC;AAQlE,wBAAgB,iBAAiB,CAAC,SAAS,GAAE,SAAiB,GAAG,WAAW,CA8E3E"}