AlgoTraderAlgoTrader Documentation

Chapter 4. Creating a Trading Strategy

4.1. AlgoTrader Strategy Wizard
4.2. Adding Strategy Logic
4.3. Adding Strategy Logic in Python

This section will give a quick introduction on how to create a trading strategy by discussing the EMA (Exponential Moving Average) Strategy

Note

The AlgoTrader 30-day free trial already contains the final EMA strategy with all artifacts. In case you want to follow below steps please delete the existing EMA strategy first, by

  • Removing the existing Maven project by opening the examples pom.xml file, searching (CTRL+F) for the string module>ema< and removing up to 2 lines from that file where it is found. You will be asked if you want to remove them from the project too. Please confirm that by pressing yes.

  • Deleting the code from disk by right-clicking on the ema strategy and selecting Delete.

The AlgoTrader Strategy Wizard provides an easy way to automatically create all artifacts necessary for an AlgoTrader based trading strategy. The Wizard can be started by right-clicking on the examples project and selecting New / Module.

which will bring up the following screen where you should select Maven on the left, check the Create from archetype box and in this case select the algotrader-archetype-simple, then press Next.

Configure the next screen as displayed on the picture and click Next. The Version number will be different for later AlgoTrader versions, so please leave that value as it was set automatically.

On the following page, add 2 additional name/value pairs using the + button:

When clicking Finish the Strategy Wizard will create a new project called ema.

The Strategy Wizard also generated boiler plate code that needs to be replaced with the actual logic of the EMA strategy.

AlgoTrader strategies are regular Java programs. Due to this any type of java library or add-ons can be used. The EMA strategy is based on the TA4J library which contains a collection of over 100 technical indicators.

Now, double click the EMAService.java file which contains the main logic of the EMA strategy.

The header of the EMAService.java is already generated and no further changes are necessary. It contains the java class name (EMAService) as well as the name of the interface it is derived from (StrategyService).

Note

For Spring Auto-Wiring to work the package name needs to be ch.algotrader.strategy. If a different package is assigned services (e.g. OrderService and LookupService) will not be available.

The next part of the EMAService.java contains settings the strategy will use. Three of them are already generated by the Wizard but a few more need to be added.

    private final long accountId = 100;

    private final long securityId = 25;
    private final BigDecimal orderQuantity = new BigDecimal("10000");
    private final int emaPeriodShort = 10;
    private final int emaPeriodLong = 20;
    private final AdapterType defaultAdapterType = AdapterType.IB;
    private TimeSeries series;
    private DifferenceIndicator emaDifference;
  • The accountId defines the id of the account the strategy will use for trading.

  • The securityId will define the id of the instrument the strategy will trade.

  • The orderQuantity is the number of contracts the strategy will trade.

  • The emaPeriodSort is the look back period of the shorter EMA indicator.

  • The emaPeriodLong is the look back period of the longer EMA indicator.

  • The defaultAdapterType indicates we want to get market data from Binance by default.

In addition, the following two fields need to be defined:

  • The TimeSeries object used by the exponential moving average indicators

  • The DifferenceIndicator which will contain the difference between the short and the long EMA

Next, the Java Constructor for the EMAService class needs to be updated:

public EMAService() {

        setStrategyName("EMA");
        init();
    }

And the init() method called therein:

private void init() {

        this.series = new BaseTimeSeries();
        this.series.setMaximumBarCount(this.emaPeriodLong);
        ClosePriceIndicator closePriceIndicator = new ClosePriceIndicator(this.series);
        EMAIndicator emaShort = new EMAIndicator(closePriceIndicator, this.emaPeriodShort);
        EMAIndicator emaLong = new EMAIndicator(closePriceIndicator, this.emaPeriodLong);
        this.emaDifference = new DifferenceIndicator(emaShort, emaLong);
    }
  • First the EMAService constructor sets the name of the Strategy used during the back test.

  • Next the TimeSeries object is initialized to a length of one Bar. In addition, the number of bars the Time Series is set (in this case 20 Bars).

  • Next a ClosePriceIndicator is created which causes the system to look at closing prices of Bar events.

  • Then both the short and the long EMA indicator need to be created by associating them with the ClosePriceIndicator and setting the lookbackPeriod (in this case 10 and 20).

  • Last the DifferenceIndicator needs to be created which contains the difference between the sort EMA and the long EMA indicator.

