@lynx-crypto/kraken-api 0.2.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
@@ -8,10 +8,11 @@
8
8
  [![last commit](https://img.shields.io/github/last-commit/lynx-laboratory/kraken-api?branch=master)](https://github.com/lynx-laboratory/kraken-api/commits/master)
9
9
  [![license](https://img.shields.io/github/license/lynx-laboratory/kraken-api)](./LICENSE.md)
10
10
 
11
- TypeScript client for **Kraken SPOT**:
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
13
  - **REST API** (public + private endpoints)
14
14
  - **WebSocket v2** (public market-data + authenticated user-data/trading)
15
+ - **Bulk historical CSV datasets** (OHLCVT + Trades) via Kraken’s official Google Drive downloads
15
16
 
16
17
  See [Kraken Official Documentation](https://docs.kraken.com/api/docs/category/guides)
17
18
 
@@ -22,85 +23,329 @@ See [Kraken Official Documentation](https://docs.kraken.com/api/docs/category/gu
22
23
 
23
24
  ---
24
25
 
25
- ## Install
26
+ ## Features
26
27
 
27
- NPM:
28
- `npm i @lynx-crypto/kraken-api`
28
+ ### Spot REST API
29
29
 
30
- Yarn:
31
- `yarn add @lynx-crypto/kraken-api`
30
+ - Public market data (time, assets, pairs, ticker, OHLC, order book, trades, spreads)
31
+ - Private account data (balances, orders, trades, ledgers, export reports)
32
+ - Trading endpoints (add/amend/edit/cancel orders, batch operations)
33
+ - Funding endpoints (deposits, withdrawals, transfers)
34
+ - Earn endpoints (strategies, allocations, status)
35
+ - Subaccounts (master account operations)
36
+ - Transparency endpoints (pre-trade / post-trade data)
32
37
 
33
- pnpm:
34
- `pnpm add @lynx-crypto/kraken-api`
38
+ ### Spot WebSocket v2 API
35
39
 
36
- ### Node support
40
+ - Public streams (ticker, trades, book, OHLC, instruments)
41
+ - Private user streams (balances, executions)
42
+ - Private trading RPC methods
43
+ - Automatic request/response correlation (`req_id`)
44
+ - Optional auto-reconnect with backoff
45
+ - Works in Node.js (via `ws`) or browser environments
37
46
 
38
- - Node >= 18 recommended (uses built-in fetch / AbortController)
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)
39
65
 
40
66
  ---
41
67
 
42
- ## Quick start
68
+ ## Installation
43
69
 
44
- ESM:
70
+ Using Yarn:
45
71
 
46
- ```ts
47
- import {
48
- KrakenSpotRestClient,
49
- KrakenSpotWebsocketV2Client,
50
- } from '@lynx-crypto/kraken-api';
51
- ```
72
+ '''sh
73
+ yarn add @lynx-crypto/kraken-api
74
+ '''
52
75
 
53
- CJS:
76
+ Using npm:
54
77
 
55
- ```js
56
- const {
57
- KrakenSpotRestClient,
58
- KrakenSpotWebsocketV2Client,
59
- } = require('@lynx-crypto/kraken-api');
60
- ```
78
+ '''sh
79
+ npm install @lynx-crypto/kraken-api
80
+ '''
61
81
 
62
82
  ---
63
83
 
64
- ## REST (Spot)
84
+ ## Requirements
65
85
 
66
- ### Create a REST client
86
+ - Node.js 18+ recommended
87
+ - TypeScript 4.9+ recommended
88
+ - For private endpoints:
89
+ - Kraken API key and secret
90
+ - Appropriate permissions enabled in Kraken
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)
67
94
 
68
- ```ts
69
- const kraken = new KrakenSpotRestClient({
70
- // Optional:
71
- // baseUrl: "https://api.kraken.com",
72
- // timeoutMs: 10_000,
73
- // userAgent: "my-app/1.0.0",
95
+ ---
96
+
97
+ ## Environment Variables (for private usage)
98
+
99
+ '''bash
100
+ KRAKEN_API_KEY="your_api_key"
101
+ KRAKEN_API_SECRET="your_api_secret"
102
+ '''
103
+
104
+ ## Environment Variables (optional, for bulk downloads)
105
+
106
+ '''bash
74
107
 
75
- // Required for private endpoints:
76
- apiKey: process.env.KRAKEN_API_KEY,
77
- apiSecret: process.env.KRAKEN_API_SECRET,
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
+ '''
114
+
115
+ ---
78
116
 
79
- // Optional logger:
80
- // logger: console,
117
+ ## Package Exports
118
+
119
+ ### REST
120
+
121
+ - `KrakenSpotRestClient`
122
+
123
+ Sub-APIs available on the client:
124
+
125
+ - `marketData`
126
+ - `accountData`
127
+ - `trading`
128
+ - `funding`
129
+ - `subaccounts`
130
+ - `earn`
131
+ - `transparency`
132
+
133
+ ### WebSocket
134
+
135
+ - `KrakenSpotWebsocketV2Client`
136
+ - `KrakenWebsocketBase` (advanced / custom integrations)
137
+
138
+ Typed channel namespaces:
139
+
140
+ - `admin`
141
+ - `marketData`
142
+ - `userData`
143
+ - `userTrading`
144
+
145
+ ### Bulk
146
+
147
+ - `KrakenBulkClient`
148
+
149
+ All public request/response and stream payloads are exported as TypeScript types.
150
+
151
+ ---
152
+
153
+ ## REST Usage
154
+
155
+ ### Public REST example (no authentication)
156
+
157
+ '''ts
158
+ import { KrakenSpotRestClient } from '@lynx-crypto/kraken-api';
81
159
 
82
- // Optional rate limiting:
83
- // rateLimit: { mode: "auto" },
160
+ async function main() {
161
+ const kraken = new KrakenSpotRestClient({
162
+ userAgent: 'example-app/1.0.0',
84
163
  });
85
- ```
86
164
 
87
- ### Public endpoint example
165
+ const time = await kraken.marketData.getServerTime();
166
+ console.log('Kraken time:', time.rfc1123);
167
+ }
88
168
 
89
- (Exact public endpoints depend on what you’ve implemented under src/spot/rest.)
169
+ main().catch((err) => {
170
+ console.error(err);
171
+ process.exitCode = 1;
172
+ });
173
+ '''
90
174
 
91
- ```ts
92
- const serverTime = await kraken.public.getServerTime();
93
- console.log(serverTime);
94
- ```
175
+ ### Private REST example (authenticated)
95
176
 
96
- ### Private endpoint example
177
+ '''ts
178
+ import 'dotenv/config';
179
+ import { KrakenSpotRestClient } from '@lynx-crypto/kraken-api';
97
180
 
98
- (Exact private endpoints depend on what you’ve implemented under src/spot/rest.)
181
+ function requireEnv(name: string): string {
182
+ const v = process.env[name];
183
+ if (!v) throw new Error(`Missing env var: ${name}`);
184
+ return v;
185
+ }
186
+
187
+ async function main() {
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
+ });
99
193
 
100
- ```ts
101
194
  const balances = await kraken.accountData.getAccountBalance();
102
- console.log('USD:', balances['ZUSD']);
103
- ```
195
+ console.log('Asset count:', Object.keys(balances).length);
196
+ }
197
+
198
+ main().catch((err) => {
199
+ console.error(err);
200
+ process.exitCode = 1;
201
+ });
202
+ '''
203
+
204
+ ---
205
+
206
+ ## WebSocket v2 Usage
207
+
208
+ ### Public WebSocket (market data)
209
+
210
+ '''ts
211
+ import { KrakenSpotWebsocketV2Client } from '@lynx-crypto/kraken-api';
212
+
213
+ async function main() {
214
+ const wsClient = new KrakenSpotWebsocketV2Client({
215
+ autoReconnect: false,
216
+ });
217
+
218
+ await wsClient.publicConnection.connect();
219
+
220
+ const ack = await wsClient.marketData.subscribeTrade(
221
+ { symbol: ['BTC/USD'], snapshot: true },
222
+ { reqId: 1 },
223
+ );
224
+
225
+ if (!ack.success) {
226
+ throw new Error(`Subscribe failed: ${ack.error}`);
227
+ }
228
+
229
+ const unsubscribe = wsClient.publicConnection.addMessageHandler((msg) => {
230
+ console.log(JSON.stringify(msg));
231
+ });
232
+
233
+ setTimeout(() => {
234
+ unsubscribe();
235
+ wsClient.publicConnection.close(1000, 'example done');
236
+ }, 20_000);
237
+ }
238
+
239
+ main().catch((err) => {
240
+ console.error(err);
241
+ process.exitCode = 1;
242
+ });
243
+ '''
244
+
245
+ ### Private WebSocket (balances / executions)
246
+
247
+ Authenticated WebSocket usage requires a token obtained via REST.
248
+
249
+ '''ts
250
+ import 'dotenv/config';
251
+ import {
252
+ KrakenSpotRestClient,
253
+ KrakenSpotWebsocketV2Client,
254
+ } from '@lynx-crypto/kraken-api';
255
+
256
+ function requireEnv(name: string): string {
257
+ const v = process.env[name];
258
+ if (!v) throw new Error(`Missing env var: ${name}`);
259
+ return v;
260
+ }
261
+
262
+ async function main() {
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
+ });
268
+
269
+ const { token } = await rest.trading.getWebSocketsToken();
270
+
271
+ const wsClient = new KrakenSpotWebsocketV2Client({
272
+ authToken: token,
273
+ autoReconnect: false,
274
+ });
275
+
276
+ await wsClient.privateConnection.connect();
277
+
278
+ const ack = await wsClient.userData.subscribeBalances({}, { reqId: 10 });
279
+
280
+ if (!ack.success) {
281
+ throw new Error(`Subscribe failed: ${ack.error}`);
282
+ }
283
+
284
+ const off = wsClient.privateConnection.addMessageHandler((msg) => {
285
+ console.log(JSON.stringify(msg));
286
+ });
287
+
288
+ setTimeout(() => {
289
+ off();
290
+ wsClient.privateConnection.close(1000, 'example done');
291
+ }, 30_000);
292
+ }
293
+
294
+ main().catch((err) => {
295
+ console.error(err);
296
+ process.exitCode = 1;
297
+ });
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).
104
349
 
