nodejs-order-book 8.0.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.
@@ -0,0 +1,72 @@
1
+ /* node:coverage ignore next - Don't know why first and last line of each file count as uncovered */
2
+ import Denque from "denque";
3
+ var OrderQueue = /** @class */ (function () {
4
+ function OrderQueue(price) {
5
+ var _this = this;
6
+ this._ordersMap = {};
7
+ // returns the number of orders in queue
8
+ this.len = function () {
9
+ return _this._orders.length;
10
+ };
11
+ this.toArray = function () {
12
+ return _this._orders.toArray();
13
+ };
14
+ // returns price level of the queue
15
+ this.price = function () {
16
+ return _this._price;
17
+ };
18
+ // returns price level of the queue
19
+ this.volume = function () {
20
+ return _this._volume;
21
+ };
22
+ // returns top order in queue
23
+ this.head = function () {
24
+ return _this._orders.peekFront();
25
+ };
26
+ // returns bottom order in queue
27
+ this.tail = function () {
28
+ return _this._orders.peekBack();
29
+ };
30
+ // adds order to tail of the queue
31
+ this.append = function (order) {
32
+ _this._volume += order.size;
33
+ _this._orders.push(order);
34
+ _this._ordersMap[order.id] = _this._orders.length - 1;
35
+ return order;
36
+ };
37
+ // sets up new order to list value
38
+ this.update = function (oldOrder, newOrder) {
39
+ _this._volume -= oldOrder.size;
40
+ _this._volume += newOrder.size;
41
+ // Remove old order from head
42
+ _this._orders.shift();
43
+ delete _this._ordersMap[oldOrder.id];
44
+ // Add new order to head
45
+ _this._orders.unshift(newOrder);
46
+ _this._ordersMap[newOrder.id] = 0;
47
+ };
48
+ // removes order from the queue
49
+ this.remove = function (order) {
50
+ _this._volume -= order.size;
51
+ var deletedOrderIndex = _this._ordersMap[order.id];
52
+ _this._orders.removeOne(deletedOrderIndex);
53
+ delete _this._ordersMap[order.id];
54
+ // Update all orders indexes where index is greater than the deleted one
55
+ for (var orderId in _this._ordersMap) {
56
+ if (_this._ordersMap[orderId] > deletedOrderIndex) {
57
+ _this._ordersMap[orderId] -= 1;
58
+ }
59
+ }
60
+ };
61
+ this.updateOrderSize = function (order, size) {
62
+ _this._volume += size - order.size; // update volume
63
+ order.size = size;
64
+ order.time = Date.now();
65
+ };
66
+ this._price = price;
67
+ this._volume = 0;
68
+ this._orders = new Denque();
69
+ }
70
+ return OrderQueue;
71
+ }());
72
+ export { OrderQueue };
@@ -0,0 +1,152 @@
1
+ var __assign = (this && this.__assign) || function () {
2
+ __assign = Object.assign || function(t) {
3
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
4
+ s = arguments[i];
5
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
6
+ t[p] = s[p];
7
+ }
8
+ return t;
9
+ };
10
+ return __assign.apply(this, arguments);
11
+ };
12
+ /* node:coverage ignore next - Don't know why first and last line of each file count as uncovered */
13
+ import createRBTree from "functional-red-black-tree";
14
+ import { CustomError, ERROR } from "./errors";
15
+ import { OrderFactory } from "./order";
16
+ import { OrderQueue } from "./orderqueue";
17
+ import { Side } from "./types";
18
+ var OrderSide = /** @class */ (function () {
19
+ function OrderSide(side) {
20
+ var _this = this;
21
+ this._prices = {};
22
+ this._volume = 0;
23
+ this._total = 0;
24
+ this._numOrders = 0;
25
+ this._depthSide = 0;
26
+ this._side = Side.SELL;
27
+ // returns amount of orders
28
+ this.len = function () {
29
+ return _this._numOrders;
30
+ };
31
+ // returns depth of market
32
+ this.depth = function () {
33
+ return _this._depthSide;
34
+ };
35
+ // returns total amount of quantity in side
36
+ this.volume = function () {
37
+ return _this._volume;
38
+ };
39
+ // returns the total (size * price of each price level) in side
40
+ this.total = function () {
41
+ return _this._total;
42
+ };
43
+ // returns the price tree in side
44
+ this.priceTree = function () {
45
+ return _this._priceTree;
46
+ };
47
+ // appends order to definite price level
48
+ this.append = function (order) {
49
+ var price = order.price;
50
+ var strPrice = price.toString();
51
+ if (_this._prices[strPrice] === undefined) {
52
+ var priceQueue = new OrderQueue(price);
53
+ _this._prices[strPrice] = priceQueue;
54
+ _this._priceTree = _this._priceTree.insert(price, priceQueue);
55
+ _this._depthSide += 1;
56
+ }
57
+ _this._numOrders += 1;
58
+ _this._volume += order.size;
59
+ _this._total += order.size * order.price;
60
+ return _this._prices[strPrice].append(order);
61
+ };
62
+ // removes order from definite price level
63
+ this.remove = function (order) {
64
+ var price = order.price;
65
+ var strPrice = price.toString();
66
+ if (_this._prices[strPrice] === undefined) {
67
+ throw CustomError(ERROR.INVALID_PRICE_LEVEL);
68
+ }
69
+ _this._prices[strPrice].remove(order);
70
+ if (_this._prices[strPrice].len() === 0) {
71
+ delete _this._prices[strPrice];
72
+ _this._priceTree = _this._priceTree.remove(price);
73
+ _this._depthSide -= 1;
74
+ }
75
+ _this._numOrders -= 1;
76
+ _this._volume -= order.size;
77
+ _this._total -= order.size * order.price;
78
+ return order;
79
+ };
80
+ // Update the price of an order and return the order with the updated price
81
+ this.updateOrderPrice = function (oldOrder, orderUpdate) {
82
+ _this.remove(oldOrder);
83
+ var newOrder = OrderFactory.createOrder(__assign(__assign({}, oldOrder.toObject()), { size: orderUpdate.size !== undefined ? orderUpdate.size : oldOrder.size, price: orderUpdate.price, time: Date.now() }));
84
+ _this.append(newOrder);
85
+ return newOrder;
86
+ };
87
+ // Update the price of an order and return the order with the updated price
88
+ this.updateOrderSize = function (oldOrder, orderUpdate) {
89
+ var _a;
90
+ var newOrderPrice = (_a = orderUpdate.price) !== null && _a !== void 0 ? _a : oldOrder.price;
91
+ _this._volume += orderUpdate.size - oldOrder.size;
92
+ _this._total +=
93
+ orderUpdate.size * newOrderPrice - oldOrder.size * oldOrder.price;
94
+ _this._prices[oldOrder.price.toString()].updateOrderSize(oldOrder, orderUpdate.size);
95
+ return oldOrder;
96
+ };
97
+ // returns max level of price
98
+ this.maxPriceQueue = function () {
99
+ if (_this._depthSide > 0) {
100
+ var max = _this._side === Side.SELL ? _this._priceTree.end : _this._priceTree.begin;
101
+ return max.value;
102
+ }
103
+ };
104
+ // returns min level of price
105
+ this.minPriceQueue = function () {
106
+ if (_this._depthSide > 0) {
107
+ var min = _this._side === Side.SELL ? _this._priceTree.begin : _this._priceTree.end;
108
+ return min.value;
109
+ }
110
+ };
111
+ // returns nearest OrderQueue with price less than given
112
+ this.lowerThan = function (price) {
113
+ var node = _this._side === Side.SELL
114
+ ? _this._priceTree.lt(price)
115
+ : _this._priceTree.gt(price);
116
+ return node.value;
117
+ };
118
+ // returns nearest OrderQueue with price greater than given
119
+ this.greaterThan = function (price) {
120
+ var node = _this._side === Side.SELL
121
+ ? _this._priceTree.gt(price)
122
+ : _this._priceTree.lt(price);
123
+ return node.value;
124
+ };
125
+ // returns all orders
126
+ this.orders = function () {
127
+ var orders = [];
128
+ for (var price in _this._prices) {
129
+ var allOrders = _this._prices[price].toArray();
130
+ orders = orders.concat(allOrders);
131
+ }
132
+ return orders;
133
+ };
134
+ this.toString = function () {
135
+ var s = "";
136
+ var level = _this.maxPriceQueue();
137
+ while (level !== undefined) {
138
+ var volume = level.volume().toString();
139
+ s += "\n".concat(level.price(), " -> ").concat(volume);
140
+ level = _this.lowerThan(level.price());
141
+ }
142
+ return s;
143
+ };
144
+ var compare = side === Side.SELL
145
+ ? function (a, b) { return a - b; }
146
+ : function (a, b) { return b - a; };
147
+ this._priceTree = createRBTree(compare);
148
+ this._side = side;
149
+ }
150
+ return OrderSide;
151
+ }());
152
+ export { OrderSide };
@@ -0,0 +1,62 @@
1
+ import { StopSide } from "./stopside";
2
+ import { OrderType, Side } from "./types";
3
+ var StopBook = /** @class */ (function () {
4
+ function StopBook() {
5
+ var _this = this;
6
+ this.add = function (order) {
7
+ var stopSide = order.side === Side.BUY ? _this.bids : _this.asks;
8
+ stopSide.append(order);
9
+ };
10
+ this.remove = function (side, id, stopPrice) {
11
+ var stopSide = side === Side.BUY ? _this.bids : _this.asks;
12
+ return stopSide.remove(id, stopPrice);
13
+ };
14
+ this.removePriceLevel = function (side, priceLevel) {
15
+ var stopSide = side === Side.BUY ? _this.bids : _this.asks;
16
+ stopSide.removePriceLevel(priceLevel);
17
+ };
18
+ this.getConditionalOrders = function (side, priceBefore, marketPrice) {
19
+ var stopSide = side === Side.BUY ? _this.bids : _this.asks;
20
+ return stopSide.between(priceBefore, marketPrice);
21
+ };
22
+ /**
23
+ * Stop-Limit Order:
24
+ * Buy: marketPrice < stopPrice <= price
25
+ * Sell: marketPrice > stopPrice >= price
26
+ * Stop-Market Order:
27
+ * Buy: marketPrice < stopPrice
28
+ * Sell: marketPrice > stopPrice
29
+ */
30
+ this.validConditionalOrder = function (marketPrice, order) {
31
+ var response = false;
32
+ var type = order.type, side = order.side, stopPrice = order.stopPrice;
33
+ if (type === OrderType.STOP_LIMIT) {
34
+ // Buy: marketPrice < stopPrice <= price
35
+ if (side === Side.BUY &&
36
+ marketPrice < stopPrice &&
37
+ stopPrice <= order.price) {
38
+ response = true;
39
+ }
40
+ // Sell: marketPrice > stopPrice >= price
41
+ if (side === Side.SELL &&
42
+ marketPrice > stopPrice &&
43
+ stopPrice >= order.price) {
44
+ response = true;
45
+ }
46
+ }
47
+ else {
48
+ // Buy: marketPrice < stopPrice
49
+ if (side === Side.BUY && marketPrice < stopPrice)
50
+ response = true;
51
+ // Sell: marketPrice > stopPrice
52
+ if (side === Side.SELL && marketPrice > stopPrice)
53
+ response = true;
54
+ }
55
+ return response;
56
+ };
57
+ this.bids = new StopSide(Side.BUY);
58
+ this.asks = new StopSide(Side.SELL);
59
+ }
60
+ return StopBook;
61
+ }());
62
+ export { StopBook };
@@ -0,0 +1,53 @@
1
+ /* node:coverage ignore next - Don't know why first and last line of each file count as uncovered */
2
+ import Denque from "denque";
3
+ var StopQueue = /** @class */ (function () {
4
+ function StopQueue(price) {
5
+ var _this = this;
6
+ this._ordersMap = {};
7
+ // returns the number of orders in queue
8
+ this.len = function () {
9
+ return _this._orders.length;
10
+ };
11
+ // remove order from head of queue
12
+ this.removeFromHead = function () {
13
+ // We can't use the shift method here because we need
14
+ // to update index in the map, so we use the remove(id) function
15
+ var order = _this._orders.peekFront();
16
+ if (order === undefined)
17
+ return;
18
+ return _this.remove(order.id);
19
+ };
20
+ // adds order to tail of the queue
21
+ this.append = function (order) {
22
+ _this._orders.push(order);
23
+ _this._ordersMap[order.id] = _this._orders.length - 1;
24
+ return order;
25
+ };
26
+ // removes order from the queue
27
+ this.remove = function (id) {
28
+ var deletedOrderIndex = _this._ordersMap[id];
29
+ if (deletedOrderIndex === undefined)
30
+ return;
31
+ var deletedOrder = _this._orders.removeOne(deletedOrderIndex);
32
+ delete _this._ordersMap[id];
33
+ // Update all orders indexes where index is greater than the deleted one
34
+ for (var orderId in _this._ordersMap) {
35
+ if (_this._ordersMap[orderId] > deletedOrderIndex) {
36
+ _this._ordersMap[orderId] -= 1;
37
+ }
38
+ }
39
+ return deletedOrder;
40
+ };
41
+ this._price = price;
42
+ this._orders = new Denque();
43
+ }
44
+ Object.defineProperty(StopQueue.prototype, "price", {
45
+ get: function () {
46
+ return this._price;
47
+ },
48
+ enumerable: false,
49
+ configurable: true
50
+ });
51
+ return StopQueue;
52
+ }());
53
+ export { StopQueue };
@@ -0,0 +1,70 @@
1
+ /* node:coverage ignore next - Don't know why first and last line of each file count as uncovered */
2
+ import createRBTree from "functional-red-black-tree";
3
+ import { CustomError, ERROR } from "./errors";
4
+ import { StopQueue } from "./stopqueue";
5
+ import { Side } from "./types";
6
+ var StopSide = /** @class */ (function () {
7
+ function StopSide(side) {
8
+ var _this = this;
9
+ this._prices = {};
10
+ // appends order to definite price level
11
+ this.append = function (order) {
12
+ var price = order.stopPrice;
13
+ var strPrice = price.toString();
14
+ if (_this._prices[strPrice] === undefined) {
15
+ var priceQueue = new StopQueue(price);
16
+ _this._prices[strPrice] = priceQueue;
17
+ _this._priceTree = _this._priceTree.insert(price, priceQueue);
18
+ }
19
+ return _this._prices[strPrice].append(order);
20
+ };
21
+ // removes order from definite price level
22
+ this.remove = function (id, stopPrice) {
23
+ var strPrice = stopPrice.toString();
24
+ if (_this._prices[strPrice] === undefined) {
25
+ throw CustomError(ERROR.INVALID_PRICE_LEVEL);
26
+ }
27
+ var deletedOrder = _this._prices[strPrice].remove(id);
28
+ if (_this._prices[strPrice].len() === 0) {
29
+ delete _this._prices[strPrice];
30
+ _this._priceTree = _this._priceTree.remove(stopPrice);
31
+ }
32
+ return deletedOrder;
33
+ };
34
+ this.removePriceLevel = function (priceLevel) {
35
+ delete _this._prices[priceLevel.toString()];
36
+ _this._priceTree = _this._priceTree.remove(priceLevel);
37
+ };
38
+ // Get orders queue between two price levels
39
+ this.between = function (priceBefore, marketPrice) {
40
+ var queues = [];
41
+ var lowerBound = priceBefore;
42
+ var upperBound = marketPrice;
43
+ var highest = Math.max(priceBefore, marketPrice);
44
+ var lowest = Math.min(priceBefore, marketPrice);
45
+ if (_this._side === Side.BUY) {
46
+ lowerBound = highest;
47
+ upperBound = lowest - 1;
48
+ }
49
+ else {
50
+ lowerBound = lowest;
51
+ upperBound = highest + 1;
52
+ }
53
+ _this._priceTree.forEach(function (price, queue) {
54
+ if ((_this._side === Side.BUY && price >= lowest) ||
55
+ (_this._side === Side.SELL && price <= highest)) {
56
+ queues.push(queue);
57
+ }
58
+ }, lowerBound, // Inclusive
59
+ upperBound);
60
+ return queues;
61
+ };
62
+ var compare = side === Side.SELL
63
+ ? function (a, b) { return a - b; }
64
+ : function (a, b) { return b - a; };
65
+ this._priceTree = createRBTree(compare);
66
+ this._side = side;
67
+ }
68
+ return StopSide;
69
+ }());
70
+ export { StopSide };
@@ -0,0 +1,20 @@
1
+ export var Side;
2
+ (function (Side) {
3
+ Side["BUY"] = "buy";
4
+ Side["SELL"] = "sell";
5
+ })(Side || (Side = {}));
6
+ export var OrderType;
7
+ (function (OrderType) {
8
+ OrderType["LIMIT"] = "limit";
9
+ OrderType["MARKET"] = "market";
10
+ OrderType["OCO"] = "oco";
11
+ OrderType["STOP_LIMIT"] = "stop_limit";
12
+ OrderType["STOP_MARKET"] = "stop_market";
13
+ })(OrderType || (OrderType = {}));
14
+ export var TimeInForce;
15
+ (function (TimeInForce) {
16
+ TimeInForce["GTC"] = "GTC";
17
+ TimeInForce["IOC"] = "IOC";
18
+ TimeInForce["FOK"] = "FOK";
19
+ })(TimeInForce || (TimeInForce = {}));
20
+ /* node:coverage enable */
@@ -0,0 +1,26 @@
1
+ import type { IError } from "./types";
2
+ export declare enum ERROR {
3
+ DEFAULT = "DEFAULT",
4
+ INSUFFICIENT_QUANTITY = "INSUFFICIENT_QUANTITY",
5
+ INVALID_CONDITIONAL_ORDER = "INVALID_CONDITIONAL_ORDER",
6
+ INVALID_JOURNAL_LOG = "INVALID_JOURNAL_LOG",
7
+ INVALID_ORDER_TYPE = "INVALID_ORDER_TYPE",
8
+ INVALID_PRICE = "INVALID_PRICE",
9
+ INVALID_PRICE_LEVEL = "INVALID_PRICE_LEVEL",
10
+ INVALID_PRICE_OR_QUANTITY = "INVALID_PRICE_OR_QUANTITY",
11
+ INVALID_QUANTITY = "INVALID_QUANTITY",
12
+ INVALID_SIDE = "INVALID_SIDE",
13
+ INVALID_TIF = "INVALID_TIF",
14
+ LIMIT_ORDER_FOK_NOT_FILLABLE = "LIMIT_ORDER_FOK_NOT_FILLABLE",
15
+ LIMIT_ORDER_POST_ONLY = "LIMIT_ORDER_POST_ONLY",
16
+ ORDER_ALREDY_EXISTS = "ORDER_ALREDY_EXISTS",
17
+ ORDER_NOT_FOUND = "ORDER_NOT_FOUND"
18
+ }
19
+ export declare const ErrorCodes: Record<ERROR, number>;
20
+ export declare const ErrorMessages: Record<ERROR, string>;
21
+ export declare class OrderBookError implements IError {
22
+ message: string;
23
+ code: number;
24
+ constructor(error?: ERROR | string);
25
+ }
26
+ export declare const CustomError: (error?: ERROR | string) => OrderBookError;
@@ -0,0 +1,4 @@
1
+ import { OrderBook } from "./orderbook";
2
+ import { OrderType, Side } from "./types";
3
+ import type { CreateOrderOptions, ICancelOrder, IProcessOrder, LimitOrderOptions, MarketOrderOptions, OCOOrderOptions, OrderBookOptions, OrderUpdatePrice, OrderUpdateSize, StopLimitOrderOptions, StopMarketOrderOptions } from "./types";
4
+ export { type CreateOrderOptions, type ICancelOrder, type IProcessOrder, type LimitOrderOptions, type MarketOrderOptions, type OCOOrderOptions, OrderBook, type OrderBookOptions, OrderType, type OrderUpdatePrice, type OrderUpdateSize, Side, type StopLimitOrderOptions, type StopMarketOrderOptions, };
@@ -0,0 +1,71 @@
1
+ import { type ILimitOrder, type IStopLimitOrder, type IStopMarketOrder, type InternalLimitOrderOptions, type InternalStopLimitOrderOptions, type InternalStopMarketOrderOptions, type OrderOptions, OrderType, type Side, type TimeInForce } from "./types";
2
+ declare abstract class BaseOrder {
3
+ readonly _id: string;
4
+ readonly _side: Side;
5
+ _size: number;
6
+ _time: number;
7
+ constructor(options: OrderOptions);
8
+ get id(): string;
9
+ get side(): Side;
10
+ get size(): number;
11
+ set size(size: number);
12
+ get time(): number;
13
+ set time(time: number);
14
+ abstract toString(): void;
15
+ abstract toJSON(): void;
16
+ abstract toObject(): void;
17
+ }
18
+ export declare class LimitOrder extends BaseOrder {
19
+ private readonly _type;
20
+ readonly _origSize: number;
21
+ private _price;
22
+ private readonly _timeInForce;
23
+ private readonly _makerQty;
24
+ private readonly _takerQty;
25
+ private readonly _postOnly;
26
+ private readonly _ocoStopPrice?;
27
+ constructor(options: InternalLimitOrderOptions);
28
+ get type(): OrderType.LIMIT;
29
+ get price(): number;
30
+ set price(price: number);
31
+ get timeInForce(): TimeInForce;
32
+ get postOnly(): boolean;
33
+ get origSize(): number;
34
+ get makerQty(): number;
35
+ get takerQty(): number;
36
+ get ocoStopPrice(): number | undefined;
37
+ toString: () => string;
38
+ toJSON: () => string;
39
+ toObject: () => ILimitOrder;
40
+ }
41
+ export declare class StopMarketOrder extends BaseOrder {
42
+ private readonly _type;
43
+ private readonly _stopPrice;
44
+ constructor(options: InternalStopMarketOrderOptions);
45
+ get type(): OrderType.STOP_MARKET;
46
+ get stopPrice(): number;
47
+ toString: () => string;
48
+ toJSON: () => string;
49
+ toObject: () => IStopMarketOrder;
50
+ }
51
+ export declare class StopLimitOrder extends BaseOrder {
52
+ private readonly _type;
53
+ private _price;
54
+ private readonly _stopPrice;
55
+ private readonly _timeInForce;
56
+ private readonly _isOCO;
57
+ constructor(options: InternalStopLimitOrderOptions);
58
+ get type(): OrderType.STOP_LIMIT;
59
+ get price(): number;
60
+ set price(price: number);
61
+ get stopPrice(): number;
62
+ get timeInForce(): TimeInForce;
63
+ get isOCO(): boolean;
64
+ toString: () => string;
65
+ toJSON: () => string;
66
+ toObject: () => IStopLimitOrder;
67
+ }
68
+ export declare const OrderFactory: {
69
+ createOrder<T extends OrderOptions>(options: T): T extends InternalLimitOrderOptions ? LimitOrder : T extends InternalStopLimitOrderOptions ? StopLimitOrder : T extends InternalStopMarketOrderOptions ? StopMarketOrder : never;
70
+ };
71
+ export {};