Next, update the onInit (an AlgoTrader Live Cycle Method) method, which simply calls the init() method we defined earlier.

@Override

    protected void onInit(LifecycleEventVO event) {
        init();
    }

Next, update the onStart (an AlgoTrader Live Cycle Method) method, which will be called when the strategy starts up.

@Override

    public void onStart(final LifecycleEventVO event) {
        getSubscriptionService().subscribeMarketDataEvent(getStrategyName(), this.securityId, defaultAdapterType);
    }

For further details please visit the AlgoTrader documentation regarding Life Cycle Events.

The onStart methods calls subscribeMarketDataEvent of the SubscriptionService by passing the strategyName and the securityId of the instrument the strategy wants to receive market data for. The SubscriptionService is automatically made available to the strategy through Spring Auto Wiring.

Next, update the onBar method, which will be invoked on every incoming Bar:

@Override

public void onBar(BarVO bar) {
    this.series.addBar(toBar(bar));
    int i = this.series.getEndIndex();
    Num currentValue = this.emaDifference.getValue(i);
    Num previousValue = this.emaDifference.getValue(- 1);
    if (currentValue.isPositive() && previousValue.isNegativeOrZero()) {
        sendOrder(Side.BUY);
    } else if (currentValue.isNegative() && previousValue.isPositiveOrZero()) {
        sendOrder(Side.SELL);
    }
}
  • The method first calls the addBar method which will add the incoming Bar to the Time Series defined above

  • Next, the index i of the last element of the Time Series is retrieved

  • Then the value of the last and the second-last element of the DifferenceIndicator is retrieved

Then the actual trading rules need to be defined:

  • If the current value of the DifferenceIndicator is positive and the previous value was negative or zero a BUY order is sent. In other words, if the short EMA crossed above the long EMA a BUY order is sent.

  • If the current value of the DifferenceIndicator is negative and the previous value was positive or zero a SELL order is sent. In other words, if the short EMA crossed below the long EMA a SELL order is sent.

The trading logic is depicted in the following chart also.

As the last item, create the sendOrder method, which will take care of constructing an order object and handing it over to the OrderService:

private void sendOrder(Side side) {


    MarketOrderVO order = MarketOrderVOBuilder.create()
        .setStrategyId(getStrategy().getId())
        .setAccountId(this.accountId)
        .setSecurityId(this.securityId)
        .setQuantity(this.orderQuantity)
        .setSide(side)
        .build();
    getOrderService().sendOrder(order);
}

The sendOrder method creates a MarketOrder by using the MarketOrderVOBuilder and assigns the strategyId, the accountId, the securityId, the orderQuantity, the order side (BUY or SELL) and finally calls build to create the MarketOrder object. The order object is then handed over to the OrderService which will execute the order. The OrderService is automatically made available to the strategy through Spring Auto Wiring.

For further details on how orders are please visit the AlgoTrader documentation regarding Order Management

In addition the following Java import statements need to be added to the top:

import ch.algotrader.entity.marketData.BarVO;

import ch.algotrader.entity.trade.MarketOrderVO;
import ch.algotrader.entity.trade.MarketOrderVOBuilder;
import ch.algotrader.enumeration.AdapterType;
import ch.algotrader.enumeration.Side;
import ch.algotrader.service.StrategyService;
import ch.algotrader.vo.LifecycleEventVO;
import org.ta4j.core.BaseTimeSeries;
import org.ta4j.core.TimeSeries;
import org.ta4j.core.indicators.EMAIndicator;
import org.ta4j.core.indicators.helpers.ClosePriceIndicator;
import org.ta4j.core.indicators.helpers.DifferenceIndicator;
import org.ta4j.core.num.Num;
import java.math.BigDecimal;
import static ch.algotrader.util.TA4JUtil.toBar;

The implementation of the trading strategy is now finished a first back test can be started according to these instructions.

The EMA strategy is an example strategy based on Java code only. For details on how to build a trading strategy using Esper please visit the AlgoTrader documentation regarding Strategy Development

The AlgoTrader Python Interface allows you to implement strategies in Python 2 and 3.

