Book Image

Python for Finance Cookbook

By : Eryk Lewinson
Book Image

Python for Finance Cookbook

By: Eryk Lewinson

Overview of this book

Python is one of the most popular programming languages used in the financial industry, with a huge set of accompanying libraries. In this book, you'll cover different ways of downloading financial data and preparing it for modeling. You'll calculate popular indicators used in technical analysis, such as Bollinger Bands, MACD, RSI, and backtest automatic trading strategies. Next, you'll cover time series analysis and models, such as exponential smoothing, ARIMA, and GARCH (including multivariate specifications), before exploring the popular CAPM and the Fama-French three-factor model. You'll then discover how to optimize asset allocation and use Monte Carlo simulations for tasks such as calculating the price of American options and estimating the Value at Risk (VaR). In later chapters, you'll work through an entire data science project in the financial domain. You'll also learn how to solve the credit card fraud and default problems using advanced classifiers such as random forest, XGBoost, LightGBM, and stacked models. You'll then be able to tune the hyperparameters of the models and handle class imbalance. Finally, you'll focus on learning how to use deep learning (PyTorch) for approaching financial tasks. By the end of this book, you’ll have learned how to effectively analyze financial data using a recipe-based approach.
Table of Contents (12 chapters)

Calculating the relative strength index and testing a long/short strategy

The RSI is an indicator that uses the closing prices of an asset to identify oversold/overbought conditions. Most commonly, the RSI is calculated using a 14-day period, and it is measured on a scale from 0 to 100 (it is an oscillator). Traders usually buy an asset when it is oversold (if the RSI is below 30), and sell when it is overbought (if the RSI is above 70). More extreme high/low levels, such as 80-20, are used less frequently and, at the same time, imply stronger momentum.

In this recipe, we build a trading strategy with the following rules:

  • We can go long and short.
  • For calculating the RSI, we use 14 periods (trading days).
  • Enter a long position if the RSI crosses the lower threshold (standard value of 30) upwards; exit the position when the RSI becomes larger than the middle level (value of 50).
  • Enter a short position if the RSI crosses the upper threshold (standard value of 70) downwards; exit the position when the RSI becomes smaller than 50.
  • Only one position can be open at a time.

We evaluate the strategy on Facebook's stock in 2018, and apply a commission of 0.1%.

How to do it...

Execute the following steps to implement a strategy based on the RSI.

  1. Import the libraries:
from datetime import datetime
import backtrader as bt
  1. Define the signal strategy, based on bt.SignalStrategy:
class RsiSignalStrategy(bt.SignalStrategy):
params = dict(rsi_periods=14, rsi_upper=70,
rsi_lower=30, rsi_mid=50)

def __init__(self):

rsi = bt.indicators.RSI(period=self.p.rsi_periods,
upperband=self.p.rsi_upper,
lowerband=self.p.rsi_lower)

bt.talib.RSI(self.data, plotname='TA_RSI')


rsi_signal_long = bt.ind.CrossUp(rsi, self.p.rsi_lower,
plot=False)
self.signal_add(bt.SIGNAL_LONG, rsi_signal_long)
self.signal_add(bt.SIGNAL_LONGEXIT, -(rsi >
self.p.rsi_mid))

rsi_signal_short = -bt.ind.CrossDown(rsi, self.p.rsi_upper,
plot=False)
self.signal_add(bt.SIGNAL_SHORT, rsi_signal_short)
self.signal_add(bt.SIGNAL_SHORTEXIT, rsi < self.p.rsi_mid)
  1. Download the data:
data = bt.feeds.YahooFinanceData(dataname='FB', 
fromdate=datetime(2018, 1, 1),
todate=datetime(2018, 12, 31))
  1. Set up and run the backtest:
cerebro = bt.Cerebro(stdstats = False)

cerebro.addstrategy(RsiSignalStrategy)
cerebro.adddata(data)
cerebro.broker.setcash(1000.0)
cerebro.broker.setcommission(commission=0.001)
cerebro.addobserver(bt.observers.BuySell)
cerebro.addobserver(bt.observers.Value)

cerebro.run()
  1. Plot the results:
cerebro.plot(iplot=True, volume=False)

Running the code results in the following graph:

We look at the triangles in pairs. The first one indicates opening a position (going long if the triangle is blue and facing up; going short if the triangle is red and facing down). The next triangle (of the opposite color and direction) indicates closing a position. We can match the opening and closing of positions with the RSI below the chart. Sometimes, there are multiple triangles of the same color in sequence. That is because the RSI fluctuates around the line of opening a position, crossing it multiple times, as we can see on the preceding RSI chart. But the actual position is only opened on the first instance of a signal (no accumulation is the default setting).

How it works...

In this recipe, we built a trading strategy on top of bt.SignalStrategy. First, we defined the indicator (RSI), with selected arguments. We also added bt.talib.RSI(self.data,plotname='TA_RSI'), just to show that backtrader provides an easy way to use indicators from the popular TA-Lib library (the TA-Lib library must be installed for the code to work). The trading strategy does not depend on this second indicator; it is only plotted for reference, and we could add an arbitrary number of indicators.

Even when adding indicators for reference only, their existence influences the "warm-up period." For example, if we additionally included a 200-day SMA indicator, no trade would be carried out before there exists at least one value for the SMA indicator.

The next step was to define signals. To do so, we used the bt.CrossUp/bt.CrossDown indicators, which returned 1 if the first series (price) crossed the second (upper or lower RSI threshold) from below/above, respectively. For entering a short position, we made the signal negative, by adding a - in front of the bt.CrossDown indicator.

We can disable printing any indicator, by adding plot=False to the function call.

As the last step of defining the strategy, we added tracking of all the signals, by using the signal_add method. For exiting the positions, the conditions we used (an RSI value higher/lower than 50) resulted in a Boolean, which we had to make negative in case of exiting a long position: -True is the same as -1.

Setting up and running the backtest is analogous to the previous recipe, so please refer to it if in doubt regarding any of the steps.