105
350
  ---
106
351
 
@@ -117,27 +362,27 @@ This library supports Kraken-style rate limiting with optional automatic retries
117
362
 
118
363
  Example:
119
364
 
120
- ```ts
365
+ '''ts
121
366
  const kraken = new KrakenSpotRestClient({
122
- apiKey: process.env.KRAKEN_API_KEY,
123
- apiSecret: process.env.KRAKEN_API_SECRET,
124
- rateLimit: {
125
- mode: 'auto',
126
- tier: 'starter',
127
- retryOnRateLimit: true,
128
- maxRetries: 5,
129
- // restCostFn: (path) => (path.includes("Ledgers") ? 2 : 1),
130
- },
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
+ },
131
376
  });
132
- ```
377
+ '''
133
378
 
134
379
  Disable built-in throttling:
135
380
 
136
- ```ts
381
+ '''ts
137
382
  rateLimit: {
138
- mode: 'off';
383
+ mode: 'off',
139
384
  }
140
- ```
385
+ '''
141
386
 
142
387
  ### Redis rate limiting (multi-process / multi-container)
143
388
 
@@ -147,7 +392,7 @@ For cross-process coordination, you can use the Redis-backed token bucket limite
147
392
 
148
393
  Example (you provide the Redis client + EVAL wrapper):
149
394
 
150
- ```ts
395
+ '''ts
151
396
  import { KrakenSpotRestClient } from '@lynx-crypto/kraken-api';
152
397
  import { RedisTokenBucketLimiter } from '@lynx-crypto/kraken-api/base/redisRateLimit';
153
398
 
@@ -155,26 +400,26 @@ import { RedisTokenBucketLimiter } from '@lynx-crypto/kraken-api/base/redisRateL
155
400
  // - 0 means "proceed now"
156
401
  // - >0 means "wait this many ms then retry"
157
402
  const evalRedis = async (
158
- key: string,
159
- maxCounter: number,
160
- decayPerSec: number,
161
- cost: number,
162
- ttlSeconds: number,
163
- minWaitMs: number,
403
+ key: string,
404
+ maxCounter: number,
405
+ decayPerSec: number,
406
+ cost: number,
407
+ ttlSeconds: number,
408
+ minWaitMs: number,
164
409
  ): Promise<number> => {
165
- // Example shape (pseudo-code):
166
- // return await redis.eval(luaScript, { keys: [key], arguments: [maxCounter, decayPerSec, cost, ttlSeconds, minWaitMs] });
167
- return 0;
410
+ // Example shape (pseudo-code):
411
+ // return await redis.eval(luaScript, { keys: [key], arguments: [maxCounter, decayPerSec, cost, ttlSeconds, minWaitMs] });
412
+ return 0;
168
413
  };
169
414
 
170
415
  const kraken = new KrakenSpotRestClient({
171
- apiKey: process.env.KRAKEN_API_KEY,
172
- apiSecret: process.env.KRAKEN_API_SECRET,
173
- rateLimit: {
174
- mode: 'auto',
175
- tier: 'starter',
176
- retryOnRateLimit: true,
177
- 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,
178
423
 
179
424
  // Cross-process limiter (Redis):
180
425
  redis: {
@@ -187,9 +432,10 @@ const kraken = new KrakenSpotRestClient({
187
432
  evalRedis,
188
433
  }),
189
434
  },
190
- },
435
+
436
+ },
191
437
  });
192
- ```
438
+ '''
193
439
 
194
440
  Notes:
195
441
 
@@ -198,142 +444,40 @@ Notes:
198
444
 
199
445
  ---
200
446
 
201
- ## WebSocket v2 (Spot)
447
+ ## Examples
202
448
 
203
- This package provides a top-level v2 WS client that creates:
449
+ The repository includes a comprehensive [examples](./examples) directory:
204
450
 
205
- - a public connection (market data + admin)
206
- - a private/auth connection (user-data + user-trading)
451
+ - REST (public + safe private endpoints)
452
+ - WebSocket v2 public streams
453
+ - WebSocket v2 private streams (balances, executions)
454
+ - Bulk historical data (OHLCVT + Trades): download, extract, list pairs/intervals, query
207
455
 
208
- ### Create a WS v2 client
456
+ Bulk examples live under:
209
457
 
210
- ```ts
211
- const ws = new KrakenSpotWebsocketV2Client({
212
- // publicUrl: "wss://ws.kraken.com/v2",
213
- // privateUrl: "wss://ws-auth.kraken.com/v2",
458
+ - [./examples/bulk/ohlcvt](./examples/bulk/ohlcvt)
459
+ - [./examples/bulk/trades](./examples/bulk/trades)
214
460
 
215
- authToken: process.env.KRAKEN_WS_AUTH_TOKEN,
461
+ A minimal bulk flow:
216
462
 
217
- // autoReconnect: true,
218
- // reconnectDelayMs: 1_000,
219
- // requestTimeoutMs: 10_000,
463
+ - Download (or manually place ZIPs) → Extract → Query
220
464
 
221
- // logger: console,
222
- // WebSocketImpl: WebSocket,
223
- });
224
- ```
225
-
226
- Available sub-APIs:
227
-
228
- - `ws.admin`
229
- - `ws.marketData`
230
- - `ws.userData`
231
- - `ws.userTrading`
232
-
233
- ### Connect
234
-
235
- ```ts
236
- await ws.publicConnection.connect();
237
- await ws.privateConnection.connect();
238
- ```
239
-
240
- ---
241
-
242
- ## WS routing: receiving streaming messages
243
-
244
- ```ts
245
- const unsubscribe = ws.publicConnection.addMessageHandler((msg) => {
246
- // route by msg.channel / msg.type
247
- });
248
-
249
- // later
250
- unsubscribe();
251
- ```
252
-
253
- ---
254
-
255
- ## WS v2: Admin
256
-
257
- ```ts
258
- const pong = await ws.admin.ping({ reqId: 123 });
259
- if (!pong.success) console.error('ping failed:', pong.error);
260
- ```
261
-
262
- ---
263
-
264
- ## WS v2: User Data (authenticated)
265
-
266
- Implemented channels:
267
-
268
- - `executions`
269
- - `balances`
270
-
271
- ### Executions example
272
-
273
- ```ts
274
- const ack = await ws.userData.subscribeExecutions({
275
- snap_trades: true,
276
- snap_orders: true,
277
- order_status: true,
278
- });
279
- ```
280
-
281
- ```ts
282
- ws.privateConnection.addMessageHandler((msg) => {
283
- if (msg?.channel === 'executions') {
284
- for (const report of msg.data ?? []) {
285
- console.log(report.order_id, report.order_status);
286
- }
287
- }
288
- });
289
- ```
290
-
291
- ### Balances example
465
+ Examples are designed to be:
292
466
 
293
- ```ts
294
- const ack2 = await ws.userData.subscribeBalances({ snapshot: true });
295
- ```
296
-
297
- ```ts
298
- ws.privateConnection.addMessageHandler((msg) => {
299
- if (msg?.channel === 'balances') {
300
- console.log(msg.data);
301
- }
302
- });
303
- ```
467
+ - Runnable
468
+ - Safe by default
469
+ - Self-contained
470
+ - Easy to copy into your own project
304
471
 
305
472
  ---
306
473
 
307
- ## WS v2: User Trading (authenticated RPC)
308
-
309
- Implemented RPCs:
310
-
311
- - `add_order`
312
- - `amend_order`
313
- - `cancel_order`
314
- - `cancel_all`
315
- - `cancel_all_orders_after`
316
- - `batch_add`
317
- - `batch_cancel`
318
-
319
- Add order:
320
-
321
- ```ts
322
- const res = await ws.userTrading.addOrder({
323
- order_type: 'limit',
324
- side: 'buy',
325
- symbol: 'BTC/USD',
326
- order_qty: 0.01,
327
- limit_price: 30000,
328
- time_in_force: 'gtc',
329
- });
330
- ```
331
-
332
- Dead Man’s Switch:
474
+ ## Design Philosophy
333
475
 
334
- ```ts
335
- await ws.userTrading.cancelAllOrdersAfter({ timeout: 60 });
336
- ```
476
+ - Explicit APIs over magic
477
+ - Strong typing without sacrificing usability
478
+ - Clear separation of REST vs WebSocket concerns
479
+ - No hidden background workers
480
+ - Safe defaults, especially for trading operations
337
481
 
338
482
  ---
339
483