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 +14 -9
- package/dist/cjs/order.js +6 -4
- package/dist/cjs/orderbook.js +58 -26
- package/dist/cjs/stopbook.js +11 -0
- package/dist/cjs/stopqueue.js +3 -0
- package/dist/cjs/stopside.js +3 -0
- package/dist/cjs/utils.js +24 -0
- package/dist/esm/order.js +6 -4
- package/dist/esm/orderbook.js +58 -26
- package/dist/esm/stopbook.js +11 -0
- package/dist/esm/stopqueue.js +3 -0
- package/dist/esm/stopside.js +3 -0
- package/dist/esm/utils.js +19 -0
- package/dist/types/order.d.ts +3 -3
- package/dist/types/orderbook.d.ts +2 -3
- package/dist/types/stopbook.d.ts +11 -1
- package/dist/types/stopqueue.d.ts +1 -0
- package/dist/types/stopside.d.ts +2 -0
- package/dist/types/types.d.ts +20 -7
- package/dist/types/utils.d.ts +2 -0
- package/dist/umd/index.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
# Node.js Order Book
|
|
11
11
|
|
|
12
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
}); };
|
package/dist/cjs/orderbook.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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,
|
|
359
|
-
var level =
|
|
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
|
-
|
|
371
|
-
_this.
|
|
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 =
|
|
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
|
-
|
|
654
|
-
|
|
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,
|
|
688
|
+
orderQueue.update(headOrder, partial);
|
|
657
689
|
response.quantityLeft = 0;
|
|
658
690
|
}
|
|
659
691
|
else {
|
package/dist/cjs/stopbook.js
CHANGED
|
@@ -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
|
}
|
package/dist/cjs/stopqueue.js
CHANGED
package/dist/cjs/stopside.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
}); };
|
package/dist/esm/orderbook.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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,
|
|
356
|
-
var level =
|
|
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
|
-
|
|
368
|
-
_this.
|
|
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 =
|
|
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
|
-
|
|
651
|
-
|
|
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,
|
|
685
|
+
orderQueue.update(headOrder, partial);
|
|
654
686
|
response.quantityLeft = 0;
|
|
655
687
|
}
|
|
656
688
|
else {
|
package/dist/esm/stopbook.js
CHANGED
|
@@ -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
|
}
|
package/dist/esm/stopqueue.js
CHANGED
package/dist/esm/stopside.js
CHANGED
|
@@ -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
|
+
};
|
package/dist/types/order.d.ts
CHANGED
|
@@ -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: {
|