Search icon
Arrow left icon
All Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Newsletters
Free Learning
Arrow right icon
Python for Finance Cookbook
Python for Finance Cookbook

Python for Finance Cookbook: Over 50 recipes for applying modern Python libraries to financial data analysis

By Eryk Lewinson
$43.99
Book Jan 2020 432 pages 1st Edition
eBook
$29.99 $20.98
Print
$43.99
Subscription
$15.99 Monthly
eBook
$29.99 $20.98
Print
$43.99
Subscription
$15.99 Monthly

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Black & white paperback book shipped to your address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Buy Now

Product Details


Publication date : Jan 31, 2020
Length 432 pages
Edition : 1st Edition
Language : English
ISBN-13 : 9781789618518
Category :
Table of content icon View table of contents Preview book icon Preview Book

Python for Finance Cookbook

Technical Analysis in Python

In this chapter, we will cover the basics of technical analysis (TA) in Python. In short, TA is a methodology for determining (forecasting) the future direction of asset prices and identifying investment opportunities, based on studying past market data, especially the prices themselves and the traded volume.

We begin by introducing a simple way of visualizing stock prices using the candlestick chart. Then, we show how to calculate selected indicators (with hints on how to calculate others using selected Python libraries) used for TA. Using established Python libraries, we show how easy it is to backtest trading strategies built on the basis of TA indicators. In this way, we can evaluate the performance of these strategies in a real-life context (even including commission fees and so on).

At the end of the chapter, we also demonstrate how to create an interactive dashboard in Jupyter Notebook, which enables us to add and inspect the predefined TA indicators on the fly.

We present the following recipes in this chapter:

  • Creating a candlestick chart
  • Backtesting a strategy based on simple moving average
  • Calculating Bollinger Bands and testing a buy/sell strategy
  • Calculating the relative strength index and testing a long/short strategy
  • Building an interactive dashboard for TA

Creating a candlestick chart

A candlestick chart is a type of financial graph, used to describe a given security's price movements. A single candlestick (typically corresponding to one day, but a higher frequency is possible) combines the open, high, low, and close prices (OHLC). The elements of a bullish candlestick (where the close price in a given time period is higher than the open price) are presented in the following image (for a bearish one, we should swap the positions of the open and close prices):

In comparison to the plots introduced in the previous chapter, candlestick charts convey much more information than a simple line plot of the adjusted close price. That is why they are often used in real trading platforms, and traders use them for identifying patterns and making trading decisions.

In this recipe, we also add moving average lines (which are one of the most basic technical indicators), as well as bar charts representing volume.

Getting ready

In this recipe, we download Twitter's (adjusted) stock prices for the year 2018. We use Yahoo Finance to download the data, as described in the Getting data from Yahoo Finance recipe, found in Chapter 1, Financial Data and Preprocessing. Follow these steps:

  1. Import the libraries:
import pandas as pd 
import yfinance as yf
  1. Download the adjusted prices:
df_twtr = yf.download('TWTR', 
start='2018-01-01',
end='2018-12-31',
progress=False,
auto_adjust=True)

For creating the plot, we use the plotly and cufflinks libraries. For more details, please refer to the Visualizing time series data recipe, found in Chapter 1, Financial Data and Preprocessing.

How to do it...

Execute the following steps to create an interactive candlestick chart.

  1. Import the libraries:
import cufflinks as cf
from plotly.offline import iplot, init_notebook_mode

init_notebook_mode()
  1. Create the candlestick chart, using Twitter's stock prices:
qf = cf.QuantFig(df_twtr, title="Twitter's Stock Price", 
                 legend='top', name='TWTR')
  1. Add volume and moving averages to the figure:
qf.add_volume()
qf.add_sma(periods=20, column='Close', color='red')
qf.add_ema(periods=20, color='green')

  1. Display the plot:
qf.iplot()

We can observe the following plot (it is interactive in the notebook):

In the plot, we can see that the exponential moving average (EMA) adapts to the changes in prices much faster than the SMA. Some discontinuities in the chart are caused by the fact that we are using daily data, and there is no data for weekends/bank holidays.

How it works...

In Step 2, we created a QuantFig object by passing a DataFrame containing the input data, as well as some arguments for the title and legend's position. We could have created a simple candlestick chart by running the iplot method of QuantFig immediately afterward.

However, in Step 3, we also added two moving average lines by using the add_sma/add_ema methods. We decided to consider 20 periods (days, in this case). By default, the averages are calculated using the close column, however, we can change this by providing the column argument.

The difference between the two moving averages is that the exponential one puts more weight on recent prices. By doing so, it is more responsive to new information and reacts faster to any changes in the general trend.

See also

Backtesting a strategy based on simple moving average

