ADX based Algorithmic Trading using the Quantiacs Toolbox
This tutorial illustrates how to use the Quantiacs Toolbox to construct a simple trading strategy using the ADX (Average Directional Index). The tutorial should familiarize you with various aspects of the toolbox. At the end of this tutorial you will be able to design trading strategies of your own!
The ADX is essentially a trend strength indicator. We will identify if there is a strong trend in the market with respect to the stocks and futures in our portfolio and then decide to take a long or short position based on the market trend. Our market exposure will be determined by our interpretation of the ADX for a given market. I recommend that you refer to http://www.investopedia.com/articles/trading/07/adx-trend-indicator.asp to get a basic understanding of ADX. A general rule of thumb is that ADX values above 25 are indicators of a strong trend.
The figure above shows how the ADX of AAPL stock has changed over the period of 1 year. The white line in the bottom half of the chart shows the ADX value. We see that ADX increases with sharp price rises and falls whereas it decreases when the price remains relatively flat. We want to take advantage of these strong trends and make trades in the direction of the trend attempting to alleviate risk and increase profit potential. Note that the green and red lines along the ADX curve are directional indicators of the price. The green line is a positive directional index indicating rising prices. The converse is true for the red line. We will also be calculating these indices as they are components of the ADX.
Quantiacs Toolbox Essentials
Without further adieu let’s start implementing our trading strategy! The first thing you’d want to do is bring up the sample strategy available at https://github.com/Quantiacs/quantiacs-python/blob/master/sampleSystems/trendFollowing.py. The file contains 2 functions — myTradingSystem and mySettings. These are mandatory functions and must be declared for the toolbox. The mySettings function is where you set up attributes for your trading strategy. Some of these attributes are native to the toolbox and others can be added based on the needs of the trading strategy. Some native attributes include ‘markets’ where you define the equities that will be part of your portfolio and ‘budget’ where you define your initial capital. Please take a look at http://quantiacs-python-toolbox-documentation.readthedocs.io/en/latest/settings.html for a comprehensive list of all the native attributes.
The second function, myTradingSystem, is where we build our trading strategy. The Quantiacs Toolbox calls this function repeatedly, once for each trading day based on the start and end days defined in mySettings. This function enables you to have market exposure of your choice in various equities based on your trading strategy for each day (each iteration). The arguments to this function include opening price, closing price, daily high price, daily low price, etc for all the markets defined in the settings . These arguments are typically used to compute various indicators that assist traders in making decisions. In each iteration of the the function, you will have values that go back as many days as defined in the ‘lookback’ attribute in the settings. Please check out http://quantiacs-python-toolbox-documentation.readthedocs.io/en/latest/ts_structure.html for a comprehensive list of all the input parameters. The return value of the myTradingSystem will have the weights of the market exposure of each equity with negative value indicating a short (sell) position and a positive value indicating a long (buy) position.
In case you want to pass on custom attributes from iteration to iteration, you can do so by adding it as an attribute of the settings parameter. The myTradingSystem function, both returns and has settings as a parameter enabling us to pass on custom attributes. This will be useful for us because ADX calculations for a trading day depend on previous ADX values and we need to save its state from iteration to iteration.
Coding the ADX Based Trading Strategy
Now that we have a basic understanding of the components of the toolbox we can start coding our trading strategy. The ADX calculation can be quite cumbersome and there are a lot of terminologies involved. Please refer to the ‘Calculations’ section of http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:average_directional_index_adx for detailed steps. We will briefly overview the calculation steps in this tutorial.
First, let’s create a custom function that computes ADX value for a given trading day and market.
def calculate_ADX(market, HIGH, LOW, CLOSE, settings, ADX_period):
The function takes 6 arguments. The first one is the index of the market for which we are computing ADX. This depends on the order of the ‘markets’ attribute defined in mySettings. The next 3 arguments are high, low and closing prices for the given market over the ‘lookback’ period up to the date of the current iteration. We also pass the settings field since we need some state variables as discussed earlier. Finally, we have ADX period. ADX calculations are based on a moving average of price range expansion over a period of time. This period is usually 14 days. However, we have the option of changing this and picking the optimal period to maximize performance. For the purpose of this tutorial let’s assume that we are using a 14 day ADX period.
ADX is computed as shown below.
ADX = ((Prior ADX * 13) + Current DX Value) / 14
As seen in the equation, you also need the directional index (DX) of the current trade day which depends on the 14 day smoothed true range and 14 day smoothed directional movements. We have employed a technique called Wilder Smoothing to do the smoothing job. The code to get DX in order to then compute ADX is shown below.
Here, tradeDayVals holds all the values associated with calculating ADX. As seen in the code, we first compute the true range and directional movement values for a given trade day. We then compute the 14 day smoothed values of these terms using the previous trading day’s smoothed values and the newly computed values. The code for above computations is shown below.
It is important to note that with a 14 day ADX period you need a minimum of 28 trading days’ data to calculate the first ADX value. This is because it takes 14 days to calculate the first DX value as it requires 14 day smoothed true range and directional movement values. And it takes another 14 days to compute the first ADX value by averaging out the 14 day DX values. So for the first 27 days, we set up all the values required to be able to compute the first ADX. This is why we need a minimum number of 28 in the ‘lookback’ field of our settings. Essentially, whatever the ADX period we choose, the ‘lookback’ needs to be at least twice as long. The ADX.py file has detailed comments and you should be able to follow the logic for computing the first ADX value and subsequent ADX values easily.
Now that we are able to calculate ADX with regard to each trading day, we can formulate our trading strategy. We know that if the ADX value is 25 or higher, there a strong trend with respect to the given market. An ADX value of less than 25 is not meaningful to us, so we will have no market exposure for an equity for a given trading day when ADX is less than 25. However, if it is higher than 25, we want to know whether we need to go long or short. In order to estimate the general direction of the trend, we look at the prior 14 day average of closing prices for the given market and compare it with the current trading day. If the current price is higher, we buy hoping the rising rend continues. Otherwise we go short. Another factor to consider is the strength of the ADX value. Some markets may have values as high as 70 and others as low as 30. Therefore, we weigh our market exposure based of the strength of the ADX value.
The mandatory myTradingSystem first computes the ADX values for all the markets in our portfolio and then executes trades based on the above strategy.
The result of the simulation using historical data for a portfolio consisting of 45 futures contracts from the period of 1990 until 2017 with an ADX period of 30 days is shown below.
Clearly, the trading strategy has not done so well over time. However, there are periods where it has performed extremely well. Of course this does not say anything about our trading system as we have something very simple. But this is a great lesson as we cannot assume that a trading strategy will continue to perform just because it did amazingly well during a certain time frame. Once we design a strategy it is always a good idea to optimize our parameters to perform well with 2/3 of the historical data and then test how it performs with the remaining 1/3 of the data. If it performs well, it may be a viable strategy to deploy and continue to test with new market data over the course of time.
Back Testing and Optimization
It is always a great idea to backtest any trading strategy with historical data to get a sense of its long term performance. In order to make this process easier, you can use the Quantinator web portal available at https://www.quantinator.com/analysis/. The portal not only allows you to visualize the performance of your trading strategy but also helps you optimize various parameters in the strategy. Hence, you can maximize the performance of your trading strategy by fine-tuning the parameters. We will use the ADX trading strategy to demonstrate the functionalities of Quantinator.
In the first page, you can set the native attributes of the Toolbox, including all the markets you plan to have in your portfolio, your initial capital, date ranges, etc. You can also select the market data you require for all the iterations.
Once you have this set up, you can paste your trading strategy code as shown below. Hit ‘Run’ to visualize your trading system’s performance.
The visualization tool for ADX strategy is shown below. The tool a quite dynamic and you can view the performance of each underlying future or stock for the period of your choice.
The next step is to optimize the strategy. In the case of the ADX strategy there are 2 main parameters that have the potential to cause major changes in performance. The first is the ADX period and the second is the ADX value that we consider to be an indicator of a strong trend. By default we have set these values at 14 and 25 respectively as these are the standard values used. However, to improve our model we can provide the optimizer on Quantinator a range of values the parameters can take and pick the particular combination of these parameters that results in the best performance. In order to let the optimizer know the range of values a parameter can take, the following syntax can be used in your code.
ADX_period = 14 #%[14:7:100]#
Essentially the above statement tells the optimizer that the variable ADX_period can assume values anywhere between 14 and 100 with increments of 7. In the example above the first value used is 14. The subsequent values are incremented by 7 and the last value the variable assumes will be 98. Another increment would cause the variable to have a number greater than 100 and so the optimizer stops here.
We have also done something similar for the threshold ADX value used for triggering a trade. Please view the figure with the trading system script. Finally, with everything set up, hit the optimize button. The results should look something like this.
This is an interactive 3-d chart where you can dynamically pick relevant attributes on each axis. This helps you visualize the performance of your trading algorithm with varying combinations of parameters. In the ADX example above it is evident that the performance is greatest in the top right region of the chart (yellow spheres). The ADX_val corresponding to that region is around 50. Out of these, the best performance occurs when ADX_period is around 40.
This tool makes it easy for you to submit you trading algorithm with best possible Sharpe ratios by taking advantage of the optimizer. However, you need to be careful not to overfit for the historical data. You may be able to achieve astronomical performance by fine-tuning your system to perform ideally on the historical data and yet you may perform badly in the future. Make sure to avoid this and use the optimizer to get an intuition for your parameters and optimize logically.