nodejs-order-book 10.0.1 → 10.2.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.
@@ -1,25 +1,21 @@
1
1
  "use strict";
2
- var __assign = (this && this.__assign) || function () {
3
- __assign = Object.assign || function(t) {
4
- for (var s, i = 1, n = arguments.length; i < n; i++) {
5
- s = arguments[i];
6
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
- t[p] = s[p];
8
- }
9
- return t;
10
- };
11
- return __assign.apply(this, arguments);
12
- };
13
2
  Object.defineProperty(exports, "__esModule", { value: true });
14
3
  exports.OrderBook = void 0;
15
4
  /* node:coverage ignore next - Don't know why first and last line of each file count as uncovered */
16
- var errors_1 = require("./errors");
17
- var order_1 = require("./order");
18
- var orderside_1 = require("./orderside");
19
- var stopbook_1 = require("./stopbook");
20
- var types_1 = require("./types");
21
- var validTimeInForce = Object.values(types_1.TimeInForce);
22
- var OrderBook = /** @class */ (function () {
5
+ const errors_1 = require("./errors");
6
+ const order_1 = require("./order");
7
+ const orderside_1 = require("./orderside");
8
+ const stopbook_1 = require("./stopbook");
9
+ const types_1 = require("./types");
10
+ const validTimeInForce = Object.values(types_1.TimeInForce);
11
+ class OrderBook {
12
+ orders = {};
13
+ _lastOp = 0;
14
+ _marketPrice = 0;
15
+ bids;
16
+ asks;
17
+ enableJournaling;
18
+ stopBook;
23
19
  /**
24
20
  * Creates an instance of OrderBook.
25
21
  * @param {OrderBookOptions} [options={}] - Options for configuring the order book.
@@ -27,747 +23,7 @@ var OrderBook = /** @class */ (function () {
27
23
  * @param {JournalLog} [options.journal] - Array of journal logs (optional).
28
24
  * @param {boolean} [options.enableJournaling=false] - Flag to enable journaling. Default to false
29
25
  */
30
- function OrderBook(_a) {
31
- var _b = _a === void 0 ? {} : _a, snapshot = _b.snapshot, journal = _b.journal, _c = _b.enableJournaling, enableJournaling = _c === void 0 ? false : _c;
32
- var _this = this;
33
- this.orders = {};
34
- this._lastOp = 0;
35
- this._marketPrice = 0;
36
- /**
37
- * Create a stop market order. See {@link StopMarketOrderOptions} for details.
38
- *
39
- * @param options
40
- * @param options.side - `sell` or `buy`
41
- * @param options.size - How much of currency you want to trade in units of base currency
42
- * @param options.stopPrice - The price at which the order will be triggered.
43
- * @returns An object with the result of the processed order or an error. See {@link IProcessOrder} for the returned data structure
44
- */
45
- this.stopMarket = function (options) {
46
- var response = _this._stopMarket(options);
47
- if (_this.enableJournaling && response.err === null) {
48
- response.log = {
49
- opId: ++_this._lastOp,
50
- ts: Date.now(),
51
- op: "sm",
52
- o: options,
53
- };
54
- }
55
- return response;
56
- };
57
- /**
58
- * Create a stop limit order. See {@link StopLimitOrderOptions} for details.
59
- *
60
- * @param options
61
- * @param options.side - `sell` or `buy`
62
- * @param options.id - Unique order ID
63
- * @param options.size - How much of currency you want to trade in units of base currency
64
- * @param options.price - The price at which the order is to be fullfilled, in units of the quote currency
65
- * @param options.stopPrice - The price at which the order will be triggered.
66
- * @param options.timeInForce - Time-in-force type supported are: GTC, FOK, IOC. Default is GTC
67
- * @returns An object with the result of the processed order or an error. See {@link IProcessOrder} for the returned data structure
68
- */
69
- this.stopLimit = function (options) {
70
- var response = _this._stopLimit(options);
71
- if (_this.enableJournaling && response.err === null) {
72
- response.log = {
73
- opId: ++_this._lastOp,
74
- ts: Date.now(),
75
- op: "sl",
76
- o: options,
77
- };
78
- }
79
- return response;
80
- };
81
- /**
82
- * Create an OCO (One-Cancels-the-Other) order.
83
- * OCO order combines a `stop_limit` order and a `limit` order, where if stop price
84
- * is triggered or limit order is fully or partially fulfilled, the other is canceled.
85
- * Both orders have the same `side` and `size`. If you cancel one of the orders, the
86
- * entire OCO order pair will be canceled.
87
- *
88
- * For BUY orders the `stopPrice` must be above the current price and the `price` below the current price
89
- * For SELL orders the `stopPrice` must be below the current price and the `price` above the current price
90
- *
91
- * See {@link OCOOrderOptions} for details.
92
- *
93
- * @param options
94
- * @param options.side - `sell` or `buy`
95
- * @param options.id - Unique order ID
96
- * @param options.size - How much of currency you want to trade in units of base currency
97
- * @param options.price - The price of the `limit` order at which the order is to be fullfilled, in units of the quote currency
98
- * @param options.stopPrice - The price at which the `stop_limit` order will be triggered.
99
- * @param options.stopLimitPrice - The price of the `stop_limit` order at which the order is to be fullfilled, in units of the quote currency.
100
- * @param options.timeInForce - Time-in-force of the `limit` order. Type supported are: GTC, FOK, IOC. Default is GTC
101
- * @param options.stopLimitTimeInForce - Time-in-force of the `stop_limit` order. Type supported are: GTC, FOK, IOC. Default is GTC
102
- * @returns An object with the result of the processed order or an error. See {@link IProcessOrder} for the returned data structure
103
- */
104
- this.oco = function (options) {
105
- var response = _this._oco(options);
106
- if (_this.enableJournaling && response.err === null) {
107
- response.log = {
108
- opId: ++_this._lastOp,
109
- ts: Date.now(),
110
- op: "oco",
111
- o: options,
112
- };
113
- }
114
- return response;
115
- };
116
- /**
117
- * Modify an existing order with given ID. When an order is modified by price or quantity,
118
- * it will be deemed as a new entry. Under the price-time-priority algorithm, orders are
119
- * prioritized according to their order price and order time. Hence, the latest orders
120
- * will be placed at the back of the matching order queue.
121
- *
122
- * @param orderID - The ID of the order to be modified
123
- * @param orderUpdate - An object with the modified size and/or price of an order. The shape of the object is `{size, price}`.
124
- * @returns An object with the result of the processed order or an error
125
- */
126
- this.modify = function (orderID, orderUpdate) {
127
- var _a, _b, _c;
128
- var order = _this.orders[orderID];
129
- if (order === undefined) {
130
- return {
131
- done: [],
132
- activated: [],
133
- partial: null,
134
- partialQuantityProcessed: 0,
135
- quantityLeft: 0,
136
- err: (0, errors_1.CustomError)(errors_1.ERROR.ORDER_NOT_FOUND),
137
- };
138
- }
139
- if ((orderUpdate === null || orderUpdate === void 0 ? void 0 : orderUpdate.price) !== undefined || (orderUpdate === null || orderUpdate === void 0 ? void 0 : orderUpdate.size) !== undefined) {
140
- var newPrice = (_a = orderUpdate.price) !== null && _a !== void 0 ? _a : order.price;
141
- var newSize = (_b = orderUpdate.size) !== null && _b !== void 0 ? _b : order.size;
142
- if (newPrice > 0 && newSize > 0) {
143
- var response = _this.getProcessOrderResponse(newSize);
144
- _this._cancelOrder(order.id, true);
145
- _this.createLimitOrder(response, order.side, order.id, newSize, newPrice, order.postOnly, types_1.TimeInForce.GTC);
146
- if (_this.enableJournaling) {
147
- response.log = {
148
- opId: ++_this._lastOp,
149
- ts: Date.now(),
150
- op: "u",
151
- o: { orderID: orderID, orderUpdate: orderUpdate },
152
- };
153
- }
154
- return response;
155
- }
156
- }
157
- // Missing one of price and/or size, or the provided ones are not greater than zero
158
- return {
159
- done: [],
160
- activated: [],
161
- partial: null,
162
- partialQuantityProcessed: 0,
163
- quantityLeft: (_c = orderUpdate === null || orderUpdate === void 0 ? void 0 : orderUpdate.size) !== null && _c !== void 0 ? _c : 0,
164
- err: (0, errors_1.CustomError)(errors_1.ERROR.INVALID_PRICE_OR_QUANTITY),
165
- };
166
- };
167
- /**
168
- * Remove an existing order with given ID from the order book
169
- *
170
- * @param orderID - The ID of the order to be removed
171
- * @returns The removed order if exists or `undefined`
172
- */
173
- this.cancel = function (orderID) {
174
- return _this._cancelOrder(orderID);
175
- };
176
- /**
177
- * Get an existing order with the given ID
178
- *
179
- * @param orderID - The ID of the order to be returned
180
- * @returns The order if exists or `undefined`
181
- */
182
- this.order = function (orderID) {
183
- var _a;
184
- return (_a = _this.orders[orderID]) === null || _a === void 0 ? void 0 : _a.toObject();
185
- };
186
- // Returns price levels and volume at price level
187
- this.depth = function () {
188
- var asks = [];
189
- var bids = [];
190
- _this.asks.priceTree().forEach(function (levelPrice, level) {
191
- asks.push([levelPrice, level.volume()]);
192
- });
193
- _this.bids.priceTree().forEach(function (levelPrice, level) {
194
- bids.push([levelPrice, level.volume()]);
195
- });
196
- return [asks, bids];
197
- };
198
- this.toString = function () {
199
- /* node:coverage ignore next - Don't know what is the uncovered branch here */
200
- return "".concat(_this.asks.toString(), "\r\n------------------------------------").concat(_this.bids.toString());
201
- };
202
- // Returns total market price for requested quantity
203
- // if err is not null price returns total price of all levels in side
204
- this.calculateMarketPrice = function (side, size) {
205
- var price = 0;
206
- var err = null;
207
- var level;
208
- var iter;
209
- var quantity = size;
210
- if (side === types_1.Side.BUY) {
211
- level = _this.asks.minPriceQueue();
212
- iter = _this.asks.greaterThan;
213
- }
214
- else {
215
- level = _this.bids.maxPriceQueue();
216
- iter = _this.bids.lowerThan;
217
- }
218
- while (quantity > 0 && level !== undefined) {
219
- var levelVolume = level.volume();
220
- var levelPrice = level.price();
221
- if (_this.greaterThanOrEqual(quantity, levelVolume)) {
222
- price += levelPrice * levelVolume;
223
- quantity -= levelVolume;
224
- level = iter(levelPrice);
225
- }
226
- else {
227
- price += levelPrice * quantity;
228
- quantity = 0;
229
- }
230
- }
231
- if (quantity > 0) {
232
- err = (0, errors_1.CustomError)(errors_1.ERROR.INSUFFICIENT_QUANTITY);
233
- }
234
- return { price: price, err: err };
235
- };
236
- this.snapshot = function () {
237
- var bids = [];
238
- var asks = [];
239
- _this.bids.priceTree().forEach(function (price, orders) {
240
- bids.push({ price: price, orders: orders.toArray().map(function (o) { return o.toObject(); }) });
241
- });
242
- _this.asks.priceTree().forEach(function (price, orders) {
243
- asks.push({ price: price, orders: orders.toArray().map(function (o) { return o.toObject(); }) });
244
- });
245
- var stopBook = _this.stopBook.snapshot();
246
- return { bids: bids, asks: asks, stopBook: stopBook, ts: Date.now(), lastOp: _this._lastOp };
247
- };
248
- this._market = function (options, incomingResponse) {
249
- var response = incomingResponse !== null && incomingResponse !== void 0 ? incomingResponse : _this.validateMarketOrder(options);
250
- if (response.err !== null)
251
- return response;
252
- var quantityToTrade = options.size;
253
- var iter;
254
- var sideToProcess;
255
- if (options.side === types_1.Side.BUY) {
256
- iter = _this.asks.minPriceQueue;
257
- sideToProcess = _this.asks;
258
- }
259
- else {
260
- iter = _this.bids.maxPriceQueue;
261
- sideToProcess = _this.bids;
262
- }
263
- var priceBefore = _this._marketPrice;
264
- while (quantityToTrade > 0 && sideToProcess.len() > 0) {
265
- // if sideToProcess.len > 0 it is not necessary to verify that bestPrice exists
266
- var bestPrice = iter();
267
- var _a = _this.processQueue(bestPrice, quantityToTrade), done = _a.done, partial = _a.partial, partialQuantityProcessed = _a.partialQuantityProcessed, quantityLeft = _a.quantityLeft;
268
- response.done = response.done.concat(done);
269
- response.partial = partial;
270
- response.partialQuantityProcessed = partialQuantityProcessed;
271
- quantityToTrade = quantityLeft;
272
- }
273
- response.quantityLeft = quantityToTrade;
274
- _this.executeConditionalOrder(options.side, priceBefore, response);
275
- return response;
276
- };
277
- this._limit = function (options, incomingResponse) {
278
- var _a, _b;
279
- var response = incomingResponse !== null && incomingResponse !== void 0 ? incomingResponse : _this.validateLimitOrder(options);
280
- if (response.err !== null)
281
- return response;
282
- _this.createLimitOrder(response, options.side, options.id, options.size, options.price, (_a = options.postOnly) !== null && _a !== void 0 ? _a : false, (_b = options.timeInForce) !== null && _b !== void 0 ? _b : types_1.TimeInForce.GTC, options.ocoStopPrice);
283
- return response;
284
- };
285
- this._stopMarket = function (options) {
286
- var response = _this.validateMarketOrder(options);
287
- if (response.err !== null)
288
- return response;
289
- var stopMarket = order_1.OrderFactory.createOrder(__assign(__assign({}, options), { type: types_1.OrderType.STOP_MARKET }));
290
- return _this._stopOrder(stopMarket, response);
291
- };
292
- this._stopLimit = function (options) {
293
- var _a;
294
- var response = _this.validateLimitOrder(options);
295
- if (response.err !== null)
296
- return response;
297
- var stopLimit = order_1.OrderFactory.createOrder(__assign(__assign({}, options), { type: types_1.OrderType.STOP_LIMIT, timeInForce: (_a = options.timeInForce) !== null && _a !== void 0 ? _a : types_1.TimeInForce.GTC }));
298
- return _this._stopOrder(stopLimit, response);
299
- };
300
- this._oco = function (options) {
301
- var _a;
302
- var response = _this.validateLimitOrder(options);
303
- /* node:coverage ignore next - Already validated with limit test */
304
- if (response.err !== null)
305
- return response;
306
- if (_this.validateOCOOrder(options)) {
307
- // We use the same ID for Stop Limit and Limit Order, since
308
- // we check only on limit order for duplicated ids
309
- _this._limit({
310
- id: options.id,
311
- side: options.side,
312
- size: options.size,
313
- price: options.price,
314
- timeInForce: options.timeInForce,
315
- ocoStopPrice: options.stopPrice,
316
- }, response);
317
- /* node:coverage ignore next - Already validated with limit test */
318
- if (response.err !== null)
319
- return response;
320
- var stopLimit = order_1.OrderFactory.createOrder({
321
- type: types_1.OrderType.STOP_LIMIT,
322
- id: options.id,
323
- side: options.side,
324
- size: options.size,
325
- price: options.stopLimitPrice,
326
- stopPrice: options.stopPrice,
327
- timeInForce: (_a = options.stopLimitTimeInForce) !== null && _a !== void 0 ? _a : types_1.TimeInForce.GTC,
328
- isOCO: true,
329
- });
330
- _this.stopBook.add(stopLimit);
331
- response.done.push(stopLimit.toObject());
332
- }
333
- else {
334
- response.err = (0, errors_1.CustomError)(errors_1.ERROR.INVALID_CONDITIONAL_ORDER);
335
- }
336
- return response;
337
- };
338
- this._stopOrder = function (stopOrder, response) {
339
- if (_this.stopBook.validConditionalOrder(_this._marketPrice, stopOrder)) {
340
- _this.stopBook.add(stopOrder);
341
- response.done.push(stopOrder.toObject());
342
- }
343
- else {
344
- response.err = (0, errors_1.CustomError)(errors_1.ERROR.INVALID_CONDITIONAL_ORDER);
345
- }
346
- return response;
347
- };
348
- this.restoreSnapshot = function (snapshot) {
349
- var _a, _b, _c, _d;
350
- _this._lastOp = snapshot.lastOp;
351
- for (var _i = 0, _e = snapshot.bids; _i < _e.length; _i++) {
352
- var level = _e[_i];
353
- for (var _f = 0, _g = level.orders; _f < _g.length; _f++) {
354
- var order = _g[_f];
355
- var newOrder = order_1.OrderFactory.createOrder(order);
356
- _this.orders[newOrder.id] = newOrder;
357
- _this.bids.append(newOrder);
358
- }
359
- }
360
- for (var _h = 0, _j = snapshot.asks; _h < _j.length; _h++) {
361
- var level = _j[_h];
362
- for (var _k = 0, _l = level.orders; _k < _l.length; _k++) {
363
- var order = _l[_k];
364
- var newOrder = order_1.OrderFactory.createOrder(order);
365
- _this.orders[newOrder.id] = newOrder;
366
- _this.asks.append(newOrder);
367
- }
368
- }
369
- if (((_b = (_a = snapshot.stopBook) === null || _a === void 0 ? void 0 : _a.bids) === null || _b === void 0 ? void 0 : _b.length) > 0) {
370
- for (var _m = 0, _o = snapshot.stopBook.bids; _m < _o.length; _m++) {
371
- var level = _o[_m];
372
- for (var _p = 0, _q = level.orders; _p < _q.length; _p++) {
373
- var order = _q[_p];
374
- // @ts-expect-error // TODO fix types
375
- var newOrder = order_1.OrderFactory.createOrder(order);
376
- // @ts-expect-error // TODO fix types
377
- _this.stopBook.add(newOrder);
378
- }
379
- }
380
- }
381
- if (((_d = (_c = snapshot.stopBook) === null || _c === void 0 ? void 0 : _c.asks) === null || _d === void 0 ? void 0 : _d.length) > 0) {
382
- for (var _r = 0, _s = snapshot.stopBook.asks; _r < _s.length; _r++) {
383
- var level = _s[_r];
384
- for (var _t = 0, _u = level.orders; _t < _u.length; _t++) {
385
- var order = _u[_t];
386
- // @ts-expect-error // TODO fix types
387
- var newOrder = order_1.OrderFactory.createOrder(order);
388
- // @ts-expect-error // TODO fix types
389
- _this.stopBook.add(newOrder);
390
- }
391
- }
392
- }
393
- };
394
- /**
395
- * Remove an existing order with given ID from the order book
396
- * @param orderID The id of the order to be deleted
397
- * @param internalDeletion Set to true when the delete comes from internal operations
398
- * @returns The removed order if exists or `undefined`
399
- */
400
- this._cancelOrder = function (orderID, internalDeletion) {
401
- var _a, _b;
402
- if (internalDeletion === void 0) { internalDeletion = false; }
403
- var order = _this.orders[orderID];
404
- if (order === undefined)
405
- return;
406
- delete _this.orders[orderID];
407
- var side = order.side === types_1.Side.BUY ? _this.bids : _this.asks;
408
- var response = {
409
- order: (_a = side.remove(order)) === null || _a === void 0 ? void 0 : _a.toObject(),
410
- };
411
- // Delete OCO Order only when the delete request comes from user
412
- if (!internalDeletion && order.ocoStopPrice !== undefined) {
413
- response.stopOrder = (_b = _this.stopBook
414
- .remove(order.side, orderID, order.ocoStopPrice)) === null || _b === void 0 ? void 0 : _b.toObject();
415
- }
416
- if (_this.enableJournaling) {
417
- response.log = {
418
- opId: internalDeletion ? _this._lastOp : ++_this._lastOp,
419
- ts: Date.now(),
420
- op: "d",
421
- o: { orderID: orderID },
422
- };
423
- }
424
- return response;
425
- };
426
- this.getProcessOrderResponse = function (size) {
427
- return {
428
- done: [],
429
- activated: [],
430
- partial: null,
431
- partialQuantityProcessed: 0,
432
- quantityLeft: size,
433
- err: null,
434
- };
435
- };
436
- this.createLimitOrder = function (response, side, orderID, size, price, postOnly, timeInForce, ocoStopPrice) {
437
- var quantityToTrade = size;
438
- var sideToProcess;
439
- var sideToAdd;
440
- var comparator;
441
- var iter;
442
- if (side === types_1.Side.BUY) {
443
- sideToAdd = _this.bids;
444
- sideToProcess = _this.asks;
445
- comparator = _this.greaterThanOrEqual;
446
- iter = _this.asks.minPriceQueue;
447
- }
448
- else {
449
- sideToAdd = _this.asks;
450
- sideToProcess = _this.bids;
451
- comparator = _this.lowerThanOrEqual;
452
- iter = _this.bids.maxPriceQueue;
453
- }
454
- if (timeInForce === types_1.TimeInForce.FOK) {
455
- var fillable = _this.canFillOrder(sideToProcess, side, size, price);
456
- if (!fillable) {
457
- response.err = (0, errors_1.CustomError)(errors_1.ERROR.LIMIT_ORDER_FOK_NOT_FILLABLE);
458
- return;
459
- }
460
- }
461
- var bestPrice = iter();
462
- var priceBefore = _this._marketPrice;
463
- while (quantityToTrade > 0 &&
464
- sideToProcess.len() > 0 &&
465
- bestPrice !== undefined &&
466
- comparator(price, bestPrice.price())) {
467
- if (postOnly) {
468
- response.err = (0, errors_1.CustomError)(errors_1.ERROR.LIMIT_ORDER_POST_ONLY);
469
- return;
470
- }
471
- var _a = _this.processQueue(bestPrice, quantityToTrade), done = _a.done, partial = _a.partial, partialQuantityProcessed = _a.partialQuantityProcessed, quantityLeft = _a.quantityLeft;
472
- response.done = response.done.concat(done);
473
- response.partial = partial;
474
- response.partialQuantityProcessed = partialQuantityProcessed;
475
- quantityToTrade = quantityLeft;
476
- response.quantityLeft = quantityToTrade;
477
- bestPrice = iter();
478
- }
479
- _this.executeConditionalOrder(side, priceBefore, response);
480
- var order;
481
- var takerQty = size - quantityToTrade;
482
- var makerQty = quantityToTrade;
483
- if (quantityToTrade > 0) {
484
- 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 } : {})));
485
- if (response.done.length > 0) {
486
- response.partialQuantityProcessed = size - quantityToTrade;
487
- response.partial = order.toObject();
488
- }
489
- _this.orders[orderID] = sideToAdd.append(order);
490
- }
491
- else {
492
- var totalQuantity_1 = 0;
493
- var totalPrice_1 = 0;
494
- response.done.forEach(function (order) {
495
- totalQuantity_1 += order.size;
496
- totalPrice_1 += order.price * order.size;
497
- });
498
- if (response.partialQuantityProcessed > 0 && response.partial !== null) {
499
- totalQuantity_1 += response.partialQuantityProcessed;
500
- totalPrice_1 +=
501
- response.partial.price * response.partialQuantityProcessed;
502
- }
503
- order = order_1.OrderFactory.createOrder({
504
- id: orderID,
505
- type: types_1.OrderType.LIMIT,
506
- side: side,
507
- size: size,
508
- origSize: size,
509
- price: totalPrice_1 / totalQuantity_1,
510
- time: Date.now(),
511
- timeInForce: timeInForce,
512
- postOnly: postOnly,
513
- takerQty: takerQty,
514
- makerQty: makerQty,
515
- });
516
- response.done.push(order.toObject());
517
- }
518
- // If IOC order was not matched completely remove from the order book
519
- if (timeInForce === types_1.TimeInForce.IOC && response.quantityLeft > 0) {
520
- _this._cancelOrder(orderID, true);
521
- }
522
- return order;
523
- };
524
- this.executeConditionalOrder = function (side, priceBefore, response) {
525
- var pendingOrders = _this.stopBook.getConditionalOrders(side, priceBefore, _this._marketPrice);
526
- if (pendingOrders.length > 0) {
527
- var toBeExecuted_1 = [];
528
- // Before get all orders to be executed and clean up the stop queue
529
- // in order to avoid that an executed limit/market order run against
530
- // the same stop order queue
531
- pendingOrders.forEach(function (queue) {
532
- while (queue.len() > 0) {
533
- var headOrder = queue.removeFromHead();
534
- if (headOrder !== undefined)
535
- toBeExecuted_1.push(headOrder);
536
- }
537
- // Queue is empty now so remove the priceLevel
538
- _this.stopBook.removePriceLevel(side, queue.price);
539
- });
540
- toBeExecuted_1.forEach(function (stopOrder) {
541
- if (stopOrder.type === types_1.OrderType.STOP_MARKET) {
542
- _this._market({
543
- id: stopOrder.id,
544
- side: stopOrder.side,
545
- size: stopOrder.size,
546
- }, response);
547
- }
548
- else {
549
- if (stopOrder.isOCO) {
550
- _this._cancelOrder(stopOrder.id, true);
551
- }
552
- _this._limit({
553
- id: stopOrder.id,
554
- side: stopOrder.side,
555
- size: stopOrder.size,
556
- price: stopOrder.price,
557
- timeInForce: stopOrder.timeInForce,
558
- }, response);
559
- }
560
- response.activated.push(stopOrder.toObject());
561
- });
562
- }
563
- };
564
- this.replayJournal = function (journal) {
565
- for (var _i = 0, journal_1 = journal; _i < journal_1.length; _i++) {
566
- var log = journal_1[_i];
567
- switch (log.op) {
568
- case "m": {
569
- var _a = log.o, side = _a.side, size = _a.size;
570
- if (side == null || size == null) {
571
- throw (0, errors_1.CustomError)(errors_1.ERROR.INVALID_JOURNAL_LOG);
572
- }
573
- _this.market(log.o);
574
- break;
575
- }
576
- case "l": {
577
- var _b = log.o, side = _b.side, id = _b.id, size = _b.size, price = _b.price;
578
- if (side == null || id == null || size == null || price == null) {
579
- throw (0, errors_1.CustomError)(errors_1.ERROR.INVALID_JOURNAL_LOG);
580
- }
581
- _this.limit(log.o);
582
- break;
583
- }
584
- case "sm": {
585
- var _c = log.o, side = _c.side, size = _c.size, stopPrice = _c.stopPrice;
586
- if (side == null || size == null || stopPrice == null) {
587
- throw (0, errors_1.CustomError)(errors_1.ERROR.INVALID_JOURNAL_LOG);
588
- }
589
- _this.stopMarket(log.o);
590
- break;
591
- }
592
- case "sl": {
593
- var _d = log.o, side = _d.side, id = _d.id, size = _d.size, price = _d.price, stopPrice = _d.stopPrice;
594
- if (side == null ||
595
- id == null ||
596
- size == null ||
597
- price == null ||
598
- stopPrice == null) {
599
- throw (0, errors_1.CustomError)(errors_1.ERROR.INVALID_JOURNAL_LOG);
600
- }
601
- _this.stopLimit(log.o);
602
- break;
603
- }
604
- case "oco": {
605
- var _e = log.o, side = _e.side, id = _e.id, size = _e.size, price = _e.price, stopPrice = _e.stopPrice, stopLimitPrice = _e.stopLimitPrice;
606
- if (side == null ||
607
- id == null ||
608
- size == null ||
609
- price == null ||
610
- stopPrice == null ||
611
- stopLimitPrice == null) {
612
- throw (0, errors_1.CustomError)(errors_1.ERROR.INVALID_JOURNAL_LOG);
613
- }
614
- _this.oco(log.o);
615
- break;
616
- }
617
- case "d":
618
- if (log.o.orderID == null)
619
- throw (0, errors_1.CustomError)(errors_1.ERROR.INVALID_JOURNAL_LOG);
620
- _this.cancel(log.o.orderID);
621
- break;
622
- case "u":
623
- if (log.o.orderID == null || log.o.orderUpdate == null) {
624
- throw (0, errors_1.CustomError)(errors_1.ERROR.INVALID_JOURNAL_LOG);
625
- }
626
- _this.modify(log.o.orderID, log.o.orderUpdate);
627
- break;
628
- default:
629
- throw (0, errors_1.CustomError)(errors_1.ERROR.INVALID_JOURNAL_LOG);
630
- }
631
- }
632
- };
633
- /**
634
- * OCO Order:
635
- * Buy: price < marketPrice < stopPrice
636
- * Sell: price > marketPrice > stopPrice
637
- */
638
- this.validateOCOOrder = function (options) {
639
- var response = false;
640
- if (options.side === types_1.Side.BUY &&
641
- options.price < _this._marketPrice &&
642
- _this._marketPrice < options.stopPrice) {
643
- response = true;
644
- }
645
- if (options.side === types_1.Side.SELL &&
646
- options.price > _this._marketPrice &&
647
- _this._marketPrice > options.stopPrice) {
648
- response = true;
649
- }
650
- return response;
651
- };
652
- this.greaterThanOrEqual = function (a, b) {
653
- return a >= b;
654
- };
655
- this.lowerThanOrEqual = function (a, b) {
656
- return a <= b;
657
- };
658
- this.processQueue = function (orderQueue, quantityToTrade) {
659
- var response = {
660
- done: [],
661
- activated: [],
662
- partial: null,
663
- partialQuantityProcessed: 0,
664
- quantityLeft: quantityToTrade,
665
- err: null,
666
- };
667
- if (response.quantityLeft > 0) {
668
- while (orderQueue.len() > 0 && response.quantityLeft > 0) {
669
- var headOrder = orderQueue.head();
670
- if (headOrder !== undefined) {
671
- if (response.quantityLeft < headOrder.size) {
672
- var partial = order_1.OrderFactory.createOrder(__assign(__assign({}, headOrder.toObject()), { size: headOrder.size - response.quantityLeft }));
673
- response.partial = partial.toObject();
674
- _this.orders[headOrder.id] = partial;
675
- response.partialQuantityProcessed = response.quantityLeft;
676
- orderQueue.update(headOrder, partial);
677
- response.quantityLeft = 0;
678
- }
679
- else {
680
- response.quantityLeft = response.quantityLeft - headOrder.size;
681
- var canceledOrder = _this._cancelOrder(headOrder.id, true);
682
- /* node:coverage ignore next - Unable to test when order is undefined */
683
- if ((canceledOrder === null || canceledOrder === void 0 ? void 0 : canceledOrder.order) !== undefined) {
684
- response.done.push(canceledOrder.order);
685
- }
686
- }
687
- // Remove linked OCO Stop Order if any
688
- if (headOrder.ocoStopPrice !== undefined) {
689
- _this.stopBook.remove(headOrder.side, headOrder.id, headOrder.ocoStopPrice);
690
- }
691
- _this._marketPrice = headOrder.price;
692
- }
693
- }
694
- }
695
- return response;
696
- };
697
- this.canFillOrder = function (orderSide, side, size, price) {
698
- return side === types_1.Side.BUY
699
- ? _this.buyOrderCanBeFilled(orderSide, size, price)
700
- : _this.sellOrderCanBeFilled(orderSide, size, price);
701
- };
702
- this.buyOrderCanBeFilled = function (orderSide, size, price) {
703
- if (orderSide.volume() < size) {
704
- return false;
705
- }
706
- var cumulativeSize = 0;
707
- // biome-ignore lint/suspicious/useIterableCallbackReturn: the forEach of the priceTree must return true to break the loop
708
- orderSide.priceTree().forEach(function (_, level) {
709
- if (price >= level.price() && cumulativeSize < size) {
710
- cumulativeSize += level.volume();
711
- }
712
- else {
713
- return true; // break the loop
714
- }
715
- });
716
- return cumulativeSize >= size;
717
- };
718
- this.sellOrderCanBeFilled = function (orderSide, size, price) {
719
- if (orderSide.volume() < size) {
720
- return false;
721
- }
722
- var cumulativeSize = 0;
723
- // biome-ignore lint/suspicious/useIterableCallbackReturn: the forEach of the priceTree must return true to break the loop
724
- orderSide.priceTree().forEach(function (_, level) {
725
- if (price <= level.price() && cumulativeSize < size) {
726
- cumulativeSize += level.volume();
727
- }
728
- else {
729
- return true; // break the loop
730
- }
731
- });
732
- return cumulativeSize >= size;
733
- };
734
- this.validateMarketOrder = function (order) {
735
- var response = _this.getProcessOrderResponse(order.size);
736
- if (![types_1.Side.SELL, types_1.Side.BUY].includes(order.side)) {
737
- response.err = (0, errors_1.CustomError)(errors_1.ERROR.INVALID_SIDE);
738
- return response;
739
- }
740
- if (typeof order.size !== "number" || order.size <= 0) {
741
- response.err = (0, errors_1.CustomError)(errors_1.ERROR.INSUFFICIENT_QUANTITY);
742
- return response;
743
- }
744
- return response;
745
- };
746
- this.validateLimitOrder = function (options) {
747
- var response = _this.getProcessOrderResponse(options.size);
748
- if (![types_1.Side.SELL, types_1.Side.BUY].includes(options.side)) {
749
- response.err = (0, errors_1.CustomError)(errors_1.ERROR.INVALID_SIDE);
750
- return response;
751
- }
752
- if (_this.orders[options.id] !== undefined) {
753
- response.err = (0, errors_1.CustomError)(errors_1.ERROR.ORDER_ALREDY_EXISTS);
754
- return response;
755
- }
756
- if (typeof options.size !== "number" || options.size <= 0) {
757
- response.err = (0, errors_1.CustomError)(errors_1.ERROR.INVALID_QUANTITY);
758
- return response;
759
- }
760
- if (typeof options.price !== "number" || options.price <= 0) {
761
- response.err = (0, errors_1.CustomError)(errors_1.ERROR.INVALID_PRICE);
762
- return response;
763
- }
764
- if (options.timeInForce &&
765
- !validTimeInForce.includes(options.timeInForce)) {
766
- response.err = (0, errors_1.CustomError)(errors_1.ERROR.INVALID_TIF);
767
- return response;
768
- }
769
- return response;
770
- };
26
+ constructor({ snapshot, journal, enableJournaling = false, } = {}) {
771
27
  this.bids = new orderside_1.OrderSide(types_1.Side.BUY);
772
28
  this.asks = new orderside_1.OrderSide(types_1.Side.SELL);
773
29
  this.enableJournaling = enableJournaling;
@@ -782,27 +38,19 @@ var OrderBook = /** @class */ (function () {
782
38
  throw (0, errors_1.CustomError)(errors_1.ERROR.INVALID_JOURNAL_LOG);
783
39
  // If a snapshot is available be sure to remove logs before the last restored operation
784
40
  if (snapshot != null && snapshot.lastOp > 0) {
785
- journal = journal.filter(function (log) { return log.opId > snapshot.lastOp; });
41
+ journal = journal.filter((log) => log.opId > snapshot.lastOp);
786
42
  }
787
43
  this.replayJournal(journal);
788
44
  }
789
45
  }
790
- Object.defineProperty(OrderBook.prototype, "marketPrice", {
791
- // Getter for the market price
792
- get: function () {
793
- return this._marketPrice;
794
- },
795
- enumerable: false,
796
- configurable: true
797
- });
798
- Object.defineProperty(OrderBook.prototype, "lastOp", {
799
- // Getter for the lastOp
800
- get: function () {
801
- return this._lastOp;
802
- },
803
- enumerable: false,
804
- configurable: true
805
- });
46
+ // Getter for the market price
47
+ get marketPrice() {
48
+ return this._marketPrice;
49
+ }
50
+ // Getter for the lastOp
51
+ get lastOp() {
52
+ return this._lastOp;
53
+ }
806
54
  /**
807
55
  * Create new order. See {@link CreateOrderOptions} for details.
808
56
  *
@@ -819,7 +67,7 @@ var OrderBook = /** @class */ (function () {
819
67
  * @param options.stopLimitTimeInForce - Time-in-force supported are: `GTC` (default), `FOK`, `IOC`. Param only for limit order
820
68
  * @returns An object with the result of the processed order or an error. See {@link IProcessOrder} for the returned data structure
821
69
  */
822
- OrderBook.prototype.createOrder = function (options) {
70
+ createOrder(options) {
823
71
  switch (options.type) {
824
72
  case types_1.OrderType.MARKET:
825
73
  return this.market(options);
@@ -841,7 +89,7 @@ var OrderBook = /** @class */ (function () {
841
89
  err: (0, errors_1.CustomError)(errors_1.ERROR.INVALID_ORDER_TYPE),
842
90
  };
843
91
  }
844
- };
92
+ }
845
93
  /**
846
94
  * Create a market order. See {@link MarketOrderOptions} for details.
847
95
  *
@@ -850,8 +98,8 @@ var OrderBook = /** @class */ (function () {
850
98
  * @param options.size - How much of currency you want to trade in units of base currency
851
99
  * @returns An object with the result of the processed order or an error. See {@link IProcessOrder} for the returned data structure
852
100
  */
853
- OrderBook.prototype.market = function (options) {
854
- var response = this._market(options);
101
+ market(options) {
102
+ const response = this._market(options);
855
103
  if (this.enableJournaling && response.err === null) {
856
104
  response.log = {
857
105
  opId: ++this._lastOp,
@@ -861,6 +109,27 @@ var OrderBook = /** @class */ (function () {
861
109
  };
862
110
  }
863
111
  return response;
112
+ }
113
+ /**
114
+ * Create a stop market order. See {@link StopMarketOrderOptions} for details.
115
+ *
116
+ * @param options
117
+ * @param options.side - `sell` or `buy`
118
+ * @param options.size - How much of currency you want to trade in units of base currency
119
+ * @param options.stopPrice - The price at which the order will be triggered.
120
+ * @returns An object with the result of the processed order or an error. See {@link IProcessOrder} for the returned data structure
121
+ */
122
+ stopMarket = (options) => {
123
+ const response = this._stopMarket(options);
124
+ if (this.enableJournaling && response.err === null) {
125
+ response.log = {
126
+ opId: ++this._lastOp,
127
+ ts: Date.now(),
128
+ op: "sm",
129
+ o: options,
130
+ };
131
+ }
132
+ return response;
864
133
  };
865
134
  /**
866
135
  * Create a limit order. See {@link LimitOrderOptions} for details.
@@ -874,8 +143,8 @@ var OrderBook = /** @class */ (function () {
874
143
  * @param options.timeInForce - Time-in-force type supported are: GTC, FOK, IOC. Default is GTC
875
144
  * @returns An object with the result of the processed order or an error. See {@link IProcessOrder} for the returned data structure
876
145
  */
877
- OrderBook.prototype.limit = function (options) {
878
- var response = this._limit(options);
146
+ limit(options) {
147
+ const response = this._limit(options);
879
148
  if (this.enableJournaling && response.err === null) {
880
149
  response.log = {
881
150
  opId: ++this._lastOp,
@@ -885,7 +154,858 @@ var OrderBook = /** @class */ (function () {
885
154
  };
886
155
  }
887
156
  return response;
157
+ }
158
+ /**
159
+ * Create a stop limit order. See {@link StopLimitOrderOptions} for details.
160
+ *
161
+ * @param options
162
+ * @param options.side - `sell` or `buy`
163
+ * @param options.id - Unique order ID
164
+ * @param options.size - How much of currency you want to trade in units of base currency
165
+ * @param options.price - The price at which the order is to be fullfilled, in units of the quote currency
166
+ * @param options.stopPrice - The price at which the order will be triggered.
167
+ * @param options.timeInForce - Time-in-force type supported are: GTC, FOK, IOC. Default is GTC
168
+ * @returns An object with the result of the processed order or an error. See {@link IProcessOrder} for the returned data structure
169
+ */
170
+ stopLimit = (options) => {
171
+ const response = this._stopLimit(options);
172
+ if (this.enableJournaling && response.err === null) {
173
+ response.log = {
174
+ opId: ++this._lastOp,
175
+ ts: Date.now(),
176
+ op: "sl",
177
+ o: options,
178
+ };
179
+ }
180
+ return response;
181
+ };
182
+ /**
183
+ * Create an OCO (One-Cancels-the-Other) order.
184
+ * OCO order combines a `stop_limit` order and a `limit` order, where if stop price
185
+ * is triggered or limit order is fully or partially fulfilled, the other is canceled.
186
+ * Both orders have the same `side` and `size`. If you cancel one of the orders, the
187
+ * entire OCO order pair will be canceled.
188
+ *
189
+ * For BUY orders the `stopPrice` must be above the current price and the `price` below the current price
190
+ * For SELL orders the `stopPrice` must be below the current price and the `price` above the current price
191
+ *
192
+ * See {@link OCOOrderOptions} for details.
193
+ *
194
+ * @param options
195
+ * @param options.side - `sell` or `buy`
196
+ * @param options.id - Unique order ID
197
+ * @param options.size - How much of currency you want to trade in units of base currency
198
+ * @param options.price - The price of the `limit` order at which the order is to be fullfilled, in units of the quote currency
199
+ * @param options.stopPrice - The price at which the `stop_limit` order will be triggered.
200
+ * @param options.stopLimitPrice - The price of the `stop_limit` order at which the order is to be fullfilled, in units of the quote currency.
201
+ * @param options.timeInForce - Time-in-force of the `limit` order. Type supported are: GTC, FOK, IOC. Default is GTC
202
+ * @param options.stopLimitTimeInForce - Time-in-force of the `stop_limit` order. Type supported are: GTC, FOK, IOC. Default is GTC
203
+ * @returns An object with the result of the processed order or an error. See {@link IProcessOrder} for the returned data structure
204
+ */
205
+ oco = (options) => {
206
+ const response = this._oco(options);
207
+ if (this.enableJournaling && response.err === null) {
208
+ response.log = {
209
+ opId: ++this._lastOp,
210
+ ts: Date.now(),
211
+ op: "oco",
212
+ o: options,
213
+ };
214
+ }
215
+ return response;
216
+ };
217
+ /**
218
+ * Modify an existing order with given ID. When an order is modified by price or quantity,
219
+ * it will be deemed as a new entry. Under the price-time-priority algorithm, orders are
220
+ * prioritized according to their order price and order time. Hence, the latest orders
221
+ * will be placed at the back of the matching order queue.
222
+ *
223
+ * @param orderID - The ID of the order to be modified
224
+ * @param orderUpdate - An object with the modified size and/or price of an order. The shape of the object is `{size, price}`.
225
+ * @returns An object with the result of the processed order or an error
226
+ */
227
+ modify = (orderID, orderUpdate) => {
228
+ const order = this.orders[orderID];
229
+ if (order === undefined) {
230
+ return {
231
+ done: [],
232
+ activated: [],
233
+ partial: null,
234
+ partialQuantityProcessed: 0,
235
+ quantityLeft: 0,
236
+ err: (0, errors_1.CustomError)(errors_1.ERROR.ORDER_NOT_FOUND),
237
+ };
238
+ }
239
+ if (orderUpdate?.price !== undefined || orderUpdate?.size !== undefined) {
240
+ const newPrice = orderUpdate.price ?? order.price;
241
+ const newSize = orderUpdate.size ?? order.size;
242
+ if (newPrice > 0 && newSize > 0) {
243
+ const response = this.getProcessOrderResponse(newSize);
244
+ this._cancelOrder(order.id, true);
245
+ this.createLimitOrder(response, order.side, order.id, newSize, newPrice, order.postOnly, types_1.TimeInForce.GTC, undefined, "", types_1.SelfTradePreventionMode.NONE);
246
+ if (this.enableJournaling) {
247
+ response.log = {
248
+ opId: ++this._lastOp,
249
+ ts: Date.now(),
250
+ op: "u",
251
+ o: { orderID, orderUpdate },
252
+ };
253
+ }
254
+ return response;
255
+ }
256
+ }
257
+ // Missing one of price and/or size, or the provided ones are not greater than zero
258
+ return {
259
+ done: [],
260
+ activated: [],
261
+ partial: null,
262
+ partialQuantityProcessed: 0,
263
+ quantityLeft: orderUpdate?.size ?? 0,
264
+ err: (0, errors_1.CustomError)(errors_1.ERROR.INVALID_PRICE_OR_QUANTITY),
265
+ };
266
+ };
267
+ /**
268
+ * Remove an existing order with given ID from the order book
269
+ *
270
+ * @param orderID - The ID of the order to be removed
271
+ * @returns The removed order if exists or `undefined`
272
+ */
273
+ cancel = (orderID) => {
274
+ return this._cancelOrder(orderID);
275
+ };
276
+ /**
277
+ * Get an existing order with the given ID
278
+ *
279
+ * @param orderID - The ID of the order to be returned
280
+ * @returns The order if exists or `undefined`
281
+ */
282
+ order = (orderID) => {
283
+ return this.orders[orderID]?.toObject();
284
+ };
285
+ // Returns price levels and volume at price level
286
+ depth = () => {
287
+ const asks = [];
288
+ const bids = [];
289
+ this.asks.priceTree().forEach((levelPrice, level) => {
290
+ asks.push([levelPrice, level.volume()]);
291
+ });
292
+ this.bids.priceTree().forEach((levelPrice, level) => {
293
+ bids.push([levelPrice, level.volume()]);
294
+ });
295
+ return [asks, bids];
296
+ };
297
+ toString = () => {
298
+ /* node:coverage ignore next - Don't know what is the uncovered branch here */
299
+ return `${this.asks.toString()}\r\n------------------------------------${this.bids.toString()}`;
300
+ };
301
+ // Returns total market price for requested quantity
302
+ // if err is not null price returns total price of all levels in side
303
+ calculateMarketPrice = (side, size) => {
304
+ let price = 0;
305
+ let err = null;
306
+ let level;
307
+ let iter;
308
+ let quantity = size;
309
+ if (side === types_1.Side.BUY) {
310
+ level = this.asks.minPriceQueue();
311
+ iter = this.asks.greaterThan;
312
+ }
313
+ else {
314
+ level = this.bids.maxPriceQueue();
315
+ iter = this.bids.lowerThan;
316
+ }
317
+ while (quantity > 0 && level !== undefined) {
318
+ const levelVolume = level.volume();
319
+ const levelPrice = level.price();
320
+ if (this.greaterThanOrEqual(quantity, levelVolume)) {
321
+ price += levelPrice * levelVolume;
322
+ quantity -= levelVolume;
323
+ level = iter(levelPrice);
324
+ }
325
+ else {
326
+ price += levelPrice * quantity;
327
+ quantity = 0;
328
+ }
329
+ }
330
+ if (quantity > 0) {
331
+ err = (0, errors_1.CustomError)(errors_1.ERROR.INSUFFICIENT_QUANTITY);
332
+ }
333
+ return { price, err };
334
+ };
335
+ snapshot = () => {
336
+ const bids = [];
337
+ const asks = [];
338
+ this.bids.priceTree().forEach((price, orders) => {
339
+ bids.push({ price, orders: orders.toArray().map((o) => o.toObject()) });
340
+ });
341
+ this.asks.priceTree().forEach((price, orders) => {
342
+ asks.push({ price, orders: orders.toArray().map((o) => o.toObject()) });
343
+ });
344
+ const stopBook = this.stopBook.snapshot();
345
+ return { bids, asks, stopBook, ts: Date.now(), lastOp: this._lastOp };
346
+ };
347
+ _market = (options, incomingResponse) => {
348
+ const response = incomingResponse ?? this.validateMarketOrder(options);
349
+ if (response.err !== null)
350
+ return response;
351
+ const takerAccountId = options.accountId;
352
+ const takerStpMode = options.stpMode;
353
+ let quantityToTrade = options.size;
354
+ let iter;
355
+ let sideToProcess;
356
+ if (options.side === types_1.Side.BUY) {
357
+ iter = this.asks.minPriceQueue;
358
+ sideToProcess = this.asks;
359
+ }
360
+ else {
361
+ iter = this.bids.maxPriceQueue;
362
+ sideToProcess = this.bids;
363
+ }
364
+ const priceBefore = this._marketPrice;
365
+ while (quantityToTrade > 0 &&
366
+ sideToProcess.len() > 0 &&
367
+ response.err === null) {
368
+ // if sideToProcess.len > 0 it is not necessary to verify that bestPrice exists
369
+ const bestPrice = iter();
370
+ const { done, partial, partialQuantityProcessed, quantityLeft, stpExpired, err, } = this.processQueue(bestPrice, quantityToTrade, takerAccountId, takerStpMode);
371
+ response.done = response.done.concat(done);
372
+ response.partial = partial;
373
+ response.partialQuantityProcessed = partialQuantityProcessed;
374
+ quantityToTrade = quantityLeft;
375
+ if (err !== null) {
376
+ response.err = err;
377
+ }
378
+ if (stpExpired !== undefined) {
379
+ if (response.stpExpired === undefined) {
380
+ response.stpExpired = [];
381
+ }
382
+ response.stpExpired = response.stpExpired.concat(stpExpired);
383
+ }
384
+ }
385
+ response.quantityLeft = quantityToTrade;
386
+ if (response.err === null) {
387
+ this.executeConditionalOrder(options.side, priceBefore, response);
388
+ }
389
+ return response;
390
+ };
391
+ _limit = (options, incomingResponse) => {
392
+ const response = incomingResponse ?? this.validateLimitOrder(options);
393
+ if (response.err !== null)
394
+ return response;
395
+ this.createLimitOrder(response, options.side, options.id, options.size, options.price, options.postOnly ?? false, options.timeInForce ?? types_1.TimeInForce.GTC, options.ocoStopPrice, options.accountId, options.stpMode);
396
+ return response;
397
+ };
398
+ _stopMarket = (options) => {
399
+ const response = this.validateMarketOrder(options);
400
+ if (response.err !== null)
401
+ return response;
402
+ const stopMarket = order_1.OrderFactory.createOrder({
403
+ ...options,
404
+ type: types_1.OrderType.STOP_MARKET,
405
+ });
406
+ return this._stopOrder(stopMarket, response);
407
+ };
408
+ _stopLimit = (options) => {
409
+ const response = this.validateLimitOrder(options);
410
+ if (response.err !== null)
411
+ return response;
412
+ const stopLimit = order_1.OrderFactory.createOrder({
413
+ ...options,
414
+ type: types_1.OrderType.STOP_LIMIT,
415
+ timeInForce: options.timeInForce ?? types_1.TimeInForce.GTC,
416
+ });
417
+ return this._stopOrder(stopLimit, response);
418
+ };
419
+ _oco = (options) => {
420
+ const response = this.validateLimitOrder(options);
421
+ /* node:coverage ignore next - Already validated with limit test */
422
+ if (response.err !== null)
423
+ return response;
424
+ if (this.validateOCOOrder(options)) {
425
+ // We use the same ID for Stop Limit and Limit Order, since
426
+ // we check only on limit order for duplicated ids
427
+ this._limit({
428
+ id: options.id,
429
+ side: options.side,
430
+ size: options.size,
431
+ price: options.price,
432
+ timeInForce: options.timeInForce,
433
+ ocoStopPrice: options.stopPrice,
434
+ }, response);
435
+ /* node:coverage ignore next - Already validated with limit test */
436
+ if (response.err !== null)
437
+ return response;
438
+ const stopLimit = order_1.OrderFactory.createOrder({
439
+ type: types_1.OrderType.STOP_LIMIT,
440
+ id: options.id,
441
+ side: options.side,
442
+ size: options.size,
443
+ price: options.stopLimitPrice,
444
+ stopPrice: options.stopPrice,
445
+ timeInForce: options.stopLimitTimeInForce ?? types_1.TimeInForce.GTC,
446
+ isOCO: true,
447
+ });
448
+ this.stopBook.add(stopLimit);
449
+ response.done.push(stopLimit.toObject());
450
+ }
451
+ else {
452
+ response.err = (0, errors_1.CustomError)(errors_1.ERROR.INVALID_CONDITIONAL_ORDER);
453
+ }
454
+ return response;
455
+ };
456
+ _stopOrder = (stopOrder, response) => {
457
+ if (this.stopBook.validConditionalOrder(this._marketPrice, stopOrder)) {
458
+ this.stopBook.add(stopOrder);
459
+ response.done.push(stopOrder.toObject());
460
+ }
461
+ else {
462
+ response.err = (0, errors_1.CustomError)(errors_1.ERROR.INVALID_CONDITIONAL_ORDER);
463
+ }
464
+ return response;
465
+ };
466
+ restoreSnapshot = (snapshot) => {
467
+ this._lastOp = snapshot.lastOp;
468
+ for (const level of snapshot.bids) {
469
+ for (const order of level.orders) {
470
+ const newOrder = order_1.OrderFactory.createOrder(order);
471
+ this.orders[newOrder.id] = newOrder;
472
+ this.bids.append(newOrder);
473
+ }
474
+ }
475
+ for (const level of snapshot.asks) {
476
+ for (const order of level.orders) {
477
+ const newOrder = order_1.OrderFactory.createOrder(order);
478
+ this.orders[newOrder.id] = newOrder;
479
+ this.asks.append(newOrder);
480
+ }
481
+ }
482
+ if (snapshot.stopBook?.bids?.length > 0) {
483
+ for (const level of snapshot.stopBook.bids) {
484
+ for (const order of level.orders) {
485
+ // @ts-expect-error // TODO fix types
486
+ const newOrder = order_1.OrderFactory.createOrder(order);
487
+ // @ts-expect-error // TODO fix types
488
+ this.stopBook.add(newOrder);
489
+ }
490
+ }
491
+ }
492
+ if (snapshot.stopBook?.asks?.length > 0) {
493
+ for (const level of snapshot.stopBook.asks) {
494
+ for (const order of level.orders) {
495
+ // @ts-expect-error // TODO fix types
496
+ const newOrder = order_1.OrderFactory.createOrder(order);
497
+ // @ts-expect-error // TODO fix types
498
+ this.stopBook.add(newOrder);
499
+ }
500
+ }
501
+ }
502
+ };
503
+ /**
504
+ * Remove an existing order with given ID from the order book
505
+ * @param orderID The id of the order to be deleted
506
+ * @param internalDeletion Set to true when the delete comes from internal operations
507
+ * @returns The removed order if exists or `undefined`
508
+ */
509
+ _cancelOrder = (orderID, internalDeletion = false) => {
510
+ const order = this.orders[orderID];
511
+ if (order === undefined)
512
+ return;
513
+ delete this.orders[orderID];
514
+ const side = order.side === types_1.Side.BUY ? this.bids : this.asks;
515
+ const response = {
516
+ order: side.remove(order)?.toObject(),
517
+ };
518
+ // Delete OCO Order only when the delete request comes from user
519
+ if (!internalDeletion && order.ocoStopPrice !== undefined) {
520
+ response.stopOrder = this.stopBook
521
+ .remove(order.side, orderID, order.ocoStopPrice)
522
+ ?.toObject();
523
+ }
524
+ if (this.enableJournaling) {
525
+ response.log = {
526
+ opId: internalDeletion ? this._lastOp : ++this._lastOp,
527
+ ts: Date.now(),
528
+ op: "d",
529
+ o: { orderID },
530
+ };
531
+ }
532
+ return response;
533
+ };
534
+ getProcessOrderResponse = (size) => {
535
+ return {
536
+ done: [],
537
+ activated: [],
538
+ partial: null,
539
+ partialQuantityProcessed: 0,
540
+ quantityLeft: size,
541
+ err: null,
542
+ };
543
+ };
544
+ createLimitOrder = (response, side, orderID, size, price, postOnly, timeInForce, ocoStopPrice, takerAccountId, stpMode) => {
545
+ let quantityToTrade = size;
546
+ let sideToProcess;
547
+ let sideToAdd;
548
+ let comparator;
549
+ let iter;
550
+ if (side === types_1.Side.BUY) {
551
+ sideToAdd = this.bids;
552
+ sideToProcess = this.asks;
553
+ comparator = this.greaterThanOrEqual;
554
+ iter = this.asks.minPriceQueue;
555
+ }
556
+ else {
557
+ sideToAdd = this.asks;
558
+ sideToProcess = this.bids;
559
+ comparator = this.lowerThanOrEqual;
560
+ iter = this.bids.maxPriceQueue;
561
+ }
562
+ if (timeInForce === types_1.TimeInForce.FOK) {
563
+ // FOK is atomic, so this check is STP-aware.
564
+ const fillable = this.canFillOrder(sideToProcess, (levelPrice) => comparator(price, levelPrice), size, takerAccountId, stpMode);
565
+ if (!fillable) {
566
+ response.err = (0, errors_1.CustomError)(errors_1.ERROR.LIMIT_ORDER_FOK_NOT_FILLABLE);
567
+ return;
568
+ }
569
+ }
570
+ let bestPrice = iter();
571
+ const priceBefore = this._marketPrice;
572
+ while (quantityToTrade > 0 &&
573
+ sideToProcess.len() > 0 &&
574
+ bestPrice !== undefined &&
575
+ comparator(price, bestPrice.price()) &&
576
+ response.err === null) {
577
+ if (postOnly) {
578
+ response.err = (0, errors_1.CustomError)(errors_1.ERROR.LIMIT_ORDER_POST_ONLY);
579
+ return;
580
+ }
581
+ const { done, partial, partialQuantityProcessed, quantityLeft, stpExpired, err, } = this.processQueue(bestPrice, quantityToTrade, takerAccountId, stpMode);
582
+ response.done = response.done.concat(done);
583
+ response.partial = partial;
584
+ response.partialQuantityProcessed = partialQuantityProcessed;
585
+ quantityToTrade = quantityLeft;
586
+ response.quantityLeft = quantityToTrade;
587
+ if (err !== null) {
588
+ response.err = err;
589
+ }
590
+ if (stpExpired !== undefined) {
591
+ if (response.stpExpired === undefined) {
592
+ response.stpExpired = [];
593
+ }
594
+ response.stpExpired = response.stpExpired.concat(stpExpired);
595
+ }
596
+ bestPrice = iter();
597
+ }
598
+ // If STP triggered (EXPIRE_TAKER or EXPIRE_BOTH), don't add order to book
599
+ if (response.err !== null) {
600
+ return;
601
+ }
602
+ this.executeConditionalOrder(side, priceBefore, response);
603
+ let order;
604
+ const takerQty = size - quantityToTrade;
605
+ const makerQty = quantityToTrade;
606
+ if (quantityToTrade > 0) {
607
+ order = order_1.OrderFactory.createOrder({
608
+ type: types_1.OrderType.LIMIT,
609
+ id: orderID,
610
+ side,
611
+ size: quantityToTrade,
612
+ origSize: size,
613
+ price,
614
+ time: Date.now(),
615
+ timeInForce,
616
+ postOnly,
617
+ takerQty,
618
+ makerQty,
619
+ accountId: takerAccountId,
620
+ stpMode: stpMode,
621
+ ...(ocoStopPrice !== undefined ? { ocoStopPrice } : {}),
622
+ });
623
+ if (response.done.length > 0) {
624
+ response.partialQuantityProcessed = size - quantityToTrade;
625
+ response.partial = order.toObject();
626
+ }
627
+ this.orders[orderID] = sideToAdd.append(order);
628
+ }
629
+ else {
630
+ let totalQuantity = 0;
631
+ let totalPrice = 0;
632
+ response.done.forEach((order) => {
633
+ totalQuantity += order.size;
634
+ totalPrice += order.price * order.size;
635
+ });
636
+ if (response.partialQuantityProcessed > 0 && response.partial !== null) {
637
+ totalQuantity += response.partialQuantityProcessed;
638
+ totalPrice +=
639
+ response.partial.price * response.partialQuantityProcessed;
640
+ }
641
+ order = order_1.OrderFactory.createOrder({
642
+ id: orderID,
643
+ type: types_1.OrderType.LIMIT,
644
+ side,
645
+ size,
646
+ origSize: size,
647
+ price: totalPrice / totalQuantity,
648
+ time: Date.now(),
649
+ timeInForce,
650
+ postOnly,
651
+ takerQty,
652
+ makerQty,
653
+ accountId: takerAccountId,
654
+ stpMode: stpMode,
655
+ });
656
+ response.done.push(order.toObject());
657
+ }
658
+ // If IOC order was not matched completely remove from the order book
659
+ if (timeInForce === types_1.TimeInForce.IOC && response.quantityLeft > 0) {
660
+ this._cancelOrder(orderID, true);
661
+ }
662
+ return order;
663
+ };
664
+ executeConditionalOrder = (side, priceBefore, response) => {
665
+ const pendingOrders = this.stopBook.getConditionalOrders(side, priceBefore, this._marketPrice);
666
+ if (pendingOrders.length > 0) {
667
+ const toBeExecuted = [];
668
+ // Before get all orders to be executed and clean up the stop queue
669
+ // in order to avoid that an executed limit/market order run against
670
+ // the same stop order queue
671
+ pendingOrders.forEach((queue) => {
672
+ while (queue.len() > 0) {
673
+ const headOrder = queue.removeFromHead();
674
+ if (headOrder !== undefined)
675
+ toBeExecuted.push(headOrder);
676
+ }
677
+ // Queue is empty now so remove the priceLevel
678
+ this.stopBook.removePriceLevel(side, queue.price);
679
+ });
680
+ toBeExecuted.forEach((stopOrder) => {
681
+ if (stopOrder.type === types_1.OrderType.STOP_MARKET) {
682
+ this._market({
683
+ id: stopOrder.id,
684
+ side: stopOrder.side,
685
+ size: stopOrder.size,
686
+ accountId: stopOrder.accountId,
687
+ stpMode: stopOrder.stpMode,
688
+ }, response);
689
+ }
690
+ else {
691
+ if (stopOrder.isOCO) {
692
+ this._cancelOrder(stopOrder.id, true);
693
+ }
694
+ this._limit({
695
+ id: stopOrder.id,
696
+ side: stopOrder.side,
697
+ size: stopOrder.size,
698
+ price: stopOrder.price,
699
+ timeInForce: stopOrder.timeInForce,
700
+ accountId: stopOrder.accountId,
701
+ stpMode: stopOrder.stpMode,
702
+ }, response);
703
+ }
704
+ response.activated.push(stopOrder.toObject());
705
+ });
706
+ }
707
+ };
708
+ replayJournal = (journal) => {
709
+ for (const log of journal) {
710
+ switch (log.op) {
711
+ case "m": {
712
+ const { side, size } = log.o;
713
+ if (side == null || size == null) {
714
+ throw (0, errors_1.CustomError)(errors_1.ERROR.INVALID_JOURNAL_LOG);
715
+ }
716
+ this.market(log.o);
717
+ break;
718
+ }
719
+ case "l": {
720
+ const { side, id, size, price } = log.o;
721
+ if (side == null || id == null || size == null || price == null) {
722
+ throw (0, errors_1.CustomError)(errors_1.ERROR.INVALID_JOURNAL_LOG);
723
+ }
724
+ this.limit(log.o);
725
+ break;
726
+ }
727
+ case "sm": {
728
+ const { side, size, stopPrice } = log.o;
729
+ if (side == null || size == null || stopPrice == null) {
730
+ throw (0, errors_1.CustomError)(errors_1.ERROR.INVALID_JOURNAL_LOG);
731
+ }
732
+ this.stopMarket(log.o);
733
+ break;
734
+ }
735
+ case "sl": {
736
+ const { side, id, size, price, stopPrice } = log.o;
737
+ if (side == null ||
738
+ id == null ||
739
+ size == null ||
740
+ price == null ||
741
+ stopPrice == null) {
742
+ throw (0, errors_1.CustomError)(errors_1.ERROR.INVALID_JOURNAL_LOG);
743
+ }
744
+ this.stopLimit(log.o);
745
+ break;
746
+ }
747
+ case "oco": {
748
+ const { side, id, size, price, stopPrice, stopLimitPrice } = log.o;
749
+ if (side == null ||
750
+ id == null ||
751
+ size == null ||
752
+ price == null ||
753
+ stopPrice == null ||
754
+ stopLimitPrice == null) {
755
+ throw (0, errors_1.CustomError)(errors_1.ERROR.INVALID_JOURNAL_LOG);
756
+ }
757
+ this.oco(log.o);
758
+ break;
759
+ }
760
+ case "d":
761
+ if (log.o.orderID == null)
762
+ throw (0, errors_1.CustomError)(errors_1.ERROR.INVALID_JOURNAL_LOG);
763
+ this.cancel(log.o.orderID);
764
+ break;
765
+ case "u":
766
+ if (log.o.orderID == null || log.o.orderUpdate == null) {
767
+ throw (0, errors_1.CustomError)(errors_1.ERROR.INVALID_JOURNAL_LOG);
768
+ }
769
+ this.modify(log.o.orderID, log.o.orderUpdate);
770
+ break;
771
+ default:
772
+ throw (0, errors_1.CustomError)(errors_1.ERROR.INVALID_JOURNAL_LOG);
773
+ }
774
+ }
775
+ };
776
+ /**
777
+ * OCO Order:
778
+ * Buy: price < marketPrice < stopPrice
779
+ * Sell: price > marketPrice > stopPrice
780
+ */
781
+ validateOCOOrder = (options) => {
782
+ let response = false;
783
+ if (options.side === types_1.Side.BUY &&
784
+ options.price < this._marketPrice &&
785
+ this._marketPrice < options.stopPrice) {
786
+ response = true;
787
+ }
788
+ if (options.side === types_1.Side.SELL &&
789
+ options.price > this._marketPrice &&
790
+ this._marketPrice > options.stopPrice) {
791
+ response = true;
792
+ }
793
+ return response;
794
+ };
795
+ greaterThanOrEqual = (a, b) => {
796
+ return a >= b;
797
+ };
798
+ lowerThanOrEqual = (a, b) => {
799
+ return a <= b;
800
+ };
801
+ processQueue = (orderQueue, quantityToTrade, takerAccountId, stpMode) => {
802
+ const response = {
803
+ done: [],
804
+ activated: [],
805
+ partial: null,
806
+ partialQuantityProcessed: 0,
807
+ quantityLeft: quantityToTrade,
808
+ err: null,
809
+ };
810
+ if (response.quantityLeft > 0) {
811
+ while (orderQueue.len() > 0 && response.quantityLeft > 0) {
812
+ const headOrder = orderQueue.head();
813
+ if (headOrder !== undefined) {
814
+ // Self-Trade Prevention check
815
+ if (takerAccountId &&
816
+ headOrder.accountId === takerAccountId &&
817
+ stpMode != null &&
818
+ stpMode !== types_1.SelfTradePreventionMode.NONE) {
819
+ switch (stpMode) {
820
+ case types_1.SelfTradePreventionMode.EXPIRE_MAKER: {
821
+ // Remove the maker order from the book, continue matching
822
+ const removedOrder = this._cancelOrder(headOrder.id, true);
823
+ if (removedOrder?.order !== undefined) {
824
+ if (response.stpExpired === undefined) {
825
+ response.stpExpired = [];
826
+ }
827
+ response.stpExpired.push(removedOrder.order);
828
+ }
829
+ continue;
830
+ }
831
+ case types_1.SelfTradePreventionMode.EXPIRE_TAKER: {
832
+ // Taker expires immediately, nothing matches.
833
+ // response.quantityLeft is intentionally left untouched: it already
834
+ // holds the unfilled quantity, which is lower than the level-entry
835
+ // `quantityToTrade` when STP triggers after a partial fill inside
836
+ // this price level. Resetting it would resurrect filled quantity.
837
+ response.err = (0, errors_1.CustomError)(errors_1.ERROR.STP_TRIGGERED);
838
+ return response;
839
+ }
840
+ case types_1.SelfTradePreventionMode.EXPIRE_BOTH: {
841
+ // Remove maker from book AND expire taker.
842
+ // As in EXPIRE_TAKER, response.quantityLeft must keep the running
843
+ // unfilled quantity rather than the level-entry quantityToTrade.
844
+ const removedOrder = this._cancelOrder(headOrder.id, true);
845
+ if (removedOrder?.order !== undefined) {
846
+ if (response.stpExpired === undefined) {
847
+ response.stpExpired = [];
848
+ }
849
+ response.stpExpired.push(removedOrder.order);
850
+ }
851
+ response.err = (0, errors_1.CustomError)(errors_1.ERROR.STP_TRIGGERED);
852
+ return response;
853
+ }
854
+ }
855
+ }
856
+ if (response.quantityLeft < headOrder.size) {
857
+ const partial = order_1.OrderFactory.createOrder({
858
+ ...headOrder.toObject(),
859
+ size: headOrder.size - response.quantityLeft,
860
+ });
861
+ response.partial = partial.toObject();
862
+ this.orders[headOrder.id] = partial;
863
+ response.partialQuantityProcessed = response.quantityLeft;
864
+ orderQueue.update(headOrder, partial);
865
+ response.quantityLeft = 0;
866
+ }
867
+ else {
868
+ response.quantityLeft = response.quantityLeft - headOrder.size;
869
+ const canceledOrder = this._cancelOrder(headOrder.id, true);
870
+ /* node:coverage ignore next - Unable to test when order is undefined */
871
+ if (canceledOrder?.order !== undefined) {
872
+ response.done.push(canceledOrder.order);
873
+ }
874
+ }
875
+ // Remove linked OCO Stop Order if any
876
+ if (headOrder.ocoStopPrice !== undefined) {
877
+ this.stopBook.remove(headOrder.side, headOrder.id, headOrder.ocoStopPrice);
878
+ }
879
+ this._marketPrice = headOrder.price;
880
+ }
881
+ }
882
+ }
883
+ return response;
884
+ };
885
+ /**
886
+ * Tells whether a self-trade check is required for this order, which is also the
887
+ * point past which level volumes stop being enough and orders must be walked
888
+ * individually.
889
+ *
890
+ * The conditions mirror the guard in `processQueue`, and the two must stay
891
+ * equivalent: if this reports a check where the matcher would not, a FOK order
892
+ * gets rejected as not fillable while the same order would otherwise have
893
+ * matched.
894
+ */
895
+ shouldCheckSelfTrade = (takerAccountId, stpMode) => {
896
+ return (Boolean(takerAccountId) &&
897
+ stpMode != null &&
898
+ stpMode !== types_1.SelfTradePreventionMode.NONE);
899
+ };
900
+ /**
901
+ * Decides whether a FOK order can be filled in full, before the book is touched.
902
+ *
903
+ * Liquidity is walked in the same order the matcher consumes it, and a maker
904
+ * belonging to the taker's own account is not tradeable when STP is active.
905
+ * The three modes differ in how they stop the walk:
906
+ * EXPIRE_MAKER expires the maker and matching continues past it, while
907
+ * EXPIRE_TAKER and EXPIRE_BOTH stop the taker there, leaving no liquidity
908
+ * beyond that maker reachable.
909
+ *
910
+ * Only the STP-active path has to inspect individual orders. Without STP there
911
+ * is nothing to rule out, so level volumes are exact and the walk stays one
912
+ * step per price level.
913
+ *
914
+ * @param isLevelReachable - tells whether a level is within the taker's price.
915
+ * @returns true when `size` can be filled in full.
916
+ */
917
+ canFillOrder = (orderSide, isLevelReachable, size, takerAccountId, stpMode) => {
918
+ // Necessary condition, so it is safe to reject on regardless of STP.
919
+ if (orderSide.volume() < size) {
920
+ return false;
921
+ }
922
+ if (!this.shouldCheckSelfTrade(takerAccountId, stpMode)) {
923
+ let cumulativeSize = 0;
924
+ // biome-ignore lint/suspicious/useIterableCallbackReturn: the forEach of the priceTree must return true to break the loop
925
+ orderSide.priceTree().forEach((levelPrice, level) => {
926
+ if (isLevelReachable(levelPrice) && cumulativeSize < size) {
927
+ cumulativeSize += level.volume();
928
+ }
929
+ else {
930
+ return true; // break the loop
931
+ }
932
+ });
933
+ return cumulativeSize >= size;
934
+ }
935
+ // A same-account maker is not tradeable, so the walk has to inspect orders
936
+ // rather than just level volumes. The price tree is ordered best-price-first
937
+ // on both sides, which is the order the matcher consumes levels in. Snapshot
938
+ // the reachable levels rather than polling for the next one: this walk removes
939
+ // nothing, so polling would keep returning the level it is already on.
940
+ const levels = [];
941
+ orderSide.priceTree().forEach((levelPrice, level) => {
942
+ if (isLevelReachable(levelPrice)) {
943
+ levels.push(level);
944
+ }
945
+ });
946
+ let quantityLeft = size;
947
+ for (const level of levels) {
948
+ for (const order of level.toArray()) {
949
+ // `shouldCheckSelfTrade` above already guarantees takerAccountId is a
950
+ // non-empty string, so this cannot match an order that has no
951
+ // accountId, nor a second order with an empty one.
952
+ if (order.accountId === takerAccountId) {
953
+ if (stpMode !== types_1.SelfTradePreventionMode.EXPIRE_MAKER) {
954
+ // EXPIRE_TAKER / EXPIRE_BOTH stop the taker here, so no
955
+ // liquidity beyond this maker is reachable. STP is guaranteed
956
+ // active in this branch, so every other mode aborts.
957
+ return false;
958
+ }
959
+ // EXPIRE_MAKER expires the maker, so it offers no tradeable size
960
+ // and the walk continues past it.
961
+ continue;
962
+ }
963
+ if (quantityLeft <= order.size) {
964
+ // The taker completes here, so an STP maker further down the queue
965
+ // is never reached, exactly as in `processQueue`.
966
+ return true;
967
+ }
968
+ quantityLeft -= order.size;
969
+ }
970
+ }
971
+ return false;
972
+ };
973
+ validateMarketOrder = (order) => {
974
+ const response = this.getProcessOrderResponse(order.size);
975
+ if (![types_1.Side.SELL, types_1.Side.BUY].includes(order.side)) {
976
+ response.err = (0, errors_1.CustomError)(errors_1.ERROR.INVALID_SIDE);
977
+ return response;
978
+ }
979
+ if (typeof order.size !== "number" || order.size <= 0) {
980
+ response.err = (0, errors_1.CustomError)(errors_1.ERROR.INSUFFICIENT_QUANTITY);
981
+ return response;
982
+ }
983
+ return response;
984
+ };
985
+ validateLimitOrder = (options) => {
986
+ const response = this.getProcessOrderResponse(options.size);
987
+ if (![types_1.Side.SELL, types_1.Side.BUY].includes(options.side)) {
988
+ response.err = (0, errors_1.CustomError)(errors_1.ERROR.INVALID_SIDE);
989
+ return response;
990
+ }
991
+ if (this.orders[options.id] !== undefined) {
992
+ response.err = (0, errors_1.CustomError)(errors_1.ERROR.ORDER_ALREDY_EXISTS);
993
+ return response;
994
+ }
995
+ if (typeof options.size !== "number" || options.size <= 0) {
996
+ response.err = (0, errors_1.CustomError)(errors_1.ERROR.INVALID_QUANTITY);
997
+ return response;
998
+ }
999
+ if (typeof options.price !== "number" || options.price <= 0) {
1000
+ response.err = (0, errors_1.CustomError)(errors_1.ERROR.INVALID_PRICE);
1001
+ return response;
1002
+ }
1003
+ if (options.timeInForce &&
1004
+ !validTimeInForce.includes(options.timeInForce)) {
1005
+ response.err = (0, errors_1.CustomError)(errors_1.ERROR.INVALID_TIF);
1006
+ return response;
1007
+ }
1008
+ return response;
888
1009
  };
889
- return OrderBook;
890
- }());
1010
+ }
891
1011
  exports.OrderBook = OrderBook;