The general idea behind backtesting is to evaluate the performance of a trading strategy—built using some heuristics or technical indicators—by applying it to historical data.

In this recipe, we introduce one of the available frameworks for backtesting in Python: backtrader. Key features of this framework include:

  • A vast amount of available technical indicators (backtrader also provides a wrapper around the popular TA-Lib library) and performance measures
  • Ease of building and applying new indicators
  • Multiple data sources available (including Yahoo Finance, Quandl)
  • Simulating many aspects of real brokers, such as different types of orders (market, limit, stop), slippage (the difference between the intended and actual execution prices of an order), commission, going long/short, and so on
  • A one-line call for a plot, with all results

For this recipe, we consider a basic strategy based on the SMA. The key points of the strategy are as follows:

  • When the close price becomes higher than the 20-day SMA, buy one share.
  • When the close price becomes lower than the 20-day SMA and we have a share, sell it.
  • We can only have a maximum of one share at any given time.
  • No short selling is allowed.

We run the backtesting of this strategy, using Apple's stock prices from the year 2018.

How to do it...

In this example, we present two possible approaches: building a trading strategy, using a signal (bt.Signal) or defining a full strategy (bt.Strategy). Both yield the same results, however, the lengthier one, using bt.Strategy, provides more logging of what is actually happening in the background. This makes it easier to debug and keep track of all operations (the level of detail included in the logging depends on our needs).

Signal

Execute the following steps to create a backtest, using the bt.Signal class.

  1. Import the libraries:
from datetime import datetime
import backtrader as bt
  1. Define a class representing the trading strategy:
class SmaSignal(bt.Signal):
params = (('period', 20), )

def __init__(self):
self.lines.signal = self.data - bt.ind.SMA(period=self.p.period)
  1. Download data from Yahoo Finance:
data = bt.feeds.YahooFinanceData(dataname='AAPL', 
fromdate=datetime(2018, 1, 1),
todate=datetime(2018, 12, 31))
  1. Set up the backtest:
cerebro = bt.Cerebro(stdstats = False)

cerebro.adddata(data)
cerebro.broker.setcash(1000.0)
cerebro.add_signal(bt.SIGNAL_LONG, SmaSignal)
cerebro.addobserver(bt.observers.BuySell)
cerebro.addobserver(bt.observers.Value)
  1. Run the backtest:
print(f'Starting Portfolio Value: {cerebro.broker.getvalue():.2f}')
cerebro.run()
print(f'Final Portfolio Value: {cerebro.broker.getvalue():.2f}')
  1. Plot the results:
cerebro.plot(iplot=True, volume=False)

The plot is divided into three parts: the evolution of the portfolio's value, the price of the asset (together with the buy/sell signals), and—lastly—the technical indicator of our choosing, as shown in the following plot:

From the preceding plot, we can see that, in the end, the trading strategy made money: the terminal value of the portfolio is $1011.56.

Strategy

To make the code more readable, we first present the general outline of the class (trading strategy) and then define separate pieces in the following code blocks.

  1. The template of the strategy is presented below:
class SmaStrategy(bt.Strategy):
params = (('ma_period', 20), )

def __init__(self):
# some code

def log(self, txt):
# some code

def notify_order(self, order):
# some code

def notify_trade(self, trade):
# some code

def next(self):
# some code

The __init__ block is defined as:

def __init__(self):
self.data_close = self.datas[0].close

self.order = None
self.price = None
self.comm = None

self.sma = bt.ind.SMA(self.datas[0],
period=self.params.ma_period)

The log block is defined as:

def log(self, txt):
dt = self.datas[0].datetime.date(0).isoformat()
print(f'{dt}, {txt}')

The notify_order block is defined as:

def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
return

if order.status in [order.Completed]:
if order.isbuy():
self.log(f'BUY EXECUTED --- Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Commission: {order.executed.comm:.2f}')
self.price = order.executed.price
self.comm = order.executed.comm
else:
self.log(f'SELL EXECUTED --- Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Commission: {order.executed.comm:.2f}')

self.bar_executed = len(self)

elif order.status in [order.Canceled, order.Margin,
order.Rejected]:
self.log('Order Failed')

self.order = None

The notify_trade block is defined as:

def notify_trade(self, trade):
if not trade.isclosed:
return

self.log(f'OPERATION RESULT --- Gross: {trade.pnl:.2f}, Net: {trade.pnlcomm:.2f}')

The next block is defined as:

def next(self):
if self.order:
return