Please refer to the Reference Guide for more details on how to setup AlgoTrader with Python, most notably the installation and Python strategy development sections.

The AlgoTrader trial version has an example Python strategy installed. You can find it by searching for ema-python-strategy.py and opening the file in your Python IDE.

The header of the file contains all necessary imports, in particular the algotrader_com interface.

import logging
from decimal import Decimal

import numpy as np

from algotrader_com.domain.order import MarketOrder
from algotrader_com.interfaces.connection import connect_to_algotrader,
wait_for_algotrader_to_disconnect
from algotrader_com.services.strategy import StrategyService
...

The next part of the file contains settings that the strategy will use.

...
ACCOUNT_ID = 1
SECURITY_ID = 25
ORDER_QUANTITY = Decimal("10000")
DEFAULT_ADAPTER_TYPE = "IB"
EMA_PERIOD_SHORT = 10
EMA_PERIOD_LONG = 20
...

  • The ACCOUNT_ID defines the id of the account, which the strategy will use for trading.

  • The SECURITY_ID defines the id of the instrument, which the strategy will trade.

  • The ORDER_QUANTITY is the number of contracts the strategy will trade.

  • The DEFAULT_ADAPTER_TYPE indicates from where we get market data and where we want to trade (Interactive Brokers by default).

  • The EMA_PERIOD_SHORT is the lookback period of the shorter EMA indicator.

  • The EMA_PERIOD_LONG is the lookback period of the longer EMA indicator.

In addition, the following fields need to be defined:

  • The close_price_window1 and close_price_window2, used by the exponential moving average indicators.

  • The strategy_id, a strategy variable that holds the Strategy Id, fetched from the AlgoTrader server.

Next, the Python Class for the EMAStrategyService needs to be created:

...
class EMAStrategyService(StrategyService):

    def __init__(self):
    StrategyService.__init__(self)

    STRATEGY_NAME = "EMA"
    previous_difference = 0
    first_order_sent = False
    position = 0.0
    ...

  • First, the Superclass StrategyService constructor is called.

  • Next, the STRATEGY_NAME global variable is set. That will be later passed to the Entry Point

  • The previous_difference variable is initialized. This will be used to hold a difference between two EMA series.

  • Then, the first_order_sent boolean variable is initialized with false value. This will be used to determine the closing quantity of orders (First order will have size ORDER_QUANTITY, all following orders must have size ORDER_QUANTITY * 2 since they have to close existing position and open new in the opposite direction).

  • Next, the position variable is initialized. This will be used to store the current position size.

Implement the on_init method, which will be called when the strategy gets initialized. We have to pass STRATEGY_NAME to the Entry Point in this method.

   ...
    def on_init(self, lifecycle_event):
        self.python_to_at_entry_point.set_strategy_name(self.STRATEGY_NAME)
    ...

Next, update the on_start (an AlgoTrader Live Cycle Method) method, which will be called when the strategy starts up.

    ...
    def on_start(self, lifecycle_event):
        # noinspection PyBroadException
        try:
            self.python_to_at_entry_point.subscription_service.subscribe_market_data_event(
                self.STRATEGY_NAME, SECURITY_ID, DEFAULT_ADAPTER_TYPE)
        except:
            pass
    ...

For further details please visit the AlgoTrader documentation regarding Life Cycle Events.

The on_start method calls subscribe_market_data_event of the subscription_service exposed by the AlgoTrader's Python interface by passing the STRATEGY_NAME and the SECURITY_ID of the instrument the strategy wants to receive market data for.

Next, update the on_bar method, which will be invoked on every incoming Bar:

    ...
    def on_bar(self, bar):

        close_price_window1.append(float(bar.close))
        close_price_window2.append(float(bar.close))

        ...

        if len(close_price_window1) > EMA_PERIOD_SHORT + 1:  # remove the oldest element from the list
            close_price_window1.pop(0)  # remove the oldest element from the list
        if len(close_price_window2) > EMA_PERIOD_LONG + 1:  # remove the oldest element from the list
            close_price_window2.pop(0)  # remove the oldest element from the list

        # if we have enough data already, calculate EMA averages difference, buy/sell on cross
        if len(close_price_window2) >= EMA_PERIOD_LONG:

        ...

  • The method first updates close_price_window1 and close_price_window2 with the latest bar received in the method's parameter.

  • Then the lengths of the price windows (close_price_window1 and close_price_window2) must be cut to the desired size. In our case, close_price_window1 will have a maximum length of 10 (10 period EMA) and close_price_window2 will have a maximum length of 20 (20 periods EMA).

  • Next, we check if we already have enough data to compute the EMA, which we need to proceed with the strategy logic.

