Algorithmic trading based on Technical Analysis in Python

Learn how to create and implement trading strategies based on Technical Analysis!

Eryk Lewinson
Oct 23 · 8 min read
BUX Zero, our new zero-commission stock trading app

Investing was always associated with large amounts of money, both in terms of the invested amount as well as costs associated with it. Here at BUX, we want to make investing accessible to everyone. That is why we recently launched BUX Zero in the Netherlands and other European countries will follow soon! BUX Zero is a zero-commission stock trading app, which makes investing not only accessible but also easy to do directly from your phone.

This is the second article on backtesting trading strategies in Python. The previous one described how to create simple backtests using custom data — in this case — EU stocks.

This time, the goal of the article is to show how to create trading strategies based on Technical Analysis (TA in short). Quoting Wikipedia, technical analysis is a “methodology for forecasting the direction of prices through the study of past market data, primarily price, and volume”.

In this article, I show how to use a popular Python library for calculating TA indicators — TA-Lib — together with the zipline backtesting framework. I will create 5 strategies and see which one performs best over the same investment horizon.

By the end of the article, you will see how easy it is to backtest trading strategies based on Technical Analysis. Knowing that you can come up with custom strategies and easily implement them in practice with BUX Zero.

The Setup

For this article I use the following libraries:

pyfolio    0.9.2
numpy 1.14.6
matplotlib 3.0.0
pandas 0.22.0
json 2.0.9
empyrical 0.5.0
zipline 1.3.0

Strategies

In this article we use the following problem setting:

  • the investor has a capital of 1000€
  • the investment horizon covers years 2018
  • the investor can only invest in Unilever
  • we assume no transactions costs — zero-commission trading as in BUX Zero :)
  • there is no short selling (the investor can only sell what he/she currently owns)
  • when the investor opens a position (buys the stock), the investor goes “all in” — allocates all available resources for the purchase

We start with the most basic strategy — Buy and Hold. The idea is that we buy a certain asset and do not do anything for the entire duration of the investment horizon. So at the first possible date, we buy as much stock as we can with our capital and do nothing later.

This simple strategy can also be considered a benchmark for more advanced ones — because there is no point in using a very complex strategy that generates less money (in general or due to transaction costs) than buying once and doing nothing.

We load the performance DataFrame:

buy_and_hold_results = pd.read_pickle('buy_and_hold.pkl')

What can happen here (it did not, but it is good to be aware of the possibility) is the sudden appearance of negative ending_cash. The reason for that could be the fact that the amount of shares we want to buy is calculated at the end of the day, using that day’s (closing) price. However, the order is executed on the next day, and the price can change significantly. In zipline the order is not rejected due to insufficient funds, but we can end up with a negative balance. This can happen mostly with strategies that go “all-in”. We could come up with some ways to avoid it — for example manually calculating the number of shares we can buy the next day and also including some markup to prevent such a situation from occurring, however, for simplicity we accept that this can happen.

We use a helper function to visualize some details of the strategy: the evolution of the portfolio’s value, the transactions on top of the price series, and the daily returns.

qf.visualize_results(buy_and_hold_results, 'Buy and Hold Strategy - UNA', '€')

We also create the performance summary (using another helper function), which will be used in the last section:

buy_and_hold_perf = qf.get_performance_summary(buy_and_hold_results.returns)

For brevity, we will not show all these steps (such as loading the performance DataFrame or getting the performance summary) for each strategy, because they are done in the same manner each time.

Additionally, we can use a library called pyfolio to quickly calculate the most important performance metrics of the returns series.

pf.create_simple_tear_sheet(buy_and_hold_results.returns)
Performance of the Buy and Hold strategy

Simple Moving Average Strategy

The second strategy we consider is based on the simple moving average (SMA). The logic of the strategy can be summarized by the following:

  • when the price crosses the 20-day SMA upwards — buy shares
  • when the price crosses the 20-day SMA downwards — sell all the shares
  • the moving average uses 19 prior days and the current day — the trading decision is for the next day

Below we illustrate the strategy:

The plot below shows the price series together with the 20-day moving average. We have additionally marked the orders, which are executed on the next trading day after the signal was generated.

Moving Average Crossover

This strategy can be considered an extension of the previous one — instead of a single moving average, we use two averages of different window sizes. The 100-day moving average is the one that takes longer to adjust to sudden price changes, while the 20-day one is much faster to account for sudden changes.