if not self.position:
if self.data_close[0] > self.sma[0]:
self.log(f'BUY CREATED --- Price: {self.data_close[0]:.2f}')
self.order = self.buy()
else:
if self.data_close[0] < self.sma[0]:
self.log(f'SELL CREATED --- Price: {self.data_close[0]:.2f}')
self.order = self.sell()
The code for data is the same as in the signal strategy, so it is not included here, to avoid repetition.
  1. Set up the backtest:
cerebro = bt.Cerebro(stdstats = False)

cerebro.adddata(data)
cerebro.broker.setcash(1000.0)
cerebro.addstrategy(SmaStrategy)
cerebro.addobserver(bt.observers.BuySell)
cerebro.addobserver(bt.observers.Value)
  1. Run the backtest:
print(f'Starting Portfolio Value: {cerebro.broker.getvalue():.2f}')
cerebro.run()
print(f'Final Portfolio Value: {cerebro.broker.getvalue():.2f}')
  1. Plot the results:
cerebro.plot(iplot=True, volume=False)

The resulting graph is presented below:

From the preceding graph, we see that the strategy managed to make $11.56 over the year. Additionally, we present a piece of the log:

The log contains information about all the created and executed trades, as well as the operation results, in case it was a sell.

How it works...

The key idea of working with backtrader is that there is the main brain—Cerebro—and by using different methods, we provided it with historical data, the designed trading strategy, additional metrics we wanted to calculate (for example, Portfolio Value over the investment horizon, or the overall Sharpe ratio), information about commissions/slippage, and so on. These were the common elements between the two approaches. The part that differed was the definition of the strategy. We start by describing the common elements of the backtrader framework while assuming a trading strategy already exists, and we then explain the details of the particular strategies.

Common elements

We started with downloading price data from Yahoo Finance, with the help of the bt.feeds.YahooFinanceData() function. What followed was a series of operations connected to Cerebro, as described here:

  1. Creating the instance of bt.Cerebro and setting stdstats = False, in order to suppress a lot of default elements of the plot. Doing so avoided cluttering the output, and then we manually picked the interesting elements (observers and indicators).
  2. Adding data, using the adddata method.
  3. Setting up the amount of available money, using the broker.setcash method.
  4. Adding the signal/strategy, using the add_signal/addstrategy methods.
  5. Adding Observers, using addobserver. We selected two Observers: BuySell, to display the buy/sell decisions on the plot (denoted by blue and red triangles), and Value, for tracking how the portfolio value changed over time.
You can also add data from a CSV file, a pandas DataFrame, Quandl, and other sources. For a list of available options, please refer to bt.feeds.

The last step involved running the backtest with cerebro.run() and displaying the resulting plot with cerebro.plot(). In the latter step, we disabled displaying the volume bar charts, to avoid cluttering the graph.

Signal

The signal was built as a class, inheriting from bt.Signal. The signal was represented as a number—in this case, the difference between the current data point (self.data) and the moving average (bt.ind.SMA). If the signal is positive, it is an indication to go long (buy). A negative one indicates short (selling). The value of 0 means there is no signal.

The next step was to add the signal to Cerebro, using the add_signal method. When doing so, we also had to specify what kind of signal we were adding.

The following is a description of the available signal types:

  • LONGSHORT: This takes into account both long and short indications from the signal.
  • LONG: Positive signals indicate going long; negative ones are used to close the long position.
  • SHORT: Negative signals indicate shorting; positive ones are used to close the short position.
  • LONGEXIT: A negative signal is used to exit a long position.
  • SHORTEXIT: A positive signal is used to exit a short position.

However, exiting positions can be more complex (enabling users to build more sophisticated strategies), as described here:

  • LONG: If there is a LONGEXIT signal, it is used to exit the long position, instead of the default behavior mentioned previously. If there is a SHORT signal and no LONGEXIT signal, the SHORT signal is used to close the long position before opening a short one.
  • SHORT: If there is a SHORTEXIT signal, it is used to exit the short position, instead of the default behavior mentioned previously. If there is a LONG signal and no SHORTEXIT signal, the LONG signal is used to close the short position before opening a long one.
As you might have already realized, the signal is calculated for every time point (as visualized in the bottom of the plot), which effectively creates a continuous stream of positions to be opened/closed (the signal value of 0 is not very likely to happen). That is why backtrader, by default, disables accumulation (the constant opening of new positions, even when we have one already opened) and concurrency (generating new orders without hearing back from the broker whether the previously submitted ones were executed successfully).

Strategy