Then the actual trading rules are defined:

           ...
            close_price_window1.pop(0)

            ema1 = _numpy_ewma_vectorized_v2(np.array(close_price_window1), EMA_PERIOD_SHORT)
            ema2 = _numpy_ewma_vectorized_v2(np.array(close_price_window2), EMA_PERIOD_LONG)
            difference = ema1[-1] - ema2[-1]
            global strategy_id
            if strategy_id is None:
                strategy_id = self.python_to_at_entry_point.get_strategy_id()

            account_id = ACCOUNT_ID
            security_id = SECURITY_ID
            if not self.first_order_sent:
                quantity = ORDER_QUANTITY
            else:
                quantity = ORDER_QUANTITY * 2  # closing opposite position and opening new one

            if difference > 0.0 and (self.previous_difference == 0 or self.previous_difference < 0.0):
                side = "BUY"
                market_order = MarketOrder(quantity=quantity, side=side, strategy_id=strategy_id, account_id=account_id,
                                           security_id=security_id)
                self.python_to_at_entry_point.order_service.send_order(market_order)
                self.position += float(market_order.quantity)
                self.first_order_sent = True
            if difference < 0.0 and (self.previous_difference == 0 or self.previous_difference > 0.0):
                side = "SELL"
                market_order = MarketOrder(quantity=quantity, side=side, strategy_id=strategy_id, account_id=account_id,
                                           security_id=security_id)
                self.python_to_at_entry_point.order_service.send_order(market_order)
                self.position -= float(market_order.quantity)
                self.first_order_sent = True
            self.previous_difference = difference
            ...

  • Calculate both EMA indicators (in the example we use a custom function for fast EMA calculation, see _numpy_ewma_vectorized_v2 for details).

  • Calculate the difference indicator (ema1 - ema2).

  • If the current value of the difference indicator is positive and the previous value was negative or zero a BUY order is sent. In other words, if the short EMA crossed above the long EMA a BUY order is sent.

  • If the current value of the difference indicator is negative and the previous value was positive or zero a SELL order is sent. In other words, if the short EMA crossed below the long EMA a SELL order is sent.

The trading logic is depicted in the following chart:

The orders are sent through AlgoTrader's Python interface using the send_order method in the order_service provided by Entry Point.

...
market_order = MarketOrder(quantity=quantity, side=side, strategy_id=strategy_id, account_id=account_id, security_id=security_id)
self.python_to_at_entry_point.order_service.send_order(market_order)
...

The above creates a MarketOrder entity and assigns the strategy_id, the account_id, the security_id, the quantity, the order side (BUY or SELL). The order object is then handed over to the order_service which will execute the order.

For further details on how order are created and behave, please consult the AlgoTrader documentation regarding Order Management .

In addition, the following statements were added to the bottom of the strategy.

The first is optional. If not specified, all callback methods are subscribed:

...
only_subscribe_methods_list = ["onInit", "onStart", "onExit", "onBar", "onTick"]
...

The line below connects the Python interface to the AltoTrader server:

...
_python_to_at_entry_point = connect_to_algotrader(EMAStrategyService(), only_subscribe_methods_list)
...

The below tries to subscribe to market data if the AlgoTrader server is already running on strategy start-up. Otherwise, the subscriptions will be initialized during the onStart lifecycle event:

...
# noinspection PyBroadException
try:  # try subscribing to market data, if AT is already up. otherwise data will be subscribed on START lifecycle event
    _python_to_at_entry_point.subscription_service\
        .subscribe_market_data_event(EMAStrategyService.STRATEGY_NAME, SECURITY_ID, DEFAULT_ADAPTER_TYPE)
except:
    pass
...

This example strategy will stop when the server process is stopped.

The EMA strategy is an example strategy based on Python code only. For details on how to build a trading strategy using Java or Esper please visit the AlgoTrader documentation regarding Strategy Development