Policy

What is a policy

The Policy is responsible for creating the orders that will be placed at the broker. The most common use-case is that a Policy does this based on the signals it receives from a Strategy (but there are other use-cases).

A good Policy implementation is key in making your solution as robust as possible. Important things to consider when implementing a policy:

  • What is a good allocation strategy, so how much of your buying power do you allocate to a certain asset

  • What order types to create

  • How to handle new orders when there are still open orders (for the same asset)

  • How to limit the maximum number of orders send to a broker (circuit breaker)

  • How to deal with conflicting signals from strategies

  • How to handle yo-yo signals (buy-sell-buy-sell) in a short timeframe

  • How to manage overall risk and exposure when volatility changes

  • How to ensure there is still enough buying power left to avoid margin calls

If there is one thing that prevents algo-traders from going live with a strategy, it is that there is not a robust policy in place that handles all the possible edge-cases. The logic required in a robust policy is anything but trivial and should incorporate an extensive testing period.

Out-of-the-box policies

DefaultPolicy

As the name already suggests, this is the default policy that will be used if no other policy is specified when create an instance of the Roboquant class. It provides several configuration properties that influence its behavior.

You can also easily extend the DefaultPolicy to and overwrite some methods for example to create your own order types.

class MyDefaultPolicy(private val percentage:Double = 0.05) : DefaultPolicy() {

    override fun createOrder(signal: Signal, size: Size, price: Double): Order? {
        // We don't short and  all other sell/exit orders are covered by the bracket order
        if (size < 0) return null

        val asset = signal.asset
        return BracketOrder(
            MarketOrder(asset, size),
            TrailOrder(asset, -size, percentage/2.0),
            StopOrder(asset, -size, price * (1 - percentage))
        )
    }
}

Fractional Order sizes

Roboquant doesn’t make assumptions on the permitted order- and position-sizes. They are not limited to integers only and so there is no restriction on using a broker that supports fractional trading. Since it is the Policy that create the orders, here you can also put any type of order size logic yuo require.

To make it even easier, there is an OrderSizer interface with default implementations you can reuse.

Custom Policies

In case the out-of-the-box policies will not do, you can implement your own Policy. You only have to implement a single method named act. This method has access both to the generated signals as well ass the Account and Event.

class MyPolicy : Policy {

    override fun act(signals: List<Signal>, account: Account, event: Event): List<Order> {
        val orders = mutableListOf<Order>()
        // Your code here
        return orders
    }
}

So a very naive and not robust implementation could look something like this:

class MyNaivePolicy : Policy {

    override fun act(signals: List<Signal>, account: Account, event: Event): List<Order> {
        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)
        }
        return orders
    }
}

Or using the more concise Kotlin way of doing things:

class MyNaivePolicy : Policy {

    override fun act(signals: List<Signal>, account: Account, event: Event): List<Order> {
        return signals.map {
            val size = if (it.rating.isPositive) 100 else -100
            MarketOrder(it.asset, size)
        }
    }
}

The following example is more realistic and shows an implementation that calculates the ATR (Average True Range) that is than used to set the limit amount in a Limit Order. This example use the DefaultPolicy as its base class that will take care of much of the logic like sizing and dealing with concurrent orders.

/**
 * Custom Policy that extends the DefaultPolicy and captures the ATR (Average True Range) using the TaLibMetric. It
 * then uses the ATR to set the limit amount of a LimitOrder.
 */
class SmartLimitPolicy(private val atrPercentage: Double = 0.02, private val atrPeriod: Int) : DefaultPolicy() {

    // use TaLibMetric to calculate the ATR values
    private val atr = TaLibMetric("atr", atrPeriod + 1) { atr(it, atrPeriod) }
    private var atrMetrics: MetricResults = emptyMap()

    override fun act(signals: List<Signal>, account: Account, event: Event): List<Order> {
        // Update the metrics and store the results, so we have them available when the
        // createOrder is invoked.
        atrMetrics = atr.calculate(account, event)

        // Call the regular DefaultPolicy processing
        return super.act(signals, account, event)
    }

    /**
     * Override the default behavior of creating a simple MarkerOrder. Create limit BUY and SELL orders with the
     * actual limit based on the ATR of the underlying asset.
     */
    override fun createOrder(signal: Signal, size: Size, price: Double): Order? {
        val metricName = "atr.${signal.asset.symbol.lowercase()}"
        val value = atrMetrics[metricName]
        return if (value != null) {
            val direction = if (size > 0) 1 else -1
            val limit = price - direction * value * atrPercentage
            LimitOrder(signal.asset, size, limit)
        } else {
            null
        }
    }

    override fun reset() {
        atr.reset()
        super.reset()
    }
}

When developing custom policies, they should not only be robust and deal with corner-cases, but they should also be explainable. Back-testing over large amounts of data is already challenging enough without having a Policy in place which behavior is very difficult to wrap your head around. So the KISS design principle (Keep It Simple, Stupid) applies to policies.

Advanced Policies

Most commonly a Policy is used to transform signals it receives from the Strategy into orders. But there are other use-cases where the policy is not using Signals to create orders. Common use-case are:

  1. Re-balancing your portfolio at regular intervals (like monthly) based on some risk profile. Since a Strategy doesn’t have access to an Account and there for also not to a Portfolio, this need to be done in a Policy.

  2. Advanced machine learning techniques like reinforcement learning that train the algorithm to directly create orders based on events (and not create intermediate signals).

In these cases you don’t require a Strategy and implement all the logic in the Policy instead. You can use the NoSignalStrategy as strategy that doesn’t perform any action.

val roboquant = Roboquant(NoSignalStrategy(), policy = myAdvancedPolicy)