The strategy was built as a class, inheriting from bt.Strategy. Inside the class, we defined the following methods (we were actually overwriting them to make them tailor-made for our needs):

  • __init__: Here, we defined the objects that we would like to keep track of, for example, close price, order, buy price, commission, indicators such as SMA, and so on.
  • log: This is defined for logging purposes.
  • notify_order: This is defined for reporting the status of the order (position). In general, on day t, the indicator can suggest opening/closing a position based on the close price (assuming we are working with daily data). Then, the (market) order will be carried out on the next day (using day t + 1's open price). However, there is no guarantee that the order will be executed, as it can be canceled, or we might have insufficient cash. This behavior is also true for strategies built with signals. It also removes any pending order, by setting self.order = None.
  • notify_trade: This is defined for reporting the results of trades (after the positions are closed).
  • next: This is the place containing the trading strategy's logic. First, we check whether there is an order already pending, and do nothing if there is. The second check is to see whether we already have a position (enforced by our strategy; not a must), and, if we do not, we check whether the close price is higher than the moving average. A positive outcome results in an entry to the log, and the placing of a buy order self.order = self.buy(). This is also the place where we can choose the stake (number of assets we want to buy). A default outcome in self.buy(size=1).

Here are some general notes:

  • Cerebro should only be used once. If we want to run another backtest, we should create a new instance, not add something to it after prior calculations.
  • The strategy built on bt.Signal inherits from bt.Signal, and uses only one signal. However, we can combine multiple signals, based on different conditions, when we use bt.SignalStrategy instead.
  • When we do not specify otherwise, all trades are carried out on one unit of the asset.
  • backtrader automatically handles the warm-up period. In this case, no trade can be carried out until there are enough data points to calculate the 20-day SMA. When considering multiple indicators at once, backtrader automatically selects the longest necessary period.

There's more...

It is worth mentioning that backtrader has parameter optimization capabilities, which we present in the code that follows. The code is a modified version of the strategy from this recipe, in which we optimize the number of days in the SMA.

The following list provides details of modifications to the code (we only show the relevant ones, as the bulk of the code is identical to that using bt.Strategy):

  • We add an extra attribute called stop to the class definition—it returns the Terminal portfolio value for each parameter:
def stop(self):
self.log(f'(ma_period = {self.params.ma_period:2d}) --- Terminal Value: {self.broker.getvalue():.2f}')
  • Instead of using cerebro.addstrategy(), we use cerebro.optstrategy(), and provide the strategy name and parameter values:
cerebro.optstrategy(SmaStrategy, ma_period=range(10, 31))
  • We increase the number of CPU cores when running the backtesting: cerebro.run(maxcpus=4)

We present the results in the following summary (the order of parameters is not preserved, as the testing was carried out on four cores):

We see that the strategy performed best for ma_period = 22.

See also

Additional resources are available here:

  • https://www.zipline.io/: An alternative framework for backtesting. Developed and actively maintained by Quantopian.

Calculating Bollinger Bands and testing a buy/sell strategy

Bollinger Bands are a statistical method, used for deriving information about the prices and volatility of a certain asset over time. To obtain the Bollinger Bands, we need to calculate the moving average and standard deviation of the time series (prices), using a specified window (typically, 20 days). Then, we set the upper/lower bands at K times (typically, 2) the moving standard deviation above/below the moving average.

The interpretation of the bands is quite sample: the bands widen with an increase in volatility and contract with a decrease in volatility.

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

  • Buy when the price crosses the lower Bollinger Band upwards.
  • Sell (only if stocks are in possession) when the price crosses the upper Bollinger Band downward.
  • All-in strategy—when creating a buy order, buy as many shares as possible.
  • Short selling is not allowed.

We evaluate the strategy on Microsoft's stock in 2018. Additionally, we set the commission to be equal to 0.1%.

How to do it...

Execute the following steps to backtest a strategy based on the Bollinger Bands.

  1. Import the libraries:
import backtrader as bt
import datetime
import pandas as pd
  1. The template of the strategy is presented:
class BBand_Strategy(bt.Strategy):
params = (('period', 20),
('devfactor', 2.0),)

def __init__(self):
# some code

def log(self, txt):
# some code

def notify_order(self, order):
# some code

def notify_trade(self, trade):
# some code

def next_open(self):
# some code

The __init__ block is defined as:

    def __init__(self):
        # keep track of close price in the series
        self.data_close = self.datas[0].close
        self.data_open = self.datas[0].open

        # keep track of pending orders/buy price/buy commission
        self.order = None
        self.price = None
        self.comm = None

        # add Bollinger Bands indicator and track the buy/sell signals
        self.b_band = bt.ind.BollingerBands(self.datas[0], 
                                            period=self.p.period, 
                                            devfactor=self.p.devfactor)
        self.buy_signal = bt.ind.CrossOver(self.datas[0], 
                                           self.b_band.lines.bot)
        self.sell_signal = bt.ind.CrossOver(self.datas[0], 
                                            self.b_band.lines.top)

The log block is defined as:

def log(self, txt):
dt = self.datas[0].datetime.date(0).isoformat()
print(f'{dt}, {txt}')

The notify_order block is defined as:

 def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
return

if order.status in [order.Completed]:
if order.isbuy():
self.log(
f'BUY EXECUTED --- Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Commission: {order.executed.comm:.2f}'
)
self.price = order.executed.price
self.comm = order.executed.comm
else:
self.log(
f'SELL EXECUTED --- Price: {order.executed.price:.2f}, Cost: {order.executed.value:.2f}, Commission: {order.executed.comm:.2f}'
)

elif order.status in [order.Canceled, order.Margin,
order.Rejected]:
self.log('Order Failed')

self.order = None

The notify_trade block is defined as:

def notify_trade(self, trade):
if not trade.isclosed:
return

self.log(f'OPERATION RESULT --- Gross: {trade.pnl:.2f}, Net: {trade.pnlcomm:.2f}')

The next_open block is defined as:

def next_open(self):
if not self.position:
if self.buy_signal > 0:
size = int(self.broker.getcash() / self.datas[0].open)
self.log(f'BUY CREATED --- Size: {size}, Cash: {self.broker.getcash():.2f}, Open: {self.data_open[0]}, Close: {self.data_close[0]}')
self.buy(size=size)
else:
if self.sell_signal < 0:
self.log(f'SELL CREATED --- Size: {self.position.size}')
self.sell(size=self.position.size)

  1. Download the data:
data = bt.feeds.YahooFinanceData(
dataname='MSFT', fromdate=datetime.datetime(2018, 1, 1), todate=datetime.datetime(2018, 12, 31)
)
  1. Set up the backtest:
cerebro = bt.Cerebro(stdstats = False, cheat_on_open=True)

cerebro.addstrategy(BBand_Strategy)
cerebro.adddata(data)
cerebro.broker.setcash(10000.0)
cerebro.broker.setcommission(commission=0.001)
cerebro.addobserver(bt.observers.BuySell)
cerebro.addobserver(bt.observers.Value)
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')
cerebro.addanalyzer(bt.analyzers.TimeReturn, _name='time_return')
  1. Run the backtest:
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
backtest_result = cerebro.run()
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
  1. Plot the results:
cerebro.plot(iplot=True, volume=False)

The resulting graph is presented below:

The log is presented below:

We can see that the strategy managed to make money, even after accounting for commission costs. We now turn to an inspection of the analyzers.

  1. Run the following code to investigate different returns metrics:
print(backtest_result[0].analyzers.returns.get_analysis())

The output of the preceding line is as follows:

OrderedDict([('rtot', 0.06155731237239935), 
('ravg', 0.00024622924948959743),
('rnorm', 0.06401530037885826),
('rnorm100', 6.401530037885826)])
  1. Create a plot of daily portfolio returns:
returns_dict = backtest_result[0].analyzers.time_return.get_analysis()
returns_df = pd.DataFrame(list(returns_dict.items()),
columns = ['report_date', 'return']) \
.set_index('report_date')
returns_df.plot(title='Portfolio returns')

Running the code results in the following plot:

The flat lines represent periods when we have no open positions.

How it works...

There are a lot of similarities between the code used for creating the Bollinger Bands-based strategy and that used in the previous recipe. That is why we only discuss the novelties, and refer you to the Backtesting a strategy based on simple moving average recipe for more details.

As we were going all-in in this strategy, we had to use a method called cheat_on_open. This means that we calculated the signals on day t's close price, but calculated the number of shares we wanted to buy based on day t+1's open price. To do so, we had to set cheat_on_open=True when creating the bt.Cerebro object. As a result, we also defined a next_open method instead of next within the Strategy class. This clearly indicated to Cerebro that we were cheating-on-open. Before creating a potential buy order, we calculated size = int(self.broker.getcash() / self.datas[0].open), which is the maximum number of shares we could buy (the open price comes from day t+1). The last novelty was that we also added commission directly to Cerebro by using cerebro.broker.setcommission(commission=0.001).

When calculating the buy/sell signals based on the Bollinger Bands, we used the CrossOver indicator. It returned the following:

  • 1 if the first data (price) crossed the second data (indicator) upward
  • -1 if the first data (price) crossed the second data (indicator) downward


We can also use CrossUp and CrossDown when we want to consider crossing from only one direction. The buy signal would look like this: self.buy_signal = bt.ind.CrossUp(self.datas[0], self.b_band.lines.bot).

The last addition included utilizing analyzers—backtrader objects that help to evaluate what is happening with the portfolio. In the following example, we used two analyzers:

  • Returns: A collection of different logarithmic returns, calculated on the entire timeframe: total compound return, the average return over the entire period, and the annualized return.
  • TimeReturn: A collection of returns over time (using a provided time-frame, in this case, daily data).
We can obtain the same result as from the TimeReturn analyzer by adding an observer with the same name: cerebro.addobserver(bt.observers.TimeReturn). The only difference is that the Observer will be plotted on the main results plot, which is not always desired.

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.

Building an interactive dashboard for TA

In this recipe, we show how to build an interactive dashboard for technical analysis in Jupyter Notebook. Of course, the same result could be achieved without any interactivity, by writing the initial code, and then changing the parameter values inline multiple times. However, we believe it is much better to create an interactive tool that can ease the pain, as well as reduce the number of potential mistakes.

In order to do so, we leverage a tool called IPython widgets (ipywidgets), in combination with plotly and cufflinks. We select a few US tech stocks and three indicators (Bollinger Bands, MACD, and RSI) for the dashboard, but this selection can be extended to many more.

Getting ready

After installing the ipywidgets library, we need to run the following line in Terminal to enable the extension:

jupyter nbextension enable --py widgetsnbextension

How to do it...

Execute the following steps to create an interactive dashboard inside Jupyter Notebook.

  1. Import the libraries:
import ipywidgets as wd
import cufflinks as cf
import pandas as pd
import yfinance as yf
from plotly.offline import iplot, init_notebook_mode
from ipywidgets import interact, interact_manual

init_notebook_mode()
  1. Define the possible values for assets and technical indicators:
stocks = ['TWTR', 'MSFT', 'GOOGL', 'FB', 'TSLA', 'AAPL']
indicators = ['Bollinger Bands', 'MACD', 'RSI']
  1. Define a function for creating the interactive plot:
def ta_dashboard(asset, indicator, start_date, end_date, 
bb_k, bb_n, macd_fast, macd_slow, macd_signal,
rsi_periods, rsi_upper, rsi_lower):


df = yf.download(asset,
start=start_date,
end=end_date,
progress=False,
auto_adjust=True)

qf = cf.QuantFig(df, title=f'TA Dashboard - {asset}',
legend='right', name=f'{asset}')

if 'Bollinger Bands' in indicator:
qf.add_bollinger_bands(periods=bb_n,
boll_std=bb_k)
if 'MACD' in indicator:
qf.add_macd(fast_period=macd_fast,
slow_period=macd_slow,
signal_period=macd_signal)
if 'RSI' in indicator:
qf.add_rsi(periods=rsi_periods,
rsi_upper=rsi_upper,
rsi_lower=rsi_lower,
showbands=True)

return qf.iplot()
  1. Define the selectors:
stocks_selector = wd.Dropdown(
options=stocks,
value=stocks[0],
description='Asset'
)

indicator_selector = wd.SelectMultiple(
description='Indicator',
options=indicators,
value=[indicators[0]]
)

start_date_selector = wd.DatePicker(
description='Start Date',
value=pd.to_datetime('2018-01-01'),
continuous_update=False
)

end_date_selector = wd.DatePicker(
description='End Date',
value=pd.to_datetime('2018-12-31'),
continuous_update=False
)
  1. Define a label, and group the selectors inside a container:
main_selector_label = wd.Label('Main parameters', 
layout=wd.Layout(height='45px'))

main_selector_box = wd.VBox(children=[main_selector_label,
stocks_selector,
indicator_selector,
start_date_selector,
end_date_selector])
  1. Define the secondary selectors for the Bollinger Bands:
bb_label = wd.Label('Bollinger Bands')

n_param = wd.IntSlider(value=20, min=1, max=40, step=1,
description='N:', continuous_update=False)

k_param = wd.FloatSlider(value=2, min=0.5, max=4, step=0.5,
description='k:', continuous_update=False)

bollinger_box = wd.VBox(children=[bb_label, n_param, k_param])
  1. Define the secondary selectors for the MACD:
macd_label = wd.Label('MACD')

macd_fast = wd.IntSlider(value=12, min=2, max=50, step=1,
description='Fast avg:',
continuous_update=False)

macd_slow = wd.IntSlider(value=26, min=2, max=50, step=1,
description='Slow avg:',
continuous_update=False)

macd_signal = wd.IntSlider(value=9, min=2, max=50, step=1,
description='MACD signal:',
continuous_update=False)

macd_box = wd.VBox(children=[macd_label, macd_fast,
macd_slow, macd_signal])
  1. Define the secondary selectors for the RSI:
rsi_label = wd.Label('RSI')

rsi_periods = wd.IntSlider(value=14, min=2, max=50, step=1,
description='RSI periods:',
continuous_update=False)

rsi_upper = wd.IntSlider(value=70, min=1, max=100, step=1,
description='Upper Thr:',
continuous_update=False)

rsi_lower = wd.IntSlider(value=30, min=1, max=100, step=1,
description='Lower Thr:',
continuous_update=False)

rsi_box = wd.VBox(children=[rsi_label, rsi_periods,
rsi_upper, rsi_lower])
  1. Create the labels and group the selectors into containers:
sec_selector_label = wd.Label('Secondary parameters', 
layout=wd.Layout(height='45px'))
blank_label = wd.Label('', layout=wd.Layout(height='45px'))

sec_box_1 = wd.VBox([sec_selector_label, bollinger_box, macd_box])
sec_box_2 = wd.VBox([blank_label, rsi_box])

secondary_selector_box = wd.HBox([sec_box_1, sec_box_2])
  1. Group the boxes and prepare the interactive output:
controls_dict = {'asset':stocks_selector, 
'indicator':indicator_selector,
'start_date':start_date_selector,
'end_date':end_date_selector,
'bb_k':k_param,
'bb_n':n_param,
'macd_fast': macd_fast,
'macd_slow': macd_slow,
'macd_signal': macd_signal,
'rsi_periods': rsi_periods,
'rsi_upper': rsi_upper,
'rsi_lower': rsi_lower}

ui = wd.HBox([main_selector_box, secondary_selector_box])
out = wd.interactive_output(ta_dashboard, controls_dict)
  1. Display the dashboard:
display(ui, out)

Running the last line displays the following graphical user interface (GUI):

By selecting values of interest in the GUI, we can influence the interactive chart, for example, by changing the technical indicators we want to display.

This time, we plotted both the Bollinger Bands and the MACD on top of the candlestick chart. Inside of the Notebook, we can zoom in on areas of interest, to further inspect the patterns.

How it works...

After importing the libraries, we defined lists of possible assets (represented by their tickers), and the technical indicators from which to select.

In Step 3, we defined a function called ta_dashboard, which took as input all parameters we made configurable: asset, technical indicators, range of dates, and indicator-specific parameters. The function itself downloaded historical stock prices from Yahoo Finance and used cufflinks to draw a candlestick chart, as we presented in the Creating a candlestick chart recipe. Then, we added additional indicators to the figure, by using methods such as add_bollinger_bands and providing the required arguments. For a list of all supported technical indicators, please refer to the cufflinks documentation.

Having prepared the function, we started defining the elements of the GUI. In Step 4 and Step 5, we defined the main selectors (such as the asset, technical indicators, and start/end dates for downloading the data) and grouped them inside a vertical box (VBox), which serves as storage for smaller elements and makes it easier to design the GUI. To indicate which selectors belonged to a given box, we provided a list of the objects as the children argument.

In Steps 6 to 9, we created the secondary container, this time with all the parameters responsible for tuning the technical indicators. Some general notes about using selectors and boxes are:

  • We can turn off the continuous updating of sliders with continuous_update=False, so the plot only updates when a new value is set, not while moving it around.
  • We can define the default value for a selector by providing the value argument.
  • We can use blank labels (without any text) to align the elements of the boxes.

In Step 10, we used the wd.interactive_output, to indicate that the output of the ta_dashboard function would be modified by the interactive widgets (in a dictionary, we assigned widgets to certain arguments of the function). Lastly, we ran display(ui, out) to display the GUI, which in turn generated the plot.

There's more...

The main advantage of the dashboard presented in this recipe is that it is embedded within Jupyter Notebook. However, we might want to move it outside of the local notebook and make it available for everyone as a web application. To do so, we could use Dash, which is Python's equivalent of R's vastly popular Shiny framework.

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Use powerful Python libraries such as pandas, NumPy, and SciPy to analyze your financial data
  • Explore unique recipes for financial data analysis and processing with Python
  • Estimate popular financial models such as CAPM and GARCH using a problem-solution approach

Description

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.

What you will learn

Download and preprocess financial data from different sources Backtest the performance of automatic trading strategies in a real-world setting Estimate financial econometrics models in Python and interpret their results Use Monte Carlo simulations for a variety of tasks such as derivatives valuation and risk assessment Improve the performance of financial models with the latest Python libraries Apply machine learning and deep learning techniques to solve different financial problems Understand the different approaches used to model financial time series data

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Black & white paperback book shipped to your address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Buy Now

Product Details


Publication date : Jan 31, 2020
Length 432 pages
Edition : 1st Edition
Language : English
ISBN-13 : 9781789618518
Category :

Table of Contents

12 Chapters
Preface Chevron down icon Chevron up icon
Financial Data and Preprocessing Chevron down icon Chevron up icon
Technical Analysis in Python Chevron down icon Chevron up icon
Time Series Modeling Chevron down icon Chevron up icon
Multi-Factor Models Chevron down icon Chevron up icon
Modeling Volatility with GARCH Class Models Chevron down icon Chevron up icon
Monte Carlo Simulations in Finance Chevron down icon Chevron up icon
Asset Allocation in Python Chevron down icon Chevron up icon
Identifying Credit Default with Machine Learning Chevron down icon Chevron up icon
Advanced Machine Learning Models in Finance Chevron down icon Chevron up icon
Deep Learning in Finance Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon

Customer reviews

Filter icon Filter
Top Reviews
Rating distribution
Empty star icon Empty star icon Empty star icon Empty star icon Empty star icon 0
(0 Ratings)
5 star 0%
4 star 0%
3 star 0%
2 star 0%
1 star 0%

Filter reviews by


No reviews found
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

What is the delivery time and cost of print book? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela
What is custom duty/charge? Chevron down icon Chevron up icon

Customs duty are charges levied on goods when they cross international borders. It is a tax that is imposed on imported goods. These duties are charged by special authorities and bodies created by local governments and are meant to protect local industries, economies, and businesses.

Do I have to pay customs charges for the print book order? Chevron down icon Chevron up icon

The orders shipped to the countries that are listed under EU27 will not bear custom charges. They are paid by Packt as part of the order.

List of EU27 countries: www.gov.uk/eu-eea:

A custom duty or localized taxes may be applicable on the shipment and would be charged by the recipient country outside of the EU27 which should be paid by the customer and these duties are not included in the shipping charges been charged on the order.

How do I know my custom duty charges? Chevron down icon Chevron up icon

The amount of duty payable varies greatly depending on the imported goods, the country of origin and several other factors like the total invoice amount or dimensions like weight, and other such criteria applicable in your country.

For example:

  • If you live in Mexico, and the declared value of your ordered items is over $ 50, for you to receive a package, you will have to pay additional import tax of 19% which will be $ 9.50 to the courier service.
  • Whereas if you live in Turkey, and the declared value of your ordered items is over € 22, for you to receive a package, you will have to pay additional import tax of 18% which will be € 3.96 to the courier service.
How can I cancel my order? Chevron down icon Chevron up icon

Cancellation Policy for Published Printed Books:

You can cancel any order within 1 hour of placing the order. Simply contact customercare@packt.com with your order details or payment transaction id. If your order has already started the shipment process, we will do our best to stop it. However, if it is already on the way to you then when you receive it, you can contact us at customercare@packt.com using the returns and refund process.

Please understand that Packt Publishing cannot provide refunds or cancel any order except for the cases described in our Return Policy (i.e. Packt Publishing agrees to replace your printed book because it arrives damaged or material defect in book), Packt Publishing will not accept returns.

What is your returns and refunds policy? Chevron down icon Chevron up icon

Return Policy:

We want you to be happy with your purchase from Packtpub.com. We will not hassle you with returning print books to us. If the print book you receive from us is incorrect, damaged, doesn't work or is unacceptably late, please contact Customer Relations Team on customercare@packt.com with the order number and issue details as explained below:

  1. If you ordered (eBook, Video or Print Book) incorrectly or accidentally, please contact Customer Relations Team on customercare@packt.com within one hour of placing the order and we will replace/refund you the item cost.
  2. Sadly, if your eBook or Video file is faulty or a fault occurs during the eBook or Video being made available to you, i.e. during download then you should contact Customer Relations Team within 14 days of purchase on customercare@packt.com who will be able to resolve this issue for you.
  3. You will have a choice of replacement or refund of the problem items.(damaged, defective or incorrect)
  4. Once Customer Care Team confirms that you will be refunded, you should receive the refund within 10 to 12 working days.
  5. If you are only requesting a refund of one book from a multiple order, then we will refund you the appropriate single item.
  6. Where the items were shipped under a free shipping offer, there will be no shipping costs to refund.

On the off chance your printed book arrives damaged, with book material defect, contact our Customer Relation Team on customercare@packt.com within 14 days of receipt of the book with appropriate evidence of damage and we will work with you to secure a replacement copy, if necessary. Please note that each printed book you order from us is individually made by Packt's professional book-printing partner which is on a print-on-demand basis.

What tax is charged? Chevron down icon Chevron up icon

Currently, no tax is charged on the purchase of any print book (subject to change based on the laws and regulations). A localized VAT fee is charged only to our European and UK customers on eBooks, Video and subscriptions that they buy. GST is charged to Indian customers for eBooks and video purchases.

What payment methods can I use? Chevron down icon Chevron up icon

You can pay with the following card types:

  1. Visa Debit
  2. Visa Credit
  3. MasterCard
  4. PayPal
What is the delivery time and cost of print books? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela