nodejs-order-book 8.1.0 → 9.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,9 +9,10 @@
9
9
 
10
10
  # Node.js Order Book
11
11
 
12
- Ultra-fast Node.js Order Book written in TypeScript for high-frequency trading (HFT) :rocket::rocket:
13
-
12
+ <p align="center">
13
+ Ultra-fast Node.js Order Book written in TypeScript </br> for high-frequency trading (HFT) :rocket::rocket: </br></br>
14
14
  :star: Star me on GitHub — it motivates me a lot!
15
+ </p>
15
16
 
16
17
  ## Table of Contents
17
18
 
@@ -42,15 +43,16 @@ Ultra-fast Node.js Order Book written in TypeScript for high-frequency trading (
42
43
  - [License](#license)
43
44
 
44
45
  ## Features
46
+ > Initially ported from [Go orderbook](https://github.com/i25959341/orderbook), this order book has been enhanced with new features
45
47
 
46
48
  - Standard price-time priority
47
49
  - Supports both market and limit orders
48
- - Supports `post-only` limit order
49
- - Supports conditional orders [**Stop Limit, Stop Market and OCO**](#conditional-orders-) <img src="https://img.shields.io/badge/Experimental-blue" alt="Experimental">
50
- - Supports time in force GTC, FOK and IOC
50
+ - Supports `post-only` limit order <img src="https://img.shields.io/badge/New-green" alt="New">
51
+ - Supports conditional orders [**Stop Limit, Stop Market and OCO**](#conditional-orders-) <img src="https://img.shields.io/badge/New-green" alt="New"> <img src="https://img.shields.io/badge/Experimental-blue" alt="Experimental">
52
+ - Supports time in force GTC, FOK and IOC <img src="https://img.shields.io/badge/New-green" alt="New">
51
53
  - Supports order cancelling
52
- - Supports order price and/or size updating
53
- - Snapshot and journaling functionalities for restoring the order book during server startup
54
+ - Supports order price and/or size updating <img src="https://img.shields.io/badge/New-green" alt="New">
55
+ - Snapshot and journaling functionalities for restoring the order book during server startup <img src="https://img.shields.io/badge/New-green" alt="New">
54
56
  - **High performance (above 300k trades per second)**
55
57
 
56
58
  **Machine:** ASUS ExpertBook, 11th Gen Intel(R) Core(TM) i7-1165G7, 2.80Ghz, 16GB RAM, Node.js v18.4.0.
@@ -385,12 +387,15 @@ A `snapshot` represents the state of the order book at a specific point in time.
385
387
 
386
388
  - `asks`: List of ask orders, each with a `price` and a list of associated `orders`.
387
389
  - `bids`: List of bid orders, each with a `price` and a list of associated `orders`.
390
+ - `stopBook`: an object with `bids` and `asks` properties related to every `StopOrder` in the orderbook.
388
391
  - `ts`: A timestamp indicating when the snapshot was taken, in Unix timestamp format.
389
392
  - `lastOp`: The id of the last operation included in the snapshot
390
393
 
391
394
  Snapshots are crucial for restoring the order book to a previous state. The orderbook can restore from a snapshot before processing any journal logs, ensuring consistency and accuracy.
392
395
  After taking the snapshot, you can safely remove all logs preceding the `lastOp` id.
393
396
 
397
+ **Note**: The snapshot of the order book returns an object containing an `array` of `bids` and `asks`, which in turn are arrays of order objects. If the snapshot is saved to the database as a `string`, make sure to pass the snapshot in its original format when initializing the order book. For example, you can achieve this by using `JSON.parse` to convert the string back into its original object form.
398
+
394
399
  ```js
395
400
  const ob = new OrderBook({ enableJournaling: true})
396
401
 
@@ -401,7 +406,7 @@ await saveLog(order.log)
401
406
  // ... after some time take a snapshot of the order book and save it on the database
402
407
 
403
408
  const snapshot = ob.snapshot()
404
- await saveSnapshot(snapshot)
409
+ await saveSnapshot(JSON.stringify(snapshot))
405
410
 
406
411
  // If you want you can safely remove all logs preceding the `lastOp` id of the snapshot, and continue to save each subsequent log to the database
407
412
  await removePreviousLogs(snapshot.lastOp)
@@ -410,7 +415,7 @@ await removePreviousLogs(snapshot.lastOp)
410
415
  const logs = await getLogs()
411
416
  const snapshot = await getSnapshot()
412
417
 
413
- const ob = new OrderBook({ snapshot, journal: log, enableJournaling: true })
418
+ const ob = new OrderBook({ snapshot: JSON.parse(snapshot), journal: log, enableJournaling: true })
414
419
  ```
415
420
 
416
421
  ### Journal Logs
package/dist/cjs/order.js CHANGED
@@ -20,6 +20,7 @@ exports.OrderFactory = exports.StopLimitOrder = exports.StopMarketOrder = export
20
20
  var node_crypto_1 = require("node:crypto");
21
21
  var errors_1 = require("./errors");
22
22
  var types_1 = require("./types");
23
+ var utils_1 = require("./utils");
23
24
  var BaseOrder = /** @class */ (function () {
24
25
  function BaseOrder(options) {
25
26
  var _a, _b;
@@ -78,7 +79,7 @@ var LimitOrder = /** @class */ (function (_super) {
78
79
  _this.toString = function () {
79
80
  return "".concat(_this._id, ":\n type: ").concat(_this.type, "\n side: ").concat(_this._side, "\n size: ").concat(_this._size, "\n origSize: ").concat(_this._origSize, "\n price: ").concat(_this._price, "\n time: ").concat(_this._time, "\n timeInForce: ").concat(_this._timeInForce, "\n makerQty: ").concat(_this._makerQty, "\n takerQty: ").concat(_this._takerQty);
80
81
  };
81
- _this.toJSON = function () { return JSON.stringify(_this.toObject()); };
82
+ _this.toJSON = function () { return (0, utils_1.safeStringify)(_this.toObject()); };
82
83
  _this.toObject = function () { return ({
83
84
  id: _this._id,
84
85
  type: _this.type,
@@ -178,7 +179,7 @@ var StopMarketOrder = /** @class */ (function (_super) {
178
179
  _this.toString = function () {
179
180
  return "".concat(_this._id, ":\n type: ").concat(_this.type, "\n side: ").concat(_this._side, "\n size: ").concat(_this._size, "\n stopPrice: ").concat(_this._stopPrice, "\n time: ").concat(_this._time);
180
181
  };
181
- _this.toJSON = function () { return JSON.stringify(_this.toObject()); };
182
+ _this.toJSON = function () { return (0, utils_1.safeStringify)(_this.toObject()); };
182
183
  _this.toObject = function () { return ({
183
184
  id: _this._id,
184
185
  type: _this.type,
@@ -216,9 +217,9 @@ var StopLimitOrder = /** @class */ (function (_super) {
216
217
  var _a;
217
218
  var _this = _super.call(this, options) || this;
218
219
  _this.toString = function () {
219
- return "".concat(_this._id, ":\n type: ").concat(_this.type, "\n side: ").concat(_this._side, "\n size: ").concat(_this._size, "\n price: ").concat(_this._price, "\n stopPrice: ").concat(_this._stopPrice, "\n timeInForce: ").concat(_this._timeInForce, "\n time: ").concat(_this._time);
220
+ return "".concat(_this._id, ":\n type: ").concat(_this.type, "\n side: ").concat(_this._side, "\n size: ").concat(_this._size, "\n price: ").concat(_this._price, "\n stopPrice: ").concat(_this._stopPrice, "\n\tisOCO: ").concat(_this.isOCO, "\n timeInForce: ").concat(_this._timeInForce, "\n time: ").concat(_this._time);
220
221
  };
221
- _this.toJSON = function () { return JSON.stringify(_this.toObject()); };
222
+ _this.toJSON = function () { return (0, utils_1.safeStringify)(_this.toObject()); };
222
223
  _this.toObject = function () { return ({
223
224
  id: _this._id,
224
225
  type: _this.type,
@@ -226,6 +227,7 @@ var StopLimitOrder = /** @class */ (function (_super) {
226
227
  size: _this._size,
227
228
  price: _this._price,
228
229
  stopPrice: _this._stopPrice,
230
+ isOCO: _this.isOCO,
229
231
  timeInForce: _this._timeInForce,
230
232
  time: _this._time,
231
233
  }); };
@@ -190,7 +190,8 @@ var OrderBook = /** @class */ (function () {
190
190
  * @returns The order if exists or `undefined`
191
191
  */
192
192
  this.order = function (orderID) {
193
- return _this.orders[orderID];
193
+ var _a;
194
+ return (_a = _this.orders[orderID]) === null || _a === void 0 ? void 0 : _a.toObject();
194
195
  };
195
196
  // Returns price levels and volume at price level
196
197
  this.depth = function () {
@@ -246,12 +247,13 @@ var OrderBook = /** @class */ (function () {
246
247
  var bids = [];
247
248
  var asks = [];
248
249
  _this.bids.priceTree().forEach(function (price, orders) {
249
- bids.push({ price: price, orders: orders.toArray() });
250
+ bids.push({ price: price, orders: orders.toArray().map(function (o) { return o.toObject(); }) });
250
251
  });
251
252
  _this.asks.priceTree().forEach(function (price, orders) {
252
- asks.push({ price: price, orders: orders.toArray() });
253
+ asks.push({ price: price, orders: orders.toArray().map(function (o) { return o.toObject(); }) });
253
254
  });
254
- return { bids: bids, asks: asks, ts: Date.now(), lastOp: _this._lastOp };
255
+ var stopBook = _this.stopBook.snapshot();
256
+ return { bids: bids, asks: asks, stopBook: stopBook, ts: Date.now(), lastOp: _this._lastOp };
255
257
  };
256
258
  this._market = function (options, incomingResponse) {
257
259
  var response = incomingResponse !== null && incomingResponse !== void 0 ? incomingResponse : _this.validateMarketOrder(options);
@@ -336,7 +338,7 @@ var OrderBook = /** @class */ (function () {
336
338
  isOCO: true,
337
339
  });
338
340
  _this.stopBook.add(stopLimit);
339
- response.done.push(stopLimit);
341
+ response.done.push(stopLimit.toObject());
340
342
  }
341
343
  else {
342
344
  response.err = (0, errors_1.CustomError)(errors_1.ERROR.INVALID_CONDITIONAL_ORDER);
@@ -346,7 +348,7 @@ var OrderBook = /** @class */ (function () {
346
348
  this._stopOrder = function (stopOrder, response) {
347
349
  if (_this.stopBook.validConditionalOrder(_this._marketPrice, stopOrder)) {
348
350
  _this.stopBook.add(stopOrder);
349
- response.done.push(stopOrder);
351
+ response.done.push(stopOrder.toObject());
350
352
  }
351
353
  else {
352
354
  response.err = (0, errors_1.CustomError)(errors_1.ERROR.INVALID_CONDITIONAL_ORDER);
@@ -354,21 +356,48 @@ var OrderBook = /** @class */ (function () {
354
356
  return response;
355
357
  };
356
358
  this.restoreSnapshot = function (snapshot) {
359
+ var _a, _b, _c, _d;
357
360
  _this._lastOp = snapshot.lastOp;
358
- for (var _i = 0, _a = snapshot.bids; _i < _a.length; _i++) {
359
- var level = _a[_i];
360
- for (var _b = 0, _c = level.orders; _b < _c.length; _b++) {
361
- var order = _c[_b];
362
- _this.orders[order.id] = order;
363
- _this.bids.append(order);
364
- }
365
- }
366
- for (var _d = 0, _e = snapshot.asks; _d < _e.length; _d++) {
367
- var level = _e[_d];
361
+ for (var _i = 0, _e = snapshot.bids; _i < _e.length; _i++) {
362
+ var level = _e[_i];
368
363
  for (var _f = 0, _g = level.orders; _f < _g.length; _f++) {
369
364
  var order = _g[_f];
370
- _this.orders[order.id] = order;
371
- _this.asks.append(order);
365
+ var newOrder = order_1.OrderFactory.createOrder(order);
366
+ _this.orders[newOrder.id] = newOrder;
367
+ _this.bids.append(newOrder);
368
+ }
369
+ }
370
+ for (var _h = 0, _j = snapshot.asks; _h < _j.length; _h++) {
371
+ var level = _j[_h];
372
+ for (var _k = 0, _l = level.orders; _k < _l.length; _k++) {
373
+ var order = _l[_k];
374
+ var newOrder = order_1.OrderFactory.createOrder(order);
375
+ _this.orders[newOrder.id] = newOrder;
376
+ _this.asks.append(newOrder);
377
+ }
378
+ }
379
+ if (((_b = (_a = snapshot.stopBook) === null || _a === void 0 ? void 0 : _a.bids) === null || _b === void 0 ? void 0 : _b.length) > 0) {
380
+ for (var _m = 0, _o = snapshot.stopBook.bids; _m < _o.length; _m++) {
381
+ var level = _o[_m];
382
+ for (var _p = 0, _q = level.orders; _p < _q.length; _p++) {
383
+ var order = _q[_p];
384
+ // @ts-expect-error // TODO fix types
385
+ var newOrder = order_1.OrderFactory.createOrder(order);
386
+ // @ts-expect-error // TODO fix types
387
+ _this.stopBook.add(newOrder);
388
+ }
389
+ }
390
+ }
391
+ if (((_d = (_c = snapshot.stopBook) === null || _c === void 0 ? void 0 : _c.asks) === null || _d === void 0 ? void 0 : _d.length) > 0) {
392
+ for (var _r = 0, _s = snapshot.stopBook.asks; _r < _s.length; _r++) {
393
+ var level = _s[_r];
394
+ for (var _t = 0, _u = level.orders; _t < _u.length; _t++) {
395
+ var order = _u[_t];
396
+ // @ts-expect-error // TODO fix types
397
+ var newOrder = order_1.OrderFactory.createOrder(order);
398
+ // @ts-expect-error // TODO fix types
399
+ _this.stopBook.add(newOrder);
400
+ }
372
401
  }
373
402
  }
374
403
  };
@@ -379,6 +408,7 @@ var OrderBook = /** @class */ (function () {
379
408
  * @returns The removed order if exists or `undefined`
380
409
  */
381
410
  this._cancelOrder = function (orderID, internalDeletion) {
411
+ var _a, _b;
382
412
  if (internalDeletion === void 0) { internalDeletion = false; }
383
413
  var order = _this.orders[orderID];
384
414
  if (order === undefined)
@@ -386,11 +416,12 @@ var OrderBook = /** @class */ (function () {
386
416
  delete _this.orders[orderID];
387
417
  var side = order.side === types_1.Side.BUY ? _this.bids : _this.asks;
388
418
  var response = {
389
- order: side.remove(order),
419
+ order: (_a = side.remove(order)) === null || _a === void 0 ? void 0 : _a.toObject(),
390
420
  };
391
421
  // Delete OCO Order only when the delete request comes from user
392
422
  if (!internalDeletion && order.ocoStopPrice !== undefined) {
393
- response.stopOrder = _this.stopBook.remove(order.side, orderID, order.ocoStopPrice);
423
+ response.stopOrder = (_b = _this.stopBook
424
+ .remove(order.side, orderID, order.ocoStopPrice)) === null || _b === void 0 ? void 0 : _b.toObject();
394
425
  }
395
426
  if (_this.enableJournaling) {
396
427
  response.log = {
@@ -463,7 +494,7 @@ var OrderBook = /** @class */ (function () {
463
494
  order = order_1.OrderFactory.createOrder(__assign({ type: types_1.OrderType.LIMIT, id: orderID, side: side, size: quantityToTrade, origSize: size, price: price, time: Date.now(), timeInForce: timeInForce, postOnly: postOnly, takerQty: takerQty, makerQty: makerQty }, (ocoStopPrice !== undefined ? { ocoStopPrice: ocoStopPrice } : {})));
464
495
  if (response.done.length > 0) {
465
496
  response.partialQuantityProcessed = size - quantityToTrade;
466
- response.partial = order;
497
+ response.partial = order.toObject();
467
498
  }
468
499
  _this.orders[orderID] = sideToAdd.append(order);
469
500
  }
@@ -492,7 +523,7 @@ var OrderBook = /** @class */ (function () {
492
523
  takerQty: takerQty,
493
524
  makerQty: makerQty,
494
525
  });
495
- response.done.push(order);
526
+ response.done.push(order.toObject());
496
527
  }
497
528
  // If IOC order was not matched completely remove from the order book
498
529
  if (timeInForce === types_1.TimeInForce.IOC && response.quantityLeft > 0) {
@@ -538,7 +569,7 @@ var OrderBook = /** @class */ (function () {
538
569
  timeInForce: stopOrder.timeInForce,
539
570
  }, response);
540
571
  }
541
- response.activated.push(stopOrder);
572
+ response.activated.push(stopOrder.toObject());
542
573
  });
543
574
  }
544
575
  };
@@ -650,10 +681,11 @@ var OrderBook = /** @class */ (function () {
650
681
  var headOrder = orderQueue.head();
651
682
  if (headOrder !== undefined) {
652
683
  if (response.quantityLeft < headOrder.size) {
653
- response.partial = order_1.OrderFactory.createOrder(__assign(__assign({}, headOrder.toObject()), { size: headOrder.size - response.quantityLeft }));
654
- _this.orders[headOrder.id] = response.partial;
684
+ var partial = order_1.OrderFactory.createOrder(__assign(__assign({}, headOrder.toObject()), { size: headOrder.size - response.quantityLeft }));
685
+ response.partial = partial.toObject();
686
+ _this.orders[headOrder.id] = partial;
655
687
  response.partialQuantityProcessed = response.quantityLeft;
656
- orderQueue.update(headOrder, response.partial);
688
+ orderQueue.update(headOrder, partial);
657
689
  response.quantityLeft = 0;
658
690
  }
659
691
  else {
@@ -57,6 +57,17 @@ var StopBook = /** @class */ (function () {
57
57
  }
58
58
  return response;
59
59
  };
60
+ this.snapshot = function () {
61
+ var bids = [];
62
+ var asks = [];
63
+ _this.bids.priceTree().forEach(function (price, orders) {
64
+ bids.push({ price: price, orders: orders.toArray().map(function (o) { return o.toObject(); }) });
65
+ });
66
+ _this.asks.priceTree().forEach(function (price, orders) {
67
+ asks.push({ price: price, orders: orders.toArray().map(function (o) { return o.toObject(); }) });
68
+ });
69
+ return { bids: bids, asks: asks };
70
+ };
60
71
  this.bids = new stopside_1.StopSide(types_1.Side.BUY);
61
72
  this.asks = new stopside_1.StopSide(types_1.Side.SELL);
62
73
  }
@@ -44,6 +44,9 @@ var StopQueue = /** @class */ (function () {
44
44
  }
45
45
  return deletedOrder;
46
46
  };
47
+ this.toArray = function () {
48
+ return _this._orders.toArray();
49
+ };
47
50
  this._price = price;
48
51
  this._orders = new denque_1.default();
49
52
  }
@@ -65,6 +65,9 @@ var StopSide = /** @class */ (function () {
65
65
  upperBound);
66
66
  return queues;
67
67
  };
68
+ this.priceTree = function () {
69
+ return _this._priceTree;
70
+ };
68
71
  var compare = side === types_1.Side.SELL
69
72
  ? function (a, b) { return a - b; }
70
73
  : function (a, b) { return b - a; };
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.safeParse = exports.safeStringify = void 0;
4
+ /* node:coverage ignore next - Don't know why this line is uncovered */
5
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
6
+ var safeStringify = function (value) {
7
+ try {
8
+ return JSON.stringify(value);
9
+ }
10
+ catch (error) {
11
+ return null;
12
+ }
13
+ };
14
+ exports.safeStringify = safeStringify;
15
+ /* node:coverage ignore next - Don't know why this line is uncovered */
16
+ var safeParse = function (value) {
17
+ try {
18
+ return JSON.parse(value);
19
+ }
20
+ catch (error) {
21
+ return null;
22
+ }
23
+ };
24
+ exports.safeParse = safeParse;
package/dist/esm/order.js CHANGED
@@ -17,6 +17,7 @@ var __extends = (this && this.__extends) || (function () {
17
17
  import { randomUUID } from "node:crypto";
18
18
  import { CustomError, ERROR } from "./errors";
19
19
  import { OrderType, } from "./types";
20
+ import { safeStringify } from "./utils";
20
21
  var BaseOrder = /** @class */ (function () {
21
22
  function BaseOrder(options) {
22
23
  var _a, _b;
@@ -75,7 +76,7 @@ var LimitOrder = /** @class */ (function (_super) {
75
76
  _this.toString = function () {
76
77
  return "".concat(_this._id, ":\n type: ").concat(_this.type, "\n side: ").concat(_this._side, "\n size: ").concat(_this._size, "\n origSize: ").concat(_this._origSize, "\n price: ").concat(_this._price, "\n time: ").concat(_this._time, "\n timeInForce: ").concat(_this._timeInForce, "\n makerQty: ").concat(_this._makerQty, "\n takerQty: ").concat(_this._takerQty);
77
78
  };
78
- _this.toJSON = function () { return JSON.stringify(_this.toObject()); };
79
+ _this.toJSON = function () { return safeStringify(_this.toObject()); };
79
80
  _this.toObject = function () { return ({
80
81
  id: _this._id,
81
82
  type: _this.type,
@@ -175,7 +176,7 @@ var StopMarketOrder = /** @class */ (function (_super) {
175
176
  _this.toString = function () {
176
177
  return "".concat(_this._id, ":\n type: ").concat(_this.type, "\n side: ").concat(_this._side, "\n size: ").concat(_this._size, "\n stopPrice: ").concat(_this._stopPrice, "\n time: ").concat(_this._time);
177
178
  };
178
- _this.toJSON = function () { return JSON.stringify(_this.toObject()); };
179
+ _this.toJSON = function () { return safeStringify(_this.toObject()); };
179
180
  _this.toObject = function () { return ({
180
181
  id: _this._id,
181
182
  type: _this.type,
@@ -213,9 +214,9 @@ var StopLimitOrder = /** @class */ (function (_super) {
213
214
  var _a;
214
215
  var _this = _super.call(this, options) || this;
215
216
  _this.toString = function () {
216
- return "".concat(_this._id, ":\n type: ").concat(_this.type, "\n side: ").concat(_this._side, "\n size: ").concat(_this._size, "\n price: ").concat(_this._price, "\n stopPrice: ").concat(_this._stopPrice, "\n timeInForce: ").concat(_this._timeInForce, "\n time: ").concat(_this._time);
217
+ return "".concat(_this._id, ":\n type: ").concat(_this.type, "\n side: ").concat(_this._side, "\n size: ").concat(_this._size, "\n price: ").concat(_this._price, "\n stopPrice: ").concat(_this._stopPrice, "\n\tisOCO: ").concat(_this.isOCO, "\n timeInForce: ").concat(_this._timeInForce, "\n time: ").concat(_this._time);
217
218
  };
218
- _this.toJSON = function () { return JSON.stringify(_this.toObject()); };
219
+ _this.toJSON = function () { return safeStringify(_this.toObject()); };
219
220
  _this.toObject = function () { return ({
220
221
  id: _this._id,
221
222
  type: _this.type,
@@ -223,6 +224,7 @@ var StopLimitOrder = /** @class */ (function (_super) {
223
224
  size: _this._size,
224
225
  price: _this._price,
225
226
  stopPrice: _this._stopPrice,
227
+ isOCO: _this.isOCO,
226
228
  timeInForce: _this._timeInForce,
227
229
  time: _this._time,
228
230
  }); };
@@ -187,7 +187,8 @@ var OrderBook = /** @class */ (function () {
187
187
  * @returns The order if exists or `undefined`
188
188
  */
189
189
  this.order = function (orderID) {
190
- return _this.orders[orderID];
190
+ var _a;
191
+ return (_a = _this.orders[orderID]) === null || _a === void 0 ? void 0 : _a.toObject();
191
192
  };
192
193
  // Returns price levels and volume at price level
193
194
  this.depth = function () {
@@ -243,12 +244,13 @@ var OrderBook = /** @class */ (function () {
243
244
  var bids = [];
244
245
  var asks = [];
245
246
  _this.bids.priceTree().forEach(function (price, orders) {
246
- bids.push({ price: price, orders: orders.toArray() });
247
+ bids.push({ price: price, orders: orders.toArray().map(function (o) { return o.toObject(); }) });
247
248
  });
248
249
  _this.asks.priceTree().forEach(function (price, orders) {
249
- asks.push({ price: price, orders: orders.toArray() });
250
+ asks.push({ price: price, orders: orders.toArray().map(function (o) { return o.toObject(); }) });
250
251
  });
251
- return { bids: bids, asks: asks, ts: Date.now(), lastOp: _this._lastOp };
252
+ var stopBook = _this.stopBook.snapshot();
253
+ return { bids: bids, asks: asks, stopBook: stopBook, ts: Date.now(), lastOp: _this._lastOp };
252
254
  };
253
255
  this._market = function (options, incomingResponse) {
254
256
  var response = incomingResponse !== null && incomingResponse !== void 0 ? incomingResponse : _this.validateMarketOrder(options);
@@ -333,7 +335,7 @@ var OrderBook = /** @class */ (function () {
333
335
  isOCO: true,
334
336
  });
335
337
  _this.stopBook.add(stopLimit);
336
- response.done.push(stopLimit);
338
+ response.done.push(stopLimit.toObject());
337
339
  }
338
340
  else {
339
341
  response.err = CustomError(ERROR.INVALID_CONDITIONAL_ORDER);
@@ -343,7 +345,7 @@ var OrderBook = /** @class */ (function () {
343
345
  this._stopOrder = function (stopOrder, response) {
344
346
  if (_this.stopBook.validConditionalOrder(_this._marketPrice, stopOrder)) {
345
347
  _this.stopBook.add(stopOrder);
346
- response.done.push(stopOrder);
348
+ response.done.push(stopOrder.toObject());
347
349
  }
348
350
  else {
349
351
  response.err = CustomError(ERROR.INVALID_CONDITIONAL_ORDER);
@@ -351,21 +353,48 @@ var OrderBook = /** @class */ (function () {
351
353
  return response;
352
354
  };
353
355
  this.restoreSnapshot = function (snapshot) {
356
+ var _a, _b, _c, _d;
354
357
  _this._lastOp = snapshot.lastOp;
355
- for (var _i = 0, _a = snapshot.bids; _i < _a.length; _i++) {
356
- var level = _a[_i];
357
- for (var _b = 0, _c = level.orders; _b < _c.length; _b++) {
358
- var order = _c[_b];
359
- _this.orders[order.id] = order;
360
- _this.bids.append(order);
361
- }
362
- }
363
- for (var _d = 0, _e = snapshot.asks; _d < _e.length; _d++) {
364
- var level = _e[_d];
358
+ for (var _i = 0, _e = snapshot.bids; _i < _e.length; _i++) {
359
+ var level = _e[_i];
365
360
  for (var _f = 0, _g = level.orders; _f < _g.length; _f++) {
366
361
  var order = _g[_f];
367
- _this.orders[order.id] = order;
368
- _this.asks.append(order);
362
+ var newOrder = OrderFactory.createOrder(order);
363
+ _this.orders[newOrder.id] = newOrder;
364
+ _this.bids.append(newOrder);
365
+ }
366
+ }
367
+ for (var _h = 0, _j = snapshot.asks; _h < _j.length; _h++) {
368
+ var level = _j[_h];
369
+ for (var _k = 0, _l = level.orders; _k < _l.length; _k++) {
370
+ var order = _l[_k];
371
+ var newOrder = OrderFactory.createOrder(order);
372
+ _this.orders[newOrder.id] = newOrder;
373
+ _this.asks.append(newOrder);
374
+ }
375
+ }
376
+ if (((_b = (_a = snapshot.stopBook) === null || _a === void 0 ? void 0 : _a.bids) === null || _b === void 0 ? void 0 : _b.length) > 0) {
377
+ for (var _m = 0, _o = snapshot.stopBook.bids; _m < _o.length; _m++) {
378
+ var level = _o[_m];
379
+ for (var _p = 0, _q = level.orders; _p < _q.length; _p++) {
380
+ var order = _q[_p];
381
+ // @ts-expect-error // TODO fix types
382
+ var newOrder = OrderFactory.createOrder(order);
383
+ // @ts-expect-error // TODO fix types
384
+ _this.stopBook.add(newOrder);
385
+ }
386
+ }
387
+ }
388
+ if (((_d = (_c = snapshot.stopBook) === null || _c === void 0 ? void 0 : _c.asks) === null || _d === void 0 ? void 0 : _d.length) > 0) {
389
+ for (var _r = 0, _s = snapshot.stopBook.asks; _r < _s.length; _r++) {
390
+ var level = _s[_r];
391
+ for (var _t = 0, _u = level.orders; _t < _u.length; _t++) {
392
+ var order = _u[_t];
393
+ // @ts-expect-error // TODO fix types
394
+ var newOrder = OrderFactory.createOrder(order);
395
+ // @ts-expect-error // TODO fix types
396
+ _this.stopBook.add(newOrder);
397
+ }
369
398
  }
370
399
  }
371
400
  };
@@ -376,6 +405,7 @@ var OrderBook = /** @class */ (function () {
376
405
  * @returns The removed order if exists or `undefined`
377
406
  */
378
407
  this._cancelOrder = function (orderID, internalDeletion) {
408
+ var _a, _b;
379
409
  if (internalDeletion === void 0) { internalDeletion = false; }
380
410
  var order = _this.orders[orderID];
381
411
  if (order === undefined)
@@ -383,11 +413,12 @@ var OrderBook = /** @class */ (function () {
383
413
  delete _this.orders[orderID];
384
414
  var side = order.side === Side.BUY ? _this.bids : _this.asks;
385
415
  var response = {
386
- order: side.remove(order),
416
+ order: (_a = side.remove(order)) === null || _a === void 0 ? void 0 : _a.toObject(),
387
417
  };
388
418
  // Delete OCO Order only when the delete request comes from user
389
419
  if (!internalDeletion && order.ocoStopPrice !== undefined) {
390
- response.stopOrder = _this.stopBook.remove(order.side, orderID, order.ocoStopPrice);
420
+ response.stopOrder = (_b = _this.stopBook
421
+ .remove(order.side, orderID, order.ocoStopPrice)) === null || _b === void 0 ? void 0 : _b.toObject();
391
422
  }
392
423
  if (_this.enableJournaling) {
393
424
  response.log = {
@@ -460,7 +491,7 @@ var OrderBook = /** @class */ (function () {
460
491
  order = OrderFactory.createOrder(__assign({ type: OrderType.LIMIT, id: orderID, side: side, size: quantityToTrade, origSize: size, price: price, time: Date.now(), timeInForce: timeInForce, postOnly: postOnly, takerQty: takerQty, makerQty: makerQty }, (ocoStopPrice !== undefined ? { ocoStopPrice: ocoStopPrice } : {})));
461
492
  if (response.done.length > 0) {
462
493
  response.partialQuantityProcessed = size - quantityToTrade;
463
- response.partial = order;
494
+ response.partial = order.toObject();
464
495
  }
465
496
  _this.orders[orderID] = sideToAdd.append(order);
466
497
  }
@@ -489,7 +520,7 @@ var OrderBook = /** @class */ (function () {
489
520
  takerQty: takerQty,
490
521
  makerQty: makerQty,
491
522
  });
492
- response.done.push(order);
523
+ response.done.push(order.toObject());
493
524
  }
494
525
  // If IOC order was not matched completely remove from the order book
495
526
  if (timeInForce === TimeInForce.IOC && response.quantityLeft > 0) {
@@ -535,7 +566,7 @@ var OrderBook = /** @class */ (function () {
535
566
  timeInForce: stopOrder.timeInForce,
536
567
  }, response);
537
568
  }
538
- response.activated.push(stopOrder);
569
+ response.activated.push(stopOrder.toObject());
539
570
  });
540
571
  }
541
572
  };
@@ -647,10 +678,11 @@ var OrderBook = /** @class */ (function () {
647
678
  var headOrder = orderQueue.head();
648
679
  if (headOrder !== undefined) {
649
680
  if (response.quantityLeft < headOrder.size) {
650
- response.partial = OrderFactory.createOrder(__assign(__assign({}, headOrder.toObject()), { size: headOrder.size - response.quantityLeft }));
651
- _this.orders[headOrder.id] = response.partial;
681
+ var partial = OrderFactory.createOrder(__assign(__assign({}, headOrder.toObject()), { size: headOrder.size - response.quantityLeft }));
682
+ response.partial = partial.toObject();
683
+ _this.orders[headOrder.id] = partial;
652
684
  response.partialQuantityProcessed = response.quantityLeft;
653
- orderQueue.update(headOrder, response.partial);
685
+ orderQueue.update(headOrder, partial);
654
686
  response.quantityLeft = 0;
655
687
  }
656
688
  else {
@@ -54,6 +54,17 @@ var StopBook = /** @class */ (function () {
54
54
  }
55
55
  return response;
56
56
  };
57
+ this.snapshot = function () {
58
+ var bids = [];
59
+ var asks = [];
60
+ _this.bids.priceTree().forEach(function (price, orders) {
61
+ bids.push({ price: price, orders: orders.toArray().map(function (o) { return o.toObject(); }) });
62
+ });
63
+ _this.asks.priceTree().forEach(function (price, orders) {
64
+ asks.push({ price: price, orders: orders.toArray().map(function (o) { return o.toObject(); }) });
65
+ });
66
+ return { bids: bids, asks: asks };
67
+ };
57
68
  this.bids = new StopSide(Side.BUY);
58
69
  this.asks = new StopSide(Side.SELL);
59
70
  }
@@ -38,6 +38,9 @@ var StopQueue = /** @class */ (function () {
38
38
  }
39
39
  return deletedOrder;
40
40
  };
41
+ this.toArray = function () {
42
+ return _this._orders.toArray();
43
+ };
41
44
  this._price = price;
42
45
  this._orders = new Denque();
43
46
  }
@@ -59,6 +59,9 @@ var StopSide = /** @class */ (function () {
59
59
  upperBound);
60
60
  return queues;
61
61
  };
62
+ this.priceTree = function () {
63
+ return _this._priceTree;
64
+ };
62
65
  var compare = side === Side.SELL
63
66
  ? function (a, b) { return a - b; }
64
67
  : function (a, b) { return b - a; };
@@ -0,0 +1,19 @@
1
+ /* node:coverage ignore next - Don't know why this line is uncovered */
2
+ // biome-ignore lint/suspicious/noExplicitAny: <explanation>
3
+ export var safeStringify = function (value) {
4
+ try {
5
+ return JSON.stringify(value);
6
+ }
7
+ catch (error) {
8
+ return null;
9
+ }
10
+ };
11
+ /* node:coverage ignore next - Don't know why this line is uncovered */
12
+ export var safeParse = function (value) {
13
+ try {
14
+ return JSON.parse(value);
15
+ }
16
+ catch (error) {
17
+ return null;
18
+ }
19
+ };
@@ -35,7 +35,7 @@ export declare class LimitOrder extends BaseOrder {
35
35
  get takerQty(): number;
36
36
  get ocoStopPrice(): number | undefined;
37
37
  toString: () => string;
38
- toJSON: () => string;
38
+ toJSON: () => string | null;
39
39
  toObject: () => ILimitOrder;
40
40
  }
41
41
  export declare class StopMarketOrder extends BaseOrder {
@@ -45,7 +45,7 @@ export declare class StopMarketOrder extends BaseOrder {
45
45
  get type(): OrderType.STOP_MARKET;
46
46
  get stopPrice(): number;
47
47
  toString: () => string;
48
- toJSON: () => string;
48
+ toJSON: () => string | null;
49
49
  toObject: () => IStopMarketOrder;
50
50
  }
51
51
  export declare class StopLimitOrder extends BaseOrder {
@@ -62,7 +62,7 @@ export declare class StopLimitOrder extends BaseOrder {
62
62
  get timeInForce(): TimeInForce;
63
63
  get isOCO(): boolean;
64
64
  toString: () => string;
65
- toJSON: () => string;
65
+ toJSON: () => string | null;
66
66
  toObject: () => IStopLimitOrder;
67
67
  }
68
68
  export declare const OrderFactory: {