Skip to content

Commit dba8c12

Browse files
authored
Merge pull request #421 from fasenderos/post-only
New `postOnly` option for limit order
2 parents def26de + 524e1f9 commit dba8c12

13 files changed

+243
-165
lines changed

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ To add an order to the order book you can call the general `createOrder()` funct
8989

9090
```js
9191
// Create limit order
92-
ob.createOrder({ type: 'limit', side: 'buy' | 'sell', size: number, price: number, id: string, timeInForce?: 'GTC' | 'FOK' | 'IOC' })
92+
ob.createOrder({ type: 'limit', side: 'buy' | 'sell', size: number, price: number, id: string, postOnly?: boolean, timeInForce?: 'GTC' | 'FOK' | 'IOC' })
9393

9494
// Create market order
9595
ob.createOrder({ type: 'market', side: 'buy' | 'sell', size: number })
@@ -115,10 +115,11 @@ ob.createOrder({ type: 'oco', side: 'buy' | 'sell', size: number, stopPrice: num
115115
* @param options.id - Unique order ID
116116
* @param options.size - How much of currency you want to trade in units of base currency
117117
* @param options.price - The price at which the order is to be fullfilled, in units of the quote currency
118+
* @param options.postOnly - When `true` the order will be rejected if immediately matches and trades as a taker. Default is `false`
118119
* @param options.timeInForce - Time-in-force type supported are: GTC, FOK, IOC. Default is GTC
119120
* @returns An object with the result of the processed order or an error. See {@link IProcessOrder} for the returned data structure
120121
*/
121-
ob.limit({ side: 'buy' | 'sell', id: string, size: number, price: number, timeInForce?: 'GTC' | 'FOK' | 'IOC' })
122+
ob.limit({ side: 'buy' | 'sell', id: string, size: number, price: number, postOnly?: boolean, timeInForce?: 'GTC' | 'FOK' | 'IOC' })
122123
```
123124

124125
For example:

src/errors.ts

+54-45
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,59 @@
11
export enum ERROR {
2-
Default = 'Something wrong',
3-
ErrInsufficientQuantity = 'orderbook: insufficient quantity to calculate price',
4-
ErrInvalidConditionalOrder = 'orderbook: Stop-Limit Order (BUY: marketPrice < stopPrice <= price, SELL: marketPrice > stopPrice >= price). Stop-Market Order (BUY: marketPrice < stopPrice, SELL: marketPrice > stopPrice). OCO order (BUY: price < marketPrice < stopPrice, SELL: price > marketPrice > stopPrice)',
5-
ErrInvalidOrderType = "orderbook: supported order type are 'limit' and 'market'",
6-
ErrInvalidPrice = 'orderbook: invalid order price',
7-
ErrInvalidPriceLevel = 'orderbook: invalid order price level',
8-
ErrInvalidPriceOrQuantity = 'orderbook: invalid order price or quantity',
9-
ErrInvalidQuantity = 'orderbook: invalid order quantity',
10-
ErrInvalidSide = "orderbook: given neither 'bid' nor 'ask'",
11-
ErrInvalidTimeInForce = "orderbook: supported time in force are 'GTC', 'IOC' and 'FOK'",
12-
ErrLimitFOKNotFillable = 'orderbook: limit FOK order not fillable',
13-
ErrOrderExists = 'orderbook: order already exists',
14-
ErrOrderNotFound = 'orderbook: order not found',
15-
ErrJournalLog = 'journal: invalid journal log format',
2+
DEFAULT = 'DEFAULT',
3+
INSUFFICIENT_QUANTITY = 'INSUFFICIENT_QUANTITY',
4+
INVALID_CONDITIONAL_ORDER = 'INVALID_CONDITIONAL_ORDER',
5+
INVALID_JOURNAL_LOG = 'INVALID_JOURNAL_LOG',
6+
INVALID_ORDER_TYPE = 'INVALID_ORDER_TYPE',
7+
INVALID_PRICE = 'INVALID_PRICE',
8+
INVALID_PRICE_LEVEL = 'INVALID_PRICE_LEVEL',
9+
INVALID_PRICE_OR_QUANTITY = 'INVALID_PRICE_OR_QUANTITY',
10+
INVALID_QUANTITY = 'INVALID_QUANTITY',
11+
INVALID_SIDE = 'INVALID_SIDE',
12+
INVALID_TIF = 'INVALID_TIF',
13+
LIMIT_ORDER_FOK_NOT_FILLABLE = 'LIMIT_ORDER_FOK_NOT_FILLABLE',
14+
LIMIT_ORDER_POST_ONLY = 'LIMIT_ORDER_POST_ONLY',
15+
ORDER_ALREDY_EXISTS = 'ORDER_ALREDY_EXISTS',
16+
ORDER_NOT_FOUND = 'ORDER_NOT_FOUND',
1617
}
1718

