Multiple Strategies in a Single Algo

Sometimes we may want to run multiple strategies with different logic and order/ positions management within a single algo. This maybe useful when we are running an ensemble of strategies (with a capital allocation rule) in a single algorithm. There are other cases too. Blueshift API support such use cases by allowing to dynamically add or remove sub-strategies in a single algo.

Sub-Strategies

An algo run on Blueshift with the main context as defined by the context variable available through the main callback functions. This is the main context of the algo run. However, by using the sub-strategies API, we can add a sub-strategy to the algo (and remove it when required). Each such sub-strategy runs within its own context - also known as sub-context. Since each such sub-contexts are maintained independently, a sub-strategy can maintain its independent orders and positions tracking. We can generally use the API functions as is in either in the context of the main strategy or any sub-strategies. Blueshift will automatically apply the correct context. This allows the main and the sub-strategies to work independetly from each other, without being aware of each others presence.

Defining a sub-strategy

A sub-strategy can be defined by sub-classing the Strategy class and then overriding the event handlers as required.

class blueshift.core.algorithm.strategy.Strategy

Interface for class based strategy on Blueshift. Subclass this strategy to create a sub-strategy. This class can also be used to create the main strategy (instead of defining the main callback functions directly in the strategy code).

Parameters:
  • name (str) – Name of the strategy.

  • initial_capital (float) – Initial capital allocation.

Sub-strategy methods and attributes

Strategy.name

Name (and the context name) of this strategy.

Strategy.initialize(context)

override this function to run at initialization.

Strategy.before_trading_start(context, data)

override this function to run at start of the day.

Strategy.handle_data(context, data)

override this function to run at every cycle.

Strategy.on_data(context, data)

override this function to run at new data arrival.

Strategy.on_trade(context, data)

override this function to run at any order fill event.

Strategy.after_trading_hours(context, data)

override this function to run at end of the day.

Strategy.analyze(context, perf)

override this function to run at end of the run.

Strategy.on_error(context, error)

override this function to run before the strategy exit on error.

Strategy.on_cancel(context)

override this function to run before the strategy exit on user cancel.

The callbacks are similar in functionality as the main event callbacks. These will be automatically called (on appropriate events) once a sub-strategy is added in the algo run. Once a sub-strategy is removed, its callbacks will not be invoked anymore.

Adding a sub-strategy

TradingAlgorithm.add_strategy(strategy)

Add a sub strategy to the current algo. Sub strategies allow a modular approach to incorporate multiple independent rules in a single strategy. For more see :ref: sub-strategies<Sub-Strategies>.

Note

Sub strategies can only be added in regular modes (not in EXECUTION mode).

Parameters:

strategy (Strategy.) – The strategy to add.

This API method must be called from the main strategy, as a sub-strategy can be added from the main context only.

Cancelling a sub-strategy

TradingAlgorithm.cancel_strategy(name, cancel_orders=True, square_off=False)

Cancel (remove) a sub strategy previously added. For more on sub-strategies, see :ref: sub-strategies<Sub-Strategies>.

Parameters:
  • name (str) – Name of the sub-strategy to remove.

  • cancel_orders (bool) – Cancel open orders on removal.

  • square_off (bool) – Square off on removal.

This API method must be called from the main strategy, or from the sub-strategy itself that is being cancelled.

Sub-strategy order and position tracking

Each sub-strategies, and the main strategy, will maintain their independent version of orders and positions tracking. That means order APIs (trading APIs), including order placement, order management, algo order and stoploss/ take-profit APIs will maintain and track their own contexts. Simulation APIs (simulation APIs) will also affect behaviour of only the respective contexts. For example, calling get_open_orders from a sub-context will return only the open orders placed from the corresponding sub-strategy.

However, a few sets of APIs are global, in the sense that they will affect all contexts - the main context as well as any sub-contexts created. This inclues the squareoff API, the pipeline APIs (pipeline) and some risk management APIs). This means, for example, if we call square-off from any context, all positions in all active contexts will be squared-off by default. See more in the documentation of the respective APIs.

Below code snippet shows how to add and cancel of sub-strategies in an algo run. Sub-strategies are added in the main context, but can be removed from itself or from the main context.

from blueshift.api import order, symbol, schedule_once, cancel_strategy
from blueshift.api import add_strategy, exit_when_done, get_context
from blueshift.protocol import Strategy

class Strategy1(Strategy):

    def initialize(self, context):
        # this context refers to the sub-context for this sub-strategy
        schedule_once(self.strategy)

    def strategy(self, context, data):
        order(symbol('MSFT'), 1)
        for oid in context.orders:
            # prints only MSFT order
            print(context.orders[oid].to_dict())
        # cancelling sub-strategy from itself
        cancel_strategy(self.name)

class Strategy2(Strategy):

    def initialize(self, context):
        # this context refers to the sub-context for this sub-strategy
        schedule_once(self.strategy)

    def strategy(self, context, data):
        order(symbol('AAPL'), 1)
        for oid in context.orders:
            # prints only AAPL order
            print(context.orders[oid].to_dict())

def initialize(context):
    # this context refers to the main context
    # add the sub-strategies
    add_strategy(Strategy1('strategy1', 15000))
    add_strategy(Strategy2('strategy2', 25000))
    schedule_once(strategy)

def strategy(context, data):
    order(symbol('AMZN'), 1)
    for oid in context.orders:
        # prints only AMZN order
        print(context.orders[oid].to_dict())

    schedule_once(wrap_up)

def wrap_up(context, data):
    # cancelling sub-strategy from main context
    cancel_strategy('strategy2')
    # exit after all orders, (AAPL, MSFT and AMZN) are done
    exit_when_done()

def analyze(context, perf):
    # print the performance for AMZN from the main context
    print(context.blotter.performance)
    # print the performance for AAPL from the sub-context "strategy1"
    ctx_1 = get_context('strategy1')
    print(ctx1.blotter.performance)

The sub-strategy APIs provide a powerful way to create ensemble strategies, run multiple algos more efficiently and otherwise write clean modular algos and trading logic.