19.03.2021
In the previous article, I’ve mentioned that ETNA Robot is designed primarily for automated trading and is focused on providing market data to strategies, enabling them to make better decisions based on their algorithms. We consider Level I market data (quotes) as primary events for strategies. But these streams are rather huge and fluctuating, so strategies, which are designed to analyze relatively long-term trends and regularities, are usually based on cumulative data sources like time bars, which aggregate prices and volumes for a specific time frame. Time bars may not be the only source of decision making. It’s useful to analyze prices across several periods and discover more long-term regularities, which are lying in the basis. This is done usually with the help of technical indicators. There are tens of known indicators, but some investors may use their own implementations based on existing ones or completely new logic and formulas. I’m going to explain below how to implement custom indicators in Robot and use them in strategies.
To say it simply, any indicator in Robot is considered to be a function over a set of bars. This concept is simple and fully describes how indicators are treated by the system. In this way, implementing an indicator is just writing a method, which can calculate a single value on a given list of bars. Indicators may be also used as ‘helping functions’ for strategies, since their calculation is based on double-precision float numbers, though strategies use big-decimal numbers to ensure precision of pricing and quantities during calculations, that’s why indicators are also a good point to inject third-party statistics and math libraries.
I’d like to show how to implement a simple moving average indicator to make you familiar with the basics of indicators.
The first thing we need to do is to implement two classes, which extend Indicator and Value classes. Also, we can use additional annotations to make displaying of the indicators on the client-side easier.
public class SmaValue extends Value { private final double sma; public SmaValue(V base) { super(base); this.sma = base.getSma(); } public SmaValue(Date date, double sma) { super(date); this.sma = sma; } @ValueMeta(name = "SMA", color = 0xff0000, isMain = true) public double getSma() { return sma; } } @TechnicalIndicator(name = "Simple Moving Average") public class SimpleMovingAvgIndicator extends Indicator { @Override protected List<IndicatorBase<List, ? extends Value>> getInitializedBoundIndicators() { return emptyList(); } @Override protected SmaValue calculateIndicator(List source) { … } }
There are several special methods and annotations we need to focus on. Let’s look at base classes first. The base class for indicator value is Value. This class plays two roles: it contains the date field, which is required for each indicator, since all their results are bound to price data at a specific date; also this class is a marker to indicate that a particular object is actually an indicator value. Other fields are selected depending on the indicator’s nature. In the current case we have just average price value, but it may be also several values in channels or a sort of enumeration, if the indicator is used as a signal provider for a strategy. Indicator values may also have ValueMeta annotation. This annotation adds chart-specific descriptions to the value point: a human-friendly name, a type of chart, color and whether the value is a primary characteristic of the point. These data are used by the client to properly display indicators.
There are actually two basic classes for indicators: Indicator and ComplexIndicator. They differ in data sets, which are provided for calculation. A simple indicator uses single-symbol data set; though complex indicator is provided with a multi-symbol set, which means that it can be used to calculate correlations or other common characteristics of several price sequences. We use a single-symbol indicator for SMA, since it uses only prices of a single series. Indicators also have a specific annotation TechnicalIndicator, which is used to properly display indicators’ charts on the client side. This annotation determines a human-friendly name and whether the indicator should be shown in a separate frame.
Let’s take a look at the methods we need to implement. The first method, which ought to be implemented, is getInitializedBoundIndicators. To properly understand it, we need to become familiar with bound indicators concept.
Some indicators may rely on values of other indicators. For instance, EMA uses SMA as a first value in the series. You may also create custom indicators, combining different ones together (e.g., RSI and Fisher). In this case, a new indicator will be based on values of underlying or ‘bound’ indicators and the system will ensure that these indicators are calculated before the calculation of a compound indicator begins. Since SMA is not based on other indicators, we just return an empty list.
When the bound indicators’ values are ready, the calculation of the indicator begins. It’s done in calculateIndicator method. This method receives a list of the latest known bars (they are called in indicators ‘source data’ to distinguish them from bars provided to strategies). The list is ordered by date descending and the first element references to the latest known bar. The convention for this method requires that it should return a new indicator value for the current date (date of the latest bar), or null if the value can’t be calculated. This requirement makes it clear for the strategy and the user when the indicator’s value is zero and when it can exists at all. Assuming said above, the implementation of the SMA calculation may look like this.
@Override protected SmaValue calculateIndicator(List source) { // current date Date date = source.get(0).getDate(); if (source.size() >= interval) { //sum of closing prices divided by period double sum = 0; for (int i = 0; i < interval; i++) { sum += source.get(i).getClose(); } double result=sum / interval; return new SmaValue(date,result); } else { // not enough data - nothing to calculate and return return null; } }
You may notice, that we reference to an interval variable. It’s known that SMA calculates average price during specific period. This period is called ‘interval’ and measured in bar intervals (e.g., when we use hourly bars and indicator with interval 3, than the value is an average price for the last 3 hours). Also it’s notable that when there are not enough bars to calculate the value (size is less than interval) the method returns null.
Indicator’s properties, which influence the calculation process and may be changed by user or a strategy, should be specially declared with the Property annotation.
@Property(name = "Interval", defaultValue = "10") public int getInterval() { return interval; } public void setInterval(int interval) { this.interval = interval; }
This annotation lets the system know what parameters can be configured. Also, you should notice that this parameter influences the calculation process and applies certain limitations on the source series; in the current case, the number of bars available should be equal or greater than interval value. To indicate, that the indicator have specific requirements for series length, we can use getRequiredNumOfSourceDataItems method.
@Override public int getRequiredNumOfSourceDataItems() { return interval; }
This method is used by the indicator’s provider in core to store and provide enough historical data for calculations, but not to over-use memory (store more bars, than could be used).
In this article I’ve described how to create a simple moving average indicator using ETNA Robot API. This API provides ability to quickly create a simple indicator or go deeply and build new indicators above existing ones. Also it’s possible to create indicators across several bar series to discover their correlation. In the upcoming articles I’m going to demonstrate how to create a simple strategy which uses this indicator.
Demo RIA Software
Manage portfolios with advanced rebalancing and real-time insights.
Access customizable client reports and streamlined compliance tools.
Designed for advisors seeking efficient client and portfolio management.
Demo Advanced Trading Platform
Test multi-asset strategies with real-time and historical data.
Analyze market depth, execute complex options, and algorithmic orders.
Ideal for refining strategies and risk management before live trading.
Demo Paper Trading Platform
Practice trading with virtual funds in real market conditions.
Simulate cash, margin, and day-trader accounts to gain experience.
Perfect for honing skills in a risk-free, customizable environment.