roboquant avatar

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:

  • How to manage overall risk and exposure when market conditions change

  • 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) that occur in a short timeframe

  • 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 always incorporate 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 many 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, priceAction: PriceAction): 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
        val price = priceAction.getPrice(config.priceType)

        // 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 a Policy instance that creates 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 to the generated signals and the Account and Event object.

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). The ATR is then used to set the limit amount in a Limit Order.

This example uses 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 = AssetPriceBarSeries(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, priceAction: PriceAction): Order? {
        val asset = signal.asset
        val price = priceAction.getPrice(config.priceType)

        // 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.

Things you might want to check before creating new orders in your policy and take the appropriate action:

  • Are there pending open orders for that same asset?

  • What are the total numbers of open orders?

  • How many orders have been created in the last x minutes?

  • How many open positions are there right now?

  • How much buying power is left?

  • What is the unrealized PNL of my open positions?

  • What is the realized PNL in the last x minutes?

See also how to use the account to help answer some of the above questions/

Advanced Policies

Most commonly, a Policy is used to transform signals it receives from the Strategy into orders. But there are 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 criteria. Since a Strategy doesn’t have access to an Account and therefore also not to the positions in the Account, 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.

See also the Account description on how to use information available in the account object to make decisions.

The following example shows the boilerplate code 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 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()
)

The following example shows an approach to trading a single asset. This would be applicable if, for example, you only trade a single currency pair.

The advantage over using a strategy is that you have full control over all the logic in a single place.

class SingleAssetPolicy(private val asset: Asset) : Policy {

    private val history = PriceSeries(10)

    /**
     * Open a position
     */
    private fun openPosition(account: Account, priceAction: PriceAction): Order? {
        val available = account.buyingPower.value * 0.9 // Let's not use 100%
        val price = priceAction.getPrice()
        val size = asset.contractSize(available, price, fractions = 0)

        return if (history.add(price)) {
            // Your logic to decide to open a position
            TODO()
        } else {
            null
        }
    }

    /**
     * Close a position
     */
    private fun closePosition(position: Position, priceAction: PriceAction): Order? {
        val price = priceAction.getPrice()
        val profit = price / position.avgPrice - 1.0

        return when {
            profit >= 0.01 -> MarketOrder(position.asset, -position.size) // take profit
            profit <= 0.01 -> MarketOrder(position.asset, -position.size) // stop loss
            else -> null
        }
    }

    override fun act(signals: List<Signal>, account: Account, event: Event): List<Order> {
        // If there is already an open order, don't do anything
        if (account.openOrders.assets.contains(asset)) return emptyList()

        // If there is no price, don't do anything
        val priceAction = event.prices[asset] ?: return emptyList()

        val position = account.positions.firstOrNull { it.asset == asset }
        val order = if (position != null) {
            closePosition(position, priceAction)
        } else {
            openPosition(account, priceAction)
        }

        return if (order == null) emptyList() else listOf(order)
    }

    override fun reset() {
        history.clear()
    }

}

val roboquant = Roboquant(
    NoSignalStrategy(), // will always return an empty list of signals
    policy = SingleAssetPolicy(feed.assets.first())
)

roboquant.run(feed)