@lynx-crypto/kraken-api 1.0.0 → 1.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/README.md CHANGED
@@ -9,12 +9,15 @@
9
9
  [![license](https://img.shields.io/github/license/lynx-laboratory/kraken-api)](./LICENSE.md)
10
10
 
11
11
  A modern, strongly-typed TypeScript client for the **Kraken Spot API**, providing both **REST** and **WebSocket v2** access in a single package.
12
+
12
13
  - **REST API** (public + private endpoints)
13
14
  - **WebSocket v2** (public market-data + authenticated user-data/trading)
15
+ - **Bulk historical CSV datasets** (OHLCVT + Trades) via Kraken’s official Google Drive downloads
14
16
 
15
17
  See [Kraken Official Documentation](https://docs.kraken.com/api/docs/category/guides)
16
18
 
17
19
  ## Important
20
+
18
21
  - This package is currently **SPOT only**. It does **not** implement Kraken Futures.
19
22
  - Unofficial project. Not affiliated with Kraken.
20
23
 
@@ -23,6 +26,7 @@ See [Kraken Official Documentation](https://docs.kraken.com/api/docs/category/gu
23
26
  ## Features
24
27
 
25
28
  ### Spot REST API
29
+
26
30
  - Public market data (time, assets, pairs, ticker, OHLC, order book, trades, spreads)
27
31
  - Private account data (balances, orders, trades, ledgers, export reports)
28
32
  - Trading endpoints (add/amend/edit/cancel orders, batch operations)
@@ -32,6 +36,7 @@ See [Kraken Official Documentation](https://docs.kraken.com/api/docs/category/gu
32
36
  - Transparency endpoints (pre-trade / post-trade data)
33
37
 
34
38
  ### Spot WebSocket v2 API
39
+
35
40
  - Public streams (ticker, trades, book, OHLC, instruments)
36
41
  - Private user streams (balances, executions)
37
42
  - Private trading RPC methods
@@ -39,21 +44,40 @@ See [Kraken Official Documentation](https://docs.kraken.com/api/docs/category/gu
39
44
  - Optional auto-reconnect with backoff
40
45
  - Works in Node.js (via `ws`) or browser environments
41
46
 
47
+ ### Bulk historical CSV datasets (OHLCVT + Trades)
48
+
49
+ Support for Kraken’s downloadable historical datasets:
50
+
51
+ - OHLCVT (open/high/low/close/volume/trades)
52
+ - Trades (time and sales)
53
+
54
+ Data source (Kraken Support):
55
+
56
+ - https://support.kraken.com/sections/360009899492-csv-data
57
+ - https://support.kraken.com/articles/360047124832-downloadable-historical-ohlcvt-open-high-low-close-volume-trades-data
58
+ - https://support.kraken.com/articles/360047543791-downloadable-historical-market-data-time-and-sales-
59
+
60
+ Bulk workflow:
61
+
62
+ - Download ZIPs (seamless via Drive API, or manually)
63
+ - Extract ZIPs into a stable on-disk layout
64
+ - Query extracted CSVs (streaming)
65
+
42
66
  ---
43
67
 
44
68
  ## Installation
45
69
 
46
70
  Using Yarn:
47
71
 
48
- ```sh
72
+ '''sh
49
73
  yarn add @lynx-crypto/kraken-api
50
- ```
74
+ '''
51
75
 
52
76
  Using npm:
53
77
 
54
- ```sh
78
+ '''sh
55
79
  npm install @lynx-crypto/kraken-api
56
- ```
80
+ '''
57
81
 
58
82
  ---
59
83
 
@@ -65,24 +89,39 @@ npm install @lynx-crypto/kraken-api
65
89
  - Kraken API key and secret
66
90
  - Appropriate permissions enabled in Kraken
67
91
  - “WebSocket interface” enabled for private WS usage
92
+ - For bulk downloads (optional):
93
+ - A Google Drive API key for seamless downloads (manual ZIP placement is supported)
68
94
 
69
95
  ---
70
96
 
71
97
  ## Environment Variables (for private usage)
72
98
 
73
- ```bash
99
+ '''bash
74
100
  KRAKEN_API_KEY="your_api_key"
75
101
  KRAKEN_API_SECRET="your_api_secret"
76
- ```
102
+ '''
103
+
104
+ ## Environment Variables (optional, for bulk downloads)
105
+
106
+ '''bash
107
+
108
+ # If set, bulk downloads can be performed via the Google Drive API.
109
+
110
+ # If not set, ZIPs can be downloaded manually and placed into the expected folders.
111
+
112
+ LYNX_CRYPTO_KRAKEN_API_GOOGLE_DRIVE_API_KEY="your_google_drive_api_key"
113
+ '''
77
114
 
78
115
  ---
79
116
 
80
117
  ## Package Exports
81
118
 
82
119
  ### REST
120
+
83
121
  - `KrakenSpotRestClient`
84
122
 
85
123
  Sub-APIs available on the client:
124
+
86
125
  - `marketData`
87
126
  - `accountData`
88
127
  - `trading`
@@ -92,68 +131,75 @@ Sub-APIs available on the client:
92
131
  - `transparency`
93
132
 
94
133
  ### WebSocket
134
+
95
135
  - `KrakenSpotWebsocketV2Client`
96
136
  - `KrakenWebsocketBase` (advanced / custom integrations)
97
137
 
98
138
  Typed channel namespaces:
139
+
99
140
  - `admin`
100
141
  - `marketData`
101
142
  - `userData`
102
143
  - `userTrading`
103
144
 
145
+ ### Bulk
146
+
147
+ - `KrakenBulkClient`
148
+
104
149
  All public request/response and stream payloads are exported as TypeScript types.
105
150
 
106
151
  ---
152
+
107
153
  ## REST Usage
108
154
 
109
155
  ### Public REST example (no authentication)
110
156
 
111
- ```ts
157
+ '''ts
112
158
  import { KrakenSpotRestClient } from '@lynx-crypto/kraken-api';
113
159
 
114
160
  async function main() {
115
- const kraken = new KrakenSpotRestClient({
116
- userAgent: 'example-app/1.0.0',
117
- });
161
+ const kraken = new KrakenSpotRestClient({
162
+ userAgent: 'example-app/1.0.0',
163
+ });
118
164
 
119
- const time = await kraken.marketData.getServerTime();
120
- console.log('Kraken time:', time.rfc1123);
165
+ const time = await kraken.marketData.getServerTime();
166
+ console.log('Kraken time:', time.rfc1123);
121
167
  }
122
168
 
123
169
  main().catch((err) => {
124
- console.error(err);
125
- process.exitCode = 1;
170
+ console.error(err);
171
+ process.exitCode = 1;
126
172
  });
127
- ```
173
+ '''
128
174
 
129
175
  ### Private REST example (authenticated)
130
176
 
131
- ```ts
177
+ '''ts
132
178
  import 'dotenv/config';
133
179
  import { KrakenSpotRestClient } from '@lynx-crypto/kraken-api';
134
180
 
135
181
  function requireEnv(name: string): string {
136
- const v = process.env[name];
137
- if (!v) throw new Error(`Missing env var: ${name}`);
138
- return v;
182
+ const v = process.env[name];
183
+ if (!v) throw new Error(`Missing env var: ${name}`);
184
+ return v;
139
185
  }
140
186
 
141
187
  async function main() {
142
- const kraken = new KrakenSpotRestClient({
143
- userAgent: 'example-app/1.0.0',
144
- apiKey: requireEnv('KRAKEN_API_KEY'),
145
- apiSecret: requireEnv('KRAKEN_API_SECRET'),
146
- });
147
-
148
- const balances = await kraken.accountData.getAccountBalance();
149
- console.log('Asset count:', Object.keys(balances).length);
188
+ const kraken = new KrakenSpotRestClient({
189
+ userAgent: 'example-app/1.0.0',
190
+ apiKey: requireEnv('KRAKEN_API_KEY'),
191
+ apiSecret: requireEnv('KRAKEN_API_SECRET'),
192
+ });
193
+
194
+ const balances = await kraken.accountData.getAccountBalance();
195
+ console.log('Asset count:', Object.keys(balances).length);
150
196
  }
151
197
 
152
198
  main().catch((err) => {
153
- console.error(err);
154
- process.exitCode = 1;
199
+ console.error(err);
200
+ process.exitCode = 1;
155
201
  });
156
- ```
202
+ '''
157
203
 
158
204
  ---
159
205
 
@@ -161,95 +207,145 @@ main().catch((err) => {
161
207
 
162
208
  ### Public WebSocket (market data)
163
209
 
164
- ```ts
210
+ '''ts
165
211
  import { KrakenSpotWebsocketV2Client } from '@lynx-crypto/kraken-api';
166
212
 
167
213
  async function main() {
168
- const wsClient = new KrakenSpotWebsocketV2Client({
169
- autoReconnect: false,
170
- });
214
+ const wsClient = new KrakenSpotWebsocketV2Client({
215
+ autoReconnect: false,
216
+ });
171
217
 
172
- await wsClient.publicConnection.connect();
218
+ await wsClient.publicConnection.connect();
173
219
 
174
- const ack = await wsClient.marketData.subscribeTrade(
175
- { symbol: ['BTC/USD'], snapshot: true },
176
- { reqId: 1 },
177
- );
220
+ const ack = await wsClient.marketData.subscribeTrade(
221
+ { symbol: ['BTC/USD'], snapshot: true },
222
+ { reqId: 1 },
223
+ );
178
224
 
179
- if (!ack.success) {
180
- throw new Error(`Subscribe failed: ${ack.error}`);
181
- }
225
+ if (!ack.success) {
226
+ throw new Error(`Subscribe failed: ${ack.error}`);
227
+ }
182
228
 
183
- const unsubscribe = wsClient.publicConnection.addMessageHandler((msg) => {
184
- console.log(JSON.stringify(msg));
185
- });
229
+ const unsubscribe = wsClient.publicConnection.addMessageHandler((msg) => {
230
+ console.log(JSON.stringify(msg));
231
+ });
186
232
 
187
- setTimeout(() => {
188
- unsubscribe();
189
- wsClient.publicConnection.close(1000, 'example done');
190
- }, 20_000);
233
+ setTimeout(() => {
234
+ unsubscribe();
235
+ wsClient.publicConnection.close(1000, 'example done');
236
+ }, 20_000);
191
237
  }
192
238
 
193
239
  main().catch((err) => {
194
- console.error(err);
195
- process.exitCode = 1;
240
+ console.error(err);
241
+ process.exitCode = 1;
196
242
  });
197
- ```
243
+ '''
198
244
 
199
245
  ### Private WebSocket (balances / executions)
200
246
 
201
247
  Authenticated WebSocket usage requires a token obtained via REST.
202
248
 
203
- ```ts
249
+ '''ts
204
250
  import 'dotenv/config';
205
251
  import {
206
- KrakenSpotRestClient,
207
- KrakenSpotWebsocketV2Client,
252
+ KrakenSpotRestClient,
253
+ KrakenSpotWebsocketV2Client,
208
254
  } from '@lynx-crypto/kraken-api';
209
255
 
210
256
  function requireEnv(name: string): string {
211
- const v = process.env[name];
212
- if (!v) throw new Error(`Missing env var: ${name}`);
213
- return v;
257
+ const v = process.env[name];
258
+ if (!v) throw new Error(`Missing env var: ${name}`);
259
+ return v;
214
260
  }
215
261
 
216
262
  async function main() {
217
- const rest = new KrakenSpotRestClient({
218
- userAgent: 'example-app/1.0.0',
219
- apiKey: requireEnv('KRAKEN_API_KEY'),
220
- apiSecret: requireEnv('KRAKEN_API_SECRET'),
221
- });
263
+ const rest = new KrakenSpotRestClient({
264
+ userAgent: 'example-app/1.0.0',
265
+ apiKey: requireEnv('KRAKEN_API_KEY'),
266
+ apiSecret: requireEnv('KRAKEN_API_SECRET'),
267
+ });
222
268
 
223
- const { token } = await rest.trading.getWebSocketsToken();
269
+ const { token } = await rest.trading.getWebSocketsToken();
224
270
 
225
- const wsClient = new KrakenSpotWebsocketV2Client({
226
- authToken: token,
227
- autoReconnect: false,
228
- });
271
+ const wsClient = new KrakenSpotWebsocketV2Client({
272
+ authToken: token,
273
+ autoReconnect: false,
274
+ });
229
275
 
230
- await wsClient.privateConnection.connect();
276
+ await wsClient.privateConnection.connect();
231
277
 
232
- const ack = await wsClient.userData.subscribeBalances({}, { reqId: 10 });
278
+ const ack = await wsClient.userData.subscribeBalances({}, { reqId: 10 });
233
279
 
234
- if (!ack.success) {
235
- throw new Error(`Subscribe failed: ${ack.error}`);
236
- }
280
+ if (!ack.success) {
281
+ throw new Error(`Subscribe failed: ${ack.error}`);
282
+ }
237
283
 
238
- const off = wsClient.privateConnection.addMessageHandler((msg) => {
239
- console.log(JSON.stringify(msg));
240
- });
284
+ const off = wsClient.privateConnection.addMessageHandler((msg) => {
285
+ console.log(JSON.stringify(msg));
286
+ });
241
287
 
242
- setTimeout(() => {
243
- off();
244
- wsClient.privateConnection.close(1000, 'example done');
245
- }, 30_000);
288
+ setTimeout(() => {
289
+ off();
290
+ wsClient.privateConnection.close(1000, 'example done');
291
+ }, 30_000);
246
292
  }
247
293
 
248
294
  main().catch((err) => {
249
- console.error(err);
250
- process.exitCode = 1;
295
+ console.error(err);
296
+ process.exitCode = 1;
251
297
  });
252
- ```
298
+ '''
299
+
300
+ ---
301
+
302
+ ## Bulk historical data (OHLCVT + Trades)
303
+
304
+ Bulk data is managed by `KrakenBulkClient`. It supports:
305
+
306
+ - Downloading complete and quarterly ZIPs
307
+ - Extracting ZIPs into a stable directory layout
308
+ - Listing available pairs (and OHLC intervals)
309
+ - Streaming query results from extracted CSVs
310
+
311
+ ### Storage layout
312
+
313
+ A `storageDir` is provided when constructing the client (examples use `.bulk-data`). The library stores ZIPs and extracted CSVs under dataset-specific folders:
314
+
315
+ '''txt
316
+ <storageDir>/
317
+ ohlcvt/
318
+ zips/
319
+ complete/complete.zip
320
+ quarterly/<YYYYQn>.zip
321
+ extracted/
322
+ complete/
323
+ quarterly/<YYYYQn>/
324
+ trades/
325
+ zips/
326
+ complete/complete.zip
327
+ quarterly/<YYYYQn>.zip
328
+ extracted/
329
+ complete/
330
+ quarterly/<YYYYQn>/
331
+ '''
332
+
333
+ Recommended:
334
+
335
+ - Add the storage directory to `.gitignore` (e.g. `.bulk-data/`).
336
+
337
+ ### Downloading ZIPs (two options)
338
+
339
+ 1. Seamless downloads (Drive API)
340
+
341
+ - Set `LYNX_CRYPTO_KRAKEN_API_GOOGLE_DRIVE_API_KEY`.
342
+ - Run the bulk download examples (see below).
343
+
344
+ 2. Manual ZIP placement (no API key)
345
+
346
+ - Download the ZIP(s) from Kraken’s support pages.
347
+ - Place the ZIP(s) into the expected `zips/complete/` or `zips/quarterly/` folders.
348
+ - Run extraction and query examples as usual (extraction/query do not require an API key).
253
349
 
254
350
  ---
255
351
 
@@ -266,27 +362,27 @@ This library supports Kraken-style rate limiting with optional automatic retries
266
362
 
267
363
  Example:
268
364
 
269
- ```ts
365
+ '''ts
270
366
  const kraken = new KrakenSpotRestClient({
271
- apiKey: process.env.KRAKEN_API_KEY,
272
- apiSecret: process.env.KRAKEN_API_SECRET,
273
- rateLimit: {
274
- mode: 'auto',
275
- tier: 'starter',
276
- retryOnRateLimit: true,
277
- maxRetries: 5,
278
- // restCostFn: (path) => (path.includes("Ledgers") ? 2 : 1),
279
- },
367
+ apiKey: process.env.KRAKEN_API_KEY,
368
+ apiSecret: process.env.KRAKEN_API_SECRET,
369
+ rateLimit: {
370
+ mode: 'auto',
371
+ tier: 'starter',
372
+ retryOnRateLimit: true,
373
+ maxRetries: 5,
374
+ // restCostFn: (path) => (path.includes("Ledgers") ? 2 : 1),
375
+ },
280
376
  });
281
- ```
377
+ '''
282
378
 
283
379
  Disable built-in throttling:
284
380
 
285
- ```ts
381
+ '''ts
286
382
  rateLimit: {
287
- mode: 'off';
383
+ mode: 'off',
288
384
  }
289
- ```
385
+ '''
290
386
 
291
387
  ### Redis rate limiting (multi-process / multi-container)
292
388
 
@@ -296,7 +392,7 @@ For cross-process coordination, you can use the Redis-backed token bucket limite
296
392
 
297
393
  Example (you provide the Redis client + EVAL wrapper):
298
394
 
299
- ```ts
395
+ '''ts
300
396
  import { KrakenSpotRestClient } from '@lynx-crypto/kraken-api';
301
397
  import { RedisTokenBucketLimiter } from '@lynx-crypto/kraken-api/base/redisRateLimit';
302
398
 
@@ -304,26 +400,26 @@ import { RedisTokenBucketLimiter } from '@lynx-crypto/kraken-api/base/redisRateL
304
400
  // - 0 means "proceed now"
305
401
  // - >0 means "wait this many ms then retry"
306
402
  const evalRedis = async (
307
- key: string,
308
- maxCounter: number,
309
- decayPerSec: number,
310
- cost: number,
311
- ttlSeconds: number,
312
- minWaitMs: number,
403
+ key: string,
404
+ maxCounter: number,
405
+ decayPerSec: number,
406
+ cost: number,
407
+ ttlSeconds: number,
408
+ minWaitMs: number,
313
409
  ): Promise<number> => {
314
- // Example shape (pseudo-code):
315
- // return await redis.eval(luaScript, { keys: [key], arguments: [maxCounter, decayPerSec, cost, ttlSeconds, minWaitMs] });
316
- return 0;
410
+ // Example shape (pseudo-code):
411
+ // return await redis.eval(luaScript, { keys: [key], arguments: [maxCounter, decayPerSec, cost, ttlSeconds, minWaitMs] });
412
+ return 0;
317
413
  };
318
414
 
319
415
  const kraken = new KrakenSpotRestClient({
320
- apiKey: process.env.KRAKEN_API_KEY,
321
- apiSecret: process.env.KRAKEN_API_SECRET,
322
- rateLimit: {
323
- mode: 'auto',
324
- tier: 'starter',
325
- retryOnRateLimit: true,
326
- maxRetries: 5,
416
+ apiKey: process.env.KRAKEN_API_KEY,
417
+ apiSecret: process.env.KRAKEN_API_SECRET,
418
+ rateLimit: {
419
+ mode: 'auto',
420
+ tier: 'starter',
421
+ retryOnRateLimit: true,
422
+ maxRetries: 5,
327
423
 
328
424
  // Cross-process limiter (Redis):
329
425
  redis: {
@@ -336,9 +432,10 @@ const kraken = new KrakenSpotRestClient({
336
432
  evalRedis,
337
433
  }),
338
434
  },
339
- },
435
+
436
+ },
340
437
  });
341
- ```
438
+ '''
342
439
 
343
440
  Notes:
344
441
 
@@ -354,9 +451,19 @@ The repository includes a comprehensive [examples](./examples) directory:
354
451
  - REST (public + safe private endpoints)
355
452
  - WebSocket v2 public streams
356
453
  - WebSocket v2 private streams (balances, executions)
357
- - Clean lifecycle examples (connect subscribe receive close)
454
+ - Bulk historical data (OHLCVT + Trades): download, extract, list pairs/intervals, query
455
+
456
+ Bulk examples live under:
457
+
458
+ - [./examples/bulk/ohlcvt](./examples/bulk/ohlcvt)
459
+ - [./examples/bulk/trades](./examples/bulk/trades)
460
+
461
+ A minimal bulk flow:
462
+
463
+ - Download (or manually place ZIPs) → Extract → Query
358
464
 
359
465
  Examples are designed to be:
466
+
360
467
  - Runnable
361
468
  - Safe by default
362
469
  - Self-contained