The logic of the strategy is as follows:

  • when the fast MA crosses the slow one upwards, we buy the asset
  • when the slow MA crosses the fast one upwards, we sell the asset

Bear in mind that many different window-lengths combinations defining the fast and slow MA can be considered for this strategy.

Below we plotted the two moving averages on top of the price series. We see that the strategy generated much fewer signals than the one based on SMA.

MACD

MACD stands for Moving Average Convergence/Divergence and is an indicator/oscillator used in technical analysis of stock prices.

MACD is a collection of three time-series calculated using historical close prices:

  • the MACD series — the difference between the fast (shorter period) and slow (longer period) exponential moving averages
  • the signal — EMA on the MACD series
  • the divergence — the difference between the MACD series and the signal

MACD is parametrized by the number of days used to calculate the three moving averages — MACD(a,b,c). The parameter a corresponds to the fast EMA, b to the slow EMA, and c to the MACD signal EMA. The most common setup, also used in this article, is MACD(12,26,9). Historically, these numbers corresponded to 2 weeks, 1 month and 1.5 weeks based on a 6-day working week.

One thing to remember is that MACD is a lagging indicator, as it is based on moving averages. That is why the MACD is less useful for stocks that do not exhibit a trend or are trading with erratic price action.

The strategy we use in this article can be described by:

  • buy shares when the MACD crosses the signal line upwards
  • sell shares when the MACD crosses the signal line downwards

Below we plot the MACD and the signal lines, where the crossovers indicate buy/sell signals. Additionally, one could plot the MACD divergence in the form of a barplot (it is commonly referred to as the MACD histogram).

RSI

RSI stands for the Relative Strength Index, which is another technical indicator we can use to create trading strategies. The RSI is classified as a momentum oscillator and it measures the velocity and magnitude of directional price movements. Momentum describes the rate at which the price of the asset rises or falls.

Without going into too many technical details, the RSI measures momentum as the ratio of higher closes to lower closes. Assets with more/stronger positive changes have higher RSI than assets with more/stronger negative changes.

The output of the RSI is a number on a scale from 0 to 100 and it is typically calculated on a 14-day basis. To generate the trading signals, it is common to specify the low and high levels of the RSI at 30 and 70, respectively. The interpretation of the thresholds is that the lower one indicates that the asset is oversold, and the upper one that the asset is overbought.

Sometimes, a medium level (halfway between low and high) is also specified, for example in case of strategies which also allow for short-selling. We can also select more extreme thresholds such as 20 and 80, which would then indicate stronger momentum. However, this should be specified using domain knowledge or by running backtests.

The strategy we consider can be described as:

  • when the RSI crosses the lower threshold (30) — buy the asset
  • when the RSI crosses the upper threshold (70) — sell the asset

Below we plot the RSI together with the upper and lower threshold.

Evaluating the performance

The last step involves putting all the performance metrics into one DataFrame and inspecting the results. We can see that in the case of our backtest, the strategy based on the RSI performed best in terms of generated returns. It also had the highest Sharpe ratio — the highest excess return (in this case return, as we do not consider a risk-free asset) per unit of risk. The second-best strategy turned out to be the simple Buy and Hold. It is also important to notice that two strategies — based on the MACD and SMA — actually generated losses.

perf_df = pd.DataFrame({'Buy and Hold': buy_and_hold_perf,
'Simple Moving Average': sma_perf,
'Moving Average Crossover': mac_perf,
'MACD': macd_perf,
'RSI': rsi_perf})
perf_df.transpose()

Conclusions

In this short article, I showed how to combine zipline with talib in order to backtest trading strategies based on popular technical indicators such as moving averages, the MACD, the RSI, etc. But this was only the beginning, as it is possible to create much more sophisticated strategies.

Some of the possible future directions:

  • include multiple assets into the strategies
  • allow for short-selling
  • mix the indicators
  • backtest different parameters for each strategy to find the best-performing ones

If you are interested in stocks trading with no commission and nice UI be sure to check out BUX Zero. We are currently live in the Netherlands and other European countries will follow soon!

We must also remember that the fact that the strategy performed well in the past is no guarantee that this will happen again in the future.

You can find the code used for this article on my GitHub.

Inside BUX

Stories of the people who make BUX.

Eryk Lewinson

Written by

Data Scientist, ML/DL enthusiast, quantitative finance, gamer.

Inside BUX

Stories of the people who make BUX.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade