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:
-
CreateOrder
: This represents a regular new order, for example aMarketOrder
orLimitOrder
. But also more complex order types like aBracketOrder
are subclasses of the CreateOrder. -
ModifyOrder
: This modifies an existing open create-order. Right now there are two concrete subclasses:UpdateOrder
andCancelOrder
.
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.
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:
-
Time based, defining how long the order remains valid. Two common ones are GTD (Good Till Day) and GTC (Good Till Cancelled)
-
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:
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:
-
Extend one of the two
CreateOrder
orModifyOrder
abstract classes with your own order type -
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 theSimBroker
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)
}