18-
export const CustomError = (error?: ERROR | string): Error => {
19-
switch (error) {
20-
case ERROR.ErrInvalidQuantity:
21-
return new Error(ERROR.ErrInvalidQuantity)
22-
case ERROR.ErrInsufficientQuantity:
23-
return new Error(ERROR.ErrInsufficientQuantity)
24-
case ERROR.ErrInvalidPrice:
25-
return new Error(ERROR.ErrInvalidPrice)
26-
case ERROR.ErrInvalidPriceLevel:
27-
return new Error(ERROR.ErrInvalidPriceLevel)
28-
case ERROR.ErrInvalidPriceOrQuantity:
29-
return new Error(ERROR.ErrInvalidPriceOrQuantity)
30-
case ERROR.ErrOrderExists:
31-
return new Error(ERROR.ErrOrderExists)
32-
case ERROR.ErrOrderNotFound:
33-
return new Error(ERROR.ErrOrderNotFound)
34-
case ERROR.ErrInvalidSide:
35-
return new Error(ERROR.ErrInvalidSide)
36-
case ERROR.ErrInvalidConditionalOrder:
37-
return new Error(ERROR.ErrInvalidConditionalOrder)
38-
case ERROR.ErrInvalidOrderType:
39-
return new Error(ERROR.ErrInvalidOrderType)
40-
case ERROR.ErrInvalidTimeInForce:
41-
return new Error(ERROR.ErrInvalidTimeInForce)
42-
case ERROR.ErrLimitFOKNotFillable:
43-
return new Error(ERROR.ErrLimitFOKNotFillable)
44-
case ERROR.ErrJournalLog:
45-
return new Error(ERROR.ErrJournalLog)
46-
default:
47-
error = error === undefined || error === '' ? '' : `: ${error}`
48-
return new Error(`${ERROR.Default}${error}`)
19+
export const ErrorMessages: Record<ERROR, string> = {
20+
[ERROR.DEFAULT]: 'Something wrong',
21+
[ERROR.INSUFFICIENT_QUANTITY]:
22+
'orderbook: insufficient quantity to calculate price',
23+
[ERROR.INVALID_CONDITIONAL_ORDER]:
24+
'orderbook: Stop-Limit Order (BUY: marketPrice < stopPrice <= price, SELL: marketPrice > stopPrice >= price). Stop-Market Order (BUY: marketPrice < stopPrice, SELL: marketPrice > stopPrice). OCO order (BUY: price < marketPrice < stopPrice, SELL: price > marketPrice > stopPrice)',
25+
[ERROR.INVALID_ORDER_TYPE]:
26+
"orderbook: supported order type are 'limit' and 'market'",
27+
[ERROR.INVALID_PRICE]: 'orderbook: invalid order price',
28+
[ERROR.INVALID_PRICE_LEVEL]: 'orderbook: invalid order price level',
29+
[ERROR.INVALID_PRICE_OR_QUANTITY]:
30+
'orderbook: invalid order price or quantity',
31+
[ERROR.INVALID_QUANTITY]: 'orderbook: invalid order quantity',
32+
[ERROR.INVALID_SIDE]: "orderbook: given neither 'bid' nor 'ask'",
33+
[ERROR.INVALID_TIF]:
34+
"orderbook: supported time in force are 'GTC', 'IOC' and 'FOK'",
35+
[ERROR.LIMIT_ORDER_FOK_NOT_FILLABLE]:
36+
'orderbook: limit FOK order not fillable',
37+
[ERROR.LIMIT_ORDER_POST_ONLY]:
38+
'orderbook: Post-only order rejected because would execute immediately',
39+
[ERROR.ORDER_ALREDY_EXISTS]: 'orderbook: order already exists',
40+
[ERROR.ORDER_NOT_FOUND]: 'orderbook: order not found',
41+
[ERROR.INVALID_JOURNAL_LOG]: 'journal: invalid journal log format'
42+
}
43+
44+
class CustomErrorFactory extends Error {
45+
constructor (error?: ERROR | string) {
46+
let errorMessage: string
47+
if (error != null && ErrorMessages[error as ERROR] != null) {
48+
errorMessage = ErrorMessages[error as ERROR]
49+
} else {
50+
const customMessage = error === undefined || error === '' ? '' : `: ${error}`
51+
errorMessage = `${ErrorMessages.DEFAULT}${customMessage}`
52+
}
53+
super(errorMessage)
4954
}
5055
}
56+
57+
export const CustomError = (error?: ERROR | string): Error => {
58+
return new CustomErrorFactory(error)
59+
}

src/index.ts

+31-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,33 @@
11
import { OrderBook } from './orderbook'
2+
import { Side } from './side'
3+
import { OrderType } from './types'
4+
import type {
5+
CreateOrderOptions,
6+
ICancelOrder,
7+
IProcessOrder,
8+
LimitOrderOptions,
9+
MarketOrderOptions,
10+
OCOOrderOptions,
11+
OrderBookOptions,
12+
OrderUpdatePrice,
13+
OrderUpdateSize,
14+
StopLimitOrderOptions,
15+
StopMarketOrderOptions
16+
} from './types'
217

3-
export { OrderBook }
18+
export {
19+
CreateOrderOptions,
20+
ICancelOrder,
21+
IProcessOrder,
22+
LimitOrderOptions,
23+
MarketOrderOptions,
24+
OCOOrderOptions,
25+
OrderBook,
26+
OrderBookOptions,
27+
OrderType,
28+
OrderUpdatePrice,
29+
OrderUpdateSize,
30+
Side,
31+
StopLimitOrderOptions,
32+
StopMarketOrderOptions
33+
}

src/order.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export class LimitOrder extends BaseOrder {
7575
private _price: number
7676
private readonly _timeInForce: TimeInForce
7777
private readonly _isMaker: boolean
78+
private readonly _postOnly: boolean
7879
// Refers to the linked Stop Limit order stopPrice
7980
private readonly _ocoStopPrice?: number
8081
constructor (options: InternalLimitOrderOptions) {
@@ -83,6 +84,7 @@ export class LimitOrder extends BaseOrder {
8384
this._price = options.price
8485
this._timeInForce = options.timeInForce
8586
this._isMaker = options.isMaker
87+
this._postOnly = options.postOnly ?? false
8688
this._ocoStopPrice = options.ocoStopPrice
8789
}
8890

@@ -106,6 +108,11 @@ export class LimitOrder extends BaseOrder {
106108
return this._timeInForce
107109
}
108110

111+
// Getter for order postOnly
112+
get postOnly (): boolean {
113+
return this._postOnly
114+
}
115+
109116
// Getter for order isMaker
110117
get isMaker (): boolean {
111118
return this._isMaker
@@ -281,7 +288,7 @@ export const OrderFactory = {
281288
case OrderType.STOP_MARKET:
282289
return new StopMarketOrder(options) as any
283290
default:
284-
throw CustomError(ERROR.ErrInvalidOrderType)
291+
throw CustomError(ERROR.INVALID_ORDER_TYPE)
285292
}
286293
}
287294
}

0 commit comments

Comments
 (0)