Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for OCO orders #418

Merged
merged 1 commit into from
Jul 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 40 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Ultra-fast matching engine written in TypeScript

- Standard price-time priority
- Supports both market and limit orders
- Supports conditional orders (Stop Market and Stop Limit)
- Supports conditional orders (Stop Market, Stop Limit and OCO)
- Supports time in force GTC, FOK and IOC
- Supports order cancelling
- Supports order price and/or size updating
Expand Down Expand Up @@ -57,14 +57,16 @@ const lob = new OrderBook()
Then you'll be able to use next primary functions:

```js
lob.createOrder({ type: 'limit' | 'market' | 'stop_limit' | 'stop_market', side: 'buy' | 'sell', size: number, price?: number, id?: string, stopPrice?: number, timeInForce?: 'GTC' | 'FOK' | 'IOC' })
lob.createOrder({ type: 'limit' | 'market' | 'stop_limit' | 'stop_market' | 'oco', side: 'buy' | 'sell', size: number, price?: number, id?: string, stopPrice?: number, timeInForce?: 'GTC' | 'FOK' | 'IOC' })

lob.limit({ id: string, side: 'buy' | 'sell', size: number, price: number, timeInForce?: 'GTC' | 'FOK' | 'IOC' })

lob.market({ side: 'buy' | 'sell', size: number })

lob.stopLimit({ id: string, side: 'buy' | 'sell', size: number, price: number, stopPrice: number, timeInForce?: 'GTC' | 'FOK' | 'IOC' })

lob.oco({ id: string, side: 'buy' | 'sell', size: number, price: number, stopPrice: number, stopLimitPrice: number, timeInForce?: 'GTC' | 'FOK' | 'IOC', stopLimitTimeInForce?: 'GTC' | 'FOK' | 'IOC' })

lob.stopMarket({ side: 'buy' | 'sell', size: number, stopPrice: number })

lob.modify(orderID: string, { side: 'buy' | 'sell', size: number, price: number })
Expand All @@ -79,17 +81,20 @@ To add an order to the order book you can call the general `createOrder()` funct
### Create Order

```js
// Create a limit order
// Create limit order
createOrder({ type: 'limit', side: 'buy' | 'sell', size: number, price: number, id: string, timeInForce?: 'GTC' | 'FOK' | 'IOC' })

// Create a market order
// Create market order
createOrder({ type: 'market', side: 'buy' | 'sell', size: number })

// Create a stop limit order
// Create stop limit order
createOrder({ type: 'stop_limit', side: 'buy' | 'sell', size: number, price: number, id: string, stopPrice: number, timeInForce?: 'GTC' | 'FOK' | 'IOC' })

// Create a stop market order
// Create stop market order
createOrder({ type: 'stop_market', side: 'buy' | 'sell', size: number, stopPrice: number })

// Create OCO order
createOrder({ type: 'oco', side: 'buy' | 'sell', size: number, stopPrice: number, stopLimitPrice: number, timeInForce?: 'GTC' | 'FOK' | 'IOC', stopLimitTimeInForce?: 'GTC' | 'FOK' | 'IOC' })
```

### Create Limit Order
Expand Down Expand Up @@ -228,6 +233,35 @@ stopLimit({ side: 'buy' | 'sell', id: string, size: number, price: number, stopP
stopMarket({ side: 'buy' | 'sell', size: number, stopPrice: number })
```

### Create OCO (One-Cancels-the-Other) Order

```js
/**
* Create an OCO (One-Cancels-the-Other) order.
* OCO order combines a `stop_limit` order and a `limit` order, where if stop price
* is triggered or limit order is fully or partially fulfilled, the other is canceled.
* Both orders have the same `side` and `size`. If you cancel one of the orders, the
* entire OCO order pair will be canceled.
*
* For BUY orders the `stopPrice` must be above the current price and the `price` below the current price
* For SELL orders the `stopPrice` must be below the current price and the `price` above the current price
*
* See {@link OCOOrderOptions} for details.
*
* @param options
* @param options.side - `sell` or `buy`
* @param options.id - Unique order ID
* @param options.size - How much of currency you want to trade in units of base currency
* @param options.price - The price of the `limit` order at which the order is to be fullfilled, in units of the quote currency
* @param options.stopPrice - The price at which the `stop_limit` order will be triggered.
* @param options.stopLimitPrice - The price of the `stop_limit` order at which the order is to be fullfilled, in units of the quote currency.
* @param options.timeInForce - Time-in-force of the `limit` order. Type supported are: GTC, FOK, IOC. Default is GTC
* @param options.stopLimitTimeInForce - Time-in-force of the `stop_limit` order. Type supported are: GTC, FOK, IOC. Default is GTC
* @returns An object with the result of the processed order or an error. See {@link IProcessOrder} for the returned data structure
*/
oco({ side: 'buy' | 'sell', id: string, size: number, price: number, stopPrice: number, stopLimitPrice: number, timeInForce?: 'GTC' | 'FOK' | 'IOC', stopLimitTimeInForce?: 'GTC' | 'FOK' | 'IOC' })
```

### Modify an existing order

```js
Expand Down
6 changes: 3 additions & 3 deletions src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
export enum ERROR {
Default = 'Something wrong',
ErrInsufficientQuantity = 'orderbook: insufficient quantity to calculate price',
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)',
ErrInvalidOrderType = "orderbook: supported order type are 'limit' and 'market'",
ErrInvalidPrice = 'orderbook: invalid order price',
ErrInvalidPriceLevel = 'orderbook: invalid order price level',
ErrInvalidPriceOrQuantity = 'orderbook: invalid order price or quantity',
ErrInvalidQuantity = 'orderbook: invalid order quantity',
ErrInvalidSide = "orderbook: given neither 'bid' nor 'ask'",
ErrInvalidStopPrice = 'orderbook: Invalid Stop Price. For Stop-Limit Order (BUY: marketPrice < stopPrice <= price, SELL: marketPrice > stopPrice >= price). For Stop-Market Order (BUY: marketPrice < stopPrice, SELL: marketPrice > stopPrice)',
ErrInvalidTimeInForce = "orderbook: supported time in force are 'GTC', 'IOC' and 'FOK'",
ErrLimitFOKNotFillable = 'orderbook: limit FOK order not fillable',
ErrOrderExists = 'orderbook: order already exists',
Expand All @@ -33,8 +33,8 @@ export const CustomError = (error?: ERROR | string): Error => {
return new Error(ERROR.ErrOrderNotFound)
case ERROR.ErrInvalidSide:
return new Error(ERROR.ErrInvalidSide)
case ERROR.ErrInvalidStopPrice:
return new Error(ERROR.ErrInvalidStopPrice)
case ERROR.ErrInvalidConditionalOrder:
return new Error(ERROR.ErrInvalidConditionalOrder)
case ERROR.ErrInvalidOrderType:
return new Error(ERROR.ErrInvalidOrderType)
case ERROR.ErrInvalidTimeInForce:
Expand Down
21 changes: 18 additions & 3 deletions src/order.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,15 @@ export class LimitOrder extends BaseOrder {
private _price: number
private readonly _timeInForce: TimeInForce
private readonly _isMaker: boolean
// Refers to the linked Stop Limit order stopPrice
private readonly _ocoStopPrice?: number
constructor (options: InternalLimitOrderOptions) {
super(options)
this._type = options.type
this._price = options.price
this._timeInForce = options.timeInForce
this._isMaker = options.isMaker
this._ocoStopPrice = options.ocoStopPrice
}

// Getter for order type
Expand Down Expand Up @@ -108,6 +111,10 @@ export class LimitOrder extends BaseOrder {
return this._isMaker
}

get ocoStopPrice (): number | undefined {
return this._ocoStopPrice
}

toString = (): string =>
`${this._id}:
type: ${this.type}
Expand Down Expand Up @@ -170,7 +177,7 @@ export class StopMarketOrder extends BaseOrder {
side: this._side,
size: this._size,
origSize: this._origSize,
stopPrice: this.stopPrice,
stopPrice: this._stopPrice,
time: this._time
})
}
Expand All @@ -181,13 +188,16 @@ export class StopLimitOrder extends BaseOrder {
private readonly _stopPrice: number
private readonly _timeInForce: TimeInForce
private readonly _isMaker: boolean
// It's true when there is a linked Limit Order
private readonly _isOCO: boolean
constructor (options: InternalStopLimitOrderOptions) {
super(options)
this._type = options.type
this._price = options.price
this._stopPrice = options.stopPrice
this._timeInForce = options.timeInForce
this._isMaker = options.isMaker
this._isOCO = options.isOCO ?? false
}

// Getter for order type
Expand Down Expand Up @@ -220,6 +230,11 @@ export class StopLimitOrder extends BaseOrder {
return this._isMaker
}

// Getter for order isOCO
get isOCO (): boolean {
return this._isOCO
}

toString = (): string =>
`${this._id}:
type: ${this.type}
Expand All @@ -241,8 +256,8 @@ export class StopLimitOrder extends BaseOrder {
size: this._size,
origSize: this._origSize,
price: this._price,
stopPrice: this.stopPrice,
timeInForce: this.timeInForce,
stopPrice: this._stopPrice,
timeInForce: this._timeInForce,
time: this._time,
isMaker: this._isMaker
})
Expand Down
Loading
Loading