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 sent 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, it is that there is not a robust policy in place that handles all the possible edge-cases. The logic required for a robust policy is anything but trivial and should incorporate an extensive testing.

Out-of-the-box policies

FlexPolicy

This is the default policy that will be used if no other policy is specified when creating an instance of the Roboquant class. It provides several constructor parameters that influence its behavior.

val policy = FlexPolicy(
    orderPercentage = 0.01,
    shorting = true,
    priceType = "OPEN",
    fractions = 4,
    oneOrderOnly = true,
    safetyMargin = 0.1,
    minPrice = 1000.USD
)

You can also extend the FlexPolicy and overwrite some of its methods. For example, to create different order types you would implement something like this:

class MyFlexPolicy : FlexPolicy() {

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

        val asset = signal.asset

        // Create a bracket order with an additional take-profit and stop-loss defined
        return BracketOrder(
            LimitOrder(asset, size, price), // limit order at current price for entry
            TrailOrder(asset, -size, 0.05), // 5% trail order for take profit
            StopOrder(asset, -size, price * 0.98) // stop loss order 2% under current price
        )
    }
}

Chaining

There are several extension methods available that can add functionality to any type of policy by creating a chain of policies in which each policy has a particular tasks. Typically, the extension methods perform one of two tasks:

  1. Remove signals before the next policy is invoked

  2. Remove orders before they are handed to the broker

val policy = MyPolicy()
    .resolve(SignalResolution.NO_CONFLICTS) // remove all conflicting signals
    .circuitBreaker(10, 1.days) // stop orders if there are too many created

Fractional Order sizes

Roboquant doesn’t make assumptions on the minimal 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 you require.

The FlexPolicy allows you to specify the number of decimals for sizing calculations, allowing to easily enable fractional orders.

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 and to 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 goes 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 then used to set the limit amount in a Limit Order. This example use the FlexPolicy 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 FlexPolicy and uses the ATR (Average True Range)
 * to set the limit amount of a LimitOrder.
 */
class SmartLimitPolicy(val atrPercentage: Double = 0.02, val windowSize: Int = 5) : FlexPolicy() {

    // Keep track of historic prices per asset
    private var prices = PriceBarSeries(windowSize + 1)

    // Use TaLib for calculation of the ATR
    private val taLib = TaLib()

    override fun act(signals: List<Signal>, account: Account, event: Event): List<Order> {
        // Update prices, so we have them available when the createOrder is invoked.
        prices.addAll(event)

        // Call the regular signal 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 asset = signal.asset

        // We set a limit based on the ATR. The higher the ATR, the more the limit price
        // will be distanced from the current price.
        val priceBarSerie = prices.getValue(asset)
        if (! priceBarSerie.isFull()) return null

        val atr = taLib.atr(priceBarSerie, windowSize)
        val limit = price - size.sign * atr * atrPercentage

        return LimitOrder(asset, size, limit)
    }

    override fun reset() {
        prices.clear()
        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 whose 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 use cases where the policy is not using Signals to create orders. Common use cases 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 therefore neither to a Portfolio, this needs 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 (without creating intermediate signals).

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

The following example shows the boilerplate for a policy that at a regular interval (20 days) re-balances the portfolio:

class MyPolicy : Policy {

    private var rebalanceDate = Instant.MIN
    private val holdingPeriod = 20.days

    /**
     * Based on some logic determine the target portfolio
     */
    fun getTargetPortfolio() : List<Position> {
        TODO("your logic for goes here")
    }

    override fun act(signals: List<Signal>, account: Account, event: Event): List<Order> {
        if (event.time < rebalanceDate) return emptyList()

        rebalanceDate = event.time + holdingPeriod
        val targetPortfolio = getTargetPortfolio()

        // Get the difference of target portfolio and the current portfolio
        val diff = account.positions.diff(targetPortfolio)

        // Transform the difference into MarketOrders
        return diff.map { MarketOrder(it.key, it.value) }
    }

    override fun reset() { rebalanceDate = Instant.MIN }
}

val roboquant = Roboquant(
    NoSignalStrategy(), // will always return an empty list of signals
    policy = MyPolicy()
)