Orders

Order Types

Roboquant comes out of the box with support for different types of orders. All orders are a subclass of one of these two abstract classes:

  1. CreateOrder: represents a regular new order, for example a MarketOrder or LimitOrder.

  2. ModifyOrder: modifies an existing open create order, for example an UpdateOrder or CancelOrder.

The following diagram shows the order types that are supported out-of-the-box and also their relationship to each other. The SimBroker supports all these order types, but other Broker implementations might only support a subset.

See also the SimBroker section on order handling for more details how orders are exactly processed.

order types

All orders refer to a single asset, including advanced order types like BracketOrders. The reason for putting this restriction in place is that real brokers only have limited support for multi-asset orders and this could lead to issues that are only discovered during live trading. Additionally, it makes back testing inconsistent if the price is only known for one of the assets.

Creating Orders

The Policy used during a run is responsible for creating the orders. This can be based on the received signals from the Strategy, but can also be done for other reasons (like for example cyclic portfolio re-balancing).

An example on how to convert a list of signals into a list of (market) orders:

val orders = mutableListOf<Order>()
for (signal in signals) {
    val size = if (signal.rating.isPositive) 100 else -100
    val order = MarketOrder(signal.asset, size)
    orders.add(order)
}
There is no explicit notion of BUY or SELL order in roboquant. A BUY order is just an order with a positive size and a SELL order has a negative size. But other than that, they are equal and created the same way.

Most advanced order types are often constructed using multiple SingleOrder types. The following snippet shows how to instantiate a BracketOrder using three individual SingleOrders.

val size = Size(10)
val order = BracketOrder(
    MarketOrder(asset, size), // entry order
    LimitOrder(asset, -size, price * 1.05), // take profit exit order
    StopOrder(asset, -size, price * 0.95) // stop loss exit order
)
During back testing make sure to only use order types that are also supported by the broker you plan to use for live trading. Also be aware that the way advanced order types are executed might differ slightly between brokers.

TIF (Time In Force)

TIF sets an execution policy for an order, defining under what conditions the order remains valid. Roughly speaking, there are two types of TIF:

  1. Time based, defining how long the order stays valid. Two common ones are GTD (Good Till Day) and GTC (Good Till Cancelled)

  2. Fill based, defining under which fill conditions the order stays valid. A common one if FOK (Fill Or Kill)

The following diagram shows the time in force policies that are available out of the box:

tif

The default TIF for orders in roboquant, if none is explicitly specified, is GTC (Good Till Cancelled). All TIF policies only become active once an CreateOrder has been ACCEPTED (so it’s not anymore in the INITIAL state).

Custom Order Types

Orders can be extended with your own order types if the ones that come out of the box are not sufficient. The steps required to make yor own order types are:

  1. Extend one of the two CreateOrder or ModifyOrder abstract classes

  2. If you want to use these orders also in back testing, you’ll also need to implement and register an OrderExecutor

After that, you can use this new order type in your policy just like any of the included order types.

// Simple custom order type
class MyOrder(
    asset: Asset,
    val size: Size,
    val customProperty: Int,
    tag: String = ""
) : CreateOrder(asset, tag)

// Define a handler for your custom order type.
// This is only required if you want your order to be supported by the SimBroker
class MyOrderExecutor(override val order: MyOrder) : CreateOrderExecutor<MyOrder> {

    override var status = OrderStatus.INITIAL

    // Our order type can be cancelled
    override fun cancel(time: Instant) : Boolean {
        return if (status.open) {
            status = OrderStatus.CANCELLED
            true
        } else {
            false
        }
    }

    // Our order type cannot be updated
    override fun update(order: CreateOrder, time: Instant) = false

    // Execute the order. This will only be called when there is a price available
    // for the asset
    override fun execute(pricing: Pricing, time: Instant): List<Execution> {

        // Set state to accepted
        status = OrderStatus.ACCEPTED

        // Get the price to use for the execution
        val price = pricing.marketPrice(order.size)

        // the logic for the custom order type
        // ....

        // Set the state to be COMPLETED once done.
        // As long as the state is not in a closed state, the executor stays active and will be invoked when new
        // price actions become available for the underlying asset
        status = OrderStatus.COMPLETED

        // Return the executions, or empty list if there are no executions
        return listOf(Execution(order, order.size, price))
    }

}

// Register the handler so the SimBroker knows how to execute this new order type
ExecutionEngine.register<MyOrder> { MyOrderExecutor(it) }

Account Orders

After an order has been placed at a Broker, the state can be tracked via Account.openOrders or Account.closedOrders. It is important to realize that these two collections contain OrderState objects that wrap the original order that was placed at the broker. Besides the underlying order, the OrderState object also contains the OrderStatus and timestamps when the order was accepted and closed.

val asset = Asset("AAPL")
val order = MarketOrder(asset, 100)
val account = broker.place(listOf(order))

val openOrder = account.openOrders.last()
assert(openOrder.order is MarketOrder)
assert(order == openOrder.order)

with (openOrder) {
    println("status=$status openedAt=$openedAt closedAt=$closedAt")
}
When creating a new order in a Policy, always validate if you still have an open order for the same asset and take the appropriate action.