roboquant avatar

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: This represents a regular new order, for example a MarketOrder or LimitOrder. But also more complex order types like a BracketOrder are subclasses of the CreateOrder.

  2. ModifyOrder: This modifies an existing open create-order. Right now there are two concrete subclasses: UpdateOrder and 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 real 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

An order always refers 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 unpredictable 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.

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 during live trading 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. There are two types of TIF:

  1. Time based, defining how long the order remains 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 use your own order types are:

  1. Extend one of the two CreateOrder or ModifyOrder abstract classes with your own order type

  2. If you want to use these orders also in back testing, you’ll also need to implement and register an OrderExecutor. This is used by the SimBroker to simulate the executions of your order type.

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 an executor for your custom order type.
class MyOrderExecutor(override val order: MyOrder) : OrderExecutor<MyOrder> {

    override var status = OrderStatus.INITIAL

    // Our order type can be cancelled, but doesn't support other modify order types
    override fun modify(modifyOrder: ModifyOrder, time: Instant): Boolean {
        return if (modifyOrder is CancelOrder && status.open) {
            status = OrderStatus.CANCELLED
            true
        } else {
            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) }

Tracking 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 latest OrderStatus and timestamps when the order was accepted and closed.

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

// You might want to sleep a bit to give broker time to process your order
Thread.sleep(1_000)

// Get the latest state
broker.sync()
val account = broker.account

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

with(openOrder) {
    println("status=$status openedAt=$openedAt closedAt=$closedAt")
}

Own order logic

Although you typically would use an instance of Roboquant to handle method invocations on the various components, you can also invoke these methods directly from your own code.

For example, if you want to place a market order, you can directly invoke the Broker.place method:

// Connect to the broker
val broker = AlpacaBroker {
    publicKey = "..."
    secretKey = "..."
}

// create the order
val tesla = Asset("TSLA")
val order = MarketOrder(tesla, 10)

// place the order
broker.place(listOf(order))

// Get an updated versions of your broker account
repeat(10) {
    broker.sync()
    println(broker.account.summary())
    Thread.sleep(1_000)
}