100% found this document useful (1 vote)
816 views

Backtrader Documentation 1.9.58.122 WJ

This document provides an overview and contents for the backtrader documentation. The documentation covers topics such as installation, quickstart guides, platform concepts, operating the platform, exceptions, core components like Cerebro and strategies, order management, data feeds, indicators, observers, plotting and more. The documentation is intended to help users understand how to set up, run simulations and analyze results using the backtrader platform.

Uploaded by

zr j
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
816 views

Backtrader Documentation 1.9.58.122 WJ

This document provides an overview and contents for the backtrader documentation. The documentation covers topics such as installation, quickstart guides, platform concepts, operating the platform, exceptions, core components like Cerebro and strategies, order management, data feeds, indicators, observers, plotting and more. The documentation is intended to help users understand how to set up, run simulations and analyze results using the backtrader platform.

Uploaded by

zr j
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 864

backtrader’s documentation Version-1.9.58.

122

backtrader’s documentation
Version-1.9.58.122
Edited By Wilson J
https://www.backtrader.com/docu/index.html

Contents:

目录
backtrader’s documentation ....................................................................................................................................... 1
Introduction ................................................................................................................................................. 2
Installation ............................................................................................................................................. 3
Quickstart ................................................................................................................................................. 4
Platform Concepts ..................................................................................................................................... 50
Operating the platform .............................................................................................................................. 66
Exceptions ............................................................................................................................................... 75
Cerebro ...................................................................................................................................................... 76
Cheat On Open .......................................................................................................................................... 90
Strategy ...................................................................................................................................................... 98
Sizers - Smart Staking ............................................................................................................................... 111
Timers ...................................................................................................................................................... 118
Target Orders ........................................................................................................................................... 139
Strategy with Signals ................................................................................................................................ 152
Broker....................................................................................................................................................... 164
Slippage.................................................................................................................................................... 169
Fillers ........................................................................................................................................................ 173
Orders ...................................................................................................................................................... 175
Order Management and Execution.......................................................................................................... 181
OCO orders............................................................................................................................................... 205
StopTrail(Limit) ......................................................................................................................................... 214
Bracket Orders ......................................................................................................................................... 224
Futures and Spot Compensation ............................................................................................................. 239
Data Feeds ............................................................................................................................................... 244
CSV Data Feed Development ................................................................................................................... 249
Binary Datafeed Development................................................................................................................. 252
Extending a Datafeed ............................................................................................................................... 260
Pandas DataFeed Example ....................................................................................................................... 262
Trading Calendar ...................................................................................................................................... 267
Data Resampling ...................................................................................................................................... 288
Data - Multiple Timeframes ..................................................................................................................... 294
Data - Replay ............................................................................................................................................ 303
Rolling over Futures ................................................................................................................................. 311
Filters ....................................................................................................................................................... 325
1
backtrader’s documentation Version-1.9.58.122

Using Indicators ....................................................................................................................................... 331


TA-Lib ....................................................................................................................................................... 336
Mixing Timeframes in Indicators.............................................................................................................. 360
Indicator Development ............................................................................................................................ 365
Observers and Statistics ........................................................................................................................... 371
Benchmarking .......................................................................................................................................... 384
Analyzers .................................................................................................................................................. 397
PyFolio Overview ..................................................................................................................................... 404
Pyfolio Integration ................................................................................................................................... 406
Writer ....................................................................................................................................................... 425
Commissions: Stocks vs Futures .............................................................................................................. 427
Extending Commissions ........................................................................................................................... 437
User Defined Commissions ...................................................................................................................... 448
Commissions: Credit ................................................................................................................................ 453
Position .................................................................................................................................................... 455
Trade ........................................................................................................................................................ 455
Plotting..................................................................................................................................................... 457
Plotting Date Ranges ................................................................................................................................ 470
Plotting on the same axis ......................................................................................................................... 476
Optimization improvements .................................................................................................................... 480
Automating BackTesting .......................................................................................................................... 485
Saving Memory ........................................................................................................................................ 506
DateTime Management ........................................................................................................................... 514
Live Data Feeds and Live Trading ............................................................................................................. 518
Data Feeds Reference ......................................................................................................................... 518
Yahoo Data Feed Notes ............................................................................................................................ 542
Indicator Reference.................................................................................................................................. 542
TA-Lib Indicator Reference ....................................................................................................................... 639
Strategies Reference ................................................................................................................................ 722
Analyzers Reference ........................................................................................................................... 723
Observers Reference ................................................................................................................................ 724
Sizers Reference ....................................................................................................................................... 727
Filters Reference ...................................................................................................................................... 728

Introduction

Backtrader is Python based backtesting/trading platform for developing home


cooked indicators and trading strategies.

Features:

• Live Data Feed and Trading with

2
backtrader’s documentation Version-1.9.58.122

o Interactive Brokers (needs IbPy and benefits greatly from an


installed pytz)
o Visual Chart (needs a fork of comtypes until a pull request is
integrated in the release and benefits from pytz)
o Oanda (needs oandapy)
• Data feeds from csv/files, online sources or from pandas and blaze
• Filters for datas (like breaking a daily bar into chunks to simulate
intraday)
• Multiple data feeds and multiple strategies supported
• Multiple timeframes at once
• Integrated Resampling and Replaying
• Step by Step backtesting or at once (except in the evaluation of the
Strategy)
• Integrated battery of indicators
• TA-Lib indicator support
• Easy development of custom indicators
• Analyzers (for example: TimeReturn, Sharpe Ratio, SQN) and pyfolio
integration
• Flexible definition of commission schemes
• Integrated broker simulation with Market, Close, Limit, Stop and
StopLimit orders, slippage and continuous cash adjustmet for future-
like instruments
• Plotting (requires matplotlib)

The platform has 2 main objectives:

1. Ease of use
2. Go back to 1

Loosely based on the Karate (Kid) rules by Mr. Miyagi.

The basics of running this platform:

• Create a Strategy
o Decide on potential adjustable parameters
o Instantiate the Indicators you need in the Strategy
o Write down the logic for entering/exiting the market

Or alternatively:

• Prepare some indicators to work as long/short signals

And then

• Create a Cerebro Engine


3
backtrader’s documentation Version-1.9.58.122

o First:
Inject the Strategy

or

Inject the Signals

And then:

o Load and Inject a Data Feed


o And do cerebro.run()
o For visual feedback execute: cerebro.plot()

The platform is highly configurable

Let’s hope you, the user, find the platform useful and fun to work with.

Installation

Requirements and versions

backtrader is self-contained with no external dependencies (except if you want to


plot)

Basic requirements are:

• Python 2.7
• Python 3.2/3.3/3.4/3.5
• pypy/pypy3

Additional requirements if plotting is wished:

• Matplotlib >= 1.4.1

It may work with previous versions, but this the one used for
development

Note

At the time of writing Matplotlib is not supported under pypy/pypy3

4
backtrader’s documentation Version-1.9.58.122

Python 2.x/3.x compatibility

Development takes place under Python 2.7 and sometimes under 3.4. Tests are run
locally with both versions.

Compatibility with 3.2/3.3/3.5 and pypy/pyp3 is checked with continuous


integration under Travis

Install from pypi

For example using pip:

pip install backtrader

easy_install with the same syntax can also be applied

Install from pypi (including matplotlib)

Use this if plotting capabilities are wished:

pip install backtrader[plotting]

This pulls in matplotlib which will in turn pull in other dependencies.

Again you may prefer (or only have access to ...) easy_install

Install from source

First downloading a release or the latest tarball from the github site:

• https://github.com/mementum/backtrader

And after unpacking run the command:

python setup.py install

5
backtrader’s documentation Version-1.9.58.122

Run from source in your project

Download a release or the latest tarball from the github site:

https://github.com/mementum/backtrader

And then copy the backtrader package directory to your own project. Under a Unix-
like OS for example:

Again tar xzf backgrader.tgz


cd backtrader
cp -r backtrader project_directory

Remember that you would then need to manually install matplotlib for plotting.

Quickstart

Using the platform

Let’s run through a series of examples (from almost an empty one to a fully
fledged strategy) but not without before roughly explaining 2 basic concepts when
working with backtrader

1. Lines

Data Feeds, Indicators and Strategies have lines.

A line is a succession of points that when joined together form this


line. When talking about the markets, a Data Feed has usually the
following set of points per day:

o Open, High, Low, Close, Volume, OpenInterest

The series of “Open”s along time is a Line. And therefore a Data


Feed has usually 6 lines.

If we also consider “DateTime” (which is the actual reference for a


single point), we could count 7 lines.

2. Index 0 Approach

6
backtrader’s documentation Version-1.9.58.122

When accessing the values in a line, the current value is accessed


with index: 0

And the “last” output value is accessed with -1. This in line with
Python conventions for iterables (and a line can be iterated and is
therefore an iterable) where index -1 is used to access the “last”
item of the iterable/array.

In our case is the last output value what’s getting accessed.

As such and being index 0 right after -1, it is used to access the
current moment in line.

With that in mind and if we imagine a Strategy featuring a Simple Moving average
created during initialization:

self.sma = SimpleMovingAverage(.....)

The easiest and simplest way to access the current value of this moving average:

av = self.sma[0]

There is no need to know how many bars/minutes/days/months have been processed,


because “0” uniquely identifies the current instant.

Following pythonic tradition, the “last” output value is accessed using -1:

previous_value = self.sma[-1]

Of course earlier output values can be accessed with -2, -3, ...

From 0 to 100: the samples

Basic Setup

Let’s get running.

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import backtrader as bt

if __name__ == '__main__':
7
backtrader’s documentation Version-1.9.58.122

cerebro = bt.Cerebro()

print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

cerebro.run()

print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

After the execution the output is:

Starting Portfolio Value: 10000.00


Final Portfolio Value: 10000.00

In this example:

• backtrader was imported


• The Cerebro engine was instantiated
• The resulting cerebro instance was told to run (loop over data)
• And the resulting outcome was printed out

Although it doesn’t seem much, let’s point out something explicitly shown:

• The Cerebro engine has created a broker instance in the background


• The instance already has some cash to start with

This behind the scenes broker instantiation is a constant trait in the platform
to simplify the life of the user. If no broker is set by the user, a default one
is put in place.

And 10K monetary units is a usual value with some brokers to begin with.

Setting the Cash

In the world of finance, for sure only “losers” start with 10k. Let’s change
the cash and run the example again.

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import backtrader as bt

if __name__ == '__main__':
cerebro = bt.Cerebro()
8
backtrader’s documentation Version-1.9.58.122

cerebro.broker.setcash(100000.0)

print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

cerebro.run()

print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

After the execution the output is:

Starting Portfolio Value: 1000000.00


Final Portfolio Value: 1000000.00

Mission accomplished. Let’s move to tempestuous waters.

Adding a Data Feed

Having cash is fun, but the purpose behind all this is to let an automated
strategy multiply the cash without moving a finger by operating on an asset which
we see as a Data Feed

Ergo ... No Data Feed -> No Fun. Let’s add one to the ever growing example.

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import datetime # For datetime objects


import os.path # To manage paths
import sys # To find out the script name (in argv[0])

# Import the backtrader platform


import backtrader as bt

if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()

# Datas are in a subfolder of the samples. Need to find where the script is
# because it could have been called from anywhere
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

9
backtrader’s documentation Version-1.9.58.122

# Create a Data Feed


data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
# Do not pass values before this date
fromdate=datetime.datetime(2000, 1, 1),
# Do not pass values after this date
todate=datetime.datetime(2000, 12, 31),
reverse=False)

# Add the Data Feed to Cerebro


cerebro.adddata(data)

# Set our desired cash start


cerebro.broker.setcash(100000.0)

# Print out the starting conditions


print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

# Run over everything


cerebro.run()

# Print out the final result


print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

After the execution the output is:

Starting Portfolio Value: 1000000.00


Final Portfolio Value: 1000000.00

The amount of boilerplate has grown slightly, because we added:

• Finding out where our example script is to be able to locate the


sample Data Feed file
• Having datetime objects to filter on which data from the Data Feed we
will be operatign

Aside from that, the Data Feed is created and added to cerebro.

The output has not changed and it would be a miracle if it had.

Note

Yahoo Online sends the CSV data in date descending order, which is not the
standard convention. The reversed=True prameter takes into account that the CSV
10
backtrader’s documentation Version-1.9.58.122

data in the file has already been reversed and has the standard expected date
ascending order.

Our First Strategy¶

The cash is in the broker and the Data Feed is there. It seems like risky
business is just around the corner.

Let’s put a Strategy into the equation and print the “Close” price of each day
(bar).

DataSeries (the underlying class in Data Feeds) objects have aliases to access
the well known OHLC (Open High Low Close) daily values. This should ease up the
creation of our printing logic.

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import datetime # For datetime objects


import os.path # To manage paths
import sys # To find out the script name (in argv[0])

# Import the backtrader platform


import backtrader as bt

# Create a Stratey
class TestStrategy(bt.Strategy):

def log(self, txt, dt=None):


''' Logging function for this strategy'''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))

def __init__(self):
# Keep a reference to the "close" line in the data[0] dataseries
self.dataclose = self.datas[0].close

def next(self):
# Simply log the closing price of the series from the reference
self.log('Close, %.2f' % self.dataclose[0])

11
backtrader’s documentation Version-1.9.58.122

if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()

# Add a strategy
cerebro.addstrategy(TestStrategy)

# Datas are in a subfolder of the samples. Need to find where the script is
# because it could have been called from anywhere
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

# Create a Data Feed


data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
# Do not pass values before this date
fromdate=datetime.datetime(2000, 1, 1),
# Do not pass values before this date
todate=datetime.datetime(2000, 12, 31),
# Do not pass values after this date
reverse=False)

# Add the Data Feed to Cerebro


cerebro.adddata(data)

# Set our desired cash start


cerebro.broker.setcash(100000.0)

# Print out the starting conditions


print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

# Run over everything


cerebro.run()

# Print out the final result


print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

After the execution the output is:

Starting Portfolio Value: 100000.00


2000-01-03T00:00:00, Close, 27.85
2000-01-04T00:00:00, Close, 25.39
2000-01-05T00:00:00, Close, 24.05

12
backtrader’s documentation Version-1.9.58.122

...
...
...
2000-12-26T00:00:00, Close, 29.17
2000-12-27T00:00:00, Close, 28.94
2000-12-28T00:00:00, Close, 29.29
2000-12-29T00:00:00, Close, 27.41
Final Portfolio Value: 100000.00

Someone said the stockmarket was risky business, but it doesn’t seem so.

Let’s explain some of the magic:

• Upon __init__ being called the strategy already has a list of datas that
are present in the platform

This is a standard Python list and datas can be accessed in the order they
were inserted.

The first data in the list self.datas[0] is the default data for trading
operations and to keep all strategy elements synchronized (it’s the system
clock)

• self.dataclose = self.datas[0].close keeps a reference to the close line.


Only one level of indirection is later needed to access the close values.
• The strategy next method will be called on each bar of the system clock
(self.datas[0]). This is true until other things come into play like
indicators, which need some bars to start producing an output. More on that
later.

Adding some Logic to the Strategy

Let’s try some crazy idea we had by looking at some charts

• If the price has been falling 3 sessions in a row ... BUY BUY BUY!!!

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import datetime # For datetime objects


import os.path # To manage paths
import sys # To find out the script name (in argv[0])

# Import the backtrader platform


13
backtrader’s documentation Version-1.9.58.122

import backtrader as bt

# Create a Stratey
class TestStrategy(bt.Strategy):

def log(self, txt, dt=None):


''' Logging function fot this strategy'''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))

def __init__(self):
# Keep a reference to the "close" line in the data[0] dataseries
self.dataclose = self.datas[0].close

def next(self):
# Simply log the closing price of the series from the reference
self.log('Close, %.2f' % self.dataclose[0])

if self.dataclose[0] < self.dataclose[-1]:


# current close less than previous close

if self.dataclose[-1] < self.dataclose[-2]:


# previous close less than the previous close

# BUY, BUY, BUY!!! (with all possible default parameters)


self.log('BUY CREATE, %.2f' % self.dataclose[0])
self.buy()

if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()

# Add a strategy
cerebro.addstrategy(TestStrategy)

# Datas are in a subfolder of the samples. Need to find where the script is
# because it could have been called from anywhere
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

# Create a Data Feed


data = bt.feeds.YahooFinanceCSVData(
14
backtrader’s documentation Version-1.9.58.122

dataname=datapath,
# Do not pass values before this date
fromdate=datetime.datetime(2000, 1, 1),
# Do not pass values before this date
todate=datetime.datetime(2000, 12, 31),
# Do not pass values after this date
reverse=False)

# Add the Data Feed to Cerebro


cerebro.adddata(data)

# Set our desired cash start


cerebro.broker.setcash(100000.0)

# Print out the starting conditions


print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

# Run over everything


cerebro.run()

# Print out the final result


print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

After the execution the output is:

Starting Portfolio Value: 100000.00


2000-01-03, Close, 27.85
2000-01-04, Close, 25.39
2000-01-05, Close, 24.05
2000-01-05, BUY CREATE, 24.05
2000-01-06, Close, 22.63
2000-01-06, BUY CREATE, 22.63
2000-01-07, Close, 24.37
...
...
...
2000-12-20, BUY CREATE, 26.88
2000-12-21, Close, 27.82
2000-12-22, Close, 30.06
2000-12-26, Close, 29.17
2000-12-27, Close, 28.94
2000-12-27, BUY CREATE, 28.94
2000-12-28, Close, 29.29
2000-12-29, Close, 27.41

15
backtrader’s documentation Version-1.9.58.122

Final Portfolio Value: 99725.08

Several “BUY” creation orders were issued, our porftolio value was decremented.
A couple of important things are clearly missing.

• The order was created but it is unknown if it was executed, when and at
what price.

The next example will build upon that by listening to notifications of


order status.

The curious reader may ask how many shares are being bought, what asset is being
bought and how are orders being executed. Where possible (and in this case it is)
the platform fills in the gaps:

• self.datas[0] (the main data aka system clock) is the target asset if no other one is specified
• The stake is provided behind the scenes by a position sizer which uses a fixed stake, being the default “1”.
It will be modified in a later example
• The order is executed “At Market”. The broker (shown in previous examples) executes this using the
opening price of the next bar, because that’s the 1st tick after the current under examination bar.
• The order is executed so far without any commission (more on that later)

Do not only buy ... but SELL

After knowing how to enter the market (long), an “exit concept” is needed and
also understanding whether the strategy is in the market.

• Luckily a Strategy object offers access to a position attribute for the default data feed
• Methods buy and sell return the created (not yet executed) order
• Changes in orders’ status will be notified to the strategy via a notify method

The “exit concept” will be an easy one:

• Exit after 5 bars (on the 6th bar) have elapsed for good or for worse

Please notice that there is no “time” or “timeframe” implied: number of


bars. The bars can represent 1 minute, 1 hour, 1 day, 1 week or any other
time period.

Although we know the data source is a daily one, the strategy makes no
assumption about that.

Additionally and to simplify:

• Do only allow a Buy order if not yet in the market


16
backtrader’s documentation Version-1.9.58.122

Note

The next method gets no “bar index” passed and therefore it seems obscure how
to understand when 5 bars may have elapsed, but this has been modeled in pythonic
way: call len on an object and it will tell you the length of its lines. Just
write down (save in a variable) at which length in an operation took place and
see if the current length is 5 bars away.

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import datetime # For datetime objects


import os.path # To manage paths
import sys # To find out the script name (in argv[0])

# Import the backtrader platform


import backtrader as bt

# Create a Stratey
class TestStrategy(bt.Strategy):

def log(self, txt, dt=None):


''' Logging function fot this strategy'''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))

def __init__(self):
# Keep a reference to the "close" line in the data[0] dataseries
self.dataclose = self.datas[0].close

# To keep track of pending orders


self.order = None

def notify_order(self, order):


if order.status in [order.Submitted, order.Accepted]:
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
return

# Check if an order has been completed


# Attention: broker could reject order if not enougth cash
if order.status in [order.Completed]:
if order.isbuy():
self.log('BUY EXECUTED, %.2f' % order.executed.price)

17
backtrader’s documentation Version-1.9.58.122

elif order.issell():
self.log('SELL EXECUTED, %.2f' % order.executed.price)

self.bar_executed = len(self)

elif order.status in [order.Canceled, order.Margin, order.Rejected]:


self.log('Order Canceled/Margin/Rejected')

# Write down: no pending order


self.order = None

def next(self):
# Simply log the closing price of the series from the reference
self.log('Close, %.2f' % self.dataclose[0])

# Check if an order is pending ... if yes, we cannot send a 2nd one


if self.order:
return

# Check if we are in the market


if not self.position:

# Not yet ... we MIGHT BUY if ...


if self.dataclose[0] < self.dataclose[-1]:
# current close less than previous close

if self.dataclose[-1] < self.dataclose[-2]:


# previous close less than the previous close

# BUY, BUY, BUY!!! (with default parameters)


self.log('BUY CREATE, %.2f' % self.dataclose[0])

# Keep track of the created order to avoid a 2nd order


self.order = self.buy()

else:

# Already in the market ... we might sell


if len(self) >= (self.bar_executed + 5):
# SELL, SELL, SELL!!! (with all possible default parameters)
self.log('SELL CREATE, %.2f' % self.dataclose[0])

# Keep track of the created order to avoid a 2nd order


self.order = self.sell()
18
backtrader’s documentation Version-1.9.58.122

if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()

# Add a strategy
cerebro.addstrategy(TestStrategy)

# Datas are in a subfolder of the samples. Need to find where the script is
# because it could have been called from anywhere
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

# Create a Data Feed


data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
# Do not pass values before this date
fromdate=datetime.datetime(2000, 1, 1),
# Do not pass values before this date
todate=datetime.datetime(2000, 12, 31),
# Do not pass values after this date
reverse=False)

# Add the Data Feed to Cerebro


cerebro.adddata(data)

# Set our desired cash start


cerebro.broker.setcash(100000.0)

# Print out the starting conditions


print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

# Run over everything


cerebro.run()

# Print out the final result


print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

After the execution the output is:

Starting Portfolio Value: 100000.00


2000-01-03T00:00:00, Close, 27.85
2000-01-04T00:00:00, Close, 25.39

19
backtrader’s documentation Version-1.9.58.122

2000-01-05T00:00:00, Close, 24.05


2000-01-05T00:00:00, BUY CREATE, 24.05
2000-01-06T00:00:00, BUY EXECUTED, 23.61
2000-01-06T00:00:00, Close, 22.63
2000-01-07T00:00:00, Close, 24.37
2000-01-10T00:00:00, Close, 27.29
2000-01-11T00:00:00, Close, 26.49
2000-01-12T00:00:00, Close, 24.90
2000-01-13T00:00:00, Close, 24.77
2000-01-13T00:00:00, SELL CREATE, 24.77
2000-01-14T00:00:00, SELL EXECUTED, 25.70
2000-01-14T00:00:00, Close, 25.18
...
...
...
2000-12-15T00:00:00, SELL CREATE, 26.93
2000-12-18T00:00:00, SELL EXECUTED, 28.29
2000-12-18T00:00:00, Close, 30.18
2000-12-19T00:00:00, Close, 28.88
2000-12-20T00:00:00, Close, 26.88
2000-12-20T00:00:00, BUY CREATE, 26.88
2000-12-21T00:00:00, BUY EXECUTED, 26.23
2000-12-21T00:00:00, Close, 27.82
2000-12-22T00:00:00, Close, 30.06
2000-12-26T00:00:00, Close, 29.17
2000-12-27T00:00:00, Close, 28.94
2000-12-28T00:00:00, Close, 29.29
2000-12-29T00:00:00, Close, 27.41
2000-12-29T00:00:00, SELL CREATE, 27.41
Final Portfolio Value: 100018.53

Blistering Barnacles!!! The system made money ... something must be wrong

The broker says: Show me the money!

And the money is called “commission”.

Let’s add a reasonable 0.1% commision rate per operation (both for buying and
selling ... yes the broker is avid ...)

A single line will suffice for it:

cerebro.broker.setcommission(commission=0.001) # 0.1% ... divide by 100 to remove


the %
20
backtrader’s documentation Version-1.9.58.122

Being experienced with the platform we want to see the profit or loss after a
buy/sell cycle, with and without commission.

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import datetime # For datetime objects


import os.path # To manage paths
import sys # To find out the script name (in argv[0])

# Import the backtrader platform


import backtrader as bt

# Create a Stratey
class TestStrategy(bt.Strategy):

def log(self, txt, dt=None):


''' Logging function fot this strategy'''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))

def __init__(self):
# Keep a reference to the "close" line in the data[0] dataseries
self.dataclose = self.datas[0].close

# To keep track of pending orders and buy price/commission


self.order = None
self.buyprice = None
self.buycomm = None

def notify_order(self, order):


if order.status in [order.Submitted, order.Accepted]:
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
return

# Check if an order has been completed


# Attention: broker could reject order if not enougth cash
if order.status in [order.Completed]:
if order.isbuy():
self.log(
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,

21
backtrader’s documentation Version-1.9.58.122

order.executed.comm))

self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else: # Sell
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))

self.bar_executed = len(self)

elif order.status in [order.Canceled, order.Margin, order.Rejected]:


self.log('Order Canceled/Margin/Rejected')

self.order = None

def notify_trade(self, trade):


if not trade.isclosed:
return

self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %


(trade.pnl, trade.pnlcomm))

def next(self):
# Simply log the closing price of the series from the reference
self.log('Close, %.2f' % self.dataclose[0])

# Check if an order is pending ... if yes, we cannot send a 2nd one


if self.order:
return

# Check if we are in the market


if not self.position:

# Not yet ... we MIGHT BUY if ...


if self.dataclose[0] < self.dataclose[-1]:
# current close less than previous close

if self.dataclose[-1] < self.dataclose[-2]:


# previous close less than the previous close

# BUY, BUY, BUY!!! (with default parameters)


self.log('BUY CREATE, %.2f' % self.dataclose[0])
22
backtrader’s documentation Version-1.9.58.122

# Keep track of the created order to avoid a 2nd order


self.order = self.buy()

else:

# Already in the market ... we might sell


if len(self) >= (self.bar_executed + 5):
# SELL, SELL, SELL!!! (with all possible default parameters)
self.log('SELL CREATE, %.2f' % self.dataclose[0])

# Keep track of the created order to avoid a 2nd order


self.order = self.sell()

if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()

# Add a strategy
cerebro.addstrategy(TestStrategy)

# Datas are in a subfolder of the samples. Need to find where the script is
# because it could have been called from anywhere
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

# Create a Data Feed


data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
# Do not pass values before this date
fromdate=datetime.datetime(2000, 1, 1),
# Do not pass values before this date
todate=datetime.datetime(2000, 12, 31),
# Do not pass values after this date
reverse=False)

# Add the Data Feed to Cerebro


cerebro.adddata(data)

# Set our desired cash start


cerebro.broker.setcash(100000.0)

# Set the commission - 0.1% ... divide by 100 to remove the %


23
backtrader’s documentation Version-1.9.58.122

cerebro.broker.setcommission(commission=0.001)

# Print out the starting conditions


print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

# Run over everything


cerebro.run()

# Print out the final result


print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

After the execution the output is:

Starting Portfolio Value: 100000.00


2000-01-03T00:00:00, Close, 27.85
2000-01-04T00:00:00, Close, 25.39
2000-01-05T00:00:00, Close, 24.05
2000-01-05T00:00:00, BUY CREATE, 24.05
2000-01-06T00:00:00, BUY EXECUTED, Price: 23.61, Cost: 23.61, Commission 0.02
2000-01-06T00:00:00, Close, 22.63
2000-01-07T00:00:00, Close, 24.37
2000-01-10T00:00:00, Close, 27.29
2000-01-11T00:00:00, Close, 26.49
2000-01-12T00:00:00, Close, 24.90
2000-01-13T00:00:00, Close, 24.77
2000-01-13T00:00:00, SELL CREATE, 24.77
2000-01-14T00:00:00, SELL EXECUTED, Price: 25.70, Cost: 25.70, Commission 0.03
2000-01-14T00:00:00, OPERATION PROFIT, GROSS 2.09, NET 2.04
2000-01-14T00:00:00, Close, 25.18
...
...
...
2000-12-15T00:00:00, SELL CREATE, 26.93
2000-12-18T00:00:00, SELL EXECUTED, Price: 28.29, Cost: 28.29, Commission 0.03
2000-12-18T00:00:00, OPERATION PROFIT, GROSS -0.06, NET -0.12
2000-12-18T00:00:00, Close, 30.18
2000-12-19T00:00:00, Close, 28.88
2000-12-20T00:00:00, Close, 26.88
2000-12-20T00:00:00, BUY CREATE, 26.88
2000-12-21T00:00:00, BUY EXECUTED, Price: 26.23, Cost: 26.23, Commission 0.03
2000-12-21T00:00:00, Close, 27.82
2000-12-22T00:00:00, Close, 30.06
2000-12-26T00:00:00, Close, 29.17
2000-12-27T00:00:00, Close, 28.94

24
backtrader’s documentation Version-1.9.58.122

2000-12-28T00:00:00, Close, 29.29


2000-12-29T00:00:00, Close, 27.41
2000-12-29T00:00:00, SELL CREATE, 27.41
Final Portfolio Value: 100016.98

God Save the Queen!!! The system still made money.

Before moving on, let’s notice something by filtering the “OPERATION PROFIT”
lines:

2000-01-14T00:00:00, OPERATION PROFIT, GROSS 2.09, NET 2.04


2000-02-07T00:00:00, OPERATION PROFIT, GROSS 3.68, NET 3.63
2000-02-28T00:00:00, OPERATION PROFIT, GROSS 4.48, NET 4.42
2000-03-13T00:00:00, OPERATION PROFIT, GROSS 3.48, NET 3.41
2000-03-22T00:00:00, OPERATION PROFIT, GROSS -0.41, NET -0.49
2000-04-07T00:00:00, OPERATION PROFIT, GROSS 2.45, NET 2.37
2000-04-20T00:00:00, OPERATION PROFIT, GROSS -1.95, NET -2.02
2000-05-02T00:00:00, OPERATION PROFIT, GROSS 5.46, NET 5.39
2000-05-11T00:00:00, OPERATION PROFIT, GROSS -3.74, NET -3.81
2000-05-30T00:00:00, OPERATION PROFIT, GROSS -1.46, NET -1.53
2000-07-05T00:00:00, OPERATION PROFIT, GROSS -1.62, NET -1.69
2000-07-14T00:00:00, OPERATION PROFIT, GROSS 2.08, NET 2.01
2000-07-28T00:00:00, OPERATION PROFIT, GROSS 0.14, NET 0.07
2000-08-08T00:00:00, OPERATION PROFIT, GROSS 4.36, NET 4.29
2000-08-21T00:00:00, OPERATION PROFIT, GROSS 1.03, NET 0.95
2000-09-15T00:00:00, OPERATION PROFIT, GROSS -4.26, NET -4.34
2000-09-27T00:00:00, OPERATION PROFIT, GROSS 1.29, NET 1.22
2000-10-13T00:00:00, OPERATION PROFIT, GROSS -2.98, NET -3.04
2000-10-26T00:00:00, OPERATION PROFIT, GROSS 3.01, NET 2.95
2000-11-06T00:00:00, OPERATION PROFIT, GROSS -3.59, NET -3.65
2000-11-16T00:00:00, OPERATION PROFIT, GROSS 1.28, NET 1.23
2000-12-01T00:00:00, OPERATION PROFIT, GROSS 2.59, NET 2.54
2000-12-18T00:00:00, OPERATION PROFIT, GROSS -0.06, NET -0.12

Adding up the “NET” profits the final figure is:

15.83

But the system said the following at the end:

2000-12-29T00:00:00, SELL CREATE, 27.41


Final Portfolio Value: 100016.98

And obviously 15.83 is not 16.98. There is no error whatsoever. The “NET”
profit of 15.83 is already cash in the bag.
25
backtrader’s documentation Version-1.9.58.122

Unfortunately (or fortunately to better understand the platform) there is an open


position on the last day of the Data Feed. Even if a SELL operation has been
sent ... IT HAS NOT YET BEEN EXECUTED.

The “Final Portfolio Value” calculated by the broker takes into account the
“Close” price on 2000-12-29. The actual execution price would have been set on
the next trading day which happened to be 2001-01-02. Extending the Data Feed”
to take into account this day the output is:

2001-01-02T00:00:00, SELL EXECUTED, Price: 27.87, Cost: 27.87, Commission 0.03


2001-01-02T00:00:00, OPERATION PROFIT, GROSS 1.64, NET 1.59
2001-01-02T00:00:00, Close, 24.87
2001-01-02T00:00:00, BUY CREATE, 24.87
Final Portfolio Value: 100017.41

Now adding the previous NET profit to the completed operation’s net profit:

15.83 + 1.59 = 17.42

Which (discarding rounding errors in the “print” statements) is the extra


Portfolio above the initial 100000 monetary units the strategy started with.

Customizing the Strategy: Parameters

It would a bit unpractical to hardcode some of the values in the strategy and
have no chance to change them easily. Parameters come in handy to help.

Definition of parameters is easy and looks like:

params = (('myparam', 27), ('exitbars', 5),)

Being this a standard Python tuple with some tuples inside it, the following may
look more appealling to some:

params = (
('myparam', 27),
('exitbars', 5),
)

With either formatting parametrization of the strategy is allowed when adding the
strategy to the Cerebro engine:

# Add a strategy
cerebro.addstrategy(TestStrategy, myparam=20, exitbars=7)

26
backtrader’s documentation Version-1.9.58.122

Note

The setsizing method below is deprecated. This content is kept here for anyone
looking at old samples of the sources. The sources have been update to use:

cerebro.addsizer(bt.sizers.FixedSize, stake=10)``

Please read the section about sizers

Using the parameters in the strategy is easy, as they are stored in a “params”
attribute. If we for example want to set the stake fix, we can pass the stake
parameter to the position sizer like this durint __init__:

# Set the sizer stake from the params


self.sizer.setsizing(self.params.stake)

We could have also called buy and sell with a stake parameter and
self.params.stake as the value.

The logic to exit gets modified:

# Already in the market ... we might sell


if len(self) >= (self.bar_executed + self.params.exitbars):

With all this in mind the example evolves to look like:

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import datetime # For datetime objects


import os.path # To manage paths
import sys # To find out the script name (in argv[0])

# Import the backtrader platform


import backtrader as bt

# Create a Stratey
class TestStrategy(bt.Strategy):
params = (
('exitbars', 5),
)

def log(self, txt, dt=None):


''' Logging function fot this strategy'''

27
backtrader’s documentation Version-1.9.58.122

dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))

def __init__(self):
# Keep a reference to the "close" line in the data[0] dataseries
self.dataclose = self.datas[0].close

# To keep track of pending orders and buy price/commission


self.order = None
self.buyprice = None
self.buycomm = None

def notify_order(self, order):


if order.status in [order.Submitted, order.Accepted]:
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
return

# Check if an order has been completed


# Attention: broker could reject order if not enougth cash
if order.status in [order.Completed]:
if order.isbuy():
self.log(
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))

self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else: # Sell
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))

self.bar_executed = len(self)

elif order.status in [order.Canceled, order.Margin, order.Rejected]:


self.log('Order Canceled/Margin/Rejected')

self.order = None

def notify_trade(self, trade):


if not trade.isclosed:
28
backtrader’s documentation Version-1.9.58.122

return

self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %


(trade.pnl, trade.pnlcomm))

def next(self):
# Simply log the closing price of the series from the reference
self.log('Close, %.2f' % self.dataclose[0])

# Check if an order is pending ... if yes, we cannot send a 2nd one


if self.order:
return

# Check if we are in the market


if not self.position:

# Not yet ... we MIGHT BUY if ...


if self.dataclose[0] < self.dataclose[-1]:
# current close less than previous close

if self.dataclose[-1] < self.dataclose[-2]:


# previous close less than the previous close

# BUY, BUY, BUY!!! (with default parameters)


self.log('BUY CREATE, %.2f' % self.dataclose[0])

# Keep track of the created order to avoid a 2nd order


self.order = self.buy()

else:

# Already in the market ... we might sell


if len(self) >= (self.bar_executed + self.params.exitbars):
# SELL, SELL, SELL!!! (with all possible default parameters)
self.log('SELL CREATE, %.2f' % self.dataclose[0])

# Keep track of the created order to avoid a 2nd order


self.order = self.sell()

if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()

# Add a strategy
29
backtrader’s documentation Version-1.9.58.122

cerebro.addstrategy(TestStrategy)

# Datas are in a subfolder of the samples. Need to find where the script is
# because it could have been called from anywhere
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

# Create a Data Feed


data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
# Do not pass values before this date
fromdate=datetime.datetime(2000, 1, 1),
# Do not pass values before this date
todate=datetime.datetime(2000, 12, 31),
# Do not pass values after this date
reverse=False)

# Add the Data Feed to Cerebro


cerebro.adddata(data)

# Set our desired cash start


cerebro.broker.setcash(100000.0)

# Add a FixedSize sizer according to the stake


cerebro.addsizer(bt.sizers.FixedSize, stake=10)

# Set the commission - 0.1% ... divide by 100 to remove the %


cerebro.broker.setcommission(commission=0.001)

# Print out the starting conditions


print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

# Run over everything


cerebro.run()

# Print out the final result


print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

After the execution the output is:

Starting Portfolio Value: 100000.00


2000-01-03T00:00:00, Close, 27.85
2000-01-04T00:00:00, Close, 25.39
2000-01-05T00:00:00, Close, 24.05

30
backtrader’s documentation Version-1.9.58.122

2000-01-05T00:00:00, BUY CREATE, 24.05


2000-01-06T00:00:00, BUY EXECUTED, Size 10, Price: 23.61, Cost: 236.10,
Commission 0.24
2000-01-06T00:00:00, Close, 22.63
...
...
...
2000-12-20T00:00:00, BUY CREATE, 26.88
2000-12-21T00:00:00, BUY EXECUTED, Size 10, Price: 26.23, Cost: 262.30,
Commission 0.26
2000-12-21T00:00:00, Close, 27.82
2000-12-22T00:00:00, Close, 30.06
2000-12-26T00:00:00, Close, 29.17
2000-12-27T00:00:00, Close, 28.94
2000-12-28T00:00:00, Close, 29.29
2000-12-29T00:00:00, Close, 27.41
2000-12-29T00:00:00, SELL CREATE, 27.41
Final Portfolio Value: 100169.80

In order to see the difference, the print outputs have also been extended to show
the execution size.

Having multiplied the stake by 10, the obvious has happened: the profit and loss
has been multiplied by 10. Instead of 16.98, the surplus is now 169.80

Adding an indicator

Having heard of indicators, the next thing anyone would add to the strategy is
one of them. For sure they must be much better than a simple “3 lower closes”
strategy.

Inspired in one of the examples from PyAlgoTrade a strategy using a Simple Moving
Average.

• Buy “AtMarket” if the close is greater than the Average


• If in the market, sell if the close is smaller than the Average
• Only 1 active operation is allowed in the market

Most of the existing code can be kept in place. Let’s add the average during
__init__ and keep a reference to it:

self.sma = bt.indicators.MovingAverageSimple(self.datas[0],
period=self.params.maperiod)

31
backtrader’s documentation Version-1.9.58.122

And of course the logic to enter and exit the market will rely on the Average
values. Look in the code for the logic.

Note

The starting cash will be 1000 monetary units to be in line with the PyAlgoTrade
example and no commission will be applied

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import datetime # For datetime objects


import os.path # To manage paths
import sys # To find out the script name (in argv[0])

# Import the backtrader platform


import backtrader as bt

# Create a Stratey
class TestStrategy(bt.Strategy):
params = (
('maperiod', 15),
)

def log(self, txt, dt=None):


''' Logging function fot this strategy'''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))

def __init__(self):
# Keep a reference to the "close" line in the data[0] dataseries
self.dataclose = self.datas[0].close

# To keep track of pending orders and buy price/commission


self.order = None
self.buyprice = None
self.buycomm = None

# Add a MovingAverageSimple indicator


self.sma = bt.indicators.SimpleMovingAverage(
self.datas[0], period=self.params.maperiod)

def notify_order(self, order):


if order.status in [order.Submitted, order.Accepted]:
32
backtrader’s documentation Version-1.9.58.122

# Buy/Sell order submitted/accepted to/by broker - Nothing to do


return

# Check if an order has been completed


# Attention: broker could reject order if not enougth cash
if order.status in [order.Completed]:
if order.isbuy():
self.log(
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))

self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else: # Sell
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))

self.bar_executed = len(self)

elif order.status in [order.Canceled, order.Margin, order.Rejected]:


self.log('Order Canceled/Margin/Rejected')

self.order = None

def notify_trade(self, trade):


if not trade.isclosed:
return

self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %


(trade.pnl, trade.pnlcomm))

def next(self):
# Simply log the closing price of the series from the reference
self.log('Close, %.2f' % self.dataclose[0])

# Check if an order is pending ... if yes, we cannot send a 2nd one


if self.order:
return

# Check if we are in the market


33
backtrader’s documentation Version-1.9.58.122

if not self.position:

# Not yet ... we MIGHT BUY if ...


if self.dataclose[0] > self.sma[0]:

# BUY, BUY, BUY!!! (with all possible default parameters)


self.log('BUY CREATE, %.2f' % self.dataclose[0])

# Keep track of the created order to avoid a 2nd order


self.order = self.buy()

else:

if self.dataclose[0] < self.sma[0]:


# SELL, SELL, SELL!!! (with all possible default parameters)
self.log('SELL CREATE, %.2f' % self.dataclose[0])

# Keep track of the created order to avoid a 2nd order


self.order = self.sell()

if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()

# Add a strategy
cerebro.addstrategy(TestStrategy)

# Datas are in a subfolder of the samples. Need to find where the script is
# because it could have been called from anywhere
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

# Create a Data Feed


data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
# Do not pass values before this date
fromdate=datetime.datetime(2000, 1, 1),
# Do not pass values before this date
todate=datetime.datetime(2000, 12, 31),
# Do not pass values after this date
reverse=False)

# Add the Data Feed to Cerebro


34
backtrader’s documentation Version-1.9.58.122

cerebro.adddata(data)

# Set our desired cash start


cerebro.broker.setcash(1000.0)

# Add a FixedSize sizer according to the stake


cerebro.addsizer(bt.sizers.FixedSize, stake=10)

# Set the commission


cerebro.broker.setcommission(commission=0.0)

# Print out the starting conditions


print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

# Run over everything


cerebro.run()

# Print out the final result


print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

Now, before skipping to the next section LOOK CAREFULLY to the first date which
is shown in the log:

• It’ no longer 2000-01-03, the first trading day in the year 2K.

It’s 2000-01-24 ... Who has stolen my cheese?

The missing days are not missing. The platform has adapted to the new
circumstances:

• An indicator (SimpleMovingAverage) has been added to the Strategy.


• This indicator needs X bars to produce an output: in the example: 15
• 2000-01-24 is the day in which the 15th bar occurs

The backtrader platform assumes that the Strategy has the indicator in place for
a good reason, to use it in the decision making process. And it makes no sense
to try to make decisions if the indicator is not yet ready and producing values.

• next will be 1st called when all indicators have already reached the minimum needed period to produce a
value
• In the example there is a single indicator, but the strategy could have any number of them.

After the execution the output is:

Starting Portfolio Value: 1000.00


35
backtrader’s documentation Version-1.9.58.122

2000-01-24T00:00:00, Close, 25.55


2000-01-25T00:00:00, Close, 26.61
2000-01-25T00:00:00, BUY CREATE, 26.61
2000-01-26T00:00:00, BUY EXECUTED, Size 10, Price: 26.76, Cost: 267.60,
Commission 0.00
2000-01-26T00:00:00, Close, 25.96
2000-01-27T00:00:00, Close, 24.43
2000-01-27T00:00:00, SELL CREATE, 24.43
2000-01-28T00:00:00, SELL EXECUTED, Size 10, Price: 24.28, Cost: 242.80,
Commission 0.00
2000-01-28T00:00:00, OPERATION PROFIT, GROSS -24.80, NET -24.80
2000-01-28T00:00:00, Close, 22.34
2000-01-31T00:00:00, Close, 23.55
2000-02-01T00:00:00, Close, 25.46
2000-02-02T00:00:00, Close, 25.61
2000-02-02T00:00:00, BUY CREATE, 25.61
2000-02-03T00:00:00, BUY EXECUTED, Size 10, Price: 26.11, Cost: 261.10,
Commission 0.00
...
...
...
2000-12-20T00:00:00, SELL CREATE, 26.88
2000-12-21T00:00:00, SELL EXECUTED, Size 10, Price: 26.23, Cost: 262.30,
Commission 0.00
2000-12-21T00:00:00, OPERATION PROFIT, GROSS -20.60, NET -20.60
2000-12-21T00:00:00, Close, 27.82
2000-12-21T00:00:00, BUY CREATE, 27.82
2000-12-22T00:00:00, BUY EXECUTED, Size 10, Price: 28.65, Cost: 286.50,
Commission 0.00
2000-12-22T00:00:00, Close, 30.06
2000-12-26T00:00:00, Close, 29.17
2000-12-27T00:00:00, Close, 28.94
2000-12-28T00:00:00, Close, 29.29
2000-12-29T00:00:00, Close, 27.41
2000-12-29T00:00:00, SELL CREATE, 27.41
Final Portfolio Value: 973.90

In the name of the King!!! A winning system turned into a losing one ... and that
with no commission. It may well be that simply adding an indicator is not the
universal panacea.

Note

36
backtrader’s documentation Version-1.9.58.122

The same logic and data with PyAlgoTrade yields a slightly different result
(slightly off). Looking at the entire printout reveals that some operations are
not exactly the same. Being the culprit again the usual suspect: rounding.

PyAlgoTrade does not round the datafeed values when applying the divided
“adjusted close” to the data feed values.

The Yahoo Data Feed provided by backtrader rounds the values down to 2 decimals
after applying the adjusted close. Upon printing the values everything seems the
same, but it’s obvious that sometimes that 5th place decimal plays a role.

Rounding down to 2 decimals seems more realistic, because Market Exchanges do


only allow a number of decimals per asset (being that 2 decimals usually for
stocks)

Note

The Yahoo Data Feed (starting with version 1.8.11.99 allows to specify if
rounding has to happen and how many decimals)

Visual Inspection: Plotting

A printout or log of the actual whereabouts of the system at each bar-instant is


good but humans tend to be visual and therefore it seems right to offer a view of
the same whereabouts as chart.

Note

To plot you need to have matplotlib installed

Once again defaults for plotting are there to assist the platform user. Plotting
is incredibly a 1 line operation:

cerebro.plot()

Being the location for sure after cerebro.run() has been called.

In order to display the automatic plotting capabilities and a couple of easy


customizations, the following will be done:

• A 2nd MovingAverage (Exponential) will be added. The defaults will plot it (just like the 1st) with the data.
• A 3rd MovingAverage (Weighted) will be added. Customized to plot in an own plot (even if not sensible)
• A Stochastic (Slow) will be added. No change to the defaults.
• A MACD will be added. No change to the defaults.
• A RSI will be added. No change to the defaults.
37
backtrader’s documentation Version-1.9.58.122

• A MovingAverage (Simple) will be applied to the RSI. No change to the defaults (it will be plotted with the
RSI)
• An AverageTrueRange will be added. Changed defaults to avoid it being plotted.

The entire set of additions to the __init__ method of the Strategy:

# Indicators for the plotting show


bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
bt.indicators.WeightedMovingAverage(self.datas[0], period=25).subplot = True
bt.indicators.StochasticSlow(self.datas[0])
bt.indicators.MACDHisto(self.datas[0])
rsi = bt.indicators.RSI(self.datas[0])
bt.indicators.SmoothedMovingAverage(rsi, period=10)
bt.indicators.ATR(self.datas[0]).plot = False

Note

Even if indicators are not explicitly added to a member variable of the strategy
(like self.sma = MovingAverageSimple...), they will autoregister with the
strategy and will influence the minimum period for next and will be part of the
plotting.

In the example only RSI is added to a temporary variable rsi with the only
intention to create a MovingAverageSmoothed on it.

The example now:

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import datetime # For datetime objects


import os.path # To manage paths
import sys # To find out the script name (in argv[0])

# Import the backtrader platform


import backtrader as bt

# Create a Stratey
class TestStrategy(bt.Strategy):
params = (
('maperiod', 15),
)

def log(self, txt, dt=None):


38
backtrader’s documentation Version-1.9.58.122

''' Logging function fot this strategy'''


dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))

def __init__(self):
# Keep a reference to the "close" line in the data[0] dataseries
self.dataclose = self.datas[0].close

# To keep track of pending orders and buy price/commission


self.order = None
self.buyprice = None
self.buycomm = None

# Add a MovingAverageSimple indicator


self.sma = bt.indicators.SimpleMovingAverage(
self.datas[0], period=self.params.maperiod)

# Indicators for the plotting show


bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
bt.indicators.WeightedMovingAverage(self.datas[0], period=25,
subplot=True)
bt.indicators.StochasticSlow(self.datas[0])
bt.indicators.MACDHisto(self.datas[0])
rsi = bt.indicators.RSI(self.datas[0])
bt.indicators.SmoothedMovingAverage(rsi, period=10)
bt.indicators.ATR(self.datas[0], plot=False)

def notify_order(self, order):


if order.status in [order.Submitted, order.Accepted]:
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
return

# Check if an order has been completed


# Attention: broker could reject order if not enougth cash
if order.status in [order.Completed]:
if order.isbuy():
self.log(
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))

self.buyprice = order.executed.price
self.buycomm = order.executed.comm
39
backtrader’s documentation Version-1.9.58.122

else: # Sell
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))

self.bar_executed = len(self)

elif order.status in [order.Canceled, order.Margin, order.Rejected]:


self.log('Order Canceled/Margin/Rejected')

# Write down: no pending order


self.order = None

def notify_trade(self, trade):


if not trade.isclosed:
return

self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %


(trade.pnl, trade.pnlcomm))

def next(self):
# Simply log the closing price of the series from the reference
self.log('Close, %.2f' % self.dataclose[0])

# Check if an order is pending ... if yes, we cannot send a 2nd one


if self.order:
return

# Check if we are in the market


if not self.position:

# Not yet ... we MIGHT BUY if ...


if self.dataclose[0] > self.sma[0]:

# BUY, BUY, BUY!!! (with all possible default parameters)


self.log('BUY CREATE, %.2f' % self.dataclose[0])

# Keep track of the created order to avoid a 2nd order


self.order = self.buy()

else:

if self.dataclose[0] < self.sma[0]:


40
backtrader’s documentation Version-1.9.58.122

# SELL, SELL, SELL!!! (with all possible default parameters)


self.log('SELL CREATE, %.2f' % self.dataclose[0])

# Keep track of the created order to avoid a 2nd order


self.order = self.sell()

if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()

# Add a strategy
cerebro.addstrategy(TestStrategy)

# Datas are in a subfolder of the samples. Need to find where the script is
# because it could have been called from anywhere
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

# Create a Data Feed


data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
# Do not pass values before this date
fromdate=datetime.datetime(2000, 1, 1),
# Do not pass values before this date
todate=datetime.datetime(2000, 12, 31),
# Do not pass values after this date
reverse=False)

# Add the Data Feed to Cerebro


cerebro.adddata(data)

# Set our desired cash start


cerebro.broker.setcash(1000.0)

# Add a FixedSize sizer according to the stake


cerebro.addsizer(bt.sizers.FixedSize, stake=10)

# Set the commission


cerebro.broker.setcommission(commission=0.0)

# Print out the starting conditions


print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

41
backtrader’s documentation Version-1.9.58.122

# Run over everything


cerebro.run()

# Print out the final result


print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

# Plot the result


cerebro.plot()

After the execution the output is:

Starting Portfolio Value: 1000.00


2000-02-18T00:00:00, Close, 27.61
2000-02-22T00:00:00, Close, 27.97
2000-02-22T00:00:00, BUY CREATE, 27.97
2000-02-23T00:00:00, BUY EXECUTED, Size 10, Price: 28.38, Cost: 283.80,
Commission 0.00
2000-02-23T00:00:00, Close, 29.73
...
...
...
2000-12-21T00:00:00, BUY CREATE, 27.82
2000-12-22T00:00:00, BUY EXECUTED, Size 10, Price: 28.65, Cost: 286.50,
Commission 0.00
2000-12-22T00:00:00, Close, 30.06
2000-12-26T00:00:00, Close, 29.17
2000-12-27T00:00:00, Close, 28.94
2000-12-28T00:00:00, Close, 29.29
2000-12-29T00:00:00, Close, 27.41
2000-12-29T00:00:00, SELL CREATE, 27.41
Final Portfolio Value: 981.00
The final result has changed even if the logic hasn’t. This is true but the
logic has not been applied to the same number of bars.

Note

As explained before, the platform will first call next when all indicators are
ready to produce a value. In this plotting example (very clear in the chart) the
MACD is the last indicator to be fully ready (all 3 lines producing an output).
The 1st BUY order is no longer scheduled during Jan 2000 but close to the end of
Feb 2000.

The chart:

42
backtrader’s documentation Version-1.9.58.122

Let’s Optimize

Many trading books say each market and each traded stock (or commodity or ..)
have different rythms. That there is no such thing as a one size fits all.

Before the plotting sample, when the strategy started using an indicator the
period default value was 15 bars. It’s a strategy parameter and this can be used
in an optimization to change the value of the parameter and see which one better
fits the market.

Note

There is plenty of literature about Optimization and associated pros and cons.
But the advice will always point in the same direction: do not overoptimize. If a
trading idea is not sound, optimizing may end producing a positive result which
is only valid for the backtested dataset.

43
backtrader’s documentation Version-1.9.58.122

The sample is modified to optimize the period of the Simple Moving Average. For
the sake of clarity any output with regards to Buy/Sell orders has been removed

The example now:

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import datetime # For datetime objects


import os.path # To manage paths
import sys # To find out the script name (in argv[0])

# Import the backtrader platform


import backtrader as bt

# Create a Stratey
class TestStrategy(bt.Strategy):
params = (
('maperiod', 15),
('printlog', False),
)

def log(self, txt, dt=None, doprint=False):


''' Logging function fot this strategy'''
if self.params.printlog or doprint:
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))

def __init__(self):
# Keep a reference to the "close" line in the data[0] dataseries
self.dataclose = self.datas[0].close

# To keep track of pending orders and buy price/commission


self.order = None
self.buyprice = None
self.buycomm = None

# Add a MovingAverageSimple indicator


self.sma = bt.indicators.SimpleMovingAverage(
self.datas[0], period=self.params.maperiod)

def notify_order(self, order):

44
backtrader’s documentation Version-1.9.58.122

if order.status in [order.Submitted, order.Accepted]:


# Buy/Sell order submitted/accepted to/by broker - Nothing to do
return

# Check if an order has been completed


# Attention: broker could reject order if not enougth cash
if order.status in [order.Completed]:
if order.isbuy():
self.log(
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))

self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else: # Sell
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))

self.bar_executed = len(self)

elif order.status in [order.Canceled, order.Margin, order.Rejected]:


self.log('Order Canceled/Margin/Rejected')

# Write down: no pending order


self.order = None

def notify_trade(self, trade):


if not trade.isclosed:
return

self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %


(trade.pnl, trade.pnlcomm))

def next(self):
# Simply log the closing price of the series from the reference
self.log('Close, %.2f' % self.dataclose[0])

# Check if an order is pending ... if yes, we cannot send a 2nd one


if self.order:
return
45
backtrader’s documentation Version-1.9.58.122

# Check if we are in the market


if not self.position:

# Not yet ... we MIGHT BUY if ...


if self.dataclose[0] > self.sma[0]:

# BUY, BUY, BUY!!! (with all possible default parameters)


self.log('BUY CREATE, %.2f' % self.dataclose[0])

# Keep track of the created order to avoid a 2nd order


self.order = self.buy()

else:

if self.dataclose[0] < self.sma[0]:


# SELL, SELL, SELL!!! (with all possible default parameters)
self.log('SELL CREATE, %.2f' % self.dataclose[0])

# Keep track of the created order to avoid a 2nd order


self.order = self.sell()

def stop(self):
self.log('(MA Period %2d) Ending Value %.2f' %
(self.params.maperiod, self.broker.getvalue()), doprint=True)

if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()

# Add a strategy
strats = cerebro.optstrategy(
TestStrategy,
maperiod=range(10, 31))

# Datas are in a subfolder of the samples. Need to find where the script is
# because it could have been called from anywhere
modpath = os.path.dirname(os.path.abspath(sys.argv[0]))
datapath = os.path.join(modpath, '../../datas/orcl-1995-2014.txt')

# Create a Data Feed


data = bt.feeds.YahooFinanceCSVData(
dataname=datapath,
46
backtrader’s documentation Version-1.9.58.122

# Do not pass values before this date


fromdate=datetime.datetime(2000, 1, 1),
# Do not pass values before this date
todate=datetime.datetime(2000, 12, 31),
# Do not pass values after this date
reverse=False)

# Add the Data Feed to Cerebro


cerebro.adddata(data)

# Set our desired cash start


cerebro.broker.setcash(1000.0)

# Add a FixedSize sizer according to the stake


cerebro.addsizer(bt.sizers.FixedSize, stake=10)

# Set the commission


cerebro.broker.setcommission(commission=0.0)

# Run over everything


cerebro.run()

Instead of calling addstrategy to add a stratey class to Cerebro, the call is


made to optstrategy. And instead of passing a value a range of values is passed.

One of the “Strategy” hooks is added, the stop method, which will be called
when the data has been exhausted and backtesting is over. It’s used to print the
final net value of the portfolio in the broker (it was done in Cerebro
previously)

The system will execute the strategy for each value of the range. The following
will be output:

2000-12-29, (MA Period 10) Ending Value 880.30


2000-12-29, (MA Period 11) Ending Value 880.00
2000-12-29, (MA Period 12) Ending Value 830.30
2000-12-29, (MA Period 13) Ending Value 893.90
2000-12-29, (MA Period 14) Ending Value 896.90
2000-12-29, (MA Period 15) Ending Value 973.90
2000-12-29, (MA Period 16) Ending Value 959.40
2000-12-29, (MA Period 17) Ending Value 949.80
2000-12-29, (MA Period 18) Ending Value 1011.90
2000-12-29, (MA Period 19) Ending Value 1041.90
2000-12-29, (MA Period 20) Ending Value 1078.00
2000-12-29, (MA Period 21) Ending Value 1058.80
47
backtrader’s documentation Version-1.9.58.122

2000-12-29, (MA Period 22) Ending Value 1061.50


2000-12-29, (MA Period 23) Ending Value 1023.00
2000-12-29, (MA Period 24) Ending Value 1020.10
2000-12-29, (MA Period 25) Ending Value 1013.30
2000-12-29, (MA Period 26) Ending Value 998.30
2000-12-29, (MA Period 27) Ending Value 982.20
2000-12-29, (MA Period 28) Ending Value 975.70
2000-12-29, (MA Period 29) Ending Value 983.30
2000-12-29, (MA Period 30) Ending Value 979.80

Results:

• For periods below 18 the strategy (commissionless) loses money.


• For periods between 18 and 26 (both included) the strategy makes money.
• Above 26 money is lost agagin.

And the winning period for this strategy and the given data set is:

• 20 bars, which wins 78.00 units over 1000 $/€ (a 7.8%)

Note

The extra indicators from the plotting example have been removed and the start of
operations is only influenced by the Simple Moving Average which is being
optimized. Hence the slightly different results for period 15

Conclusion

The incremental samples have shown how to go from a barebones script to a fully
working trading system which even plots the results and can be optimized.

A lot more can be done to try to improve the chances of winning:

• Self defined Indicators

Creating an indicator is easy (and even plotting them is easy)

• Sizers

Money Management is for many the key to success

• Order Types (limit, stop, stoplimit)


• Some others

48
backtrader’s documentation Version-1.9.58.122

To ensure all the above items can be fully utilized the documentation provides an
insight into them (and other topics)

Look in the table of contents and keep on reading ... and developing.

Best of luck

Platform Concepts

This is a collection of some of the concepts of the platform. It tries to gather


information bits which can be useful in using the platform.

Before Starting

All mini-code examples assume the following imports are available:

import backtrader as bt
import backtrader.indicators as btind
import backtrader.feeds as btfeeds

Note

An alternative syntax for accessing sub-modules like indicators and feeds:

import backtrader as bt

And then:

thefeed = bt.feeds.OneOfTheFeeds(...)
theind = bt.indicators.SimpleMovingAverage(...)

Data Feeds - Passing them around

The basis of the work with the platform will be done with Strategies. And these
will get passed Data Feeds The platform end user does not need to care about
receiving them:

Data Feeds are automagically provided member variables to the strategy in the form of an array and shortcuts to
the array positions

Quick preview of a Strategy derived class declaration and running the platform:

class MyStrategy(bt.Strategy):
49
backtrader’s documentation Version-1.9.58.122

params = dict(period=20)

def __init__(self):

sma = btind.SimpleMovingAverage(self.datas[0], period=self.params.period)

...

cerebro = bt.Cerebro()

...

data = btfeeds.MyFeed(...)
cerebro.adddata(data)

...

cerebro.addstrategy(MyStrategy, period=30)

...

Notice the following:

• No *args or **kwargs are being received by the strategy’s __init__ method (they may still be used)
• A member variable self.datas exists which is array/list/iterable holding at least one item (hopefully or else
an exception will be raised)

So it is. Data Feeds get added to the platform and they will show up inside the
strategy in the sequential order in which they were added to the system.

Note

This also applies to Indicators, should the end user develop his own custom
Indicator or when having a look at the source code for some of the existing
Indicator Reference

Shortcuts for Data Feeds

The self.datas array items can be directly accessed with additional automatic
member variables:

• self.data targets self.datas[0]


• self.dataX targets self.datas[X]

50
backtrader’s documentation Version-1.9.58.122

The example then:

class MyStrategy(bt.Strategy):
params = dict(period=20)

def __init__(self):

sma = btind.SimpleMovingAverage(self.data, period=self.params.period)

...

Omitting the Data Feeds

The example above can be further simplified to:

class MyStrategy(bt.Strategy):
params = dict(period=20)

def __init__(self):

sma = btind.SimpleMovingAverage(period=self.params.period)

...

self.data has been completely removed from the invocation of SimpleMovingAverage.


If this is done, the indicator (in this case the SimpleMovingAverage) receives
the first data of the object in which is being created (the Strategy), which is
self.data (aka self.data0 or self.datas[0])

Almost everything is a Data Feed

Not only Data Feeds are data and can be passed around. Indicators and results of
Operations are also data.

In the previous example the SimpleMovingAverage was receiving self.datas[0] as


input to operate on. An example with operations and extra indicators:

class MyStrategy(bt.Strategy):
params = dict(period1=20, period2=25, period3=10, period4)

def __init__(self):

sma1 = btind.SimpleMovingAverage(self.datas[0], period=self.p.period1)


51
backtrader’s documentation Version-1.9.58.122

# This 2nd Moving Average operates using sma1 as "data"


sma2 = btind.SimpleMovingAverage(sma1, period=self.p.period2)

# New data created via arithmetic operation


something = sma2 - sma1 + self.data.close

# This 3rd Moving Average operates using something as "data"


sma3 = btind.SimpleMovingAverage(something, period=self.p.period3)

# Comparison operators work too ...


greater = sma3 > sma

# Pointless Moving Average of True/False values but valid


# This 4th Moving Average operates using greater as "data"
sma3 = btind.SimpleMovingAverage(greater, period=self.p.period4)

...

Basically everything gets transformed into an object which can be used as a data
feed once it has been operated upon.

Parameters

Mostly every other class in the platform supports the notion of parameters.

• Parameters along with default values are declared as a class attribute (tuple of tuples or dict-like object)
• Keywords args (**kwargs) are scanned for matching parameters, removing them from **kwargs if found
and assigning the value to the corresponding parameter
• And parameters can be finally used in instances of the class by accessing the member variable self.params
(shorthand: self.p)

The previous quick Strategy preview already contains a parameters example, but
for the sake of redundancy, again, focusing only on the parameters. Using tuples:

class MyStrategy(bt.Strategy):
params = (('period', 20),)

def __init__(self):
sma = btind.SimpleMovingAverage(self.data, period=self.p.period)

And using a dict:

class MyStrategy(bt.Strategy):
52
backtrader’s documentation Version-1.9.58.122

params = dict(period=20)

def __init__(self):
sma = btind.SimpleMovingAverage(self.data, period=self.p.period)

Lines

Again mostly every other object in the platform is a Lines enabled object. From a
end user point of view this means:

• It can hold one of more line series, being a line series an array of values were the values put together in a
chart they would form a line.

A good example of a line (or lineseries) is the line formed by the closing prices
of a stock. This is actually a well-known chart representation of the evolution
of prices (known as Line on Close)

Regular use of the platform is only concerned with accessing lines. The previous
mini-strategy example, lightly extended, comes in handy again:

class MyStrategy(bt.Strategy):
params = dict(period=20)

def __init__(self):

self.movav = btind.SimpleMovingAverage(self.data, period=self.p.period)

def next(self):
if self.movav.lines.sma[0] > self.data.lines.close[0]:
print('Simple Moving Average is greater than the closing price')

Two objects with lines have been exposed:

• self.data It has a lines attribute which contains a close attribute in turn


• self.movav which is a SimpleMovingAverage indicator It has a lines attribute which contains a sma
attribute in turn

Note

It should be obvious from this, that lines are named. They can also be accessed
sequentially following the declaration order, but this should only be used in
Indicator development

53
backtrader’s documentation Version-1.9.58.122

And both lines, namely close and sma can be queried for a point (index 0) to
compare the values.

Shorthand access to lines do exist:

• xxx.lines can be shortened to xxx.l


• xxx.lines.name can be shortened to xxx.lines_name
• Complex objects like Strategies and Indicators offer quick access to data’s lines
o self.data_name offers a direct access to self.data.lines.name
o Which also applies to the numbered data variables: self.data1_name -> self.data1.lines.name

Additionally the line names are directly accessible with:

• self.data.close and self.movav.sma

But the notation doesn’t make as clear as the previous one if lines are
actually being accessed.

Note

Setting/Assigning the lines with these two later notations is not supported

Lines declaration

If an Indicator is being developed, the lines which the indicator has must be
declared.

Just as with params this takes place as a class attribute this time ONLY as a
tuple. Dictionaries are not supported because they do not store things following
insertion order.

For the Simple Moving Average it would be done like this:

class SimpleMovingAverage(Indicator):
lines = ('sma',)

...

Note

The comma following the declaration is needed in tuples if you pass a single
string to the tuple or else each letter in the string would be interpreted as an
item to be added to the tuple. Possibly one of the few spots where Python’s
syntax got it wrong.

54
backtrader’s documentation Version-1.9.58.122

As seen in the previous example this declaration creates a sma line in the
Indicator that can be later accessed in the Strategy’s logic (and possibly by
other indicators to create more complex indicators)

For development is sometimes useful to access the lines in a generic non-named


manner and this is where numbered access comes in handy:

• self.lines[0] points to self.lines.sma

Had more lines been defined they would be accessed with index 1, 2, and higher.

And of course, extra shorthand versions do exist:

• self.line points to self.lines[0]


• self.lineX point to self.lines[X]
• self.line_X point to self.lines[X]

Inside objects which are receiving datas feeds the lines below these data feeds
can also be quickly accessed by number:

• self.dataY points to self.data.lines[Y]


• self.dataX_Y points to self.dataX.lines[X] which is a full shorthard version of self.datas[X].lines[Y]

Accessing lines in Data Feeds

Inside data feeds the lines can also be accessed omitting the lines. This makes
it more natural to work with thinks like close prices.

For example:

data = btfeeds.BacktraderCSVData(dataname='mydata.csv')

...

class MyStrategy(bt.Strategy):

...

def next(self):

if self.data.close[0] > 30.0:


...

55
backtrader’s documentation Version-1.9.58.122

Which seems more natural than the also valid: if self.data.lines.close[0] >
30.0:. The same doesn’t apply to Indicators with the reasoning being:

• An Indicator could have an attribute close which holds an intermediate calculation, which is later
delivered to the actual lines also named close

In the case of Data Feeds, no calculation takes place, because it is only a data
source.

Lines len

Lines have a set of points and grow dynamically during execution, therefore the
length can be measured at any time by invoking the standard Python len function.

This applies to for example:

• Data Feeds
• Strategies
• Indicators

An additional property applies to Data Feeds when the data is preloaded:

• Method buflen

The method returns the actual number of bars the Data Feed has available.

The difference between len and buflen

• len reports how many bars have been processed


• buflen reports the total number of bars which have been loaded for the Data Feed

If both return the same value, either no data has been preloaded or the
processing of bars has consumed all preloaded bars (and unless the system is
connected to a live feed, this will mean the end of processing)

Inheritance of Lines and Params

A kind of metalanguage is in place to support declaration of Params and Lines.


Every effort has been made to make it compatible with standard Python inheritance
rules.

56
backtrader’s documentation Version-1.9.58.122

Params inheritance

Inheritance should work as expected:

• Multiple inheritance is supported


• Params from base classes are inherited
• If multiple base classes define the same param the default value of the last class in the inheritance list is
used
• If the same param is redefined in a child class, the new default value takes over that of the base class

Lines Inheritance

• Multiple inheritance is supported


• Lines from all base classes are inherited. Being named lines there will only be one version of a line if the
same name has been used more than once in base classes

Indexing: 0 and -1

Lines as seen before are line series and have a set of points that conform a line
when drawn together (like when joining all closing prices together along a time
axis)

To access those points in regular code, the choice has been to use a 0 based
approach for the current get/set instant.

Strategies do only get values. Indicators do also set values.

From the previous quick strategy example where the next method was briefly seen:

def next(self):
if self.movav.lines.sma[0] > self.data.lines.close[0]:
print('Simple Moving Average is greater than the closing price')

The logic is getting the current value of the moving average and the current
closing price by applying index 0.

Note

Actually for index 0 and when applying logic/arithmetic operators the comparison
can be made directly as in:

if self.movav.lines.sma > self.data.lines.close:


...

57
backtrader’s documentation Version-1.9.58.122

See later in the document the explanation for operators.

Setting is meant to be used when developing, for example, an Indicator, because


the current output value has to be set by the indicator.

A SimpleMovingAverage can be calculated for the current get/set point as follows:

def next(self):
self.line[0] = math.fsum(self.data.get(0, size=self.p.period)) / self.p.period

Accessing previous set points has been modeled following the definition Python
makes for -1 when accessing an array/iterable

• It points to the last item of the array

The platform consider the last set item (before the current live get/set point)
to be -1.

As such comparing the current close to the previous close is a 0 vs -1 thing. In


a strategy, for example:

def next(self):
if self.data.close[0] > self.data.close[-1]:
print('Closing price is higher today')

Of course and logically, prices set before -1 will be accessed with -2, -3, ....

Slicing

backtrader doesn’t support slicing for lines objects and this is a design
decision following the [0] and [-1] indexing scheme. With regular indexable
Python objects you would do things like:

myslice = self.my_sma[0:] # slice from the beginning til the end

But remember that with the choice for 0 ... it is actually the currently
delivered value, there is nothing after it. Also:

myslice = self.my_sma[0:-1] # slice from the beginning til the end

Again ... 0 is the current value and -1 is the latest (previous) delivered value.
That’s why a slice from 0 -> -1 makes no sense in the backtrader ecosystem.

If slicing were ever to be supported, it would look like:

58
backtrader’s documentation Version-1.9.58.122

myslice = self.my_sma[:0] # slice from current point backwards to the beginning

or:

myslice = self.my_sma[-1:0] # last value and current value

or:

myslice = self.my_sma[-3:-1] # from last value backwards to the 3rd last value

Getting a slice

An array with the latest values can still be gotten. The syntax:

myslice = self.my_sma.get(ago=0, size=1) # default values shown

That would have returned an arry with 1 value (size=1) with the current moment 0
as the staring point to look backwards.

To get 10 values from the current point in time (i.e.: the last 10 values):

myslice = self.my_sma.get(size=10) # ago defaults to 0

Of course the array has the ordering you would expect. The leftmost value is the
oldest one and the rightmost value is the most current (it is a regular python
array and not a lines object)

To get the last 10 values skipping only the current point:

myslice = self.my_sma.get(ago=-1, size=10)

Lines: DELAYED indexing

The [] operator syntax is there to extract individual values during the next
logic phase. Lines objects support an additional notation to address values
through a delayed lines object during the __init__ phase.

Let’s say that the interest in the logic is to compare the previous close value
to the actual value of a simple moving average. Rather than doing it manually in
each next iteration a pre-canned lines object can be generated:

class MyStrategy(bt.Strategy):
params = dict(period=20)

59
backtrader’s documentation Version-1.9.58.122

def __init__(self):

self.movav = btind.SimpleMovingAverage(self.data, period=self.p.period)


self.cmpval = self.data.close(-1) > self.sma

def next(self):
if self.cmpval[0]:
print('Previous close is higher than the moving average')

Here the (delay) notation is being used:

• This delivers a replica of the close prices but delayed by -1.

And the comparison self.data.close(-1) > self.sma generates another lines


object which returns either 1 if the condition is True or 0 if False

Lines Coupling

The operator () can be used as shown above with delay value to provide a delayed
version of a lines object.

If the syntax is used WITHOUT providing a delay value, then a LinesCoupler lines
object is returned. This is meant to establish a coupling between indicators that
operate on datas with different timeframes.

Data Feeds with different timeframes have different lengths, and the indicators
operating on them replicate the length of the data. Example:

• A daily data feed has around 250 bars per year


• A weekly data feed has 52 bars per year

Trying to create an operation (for example) which compares 2 simple moving


averages, each operating on the datas quoted above would break. It would be
unclear how to match the 250 bars from the daily timeframe to the 52 bars of the
weekly timeframe.

The reader could imagine a date comparison taking place in the background to find
out a day - week correspondence, but:

• Indicators are just mathematical formulas and have no datetime information

They know nothing about the environment, just that if the data provides
enough values, a calculation can take place.

The () (empty call) notation comes to the rescue:


60
backtrader’s documentation Version-1.9.58.122

class MyStrategy(bt.Strategy):
params = dict(period=20)

def __init__(self):

# data0 is a daily data


sma0 = btind.SMA(self.data0, period=15) # 15 days sma
# data1 is a weekly data
sma1 = btind.SMA(self.data1, period=5) # 5 weeks sma

self.buysig = sma0 > sma1()

def next(self):
if self.buysig[0]:
print('daily sma is greater than weekly sma1')

Here the larger timeframe indicator, sma1 is coupled to the daily timeframe with
sma1(). This returns an object which is compatible with the larger numbers of
bars of sma0 and copies the values produced by sma1, effectively spreading the 52
weekly bars in 250 daily bars

Operators, using natural constructs

In order to achieve the “ease of use” goal the platform allows (within the
constraints of Python) the use of operators. And to further enhance this goal ,
the use of operators has been broken in two stages.

Stage 1 - Operators Create Objects

An example has already been seen even if not explicitly meant for this. During
the initialization phase (__init__ method) of objects like Indicators and
Strategies, operators create objects that can be operated upon, assigned or kept
as reference for later using during the evaluation phase of the Strategy’s
logic.

Once again a potential implementation of a SimpleMovingAverage, further broken


down into steps.

The code inside the SimpleMovingAverage indicator __init__ could look like:

def __init__(self):
# Sum N period values - datasum is now a *Lines* object
# that when queried with the operator [] and index 0
61
backtrader’s documentation Version-1.9.58.122

# returns the current sum

datasum = btind.SumN(self.data, period=self.params.period)

# datasum (being *Lines* object although single line) can be


# naturally divided by an int/float as in this case. It could
# actually be divided by anothr *Lines* object.
# The operation returns an object assigned to "av" which again
# returns the current average at the current instant in time
# when queried with [0]

av = datasum / self.params.period

# The av *Lines* object can be naturally assigned to the named


# line this indicator delivers. Other objects using this
# indicator will have direct access to the calculation

self.line.sma = av

A more complete use case is shown during the initialization of a Strategy:

class MyStrategy(bt.Strategy):

def __init__(self):

sma = btind.SimpleMovinAverage(self.data, period=20)

close_over_sma = self.data.close > sma


sma_dist_to_high = self.data.high - sma

sma_dist_small = sma_dist_to_high < 3.5

# Unfortunately "and" cannot be overridden in Python being


# a language construct and not an operator and thus a
# function has to be provided by the platform to emulate it

sell_sig = bt.And(close_over_sma, sma_dist_small)

After the above operations have taken place, sell_sig is a Lines object which can
be later used in the logic of the Strategy, indicating if the conditions are met
or not.

62
backtrader’s documentation Version-1.9.58.122

Stage 2 - Operators true to nature

Let’s first remember that a strategy has a next method which is called for every
bar the system processes. This is where operators are actually in the stage 2
mode. Building on the previous example:

class MyStrategy(bt.Strategy):

def __init__(self):

self.sma = sma = btind.SimpleMovinAverage(self.data, period=20)

close_over_sma = self.data.close > sma


self.sma_dist_to_high = self.data.high - sma

sma_dist_small = sma_dist_to_high < 3.5

# Unfortunately "and" cannot be overridden in Python being


# a language construct and not an operator and thus a
# function has to be provided by the platform to emulate it

self.sell_sig = bt.And(close_over_sma, sma_dist_small)

def next(self):

# Although this does not seem like an "operator" it actually is


# in the sense that the object is being tested for a True/False
# response

if self.sma > 30.0:


print('sma is greater than 30.0')

if self.sma > self.data.close:


print('sma is above the close price')

if self.sell_sig: # if sell_sig == True: would also be valid


print('sell sig is True')
else:
print('sell sig is False')

if self.sma_dist_to_high > 5.0:


print('distance from sma to hig is greater than 5.0')

63
backtrader’s documentation Version-1.9.58.122

Not a very useful strategy, just an example. During Stage 2 operators return the
expected values (boolean if testing for truth and floats if comparing them to
floats) and also arithmetic operations do.

Note

Notice that comparisons are actually not using the [] operator. This is meant to
further simplify things.

if self.sma > 30.0: ... compares self.sma[0] to 30.0 (1st line and current value)

if self.sma > self.data.close: ... compares self.sma[0] to self.data.close[0]

Some non-overriden operators/functions

Python will not allow overriding everything and thus some functions are provided
to cope with the cases.

Note

Only meant to be used during Stage 1, to create objects which later provide
values.

Operators:

• and -> And


• or -> Or

Logic Control:

• if -> If

Functions:

• any -> Any


• all -> All
• cmp -> Cmp
• max -> Max
• min -> Min
• sum -> Sum
• reduce -> Reduce

Sum actually uses math.fsum as the underlying operation because the


platform works with floating point numbers and applying a regular sum may
have an impact on precision.
64
backtrader’s documentation Version-1.9.58.122

• reduce -> Reduce

These utility operators/functions operate on iterables. The elements in the


iterables can be regular Python numeric types (ints, floats, ...) and also
objects with Lines.

An example generating a very dumb buy signal:

class MyStrategy(bt.Strategy):

def __init__(self):

sma1 = btind.SMA(self.data.close, period=15)


self.buysig = bt.And(sma1 > self.data.close, sma1 > self.data.high)

def next(self):
if self.buysig[0]:
pass # do something here

It is obvious that if the sma1 is higher than the high, it must be higher than
the close. But the point is illustrating the use of bt.And.

Using bt.If:

class MyStrategy(bt.Strategy):

def __init__(self):

sma1 = btind.SMA(self.data.close, period=15)


high_or_low = bt.If(sma1 > self.data.close, self.data.low,
self.data.high)
sma2 = btind.SMA(high_or_low, period=15)

Breakdown:

• Generate a SMA on data.close of period=15


• And then
o bt.If the value of the sma is larger than close, return low, else
return high

Remember that no actual value is being returned when bt.If is being


invoked. It returns a Lines object which is just like a
SimpleMovingAverage.

The values will be calculated later when the system runs

65
backtrader’s documentation Version-1.9.58.122

• The generated bt.If Lines object is then fed to a 2nd SMA which will
sometimes use the low prices and sometimes the high prices for the
calculation

Those functions take also numeric values. The same example with a modification:

class MyStrategy(bt.Strategy):

def __init__(self):

sma1 = btind.SMA(self.data.close, period=15)


high_or_30 = bt.If(sma1 > self.data.close, 30.0, self.data.high)
sma2 = btind.SMA(high_or_low, period=15)

Now the 2nd moving average uses either 30.0 or the high prices to perform the
calculation, depending on the logic status of sma vs close

Note

The value 30 is transformed internally into a pseudo-iterable which always


returns 30

Operating the platform

Line Iterators

To engage into operations, the plaftorm uses the notion of line iterators. They
have been loosely modeled after Python’s iterators but have actually nothing to
do with them.

Strategies and Indicators are line iterators.

The line iterator concept tries to describe the following:

• A Line Iterator kicks slave line iterators telling them to iterate


• A Line Iterator then iterates over its own declared named lines setting values

The key to iteration, just like with regular Python iterators, is:

• The next method

It will be called for each iteration. The datas array which the line
iterator has and serve as basis for logic/calculations will have already
been moved to the next index by the platform (barring data replay)
66
backtrader’s documentation Version-1.9.58.122

Called when the minimum period for the line iterator has been met. A bit
more on this below.

But because they are not regular iterators, two additional methods exist:

• prenext

Called before the minimum period for the line iterator` has been met.

• nextstart

Called exactly ONCE when the minimum period for the line iterator` has been
met.

The default behavior is to forward the call to next, but can of course be
overriden if needed.

Extra methods for Indicators

To speed up operations, Indicators support a batch operation mode which has been
termed as runonce. It is not strictly needed (a next method suffices) but it
greatly reduces time.

The runonce methods rules void the get/set point with index 0 and relies on
direct access to the underlying arrays holding the data and being passed the
right indices for each state.

The defined methods follow the naming of the next family:

• once(self, start, end)

Called when the minimum period has been met. The internal array must be
processed between start and end which are zero based from the start of the
internal array

• preonce(self, start, end)

Called before the minimum period has been met.

• oncestart(self, start, end)

Called exactly ONCE when the minimum period has been met.

The default behavior is to forward the call to once, but can of course be
overriden if needed.
67
backtrader’s documentation Version-1.9.58.122

Minimum Period

A picture is worth a thousand words and in this case possibly an example too. A
SimpleMovingAverage is capable of explaining it:

class SimpleMovingAverage(Indicator):
lines = ('sma',)
params = dict(period=20)

def __init__(self):
... # Not relevant for the explanation

def prenext(self):
print('prenext:: current period:', len(self))

def nextstart(self):
print('nextstart:: current period:', len(self))
# emulate default behavior ... call next
self.next()

def next(self):
print('next:: current period:', len(self))

And the instantiation could look like:

sma = btind.SimpleMovingAverage(self.data, period=25)

Briefly explained:

• Assuming the data passed to the moving average is a standard data feed its default period is 1 that is: the
data feed produces a bar with no initial delay.
• Then the “period=25” instantiated moving average would have its methods called as follows:
o prenext 24 times
o nextstart 1 time (in turn calling next)
o next n additional times until the data feed has been exhausted

Let’s go for the killer indicator: a SimpleMovingAverage over another


SimpleMovingAverage. The instantiation could look like:

sma1 = btind.SimpleMovingAverage(self.data, period=25)

sma2 = btind.SimpleMovingAverage(sma1, period=20)

What now goes on:


68
backtrader’s documentation Version-1.9.58.122

• The same as above for sma1


• sma2 is receiving a data feed which has a minimum period of 25 which is our sma1 and therefore
• The sma2 methods are called as indicated:
o prenext the first 25 + 18 times for a total of 43 times
25 times to let sma1 produce its 1st sensible value
18 times to accumulate extra sma1 values
For a total of 19 values (1 after 25 calls and then 18 more)
o nextstart then 1 time (in turn calling next)
o next the n additional times until the data feed has been exhausted

The platform is calling next when the system has already processed 44 bars.

The minimum period has been automatically adjusted to the incoming data.

Strategies and Indicators adhere to this behavior:

• Only when the automatically calculated minimum period has been reached will next be called (barring the
initial hook call to nextstart)

Note

The same rules apply to preonce, oncestart and once for the runonce batch
operation mode

Note

The minimum period behavior can be manipulated although it’s not recommended.
Should it be wished used the setminperiod(minperiod) method in either Strategies
or Indicators

Up and Running

Getting up and running involves at least 3 Lines objects:

• A Data feed
• A Strategy (actually a class derived from Strategy)
• A Cerebro (brain in Spanish)

Data Feeds

These objects, obviously, provide the data which will be backtested by applying
calculations (direct and/or with Indicators)

The platform provides several data feeds:


69
backtrader’s documentation Version-1.9.58.122

• Several CSV Format and a Generic CSV reader


• Yahoo online fetcher
• Support for receiving Pandas DataFrames and blaze objects
• Live Data Feeds with Interacive Brokers, Visual Chart and Oanda

The platform makes no assumption about the content of the data feed such as
timeframe and compression. Those values, together with a name, can be supplied
for informational purposes and advance operations like Data Feed Resampling
(turning a for example a 5 minute Data Feed into a Daily Data Feed)

Example of setting up a Yahoo Finance Data Feed:

import backtrader as bt
import backtrader.feeds as btfeeds

...

datapath = 'path/to/your/yahoo/data.csv'

data = btfeeds.YahooFinanceCSVData(
dataname=datapath,
reversed=True)

The optional reversed parameter for Yahoo is shown, because the CSV files
directly downloaded from Yahoo start with the latest date, rather than with the
oldest.

If your data spans a large time range, the actual loaded data can be limited as
follows:

data = btfeeds.YahooFinanceCSVData(
dataname=datapath,
reversed=True
fromdate=datetime.datetime(2014, 1, 1),
todate=datetime.datetime(2014, 12, 31))

Both the fromdate and the todate will be included if present in the data feed.

As already mentioned timeframe, compression and name can be added:

data = btfeeds.YahooFinanceCSVData(
dataname=datapath,
reversed=True
fromdate=datetime.datetime(2014, 1, 1),
todate=datetime.datetime(2014, 12, 31)
70
backtrader’s documentation Version-1.9.58.122

timeframe=bt.TimeFrame.Days,
compression=1,
name='Yahoo'
)

If the data is plotted, those values will be used.

A Strategy (derived) class

Note

Before going on and for a more simplified approach, please check the Signals
section of the documentation if subclassing a strategy is not wished.

The goal of anyone using the platform is backtesting the data and this is done
inside a Strategy (derived class).

There are 2 methods which at least need customization:

• __init__
• next

During initialization indicators on data and other calculations are created


prepared to later apply the logic.

The next method is later called to apply the logic for each and every bar of the
data.

Note

If data feeds of different timeframes (and thus different bar counts) are passed
the next method will be called for the master data (the 1st one passed to
cerebro, see below) which must be the the data with the smaller timeframe

Note

If the Data Replay functionality is used, the next method will be called several
time for the same bar as the development of the bar is replayed.

A basic Strategy derived class:

class MyStrategy(bt.Strategy):

def __init__(self):

71
backtrader’s documentation Version-1.9.58.122

self.sma = btind.SimpleMovingAverage(self.data, period=20)

def next(self):

if self.sma > self.data.close:


self.buy()

elif self.sma < self.data.close:


self.sell()

Strategies have other methods (or hook points) which can be overriden:

class MyStrategy(bt.Strategy):

def __init__(self):

self.sma = btind.SimpleMovingAverage(self.data, period=20)

def next(self):

if self.sma > self.data.close:


submitted_order = self.buy()

elif self.sma < self.data.close:


submitted_order = self.sell()

def start(self):
print('Backtesting is about to start')

def stop(self):
print('Backtesting is finished')

def notify_order(self, order):


print('An order new/changed/executed/canceled has been received')

The start and stop methods should be self-explanatory. As expected and following
the text in the print function, the notify_order method will be called when the
strategy needs a notification. Use case:

• A buy or sell is requested (as seen in next)

buy/sell will return an order which is submitted to the broker. Keeping a


reference to this submitted order is up to the caller.

72
backtrader’s documentation Version-1.9.58.122

It can for example be used to ensure that no new orders are submitted if an
order is still pending.

• If the order is Accepted/Executed/Canceled/Changed the broker will notify


the status change (and for example execution size) back to the strategy via
the notify method

The QuickStart guide has a complete and functional example of order management in
the notify_order method.

More can be done with other Strategy classes:

• buy / sell / close

Use the underlying broker and sizer to send the broker a buy/sell order

The same could be done by manually creating an Order and passing it over to
the broker. But the platform is about making it easy for those using it.

close will get the current market position and close it immediately.

• getposition (or the property “position”)

Returns the current market position

• setsizer/getsizer (or the property “sizer”)

These allow setting/getting the underlying stake Sizer. The same logic can
be checked against Sizers which provide different stakes for the same
situation (fixed size, proportional to capital, exponential)

There is plenty of literature but Van K. Tharp has excellent books on the
subject.

A Strategy is a Lines object and these support parameters, which are collected
using the standard Python kwargs argument:

class MyStrategy(bt.Strategy):

params = (('period', 20),)

def __init__(self):

self.sma = btind.SimpleMovingAverage(self.data,
period=self.params.period)

73
backtrader’s documentation Version-1.9.58.122

...
...

Notice how the SimpleMovingAverage is no longer instantiated with a fixed value


of 20, but rather with the parameter “period” which has been defined for the
strategy.

A Cerebro

Once Data Feeds are available and the Strategy has been defined, a Cerebro
instance is what brings everything together and execute the actions.
Instantiating one is easy:

cerebro = bt.Cerebro()

Defaults are taking care of if nothing special is wished.

• A default broker is created


• No commission for the operations
• Data Feeds will be preloaded
• The default execution mode will be runonce (batch operation) which is the
faster

All indicators must support the runonce mode for full speed. The ones
included in the platform do.

Custom indicators do not need to implement the runonce functionality.


Cerebro will simulate it, which means those non-runonce compatible
indicators will run slower. But still most of the system will run in batch
mode.

Since a Data feed is already available and a Strategy too (created earlier) the
standard way to put it all together and get it up and running is:

cerebro.adddata(data)
cerebro.addstrategy(MyStrategy, period=25)
cerebro.run()

Notice the following:

• The Data Feed “instance” is added


• The MyStrategy “class” is added along with parameters (kwargs) that will
be passed to it.

74
backtrader’s documentation Version-1.9.58.122

The instantiation of MyStrategy will be done by cerebro in the background


and any kwargs in “addstrategy” will be passed to it

The user may add as many Strategies and Data Feeds as wished. How Strategies
communicate with each other to achieve coordination (if wished be) is not
enforced/restricted by the platform.

Of course a Cerebro offers additional possibilities:

• Decide about preloading and operation mode:


• cerebro = bt.Cerebro(runonce=True, preload=True)

There is a constraint here: runonce needs preloading (if not, a batch


operation cannot be run) Of course preloading Data Feeds does not enforce
runonce

• setbroker / getbroker (and the broker property)

A custom broker can be set if wished. The actual broker instance can also
be accesed

• Plotting. In a regular case as easy as:


• cerebro.run()
• cerebro.plot()

plot takes some arguments for the customization

o numfigs=1

If the plot is too dense it may be broken down into several plots

o plotter=None

A customer plotter instance can be passed and cerebro will not


instantiate a default one

o **kwargs - standard keyword arguments

Which will get passed to the plotter.

Please see the plotting section for more information.

• Optimization of strategies.

75
backtrader’s documentation Version-1.9.58.122

As mentioned above, Cerebro gets a Strategy derived class (not an instance)


and the keyword arguments that will be passed to it upon instantiation,
which will happen when “run” is called.

This is so to enable optimization. The same Strategy class will be


instantiated as many times as needed with new parameters. If an instance
had been passed to cerebro ... this would not be possible.

Optimization is requested as follows:

cerebro.optstrategy(MyStrategy, period=xrange(10, 20))

The method optstrategy has the same signature as addstrategy but does extra
housekeeping to ensure optimization runs as expected. A strategy could be
expecting a range as a normal parameter for a strategy and addstrategy will
make no assumptions about the passed parameter.

On the other hand, optstrategy will understand that an iterable is a set of


values that has to be passed in sequence to each instantiation of the
Strategy class.

Notice that instead of a single value a range of values is passed. In this


simple case 10 values 10 -> 19 (20 is the upper limit) will be tried for
this strategy.

If a more complex strategy is developed with extra parameters they can all
be passed to optstrategy. Parameters which must not undergo optimization
can be passed directly without the end user having to create a dummy
iterable of just one value. Example:

cerebro.optstrategy(MyStrategy, period=xrange(10, 20), factor=3.5)

The optstrategy method sees factor and creates (a needed) dummy iterable in
the background for factor which has a single element (in the example 3.5)

Note

Interactive Python shells and some types of frozen executables under


Windows have problems with the Python multiprocessing module

Please read the Python documentation about multiprocessing.

76
backtrader’s documentation Version-1.9.58.122

Exceptions

One of the design goals was to quit as early as possible and let the users have
full transparency of what was happening with errors. With the goal to force
oneself to have code that would break on exceptions and forced revisiting the
affected part.

But the time has come and some exceptions may slowly get added to the platform.

Hierarchy

The base class for all exceptions is BacktraderError (which is a direct subclass
of Exception)

Location

1. Inside the module errors which can be reached as in for example:


2. import backtrader as bt
3.
4. class Strategy(bt.Strategy):
5.
6. def __init__(self):
7. if something_goes_wrong():
8. raise bt.errors.StrategySkipError
9. Directly from backtrader as in:
10.import backtrader as bt
11.
12.class Strategy(bt.Strategy):
13.
14. def __init__(self):
15. if something_goes_wrong():
16. raise bt.StrategySkipError

Exceptions

StrategySkipError

Requests the platform to skip this strategy for backtesting. To be raised during
the initialization (__init__) phase of the instance

77
backtrader’s documentation Version-1.9.58.122

Cerebro

This class is the cornerstone of backtrader because it serves as a central point


for:

1. Gathering all inputs (Data Feeds), actors (Stratgegies), spectators (Observers), critics (Analyzers) and
documenters (Writers) ensuring the show still goes on at any moment.
2. Execute the backtesting/or live data feeding/trading
3. Returning the results
4. Giving access to the plotting facilities

Gathering input

1. Start by creating a cerebro:


2. cerebro = bt.Cerebro(**kwargs)

Some **kwargs to control execution are supported, see the reference (the
same arguments can be applied later to the run method)

3. Add Data feeds

The most usual pattern is cerebro.adddata(data), where data is a data feed


already instantiated. Example:

data = bt.BacktraderCSVData(dataname='mypath.days',
timeframe=bt.TimeFrame.Days)
cerebro.adddata(data)

Resampling and Replaying a data is possible and follows the same pattern:

data = bt.BacktraderCSVData(dataname='mypath.min',
timeframe=bt.TimeFrame.Minutes)
cerebro.resampledata(data, timeframe=bt.TimeFrame.Days)

or:

data = bt.BacktraderCSVData(dataname='mypath.min',
timeframe=bt.TimeFrame.Minutes)
cerebro.replaydatadata(data, timeframe=bt.TimeFrame.Days)

The system can accept any number of data feeds, including mixing regular
data with resampled and/or replayed data. Of course some of this
combinationns will for sure make no sense and a restriction apply in order

78
backtrader’s documentation Version-1.9.58.122

to be able to combine datas: time aligment. See the Data - Multiple


Timeframes, Data Resampling - Resampling` and Data - Replay sections.

4. Add Strategies

Unlike the datas feeds which are already an instance of a class, cerebro
takes directly the Strategy class and the arguments to pass to it. The
rationale behind: in an optimization scenario the class will be
instantiated several times and passed different arguments

Even if no optimization is run, the pattern still applies:

cerebro.addstrategy(MyStrategy, myparam1=value1, myparam2=value2)

When optimizing the parameters have to be added as iterables. See the


Optimization section for a detailed explanation. The basic pattern:

cerebro.optstrategy(MyStrategy, myparam1=range(10, 20))

Which will run MyStrategy 10 times with myparam1 taking values from 10 to
19 (remember ranges in Python are half-open and 20 will not be reached)

5. Other elements

There are some other elements which can be added to enhance the backtesting
experience. See the appropriate sections for it. The methods are:

o addwriter
o addanalyzer
o addobserver (or addobservermulti)
6. Changing the broker

Cerebro will use the default broker in backtrader, but this can be
overriden:

broker = MyBroker()
cerebro.broker = broker # property using getbroker/setbroker methods

7. Receive notifications

If data feeds and/or brokers send notifications (or a store provider which
creates them) they will be received through the Cerebro.notify_store
method. There are three (3) ways to work with these notifications

79
backtrader’s documentation Version-1.9.58.122

o Add a callback to a cerebro instance via the


addnotifycallback(callback) call. The callback has to support this
signature:
o callback(msg, *args, **kwargs)

The actual msg, *args and **kwargs received are implementation


defined (depend entirely on the data/broker/store) but in general one
should expect them to be printable to allow for reception and
experimentation.

o Override the notify_store method in the Strategy subclass which is


added to a cerebro instance.

The signature: notify_store(self, msg, *args, **kwargs)

o Subclass Cerebro and override notify_store (same signature as in the


Strategy)

This should be the least preferred method

Execute the backtesting

There is a single method to do it, but it supports several options (which can be
also specified when instantiating) to decide how to run:

result = cerebro.run(**kwargs)

See the reerence below to understand which arguments are available.

Standard Observers

cerebro (unless otherwise specified) automatically instantiates three standard


observers

• A Broker observer which keeps track of cash and value (portfolio)


• A Trades observer which should show how effective each trade has been
• A Buy/Sell observer which should document when operations are executed

Should a cleaner plotting be wished just disable them with stdstats=False

80
backtrader’s documentation Version-1.9.58.122

Returning the results

cerebro returns the instances of the strategies it created during backtesting.


This allows to analyze what they did, because all elements in the strategies are
accessible:

result = cerebro.run(**kwargs)

The format of result returned by run will vary depending on whether optimization
is used (a strategy was added with optstrategy):

• All strategies added with addstrategy

result will be a list of the instances run during the backtesting

• 1 or more strategies were added with optstrategy

result will be a list of list. Each internal list will contain the
strategies after each optimization run

Note

The default behavior for optimization was changed to only return the analyzers
present in the system, to make message passing across computer cores lighter.

If the complete set of strategies is wished as return value, set the parameter
optreturn to False

Giving access to the plotting facilities

As an extra an if matplotlib is installed, the strategies can be plotted. With


the usual pattern being:

cerebro.plot()

See below for the reference and the section Plotting

Backtesting logic

Brief outline of the flow of things:

1. Deliver any store notifications


2. Ask data feeds to deliver the next set of ticks/bars
81
backtrader’s documentation Version-1.9.58.122

Changed in version 1.9.0.99: New Behavior

Data Feeds are synchronized by peeking at the datetime which is going to be


provided next by available data feeds. Feeds which have not traded in the
new period still provide the old data points, whilst data feeds which have
new data available offer this one (along with the calculation of
indicators)

Old Behavior (retained when using oldsync=True with Cerebro)

The 1st data inserted into the system is the datamaster and the system will
wait for it to deliver a tick

The other data feeds are, more or less, slaves to the datamaster and:

o If the next tick to deliver is newer (datetime-wise) than the one delivered by the datamaster it
will not be delivered
o May return without delivering a new tick for a number of reasons

The logic was designed to easily synchronize multiple data feeds and data
feeds with different timeframes

3. Notify the strategy about queued broker notifications of orders, trades and
cash/value
4. Tell the broker to accept queued orders and execute the pending orders with
the new data
5. Call the strategies’ next method to let the strategy evaluate the new data
(and maybe issue orders which are queued in the broker)

Depending on the stage it may be prenext or nextstart before the minimum


period requirements of the strategy/indicators are met

Internally the strategies will also kick the observers, indicators,


analyzers and other active elements

6. Tell any writers to write the data to its target

Important to take into account:

Note

In step 1 above when the data feeds deliver the new set of bars, those bars are
closed. This means the data has already happened.

As such, orders issued by the strategy in step 4 cannot be executed with the data
from step 1.
82
backtrader’s documentation Version-1.9.58.122

This means that orders will be executed with the concept of x + 1. Where x is the
bar moment at which the order was executed and x + 1 the next one, which is the
earliest moment in time for a possible order execution

Reference

class backtrader.Cerebro

Params:

• preload (default: True)

Whether to preload the different data feeds passed to cerebro for the
Strategies

• runonce (default: True)

Run Indicators in vectorized mode to speed up the entire system.


Strategies and Observers will always be run on an event based basis

• live (default: False)

If no data has reported itself as live (via the data’s islive method
but the end user still want to run in live mode, this parameter can
be set to true

This will simultaneously deactivate preload and runonce. It will have


no effect on memory saving schemes.

Run Indicators in vectorized mode to speed up the entire system.


Strategies and Observers will always be run on an event based basis

• maxcpus (default: None -> all available cores)

How many cores to use simultaneously for optimization

• stdstats (default: True)

If True default Observers will be added: Broker (Cash and Value),


Trades and BuySell

• oldbuysell (default: False)

If stdstats is True and observers are getting automatically added,


this switch controls the main behavior of the BuySell observer
83
backtrader’s documentation Version-1.9.58.122

o False: use the modern behavior in which the buy / sell signals are plotted below / above
the low / high prices respectively to avoid cluttering the plot
o True: use the deprecated behavior in which the buy / sell signals are plotted where the
average price of the order executions for the given moment in time is. This will of course
be on top of an OHLC bar or on a Line on Cloe bar, difficulting the recognition of the plot.
• oldtrades (default: False)

If stdstats is True and observers are getting automatically added,


this switch controls the main behavior of the Trades observer

o False: use the modern behavior in which trades for all datas are plotted with different
markers
o True: use the old Trades observer which plots the trades with the same markers,
differentiating only if they are positive or negative
• exactbars (default: False)

With the default value each and every value stored in a line is kept
in memory

Possible values:

o True or 1: all “lines” objects reduce memory usage to the


automatically calculated minimum period.

If a Simple Moving Average has a period of 30, the underlying data


will have always a running buffer of 30 bars to allow the calculation
of the Simple Moving Average

This setting will deactivate preload and runonce


Using this setting also deactivates plotting
o -1: datafreeds and indicators/operations at strategy level will keep
all data in memory.

For example: a RSI internally uses the indicator UpDay to make


calculations. This subindicator will not keep all data in memory

This allows to keep plotting and preloading active.


runonce will be deactivated
o -2: data feeds and indicators kept as attributes of the strategy will
keep all points in memory.

For example: a RSI internally uses the indicator UpDay to make


calculations. This subindicator will not keep all data in memory

84
backtrader’s documentation Version-1.9.58.122

If in the __init__ something like a = self.data.close -


self.data.high is defined, then a will not keep all data in memory

This allows to keep plotting and preloading active.


runonce will be deactivated
• objcache (default: False)

Experimental option to implement a cache of lines objects and reduce


the amount of them. Example from UltimateOscillator:

bp = self.data.close - TrueLow(self.data)
tr = TrueRange(self.data) # -> creates another TrueLow(self.data)

If this is True the 2nd TrueLow(self.data) inside TrueRange matches


the signature of the one in the bp calculation. It will be reused.

Corner cases may happen in which this drives a line object off its
minimum period and breaks things and it is therefore disabled.

• writer (default: False)

If set to True a default WriterFile will be created which will print


to stdout. It will be added to the strategy (in addition to any other
writers added by the user code)

• tradehistory (default: False)

If set to True, it will activate update event logging in each trade


for all strategies. This can also be accomplished on a per strategy
basis with the strategy method set_tradehistory

• optdatas (default: True)

If True and optimizing (and the system can preload and use runonce,
data preloading will be done only once in the main process to save
time and resources.

The tests show an approximate 20% speed-up moving from a sample


execution in 83 seconds to 66

• optreturn (default: True)

If True the optimization results will not be full Strategy objects


(and all datas, indicators, observers ...) but and object with the
following attributes (same as in Strategy):

85
backtrader’s documentation Version-1.9.58.122

o params (or p) the strategy had for the execution


o analyzers the strategy has executed

In most occassions, only the analyzers and with which params are the
things needed to evaluate a the performance of a strategy. If
detailed analysis of the generated values for (for example)
indicators is needed, turn this off

The tests show a 13% - 15% improvement in execution time. Combined


with optdatas the total gain increases to a total speed-up of 32% in
an optimization run.

• oldsync (default: False)

Starting with release 1.9.0.99 the synchronization of multiple datas


(same or different timeframes) has been changed to allow datas of
different lengths.

If the old behavior with data0 as the master of the system is wished,
set this parameter to true

• tz (default: None)

Adds a global timezone for strategies. The argument tz can be

o None: in this case the datetime displayed by strategies will be in UTC, which has been
always the standard behavior
o pytz instance. It will be used as such to convert UTC times to the chosen timezone
o string. Instantiating a pytz instance will be attempted.
o integer. Use, for the strategy, the same timezone as the corresponding data in the
self.datas iterable (0 would use the timezone from data0)
• cheat_on_open (default: False)

The next_open method of strategies will be called. This happens


before next and before the broker has had a chance to evaluate
orders. The indicators have not yet been recalculated. This allows
issuing an orde which takes into account the indicators of the
previous day but uses the open price for stake calculations

For cheat_on_open order execution, it is also necessary to make the


call cerebro.broker.set_coo(True) or instantite a broker with
BackBroker(coo=True) (where coo stands for cheat-on-open) or set the
broker_coo parameter to True. Cerebro will do it automatically unless
disabled below.

86
backtrader’s documentation Version-1.9.58.122

• broker_coo (default: True)

This will automatically invoke the set_coo method of the broker with
True to activate cheat_on_open execution. Will only do it if
cheat_on_open is also True

• quicknotify (default: False)

Broker notifications are delivered right before the delivery of the


next prices. For backtesting this has no implications, but with live
brokers a notification can take place long before the bar is
delivered. When set to True notifications will be delivered as soon
as possible (see qcheck in live feeds)

Set to False for compatibility. May be changed to True

addstorecb(callback)

Adds a callback to get messages which would be handled by the notify_store


method

The signature of the callback must support the following:

• callback(msg, *args, **kwargs)

The actual msg, *args and **kwargs received are implementation defined
(depend entirely on the data/broker/store) but in general one should expect
them to be printable to allow for reception and experimentation.

notify_store(msg, *args, **kwargs)

Receive store notifications in cerebro

This method can be overridden in Cerebro subclasses

The actual msg, *args and **kwargs received are implementation defined
(depend entirely on the data/broker/store) but in general one should expect
them to be printable to allow for reception and experimentation.

adddatacb(callback)

Adds a callback to get messages which would be handled by the notify_data


method

The signature of the callback must support the following:

87
backtrader’s documentation Version-1.9.58.122

• callback(data, status, *args, **kwargs)

The actual *args and **kwargs received are implementation defined (depend
entirely on the data/broker/store) but in general one should expect them to
be printable to allow for reception and experimentation.

notify_data(data, status, *args, **kwargs)

Receive data notifications in cerebro

This method can be overridden in Cerebro subclasses

The actual *args and **kwargs received are implementation defined (depend
entirely on the data/broker/store) but in general one should expect them to
be printable to allow for reception and experimentation.

adddata(data, name=None)

Adds a Data Feed instance to the mix.

If name is not None it will be put into data._name which is meant for
decoration/plotting purposes.

resampledata(dataname, name=None, **kwargs)

Adds a Data Feed to be resample by the system

If name is not None it will be put into data._name which is meant for
decoration/plotting purposes.

Any other kwargs like timeframe, compression, todate which are supported by
the resample filter will be passed transparently

replaydata(dataname, name=None, **kwargs)

Adds a Data Feed to be replayed by the system

If name is not None it will be put into data._name which is meant for
decoration/plotting purposes.

Any other kwargs like timeframe, compression, todate which are supported by
the replay filter will be passed transparently

chaindata(*args, **kwargs)

Chains several data feeds into one


88
backtrader’s documentation Version-1.9.58.122

If name is passed as named argument and is not None it will be put into
data._name which is meant for decoration/plotting purposes.

If None, then the name of the 1st data will be used

rolloverdata(*args, **kwargs)

Chains several data feeds into one

If name is passed as named argument and is not None it will be put into
data._name which is meant for decoration/plotting purposes.

If None, then the name of the 1st data will be used

Any other kwargs will be passed to the RollOver class

addstrategy(strategy, *args, **kwargs)

Adds a Strategy class to the mix for a single pass run. Instantiation will
happen during run time.

args and kwargs will be passed to the strategy as they are during
instantiation.

Returns the index with which addition of other objects (like sizers) can be
referenced

optstrategy(strategy, *args, **kwargs)

Adds a Strategy class to the mix for optimization. Instantiation will


happen during run time.

args and kwargs MUST BE iterables which hold the values to check.

Example: if a Strategy accepts a parameter period, for optimization


purposes the call to optstrategy looks like:

• cerebro.optstrategy(MyStrategy, period=(15, 25))

This will execute an optimization for values 15 and 25. Whereas

• cerebro.optstrategy(MyStrategy, period=range(15, 25))

will execute MyStrategy with period values 15 -> 25 (25 not included,
because ranges are semi-open in Python)

89
backtrader’s documentation Version-1.9.58.122

If a parameter is passed but shall not be optimized the call looks like:

• cerebro.optstrategy(MyStrategy, period=(15,))

Notice that period is still passed as an iterable ... of just 1 element

backtrader will anyhow try to identify situations like:

• cerebro.optstrategy(MyStrategy, period=15)

and will create an internal pseudo-iterable if possible

optcallback(cb)

Adds a callback to the list of callbacks that will be called with the
optimizations when each of the strategies has been run

The signature: cb(strategy)

addindicator(indcls, *args, **kwargs)

Adds an Indicator class to the mix. Instantiation will be done at run time
in the passed strategies

addobserver(obscls, *args, **kwargs)

Adds an Observer class to the mix. Instantiation will be done at run time

addobservermulti(obscls, *args, **kwargs)

Adds an Observer class to the mix. Instantiation will be done at run time

It will be added once per “data” in the system. A use case is a buy/sell
observer which observes individual datas.

A counter-example is the CashValue, which observes system-wide values

addanalyzer(ancls, *args, **kwargs)

Adds an Analyzer class to the mix. Instantiation will be done at run time

addwriter(wrtcls, *args, **kwargs)

Adds an Writer class to the mix. Instantiation will be done at run time in
cerebro

90
backtrader’s documentation Version-1.9.58.122

run(**kwargs)

The core method to perform backtesting. Any kwargs passed to it will affect
the value of the standard parameters Cerebro was instantiated with.

If cerebro has not datas the method will immediately bail out.

It has different return values:

• For No Optimization: a list contanining instances of the Strategy classes added with addstrategy
• For Optimization: a list of lists which contain instances of the Strategy classes added with
addstrategy

runstop()

If invoked from inside a strategy or anywhere else, including other threads


the execution will stop as soon as possible.

setbroker(broker)

Sets a specific broker instance for this strategy, replacing the one
inherited from cerebro.

getbroker()

Returns the broker instance.

This is also available as a property by the name broker

plot(plotter=None, numfigs=1, iplot=True, start=None, end=None, savefig=False,


figfilename=u'backtrader-plot-{j}-{i}.png', width=16, height=9, dpi=300, tight=True, use=None, **kwargs)

Plots the strategies inside cerebro

If plotter is None a default Plot instance is created and kwargs are passed
to it during instantiation.

numfigs split the plot in the indicated number of charts reducing chart
density if wished

iplot: if True and running in a notebook the charts will be displayed


inline

use: set it to the name of the desired matplotlib backend. It will take
precedence over iplot

91
backtrader’s documentation Version-1.9.58.122

start: An index to the datetime line array of the strategy or a


datetime.date, datetime.datetime instance indicating the start of the plot

end: An index to the datetime line array of the strategy or a


datetime.date, datetime.datetime instance indicating the end of the plot

savefig: set to True to save to a file rather than plot

figfilename: name of the file. Use {j} in the name for the strategy index
to which the figure corresponds and use {i} to insert figure number if
multiple figures are being used per strategy plot

width: in inches of the saved figure

height: in inches of the saved figure

dpi: quality in dots per inches of the saved figure

tight: only save actual content and not the frame of the figure

addsizer(sizercls, *args, **kwargs)

Adds a Sizer class (and args) which is the default sizer for any strategy
added to cerebro

addsizer_byidx(idx, sizercls, *args, **kwargs)

Adds a Sizer class by idx. This idx is a reference compatible to the one
returned by addstrategy. Only the strategy referenced by idx will receive
this size

add_signal(sigtype, sigcls, *sigargs, **sigkwargs)

Adds a signal to the system which will be later added to a SignalStrategy

signal_concurrent(onoff)

If signals are added to the system and the concurrent value is set to True,
concurrent orders will be allowed

signal_accumulate(onoff)

If signals are added to the system and the accumulate value is set to True,
entering the market when already in the market, will be allowed to increase
a position

92
backtrader’s documentation Version-1.9.58.122

signal_strategy(stratcls, *args, **kwargs)

Adds a SignalStrategy subclass which can accept signals

addcalendar(cal)

Adds a global trading calendar to the system. Individual data feeds may
have separate calendars which override the global one

cal can be an instance of TradingCalendar a string or an instance of


pandas_market_calendars. A string will be will be instantiated as a
PandasMarketCalendar (which needs the module pandas_market_calendar
installed in the system.

If a subclass of TradingCalendarBase is passed (not an instance) it will be


instantiated

addtz(tz)

This can also be done with the parameter tz

Adds a global timezone for strategies. The argument tz can be

• None: in this case the datetime displayed by strategies will be in UTC, which has been always the
standard behavior
• pytz instance. It will be used as such to convert UTC times to the chosen timezone
• string. Instantiating a pytz instance will be attempted.
• integer. Use, for the strategy, the same timezone as the corresponding data in the self.datas
iterable (0 would use the timezone from data0)

add_timer(when, offset=datetime.timedelta(0), repeat=datetime.timedelta(0), weekdays=[],


weekcarry=False, monthdays=[], monthcarry=True, allow=None, tzdata=None, cheat=False, *args,
**kwargs)

Schedules a timer to invoke notify_timer

when (-) –

can be

• datetime.time instance (see below tzdata)


Parameters:
• bt.timer.SESSION_START to reference a session start
• bt.timer.SESSION_END to reference a session end

• offset which must be a datetime.timedelta instance

93
backtrader’s documentation Version-1.9.58.122

Used to offset the value when. It has a meaningful use in


combination with SESSION_START and SESSION_END, to
indicated things like a timer being called 15 minutes after
the session start.

• repeat which must be a datetime.timedelta instance

Indicates if after a 1st call, further calls will be


scheduled within the same session at the scheduled repeat
delta

Once the timer goes over the end of the session it is reset
to the original value for when

• weekdays: a sorted iterable with integers indicating on which days (iso codes,
Monday is 1, Sunday is 7) the timers can be actually invoked

If not specified, the timer will be active on all days

• weekcarry (default: False). If True and the weekday was not seen (ex: trading
holiday), the timer will be executed on the next day (even if in a new week)
• monthdays: a sorted iterable with integers indicating on which days of the month a
timer has to be executed. For example always on day 15 of the month

If not specified, the timer will be active on all days

• monthcarry (default: True). If the day was not seen (weekend, trading holiday), the
timer will be executed on the next available day.
• allow (default: None). A callback which receives a datetime.date` instance and
returns True if the date is allowed for timers or else returns False
• tzdata which can be either None (default), a pytz instance or a data feed instance.

None: when is interpreted at face value (which translates


to handling it as if it where UTC even if it’s not)

pytz instance: when will be interpreted as being specified


in the local time specified by the timezone instance.

data feed instance: when will be interpreted as being


specified in the local time specified by the tz parameter
of the data feed instance.

Note: If when is either SESSION_START or


SESSION_END and tzdata is None, the 1st data feed in the system (aka self.data0) will
be used as the reference to find out the session times.

94
backtrader’s documentation Version-1.9.58.122

• strats (default: False) call also the notify_timer of strategies


• cheat (default False) if True the timer will be called before the broker has a chance
to evaluate the orders. This opens the chance to issue orders based on opening
price for example right before the session starts
• *args: any extra args will be passed to notify_timer
• **kwargs: any extra kwargs will be passed to notify_timer

Return Value:

• The created timer

notify_timer(timer, when, *args, **kwargs)

Receives a timer notification where timer is the timer which was returned
by add_timer, and when is the calling time. args and kwargs are any
additional arguments passed to add_timer

The actual when time can be later, but the system may have not be able to
call the timer before. This value is the timer value and no the system
time.

add_order_history(orders, notify=True)

Add a history of orders to be directly executed in the broker for


performance evaluation

• orders: is an iterable (ex: list, tuple, iterator, generator) in


which each element will be also an iterable (with length) with the
following sub-elements (2 formats are possible)

[datetime, size, price] or [datetime, size, price, data]

Note: it must be sorted (or produce sorted elements) by

datetime ascending

where:

o datetime is a python date/datetime instance or a string with format YYYY-MM-


DD[THH:MM:SS[.us]] where the elements in brackets are optional
o size is an integer (positive to buy, negative to sell)
o price is a float/integer
o data if present can take any of the following values
None - The 1st data feed will be used as target
95
backtrader’s documentation Version-1.9.58.122

integer - The data with that index (insertion order in Cerebro) will be used
string - a data with that name, assigned for example with cerebro.addata(data,
name=value), will be the target
• notify (default: True)

If True the 1st strategy inserted in the system will be notified of


the artificial orders created following the information from each
order in orders

Note: Implicit in the description is the need to add a data feed


which is the target of the orders. This is for example needed by analyzers which track for example the
returns

Cheat On Open

Release 1.9.44.116 adds support for Cheat-On-Open. This seems to be a demanded


feature for people who go all-in, having made a calculation after the close of a
bar, but expecting to be matched against the open price.

Such a use case fails when the opening price gaps (up or down, depending on
whether buy or sell is in effect) and the cash is not enough for an all-in
operation. This forces the broker to reject the operation.

And although people can try to look into the future with a positive [1] index
approach, this requires preloading data which is not always available.

The pattern:

cerebro = bt.Cerebro(cheat_on_open=True)

This:

• Activates an extra cycle in the system which calls the methods in the
strategy next_open, nextstart_open and prenext_open

The decision to have an additional family of methods has been made to make
a clear separation between the regular methods which operate on the basis
that the prices being examined are no longer available and the future is
unknown and the operation in cheating mode.

This also avoids having 2 calls to the regular next method.

The following holds true when inside a xxx_open method:


96
backtrader’s documentation Version-1.9.58.122

• The indicators have not been recalculated and hold the values that were last seen during the previous
cycle in the equivalent xxx regular methods
• The broker has not yet evaluated the pending orders for the new cycle and new orders can be introduced
which will be evaluated if possible.

Notice that:

• Cerebro also has a broker_coo (default: True) parameter which tells cerebro
that if cheat-on-open has been activated, it shall try to activate it also
in the broker if possible.

The simulation broker has a parameter named: coo and a method to set it
named set_coo

Trying cheat-on-open

The sample below has a strategy with 2 different behaviors:

• If cheat-on-open is True, it will only operate from next_open


• If cheat-on-open is False, it will only operate from next

In both cases the matching price must be the same

• If not cheating, the order is issued at the end of the previous day and
will be matched with the next incoming price which is the open price
• If cheating, the order is issued on the same day it is executed. Because
the order is issued before the broker has evaluated orders, it will also be
matched with the next incoming price, the open price.

This second scenario, allows calculation of exact stakes for all-in


strategies, because one can directly access the current open price.

In both cases

• The current open and close prices will be printed from next.

Regular execution:

$ ./cheat-on-open.py --cerebro cheat_on_open=False

...
2005-04-07 next, open 3073.4 close 3090.72
2005-04-08 next, open 3092.07 close 3088.92
Strat Len 68 2005-04-08 Send Buy, fromopen False, close 3088.92
2005-04-11 Buy Executed at price 3088.47
97
backtrader’s documentation Version-1.9.58.122

2005-04-11 next, open 3088.47 close 3080.6


2005-04-12 next, open 3080.42 close 3065.18
...

The order:

• Is issued on 2005-04-08 after the close


• It is executed on 2005-04-11 with the open price of 3088.47

Cheating execution:

$ ./cheat-on-open.py --cerebro cheat_on_open=True

...
2005-04-07 next, open 3073.4 close 3090.72
2005-04-08 next, open 3092.07 close 3088.92
2005-04-11 Send Buy, fromopen True, close 3080.6
2005-04-11 Buy Executed at price 3088.47
2005-04-11 next, open 3088.47 close 3080.6
98
backtrader’s documentation Version-1.9.58.122

2005-04-12 next, open 3080.42 close 3065.18


...

The order:

• Is issued on 2005-04-11 before the open


• It is executed on 2005-04-11 with the open price of 3088.47

And the overall result as seen on the chart is also the same.

Conclusion

Cheating on the open allows issuing orders before the open which can for example
allow the exact calculation of stakes for all-in scenarios.

99
backtrader’s documentation Version-1.9.58.122

Sample usage

$ ./cheat-on-open.py --help
usage: cheat-on-open.py [-h] [--data0 DATA0] [--fromdate FROMDATE]
[--todate TODATE] [--cerebro kwargs] [--broker kwargs]
[--sizer kwargs] [--strat kwargs] [--plot [kwargs]]

Cheat-On-Open Sample

optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to read in (default:
../../datas/2005-2006-day-001.txt)
--fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--cerebro kwargs kwargs in key=value format (default: )
--broker kwargs kwargs in key=value format (default: )
--sizer kwargs kwargs in key=value format (default: )
--strat kwargs kwargs in key=value format (default: )
--plot [kwargs] kwargs in key=value format (default: )

Sample source

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
import datetime

import backtrader as bt

class St(bt.Strategy):
params = dict(
periods=[10, 30],
matype=bt.ind.SMA,
)

def __init__(self):
self.cheating = self.cerebro.p.cheat_on_open
mas = [self.p.matype(period=x) for x in self.p.periods]
self.signal = bt.ind.CrossOver(*mas)

100
backtrader’s documentation Version-1.9.58.122

self.order = None

def notify_order(self, order):


if order.status != order.Completed:
return

self.order = None
print('{} {} Executed at price {}'.format(
bt.num2date(order.executed.dt).date(),
'Buy' * order.isbuy() or 'Sell', order.executed.price)
)

def operate(self, fromopen):


if self.order is not None:
return
if self.position:
if self.signal < 0:
self.order = self.close()
elif self.signal > 0:
print('{} Send Buy, fromopen {}, close {}'.format(
self.data.datetime.date(),
fromopen, self.data.close[0])
)
self.order = self.buy()

def next(self):
print('{} next, open {} close {}'.format(
self.data.datetime.date(),
self.data.open[0], self.data.close[0])
)

if self.cheating:
return
self.operate(fromopen=False)

def next_open(self):
if not self.cheating:
return
self.operate(fromopen=True)

def runstrat(args=None):
args = parse_args(args)

101
backtrader’s documentation Version-1.9.58.122

cerebro = bt.Cerebro()

# Data feed kwargs


kwargs = dict()

# Parse from/to-date
dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
if a:
strpfmt = dtfmt + tmfmt * ('T' in a)
kwargs[d] = datetime.datetime.strptime(a, strpfmt)

# Data feed
data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)
cerebro.adddata(data0)

# Broker
cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))

# Sizer
cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))

# Strategy
cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))

# Execute
cerebro.run(**eval('dict(' + args.cerebro + ')'))

if args.plot: # Plot if requested to


cerebro.plot(**eval('dict(' + args.plot + ')'))

def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=(
'Cheat-On-Open Sample'
)
)

parser.add_argument('--data0', default='../../datas/2005-2006-day-001.txt',
required=False, help='Data to read in')

# Defaults for dates


102
backtrader’s documentation Version-1.9.58.122

parser.add_argument('--fromdate', required=False, default='',


help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

parser.add_argument('--todate', required=False, default='',


help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

parser.add_argument('--cerebro', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--broker', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--sizer', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--strat', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--plot', required=False, default='',


nargs='?', const='{}',
metavar='kwargs', help='kwargs in key=value format')

return parser.parse_args(pargs)

if __name__ == '__main__':
runstrat()

Strategy

A Cerebro instance is the pumping heart and controlling brain of backtrader. A


Strategy is the same for the platform user.

The Strategy’s expressed lifecycle in methods

1. Conception: __init__

This is obviously invoked during instantiation: indicators will be created


here and other needed attribute. Example:

def __init__(self):
self.sma = btind.SimpleMovingAverage(period=15)

Note
103
backtrader’s documentation Version-1.9.58.122

A strategy can be interrupted during birth by raising a StrategySkipError


exception from the module backtrader.errors

This will avoid going through the strategy during a backtesting. See the
section Exceptions

2. Birth: start

The world (cerebro) tells the strategy is time to start kicking. A default
empty method exists.

3. Childhood: prenext

indicators declared during conception will have put constraints on how long
the strategy needs to mature: this is called the minimum period. Above
__init__ created a SimpleMovingAverage with a period=15.

As long as the syste has seen less than 15 bars, prenext will be called
(there is a default method which is a no-op)

4. Adulthood: next

Once the system has seen 15 bars and the SimpleMovingAverage has a buffer
large enough to start producing values, the strategy is mature enough to
really execute.

There is a nextstart method which is called exactly once, to mark the


switch from prenext to next. The default implementation of nextstart is to
simply call next

5. Reproduction: None

Ok, strategies do not really reproduce. But in a sense they do, because the
system will instantiate them several times if optimizing (with different
parameters)

6. Death: stop

The system tells the strategy the time to come to a reset and put things in
order has come. A default empty method exists.

In most cases and for regular usage patterns this will look like:

class MyStrategy(bt.Strategy):

def __init__(self):
104
backtrader’s documentation Version-1.9.58.122

self.sma = btind.SimpleMovingAverage(period=15)

def next(self):
if self.sma > self.data.close:
# Do something
pass

elif self.sma < self.data.close:


# Do something else
pass

In this snippet:

• During __init__ an attribute is assigned an indicator


• The default empty start method is not overriden
• prenext and nexstart are not overriden
• In next the value of the indicator is compared against the closing price to do something
• The default empty stop method is not overriden

Strategies, like a trader in the real world, will get notified when events take
place. Actually once per next cycle in the backtesting process. The strategy
will:

• be notified through notify_order(order) of any status change in an order


• be notified through notify_trade(trade) of any opening/updating/closing
trade
• be notified through notify_cashvalue(cash, value) of the current cash and
portfolio in the broker
• be notified through notify_fund(cash, value, fundvalue, shares) of the
current cash and portfolio in the broker and tradking of fundvalue and
shares
• Events (implementation specific) via notify_store(msg, *args, **kwargs)

See Cerebro for an explanation on the store notifications. These will


delivered to the strategy even if they have also been delivered to a
cerebro instance (with an overriden notify_store method or via a callback)

And Strategies also like traders have the chance to operate in the market during
the next method to try to achieve profit with

• the buy method to go long or reduce/close a short position


• the sell method to go short or reduce/close a long position
• the close method to obviously close an existing position
• the cancel method to cancel a not yet executed order

105
backtrader’s documentation Version-1.9.58.122

How to Buy/Sell/Close

The Buy and Sell methods generate orders. When invoked they return an Order (or
subclass) instance that can be used as a reference. This order has a unique ref
identifier that can be used for comparison

Note

Subclasses of Order for speficic broker implementations may carry additional


unique identifiers provided by the broker.

To create the order use the following parameters:

• data (default: None)

For which data the order has to be created. If None then the first data in
the system, self.datas[0] or self.data0 (aka self.data) will be used

• size (default: None)

Size to use (positive) of units of data to use for the order.

If None the sizer instance retrieved via getsizer will be used to determine
the size.

• price (default: None)

Price to use (live brokers may place restrictions on the actual format if
it does not comply to minimum tick size requirements)

None is valid for Market and Close orders (the market determines the price)

For Limit, Stop and StopLimit orders this value determines the trigger
point (in the case of Limit the trigger is obviously at which price the
order should be matched)

• plimit (default: None)

Only applicable to StopLimit orders. This is the price at which to set the
implicit Limit order, once the Stop has been triggered (for which price has
been used)

• exectype (default: None)

Possible values:
106
backtrader’s documentation Version-1.9.58.122

o Order.Market or None. A market order will be executed with the next available price. In
backtesting it will be the opening price of the next bar
o Order.Limit. An order which can only be executed at the given price or better
o Order.Stop. An order which is triggered at price and executed like an Order.Market order
o Order.StopLimit. An order which is triggered at price and executed as an implicit Limit order with
price given by pricelimit
• valid (default: None)

Possible values:

o None: this generates an order that will not expire (aka Good til cancel) and remain in the market
until matched or canceled. In reality brokers tend to impose a temporal limit, but this is usually so
far away in time to consider it as not expiring
o datetime.datetime or datetime.date instance: the date will be used to generate an order valid
until the given datetime (aka good til date)
o Order.DAY or 0 or timedelta(): a day valid until the End of the Session (aka day order) will be
generated
o numeric value: This is assumed to be a value corresponding to a datetime in matplotlib coding
(the one used by backtrader) and will used to generate an order valid until that time (good til
date)
• tradeid (default: 0)

This is an internal value applied by backtrader to keep track of


overlapping trades on the same asset. This tradeid is sent back to the
strategy when notifying changes to the status of the orders.

• **kwargs: additional broker implementations may support extra parameters.


backtrader will pass the kwargs down to the created order objects

Example: if the 4 order execution types directly supported by backtrader


are not enough, in the case of for example Interactive Brokers the
following could be passed as kwargs:

orderType='LIT', lmtPrice=10.0, auxPrice=9.8

This would override the settings created by backtrader and generate a LIMIT
IF TOUCHED order with a touched price of 9.8 and a limit price of 10.0.

Information Bits:

• A Strategy has a length which is always equal to that of the main data
(datas[0]) and can of course be gotten with len(self)

107
backtrader’s documentation Version-1.9.58.122

next can be called without changes in length if data is being replayed or a


live feed is being passed and new ticks for the same point in time (length)
are arriving

Member Attributes:

• env: the cerebro entity in which this Strategy lives


• datas: array of data feeds which have been passed to cerebro
o data/data0 is an alias for datas[0]
o dataX is an alias for datas[X]

data feeds can also be accessed by name (see the reference) if one has been
assigned to it

• dnames: an alternative to reach the data feeds by name (either with [name]
or with .name notation)

For example if resampling a data like this:

...
data0 = bt.feeds.YahooFinanceData(datname='YHOO', fromdate=...,
name='days')
cerebro.adddata(data0)
cerebro.resampledata(data0, timeframe=bt.TimeFrame.Weeks, name='weeks')
...

Later in the strategy one can create indicators on each like this:

...
smadays = bt.ind.SMA(self.dnames.days, period=30) # or self.dnames['days']
smaweeks = bt.ind.SMA(self.dnames.weeks, period=10) # or
self.dnames['weeks']
...

• broker: reference to the broker associated to this strategy (received from


cerebro)
• stats: list/named tuple-like sequence holding the Observers created by
cerebro for this strategy
• analyzers: list/named tuple-like sequence holding the Analyzers created by
cerebro for this strategy
• position: actually a property which gives the current position for data0.

Methods to retrieve all possitions are available (see the reference)

108
backtrader’s documentation Version-1.9.58.122

Member Attributes (meant for statistics/observers/analyzers):

• _orderspending: list of orders which will be notified to the strategy before next is called
• _tradespending: list of trades which will be notified to the strategy before next is called
• _orders: list of order which have been already notified. An order can be several times in the list with
different statuses and different execution bits. The list is menat to keep the history.
• _trades: list of order which have been already notified. A trade can be several times in the list just like an
order.

Note

Bear in mind that prenext, nextstart and next can be called several times for the
same point in time (ticks updating prices for the daily bar, when a daily
timeframe is in use)

Reference: Strategy

class backtrader.Strategy(*args, **kwargs)

Base class to be subclassed for user defined strategies.

next()

This method will be called for all remaining data points when the minimum
period for all datas/indicators have been meet.

nextstart()

This method will be called once, exactly when the minimum period for all
datas/indicators have been meet. The default behavior is to call next

prenext()

This method will be called before the minimum period of all


datas/indicators have been meet for the strategy to start executing

start()

Called right before the backtesting is about to be started.

stop()

Called right before the backtesting is about to be stopped

109
backtrader’s documentation Version-1.9.58.122

notify_order(order)

Receives an order whenever there has been a change in one

notify_trade(trade)

Receives a trade whenever there has been a change in one

notify_cashvalue(cash, value)

Receives the current fund value, value status of the strategy’s broker

notify_fund(cash, value, fundvalue, shares)

Receives the current cash, value, fundvalue and fund shares

notify_store(msg, *args, **kwargs)

Receives a notification from a store provider

buy(data=None, size=None, price=None, plimit=None, exectype=None, valid=None, tradeid=0, oco=None,


trailamount=None, trailpercent=None, parent=None, transmit=True, **kwargs)

Create a buy (long) order and send it to the broker

• data (default: None)

For which data the order has to be created. If None then the first
data in the system, self.datas[0] or self.data0 (aka self.data) will
be used

• size (default: None)

Size to use (positive) of units of data to use for the order.

If None the sizer instance retrieved via getsizer will be used to


determine the size.

• price (default: None)

Price to use (live brokers may place restrictions on the actual


format if it does not comply to minimum tick size requirements)

None is valid for Market and Close orders (the market determines the
price)

110
backtrader’s documentation Version-1.9.58.122

For Limit, Stop and StopLimit orders this value determines the
trigger point (in the case of Limit the trigger is obviously at which
price the order should be matched)

• plimit (default: None)

Only applicable to StopLimit orders. This is the price at which to


set the implicit Limit order, once the Stop has been triggered (for
which price has been used)

• trailamount (default: None)

If the order type is StopTrail or StopTrailLimit, this is an absolute


amount which determines the distance to the price (below for a Sell
order and above for a buy order) to keep the trailing stop

• trailpercent (default: None)

If the order type is StopTrail or StopTrailLimit, this is a


percentage amount which determines the distance to the price (below
for a Sell order and above for a buy order) to keep the trailing stop
(if trailamount is also specified it will be used)

• exectype (default: None)

Possible values:

o Order.Market or None. A market order will be executed with the next available price. In
backtesting it will be the opening price of the next bar
o Order.Limit. An order which can only be executed at the given price or better
o Order.Stop. An order which is triggered at price and executed like an Order.Market order
o Order.StopLimit. An order which is triggered at price and executed as an implicit Limit
order with price given by pricelimit
o Order.Close. An order which can only be executed with the closing price of the session
(usually during a closing auction)
o Order.StopTrail. An order which is triggered at price minus trailamount (or trailpercent)
and which is updated if the price moves away from the stop
o Order.StopTrailLimit. An order which is triggered at price minus trailamount (or
trailpercent) and which is updated if the price moves away from the stop
• valid (default: None)

Possible values:

111
backtrader’s documentation Version-1.9.58.122

o None: this generates an order that will not expire (aka Good till cancel) and remain in the
market until matched or canceled. In reality brokers tend to impose a temporal limit, but
this is usually so far away in time to consider it as not expiring
o datetime.datetime or datetime.date instance: the date will be used to generate an order
valid until the given datetime (aka good till date)
o Order.DAY or 0 or timedelta(): a day valid until the End of the Session (aka day order) will
be generated
o numeric value: This is assumed to be a value corresponding to a datetime in matplotlib
coding (the one used by backtrader) and will used to generate an order valid until that
time (good till date)
• tradeid (default: 0)

This is an internal value applied by backtrader to keep track of


overlapping trades on the same asset. This tradeid is sent back to
the strategy when notifying changes to the status of the orders.

• oco (default: None)

Another order instance. This order will become part of an OCO (Order
Cancel Others) group. The execution of one of the orders, immediately
cancels all others in the same group

• parent (default: None)

Controls the relationship of a group of orders, for example a buy


which is bracketed by a high-side limit sell and a low side stop
sell. The high/low side orders remain inactive until the parent order
has been either executed (they become active) or is canceled/expires
(the children are also canceled) bracket orders have the same size

• transmit (default: True)

Indicates if the order has to be transmitted, ie: not only placed in


the broker but also issued. This is meant for example to control
bracket orders, in which one disables the transmission for the parent
and 1st set of children and activates it for the last children, which
triggers the full placement of all bracket orders.

• **kwargs: additional broker implementations may support extra


parameters. backtrader will pass the kwargs down to the created order
objects

Example: if the 4 order execution types directly supported by


backtrader are not enough, in the case of for example Interactive
Brokers the following could be passed as kwargs:
112
backtrader’s documentation Version-1.9.58.122

orderType='LIT', lmtPrice=10.0, auxPrice=9.8

This would override the settings created by backtrader and generate a


LIMIT IF TOUCHED order with a touched price of 9.8 and a limit price
of 10.0.

• the submitted order


Returns:

sell(data=None, size=None, price=None, plimit=None, exectype=None, valid=None, tradeid=0, oco=None,


trailamount=None, trailpercent=None, parent=None, transmit=True, **kwargs)

To create a selll (short) order and send it to the broker

See the documentation for buy for an explanation of the parameters

Returns: the submitted order

close(data=None, size=None, **kwargs)

Counters a long/short position closing it

See the documentation for buy for an explanation of the parameters

Note

• size: automatically calculated from the existing position if not provided (default: None) by the
caller

Returns: the submitted order

cancel(order)

Cancels the order in the broker

buy_bracket(data=None, size=None, price=None, plimit=None, exectype=2, valid=None, tradeid=0,


trailamount=None, trailpercent=None, oargs={}, stopprice=None, stopexec=3, stopargs={}, limitprice=None,
limitexec=2, limitargs={}, **kwargs)

Create a bracket order group (low side - buy order - high side). The
default behavior is as follows:

• Issue a buy order with execution Limit


• Issue a low side bracket sell order with execution Stop
• Issue a high side bracket sell order with execution Limit.

See below for the different parameters


113
backtrader’s documentation Version-1.9.58.122

• data (default: None)

For which data the order has to be created. If None then the first
data in the system, self.datas[0] or self.data0 (aka self.data) will
be used

• size (default: None)

Size to use (positive) of units of data to use for the order.

If None the sizer instance retrieved via getsizer will be used to


determine the size.

Note: The same size is applied to all 3 orders of the bracket

• price (default: None)

Price to use (live brokers may place restrictions on the actual


format if it does not comply to minimum tick size requirements)

None is valid for Market and Close orders (the market determines the
price)

For Limit, Stop and StopLimit orders this value determines the
trigger point (in the case of Limit the trigger is obviously at which
price the order should be matched)

• plimit (default: None)

Only applicable to StopLimit orders. This is the price at which to


set the implicit Limit order, once the Stop has been triggered (for
which price has been used)

• trailamount (default: None)

If the order type is StopTrail or StopTrailLimit, this is an absolute


amount which determines the distance to the price (below for a Sell
order and above for a buy order) to keep the trailing stop

• trailpercent (default: None)

If the order type is StopTrail or StopTrailLimit, this is a


percentage amount which determines the distance to the price (below
for a Sell order and above for a buy order) to keep the trailing stop
(if trailamount is also specified it will be used)

114
backtrader’s documentation Version-1.9.58.122

• exectype (default: bt.Order.Limit)

Possible values: (see the documentation for the method buy

• valid (default: None)

Possible values: (see the documentation for the method buy

• tradeid (default: 0)

Possible values: (see the documentation for the method buy

• oargs (default: {})

Specific keyword arguments (in a dict) to pass to the main side


order. Arguments from the default **kwargs will be applied on top of
this.

• **kwargs: additional broker implementations may support extra


parameters. backtrader will pass the kwargs down to the created order
objects

Possible values: (see the documentation for the method buy

Note: this kwargs will be applied to the 3 orders of a bracket. See


below for specific keyword arguments for the low and high side orders

• stopprice (default: None)

Specific price for the low side stop order

• stopexec (default: bt.Order.Stop)

Specific execution type for the low side order

• stopargs (default: {})

Specific keyword arguments (in a dict) to pass to the low side order.
Arguments from the default **kwargs will be applied on top of this.

• limitprice (default: None)

Specific price for the high side stop order

• stopexec (default: bt.Order.Limit)

115
backtrader’s documentation Version-1.9.58.122

Specific execution type for the high side order

• limitargs (default: {})

Specific keyword arguments (in a dict) to pass to the high side


order. Arguments from the default **kwargs will be applied on top of
this.

• A list containing the 3 orders [order, stop side, limit side]


Returns:

sell_bracket(data=None, size=None, price=None, plimit=None, exectype=2, valid=None, tradeid=0,


trailamount=None, trailpercent=None, oargs={}, stopprice=None, stopexec=3, stopargs={}, limitprice=None,
limitexec=2, limitargs={}, **kwargs)

Create a bracket order group (low side - buy order - high side). The
default behavior is as follows:

• Issue a sell order with execution Limit


• Issue a high side bracket buy order with execution Stop
• Issue a low side bracket buy order with execution Limit.

See bracket_buy for the meaning of the parameters

• A list containing the 3 orders [order, stop side, limit side]


Returns:

order_target_size(data=None, target=0, **kwargs)

Place an order to rebalance a position to have final size of target

The current position size is taken into account as the start point to
achieve target

• If target > pos.size -> buy target - pos.size


• If target < pos.size -> sell pos.size - target

It returns either:

• The generated order

or

• None if no order has been issued (target == position.size)

order_target_value(data=None, target=0.0, price=None, **kwargs)

Place an order to rebalance a position to have final value of target


116
backtrader’s documentation Version-1.9.58.122

The current value is taken into account as the start point to achieve
target

• If no target then close postion on data


• If target > value then buy on data
• If target < value then sell on data

It returns either:

• The generated order

or

• None if no order has been issued

order_target_percent(data=None, target=0.0, **kwargs)

Place an order to rebalance a position to have final value of target


percentage of current portfolio value

target is expressed in decimal: 0.05 -> 5%

It uses order_target_value to execute the order.

Example

• target=0.05 and portfolio value is 100


• The value to be reached is 0.05 * 100 = 5
• 5 is passed as the target value to order_target_value

The current value is taken into account as the start point to achieve
target

The position.size is used to determine if a position is long / short

• If target > value - buy if pos.size >= 0 (Increase a long position) - sell if pos.size < 0 (Increase a
short position)
• If target < value - sell if pos.size >= 0 (Decrease a long position) - buy if pos.size < 0 (Decrease
a short position)

It returns either:

• The generated order

or

117
backtrader’s documentation Version-1.9.58.122

• None if no order has been issued (target == position.size)

getsizer()

Returns the sizer which is in used if automatic statke calculation is used

Also available as sizer

setsizer(sizer)

Replace the default (fixed stake) sizer

getsizing(data=None, isbuy=True)

Return the stake calculated by the sizer instance for the current situation

getposition(data=None, broker=None)

Returns the current position for a given data in a given broker.

If both are None, the main data and the default broker will be used

A property position is also available

getpositionbyname(name=None, broker=None)

Returns the current position for a given name in a given broker.

If both are None, the main data and the default broker will be used

A property positionbyname is also available

getpositionsbyname(broker=None)

Returns the current by name positions directly from the broker

If the given broker is None, the default broker will be used

A property positionsbyname is also available

getdatanames()

Returns a list of the existing data names

getdatabyname(name)

Returns a given data by name using the environment (cerebro)


118
backtrader’s documentation Version-1.9.58.122

add_timer(when, offset=datetime.timedelta(0), repeat=datetime.timedelta(0), weekdays=[],


weekcarry=False, monthdays=[], monthcarry=True, allow=None, tzdata=None, cheat=False, *args,
**kwargs)

Note: can be called during __init__ or start

Schedules a timer to invoke either a specified callback or the notify_timer


of one or more strategies.

when (-) –

can be

• datetime.time instance (see below tzdata)


• bt.timer.SESSION_START to reference a session start
• bt.timer.SESSION_END to reference a session end

• offset which must be a datetime.timedelta instance

Used to offset the value when. It has a meaningful use in


combination with SESSION_START and SESSION_END, to
indicated things like a timer being called 15 minutes after
the session start.

• repeat which must be a datetime.timedelta instance

Indicates if after a 1st call, further calls will be


Parameters:
scheduled within the same session at the scheduled repeat
delta

Once the timer goes over the end of the session it is reset
to the original value for when

• weekdays: a sorted iterable with integers indicating on which days (iso codes,
Monday is 1, Sunday is 7) the timers can be actually invoked

If not specified, the timer will be active on all days

• weekcarry (default: False). If True and the weekday was not seen (ex: trading
holiday), the timer will be executed on the next day (even if in a new week)
• monthdays: a sorted iterable with integers indicating on which days of the month a
timer has to be executed. For example always on day 15 of the month

If not specified, the timer will be active on all days

119
backtrader’s documentation Version-1.9.58.122

• monthcarry (default: True). If the day was not seen (weekend, trading holiday), the
timer will be executed on the next available day.
• allow (default: None). A callback which receives a datetime.date` instance and
returns True if the date is allowed for timers or else returns False
• tzdata which can be either None (default), a pytz instance or a data feed instance.

None: when is interpreted at face value (which translates


to handling it as if it where UTC even if it’s not)

pytz instance: when will be interpreted as being specified


in the local time specified by the timezone instance.

data feed instance: when will be interpreted as being


specified in the local time specified by the tz parameter
of the data feed instance.

Note: If when is either SESSION_START or


SESSION_END and tzdata is None, the 1st data feed in the system (aka self.data0) will
be used as the reference to find out the session times.

• cheat (default False) if True the timer will be called before the broker has a chance
to evaluate the orders. This opens the chance to issue orders based on opening
price for example right before the session starts
• *args: any extra args will be passed to notify_timer
• **kwargs: any extra kwargs will be passed to notify_timer

Return Value:

• The created timer

notify_timer(timer, when, *args, **kwargs)

Receives a timer notification where timer is the timer which was returned
by add_timer, and when is the calling time. args and kwargs are any
additional arguments passed to add_timer

The actual when time can be later, but the system may have not be able to
call the timer before. This value is the timer value and no the system
time.

120
backtrader’s documentation Version-1.9.58.122

Sizers - Smart Staking

A Strategy offers methods to trade, namely: buy, sell and close. Let’s see the
signature of buy:

def buy(self, data=None,


size=None, price=None, plimit=None,
exectype=None, valid=None, tradeid=0, **kwargs):

Notice that size has a default value of None if the caller does not specify it.
This is where Sizers play an important role:

• size=None requests that the Strategy asks its Sizer for the actual stake

This obviously implies that Strategies have a Sizer: Yes, indeed!. The background
machinery adds a default sizer to a Strategy if the user has not added one. The
default Sizer added to a strategy is SizerFix. The initial lines of the
definition:

class SizerFix(SizerBase):
params = (('stake', 1),)

It is easy to guess that this Sizer simply buys/sells using a stake of 1 units
(be it shares, contracts, ...)

Using Sizers

From Cerebro

Sizers can be added via Cerebro with 2 different methods:

• addsizer(sizercls, *args, **kwargs)

Adds a Sizer that will be applied to any strategy added to cerebro. This
is, so to to say, the default Sizer. Example:

cerebro = bt.Cerebro()
cerebro.addsizer(bt.sizers.SizerFix, stake=20) # default sizer for
strategies

• addsizer_byidx(idx, sizercls, *args, **kwargs)

The Sizer will only be added to the Strategy referenced by idx


121
backtrader’s documentation Version-1.9.58.122

This idx can be gotten as return value from addstrategy. As in:

cerebro = bt.Cerebro()
cerebro.addsizer(bt.sizers.SizerFix, stake=20) # default sizer for
strategies

idx = cerebro.addstrategy(MyStrategy, myparam=myvalue)


cerebro.addsizer_byidx(idx, bt.sizers.SizerFix, stake=5)

cerebro.addstrategy(MyOtherStrategy)

In this example:

o A default Sizer has been added to the system. This one applies to all strategies which don’t have a
specific Sizer assigned
o For MyStrategy and after collecting its insertion idx, a specific sizer (changing the stake param) is
added
o A 2nd strategy, MyOtherStrategy, is added to the system. No specific Sizer is added for it
o This means that:
MyStrategy will finally have an internal specific Sizer
MyOtherStrategy will get the default sizer

Note

default doesn’t mean that that the strategies share a single Sizer
instance. Each strategy receives a different instance of the default sizer

To share a single instance, the sizer to be shared should be a singleton


class. How to define one is outside of the scope of backtrader

From Strategy

The Strategy class offers an API: setsizer and getsizer (and a property sizer) to
manage the Sizer. The signatures:

• def setsizer(self, sizer): it takes an already instantiated Sizer


• def getsizer(self): returns the current Sizer instance
• sizer it is the property which can be directly get/set

In this scenario the Sizer can be for example:

• Passed to the strategy as a parameter


• Be set during __init__ using the property sizer or setsizer as in:
• class MyStrategy(bt.Strategy):

122
backtrader’s documentation Version-1.9.58.122

• params = (('sizer', None),)



• def __init__(self):
• if self.p.sizer is not None:
• self.sizer = self.p.sizer

This would for example allow to create a Sizer at the same level as the
cerebro calls are happening and pass it as a parameter to all strategies
that go in the system, which effectevily allows sharing a Sizer

Sizer Development

Doing it is easy:

1. Subclass from backtrader.Sizer

This gives you access to self.strategy and self.broker although it


shouldn’t be needed in most cases. Things that can be accessed with the
broker

o data’s position with self.strategy.getposition(data)


o complete portfolio value through self.broker.getvalue()

Notice this could of course also be done with


self.strategy.broker.getvalue()

Some of the other things are already below as arguments

2. Override the method _getsizing(self, comminfo, cash, data, isbuy)


o comminfo: The CommissionInfo instance that contains information about the commission for the
data and allows calculation of position value, operation cost, commision for the operation
o cash: current available cash in the broker
o data: target of the operation
o isbuy: will be True for buy operations and False for sell operations

This method returns the desired size for the buy/sell operation

The returned sign is not relevant, ie: if the operation is a sell operation
(isbuy will be False) the method may return 5 or -5. Only the absolute
value will be used by the sell operation.

Sizer has already gone to the broker and requested the commission
information for the given data, the actual cash level and provides a direct
reference to the data which is the target of the operation

123
backtrader’s documentation Version-1.9.58.122

Let’s go for the definition of the FixedSize sizer:

import backtrader as bt

class FixedSize(bt.Sizer):
params = (('stake', 1),)

def _getsizing(self, comminfo, cash, data, isbuy):


return self.params.stake

This is pretty simple in that the Sizer makes no calculations and the parameters
are just there.

But the mechanism should allow the construction of complex sizing (aka
positioning) systems to manage the stakes when entering/exiting the market.

Another example: A position rerverser:

class FixedRerverser(bt.FixedSize):

def _getsizing(self, comminfo, cash, data, isbuy):


position = self.broker.getposition(data)
size = self.p.stake * (1 + (position.size != 0))
return size

This one builds on the existing FixedSize to inherit the params and overrides
_getsizing to:

• Get the position of the data via the attribute broker


• Use position.size to decide if to double the fixed stake
• Return the calculated value

This would remove the burden from the Strategy to decide if a position has to be
reversed or opened, the Sizer is in control and can at any time be replaced
without affecting the logic.

Practical Sizer Applicability

Wihtout considering complex sizing algorithms, two different sizers can be used
to turn a strategy from Long-Only to Long-Short. Simply by changing the Sizer in
the cerebro execution, the strategy will change behavior. A very simple close
crosses SMA algorithm:

class CloseSMA(bt.Strategy):

124
backtrader’s documentation Version-1.9.58.122

params = (('period', 15),)

def __init__(self):
sma = bt.indicators.SMA(self.data, period=self.p.period)
self.crossover = bt.indicators.CrossOver(self.data, sma)

def next(self):
if self.crossover > 0:
self.buy()

elif self.crossover < 0:


self.sell()

Notice how the strategy doesn’t consider the current position (by looking at
self.position) to decide whether a buy or sell has to actually be done. Only the
signal from the CrossOver is considered. The Sizers will be in charge of
everything.

This sizer will take care of only returning a non-zero size when selling if a
position is already open:

class LongOnly(bt.Sizer):
params = (('stake', 1),)

def _getsizing(self, comminfo, cash, data, isbuy):


if isbuy:
return self.p.stake

# Sell situation
position = self.broker.getposition(data)
if not position.size:
return 0 # do not sell if nothing is open

return self.p.stake

Putting it all together (and assuming backtrader has already been imported and a
data has been added to the system):

...
cerebro.addstrategy(CloseSMA)
cerebro.addsizer(LongOnly)
...
cerebro.run()
...

125
backtrader’s documentation Version-1.9.58.122

The chart (from the sample included in the sources to test this).

The Long-Short version simply changes the Sizer to be the FixedReverser shown
above:

...
cerebro.addstrategy(CloseSMA)
cerebro.addsizer(FixedReverser)
...
cerebro.run()
...

The output chart.

126
backtrader’s documentation Version-1.9.58.122

Notice the differences:

• The number of trades has duplicated


• The cash level never goes back to be the value because the strategy is always in the market

Both approaches are anyhow negative, but this is only an example.

bt.Sizer Reference

class backtrader.Sizer

This is the base class for Sizers. Any sizer should subclass this and
override the _getsizing method

Member Attribs:

• strategy: will be set by the strategy in which the sizer is working


127
backtrader’s documentation Version-1.9.58.122

Gives access to the entire api of the strategy, for example if the
actual data position would be needed in _getsizing:

position = self.strategy.getposition(data)

• broker: will be set by the strategy in which the sizer is working

Gives access to information some complex sizers may need like


portfolio value, ..

_getsizing(comminfo, cash, data, isbuy)

This method has to be overriden by subclasses of Sizer to provide the


sizing functionality

Params:

• comminfo: The CommissionInfo instance that contains information about the commission for the
data and allows calculation of position value, operation cost, commision for the operation
• cash: current available cash in the broker
• data: target of the operation
• isbuy: will be True for buy operations and False for sell operations

The method has to return the actual size (an int) to be executed. If 0 is
returned nothing will be executed.

The absolute value of the returned value will be used

Timers

Release 1.9.44.116 added timers to the arsenal of tools available in backtrader.


This functionality allows to get a call back to the notify_timer (available in
Cerebro and Strategy) at given points in time, with a fine grained end-user
control.

Note

Some corrections have been made in 1.9.46.116

Options

• Timer based in absolute time input or with regards to session start/end times
• Timezone specification for the time specification, be it directly or via pytz compatible objects or via data
feed session end times
128
backtrader’s documentation Version-1.9.58.122

• Starting offset with regards to the specified time


• Repetitions intervals
• Weekdays filter (with carry over option)
• Monthdays filter (with carry over option)
• Custom callback filter

Usage pattern

Both in Cerebro and Strategy subclasses the timer callback will be received in
the following method.

def notify_timer(self, timer, when, *args, **kwargs):


'''Receives a timer notification where ``timer`` is the timer which was
returned by ``add_timer``, and ``when`` is the calling time. ``args``
and ``kwargs`` are any additional arguments passed to ``add_timer``

The actual ``when`` time can be later, but the system may have not be
able to call the timer before. This value is the timer value and not the
system time.
'''

Adding timers - Via Strategy

Done with the method

def add_timer(self, when,


offset=datetime.timedelta(), repeat=datetime.timedelta(),
weekdays=[], weekcarry=False,
monthdays=[], monthcarry=True,
allow=None,
tzdata=None, cheat=False,
*args, **kwargs):
'''

It returns the created Timer instance.

See below for an explanation of the parameters.

129
backtrader’s documentation Version-1.9.58.122

Adding timers - Via Cerebro

Done with the same method and just the addition of the parameter strats. If set
to True the timer will not only be notified to the cerebro, it will also be
notified to all strategies running in the system.

def add_timer(self, when,


offset=datetime.timedelta(), repeat=datetime.timedelta(),
weekdays=[], weekcarry=False,
monthdays=[], monthcarry=True,
allow=None,
tzdata=None, cheat=False, strats=False,
*args, **kwargs):
'''

It returns the created Timer instance.

When are timers called

If cheat=False

This is the default. In this case a timer will be called:

• After the data feeds have loaded the new values for the current bar
• After the broker has evaluated orders and recalculated the portfolio value
• Before indicators have been recalculated (because this is triggered by the strategies)
• Before any next method of any strategy is called

If cheat=True

In this case a timer will be called:

• After the data feeds have loaded the new values for the current bar
• Before the broker has evaluated orders and recalculated the portfolio value
• And consequently before indicators have been recalculated and next method of any strategy is called

Which allows for example the following scenario with daily bars:

• Before the new bar is evaluated by the broker the timer is called
• The indicators have the value from the previous day at the close and can be used to generate an
entry/exit signal (or a flag may have been set during the last evaluation of next)

130
backtrader’s documentation Version-1.9.58.122

• Because the new prices are available, the stake can be calculated using the opening price. This assumes
that one is for example getting a good indication about the open from watching the opening auction.

Running with daily bars

The sample scheduled.py defaults to running with the standard daily bars
available in the backtrader distribution. The parameters to the strategy

class St(bt.Strategy):
params = dict(
when=bt.timer.SESSION_START,
timer=True,
cheat=False,
offset=datetime.timedelta(),
repeat=datetime.timedelta(),
weekdays=[],
)

And the data has the following session times:

• start: 09:00
• end: 17:30

Running with just a time

$ ./scheduled.py --strat when='datetime.time(15,30)'

strategy notify_timer with tid 0, when 2005-01-03 15:30:00 cheat False


1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 0, when 2005-01-04 15:30:00 cheat False
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 0, when 2005-01-05 15:30:00 cheat False
3, 2005-01-05 17:30:00, Week 1, Day 3, O 2969.0, H 2969.0, L 2942.69, C 2947.19
strategy notify_timer with tid 0, when 2005-01-06 15:30:00 cheat False
...

As specified the timer is ticking at 15:30. No surprises there. Let’s add an


offset of 30 minutes.

$ ./scheduled.py --strat
when='datetime.time(15,30)',offset='datetime.timedelta(minutes=30)'

strategy notify_timer with tid 0, when 2005-01-03 16:00:00 cheat False


1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
131
backtrader’s documentation Version-1.9.58.122

strategy notify_timer with tid 0, when 2005-01-04 16:00:00 cheat False


2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 0, when 2005-01-05 16:00:00 cheat False
...

And the time has changed from 15:30 to 16:00 for the timer. No surprises. Let’s
do the same but referencing the start of the session.

$ ./scheduled.py --strat
when='bt.timer.SESSION_START',offset='datetime.timedelta(minutes=30)'

strategy notify_timer with tid 0, when 2005-01-03 09:30:00 cheat False


1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 0, when 2005-01-04 09:30:00 cheat False
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
...

Et voilá! The time at which the callback is called is 09:30. And the session
start, see above, is 09:00. This gives the ability to simply say that one wants
to execute an action 30 minutes after the start of the session.

Let’s add a repetition:

$ ./scheduled.py --strat
when='bt.timer.SESSION_START',offset='datetime.timedelta(minutes=30)',repeat='dat
etime.timedelta(minutes=30)'

strategy notify_timer with tid 0, when 2005-01-03 09:30:00 cheat False


1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 0, when 2005-01-04 09:30:00 cheat False
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 0, when 2005-01-05 09:30:00 cheat False
...

There is no repetition. The reason being that the resolution of the prices is
daily. The timer is called for the 1st time at 09:30 like in the previous
example. But when the system get the next batch of prices, they are happening on
the next day. And the timer can only, obviously, be called once. A lower
resolution is needed.

But before moving on to a lower resolution, let’s cheat by having the timer
called before the end of the session.

$ ./scheduled.py --strat when='bt.timer.SESSION_START',cheat=True

132
backtrader’s documentation Version-1.9.58.122

strategy notify_timer with tid 1, when 2005-01-03 09:00:00 cheat True


-- 2005-01-03 Create buy order
strategy notify_timer with tid 0, when 2005-01-03 09:00:00 cheat False
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 1, when 2005-01-04 09:00:00 cheat True
strategy notify_timer with tid 0, when 2005-01-04 09:00:00 cheat False
-- 2005-01-04 Buy Exec @ 2969.78
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 1, when 2005-01-05 09:00:00 cheat True
strategy notify_timer with tid 0, when 2005-01-05 09:00:00 cheat False
...

The strategy adds a 2nd timer with cheat=True. This is added 2nd and will
therefore received the 2nd tid (timer id) which is 1 (see in the above examples
that the assigned tid was 0)

And 1 is called before 0, because that timer is cheating and is being called
before many events in the system happen (see above for the explanation)

Due to the daily resolution of the prices it doesn’t make much of a difference
except that:

• The strategy also issues an order right before the open ... and it is being
matched with the opening price the next day

This, even if cheating by acting before the open, is still the normal
behavior, because cheating-on-open has also not been activated in the
broker.

The same but with coo=True for the broker

$ ./scheduled.py --strat when='bt.timer.SESSION_START',cheat=True --broker


coo=True

strategy notify_timer with tid 1, when 2005-01-03 09:00:00 cheat True


-- 2005-01-03 Create buy order
strategy notify_timer with tid 0, when 2005-01-03 09:00:00 cheat False
-- 2005-01-03 Buy Exec @ 2952.29
1, 2005-01-03 17:30:00, Week 1, Day 1, O 2952.29, H 2989.61, L 2946.8, C 2970.02
strategy notify_timer with tid 1, when 2005-01-04 09:00:00 cheat True
strategy notify_timer with tid 0, when 2005-01-04 09:00:00 cheat False
2, 2005-01-04 17:30:00, Week 1, Day 2, O 2969.78, H 2979.88, L 2961.14, C 2971.12
strategy notify_timer with tid 1, when 2005-01-05 09:00:00 cheat True
strategy notify_timer with tid 0, when 2005-01-05 09:00:00 cheat False
...
133
backtrader’s documentation Version-1.9.58.122

And something has changed.

• The order is issued on 2005-01-03 in the cheating timer


• The order is executed on 2005-01-03 with the opening price

Effectively like if one had acted on the opening auction price seconds
before the real opening of the market.

Running with 5-minute bars

The sample scheduled-min.py defaults to running with the standard 5-minute bars
available in the backtrader distribution. The parameters to the strategy are
extended to include monthdays and the carry options

class St(bt.Strategy):
params = dict(
when=bt.timer.SESSION_START,
timer=True,
cheat=False,
offset=datetime.timedelta(),
repeat=datetime.timedelta(),
weekdays=[],
weekcarry=False,
monthdays=[],
monthcarry=True,
)

The data has the same session times:

• start: 09:00
• end: 17:30

Let’s do some experiments. First a single timer.

$ ./scheduled-min.py --strat when='datetime.time(15, 30)'

1, 2006-01-02 09:05:00, Week 1, Day 1, O 3578.73, H 3587.88, L 3578.73, C 3582.99


2, 2006-01-02 09:10:00, Week 1, Day 1, O 3583.01, H 3588.4, L 3583.01, C 3588.03
...
77, 2006-01-02 15:25:00, Week 1, Day 1, O 3599.07, H 3599.68, L 3598.47, C
3599.68
strategy notify_timer with tid 0, when 2006-01-02 15:30:00 cheat False
78, 2006-01-02 15:30:00, Week 1, Day 1, O 3599.64, H 3599.73, L 3599.0, C 3599.67
...
134
backtrader’s documentation Version-1.9.58.122

179, 2006-01-03 15:25:00, Week 1, Day 2, O 3634.72, H 3635.0, L 3634.06, C


3634.87
strategy notify_timer with tid 0, when 2006-01-03 15:30:00 cheat False
180, 2006-01-03 15:30:00, Week 1, Day 2, O 3634.81, H 3634.89, L 3634.04, C
3634.23
...

The timer kicks in as requested at 15:30. The log shows how it does that during
the 1st two days.

Adding a repeat of 15 minutes to the mix

$ ./scheduled-min.py --strat when='datetime.time(15,


30)',repeat='datetime.timedelta(minutes=15)'

...
74, 2006-01-02 15:10:00, Week 1, Day 1, O 3596.12, H 3596.63, L 3595.92, C
3596.63
75, 2006-01-02 15:15:00, Week 1, Day 1, O 3596.36, H 3596.65, L 3596.19, C
3596.65
76, 2006-01-02 15:20:00, Week 1, Day 1, O 3596.53, H 3599.13, L 3596.12, C 3598.9
77, 2006-01-02 15:25:00, Week 1, Day 1, O 3599.07, H 3599.68, L 3598.47, C
3599.68
strategy notify_timer with tid 0, when 2006-01-02 15:30:00 cheat False
78, 2006-01-02 15:30:00, Week 1, Day 1, O 3599.64, H 3599.73, L 3599.0, C 3599.67
79, 2006-01-02 15:35:00, Week 1, Day 1, O 3599.61, H 3600.29, L 3599.52, C
3599.92
80, 2006-01-02 15:40:00, Week 1, Day 1, O 3599.96, H 3602.06, L 3599.76, C
3602.05
strategy notify_timer with tid 0, when 2006-01-02 15:45:00 cheat False
81, 2006-01-02 15:45:00, Week 1, Day 1, O 3601.97, H 3602.07, L 3601.45, C
3601.83
82, 2006-01-02 15:50:00, Week 1, Day 1, O 3601.74, H 3602.8, L 3601.63, C 3602.8
83, 2006-01-02 15:55:00, Week 1, Day 1, O 3602.53, H 3602.74, L 3602.33, C
3602.61
strategy notify_timer with tid 0, when 2006-01-02 16:00:00 cheat False
84, 2006-01-02 16:00:00, Week 1, Day 1, O 3602.58, H 3602.75, L 3601.81, C
3602.14
85, 2006-01-02 16:05:00, Week 1, Day 1, O 3602.16, H 3602.16, L 3600.86, C
3600.96
86, 2006-01-02 16:10:00, Week 1, Day 1, O 3601.2, H 3601.49, L 3600.94, C 3601.27
...
strategy notify_timer with tid 0, when 2006-01-02 17:15:00 cheat False
99, 2006-01-02 17:15:00, Week 1, Day 1, O 3603.96, H 3603.96, L 3602.89, C
3603.79
135
backtrader’s documentation Version-1.9.58.122

100, 2006-01-02 17:20:00, Week 1, Day 1, O 3603.94, H 3605.95, L 3603.87, C


3603.91
101, 2006-01-02 17:25:00, Week 1, Day 1, O 3604.0, H 3604.76, L 3603.85, C
3604.64
strategy notify_timer with tid 0, when 2006-01-02 17:30:00 cheat False
102, 2006-01-02 17:30:00, Week 1, Day 1, O 3604.06, H 3604.41, L 3603.95, C
3604.33
103, 2006-01-03 09:05:00, Week 1, Day 2, O 3604.08, H 3609.6, L 3604.08, C 3609.6
104, 2006-01-03 09:10:00, Week 1, Day 2, O 3610.34, H 3617.31, L 3610.34, C
3617.31
105, 2006-01-03 09:15:00, Week 1, Day 2, O 3617.61, H 3617.87, L 3616.03, C
3617.51
106, 2006-01-03 09:20:00, Week 1, Day 2, O 3617.24, H 3618.86, L 3616.09, C
3618.42
...
179, 2006-01-03 15:25:00, Week 1, Day 2, O 3634.72, H 3635.0, L 3634.06, C
3634.87
strategy notify_timer with tid 0, when 2006-01-03 15:30:00 cheat False
180, 2006-01-03 15:30:00, Week 1, Day 2, O 3634.81, H 3634.89, L 3634.04, C
3634.23
...

As expected the 1st call is triggered at 15:30 and then starts repeating every 15
minutes until the end of the session at 17:30. When the new session kicks in, the
timer has been reset to 15:30 again.

And now with cheating before the session start

$ ./scheduled-min.py --strat when='bt.timer.SESSION_START',cheat=True

strategy notify_timer with tid 1, when 2006-01-02 09:00:00 cheat True


-- 2006-01-02 09:05:00 Create buy order
strategy notify_timer with tid 0, when 2006-01-02 09:00:00 cheat False
1, 2006-01-02 09:05:00, Week 1, Day 1, O 3578.73, H 3587.88, L 3578.73, C 3582.99
-- 2006-01-02 09:10:00 Buy Exec @ 3583.01
2, 2006-01-02 09:10:00, Week 1, Day 1, O 3583.01, H 3588.4, L 3583.01, C 3588.03
...

Order creation is t 09:05:00 and execution at 09:10:00 because the broker is not
in cheat-on-open mode. Let’s set it ...

$ ./scheduled-min.py --strat when='bt.timer.SESSION_START',cheat=True --broker


coo=True

strategy notify_timer with tid 1, when 2006-01-02 09:00:00 cheat True


136
backtrader’s documentation Version-1.9.58.122

-- 2006-01-02 09:05:00 Create buy order


strategy notify_timer with tid 0, when 2006-01-02 09:00:00 cheat False
-- 2006-01-02 09:05:00 Buy Exec @ 3578.73
1, 2006-01-02 09:05:00, Week 1, Day 1, O 3578.73, H 3587.88, L 3578.73, C 3582.99
2, 2006-01-02 09:10:00, Week 1, Day 1, O 3583.01, H 3588.4, L 3583.01, C 3588.03
...

And the issuing time and execution time are 09:05:00 with the execution price
being the opening price at 09:05:00.

Additional scenarios

Timers allow specifying on which days they have to be executed by passing a list
of days (integers following the iso spec, where Mon=1 and Sun=7) as in

• weekdays=[5] which would ask for the timer to only be valid on Fridays

In case a Friday is a non-trading day and the timer should kick-in on the
next trading day, one can add weekcarry=True

Similar to it, one can decide to act on the 15th day of each month with:

• monthdays=[15]

In case the 15th happens to be non-trading day and the timer should kick-in
on the next trading day, one can add monthcarry=True

There isn’t an implementation for things like: the 3rd Friday of March, June,
September and December (futures/options expirations), but there is a possibility
to implement rules by passing:

• allow=callable where the callable accepts datetime.date instance. Notice


this is not a datetime.datetime instance, because the allow callable is
only meant to decide if a given day is suitable for timers or not.

To implement something like the rule laid out above:

class FutOpExp(object):
def __init__(self):
self.fridays = 0
self.curmonth = -1

def __call__(self, d):


_, _, isowkday = d.isocalendar()

137
backtrader’s documentation Version-1.9.58.122

if d.month != self.curmonth:
self.curmonth = d.month
self.fridays = 0

# Mon=1 ... Sun=7


if isowkday == 5 and self.curmonth in [3, 6, 9, 12]:
self.fridays += 1

if self.friday == 3: # 3rd Friday


return True # timer allowed

return False # timer disallowed

And one would pass allow=FutOpeExp() to the creation of the timer

This would allow a timer to kick in on the 3rd Friday of those months and
may be close positions before the futures expire.

Parameters to add_timer

• when: can be
o datetime.time instance (see below tzdata)
o bt.timer.SESSION_START to reference a session start
o bt.timer.SESSION_END to reference a session end

• offset which must be a datetime.timedelta instance

Used to offset the value when. It has a meaningful use in combination with
SESSION_START and SESSION_END, to indicated things like a timer being
called 15 minutes after the session start.

• repeat which must be a datetime.timedelta instance

Indicates if after a 1st call, further calls will be scheduled within the
same session at the scheduled repeat delta

Once the timer goes over the end of the session it is reset to the original
value for when

• weekdays: a sorted iterable with integers indicating on which days (iso


codes, Monday is 1, Sunday is 7) the timers can be actually invoked

If not specified, the timer will be active on all days

138
backtrader’s documentation Version-1.9.58.122

• weekcarry (default: False). If True and the weekday was not seen (ex:
trading holiday), the timer will be executed on the next day (even if in a
new week)
• monthdays: a sorted iterable with integers indicating on which days of the
month a timer has to be executed. For example always on day 15 of the month

If not specified, the timer will be active on all days

• monthcarry (default: True). If the day was not seen (weekend, trading
holiday), the timer will be executed on the next available day.
• allow (default: None). A callback which receives a datetime.date` instance
and returns True if the date is allowed for timers or else returns False
• tzdata which can be either None (default), a pytz instance or a data feed
instance.

None: when is interpreted at face value (which translates to handling it as


if it where UTC even if it’s not)

pytz instance: when will be interpreted as being specified in the local


time specified by the timezone instance.

data feed instance: when will be interpreted as being specified in the


local time specified by the tz parameter of the data feed instance.

Note: If when is either SESSION_START or

SESSION_END and tzdata is None, the 1st data feed in the system (aka
self.data0) will be used as the reference to find out the session times.

• strats (default: False) call also the notify_timer of strategies


• cheat (default False) if True the timer will be called before the broker
has a chance to evaluate the orders. This opens the chance to issue orders
based on opening price for example right before the session starts
• *args: any extra args will be passed to notify_timer
• **kwargs: any extra kwargs will be passed to notify_timer

Sample usage scheduled.py

$ ./scheduled.py --help
usage: scheduled.py [-h] [--data0 DATA0] [--fromdate FROMDATE]
[--todate TODATE] [--cerebro kwargs] [--broker kwargs]
[--sizer kwargs] [--strat kwargs] [--plot [kwargs]]

Sample Skeleton
139
backtrader’s documentation Version-1.9.58.122

optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to read in (default:
../../datas/2005-2006-day-001.txt)
--fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--cerebro kwargs kwargs in key=value format (default: )
--broker kwargs kwargs in key=value format (default: )
--sizer kwargs kwargs in key=value format (default: )
--strat kwargs kwargs in key=value format (default: )
--plot [kwargs] kwargs in key=value format (default: )

Sample usage scheduled-min.py

$ ./scheduled-min.py --help
usage: scheduled-min.py [-h] [--data0 DATA0] [--fromdate FROMDATE]
[--todate TODATE] [--cerebro kwargs] [--broker kwargs]
[--sizer kwargs] [--strat kwargs] [--plot [kwargs]]

Timer Test Intraday

optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to read in (default: ../../datas/2006-min-005.txt)
--fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--cerebro kwargs kwargs in key=value format (default: )
--broker kwargs kwargs in key=value format (default: )
--sizer kwargs kwargs in key=value format (default: )
--strat kwargs kwargs in key=value format (default: )
--plot [kwargs] kwargs in key=value format (default: )

Sample source scheduled.py

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
import datetime

import backtrader as bt

140
backtrader’s documentation Version-1.9.58.122

class St(bt.Strategy):
params = dict(
when=bt.timer.SESSION_START,
timer=True,
cheat=False,
offset=datetime.timedelta(),
repeat=datetime.timedelta(),
weekdays=[],
)

def __init__(self):
bt.ind.SMA()
if self.p.timer:
self.add_timer(
when=self.p.when,
offset=self.p.offset,
repeat=self.p.repeat,
weekdays=self.p.weekdays,
)
if self.p.cheat:
self.add_timer(
when=self.p.when,
offset=self.p.offset,
repeat=self.p.repeat,
cheat=True,
)

self.order = None

def prenext(self):
self.next()

def next(self):
_, isowk, isowkday = self.datetime.date().isocalendar()
txt = '{}, {}, Week {}, Day {}, O {}, H {}, L {}, C {}'.format(
len(self), self.datetime.datetime(),
isowk, isowkday,
self.data.open[0], self.data.high[0],
self.data.low[0], self.data.close[0])

print(txt)

def notify_timer(self, timer, when, *args, **kwargs):


141
backtrader’s documentation Version-1.9.58.122

print('strategy notify_timer with tid {}, when {} cheat {}'.


format(timer.p.tid, when, timer.p.cheat))

if self.order is None and timer.p.cheat:


print('-- {} Create buy order'.format(self.data.datetime.date()))
self.order = self.buy()

def notify_order(self, order):


if order.status == order.Completed:
print('-- {} Buy Exec @ {}'.format(
self.data.datetime.date(), order.executed.price))

def runstrat(args=None):
args = parse_args(args)

cerebro = bt.Cerebro()

# Data feed kwargs


kwargs = dict(
timeframe=bt.TimeFrame.Days,
compression=1,
sessionstart=datetime.time(9, 0),
sessionend=datetime.time(17, 30),
)

# Parse from/to-date
dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
if a:
strpfmt = dtfmt + tmfmt * ('T' in a)
kwargs[d] = datetime.datetime.strptime(a, strpfmt)

# Data feed
data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)
cerebro.adddata(data0)

# Broker
cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))

# Sizer
cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))

# Strategy
142
backtrader’s documentation Version-1.9.58.122

cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))

# Execute
cerebro.run(**eval('dict(' + args.cerebro + ')'))

if args.plot: # Plot if requested to


cerebro.plot(**eval('dict(' + args.plot + ')'))

def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=(
'Sample Skeleton'
)
)

parser.add_argument('--data0', default='../../datas/2005-2006-day-001.txt',
required=False, help='Data to read in')

# Defaults for dates


parser.add_argument('--fromdate', required=False, default='',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

parser.add_argument('--todate', required=False, default='',


help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

parser.add_argument('--cerebro', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--broker', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--sizer', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--strat', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--plot', required=False, default='',


nargs='?', const='{}',
metavar='kwargs', help='kwargs in key=value format')

return parser.parse_args(pargs)
143
backtrader’s documentation Version-1.9.58.122

if __name__ == '__main__':
runstrat()

Sample source scheduled-min.py

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
import datetime

import backtrader as bt

class St(bt.Strategy):
params = dict(
when=bt.timer.SESSION_START,
timer=True,
cheat=False,
offset=datetime.timedelta(),
repeat=datetime.timedelta(),
weekdays=[],
weekcarry=False,
monthdays=[],
monthcarry=True,
)

def __init__(self):
bt.ind.SMA()
if self.p.timer:
self.add_timer(
when=self.p.when,
offset=self.p.offset,
repeat=self.p.repeat,
weekdays=self.p.weekdays,
weekcarry=self.p.weekcarry,
monthdays=self.p.monthdays,
monthcarry=self.p.monthcarry,
# tzdata=self.data0,
)
if self.p.cheat:

144
backtrader’s documentation Version-1.9.58.122

self.add_timer(
when=self.p.when,
offset=self.p.offset,
repeat=self.p.repeat,
weekdays=self.p.weekdays,
weekcarry=self.p.weekcarry,
monthdays=self.p.monthdays,
monthcarry=self.p.monthcarry,
# tzdata=self.data0,
cheat=True,
)

self.order = None

def prenext(self):
self.next()

def next(self):
_, isowk, isowkday = self.datetime.date().isocalendar()
txt = '{}, {}, Week {}, Day {}, O {}, H {}, L {}, C {}'.format(
len(self), self.datetime.datetime(),
isowk, isowkday,
self.data.open[0], self.data.high[0],
self.data.low[0], self.data.close[0])

print(txt)

def notify_timer(self, timer, when, *args, **kwargs):


print('strategy notify_timer with tid {}, when {} cheat {}'.
format(timer.p.tid, when, timer.p.cheat))

if self.order is None and timer.params.cheat:


print('-- {} Create buy order'.format(
self.data.datetime.datetime()))
self.order = self.buy()

def notify_order(self, order):


if order.status == order.Completed:
print('-- {} Buy Exec @ {}'.format(
self.data.datetime.datetime(), order.executed.price))

def runstrat(args=None):
args = parse_args(args)
145
backtrader’s documentation Version-1.9.58.122

cerebro = bt.Cerebro()

# Data feed kwargs


kwargs = dict(
timeframe=bt.TimeFrame.Minutes,
compression=5,
sessionstart=datetime.time(9, 0),
sessionend=datetime.time(17, 30),
)

# Parse from/to-date
dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
if a:
strpfmt = dtfmt + tmfmt * ('T' in a)
kwargs[d] = datetime.datetime.strptime(a, strpfmt)

# Data feed
data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)
cerebro.adddata(data0)

# Broker
cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))

# Sizer
cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))

# Strategy
cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))

# Execute
cerebro.run(**eval('dict(' + args.cerebro + ')'))

if args.plot: # Plot if requested to


cerebro.plot(**eval('dict(' + args.plot + ')'))

def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=(
'Timer Test Intraday'
)
)
146
backtrader’s documentation Version-1.9.58.122

parser.add_argument('--data0', default='../../datas/2006-min-005.txt',
required=False, help='Data to read in')

# Defaults for dates


parser.add_argument('--fromdate', required=False, default='',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

parser.add_argument('--todate', required=False, default='',


help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

parser.add_argument('--cerebro', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--broker', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--sizer', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--strat', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--plot', required=False, default='',


nargs='?', const='{}',
metavar='kwargs', help='kwargs in key=value format')

return parser.parse_args(pargs)

if __name__ == '__main__':
runstrat()

Target Orders

Until version 1.8.10.96 smart staking was possible with backtrader over the
Strategy methods: buy and sell. It was all about adding a Sizer to the equation
which is responsible for the size of the stake.

What a Sizer cannot do is decide if the operation has to be a buy or a sell. And
that means that a new concept is needed in which a small intelligence layer is
added to make such decision.

147
backtrader’s documentation Version-1.9.58.122

Here is where the family of order_target_xxx methods in the Strategy come into
play. Inspired by the ones in zipline, the methods offer the chance to simply
specify the final target, be the target:

• size -> amount of shares, contracts in the portfolio of a specific asset


• value -> value in monetary units of the asset in the portfolio
• percent -> percentage (from current portfolio) value of the asset in the current portfolio

Note

The reference for the methods can be found in Strategy. The summary is that the
methods use the same signature as buy and sell except for the parameter size
which is replaced by the parameter target

In this case it is all about specifying the final target and the method decides
if an operation will be a buy or a sell. The same logic applies to the 3 methods.
Let’s tart with order_target_size

• If the target is greater than the position a buy is issued, with the
difference target - position_size

Examples:

o Pos: 0, target: 7 -> buy(size=7 - 0) -> buy(size=7)


o Pos: 3, target: 7 -> buy(size=7 - 3) -> buy(size=4)
o Pos: -3, target: 7 -> buy(size=7 - -3) -> buy(size=10)
o Pos: -3, target: -2 -> buy(size=-2 - -3) -> buy(size=1)
• If the target is smaller than the position a sell is issued with the
difference position_size - target

Examples:

o Pos: 0, target: -7 -> sell(size=0 - -7) -> sell(size=7)


o Pos: 3, target: -7 -> sell(size=3 - -7) -> sell(size=10)
o Pos: -3, target: -7 -> sell(size=-3 - -7) -> sell(size=4)
o Pos: 3, target: 2 -> sell(size=3 - 2) -> sell(size=1)

When targetting a value with order_target_value, the current value of the asset
in the portfolio and the position size are both taken into account to decide what
the final underlying operation will be. The reasoning:

• If position size is negative (short) and the target value has to be greater than the current value, this
means: sell more

As such the logic works as follows:

148
backtrader’s documentation Version-1.9.58.122

• If target > value and size >=0 -> buy


• If target > value and size < 0 -> sell
• If target < value and size >= 0 -> sell
• If target < value and size* < 0 -> buy

The logic for order_target_percent is the same as that of order_target_value.


This method simply takes into account the current total value of the portfolio to
determine the target value for the asset.

The Sample

backtrader tries to have a sample for each new functionality and this is no
exception. No bells and whistles, just something to test the results are as
expected. This one is under the order_target directory in the samples.

The logic in the sample is rather dumb and only meaant for testing:

• During odd months (Jan, Mar, ...), use the day as target (in the case of
order_target_value multiplying the day by 1000)

This mimics an increasing target

• During even months (Feb, Apr, ...) use 31 - day as the target

This mimics an decreasing target

order_target_size

Let’s see what happens in Jan and Feb.

$ ./order_target.py --target-size -- plot


0001 - 2005-01-03 - Position Size: 00 - Value 1000000.00
0001 - 2005-01-03 - Order Target Size: 03
0002 - 2005-01-04 - Position Size: 03 - Value 999994.39
0002 - 2005-01-04 - Order Target Size: 04
0003 - 2005-01-05 - Position Size: 04 - Value 999992.48
0003 - 2005-01-05 - Order Target Size: 05
0004 - 2005-01-06 - Position Size: 05 - Value 999988.79
...
0020 - 2005-01-31 - Position Size: 28 - Value 999968.70
0020 - 2005-01-31 - Order Target Size: 31
0021 - 2005-02-01 - Position Size: 31 - Value 999954.68
0021 - 2005-02-01 - Order Target Size: 30

149
backtrader’s documentation Version-1.9.58.122

0022 - 2005-02-02 - Position Size: 30 - Value 999979.65


0022 - 2005-02-02 - Order Target Size: 29
0023 - 2005-02-03 - Position Size: 29 - Value 999966.33
0023 - 2005-02-03 - Order Target Size: 28
...

In Jan the target starts at 3 with the 1st trading day of the year and increases.
And the position size moves initially from 0 to 3 and then in increments of 1.

Finishing Jan the last order_target is for 31 and that position size is reported
when entering the 1st day of Feb, when the new target side is requested to be 30
and goes changing along with the position in decrements of ´1`.

order_target_value

A similar behavior is expected from target values

150
backtrader’s documentation Version-1.9.58.122

$ ./order_target.py --target-value --plot


0001 - 2005-01-03 - Position Size: 00 - Value 1000000.00
0001 - 2005-01-03 - data value 0.00
0001 - 2005-01-03 - Order Target Value: 3000.00
0002 - 2005-01-04 - Position Size: 78 - Value 999854.14
0002 - 2005-01-04 - data value 2853.24
0002 - 2005-01-04 - Order Target Value: 4000.00
0003 - 2005-01-05 - Position Size: 109 - Value 999801.68
0003 - 2005-01-05 - data value 3938.17
0003 - 2005-01-05 - Order Target Value: 5000.00
0004 - 2005-01-06 - Position Size: 138 - Value 999699.57
...
0020 - 2005-01-31 - Position Size: 808 - Value 999206.37
0020 - 2005-01-31 - data value 28449.68
0020 - 2005-01-31 - Order Target Value: 31000.00
0021 - 2005-02-01 - Position Size: 880 - Value 998807.33
0021 - 2005-02-01 - data value 30580.00
0021 - 2005-02-01 - Order Target Value: 30000.00
0022 - 2005-02-02 - Position Size: 864 - Value 999510.21
0022 - 2005-02-02 - data value 30706.56
0022 - 2005-02-02 - Order Target Value: 29000.00
0023 - 2005-02-03 - Position Size: 816 - Value 999130.05
0023 - 2005-02-03 - data value 28633.44
0023 - 2005-02-03 - Order Target Value: 28000.00
...

There is an extra line of information telling what the actual data value (in the
portfolio) is. This helps in finding out if the target value has been reachec.

The initial target is 3000.0 and the reported initial value is 2853.24. The
question here is whether this is close enough. And the answer is Yes

• The sample uses a Market order at the end of a daily bar and the last available price to calculate a target
size which meets the target value
• The execution uses then the open price of the next day and this is unlikely to be the previous close

Doing it in any other way would mean one is cheating him/herfself.

The next target value and final value are much closer: 4000 and 3938.17.

When changing into Feb the target value starts decreasing from 31000 to 30000 and
29000`. So does the *data value* with from ``30580.00 to 30706.56 and then to
28633.44. Wait:

• 30580 -> 30706.56 is a positive change


151
backtrader’s documentation Version-1.9.58.122

Indeed. In this case the calculated size for the target value met an
opening price which bumped the value to 30706.56

How this effect can be avoided:

• The sample uses a Market type execution for the orders and this effect
cannot be avoided
• The methods order_target_xxx allow specifying the execution type and price.

One could specify Limit as the execution order and let the price be the
close price (chosen by the method if nothing else be provided) or even
provide specific pricing

order_target_percent

In this case it is simply a percentage of the current portfolio value.

152
backtrader’s documentation Version-1.9.58.122

$ ./order_target.py --target-percent --plot


0001 - 2005-01-03 - Position Size: 00 - Value 1000000.00
0001 - 2005-01-03 - data percent 0.00
0001 - 2005-01-03 - Order Target Percent: 0.03
0002 - 2005-01-04 - Position Size: 785 - Value 998532.05
0002 - 2005-01-04 - data percent 0.03
0002 - 2005-01-04 - Order Target Percent: 0.04
0003 - 2005-01-05 - Position Size: 1091 - Value 998007.44
0003 - 2005-01-05 - data percent 0.04
0003 - 2005-01-05 - Order Target Percent: 0.05
0004 - 2005-01-06 - Position Size: 1381 - Value 996985.64
...
0020 - 2005-01-31 - Position Size: 7985 - Value 991966.28
0020 - 2005-01-31 - data percent 0.28
0020 - 2005-01-31 - Order Target Percent: 0.31
0021 - 2005-02-01 - Position Size: 8733 - Value 988008.94
0021 - 2005-02-01 - data percent 0.31
0021 - 2005-02-01 - Order Target Percent: 0.30
0022 - 2005-02-02 - Position Size: 8530 - Value 995005.45
0022 - 2005-02-02 - data percent 0.30
0022 - 2005-02-02 - Order Target Percent: 0.29
0023 - 2005-02-03 - Position Size: 8120 - Value 991240.75
0023 - 2005-02-03 - data percent 0.29
0023 - 2005-02-03 - Order Target Percent: 0.28
...

And the information has been changed to see the % the data represents in the
portfolio.

153
backtrader’s documentation Version-1.9.58.122

Sample Usage

$ ./order_target.py --help
usage: order_target.py [-h] [--data DATA] [--fromdate FROMDATE]
[--todate TODATE] [--cash CASH]
(--target-size | --target-value | --target-percent)
[--plot [kwargs]]

Sample for Order Target

optional arguments:
-h, --help show this help message and exit
--data DATA Specific data to be read in (default:
../../datas/yhoo-1996-2015.txt)
--fromdate FROMDATE Starting date in YYYY-MM-DD format (default:
2005-01-01)

154
backtrader’s documentation Version-1.9.58.122

--todate TODATE Ending date in YYYY-MM-DD format (default: 2006-12-31)


--cash CASH Ending date in YYYY-MM-DD format (default: 1000000)
--target-size Use order_target_size (default: False)
--target-value Use order_target_value (default: False)
--target-percent Use order_target_percent (default: False)
--plot [kwargs], -p [kwargs]
Plot the read data applying any kwargs passed For
example: --plot style="candle" (to plot candles)
(default: None)

Sample Code

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
from datetime import datetime

import backtrader as bt

class TheStrategy(bt.Strategy):
'''
This strategy is loosely based on some of the examples from the Van
K. Tharp book: *Trade Your Way To Financial Freedom*. The logic:

- Enter the market if:


- The MACD.macd line crosses the MACD.signal line to the upside
- The Simple Moving Average has a negative direction in the last x
periods (actual value below value x periods ago)

- Set a stop price x times the ATR value away from the close

- If in the market:

- Check if the current close has gone below the stop price. If yes,
exit.
- If not, update the stop price if the new stop price would be higher
than the current
'''

params = (
('use_target_size', False),

155
backtrader’s documentation Version-1.9.58.122

('use_target_value', False),
('use_target_percent', False),
)

def notify_order(self, order):


if order.status == order.Completed:
pass

if not order.alive():
self.order = None # indicate no order is pending

def start(self):
self.order = None # sentinel to avoid operrations on pending order

def next(self):
dt = self.data.datetime.date()

portfolio_value = self.broker.get_value()
print('%04d - %s - Position Size: %02d - Value %.2f' %
(len(self), dt.isoformat(), self.position.size, portfolio_value))

data_value = self.broker.get_value([self.data])

if self.p.use_target_value:
print('%04d - %s - data value %.2f' %
(len(self), dt.isoformat(), data_value))

elif self.p.use_target_percent:
port_perc = data_value / portfolio_value
print('%04d - %s - data percent %.2f' %
(len(self), dt.isoformat(), port_perc))

if self.order:
return # pending order execution

size = dt.day
if (dt.month % 2) == 0:
size = 31 - size

if self.p.use_target_size:
target = size
print('%04d - %s - Order Target Size: %02d' %
(len(self), dt.isoformat(), size))

156
backtrader’s documentation Version-1.9.58.122

self.order = self.order_target_size(target=size)

elif self.p.use_target_value:
value = size * 1000

print('%04d - %s - Order Target Value: %.2f' %


(len(self), dt.isoformat(), value))

self.order = self.order_target_value(target=value)

elif self.p.use_target_percent:
percent = size / 100.0

print('%04d - %s - Order Target Percent: %.2f' %


(len(self), dt.isoformat(), percent))

self.order = self.order_target_percent(target=percent)

def runstrat(args=None):
args = parse_args(args)

cerebro = bt.Cerebro()
cerebro.broker.setcash(args.cash)

dkwargs = dict()
if args.fromdate is not None:
dkwargs['fromdate'] = datetime.strptime(args.fromdate, '%Y-%m-%d')
if args.todate is not None:
dkwargs['todate'] = datetime.strptime(args.todate, '%Y-%m-%d')

# data
data = bt.feeds.YahooFinanceCSVData(dataname=args.data, **dkwargs)
cerebro.adddata(data)

# strategy
cerebro.addstrategy(TheStrategy,
use_target_size=args.target_size,
use_target_value=args.target_value,
use_target_percent=args.target_percent)

cerebro.run()

if args.plot:
157
backtrader’s documentation Version-1.9.58.122

pkwargs = dict(style='bar')
if args.plot is not True: # evals to True but is not True
npkwargs = eval('dict(' + args.plot + ')') # args were passed
pkwargs.update(npkwargs)

cerebro.plot(**pkwargs)

def parse_args(pargs=None):

parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Sample for Order Target')

parser.add_argument('--data', required=False,
default='../../datas/yhoo-1996-2015.txt',
help='Specific data to be read in')

parser.add_argument('--fromdate', required=False,
default='2005-01-01',
help='Starting date in YYYY-MM-DD format')

parser.add_argument('--todate', required=False,
default='2006-12-31',
help='Ending date in YYYY-MM-DD format')

parser.add_argument('--cash', required=False, action='store',


type=float, default=1000000,
help='Ending date in YYYY-MM-DD format')

pgroup = parser.add_mutually_exclusive_group(required=True)

pgroup.add_argument('--target-size', required=False, action='store_true',


help=('Use order_target_size'))

pgroup.add_argument('--target-value', required=False, action='store_true',


help=('Use order_target_value'))

pgroup.add_argument('--target-percent', required=False,
action='store_true',
help=('Use order_target_percent'))

# Plot options
parser.add_argument('--plot', '-p', nargs='?', required=False,
158
backtrader’s documentation Version-1.9.58.122

metavar='kwargs', const=True,
help=('Plot the read data applying any kwargs passed\n'
'\n'
'For example:\n'
'\n'
' --plot style="candle" (to plot candles)\n'))

if pargs is not None:


return parser.parse_args(pargs)

return parser.parse_args()

if __name__ == '__main__':
runstrat()

Strategy with Signals

Operating backtrader is also possible without having to write a Strategy.


Although this is the preferred way, due to the object hierarchy which makes up
the machinery, using Signals is also possible.

Quick summary:

• Instead of writing a Strategy class, instantiating Indicators, writing the buy/sell logic ...
• The end user add Signals (indicators anyhow) and the rest is done in the background

Quick example:

import backtrader as bt

data = bt.feeds.OneOfTheFeeds(dataname='mydataname')
cerebro.adddata(data)

cerebro.add_signal(bt.SIGNAL_LONGSHORT, MySignal)
cerebro.run()

Et voilá!.

Of course the Signal itself is missing. Let’s define a very dumb Signal which
yields:

• Long indication if the close price is above a Simple Moving Average


• Short indication if the close price is below a Simple Moving Average

159
backtrader’s documentation Version-1.9.58.122

The definition:

class MySignal(bt.Indicator):
lines = ('signal',)
params = (('period', 30),)

def __init__(self):
self.lines.signal = self.data - bt.indicators.SMA(period=self.p.period)

And now it is really done. When run is executed Cerebro will take care of
instantiating a special Strategy instance which knows what to do with the
Signals.

Initial FAQ

• How is the volume of buy/sell operations determined?

A cerebro instance adds automatically a FixedSize sizer to strategies. The


end user can change the sizer to alter the policy with cerebro.addsizer

• How are orders executed?

The execution type is Market and the validity is Good Until Canceled

Signals technicalities

From a technical and theoretical point of view can be as described:

• A callable that returns another object when called (only once)

This is in most cases the instantiation of a class, but must not be

• Supports the __getitem__ interface. The only requested key/index will be 0

From a practical point of view and looking at the example above a Signal is:

• A lines object from the backtrader ecosystem, mostly an Indicator

This helps when using other Indicators like when in the example the Simple
Moving Average is used.

160
backtrader’s documentation Version-1.9.58.122

Signals indications

The signals delivers indications when queried with signal[0] and the meaning is:

• > 0 -> long indication


• < 0 -> short indication
• == 0 -> No indication

The example does simple arithmetic with self.data - SMA and:

• Issues a long indication when the data is above the SMA


• Issues a short indication when the data is below the SMA

Note

When no specific price field is indicated for the data, the close price is the
reference price is.

Signals Types

The constants indicated below as seen in the example above, are directly
available from the main backtrader module as in:

import backtrader as bt

bt.SIGNAL_LONG

There are 5 types of Signals, broken in 2 groups.

Main Group:

• LONGSHORT: both long and short indications from this signal are taken
• LONG:
o long indications are taken to go long
o short indications are taken to close the long position. But:
If a LONGEXIT (see below) signal is in the system it will be used to exit the long
If a SHORT signal is available and no LONGEXIT is available , it will be used to close a long
before opening a short
• SHORT:
o short indications are taken to go short
o long indications are taken to close the short position. But:
If a SHORTEXIT (see below) signal is in the system it will be used to exit the short

161
backtrader’s documentation Version-1.9.58.122

If a LONG signal is available and no SHORTEXIT is available , it will be used to close a


short before opening a long

Exit Group:

This 2 signals are meant to override others and provide criteria for exitins a
long / short position

• LONGEXIT: short indications are taken to exit long positions


• SHORTEXIT: long indications are taken to exit short positions

Accumulation and Order Concurrency

The sample Signal shown above will issue long and short indications on a constant
basis, because it simply substracts the SMA value from the close price and this
will always be either > 0 and < 0 ( 0 is mathematically possible, but unlikely to
really happen)

This would lead to a continuous generation of orders that would produce 2


situations:

• Accumulation: even if already in the market, the signals would produce new orders which would increase
the possition in the market
• Concurrency: new orders would be generated without waiting for the execution of other orders

To avoid this the default behavior is:

• To Not Accumulate
• To Not allow Concurrency

Should any of these two behaviors be wished, this can be controlled via cerebro
with:

• cerebro.signal_accumulate(True) (or False to re-disable it)


• cerebro.signal_concurrency(True) (or False to re-disable it)

The sample

The backtrader sources contain a sample to test the functionality.

Main signal to be used.

class SMACloseSignal(bt.Indicator):
lines = ('signal',)
162
backtrader’s documentation Version-1.9.58.122

params = (('period', 30),)

def __init__(self):
self.lines.signal = self.data - bt.indicators.SMA(period=self.p.period)

And the Exit Signal in case the option is specified.

class SMAExitSignal(bt.Indicator):
lines = ('signal',)
params = (('p1', 5), ('p2', 30),)

def __init__(self):
sma1 = bt.indicators.SMA(period=self.p.p1)
sma2 = bt.indicators.SMA(period=self.p.p2)
self.lines.signal = sma1 - sma2

First run: long and short

$ ./signals-strategy.py --plot --signal longshort

The output

163
backtrader’s documentation Version-1.9.58.122

To notice:

• The Signal is plotted. This is normal given it is simply an indicator and the plotting rules for it apply
• The strategy is really long and short. This can be seen because the cash level never goes back to be the
value level
• Side note: even for a dumb idea ... (and without commission) the strategy hasn’t lost money ...

Second run: long only

$ ./signals-strategy.py --plot --signal longonly

The output

164
backtrader’s documentation Version-1.9.58.122

To notice:

• Here the cash level goes back to be the value level after each sell, which means the strategy is out of the
market
• Side note: Again no money has been lost ...

Third run: short only

$ ./signals-strategy.py --plot --signal shortonly

The output

165
backtrader’s documentation Version-1.9.58.122

To notice:

• The 1st operation is a sell as expected and takes place later than the 1st operation in the 2 examples
above. Not until the close is below the SMA and the simple substraction yields a minus
• Here the cash level goes back to be the value level after each buy, which means the strategy is out of the
market
• Side note: Finally the system loses money

Fourth run: long + longexit

$ ./signals-strategy.py --plot --signal longonly --exitsignal longexit

The output

166
backtrader’s documentation Version-1.9.58.122

To notice:

• Many of the trades are the same, but some are interrupted earlier because the fast moving average in the
exit signal crosses the slow moving average to the downside
• The system shows its longonly property with the cash becoming the value at the end of each trade
• Side note: Again money is made ... even with some modified trades

Usage

$ ./signals-strategy.py --help
usage: signals-strategy.py [-h] [--data DATA] [--fromdate FROMDATE]
[--todate TODATE] [--cash CASH]
[--smaperiod SMAPERIOD] [--exitperiod EXITPERIOD]
[--signal {longshort,longonly,shortonly}]
[--exitsignal {longexit,shortexit}]
[--plot [kwargs]]

167
backtrader’s documentation Version-1.9.58.122

Sample for Signal concepts

optional arguments:
-h, --help show this help message and exit
--data DATA Specific data to be read in (default:
../../datas/2005-2006-day-001.txt)
--fromdate FROMDATE Starting date in YYYY-MM-DD format (default: None)
--todate TODATE Ending date in YYYY-MM-DD format (default: None)
--cash CASH Cash to start with (default: 50000)
--smaperiod SMAPERIOD
Period for the moving average (default: 30)
--exitperiod EXITPERIOD
Period for the exit control SMA (default: 5)
--signal {longshort,longonly,shortonly}
Signal type to use for the main signal (default:
longshort)
--exitsignal {longexit,shortexit}
Signal type to use for the exit signal (default: None)
--plot [kwargs], -p [kwargs]
Plot the read data applying any kwargs passed For
example: --plot style="candle" (to plot candles)
(default: None)

The code

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
import collections
import datetime

import backtrader as bt

MAINSIGNALS = collections.OrderedDict(
(('longshort', bt.SIGNAL_LONGSHORT),
('longonly', bt.SIGNAL_LONG),
('shortonly', bt.SIGNAL_SHORT),)
)

EXITSIGNALS = {
'longexit': bt.SIGNAL_LONGEXIT,

168
backtrader’s documentation Version-1.9.58.122

'shortexit': bt.SIGNAL_LONGEXIT,
}

class SMACloseSignal(bt.Indicator):
lines = ('signal',)
params = (('period', 30),)

def __init__(self):
self.lines.signal = self.data - bt.indicators.SMA(period=self.p.period)

class SMAExitSignal(bt.Indicator):
lines = ('signal',)
params = (('p1', 5), ('p2', 30),)

def __init__(self):
sma1 = bt.indicators.SMA(period=self.p.p1)
sma2 = bt.indicators.SMA(period=self.p.p2)
self.lines.signal = sma1 - sma2

def runstrat(args=None):
args = parse_args(args)

cerebro = bt.Cerebro()
cerebro.broker.set_cash(args.cash)

dkwargs = dict()
if args.fromdate is not None:
fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
dkwargs['fromdate'] = fromdate

if args.todate is not None:


todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
dkwargs['todate'] = todate

# if dataset is None, args.data has been given


data = bt.feeds.BacktraderCSVData(dataname=args.data, **dkwargs)
cerebro.adddata(data)

cerebro.add_signal(MAINSIGNALS[args.signal],
SMACloseSignal, period=args.smaperiod)

169
backtrader’s documentation Version-1.9.58.122

if args.exitsignal is not None:


cerebro.add_signal(EXITSIGNALS[args.exitsignal],
SMAExitSignal,
p1=args.exitperiod,
p2=args.smaperiod)

cerebro.run()
if args.plot:
pkwargs = dict(style='bar')
if args.plot is not True: # evals to True but is not True
npkwargs = eval('dict(' + args.plot + ')') # args were passed
pkwargs.update(npkwargs)

cerebro.plot(**pkwargs)

def parse_args(pargs=None):

parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Sample for Signal concepts')

parser.add_argument('--data', required=False,
default='../../datas/2005-2006-day-001.txt',
help='Specific data to be read in')

parser.add_argument('--fromdate', required=False, default=None,


help='Starting date in YYYY-MM-DD format')

parser.add_argument('--todate', required=False, default=None,


help='Ending date in YYYY-MM-DD format')

parser.add_argument('--cash', required=False, action='store',


type=float, default=50000,
help=('Cash to start with'))

parser.add_argument('--smaperiod', required=False, action='store',


type=int, default=30,
help=('Period for the moving average'))

parser.add_argument('--exitperiod', required=False, action='store',


type=int, default=5,
help=('Period for the exit control SMA'))

170
backtrader’s documentation Version-1.9.58.122

parser.add_argument('--signal', required=False, action='store',


default=MAINSIGNALS.keys()[0], choices=MAINSIGNALS,
help=('Signal type to use for the main signal'))

parser.add_argument('--exitsignal', required=False, action='store',


default=None, choices=EXITSIGNALS,
help=('Signal type to use for the exit signal'))

# Plot options
parser.add_argument('--plot', '-p', nargs='?', required=False,
metavar='kwargs', const=True,
help=('Plot the read data applying any kwargs passed\n'
'\n'
'For example:\n'
'\n'
' --plot style="candle" (to plot candles)\n'))

if pargs is not None:


return parser.parse_args(pargs)

return parser.parse_args()

if __name__ == '__main__':
runstrat()

Broker

Reference

class backtrader.brokers.BackBroker

Broker Simulator

The simulation supports different order types, checking a submitted order


cash requirements against current cash, keeping track of cash and value for
each iteration of cerebro and keeping the current position on different
datas.

cash is adjusted on each iteration for instruments like futures for


which a price change implies in real brokers the addition/substracion of cash.

Supported order types:


171
backtrader’s documentation Version-1.9.58.122

• Market: to be executed with the 1st tick of the next bar (namely the open price)
• Close: meant for intraday in which the order is executed with the closing price of the last bar of
the session
• Limit: executes if the given limit price is seen during the session
• Stop: executes a Market order if the given stop price is seen
• StopLimit: sets a Limit order in motion if the given stop price is seen

Because the broker is instantiated by Cerebro and there should be (mostly)


no reason to replace the broker, the params are not controlled by the user
for the instance. To change this there are two options:

1. Manually create an instance of this class with the desired params and use cerebro.broker =
instance to set the instance as the broker for the run execution
2. Use the set_xxx to set the value using cerebro.broker.set_xxx where `xxx stands for the name of
the parameter to set

Note

cerebro.broker is a property supported by the getbroker and setbroker


methods of Cerebro

Params:

• cash (default: 10000): starting cash


• commission (default: CommInfoBase(percabs=True)) base commission
scheme which applies to all assets
• checksubmit (default: True) check margin/cash before accepting an
order into the system
• eosbar (default: False): With intraday bars consider a bar with the
same time as the end of session to be the end of the session. This is
not usually the case, because some bars (final auction) are produced
by many exchanges for many products for a couple of minutes after the
end of the session
• eosbar (default: False): With intraday bars consider a bar with the
same time as the end of session to be the end of the session. This is
not usually the case, because some bars (final auction) are produced
by many exchanges for many products for a couple of minutes after the
end of the session
• filler (default: None)

A callable with signature: callable(order, price, ago)

o order: obviously the order in execution. This provides access


to the data (and with it the ohlc and volume values), the

172
backtrader’s documentation Version-1.9.58.122

execution type, remaining size (order.executed.remsize) and


others.

Please check the Order documentation and reference for things


available inside an Order instance

o price the price at which the order is going to be executed in


the ago bar
o ago: index meant to be used with order.data for the extraction
of the ohlc and volume prices. In most cases this will be 0 but
on a corner case for Close orders, this will be -1.

In order to get the bar volume (for example) do: volume =


order.data.voluume[ago]

The callable must return the executed size (a value >= 0)

The callable may of course be an object with __call__ matching the


aforementioned signature

With the default None orders will be completely executed in a single


shot

• slip_perc (default: 0.0) Percentage in absolute termns (and positive)


that should be used to slip prices up/down for buy/sell orders

Note:

o 0.01 is 1%
o 0.001 is 0.1%
• slip_fixed (default: 0.0) Percentage in units (and positive) that
should be used to slip prices up/down for buy/sell orders

Note: if slip_perc is non zero, it takes precendence over this.

• slip_open (default: False) whether to slip prices for order execution


which would specifically used the opening price of the next bar. An
example would be Market order which is executed with the next
available tick, i.e: the opening price of the bar.

This also applies to some of the other executions, because the logic
tries to detect if the opening price would match the requested
price/execution type when moving to a new bar.

• slip_match (default: True)

173
backtrader’s documentation Version-1.9.58.122

If True the broker will offer a match by capping slippage at high/low


prices in case they would be exceeded.

If False the broker will not match the order with the current prices
and will try execution during the next iteration

• slip_limit (default: True)

Limit orders, given the exact match price requested, will be matched
even if slip_match is False.

This option controls that behavior.

If True, then Limit orders will be matched by capping prices to the


limit / high/low prices

If False and slippage exceeds the cap, then there will be no match

• slip_out (default: False)

Provide slippage even if the price falls outside the high - low
range.

• coc (default: False)

Cheat-On-Close Setting this to True with set_coc enables

matching a Market order to the closing price of the bar in which the order
was issued. This is actually cheating, because the bar is closed and any
order should first be matched against the prices in the next bar

• coo (default: False)

Cheat-On-Open Setting this to True with set_coo enables

matching a Market order to the opening price, by for example using a timer
with cheat set to True, because such a timer gets executed before the
broker has evaluated

• int2pnl (default: True)

Assign generated interest (if any) to the profit and loss of


operation that reduces a position (be it long or short). There may be
cases in which this is undesired, because different strategies are
competing and the interest would be assigned on a non-deterministic
basis to any of them.
174
backtrader’s documentation Version-1.9.58.122

• shortcash (default: True)

If True then cash will be increased when a stocklike asset is shorted


and the calculated value for the asset will be negative.

If False then the cash will be deducted as operation cost and the
calculated value will be positive to end up with the same amount

• fundstartval (default: 100.0)

This parameter controls the start value for measuring the performance
in a fund-like way, i.e.: cash can be added and deducted increasing
the amount of shares. Performance is not measured using the net asset
value of the porftoflio but using the value of the fund

• fundmode (default: False)

If this is set to True analyzers like TimeReturn can automatically


calculate returns based on the fund value and not on the total net
asset value

set_cash(cash)

Sets the cash parameter (alias: setcash)

get_cash()

Returns the current cash (alias: getcash)

get_value(datas=None, mkt=False, lever=False)

Returns the portfolio value of the given datas (if datas is None, then the
total portfolio value will be returned (alias: getvalue)

set_eosbar(eosbar)

Sets the eosbar parameter (alias: seteosbar

set_checksubmit(checksubmit)

Sets the checksubmit parameter

set_filler(filler)

Sets a volume filler for volume filling execution

175
backtrader’s documentation Version-1.9.58.122

set_coc(coc)

Configure the Cheat-On-Close method to buy the close on order bar

set_coo(coo)

Configure the Cheat-On-Open method to buy the close on order bar

set_int2pnl(int2pnl)

Configure assignment of interest to profit and loss

set_fundstartval(fundstartval)

Set the starting value of the fund-like performance tracker

set_slippage_perc(perc, slip_open=True, slip_limit=True, slip_match=True, slip_out=False)

Configure slippage to be percentage based

set_slippage_fixed(fixed, slip_open=True, slip_limit=True, slip_match=True, slip_out=False)

Configure slippage to be fixed points based

get_orders_open(safe=False)

Returns an iterable with the orders which are still open (either not
executed or partially executed

The orders returned must not be touched.

If order manipulation is needed, set the parameter safe to True

getcommissioninfo(data)

Retrieves the CommissionInfo scheme associated with the given data

setcommission(commission=0.0, margin=None, mult=1.0, commtype=None, percabs=True,


stocklike=False, interest=0.0, interest_long=False, leverage=1.0, automargin=False, name=None)

This method sets a `` CommissionInfo`` object for assets managed in the


broker with the parameters. Consult the reference for CommInfoBase

If name is None, this will be the default for assets for which no other
CommissionInfo scheme can be found

176
backtrader’s documentation Version-1.9.58.122

addcommissioninfo(comminfo, name=None)

Adds a CommissionInfo object that will be the default for all assets if
name is None

getposition(data)

Returns the current position status (a Position instance) for the given
data

get_fundshares()

Returns the current number of shares in the fund-like mode

get_fundvalue()¶

Returns the Fund-like share value

Slippage

backtesting cannot guarantee real market conditions. No matter how good the
market simulation is, under real market conditions slippage can happen. That
means:

• The requested price may not be matched.

The integrated backtesting broker supports slippage. The following parameters can
be passed to the broker

• slip_perc (default: 0.0) Percentage in absolute termns (and positive) that


should be used to slip prices up/down for buy/sell orders

Note:

o 0.01 is 1%
o 0.001 is 0.1%
• slip_fixed (default: 0.0) Percentage in units (and positive) that should be
used to slip prices up/down for buy/sell orders

Note: if slip_perc is non zero, it takes precendence over this.

• slip_open (default: False) whether to slip prices for order execution which
would specifically used the opening price of the next bar. An example would
be Market order which is executed with the next available tick, i.e: the
opening price of the bar.
177
backtrader’s documentation Version-1.9.58.122

This also applies to some of the other executions, because the logic tries
to detect if the opening price would match the requested price/execution
type when moving to a new bar.

• slip_match (default: True)

If True the broker will offer a match by capping slippage at high/low


prices in case they would be exceeded.

If False the broker will not match the order with the current prices and
will try execution during the next iteration

• slip_limit (default: True)

Limit orders, given the exact match price requested, will be matched even
if slip_match is False.

This option controls that behavior.

If True, then Limit orders will be matched by capping prices to the limit /
high/low prices

If False and slippage exceeds the cap, then there will be no match

• slip_out (default: False)

Provide slippage even if the price falls outside the high - low range.

How it works

In order to decide when to apply slippage the order execution type is taken into
account:

• Close - No slippage is applied

This order is matched against the close price and this price is the last
one of the day. Slippage cannot happen because the order can only happen
with the last tick of the session and this is a unique price with no
tolerance.

• Market - Slippage is applied

Please check the slip_open exception. Because Market orders will be matched
against the opening price of the next bar.

178
backtrader’s documentation Version-1.9.58.122

• Limit - Slippage is applied following this logic


o If the matching price would be the opening price, then slippage is applied according to the
parameter slip_open. If applied, the price will never be worse than the requested limit price
o If the matching price is not the limit price, slippage is applied capping at high/low. In this case
slip_mlimit applies to decide if a match will be happening in case the caps are exceeded
o If the matching price is the limit price, then no slippage is applied
• Stop - once the order is triggered the same logic as for Market orders
apply
• StopLimit - once the order is triggered the same logic as for Limit orders
apply

This approach tries to offer the most realistic possible approach within the
limits of the simulation and available data

Configuring slippage

A broker is already instantiated by a cerebro engine for each run with the
default parameters. There are two ways to alter the behavior:

• Use methods to configure slippage

BackBroker.set_slippage_perc(perc, slip_open=True, slip_limit=True, slip_match=True,


slip_out=False)

Configure slippage to be percentage based

BackBroker.set_slippage_fixed(fixed, slip_open=True, slip_limit=True, slip_match=True,


slip_out=False)

Configure slippage to be fixed points based

• Replace the broker as in:


• import backtrader as bt

• cerebro = bt.Cerebro()
• cerebro.broker = bt.brokers.BackBroker(slip_perc=0.005) # 0.5%

Practical examples

The sources contain a sample which uses the order execution type Market and a
long/short approach using signals. This should allow to understand the logic.

A run with no slippage and an initial plot for reference later:

179
backtrader’s documentation Version-1.9.58.122

$ ./slippage.py --plot
01 2005-03-22 23:59:59 SELL Size: -1 / Price: 3040.55
02 2005-04-11 23:59:59 BUY Size: +1 / Price: 3088.47
03 2005-04-11 23:59:59 BUY Size: +1 / Price: 3088.47
04 2005-04-19 23:59:59 SELL Size: -1 / Price: 2948.38
05 2005-04-19 23:59:59 SELL Size: -1 / Price: 2948.38
06 2005-05-19 23:59:59 BUY Size: +1 / Price: 3034.88
...
35 2006-12-19 23:59:59 BUY Size: +1 / Price: 4121.01

And the same run using slippage with a 1.5% configured:

$ ./slippage.py --slip_perc 0.015


01 2005-03-22 23:59:59 SELL Size: -1 / Price: 3040.55
02 2005-04-11 23:59:59 BUY Size: +1 / Price: 3088.47
03 2005-04-11 23:59:59 BUY Size: +1 / Price: 3088.47
04 2005-04-19 23:59:59 SELL Size: -1 / Price: 2948.38
05 2005-04-19 23:59:59 SELL Size: -1 / Price: 2948.38

180
backtrader’s documentation Version-1.9.58.122

06 2005-05-19 23:59:59 BUY Size: +1 / Price: 3034.88


...
35 2006-12-19 23:59:59 BUY Size: +1 / Price: 4121.01

There is NO CHANGE. This is the expected behavior for the scenario.

• Execution Type: Market


• And slip_open has not been set to True

The Market orders are matched against the opening price of the next bar and
we are not allowing the open price to be moved.

A run setting slip_open to True:

$ ./slippage.py --slip_perc 0.015 --slip_open


01 2005-03-22 23:59:59 SELL Size: -1 / Price: 3021.66
02 2005-04-11 23:59:59 BUY Size: +1 / Price: 3088.47
03 2005-04-11 23:59:59 BUY Size: +1 / Price: 3088.47
04 2005-04-19 23:59:59 SELL Size: -1 / Price: 2948.38
05 2005-04-19 23:59:59 SELL Size: -1 / Price: 2948.38
06 2005-05-19 23:59:59 BUY Size: +1 / Price: 3055.14
...
35 2006-12-19 23:59:59 BUY Size: +1 / Price: 4121.01

And one can immediately see tht the prices HAVE MOVED. And the allocated prices
are worst or equal like for operation 35. This is not a copy and paste error

• The open and the high on 20016-12-19 were the same.

The price cannot be pushed above the high because that would mean returning
a non-existent price.

Of course, backtrader allows to match outide the high - low range if wished with
slip_out. A run with it activated:

$ ./slippage.py --slip_perc 0.015 --slip_open --slip_out


01 2005-03-22 23:59:59 SELL Size: -1 / Price: 2994.94
02 2005-04-11 23:59:59 BUY Size: +1 / Price: 3134.80
03 2005-04-11 23:59:59 BUY Size: +1 / Price: 3134.80
04 2005-04-19 23:59:59 SELL Size: -1 / Price: 2904.15
05 2005-04-19 23:59:59 SELL Size: -1 / Price: 2904.15
06 2005-05-19 23:59:59 BUY Size: +1 / Price: 3080.40
...
35 2006-12-19 23:59:59 BUY Size: +1 / Price: 4182.83

181
backtrader’s documentation Version-1.9.58.122

A matching expression for the matched prices would be: OMG! (Oh My God!). The
prices are clearly outside of the range. Suffice to look at operation 35, which
has been matched at 4182.83. A quick inspection of the chart in this document
shows that the asset never came close to that price.

slip_match has a default of True and that means that backtrader offers a match,
be it with capped or uncapped prices as seen above. Let’s disable it:

$ ./slippage.py --slip_perc 0.015 --slip_open --no-slip_match


01 2005-04-15 23:59:59 SELL Size: -1 / Price: 3028.10
02 2005-05-18 23:59:59 BUY Size: +1 / Price: 3029.40
03 2005-06-01 23:59:59 BUY Size: +1 / Price: 3124.03
04 2005-10-06 23:59:59 SELL Size: -1 / Price: 3365.57
05 2005-10-06 23:59:59 SELL Size: -1 / Price: 3365.57
06 2005-12-01 23:59:59 BUY Size: +1 / Price: 3499.95
07 2005-12-01 23:59:59 BUY Size: +1 / Price: 3499.95
08 2006-02-28 23:59:59 SELL Size: -1 / Price: 3782.71
09 2006-02-28 23:59:59 SELL Size: -1 / Price: 3782.71
10 2006-05-23 23:59:59 BUY Size: +1 / Price: 3594.68
11 2006-05-23 23:59:59 BUY Size: +1 / Price: 3594.68
12 2006-11-27 23:59:59 SELL Size: -1 / Price: 3984.37
13 2006-11-27 23:59:59 SELL Size: -1 / Price: 3984.37

Blistering barnacles! Down to 13 from 35. The rationale:

Deactivating slip_match disallows matching operations if slippage would push the matching price above the high or
below the low of the bar. It seems that with the 1.5% of requested slippage, around 22 of the operations fail to be
executed.

The examples should have shown how the different slippage options work together.

Fillers

The backtrader broker simulation has a default strategy when it comes to using
volume for order execution:

• Ignore volume

This is based on 2 premises:

• Trade in markets liquid enough to fully absorb buy/sell orders in one go


• Real volume matching requires a real wolrd

182
backtrader’s documentation Version-1.9.58.122

A quick example is a Fill or Kill order. Even down to the tick resolution
and with enough volume for a fill, the backtrader broker cannot know how
many extra actors happen to be in the market to discriminate if such an
order would be or would not be matched to stick to the Fill part or if the
order should be Kill

But the broker can accept Volume Fillers which determine how much of the volume
at a given point in time has to be used for order matching.

The fillers signature

A filler in the backtrader ecosystem can be any callable which matches the
following signature:

callable(order, price, ago)

Where:

• order is the order which is going to be executed

This object gives access to the data object which is the target of the
operation, creation sizes/prices, execution prices/sizes/remaining sizes
and other details

• price at which the order is going to be executed


• ago is the index to the data in the order in which to look for the volume
and price elements

In almost all cases this will be 0 (current point in time) but in a corner
case to cover Close orders this may be -1

To for example access the bar volume do:

barvolume = order.data.volume[ago]

The callable can be a function or for example an instance of a class supporting


the __call__ method, like in:

class MyFiller(object):
def __call__(self, order, price, ago):
pass

183
backtrader’s documentation Version-1.9.58.122

Adding a Filler to the broker

The most straightforward method is to use the set_filler:

import backtrader as bt

cerebro = Cerebro()
cerebro.broker.set_filler(bt.broker.fillers.FixedSize())

The second choice is to completely replace the broker, although this is probably
only meant for subclasses of BrokerBack which have rewritten portions of the
functionality:

import backtrader as bt

cerebro = Cerebro()
filler = bt.broker.fillers.FixedSize()
newbroker = bt.broker.BrokerBack(filler=filler)
cerebro.broker = newbroker

The sample

The backtrader sources contain a sample named volumefilling which allows to test
some of the integrated fillers (initially all)

Reference

class backtrader.fillers.FixedSize

Returns the execution size for a given order using a percentage of the
volume in a bar.

This percentage is set with the parameter perc

Params:

• size (default: None) maximum size to be executed. The actual volume


of the bar at execution time is also a limit if smaller than the size

If the value of this parameter evaluates to False, the entire volume


of the bar will be used to match the order

class backtrader.fillers.FixedBarPerc
184
backtrader’s documentation Version-1.9.58.122

Returns the execution size for a given order using a percentage of the
volume in a bar.

This percentage is set with the parameter perc

Params:

• perc (default: 100.0) (valied values: 0.0 - 100.0)

Percentage of the volume bar to use to execute an order

class backtrader.fillers.BarPointPerc

Returns the execution size for a given order. The volume will be
distributed uniformly in the range high-low using minmov to partition.

From the allocated volume for the given price, the perc percentage will be
used

Params:

• minmov (default: 0.01)

Minimum price movement. Used to partition the range high-low to


proportionally distribute the volume amongst possible prices

• perc (default: 100.0) (valied values: 0.0 - 100.0)

Percentage of the volume allocated to the order execution price to


use for matching

Orders

Cerebro is the key control system in backtrader and Strategy (a subclass) is the
key control point of the end user. The latter needs a chaining method to other
parts of the system and that’s where orders play a key role.

Orders translate the decisions made by the logic in a Strategy into a message
suitable for the Broker to execute an action. This is done with:

• Creation

Through Strategy’s methods: buy`, sell and close (Strategy) which return
an order instance as a reference

185
backtrader’s documentation Version-1.9.58.122

• Cancellation

Through Strategy’s method: cancel (Strategy) which takes an order instance


to operate on

And the orders serve also as a communication method back to the user, to notify
how things are running in the broker.

• Notification

To Strategy method: notify_order (Strategy) which reports an order instance

Order creation

When invoking the buy, sell and close the following parameters apply for
creation:

• data (default: None)

For which data the order has to be created. If None then the first data in
the system, self.datas[0] or self.data0 (aka self.data) will be used

• size (default: None)

Size to use (positive) of units of data to use for the order.

If None the sizer instance retrieved via getsizer will be used to determine
the size.

• price (default: None)

Price to use (live brokers may place restrictions on the actual format if
it does not comply to minimum tick size requirements)

None is valid for Market and Close orders (the market determines the price)

For Limit, Stop and StopLimit orders this value determines the trigger
point (in the case of Limit the trigger is obviously at which price the
order should be matched)

• plimit (default: None)

Only applicable to StopLimit orders. This is the price at which to set the
implicit Limit order, once the Stop has been triggered (for which price has
been used)
186
backtrader’s documentation Version-1.9.58.122

• exectype (default: None)

Possible values:

o Order.Market or None. A market order will be executed with the next available price. In
backtesting it will be the opening price of the next bar
o Order.Limit. An order which can only be executed at the given price or better
o Order.Stop. An order which is triggered at price and executed like an Order.Market order
o Order.StopLimit. An order which is triggered at price and executed as an implicit Limit order with
price given by pricelimit
• valid (default: None)

Possible values:

o None: this generates an order that will not expire (aka Good till cancel) and remain in the market
until matched or canceled. In reality brokers tend to impose a temporal limit, but this is usually so
far away in time to consider it as not expiring
o datetime.datetime or datetime.date instance: the date will be used to generate an order valid
until the given datetime (aka good till date)
o Order.DAY or 0 or timedelta(): a day valid until the End of the Session (aka day order) will be
generated
o numeric value: This is assumed to be a value corresponding to a datetime in matplotlib coding
(the one used by backtrader) and will used to generate an order valid until that time (good till
date)
• tradeid (default: 0)

This is an internal value applied by backtrader to keep track of


overlapping trades on the same asset. This tradeid is sent back to the
strategy when notifying changes to the status of the orders.

• **kwargs: additional broker implementations may support extra parameters.


backtrader will pass the kwargs down to the created order objects

Example: if the 4 order execution types directly supported by backtrader


are not enough, in the case of for example Interactive Brokers the
following could be passed as kwargs:

orderType='LIT', lmtPrice=10.0, auxPrice=9.8

This would override the settings created by backtrader and generate a LIMIT
IF TOUCHED order with a touched price of 9.8 and a limit price of 10.0.

Note

187
backtrader’s documentation Version-1.9.58.122

The close method will examine the current position and correspondingly use buy or
sell to effectively close the position. size will also be automatically
calculated unless the parameter is an input from the user, in which case a
partial close or a reversal can be achieved

Order notification

To receive notifications the notify_order method has to be overriden in the user


subclassed Strategy (the default behavior is to do nothing). The following
applies to those notifications:

• Issued before the strategy’s next method is called


• May (and will) happen several times for the same order with the same or
different status during the same next cycle.

An order may be submitted to the broker and be accepted and its execution
completed before next will be invoked again.

In this case at least 3 notifications will happen with the following status
values:

o Order.Submitted because the order was sent to the broker


o Order.Accepted because the order was taken by the broker and awaits potential execution
o Order.Completed because in the example it was quickly matched and completely filled (which
may be the case usually for Market orders)

Notifications may happen even several times for the same status in the case of
Order.Partial. This status will not be seen in the backtesting broker (which
doesn’t consider volume when matching) but it will for sure be set by real
brokers.

Real brokers may issue one or more executions before updating a position, and
this group of executions will make up for an Order.Partial notification.

Actual execution data is in the attribute: order.executed which is an object of


type OrderData (see below for the reference), with usual fields as size and price

The values at the time of creation are stored in order.created which remains
unchanged throughout the lifecycle of an order

Order Status values

The following are defined:

188
backtrader’s documentation Version-1.9.58.122

• Order.Created: set when the Order instance is created. Never to be seen by


end-users unless order instances are manually created rather than through
buy, sell and close
• Order.Submitted: set when the order instance has been transmitted to the
broker. This simply means it has been sent. In backtesting mode this will
be an immediate action, but it may take actual time with a real broker,
which may receive the order and only first notify when it has been
forwarded to an exchange
• Order.Accepted: the broker has taken the order and it is in the system (or
already in a exchange) awaiting execution according to the set parameters
like execution type, size, price and validity
• Order.Partial: the order has been partially executed. order.executed
contains the current filled size and average price.

order.executed.exbits contains a complete list of ExecutionBits detailing


the partial fillings

• Order.Complete: the order has been completely filled average price.


• Order.Rejected: the broker has rejected the order. A parameter (like for
example valid to determine its lifetime) may not be accepted by the broker
and the order cannot be accepted.

The reason will be notified via the notify_store method of the strategy.
Although this may seem awkward, the reason is that real life brokers will
notify this over an event, which may or may not be direclty related to the
order. But the notification from the broker can still be seen in
notify_store.

This status will not be seen in the backtesting broker

• Order.Margin: the order execution would imply a margin call and the
previously accepted order has been taken off the system
• Order.Cancelled (or Order.Canceled): confirmation of the user requested
cancellation

It must be taken into account that a request to cancel an order via the
cancel method of the strategy is no guarantee of cancellation. The order
may have been already executed but such execution may not have yet notified
by the broker and/or the notification may not have yet been delivered to
the strategy

• Order.Expired: a previously accepted order* which had a time validity has


expired and ben taken off the system

189
backtrader’s documentation Version-1.9.58.122

Reference: Order and associated classes

These objects are the generic classes in the backtrader ecosystem. They may been
extended and/or contain extra embedded information when operating with other
brokers. See the reference of the appropriate broker

class backtrader.order.Order

Class which holds creation/execution data and type of oder.

The order may have the following status:

• Submitted: sent to the broker and awaiting confirmation


• Accepted: accepted by the broker
• Partial: partially executed
• Completed: fully exexcuted
• Canceled/Cancelled: canceled by the user
• Expired: expired
• Margin: not enough cash to execute the order.
• Rejected: Rejected by the broker

This can happen during order submission (and therefore the order will
not reach the Accepted status) or before execution with each new bar
price because cash has been drawn by other sources (future-like
instruments may have reduced the cash or orders orders may have been
executed)

Member Attributes:

• ref: unique order identifier


• created: OrderData holding creation data
• executed: OrderData holding execution data
• info: custom information passed over method addinfo(). It is kept in the form of an OrderedDict
which has been subclassed, so that keys can also be specified using ‘.’ notation

User Methods:

• isbuy(): returns bool indicating if the order buys


• issell(): returns bool indicating if the order sells
• alive(): returns bool if order is in status Partial or Accepted

class backtrader.order.OrderData(dt=None, size=0, price=0.0, pricelimit=0.0, remsize=0, pclose=0.0,


trailamount=0.0, trailpercent=0.0)

190
backtrader’s documentation Version-1.9.58.122

Holds actual order data for Creation and Execution.

In the case of Creation the request made and in the case of Execution the
actual outcome.

Member Attributes:

• exbits : iterable of OrderExecutionBits for this OrderData


• dt: datetime (float) creation/execution time
• size: requested/executed size
• price: execution price Note: if no price is given and no pricelimite is given, the closing price at the
time or order creation will be used as reference
• pricelimit: holds pricelimit for StopLimit (which has trigger first)
• trailamount: absolute price distance in trailing stops
• trailpercent: percentage price distance in trailing stops
• value: market value for the entire bit size
• comm: commission for the entire bit execution
• pnl: pnl generated by this bit (if something was closed)
• margin: margin incurred by the Order (if any)
• psize: current open position size
• pprice: current open position price

class backtrader.order.OrderExecutionBit(dt=None, size=0, price=0.0, closed=0, closedvalue=0.0,


closedcomm=0.0, opened=0, openedvalue=0.0, openedcomm=0.0, pnl=0.0, psize=0, pprice=0.0)

Intended to hold information about order exeuction. A “bit” does not


determine if the order has been fully/partially executed, it just holds
information.

Member Attributes:

• dt: datetime (float) execution time


• size: how much was executed
• price: execution price
• closed: how much of the execution closed an existing postion
• opened: how much of the execution opened a new position
• openedvalue: market value of the “opened” part
• closedvalue: market value of the “closed” part
• closedcomm: commission for the “closed” part
• openedcomm: commission for the “opened” part
• value: market value for the entire bit size
• comm: commission for the entire bit execution
• pnl: pnl generated by this bit (if something was closed)
• psize: current open position size
• pprice: current open position price
191
backtrader’s documentation Version-1.9.58.122

Order Management and Execution

Backtesting, and hence backtrader, would not be complete if orders could not be
simulated. To do so, the following is available in the platform.

For order management 3 primitives:

• buy
• sell
• cancel

Note

An update primitive is obviously something logic but common sense dictates that
such a method is mostly used by manual operators working with a judgmental
trading approach.

For order execution logic the following execution types:

• Market
• Close
• Limit
• Stop
• StopLimit

Order Management

Some examples:

# buy the main date, with sizer default stake, Market order
order = self.buy()

# Market order - valid will be "IGNORED"


order = self.buy(valid=datetime.datetime.now() + datetime.timedelta(days=3))

# Market order - price will be IGNORED


order = self.buy(price=self.data.close[0] * 1.02)

# Market order - manual stake


order = self.buy(size=25)

# Limit order - want to set the price and can set a validity
order = self.buy(exectype=Order.Limit,
192
backtrader’s documentation Version-1.9.58.122

price=self.data.close[0] * 1.02,
valid=datetime.datetime.now() + datetime.timedelta(days=3)))

# StopLimit order - want to set the price, price limit


order = self.buy(exectype=Order.StopLimit,
price=self.data.close[0] * 1.02,
plimit=self.data.close[0] * 1.07)

# Canceling an existing order


self.broker.cancel(order)

Note

All order types can be create by creating an Order instance (or one of its
subclasses) and then passed to to the broker with:

order = self.broker.submit(order)

Note

There are buy and sell primitives in the broker itself, but they are less
forgiving with regards to default parameters.

Order Execution Logic

The broker uses 2 main guidelines (assumptions?) for order execution.

• The current data has already happened and cannot be used to execcute an
order.

If the logic in the strategy is something like:

if self.data.close > self.sma: # where sma is a Simple Moving Average


self.buy()

The expectation CANNOT be that the order will be executed with the
``close`` price which is being examined in the logic BECAUSE it has already
happened.

The order CAN BE 1st EXECUTED withing the bounds of the next set of
Open/High/Low/Close price points (and the conditions set forth herein by
the order)

• Volume does not play a role

193
backtrader’s documentation Version-1.9.58.122

It actually does in real trading if the trader goes for non-liquid assets
or precisely the extremes (high/low) of a price bar are hit.

But hitting the high/low points is a seldom occurrence (if you do ... you
don’t need backtrader) and the chosen assets will have enough liquidity to
absorb the orders of any regular trading

Market

Execution:

• Opening price of the next set of Open/High/Low/Close prices (commonly referred as bar)

Rationale:

• If the logic has executed at point X in time and issued a Market order, the next price spot that will happen
is the upcoming open price

Note

This order executes always and disregards any price and valid parameters used to
create it

Close

Execution:

• Using the close price of the next barwhen the next bar actually CLOSES

Rationale:

• Most backtesting feeds contain already closed bars and the order will
execute immediately with the close price of the next bar. A daily data feed
is the most common example.

But the system could be fed with “tick” prices and the actual bar
(time/date wise) is being udpated constantly with the new ticks, without
actually moving to the next bar (because time and/or date have not changed)

Only when the time or date changes, the bar has actually been closed and
the order gets executed

194
backtrader’s documentation Version-1.9.58.122

Limit

Execution:

• The price set at order creation if the data touches it, starting with the
next price bar.

The order will be canceled if valid is set and the time point is reached

Price Matching:

• backtrader tries to provide most realistic execution price for Limit


orders.

Using the 4 price spots (Open/High/Low/Close) it can be partially inferred


if the requested price can be improved.

For Buy Orders

o Case 1:

If the open price of the bar is below the limit price the order
executes immediately with the open price. The order has been swept
during the opening phase of the session

o Case 2:

If the open price has not penetrated below the limit price but the
low price is below the limit price, then the limit price has been
seen during the session and the order can be executed

The logic is obviously inverted for Sell orders.

Stop

Execution:

• The trigger price set at order creation if the data touches it, starting
with the next price bar.

The order will be canceled if valid is set and the time point is reached

Price Matching:

195
backtrader’s documentation Version-1.9.58.122

• backtrader tries to provide most realistic trigger price for Stop orders.

Using the 4 price spots (Open/High/Low/Close) it can be partially inferred


if the requested price can be improved.

For `Stop orders which Buy

o Case 1:

If the open price of the bar is above the stop price the order is
executed immediately with the open price.

Intended to stop a loss if the price is moving upwards against an


existing short position

o Case 2:

If the open price has not penetrated above the stop price but the
high price is above the stop price, then the stop price has been seen
during the session and the order can be executed

The logic is obviously inverted for Stop orders which Sell.

StopLimit

Execution:

• The trigger price sets the order in motion starting with the next price bar.

Price Matching:

• Trigger: Uses the Stop matching logic (but only triggers and turns the order into a Limit order)
• Limit: Uses the Limit price matching logic

Some samples

As always pictures (with code) are worth several million long explanations.
Please note that the snippets concentrate on the order creation part. The full
code is at the bottom.

A price closes above/below a simple moving average strategy will be used for the
generation of the buy/sell signals

196
backtrader’s documentation Version-1.9.58.122

The signal is seen at the bottom of the charts: the CrossOver using the crossover
indicator.

A reference to generated “buy” orders will be kept to only allow one


simultaneous order at most in the system.

Execution Type: Market

See in the chart how how the orders are executed one bar after the signal is
generated with the opening price.

if self.p.exectype == 'Market':
self.buy(exectype=bt.Order.Market) # default if not given

self.log('BUY CREATE, exectype Market, price %.2f' %


self.data.close[0])

The output chart.

197
backtrader’s documentation Version-1.9.58.122

The command line and output:

$ ./order-execution-samples.py --exectype Market


2006-01-26T23:59:59+00:00, BUY CREATE, exectype Market, price 3641.42
2006-01-26T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-01-27T23:59:59+00:00, BUY EXECUTED, Price: 3643.35, Cost: 3643.35, Comm 0.00
2006-03-02T23:59:59+00:00, SELL CREATE, 3763.73
2006-03-02T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-03T23:59:59+00:00, SELL EXECUTED, Price: 3763.95, Cost: 3763.95, Comm
0.00
...
...
2006-12-11T23:59:59+00:00, BUY CREATE, exectype Market, price 4052.89
2006-12-11T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-12-12T23:59:59+00:00, BUY EXECUTED, Price: 4052.55, Cost: 4052.55, Comm 0.00

198
backtrader’s documentation Version-1.9.58.122

Execution Type: Close

Now the orders are also executed one bar after the signal but with the closing
price.

elif self.p.exectype == 'Close':


self.buy(exectype=bt.Order.Close)

self.log('BUY CREATE, exectype Close, price %.2f' %


self.data.close[0])

The output chart.

The command line and output:

199
backtrader’s documentation Version-1.9.58.122

$ ./order-execution-samples.py --exectype Close


2006-01-26T23:59:59+00:00, BUY CREATE, exectype Close, price 3641.42
2006-01-26T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-01-27T23:59:59+00:00, BUY EXECUTED, Price: 3685.48, Cost: 3685.48, Comm 0.00
2006-03-02T23:59:59+00:00, SELL CREATE, 3763.73
2006-03-02T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-03T23:59:59+00:00, SELL EXECUTED, Price: 3763.95, Cost: 3763.95, Comm
0.00
...
...
2006-11-06T23:59:59+00:00, BUY CREATE, exectype Close, price 4045.22
2006-11-06T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-07T23:59:59+00:00, BUY EXECUTED, Price: 4072.86, Cost: 4072.86, Comm 0.00
2006-11-24T23:59:59+00:00, SELL CREATE, 4048.16
2006-11-24T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-27T23:59:59+00:00, SELL EXECUTED, Price: 4045.05, Cost: 4045.05, Comm
0.00
2006-12-11T23:59:59+00:00, BUY CREATE, exectype Close, price 4052.89
2006-12-11T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-12-12T23:59:59+00:00, BUY EXECUTED, Price: 4059.74, Cost: 4059.74, Comm 0.00

Execution Type: Limit

Validity is being calculated some lines before in case it has been passed as
argument.

if self.p.valid:
valid = self.data.datetime.date(0) + \
datetime.timedelta(days=self.p.valid)
else:
valid = None

A limit price 1% below the signal generation price (the close at the signal bar)
is set. Notice how this prevents many from the orders above from being executed.

elif self.p.exectype == 'Limit':


price = self.data.close * (1.0 - self.p.perc1 / 100.0)

self.buy(exectype=bt.Order.Limit, price=price, valid=valid)

if self.p.valid:
txt = 'BUY CREATE, exectype Limit, price %.2f, valid: %s'
self.log(txt % (price, valid.strftime('%Y-%m-%d')))
200
backtrader’s documentation Version-1.9.58.122

else:
txt = 'BUY CREATE, exectype Limit, price %.2f'
self.log(txt % price)

The output chart.

Just 4 orders have been issued. Limiting the price trying to catch a small dip
has completly changed the output.

The command line and output:

$ ./order-execution-samples.py --exectype Limit --perc1 1


2006-01-26T23:59:59+00:00, BUY CREATE, exectype Limit, price 3605.01
2006-01-26T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-05-18T23:59:59+00:00, BUY EXECUTED, Price: 3605.01, Cost: 3605.01, Comm 0.00
2006-06-05T23:59:59+00:00, SELL CREATE, 3604.33
2006-06-05T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED

201
backtrader’s documentation Version-1.9.58.122

2006-06-06T23:59:59+00:00, SELL EXECUTED, Price: 3598.58, Cost: 3598.58, Comm


0.00
2006-06-21T23:59:59+00:00, BUY CREATE, exectype Limit, price 3491.57
2006-06-21T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-06-28T23:59:59+00:00, BUY EXECUTED, Price: 3491.57, Cost: 3491.57, Comm 0.00
2006-07-13T23:59:59+00:00, SELL CREATE, 3562.56
2006-07-13T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-07-14T23:59:59+00:00, SELL EXECUTED, Price: 3545.92, Cost: 3545.92, Comm
0.00
2006-07-24T23:59:59+00:00, BUY CREATE, exectype Limit, price 3596.60
2006-07-24T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED

Execution Type: Limit with validity

To not wait forever on a limit order which may only execute when the price is
moving against the “buy” order, the order will only be valid 4 (calendar) days.

The output chart.

202
backtrader’s documentation Version-1.9.58.122

More orders have been generated, but all but one “buy” order expired, further
limiting the amount of operations.

The command line and output:

$ ./order-execution-samples.py --exectype Limit --perc1 1 --valid 4


2006-01-26T23:59:59+00:00, BUY CREATE, exectype Limit, price 3605.01, valid:
2006-01-30
2006-01-26T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-01-30T23:59:59+00:00, BUY EXPIRED
2006-03-10T23:59:59+00:00, BUY CREATE, exectype Limit, price 3760.48, valid:
2006-03-14
2006-03-10T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-14T23:59:59+00:00, BUY EXPIRED
2006-03-30T23:59:59+00:00, BUY CREATE, exectype Limit, price 3835.86, valid:
2006-04-03
2006-03-30T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-04-03T23:59:59+00:00, BUY EXPIRED
203
backtrader’s documentation Version-1.9.58.122

2006-04-20T23:59:59+00:00, BUY CREATE, exectype Limit, price 3821.40, valid:


2006-04-24
2006-04-20T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-04-24T23:59:59+00:00, BUY EXPIRED
2006-05-04T23:59:59+00:00, BUY CREATE, exectype Limit, price 3804.65, valid:
2006-05-08
2006-05-04T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-05-08T23:59:59+00:00, BUY EXPIRED
2006-06-01T23:59:59+00:00, BUY CREATE, exectype Limit, price 3611.85, valid:
2006-06-05
2006-06-01T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-06-05T23:59:59+00:00, BUY EXPIRED
2006-06-21T23:59:59+00:00, BUY CREATE, exectype Limit, price 3491.57, valid:
2006-06-25
2006-06-21T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-06-26T23:59:59+00:00, BUY EXPIRED
2006-07-24T23:59:59+00:00, BUY CREATE, exectype Limit, price 3596.60, valid:
2006-07-28
2006-07-24T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-07-28T23:59:59+00:00, BUY EXPIRED
2006-09-12T23:59:59+00:00, BUY CREATE, exectype Limit, price 3751.07, valid:
2006-09-16
2006-09-12T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-09-18T23:59:59+00:00, BUY EXPIRED
2006-09-20T23:59:59+00:00, BUY CREATE, exectype Limit, price 3802.90, valid:
2006-09-24
2006-09-20T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-09-22T23:59:59+00:00, BUY EXECUTED, Price: 3802.90, Cost: 3802.90, Comm 0.00
2006-11-02T23:59:59+00:00, SELL CREATE, 3974.62
2006-11-02T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-03T23:59:59+00:00, SELL EXECUTED, Price: 3979.73, Cost: 3979.73, Comm
0.00
2006-11-06T23:59:59+00:00, BUY CREATE, exectype Limit, price 4004.77, valid:
2006-11-10
2006-11-06T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-10T23:59:59+00:00, BUY EXPIRED
2006-12-11T23:59:59+00:00, BUY CREATE, exectype Limit, price 4012.36, valid:
2006-12-15
2006-12-11T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-12-15T23:59:59+00:00, BUY EXPIRED

204
backtrader’s documentation Version-1.9.58.122

Execution Type: Stop

A stop price 1% above the signal price is set. That means that the strategy only
buys if the signal is generated and the price continues climbing up, which could
be intrepreted as a signal of strength.

This completely alters the execution panorama.

elif self.p.exectype == 'Stop':


price = self.data.close * (1.0 + self.p.perc1 / 100.0)

self.buy(exectype=bt.Order.Stop, price=price, valid=valid)

if self.p.valid:
txt = 'BUY CREATE, exectype Stop, price %.2f, valid: %s'
self.log(txt % (price, valid.strftime('%Y-%m-%d')))
else:
txt = 'BUY CREATE, exectype Stop, price %.2f'
self.log(txt % price)

The output chart.

205
backtrader’s documentation Version-1.9.58.122

The command line and output:

$ ./order-execution-samples.py --exectype Stop --perc1 1


2006-01-26T23:59:59+00:00, BUY CREATE, exectype Stop, price 3677.83
2006-01-26T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-01-27T23:59:59+00:00, BUY EXECUTED, Price: 3677.83, Cost: 3677.83, Comm 0.00
2006-03-02T23:59:59+00:00, SELL CREATE, 3763.73
2006-03-02T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-03T23:59:59+00:00, SELL EXECUTED, Price: 3763.95, Cost: 3763.95, Comm
0.00
2006-03-10T23:59:59+00:00, BUY CREATE, exectype Stop, price 3836.44
2006-03-10T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-15T23:59:59+00:00, BUY EXECUTED, Price: 3836.44, Cost: 3836.44, Comm 0.00
2006-03-28T23:59:59+00:00, SELL CREATE, 3811.45
2006-03-28T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-29T23:59:59+00:00, SELL EXECUTED, Price: 3811.85, Cost: 3811.85, Comm
0.00

206
backtrader’s documentation Version-1.9.58.122

2006-03-30T23:59:59+00:00, BUY CREATE, exectype Stop, price 3913.36


2006-03-30T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-09-29T23:59:59+00:00, BUY EXECUTED, Price: 3913.36, Cost: 3913.36, Comm 0.00
2006-11-02T23:59:59+00:00, SELL CREATE, 3974.62
2006-11-02T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-03T23:59:59+00:00, SELL EXECUTED, Price: 3979.73, Cost: 3979.73, Comm
0.00
2006-11-06T23:59:59+00:00, BUY CREATE, exectype Stop, price 4085.67
2006-11-06T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-13T23:59:59+00:00, BUY EXECUTED, Price: 4085.67, Cost: 4085.67, Comm 0.00
2006-11-24T23:59:59+00:00, SELL CREATE, 4048.16
2006-11-24T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-11-27T23:59:59+00:00, SELL EXECUTED, Price: 4045.05, Cost: 4045.05, Comm
0.00
2006-12-11T23:59:59+00:00, BUY CREATE, exectype Stop, price 4093.42
2006-12-11T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-12-13T23:59:59+00:00, BUY EXECUTED, Price: 4093.42, Cost: 4093.42, Comm 0.00

Execution Type: StopLimit

A stop price 1% above the signal price is set. But the limit price is set 0.5%
above the signal (close) price which could be interpreted as: wait for the
strength to show up but do not buy the peak. Wait for a dip.

Validity is capped at 20 (calendar) days

elif self.p.exectype == 'StopLimit':


price = self.data.close * (1.0 + self.p.perc1 / 100.0)

plimit = self.data.close * (1.0 + self.p.perc2 / 100.0)

self.buy(exectype=bt.Order.StopLimit, price=price, valid=valid,


plimit=plimit)

if self.p.valid:
txt = ('BUY CREATE, exectype StopLimit, price %.2f,'
' valid: %s, pricelimit: %.2f')
self.log(txt % (price, valid.strftime('%Y-%m-%d'), plimit))
else:
txt = ('BUY CREATE, exectype StopLimit, price %.2f,'
' pricelimit: %.2f')
self.log(txt % (price, plimit))

207
backtrader’s documentation Version-1.9.58.122

The output chart.

The command line and output:

$ ./order-execution-samples.py --exectype StopLimit --perc1 1 --perc2 0.5 --valid


20
2006-01-26T23:59:59+00:00, BUY CREATE, exectype StopLimit, price 3677.83, valid:
2006-02-15, pricelimit: 3659.63
2006-01-26T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-02-03T23:59:59+00:00, BUY EXECUTED, Price: 3659.63, Cost: 3659.63, Comm 0.00
2006-03-02T23:59:59+00:00, SELL CREATE, 3763.73
2006-03-02T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-03T23:59:59+00:00, SELL EXECUTED, Price: 3763.95, Cost: 3763.95, Comm
0.00
2006-03-10T23:59:59+00:00, BUY CREATE, exectype StopLimit, price 3836.44, valid:
2006-03-30, pricelimit: 3817.45
2006-03-10T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
208
backtrader’s documentation Version-1.9.58.122

2006-03-21T23:59:59+00:00, BUY EXECUTED, Price: 3817.45, Cost: 3817.45, Comm 0.00


2006-03-28T23:59:59+00:00, SELL CREATE, 3811.45
2006-03-28T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-03-29T23:59:59+00:00, SELL EXECUTED, Price: 3811.85, Cost: 3811.85, Comm
0.00
2006-03-30T23:59:59+00:00, BUY CREATE, exectype StopLimit, price 3913.36, valid:
2006-04-19, pricelimit: 3893.98
2006-03-30T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-04-19T23:59:59+00:00, BUY EXPIRED
...
...
2006-12-11T23:59:59+00:00, BUY CREATE, exectype StopLimit, price 4093.42, valid:
2006-12-31, pricelimit: 4073.15
2006-12-11T23:59:59+00:00, ORDER ACCEPTED/SUBMITTED
2006-12-22T23:59:59+00:00, BUY EXECUTED, Price: 4073.15, Cost: 4073.15, Comm 0.00

Test Script Execution

Detailed in the command line help:

$ ./order-execution-samples.py --help
usage: order-execution-samples.py [-h] [--infile INFILE]
[--csvformat
{bt,visualchart,sierrachart,yahoo,yahoo_unreversed}]
[--fromdate FROMDATE] [--todate TODATE]
[--plot] [--plotstyle {bar,line,candle}]
[--numfigs NUMFIGS] [--smaperiod SMAPERIOD]
[--exectype EXECTYPE] [--valid VALID]
[--perc1 PERC1] [--perc2 PERC2]

Showcase for Order Execution Types

optional arguments:
-h, --help show this help message and exit
--infile INFILE, -i INFILE
File to be read in
--csvformat {bt,visualchart,sierrachart,yahoo,yahoo_unreversed},
-c {bt,visualchart,sierrachart,yahoo,yahoo_unreversed}
CSV Format
--fromdate FROMDATE, -f FROMDATE
Starting date in YYYY-MM-DD format
--todate TODATE, -t TODATE
Ending date in YYYY-MM-DD format
--plot, -p Plot the read data
209
backtrader’s documentation Version-1.9.58.122

--plotstyle {bar,line,candle}, -ps {bar,line,candle}


Plot the read data
--numfigs NUMFIGS, -n NUMFIGS
Plot using n figures
--smaperiod SMAPERIOD, -s SMAPERIOD
Simple Moving Average Period
--exectype EXECTYPE, -e EXECTYPE
Execution Type: Market (default), Close, Limit,
Stop, StopLimit
--valid VALID, -v VALID
Validity for Limit sample: default 0 days
--perc1 PERC1, -p1 PERC1
% distance from close price at order creation time for
the limit/trigger price in Limit/Stop orders
--perc2 PERC2, -p2 PERC2
% distance from close price at order creation time for
the limit price in StopLimit orders

The full code

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
import datetime
import os.path
import time
import sys

import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind

class OrderExecutionStrategy(bt.Strategy):
params = (
('smaperiod', 15),
('exectype', 'Market'),
('perc1', 3),
('perc2', 1),
('valid', 4),
)

210
backtrader’s documentation Version-1.9.58.122

def log(self, txt, dt=None):


''' Logging function fot this strategy'''
dt = dt or self.data.datetime[0]
if isinstance(dt, float):
dt = bt.num2date(dt)
print('%s, %s' % (dt.isoformat(), txt))

def notify_order(self, order):


if order.status in [order.Submitted, order.Accepted]:
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
self.log('ORDER ACCEPTED/SUBMITTED', dt=order.created.dt)
self.order = order
return

if order.status in [order.Expired]:
self.log('BUY EXPIRED')

elif order.status in [order.Completed]:


if order.isbuy():
self.log(
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))

else: # Sell
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))

# Sentinel to None: new orders allowed


self.order = None

def __init__(self):
# SimpleMovingAverage on main data
# Equivalent to -> sma = btind.SMA(self.data, period=self.p.smaperiod)
sma = btind.SMA(period=self.p.smaperiod)

# CrossOver (1: up, -1: down) close / sma


self.buysell = btind.CrossOver(self.data.close, sma, plot=True)

# Sentinel to None: new ordersa allowed


211
backtrader’s documentation Version-1.9.58.122

self.order = None

def next(self):
if self.order:
# An order is pending ... nothing can be done
return

# Check if we are in the market


if self.position:
# In the maerket - check if it's the time to sell
if self.buysell < 0:
self.log('SELL CREATE, %.2f' % self.data.close[0])
self.sell()

elif self.buysell > 0:


if self.p.valid:
valid = self.data.datetime.date(0) + \
datetime.timedelta(days=self.p.valid)
else:
valid = None

# Not in the market and signal to buy


if self.p.exectype == 'Market':
self.buy(exectype=bt.Order.Market) # default if not given

self.log('BUY CREATE, exectype Market, price %.2f' %


self.data.close[0])

elif self.p.exectype == 'Close':


self.buy(exectype=bt.Order.Close)

self.log('BUY CREATE, exectype Close, price %.2f' %


self.data.close[0])

elif self.p.exectype == 'Limit':


price = self.data.close * (1.0 - self.p.perc1 / 100.0)

self.buy(exectype=bt.Order.Limit, price=price, valid=valid)

if self.p.valid:
txt = 'BUY CREATE, exectype Limit, price %.2f, valid: %s'
self.log(txt % (price, valid.strftime('%Y-%m-%d')))
else:
txt = 'BUY CREATE, exectype Limit, price %.2f'
212
backtrader’s documentation Version-1.9.58.122

self.log(txt % price)

elif self.p.exectype == 'Stop':


price = self.data.close * (1.0 + self.p.perc1 / 100.0)

self.buy(exectype=bt.Order.Stop, price=price, valid=valid)

if self.p.valid:
txt = 'BUY CREATE, exectype Stop, price %.2f, valid: %s'
self.log(txt % (price, valid.strftime('%Y-%m-%d')))
else:
txt = 'BUY CREATE, exectype Stop, price %.2f'
self.log(txt % price)

elif self.p.exectype == 'StopLimit':


price = self.data.close * (1.0 + self.p.perc1 / 100.0)

plimit = self.data.close * (1.0 + self.p.perc2 / 100.0)

self.buy(exectype=bt.Order.StopLimit, price=price, valid=valid,


plimit=plimit)

if self.p.valid:
txt = ('BUY CREATE, exectype StopLimit, price %.2f,'
' valid: %s, pricelimit: %.2f')
self.log(txt % (price, valid.strftime('%Y-%m-%d'), plimit))
else:
txt = ('BUY CREATE, exectype StopLimit, price %.2f,'
' pricelimit: %.2f')
self.log(txt % (price, plimit))

def runstrat():
args = parse_args()

cerebro = bt.Cerebro()

data = getdata(args)
cerebro.adddata(data)

cerebro.addstrategy(
OrderExecutionStrategy,
exectype=args.exectype,
perc1=args.perc1,
213
backtrader’s documentation Version-1.9.58.122

perc2=args.perc2,
valid=args.valid,
smaperiod=args.smaperiod
)
cerebro.run()

if args.plot:
cerebro.plot(numfigs=args.numfigs, style=args.plotstyle)

def getdata(args):

dataformat = dict(
bt=btfeeds.BacktraderCSVData,
visualchart=btfeeds.VChartCSVData,
sierrachart=btfeeds.SierraChartCSVData,
yahoo=btfeeds.YahooFinanceCSVData,
yahoo_unreversed=btfeeds.YahooFinanceCSVData
)

dfkwargs = dict()
if args.csvformat == 'yahoo_unreversed':
dfkwargs['reverse'] = True

if args.fromdate:
fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
dfkwargs['fromdate'] = fromdate

if args.todate:
fromdate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
dfkwargs['todate'] = todate

dfkwargs['dataname'] = args.infile

dfcls = dataformat[args.csvformat]

return dfcls(**dfkwargs)

def parse_args():
parser = argparse.ArgumentParser(
description='Showcase for Order Execution Types')

parser.add_argument('--infile', '-i', required=False,


214
backtrader’s documentation Version-1.9.58.122

default='../../datas/2006-day-001.txt',
help='File to be read in')

parser.add_argument('--csvformat', '-c', required=False, default='bt',


choices=['bt', 'visualchart', 'sierrachart',
'yahoo', 'yahoo_unreversed'],
help='CSV Format')

parser.add_argument('--fromdate', '-f', required=False, default=None,


help='Starting date in YYYY-MM-DD format')

parser.add_argument('--todate', '-t', required=False, default=None,


help='Ending date in YYYY-MM-DD format')

parser.add_argument('--plot', '-p', action='store_false', required=False,


help='Plot the read data')

parser.add_argument('--plotstyle', '-ps', required=False, default='bar',


choices=['bar', 'line', 'candle'],
help='Plot the read data')

parser.add_argument('--numfigs', '-n', required=False, default=1,


help='Plot using n figures')

parser.add_argument('--smaperiod', '-s', required=False, default=15,


help='Simple Moving Average Period')

parser.add_argument('--exectype', '-e', required=False, default='Market',


help=('Execution Type: Market (default), Close, Limit,'
' Stop, StopLimit'))

parser.add_argument('--valid', '-v', required=False, default=0, type=int,


help='Validity for Limit sample: default 0 days')

parser.add_argument('--perc1', '-p1', required=False, default=0.0,


type=float,
help=('%% distance from close price at order creation'
' time for the limit/trigger price in Limit/Stop'
' orders'))

parser.add_argument('--perc2', '-p2', required=False, default=0.0,


type=float,
help=('%% distance from close price at order creation'
' time for the limit price in StopLimit orders'))
215
backtrader’s documentation Version-1.9.58.122

return parser.parse_args()

if __name__ == '__main__':
runstrat()

OCO orders

Release 1.9.34.116 adds OCO (aka One Cancel Others) to the backtesting arsenal.

Note

This is only implemented in backtesting and there isn’t yet an implementation


for live brokers

Note

Updated with release 1.9.36.116. Interactive Brokers support for StopTrail,


StopTrailLimit and OCO.

• OCO Specify always the 1st order in a group as parameter oco


• StopTrailLimit: the broker simulation and the IB broker have the asme behavior. Specify: price as the initial
stop trigger price (specify also trailamount) and then plimi as the initial limit price. The difference
between the two will determine the limitoffset (the distance at which the limit price remains from the
stop trigger price)

The usage pattern tries to remain user friendly. As such and if the logic in the
strategy has decided it is the moment to issue orders, using OCO can be done like
this:

def next(self):
...
o1 = self.buy(...)
...
o2 = self.buy(..., oco=o1)
...
o3 = self.buy(..., oco=o1) # or even oco=o2, o2 is already in o1 group

Easy. The 1st order o1 will something like the group leader. o2 and o3 become
part of the OCO Group by specifying o1 with the oco named argument. See that the
comment in the snippet indicates that o3 could have also become part of the group
by specifying o2 (which as already part of the group)

216
backtrader’s documentation Version-1.9.58.122

With the group formed the following will happen:

• If any order in the group is executed, cancelled or expires, the other orders will be cancelled

The sample below puts the OCO concept in play. A standard execution with a plot:

$ ./oco.py --broker cash=50000 --plot

Note

cash is increased to 50000, because the asset reaches values of 4000 and 3 orders
of 1 item would require at least 12000 monetary units (the default in the broker
is 10000)

With the following chart.

which actually doesn’t provide much information (it is a standard SMA Crossover
strategy). The sample does the following:
217
backtrader’s documentation Version-1.9.58.122

• When the fast SMA crosses the slow SMA to the upside 3 orders are issued
• order1 is a Limit order which will expire in limdays days (parameter to the strategy) with the close price
reduced by a percentage as the limit price
• order2 is a Limit order with a much longer period to expire and a much more reduced limit price.
• order3 is a Limit order which further reduces the limit price

As such the execution of order2 and order3 is not going to happen because:

• order1 will be executed first and this should trigger the cancellation of the others

or

• order1 will expire and this will trigger the the cancellation of the others

The system keeps the ref identifier of the 3 orders and will only issue new buy
orders if the three ref identifiers are seen in notify_order as either Completed,
Cancelled, Margin or Expired

Exiting is simply done after holding the position for some bars.

To try to keep track of the actual execution, textual output is produced. Some of
it:

2005-01-28: Oref 1 / Buy at 2941.11055


2005-01-28: Oref 2 / Buy at 2896.7722
2005-01-28: Oref 3 / Buy at 2822.87495
2005-01-31: Order ref: 1 / Type Buy / Status Submitted
2005-01-31: Order ref: 2 / Type Buy / Status Submitted
2005-01-31: Order ref: 3 / Type Buy / Status Submitted
2005-01-31: Order ref: 1 / Type Buy / Status Accepted
2005-01-31: Order ref: 2 / Type Buy / Status Accepted
2005-01-31: Order ref: 3 / Type Buy / Status Accepted
2005-02-01: Order ref: 1 / Type Buy / Status Expired
2005-02-01: Order ref: 3 / Type Buy / Status Canceled
2005-02-01: Order ref: 2 / Type Buy / Status Canceled
...
2006-06-23: Oref 49 / Buy at 3532.39925
2006-06-23: Oref 50 / Buy at 3479.147
2006-06-23: Oref 51 / Buy at 3390.39325
2006-06-26: Order ref: 49 / Type Buy / Status Submitted
2006-06-26: Order ref: 50 / Type Buy / Status Submitted
2006-06-26: Order ref: 51 / Type Buy / Status Submitted
2006-06-26: Order ref: 49 / Type Buy / Status Accepted
2006-06-26: Order ref: 50 / Type Buy / Status Accepted
2006-06-26: Order ref: 51 / Type Buy / Status Accepted
218
backtrader’s documentation Version-1.9.58.122

2006-06-26: Order ref: 49 / Type Buy / Status Completed


2006-06-26: Order ref: 51 / Type Buy / Status Canceled
2006-06-26: Order ref: 50 / Type Buy / Status Canceled
...
2006-11-10: Order ref: 61 / Type Buy / Status Canceled
2006-12-11: Oref 63 / Buy at 4032.62555
2006-12-11: Oref 64 / Buy at 3971.8322
2006-12-11: Oref 65 / Buy at 3870.50995
2006-12-12: Order ref: 63 / Type Buy / Status Submitted
2006-12-12: Order ref: 64 / Type Buy / Status Submitted
2006-12-12: Order ref: 65 / Type Buy / Status Submitted
2006-12-12: Order ref: 63 / Type Buy / Status Accepted
2006-12-12: Order ref: 64 / Type Buy / Status Accepted
2006-12-12: Order ref: 65 / Type Buy / Status Accepted
2006-12-15: Order ref: 63 / Type Buy / Status Expired
2006-12-15: Order ref: 65 / Type Buy / Status Canceled
2006-12-15: Order ref: 64 / Type Buy / Status Canceled

With the following happening:

• The 1st batch of orders is issued. Order 1 expires and 2 and 3 are cancelled. As expected.
• Some months later another batch of 3 orders is issued. In this case Order 49 gets Completed and 50 and
51 are immediately cancelled
• The last batch is just like the 1st

Let’s check now the behavior without OCO:

$ ./oco.py --strat do_oco=False --broker cash=50000

2005-01-28: Oref 1 / Buy at 2941.11055


2005-01-28: Oref 2 / Buy at 2896.7722
2005-01-28: Oref 3 / Buy at 2822.87495
2005-01-31: Order ref: 1 / Type Buy / Status Submitted
2005-01-31: Order ref: 2 / Type Buy / Status Submitted
2005-01-31: Order ref: 3 / Type Buy / Status Submitted
2005-01-31: Order ref: 1 / Type Buy / Status Accepted
2005-01-31: Order ref: 2 / Type Buy / Status Accepted
2005-01-31: Order ref: 3 / Type Buy / Status Accepted
2005-02-01: Order ref: 1 / Type Buy / Status Expired

And that’s it, which isn’t much (no order execution, not much need for a chart
either)

• The batch of orders is issued

219
backtrader’s documentation Version-1.9.58.122

• Order 1 expires, but because the strategy has gotten the parameter do_oco=False, orders 2 and 3 are not
made part of the OCO group
• Orders 2 and 3 are therefore not cancelled and because the default expiration delta is 1000 days later,
they never expire with the available data for the sample (2 years of data)
• The system never issues a 2nd bath of orders.

Sample usage

$ ./oco.py --help
usage: oco.py [-h] [--data0 DATA0] [--fromdate FROMDATE] [--todate TODATE]
[--cerebro kwargs] [--broker kwargs] [--sizer kwargs]
[--strat kwargs] [--plot [kwargs]]

Sample Skeleton

optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to read in (default:
../../datas/2005-2006-day-001.txt)
--fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--cerebro kwargs kwargs in key=value format (default: )
--broker kwargs kwargs in key=value format (default: )
--sizer kwargs kwargs in key=value format (default: )
--strat kwargs kwargs in key=value format (default: )
--plot [kwargs] kwargs in key=value format (default: )

Sample Code

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
import datetime

import backtrader as bt

class St(bt.Strategy):
params = dict(
ma=bt.ind.SMA,
p1=5,
220
backtrader’s documentation Version-1.9.58.122

p2=15,
limit=0.005,
limdays=3,
limdays2=1000,
hold=10,
switchp1p2=False, # switch prices of order1 and order2
oco1oco2=False, # False - use order1 as oco for order3, else order2
do_oco=True, # use oco or not
)

def notify_order(self, order):


print('{}: Order ref: {} / Type {} / Status {}'.format(
self.data.datetime.date(0),
order.ref, 'Buy' * order.isbuy() or 'Sell',
order.getstatusname()))

if order.status == order.Completed:
self.holdstart = len(self)

if not order.alive() and order.ref in self.orefs:


self.orefs.remove(order.ref)

def __init__(self):
ma1, ma2 = self.p.ma(period=self.p.p1), self.p.ma(period=self.p.p2)
self.cross = bt.ind.CrossOver(ma1, ma2)

self.orefs = list()

def next(self):
if self.orefs:
return # pending orders do nothing

if not self.position:
if self.cross > 0.0: # crossing up

p1 = self.data.close[0] * (1.0 - self.p.limit)


p2 = self.data.close[0] * (1.0 - 2 * 2 * self.p.limit)
p3 = self.data.close[0] * (1.0 - 3 * 3 * self.p.limit)

if self.p.switchp1p2:
p1, p2 = p2, p1

o1 = self.buy(exectype=bt.Order.Limit, price=p1,
valid=datetime.timedelta(self.p.limdays))
221
backtrader’s documentation Version-1.9.58.122

print('{}: Oref {} / Buy at {}'.format(


self.datetime.date(), o1.ref, p1))

oco2 = o1 if self.p.do_oco else None


o2 = self.buy(exectype=bt.Order.Limit, price=p2,
valid=datetime.timedelta(self.p.limdays2),
oco=oco2)

print('{}: Oref {} / Buy at {}'.format(


self.datetime.date(), o2.ref, p2))

if self.p.do_oco:
oco3 = o1 if not self.p.oco1oco2 else oco2
else:
oco3 = None

o3 = self.buy(exectype=bt.Order.Limit, price=p3,
valid=datetime.timedelta(self.p.limdays2),
oco=oco3)

print('{}: Oref {} / Buy at {}'.format(


self.datetime.date(), o3.ref, p3))

self.orefs = [o1.ref, o2.ref, o3.ref]

else: # in the market


if (len(self) - self.holdstart) >= self.p.hold:
self.close()

def runstrat(args=None):
args = parse_args(args)

cerebro = bt.Cerebro()

# Data feed kwargs


kwargs = dict()

# Parse from/to-date
dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
if a:
strpfmt = dtfmt + tmfmt * ('T' in a)
222
backtrader’s documentation Version-1.9.58.122

kwargs[d] = datetime.datetime.strptime(a, strpfmt)

# Data feed
data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)
cerebro.adddata(data0)

# Broker
cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))

# Sizer
cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))

# Strategy
cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))

# Execute
cerebro.run(**eval('dict(' + args.cerebro + ')'))

if args.plot: # Plot if requested to


cerebro.plot(**eval('dict(' + args.plot + ')'))

def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=(
'Sample Skeleton'
)
)

parser.add_argument('--data0', default='../../datas/2005-2006-day-001.txt',
required=False, help='Data to read in')

# Defaults for dates


parser.add_argument('--fromdate', required=False, default='',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

parser.add_argument('--todate', required=False, default='',


help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

parser.add_argument('--cerebro', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--broker', required=False, default='',


223
backtrader’s documentation Version-1.9.58.122

metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--sizer', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--strat', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--plot', required=False, default='',


nargs='?', const='{}',
metavar='kwargs', help='kwargs in key=value format')

return parser.parse_args(pargs)

if __name__ == '__main__':
runstrat()

StopTrail(Limit)

Release 1.9.35.116 adds the StopTrail and StopTrailLimit order execution types to
the backtesting arsenal.

Note

This is only implemented in backtesting and there isn’t yet an implementation


for live brokers

Note

Updated with release 1.9.36.116. Interactive Brokers support for StopTrail,


StopTrailLimit and OCO.

• OCO Specify always the 1st order in a group as parameter oco


• StopTrailLimit: the broker simulation and the IB broker have the asme behavior. Specify: price as the initial
stop trigger price (specify also trailamount) and then plimi as the initial limit price. The difference
between the two will determine the limitoffset (the distance at which the limit price remains from the
stop trigger price)

The usage pattern is fully integrated into the standard buy, sell and close
market operation methods of the strategy instances. To notice:

• Indicate which execution type is wished as in exectype=bt.Order.StopTrail

224
backtrader’s documentation Version-1.9.58.122

• And whether the trailing price must be calculated with a fixed distance or with a percentage based
distance
o Fixed distance: trailamount=10
o Percentage based distance: trailpercent=0.02 (i.e.: 2%)

If one has entered the market long by issuing a buy, this is what a sell with
StopTrail and trailamount does:

• If no price is specified, the latest close price is used


• trailamount is substracted from the price to find the stop (or trigger) price
• The next iteration of the broker checks if the trigger price has been reached
o If Yes: the order is executed with a Market execution type approach
o If No, the stop price is recalculated by using the latest close price and substracting the
trailamount distance
If the new price goes up, it is updated
If the new price would go down (or not change at all), it is discarded

That is: the trailing stop price follows the price upwards, but remains fixed if
the prices start falling, to potentially secure a profit.

If one had entered the market with a sell, then issuing a buy order with
StopTrail simply does the opposite, i.e.: prices are followed downwards.

Some usage patterns

# For a StopTrail going downwards


# last price will be used as reference
self.buy(size=1, exectype=bt.Order.StopTrail, trailamount=0.25)
# or
self.buy(size=1, exectype=bt.Order.StopTrail, price=10.50, trailamount=0.25)

# For a StopTrail going upwards


# last price will be used as reference
self.sell(size=1, exectype=bt.Order.StopTrail, trailamount=0.25)
# or
self.sell(size=1, exectype=bt.Order.StopTrail, price=10.50, trailamount=0.25)

One can also specify trailpercent instead of trailamount and the distance to the
price will be calculated as a percentage of the price

# For a StopTrail going downwards with 2% distance


# last price will be used as reference
self.buy(size=1, exectype=bt.Order.StopTrail, trailpercent=0.02)
# or
self.buy(size=1, exectype=bt.Order.StopTrail, price=10.50, trailpercent=0.0.02)
225
backtrader’s documentation Version-1.9.58.122

# For a StopTrail going upwards with 2% difference


# last price will be used as reference
self.sell(size=1, exectype=bt.Order.StopTrail, trailpercent=0.02)
# or
self.sell(size=1, exectype=bt.Order.StopTrail, price=10.50, trailpercent=0.02)

For a StopTrailLimit

• The only difference is what happens when the trailing stop price is
triggered.
• In this case the order is executed as a Limit order (the same behavior a
StopLimit order has, but in this case with a dynamic triggering price)

Note

one has to specify plimit=x.x to buy or sell, which will be the limit price

Note

the limit price is not changed dynamically like the stop/trigger price

An example is always worth a thousand words and hence the usual backtrader
sample, which

• Uses a moving average crossing up to enter the market long


• Uses a trailing stop to exit the market

The execution with 50 points of fixed price distance

$ ./trail.py --plot --strat trailamount=50.0

Which produces the following chart

226
backtrader’s documentation Version-1.9.58.122

And the following output:

**************************************************
2005-02-14,3075.76,3025.76,3025.76
----------
2005-02-15,3086.95,3036.95,3036.95
2005-02-16,3068.55,3036.95,3018.55
2005-02-17,3067.34,3036.95,3017.34
2005-02-18,3072.04,3036.95,3022.04
2005-02-21,3063.64,3036.95,3013.64
...
...
**************************************************
2005-05-19,3051.79,3001.79,3001.79
----------
2005-05-20,3050.45,3001.79,3000.45
2005-05-23,3070.98,3020.98,3020.98

227
backtrader’s documentation Version-1.9.58.122

2005-05-24,3066.55,3020.98,3016.55
2005-05-25,3059.84,3020.98,3009.84
2005-05-26,3086.08,3036.08,3036.08
2005-05-27,3084.0,3036.08,3034.0
2005-05-30,3096.54,3046.54,3046.54
2005-05-31,3076.75,3046.54,3026.75
2005-06-01,3125.88,3075.88,3075.88
2005-06-02,3131.03,3081.03,3081.03
2005-06-03,3114.27,3081.03,3064.27
2005-06-06,3099.2,3081.03,3049.2
2005-06-07,3134.82,3084.82,3084.82
2005-06-08,3125.59,3084.82,3075.59
2005-06-09,3122.93,3084.82,3072.93
2005-06-10,3143.85,3093.85,3093.85
2005-06-13,3159.83,3109.83,3109.83
2005-06-14,3162.86,3112.86,3112.86
2005-06-15,3147.55,3112.86,3097.55
2005-06-16,3160.09,3112.86,3110.09
2005-06-17,3178.48,3128.48,3128.48
2005-06-20,3162.14,3128.48,3112.14
2005-06-21,3179.62,3129.62,3129.62
2005-06-22,3182.08,3132.08,3132.08
2005-06-23,3190.8,3140.8,3140.8
2005-06-24,3161.0,3140.8,3111.0
...
...
...
**************************************************
2006-12-19,4100.48,4050.48,4050.48
----------
2006-12-20,4118.54,4068.54,4068.54
2006-12-21,4112.1,4068.54,4062.1
2006-12-22,4073.5,4068.54,4023.5
2006-12-27,4134.86,4084.86,4084.86
2006-12-28,4130.66,4084.86,4080.66
2006-12-29,4119.94,4084.86,4069.94

Rather than waiting for the usual cross down pattern the system uses the trailing
stop to exit the market. Let’s see the 1st operation for example

• Closing price when entering long: 3075.76


• System calculated trail stop price: 3025.76 (which is 50 units away)
• Sample calculated trail stop price: 3025.76 (last price shown in each line)

After this first calculation:


228
backtrader’s documentation Version-1.9.58.122

• The closing price goes up to 3086.95 and the stop price is adjusted to 3036.95
• The following closing prices don’t exceed 3086.95 and the trigger price doesn’t change

The same pattern can be seen in the other 2 operations.

For the sake of comparison, an execution with just 30 points of fixed distance
(just the chart)

$ ./trail.py --plot --strat trailamount=30.0

And the chart

Followed by one last execution with trailpercent=0.02

$ ./trail.py --plot --strat trailpercent=0.02

The corresponding chart.

229
backtrader’s documentation Version-1.9.58.122

The sample usage

$ ./trail.py --help
usage: trail.py [-h] [--data0 DATA0] [--fromdate FROMDATE] [--todate TODATE]
[--cerebro kwargs] [--broker kwargs] [--sizer kwargs]
[--strat kwargs] [--plot [kwargs]]

StopTrail Sample

optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to read in (default:
../../datas/2005-2006-day-001.txt)
--fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--cerebro kwargs kwargs in key=value format (default: )
--broker kwargs kwargs in key=value format (default: )

230
backtrader’s documentation Version-1.9.58.122

--sizer kwargs kwargs in key=value format (default: )


--strat kwargs kwargs in key=value format (default: )
--plot [kwargs] kwargs in key=value format (default: )

The sample code

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
import datetime

import backtrader as bt

class St(bt.Strategy):
params = dict(
ma=bt.ind.SMA,
p1=10,
p2=30,
stoptype=bt.Order.StopTrail,
trailamount=0.0,
trailpercent=0.0,
)

def __init__(self):
ma1, ma2 = self.p.ma(period=self.p.p1), self.p.ma(period=self.p.p2)
self.crup = bt.ind.CrossUp(ma1, ma2)
self.order = None

def next(self):
if not self.position:
if self.crup:
o = self.buy()
self.order = None
print('*' * 50)

elif self.order is None:


self.order = self.sell(exectype=self.p.stoptype,
trailamount=self.p.trailamount,
trailpercent=self.p.trailpercent)

if self.p.trailamount:
tcheck = self.data.close - self.p.trailamount

231
backtrader’s documentation Version-1.9.58.122

else:
tcheck = self.data.close * (1.0 - self.p.trailpercent)
print(','.join(
map(str, [self.datetime.date(), self.data.close[0],
self.order.created.price, tcheck])
)
)
print('-' * 10)
else:
if self.p.trailamount:
tcheck = self.data.close - self.p.trailamount
else:
tcheck = self.data.close * (1.0 - self.p.trailpercent)
print(','.join(
map(str, [self.datetime.date(), self.data.close[0],
self.order.created.price, tcheck])
)
)

def runstrat(args=None):
args = parse_args(args)

cerebro = bt.Cerebro()

# Data feed kwargs


kwargs = dict()

# Parse from/to-date
dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
if a:
strpfmt = dtfmt + tmfmt * ('T' in a)
kwargs[d] = datetime.datetime.strptime(a, strpfmt)

# Data feed
data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)
cerebro.adddata(data0)

# Broker
cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))

# Sizer
cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))
232
backtrader’s documentation Version-1.9.58.122

# Strategy
cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))

# Execute
cerebro.run(**eval('dict(' + args.cerebro + ')'))

if args.plot: # Plot if requested to


cerebro.plot(**eval('dict(' + args.plot + ')'))

def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=(
'StopTrail Sample'
)
)

parser.add_argument('--data0', default='../../datas/2005-2006-day-001.txt',
required=False, help='Data to read in')

# Defaults for dates


parser.add_argument('--fromdate', required=False, default='',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

parser.add_argument('--todate', required=False, default='',


help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

parser.add_argument('--cerebro', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--broker', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--sizer', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--strat', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--plot', required=False, default='',


nargs='?', const='{}',
metavar='kwargs', help='kwargs in key=value format')
233
backtrader’s documentation Version-1.9.58.122

return parser.parse_args(pargs)

if __name__ == '__main__':
runstrat()

Bracket Orders

Release 1.9.37.116 adds bracket orders giving a very broad spectrum of orders
which are supported by the backtesting broker (Market, Limit, Close, Stop,
StopLimit, StopTrail, StopTrailLimit, OCO)

Note

This is implemented for backtesting and for the Interactivers Brokers store

A bracket order isn’s a single order but it is actually made up of 3 orders.


Let’s consider the long side

• A main side buy order, usually set to be a Limit or StopLimit order


• A low side sell order, usually set to be a Stop order to limit losses
• A high side sell order, usually set to be a Limit order to take profit

With corresponding sell and 2 x buy orders for the short side.

The low/high side orders do actually create a bracket around the main side order.

To put some logic into it, the following rules apply:

• The 3 orders are submitted together to avoid having any of them triggered independently
• The low/high side orders are marked as children of the main side
• The children are not active until the main side is executed
• The cancellation of the main side cancels both the low and high side
• The execution of the main side activates both the low and high side
• Upon being active
o The execution or cancellation of any of low/high side orders automatically cancels the other

Usage Pattern

There are two possibilities to create the bracket set of orders

• Single issuing of the 3 orders


• Manual issuing of the 3 orders
234
backtrader’s documentation Version-1.9.58.122

Single Issuing of a Bracket

backtrader offers two new methods in the Strategy to control bracket orders.

• buy_bracket and sell_bracket

Note

Signature and info below or in the Strategy reference section.

With a single statement a complete set of 3 orders. An example:

brackets = self.buy_bracket(limitprice=14.00, price=13.50, stopprice=13.00)

Notice how stopprice and limitprice wrap the main price

This should be enough. The actual target data would be data0 and the size would
be automatically determined by the default sizer. Of course both and many other
parameters can be specified to have a fine control of the execution.

The return value is:

• A list containing the 3 orders in this order: [main, stop, limit]

Because when issuing a sell_bracket order, the low and high sides would be turned
aound, the parameters are named following convention stop and limit

• stop is meant to stop the losses (low side in a long operation, and high side in a short operation)
• limit is meant to take the profit (high side in a long operation and low side in a short operation)

Manual Issuing of a Bracket

This involves the generation of the 3 orders and playing around with the transmit
and parent arguments. The rules:

• The main side order must be created 1st and have transmit=False
• The low/high side orders must have parent=main_side_order
• The 1st low/high side order to be created must have transmit=False
• The last order to be created (either the low or high side) sets transmit=True

A practical example doing what the single command from above did:

mainside = self.buy(price=13.50, exectype=bt.Order.Limit, transmit=False)


lowside = self.sell(price=13.00, size=mainsize.size, exectype=bt.Order.Stop,
235
backtrader’s documentation Version-1.9.58.122

transmit=False, parent=mainside)
highside = self.sell(price=14.00, size=mainsize.size, exectype=bt.Order.Limit,
transmit=True, parent=mainside)

Where there is a lot more to do:

• Keep track of the mainside order to indicate it is the parent of the others
• Control transmit to make sure only the last order triggers the joint
transmission
• Specify the execution types
• Specify the size for the low and high side

Because the size MUST be the same. If the parameter were not specified
manually and the end user had introduced a sizer, the sizer could actually
indicate a different value for the orders. That’s why it has to be
manually added to the calls after it has been set for the mainside order.

A sample of it

Running the sample from below produces this output (capped for brevity)

$ ./bracket.py --plot

2005-01-28: Oref 1 / Buy at 2941.11055


2005-01-28: Oref 2 / Sell Stop at 2881.99275
2005-01-28: Oref 3 / Sell Limit at 3000.22835
2005-01-31: Order ref: 1 / Type Buy / Status Submitted
2005-01-31: Order ref: 2 / Type Sell / Status Submitted
2005-01-31: Order ref: 3 / Type Sell / Status Submitted
2005-01-31: Order ref: 1 / Type Buy / Status Accepted
2005-01-31: Order ref: 2 / Type Sell / Status Accepted
2005-01-31: Order ref: 3 / Type Sell / Status Accepted
2005-02-01: Order ref: 1 / Type Buy / Status Expired
2005-02-01: Order ref: 2 / Type Sell / Status Canceled
2005-02-01: Order ref: 3 / Type Sell / Status Canceled
...
2005-08-11: Oref 16 / Buy at 3337.3892
2005-08-11: Oref 17 / Sell Stop at 3270.306
2005-08-11: Oref 18 / Sell Limit at 3404.4724
2005-08-12: Order ref: 16 / Type Buy / Status Submitted
2005-08-12: Order ref: 17 / Type Sell / Status Submitted
2005-08-12: Order ref: 18 / Type Sell / Status Submitted
2005-08-12: Order ref: 16 / Type Buy / Status Accepted
2005-08-12: Order ref: 17 / Type Sell / Status Accepted
236
backtrader’s documentation Version-1.9.58.122

2005-08-12: Order ref: 18 / Type Sell / Status Accepted


2005-08-12: Order ref: 16 / Type Buy / Status Completed
2005-08-18: Order ref: 17 / Type Sell / Status Completed
2005-08-18: Order ref: 18 / Type Sell / Status Canceled
...
2005-09-26: Oref 22 / Buy at 3383.92535
2005-09-26: Oref 23 / Sell Stop at 3315.90675
2005-09-26: Oref 24 / Sell Limit at 3451.94395
2005-09-27: Order ref: 22 / Type Buy / Status Submitted
2005-09-27: Order ref: 23 / Type Sell / Status Submitted
2005-09-27: Order ref: 24 / Type Sell / Status Submitted
2005-09-27: Order ref: 22 / Type Buy / Status Accepted
2005-09-27: Order ref: 23 / Type Sell / Status Accepted
2005-09-27: Order ref: 24 / Type Sell / Status Accepted
2005-09-27: Order ref: 22 / Type Buy / Status Completed
2005-10-04: Order ref: 24 / Type Sell / Status Completed
2005-10-04: Order ref: 23 / Type Sell / Status Canceled
...

Where 3 different outcomes are shown:

• In the 1st case the main side order expired and this automatically
cancelled the other two
• In the 2nd case the main side order was completed and the low (stop in the
buy case) was executed limiting losses
• In the 3rd case the main side order was completed and the high side (limit)
was executed

This can be noticed because the Completed ids are 22 and 24 and the high
side order is being issued last, which means the non-executed low side
order has id 23.

Visually

237
backtrader’s documentation Version-1.9.58.122

It can be immediately seen that the losing trades align around the same value and
winning trades too, which is the purpose of the backeting. Controlling both
sides.

The sample as run issues the 3 orders manually, but it can be told to use
buy_bracket. Let’s see the output:

$ ./bracket.py --strat usebracket=True

With the same result

238
backtrader’s documentation Version-1.9.58.122

Some reference

See the new buy_bracket and sell_bracket methods

def buy_bracket(self, data=None, size=None, price=None, plimit=None,


exectype=bt.Order.Limit, valid=None, tradeid=0,
trailamount=None, trailpercent=None, oargs={},
stopprice=None, stopexec=bt.Order.Stop, stopargs={},
limitprice=None, limitexec=bt.Order.Limit, limitargs={},
**kwargs):
'''
Create a bracket order group (low side - buy order - high side). The
default behavior is as follows:

- Issue a **buy** order with execution ``Limit`

- Issue a *low side* bracket **sell** order with execution ``Stop``


239
backtrader’s documentation Version-1.9.58.122

- Issue a *high side* bracket **sell** order with execution


``Limit``.

See below for the different parameters

- ``data`` (default: ``None``)

For which data the order has to be created. If ``None`` then the
first data in the system, ``self.datas[0] or self.data0`` (aka
``self.data``) will be used

- ``size`` (default: ``None``)

Size to use (positive) of units of data to use for the order.

If ``None`` the ``sizer`` instance retrieved via ``getsizer`` will


be used to determine the size.

**Note**: The same size is applied to all 3 orders of the bracket

- ``price`` (default: ``None``)

Price to use (live brokers may place restrictions on the actual


format if it does not comply to minimum tick size requirements)

``None`` is valid for ``Market`` and ``Close`` orders (the market


determines the price)

For ``Limit``, ``Stop`` and ``StopLimit`` orders this value


determines the trigger point (in the case of ``Limit`` the trigger
is obviously at which price the order should be matched)

- ``plimit`` (default: ``None``)

Only applicable to ``StopLimit`` orders. This is the price at which


to set the implicit *Limit* order, once the *Stop* has been
triggered (for which ``price`` has been used)

- ``trailamount`` (default: ``None``)

If the order type is StopTrail or StopTrailLimit, this is an


absolute amount which determines the distance to the price (below
for a Sell order and above for a buy order) to keep the trailing
240
backtrader’s documentation Version-1.9.58.122

stop

- ``trailpercent`` (default: ``None``)

If the order type is StopTrail or StopTrailLimit, this is a


percentage amount which determines the distance to the price (below
for a Sell order and above for a buy order) to keep the trailing
stop (if ``trailamount`` is also specified it will be used)

- ``exectype`` (default: ``bt.Order.Limit``)

Possible values: (see the documentation for the method ``buy``

- ``valid`` (default: ``None``)

Possible values: (see the documentation for the method ``buy``

- ``tradeid`` (default: ``0``)

Possible values: (see the documentation for the method ``buy``

- ``oargs`` (default: ``{}``)

Specific keyword arguments (in a ``dict``) to pass to the main side


order. Arguments from the default ``**kwargs`` will be applied on
top of this.

- ``**kwargs``: additional broker implementations may support extra


parameters. ``backtrader`` will pass the *kwargs* down to the
created order objects

Possible values: (see the documentation for the method ``buy``

**Note**: this ``kwargs`` will be applied to the 3 orders of a


bracket. See below for specific keyword arguments for the low and
high side orders

- ``stopprice`` (default: ``None``)

Specific price for the *low side* stop order

- ``stopexec`` (default: ``bt.Order.Stop``)

Specific execution type for the *low side* order


241
backtrader’s documentation Version-1.9.58.122

- ``stopargs`` (default: ``{}``)

Specific keyword arguments (in a ``dict``) to pass to the low side


order. Arguments from the default ``**kwargs`` will be applied on
top of this.

- ``limitprice`` (default: ``None``)

Specific price for the *high side* stop order

- ``stopexec`` (default: ``bt.Order.Limit``)

Specific execution type for the *high side* order

- ``limitargs`` (default: ``{}``)

Specific keyword arguments (in a ``dict``) to pass to the high side


order. Arguments from the default ``**kwargs`` will be applied on
top of this.

Returns:
- A list containing the 3 bracket orders [order, stop side, limit
side]
'''

def sell_bracket(self, data=None,


size=None, price=None, plimit=None,
exectype=bt.Order.Limit, valid=None, tradeid=0,
trailamount=None, trailpercent=None,
oargs={},
stopprice=None, stopexec=bt.Order.Stop, stopargs={},
limitprice=None, limitexec=bt.Order.Limit, limitargs={},
**kwargs):
'''
Create a bracket order group (low side - buy order - high side). The
default behavior is as follows:

- Issue a **sell** order with execution ``Limit`

- Issue a *high side* bracket **buy** order with execution ``Stop``

- Issue a *low side* bracket **buy** order with execution ``Limit``.

242
backtrader’s documentation Version-1.9.58.122

See ``bracket_buy`` for the meaning of the parameters

Returns:
- A list containing the 3 bracket orders [order, stop side, limit
side]
'''

Sample usage

$ ./bracket.py --help
usage: bracket.py [-h] [--data0 DATA0] [--fromdate FROMDATE] [--todate TODATE]
[--cerebro kwargs] [--broker kwargs] [--sizer kwargs]
[--strat kwargs] [--plot [kwargs]]

Sample Skeleton

optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to read in (default:
../../datas/2005-2006-day-001.txt)
--fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--cerebro kwargs kwargs in key=value format (default: )
--broker kwargs kwargs in key=value format (default: )
--sizer kwargs kwargs in key=value format (default: )
--strat kwargs kwargs in key=value format (default: )
--plot [kwargs] kwargs in key=value format (default: )

Sample Code

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
import datetime

import backtrader as bt

class St(bt.Strategy):
params = dict(
ma=bt.ind.SMA,
243
backtrader’s documentation Version-1.9.58.122

p1=5,
p2=15,
limit=0.005,
limdays=3,
limdays2=1000,
hold=10,
usebracket=False, # use order_target_size
switchp1p2=False, # switch prices of order1 and order2
)

def notify_order(self, order):


print('{}: Order ref: {} / Type {} / Status {}'.format(
self.data.datetime.date(0),
order.ref, 'Buy' * order.isbuy() or 'Sell',
order.getstatusname()))

if order.status == order.Completed:
self.holdstart = len(self)

if not order.alive() and order.ref in self.orefs:


self.orefs.remove(order.ref)

def __init__(self):
ma1, ma2 = self.p.ma(period=self.p.p1), self.p.ma(period=self.p.p2)
self.cross = bt.ind.CrossOver(ma1, ma2)

self.orefs = list()

if self.p.usebracket:
print('-' * 5, 'Using buy_bracket')

def next(self):
if self.orefs:
return # pending orders do nothing

if not self.position:
if self.cross > 0.0: # crossing up

close = self.data.close[0]
p1 = close * (1.0 - self.p.limit)
p2 = p1 - 0.02 * close
p3 = p1 + 0.02 * close

valid1 = datetime.timedelta(self.p.limdays)
244
backtrader’s documentation Version-1.9.58.122

valid2 = valid3 = datetime.timedelta(self.p.limdays2)

if self.p.switchp1p2:
p1, p2 = p2, p1
valid1, valid2 = valid2, valid1

if not self.p.usebracket:
o1 = self.buy(exectype=bt.Order.Limit,
price=p1,
valid=valid1,
transmit=False)

print('{}: Oref {} / Buy at {}'.format(


self.datetime.date(), o1.ref, p1))

o2 = self.sell(exectype=bt.Order.Stop,
price=p2,
valid=valid2,
parent=o1,
transmit=False)

print('{}: Oref {} / Sell Stop at {}'.format(


self.datetime.date(), o2.ref, p2))

o3 = self.sell(exectype=bt.Order.Limit,
price=p3,
valid=valid3,
parent=o1,
transmit=True)

print('{}: Oref {} / Sell Limit at {}'.format(


self.datetime.date(), o3.ref, p3))

self.orefs = [o1.ref, o2.ref, o3.ref]

else:
os = self.buy_bracket(
price=p1, valid=valid1,
stopprice=p2, stopargs=dict(valid=valid2),
limitprice=p3, limitargs=dict(valid=valid3),)

self.orefs = [o.ref for o in os]

else: # in the market


245
backtrader’s documentation Version-1.9.58.122

if (len(self) - self.holdstart) >= self.p.hold:


pass # do nothing in this case

def runstrat(args=None):
args = parse_args(args)

cerebro = bt.Cerebro()

# Data feed kwargs


kwargs = dict()

# Parse from/to-date
dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
if a:
strpfmt = dtfmt + tmfmt * ('T' in a)
kwargs[d] = datetime.datetime.strptime(a, strpfmt)

# Data feed
data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)
cerebro.adddata(data0)

# Broker
cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))

# Sizer
cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))

# Strategy
cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))

# Execute
cerebro.run(**eval('dict(' + args.cerebro + ')'))

if args.plot: # Plot if requested to


cerebro.plot(**eval('dict(' + args.plot + ')'))

def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=(
'Sample Skeleton'
246
backtrader’s documentation Version-1.9.58.122

)
)

parser.add_argument('--data0', default='../../datas/2005-2006-day-001.txt',
required=False, help='Data to read in')

# Defaults for dates


parser.add_argument('--fromdate', required=False, default='',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

parser.add_argument('--todate', required=False, default='',


help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

parser.add_argument('--cerebro', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--broker', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--sizer', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--strat', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--plot', required=False, default='',


nargs='?', const='{}',
metavar='kwargs', help='kwargs in key=value format')

return parser.parse_args(pargs)

if __name__ == '__main__':
runstrat()

Futures and Spot Compensation

Release 1.9.32.116 adds support for an interesting use case presented in the
Community

• Start a trade with a future, which includes physical delivery


• Have an indicator tell you something

247
backtrader’s documentation Version-1.9.58.122

• If needed be, close the position by operating on the spot price,


effectively canceling the physical delivery, be it for receiving the goods
or for having to deliver them (and hopefully making a profit)

The future expires on the same day the operation on the spot price takes
place

That means:

• The platform is fed with data points from two different assets
• The platform has to somehow understand the assets are related and that
operations on the spot price will close positions open on the future

In reality, the future is not closed, only the physical delivery is


compensated

Using that compensation concept, backtrader adds a way to let the user
communicate to the platform that things on one data feed will have compensating
effects on another. The usage pattern

import backtrader as bt

cerebro = bt.Cerebro()

data0 = bt.feeds.MyFavouriteDataFeed(dataname='futurename')
cerebro.adddata(data0)

data1 = bt.feeds.MyFavouriteDataFeed(dataname='spotname')
data1.compensate(data0) # let the system know ops on data1 affect data0
cerebro.adddata(data1)

...

cerebro.run()

Putting it all together

An example is always worth a thousand posts, so let’s put all the pieces
together for it.

• Use one of the standard sample feeds from the backtrader sources. This will
be the future

248
backtrader’s documentation Version-1.9.58.122

• Simulate a similar but distinct price, by reusing the same feed and adding
a filter which will randomly move the price some points above/below, to
create a spread. As simple as:
• # The filter which changes the close price
• def close_changer(data, *args, **kwargs):
• data.close[0] += 50.0 * random.randint(-1, 1)
• return False # length of stream is unchanged
• Plotting on the same axis will mix the default included BuyObserver markers
and therefore the standard observers will be disabled and manually readded
to plot with different per-data markers
• Positions will be entered randomly and exited 10 days later

This doesn’t match future expiration periods, but this is just putting the
functionality in place and not checking a trading calendar

Note

A simulation including execution on the spot price on the day of future


expiration would require activating cheat-on-close to make sure the orders are
executed when the future expires. This is not needed in this sample, because the
expiration is being chosen at random.

• Notice that the strategy


o buy operations are executed on data0
o sell operations are executed on data1
• class St(bt.Strategy):
• def __init__(self):
• bt.obs.BuySell(self.data0, barplot=True) # done here for
• BuySellArrows(self.data1, barplot=True) # different markers per
data

• def next(self):
• if not self.position:
• if random.randint(0, 1):
• self.buy(data=self.data0)
• self.entered = len(self)

• else: # in the market
• if (len(self) - self.entered) >= 10:
• self.sell(data=self.data1)

The execution:

$ ./future-spot.py --no-comp

249
backtrader’s documentation Version-1.9.58.122

With this graphical output.

And it works:

• buy operations are signaled with a green triangle pointing upwards and the legend tells us they belong to
data0 as expected
• sell operations are signaled with an arrow pointing downwards and the legend tells us they belong to
data1 as expected
• Trades are being closed, even if they are being open with data0 and being closed with data1, achieving
the desired effect (which in real life is avoiding the physical delivery of the goods acquired by means of
the future)

One could only imagine what would happen if the same logic is applied without the
compensation taking place. Let’s do it:

$ ./future-spot.py --no-comp

And the output


250
backtrader’s documentation Version-1.9.58.122

It should be quite obvious that this fails miserably:

• The logic expects positions on data0 to be closed by the operations on data1 and to only open positions
on data0 when not in the market
• But compensation has been deactivated and the intial operation on data0 (green triangle) is never closed,
so no other operation can never be initiated and short positions on data1 start accumulating.

Sample Usage

$ ./future-spot.py --help
usage: future-spot.py [-h] [--no-comp]

Compensation example

optional arguments:
-h, --help show this help message and exit
--no-comp
251
backtrader’s documentation Version-1.9.58.122

Sample Code

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
import random
import backtrader as bt

# The filter which changes the close price


def close_changer(data, *args, **kwargs):
data.close[0] += 50.0 * random.randint(-1, 1)
return False # length of stream is unchanged

# override the standard markers


class BuySellArrows(bt.observers.BuySell):
plotlines = dict(buy=dict(marker='$\u21E7$', markersize=12.0),
sell=dict(marker='$\u21E9$', markersize=12.0))

class St(bt.Strategy):
def __init__(self):
bt.obs.BuySell(self.data0, barplot=True) # done here for
BuySellArrows(self.data1, barplot=True) # different markers per data

def next(self):
if not self.position:
if random.randint(0, 1):
self.buy(data=self.data0)
self.entered = len(self)

else: # in the market


if (len(self) - self.entered) >= 10:
self.sell(data=self.data1)

def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()

dataname = '../../datas/2006-day-001.txt' # data feed

252
backtrader’s documentation Version-1.9.58.122

data0 = bt.feeds.BacktraderCSVData(dataname=dataname, name='data0')


cerebro.adddata(data0)

data1 = bt.feeds.BacktraderCSVData(dataname=dataname, name='data1')


data1.addfilter(close_changer)
if not args.no_comp:
data1.compensate(data0)
data1.plotinfo.plotmaster = data0
cerebro.adddata(data1)

cerebro.addstrategy(St) # sample strategy

cerebro.addobserver(bt.obs.Broker) # removed below with stdstats=False


cerebro.addobserver(bt.obs.Trades) # removed below with stdstats=False

cerebro.broker.set_coc(True)
cerebro.run(stdstats=False) # execute
cerebro.plot(volume=False) # and plot

def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=('Compensation example'))

parser.add_argument('--no-comp', required=False, action='store_true')


return parser.parse_args(pargs)

if __name__ == '__main__':
runstrat()

Data Feeds

backtrader comes with a set of Data Feed parsers (at the time of writing all CSV
Based) to let you load data from different sources.

• Yahoo (online or already saved to a file)


• VisualChart (see www.visualchart.com
• Backtrader CSV (own cooked format for testing)
• Generic CSV support

253
backtrader’s documentation Version-1.9.58.122

From the Quickstart guide it should be clear that you add data feeds to a Cerebro
instance. The data feeds will later be available to the different strategies in:

• An array self.datas (insertion order)


• Alias to the array objects:
o self.data and self.data0 point to the first element
o self.dataX points to elements with index X in the array

A quick reminder as to how the insertion works:

import backtrader as bt
import backtrader.feeds as btfeeds

data = btfeeds.YahooFinanceCSVData(dataname='wheremydatacsvis.csv')

cerebro = bt.Cerebro()

cerebro.adddata(data) # a 'name' parameter can be passed for plotting purposes

Data Feeds Common parameters

This data feed can download data directly from Yahoo and feed into the system.

Parameters:

• dataname (default: None) MUST BE PROVIDED

The meaning varies with the data feed type (file location, ticker, ...)

• name (default: ‘’)

Meant for decorative purposes in plotting. If not specified it may be


derived from dataname (example: last part of a file path)

• fromdate (default: mindate)

Python datetime object indicating that any datetime prior to this should be
ignored

• todate (default: maxdate)

Python datetime object indicating that any datetime posterior to this


should be ignored

• timeframe (default: TimeFrame.Days)


254
backtrader’s documentation Version-1.9.58.122

Potential values: Ticks, Seconds, Minutes, Days, Weeks, Months and Years

• compression (default: 1)

Number of actual bars per bar. Informative. Only effective in Data


Resampling/Replaying.

• sessionstart (default: None)

Indication of session starting time for the data. May be used by classes
for purposes like resampling

• sessionend (default: None)

Indication of session ending time for the data. May be used by classes for
purposes like resampling

CSV Data Feeds Common parameters

Parameters (additional to the common ones):

• headers (default: True)

Indicates if the passed data has an initial headers row

• separator (default: ”,”)

Separator to take into account to tokenize each of the CSV rows

GenericCSVData

This class exposes a generic interface allowing parsing mostly every CSV file
format out there.

Parses a CSV file according to the order and field presence defined by the
parameters

Specific parameters (or specific meaning):

• dataname

The filename to parse or a file-like object

• datetime (default: 0) column containing the date (or datetime) field


255
backtrader’s documentation Version-1.9.58.122

• time (default: -1) column containing the time field if separate from the
datetime field (-1 indicates it’s not present)
• open (default: 1) , high (default: 2), low (default: 3), close (default:
4), volume (default: 5), openinterest (default: 6)

Index of the columns containing the corresponding fields

If a negative value is passed (example: -1) it indicates the field is not


present in the CSV data

• nullvalue (default: float(‘NaN’))

Value that will be used if a value which should be there is missing (the
CSV field is empty)

• dtformat (default: %Y-%m-%d %H:%M:%S)

Format used to parse the datetime CSV field

• tmformat (default: %H:%M:%S)

Format used to parse the time CSV field if “present” (the default for the
“time” CSV field is not to be present)

An example usage covering the following requirements:

• Limit input to year 2000


• HLOC order rather than OHLC
• Missing values to be replaced with zero (0.0)
• Daily bars are provided and datetime is just the day with format YYYY-MM-DD
• No openinterest column is present

The code:

import datetime
import backtrader as bt
import backtrader.feeds as btfeeds

...
...

data = btfeeds.GenericCSVData(
dataname='mydata.csv',

fromdate=datetime.datetime(2000, 1, 1),

256
backtrader’s documentation Version-1.9.58.122

todate=datetime.datetime(2000, 12, 31),

nullvalue=0.0,

dtformat=('%Y-%m-%d'),

datetime=0,
high=1,
low=2,
open=3,
close=4,
volume=5,
openinterest=-1
)

...

Slightly modified requirements:

• Limit input to year 2000


• HLOC order rather than OHLC
• Missing values to be replaced with zero (0.0)
• Intraday bars are provided, with separate date and time columns - Date has format YYYY-MM-DD - Time
has format HH.MM.SS (instead of the usual HH:MM:SS)
• No openinterest column is present

The code:

import datetime
import backtrader as bt
import backtrader.feeds as btfeed

...
...

data = btfeeds.GenericCSVData(
dataname='mydata.csv',

fromdate=datetime.datetime(2000, 1, 1),
todate=datetime.datetime(2000, 12, 31),

nullvalue=0.0,

dtformat=('%Y-%m-%d'),
tmformat=('%H.%M.%S'),
257
backtrader’s documentation Version-1.9.58.122

datetime=0,
time=1,
high=2,
low=3,
open=4,
close=5,
volume=6,
openinterest=-1
)

This can also be made permanent with subclassing:

import datetime
import backtrader.feeds as btfeed

class MyHLOC(btfreeds.GenericCSVData):

params = (
('fromdate', datetime.datetime(2000, 1, 1)),
('todate', datetime.datetime(2000, 12, 31)),
('nullvalue', 0.0),
('dtformat', ('%Y-%m-%d')),
('tmformat', ('%H.%M.%S')),

('datetime', 0),
('time', 1),
('high', 2),
('low', 3),
('open', 4),
('close', 5),
('volume', 6),
('openinterest', -1)
)

This new class can be reused now by just providing the dataname:

data = btfeeds.MyHLOC(dataname='mydata.csv')

CSV Data Feed Development

backtrader already offers a Generic CSV Data feed and some specific CSV Data
Feeds. Summarizing:

258
backtrader’s documentation Version-1.9.58.122

• GenericCSVData
• VisualChartCSVData
• YahooFinanceData (for online downloads)
• YahooFinanceCSVData (for already downloaded data)
• BacktraderCSVData (in-house ... for testing purposed, but can be used)

But even with that, the end user may wish to develop support for a specific CSV
Data Feed.

The usual motto would be: “It’s easier said than done”. Actually the structure
is meant to make it easy.

Steps:

• Inherit from backtrader.CSVDataBase


• Define any params if needed
• Do any initialization in the start method
• Do any clean-up in the stop method
• Define a _loadline method where the actual work happens

This method receives a single argument: linetokens.

As the name suggests this contains the tokens after the current line has
been splitten according to the separator parameter (inherited from the base
class)

If after doing its work there is new data ... fill up the corresponding
lines and return True

If nothing is available and therefore the parsing has come to an end:


return False

Returning False may not even be needed if the behind the scenes code which
is reading the file lines finds out there are no more lines to parse.

Things which are already taken into account:

• Opening the file (or receiving a file-like object)


• Skipping the headers row if indicated as present
• Reading the lines
• Tokenizing the lines
• Preloading support (to load the entire data feed at once in memory)

Usually an example is worth a thousand requirement descriptions. Let’s use a


simplified version of the in-house defined CSV parsing code from

259
backtrader’s documentation Version-1.9.58.122

BacktraderCSVData. This one needs no initialization or clean-up (this could be


opening a socket and closing it later, for example).

Note

backtrader data feeds contain the usual industry standard feeds, which are the
ones to be filled. Namely:

• datetime
• open
• high
• low
• close
• volume
• openinterest

If your strategy/algorithm or simple data perusal only needs, for example the
closing prices you can leave the others untouched (each iteration fills them
automatically with a float(‘NaN’) value before the end user code has a chance
to do anything.

In this example only a daily format is supported:

import itertools
...
import backtrader as bt

class MyCSVData(bt.CSVDataBase):

def start(self):
# Nothing to do for this data feed type
pass

def stop(self):
# Nothing to do for this data feed type
pass

def _loadline(self, linetokens):


i = itertools.count(0)

dttxt = linetokens[next(i)]
# Format is YYYY-MM-DD
y = int(dttxt[0:4])
m = int(dttxt[5:7])
d = int(dttxt[8:10])
260
backtrader’s documentation Version-1.9.58.122

dt = datetime.datetime(y, m, d)
dtnum = date2num(dt)

self.lines.datetime[0] = dtnum
self.lines.open[0] = float(linetokens[next(i)])
self.lines.high[0] = float(linetokens[next(i)])
self.lines.low[0] = float(linetokens[next(i)])
self.lines.close[0] = float(linetokens[next(i)])
self.lines.volume[0] = float(linetokens[next(i)])
self.lines.openinterest[0] = float(linetokens[next(i)])

return True

The code expects all fields to be in place and be convertible to floats, except
for the datetime which has a fixed YYYY-MM-DD format and can be parsed without
using datetime.datetime.strptime.

More complex needs can be covered by adding just a few lines of code to account
for null values, date format parsing. The GenericCSVData does that.

Caveat Emptor

Using the GenericCSVData existing feed and inheritance a lot can be acomplished
in order to support formats.

Let’s add support for Sierra Chart daily format (which is always stored in CSV
format).

Definition (by looking into one of the ‘.dly’ data files:

• Fields: Date, Open, High, Low, Close, Volume, OpenInterest

The industry standard ones and the ones already supported by GenericCSVData
in the same order (which is also industry standard)

• Separator: ,
• Date Format: YYYY/MM/DD

A parser for those files:

class SierraChartCSVData(backtrader.feeds.GenericCSVData):

params = (('dtformat', '%Y/%m/%d'),)

261
backtrader’s documentation Version-1.9.58.122

The params definition simply redefines one of the existing parameters in the base
class. In this case just the formatting string for dates needs a change.

Et voilá ... the parser for Sierra Chart is finished.

Here below the parameters definition of GenericCSVData as a reminder:

class GenericCSVData(feed.CSVDataBase):
params = (
('nullvalue', float('NaN')),
('dtformat', '%Y-%m-%d %H:%M:%S'),
('tmformat', '%H:%M:%S'),

('datetime', 0),
('time', -1),
('open', 1),
('high', 2),
('low', 3),
('close', 4),
('volume', 5),
('openinterest', 6),
)

Binary Datafeed Development

Note

The binary file used in the examples goog.fd belongs to VisualChart and cannot be
distributed with backtrader.

VisualChart can be downloaded free of charge for those interested in directly


using the binary files.

CSV Data feed development has shown how to add new CSV based data feeds. The
existing base class CSVDataBase provides the framework taking most of the work
off the subclasses which in most cases can simply do:

def _loadline(self, linetokens):

# parse the linetokens here and put them in self.lines.close,


# self.lines.high, etc

return True # if data was parsed, else ... return False

262
backtrader’s documentation Version-1.9.58.122

The base class takes care of the parameters, initialization, opening of files,
reading lines, splitting the lines in tokens and additional things like skipping
lines which don’t fit into the date range (fromdate, todate) which the end user
may have defined.

Developing a non-CSV datafeed follows the same pattern without going down to the
already splitted line tokens.

Things to do:

• Derive from backtrader.feed.DataBase


• Add any parameters you may need
• Should initialization be needed, override __init__(self) and/or start(self)
• Should any clean-up code be needed, override stop(self)
• The work happens inside the method which MUST always be overriden: _load(self)

Let’s the parameters already provided by backtrader.feed.DataBase:

from backtrader.utils.py3 import with_metaclass

...
...

class DataBase(with_metaclass(MetaDataBase, dataseries.OHLCDateTime)):

params = (('dataname', None),


('fromdate', datetime.datetime.min),
('todate', datetime.datetime.max),
('name', ''),
('compression', 1),
('timeframe', TimeFrame.Days),
('sessionend', None))

Having the following meanings:

• dataname is what allows the data feed to identify how to fetch the data. In
the case of the CSVDataBase this parameter is meant to be a path to a file
or already a file-like object.
• fromdate and todate define the date range which will be passed to
strategies. Any value provided by the feed outside of this range will be
ignored
• name is cosmetic for plotting purposes
• timeframe indicates the temporal working reference

Potential values: Ticks, Seconds, Minutes, Days, Weeks, Months and Years
263
backtrader’s documentation Version-1.9.58.122

• compression (default: 1)

Number of actual bars per bar. Informative. Only effective in Data


Resampling/Replaying.

• compression
• sessionend if passed (a datetime.time object) will be added to the datafeed
datetime line which allows identifying the end of the session

Sample binary datafeed

backtrader already defines a CSV datafeed (VChartCSVData) for the exports of


VisualChart, but it is also possible to directly read the binary data files.

Let’s do it (full data feed code can be found at the bottom)

Initialization

The binary VisualChart data files can contain either daily (.fd extension) or
intraday data (.min extension). Here the parameter timeframe will be used to
distinguish which type of file is being read.

During __init__ constants which differ for each type are set up.

def __init__(self):
super(VChartData, self).__init__()

# Use the informative "timeframe" parameter to understand if the


# code passed as "dataname" refers to an intraday or daily feed
if self.p.timeframe >= TimeFrame.Days:
self.barsize = 28
self.dtsize = 1
self.barfmt = 'IffffII'
else:
self.dtsize = 2
self.barsize = 32
self.barfmt = 'IIffffII'

264
backtrader’s documentation Version-1.9.58.122

Start

The Datafeed will be started when backtesting commences (it can actually be
started several times during optimizations)

In the start method the binary file is open unless a file-like object has been
passed.

def start(self):
# the feed must start ... get the file open (or see if it was open)
self.f = None
if hasattr(self.p.dataname, 'read'):
# A file has been passed in (ex: from a GUI)
self.f = self.p.dataname
else:
# Let an exception propagate
self.f = open(self.p.dataname, 'rb')

Stop

Called when backtesting is finished.

If a file was open, it will be closed

def stop(self):
# Close the file if any
if self.f is not None:
self.f.close()
self.f = None

Actual Loading

The actual work is done in _load. Called to load the next set of data, in this
case the next : datetime, open, high, low, close, volume, openinterest. In
backtrader the “actual” moment corresponds to index 0.

A number of bytes will be read from the open file (determined by the constants
set up during __init__), parsed with the struct module, further processed if
needed (like with divmod operations for date and time) and stored in the lines of
the data feed: datetime, open, high, low, close, volume, openinterest.
265
backtrader’s documentation Version-1.9.58.122

If no data can be read from the file it is assumed that the End Of File (EOF) has
been reached

• False is returned to indicate the fact no more data is available

Else if data has been loaded and parsed:

• True is returned to indicate the loading of the data set was a success

def _load(self):
if self.f is None:
# if no file ... no parsing
return False

# Read the needed amount of binary data


bardata = self.f.read(self.barsize)
if not bardata:
# if no data was read ... game over say "False"
return False

# use struct to unpack the data


bdata = struct.unpack(self.barfmt, bardata)

# Years are stored as if they had 500 days


y, md = divmod(bdata[0], 500)
# Months are stored as if they had 32 days
m, d = divmod(md, 32)
# put y, m, d in a datetime
dt = datetime.datetime(y, m, d)

if self.dtsize > 1: # Minute Bars


# Daily Time is stored in seconds
hhmm, ss = divmod(bdata[1], 60)
hh, mm = divmod(hhmm, 60)
# add the time to the existing atetime
dt = dt.replace(hour=hh, minute=mm, second=ss)

self.lines.datetime[0] = date2num(dt)

# Get the rest of the unpacked data


o, h, l, c, v, oi = bdata[self.dtsize:]
self.lines.open[0] = o
self.lines.high[0] = h
self.lines.low[0] = l
self.lines.close[0] = c
266
backtrader’s documentation Version-1.9.58.122

self.lines.volume[0] = v
self.lines.openinterest[0] = oi

# Say success
return True

Other Binary Formats

The same model can be applied to any other binary source:

• Database
• Hierarchical data storage
• Online source

The steps again:

• __init__ -> Any init code for the instance, only once
• start -> start of backtesting (one or more times if optimization will be
run)

This would for example open the connection to the database or a socket to
an online service

• stop -> clean-up like closing the database connection or open sockets
• _load -> query the database or online source for the next set of data and
load it into the lines of the object. The standard fields being: datetime,
open, high, low, close, volume, openinterest

VChartData Test

The VCharData loading data from a local ”.fd” file for Google for the year
2006.

It’s only about loading the data, so not even a subclass of Strategy is needed.

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import datetime

import backtrader as bt
from vchart import VChartData

267
backtrader’s documentation Version-1.9.58.122

if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro(stdstats=False)

# Add a strategy
cerebro.addstrategy(bt.Strategy)

###########################################################################
# Note:
# The goog.fd file belongs to VisualChart and cannot be distributed with
# backtrader
#
# VisualChart can be downloaded from www.visualchart.com
###########################################################################
# Create a Data Feed
datapath = '../../datas/goog.fd'
data = VChartData(
dataname=datapath,
fromdate=datetime.datetime(2006, 1, 1),
todate=datetime.datetime(2006, 12, 31),
timeframe=bt.TimeFrame.Days
)

# Add the Data Feed to Cerebro


cerebro.adddata(data)

# Run over everything


cerebro.run()

# Plot the result


cerebro.plot(style='bar')

268
backtrader’s documentation Version-1.9.58.122

VChartData Full Code

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import datetime
import struct

from backtrader.feed import DataBase


from backtrader import date2num
from backtrader import TimeFrame

class VChartData(DataBase):
def __init__(self):

269
backtrader’s documentation Version-1.9.58.122

super(VChartData, self).__init__()

# Use the informative "timeframe" parameter to understand if the


# code passed as "dataname" refers to an intraday or daily feed
if self.p.timeframe >= TimeFrame.Days:
self.barsize = 28
self.dtsize = 1
self.barfmt = 'IffffII'
else:
self.dtsize = 2
self.barsize = 32
self.barfmt = 'IIffffII'

def start(self):
# the feed must start ... get the file open (or see if it was open)
self.f = None
if hasattr(self.p.dataname, 'read'):
# A file has been passed in (ex: from a GUI)
self.f = self.p.dataname
else:
# Let an exception propagate
self.f = open(self.p.dataname, 'rb')

def stop(self):
# Close the file if any
if self.f is not None:
self.f.close()
self.f = None

def _load(self):
if self.f is None:
# if no file ... no parsing
return False

# Read the needed amount of binary data


bardata = self.f.read(self.barsize)
if not bardata:
# if no data was read ... game over say "False"
return False

# use struct to unpack the data


bdata = struct.unpack(self.barfmt, bardata)

# Years are stored as if they had 500 days


270
backtrader’s documentation Version-1.9.58.122

y, md = divmod(bdata[0], 500)
# Months are stored as if they had 32 days
m, d = divmod(md, 32)
# put y, m, d in a datetime
dt = datetime.datetime(y, m, d)

if self.dtsize > 1: # Minute Bars


# Daily Time is stored in seconds
hhmm, ss = divmod(bdata[1], 60)
hh, mm = divmod(hhmm, 60)
# add the time to the existing atetime
dt = dt.replace(hour=hh, minute=mm, second=ss)

self.lines.datetime[0] = date2num(dt)

# Get the rest of the unpacked data


o, h, l, c, v, oi = bdata[self.dtsize:]
self.lines.open[0] = o
self.lines.high[0] = h
self.lines.low[0] = l
self.lines.close[0] = c
self.lines.volume[0] = v
self.lines.openinterest[0] = oi

# Say success
return True

Extending a Datafeed

Issues in GitHub are actually pushing into finishing documentation parts or


helping me to understand if backtrader has the ease of use and flexibility I
envisioned from the first moments and the decisions made along the way.

In this case is Issue #9.

The question finally seems to boil down to:

• Can the end user easily extend the existing mechanisms to add extra information in the form of lines that
gets passed along other existing price information spots like open, high, etc?

As far as I understand the question the answer is: Yes

The poster seems to have these requirements (from Issue #6):

271
backtrader’s documentation Version-1.9.58.122

• A data source which is being parsed into CSV format


• Using GenericCSVData to load the information

This generic csv support was developed in response to this Issue #6

• An extra field which apparently contains P/E information which needs to be


passed along the parsed CSV Data

Let’s build on the CSV Data Feed Development and GenericCSVData example posts.

Steps:

• Assume the P/E information is being set in the CSV data which is parsed
• Use GenericCSVData as the base class
• Extend the existng lines (open/high/low/close/volumen/openinterest) with pe
• Add a parameter to let the caller determine the column position of the P/E information

The result:

from backtrader.feeds import GenericCSVData

class GenericCSV_PE(GenericCSVData):

# Add a 'pe' line to the inherited ones from the base class
lines = ('pe',)

# openinterest in GenericCSVData has index 7 ... add 1


# add the parameter to the parameters inherited from the base class
params = (('pe', 8),)

And the job is done ...

Later and when using this data feed inside a strategy:

import backtrader as bt

....

class MyStrategy(bt.Strategy):

...

def next(self):

if self.data.close > 2000 and self.data.pe < 12:

272
backtrader’s documentation Version-1.9.58.122

# TORA TORA TORA --- Get off this market


self.sell(stake=1000000, price=0.01, exectype=Order.Limit)
...

Plotting that extra P/E line

There is obviously no automated plot support for that extra line in the data
feed.

The best alternative would be to do a SimpleMovingAverage on that line and plot


it in a separate axis:

import backtrader as bt
import backtrader.indicators as btind

....

class MyStrategy(bt.Strategy):

def __init__(self):

# The indicator autoregisters and will plot even if no obvious


# reference is kept to it in the class
btind.SMA(self.data.pe, period=1, subplot=False)

...

def next(self):

if self.data.close > 2000 and self.data.pe < 12:


# TORA TORA TORA --- Get off this market
self.sell(stake=1000000, price=0.01, exectype=Order.Limit)
...

Pandas DataFeed Example

Note

pandas and its dependencies have to be installed

Supporting Pandas Dataframes seems to be of concern to lots of people, who rely


on the already available parsing code for different data sources (including CSV)
and other functionalities offered by Pandas.
273
backtrader’s documentation Version-1.9.58.122

The important declarations for the Datafeed.

class PandasData(feed.DataBase):
'''
The ``dataname`` parameter inherited from ``feed.DataBase`` is the pandas
DataFrame
'''

params = (
# Possible values for datetime (must always be present)
# None : datetime is the "index" in the Pandas Dataframe
# -1 : autodetect position or case-wise equal name
# >= 0 : numeric index to the colum in the pandas dataframe
# string : column name (as index) in the pandas dataframe
('datetime', None),

# Possible values below:


# None : column not present
# -1 : autodetect position or case-wise equal name
# >= 0 : numeric index to the colum in the pandas dataframe
# string : column name (as index) in the pandas dataframe
('open', -1),
('high', -1),
('low', -1),
('close', -1),
('volume', -1),
('openinterest', -1),
)

The above excerpt from the PandasData class shows the keys:

• The dataname parameter to the class during instantiation holds the Pandas
Dataframe

This parameter is inherited from the base class feed.DataBase

• The new parameters have the names of the regular fields in the DataSeries
and follow these conventions
o datetime (default: None)
None : datetime is the “index” in the Pandas Dataframe
-1 : autodetect position or case-wise equal name
>= 0 : numeric index to the colum in the pandas dataframe
string : column name (as index) in the pandas dataframe
o open, high, low, high, close, volume, openinterest (default: -1 for all of them)
274
backtrader’s documentation Version-1.9.58.122

None : column not present


-1 : autodetect position or case-wise equal name
>= 0 : numeric index to the colum in the pandas dataframe
string : column name (as index) in the pandas dataframe

A small sample should be able to load the standar 2006 sample, having been parsed
by Pandas, rather than directly by backtrader

Running the sample to use the exiting “headers” in the CSV data:

$ ./panda-test.py
--------------------------------------------------
Open High Low Close Volume OpenInterest
Date
2006-01-02 3578.73 3605.95 3578.73 3604.33 0 0
2006-01-03 3604.08 3638.42 3601.84 3614.34 0 0
2006-01-04 3615.23 3652.46 3615.23 3652.46 0 0

The same but telling the script to skip the headers:

$ ./panda-test.py --noheaders
--------------------------------------------------
1 2 3 4 5 6
0
2006-01-02 3578.73 3605.95 3578.73 3604.33 0 0
2006-01-03 3604.08 3638.42 3601.84 3614.34 0 0
2006-01-04 3615.23 3652.46 3615.23 3652.46 0 0

The 2nd run is using tells pandas.read_csv:

• To skip the first input row (skiprows keyword argument set to 1)


• Not to look for a headers row (header keyword argument set to None)

The backtrader support for Pandas tries to automatically detect if column names
have been used or else numeric indices and acts accordingly, trying to offer a
best match.

The following chart is the tribute to success. The Pandas Dataframe has been
correctly loaded (in both cases)

275
backtrader’s documentation Version-1.9.58.122

The sample code for the test.

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse

import backtrader as bt
import backtrader.feeds as btfeeds

import pandas

def runstrat():
args = parse_args()

# Create a cerebro entity

276
backtrader’s documentation Version-1.9.58.122

cerebro = bt.Cerebro(stdstats=False)

# Add a strategy
cerebro.addstrategy(bt.Strategy)

# Get a pandas dataframe


datapath = ('../../datas/2006-day-001.txt')

# Simulate the header row isn't there if noheaders requested


skiprows = 1 if args.noheaders else 0
header = None if args.noheaders else 0

dataframe = pandas.read_csv(datapath,
skiprows=skiprows,
header=header,
parse_dates=True,
index_col=0)

if not args.noprint:
print('--------------------------------------------------')
print(dataframe)
print('--------------------------------------------------')

# Pass it to the backtrader datafeed and add it to the cerebro


data = bt.feeds.PandasData(dataname=dataframe)

cerebro.adddata(data)

# Run over everything


cerebro.run()

# Plot the result


cerebro.plot(style='bar')

def parse_args():
parser = argparse.ArgumentParser(
description='Pandas test script')

parser.add_argument('--noheaders', action='store_true', default=False,


required=False,
help='Do not use header rows')

parser.add_argument('--noprint', action='store_true', default=False,


277
backtrader’s documentation Version-1.9.58.122

help='Print the dataframe')

return parser.parse_args()

if __name__ == '__main__':
runstrat()

Trading Calendar

Release 1.9.42.116 adds support for Trading Calendars. This is useful when
resampling in for example the following scenarios:

• Daily to Weekly resampling can now deliver the weekly bar together with the
last bar of the week.

This is so because the trading calendar identifies the next trading day and
the last trading day of the week can be identified in advance

• Sub-daily to Daily resampling when the end of the session is not the
regular one (which can already be specified to the data feed)

Trading Calendar Interface

There is a base class TradingCalendarBase which is used as a base for any trading
calendar. It defines two (2) methods which have to be overridden:

class TradingCalendarBase(with_metaclass(MetaParams, object)):


def _nextday(self, day):
'''
Returns the next trading day (datetime/date instance) after ``day``
(datetime/date instance) and the isocalendar components

The return value is a tuple with 2 components: (nextday, (y, w, d))


where (y, w, d)
'''
raise NotImplementedError

def schedule(self, day):


'''
Returns a tuple with the opening and closing times (``datetime.time``)
for the given ``date`` (``datetime/date`` instance)
'''
278
backtrader’s documentation Version-1.9.58.122

raise NotImplementedError

Implementations

PandasMarketCalendar

This implementation is based on a neat package which is a spin-off of initial


functionality available from Quantopian. The packages is at:
pandas_market_calendars and can be easily installed:

pip install pandas_market_calendars

The implementation features the following interface:

class PandasMarketCalendar(TradingCalendarBase):
'''
Wrapper of ``pandas_market_calendars`` for a trading calendar. The package
``pandas_market_calendar`` must be installed

Params:

- ``calendar`` (default ``None``)

The param ``calendar`` accepts the following:

- string: the name of one of the calendars supported, for example


`NYSE`. The wrapper will attempt to get a calendar instance

- calendar instance: as returned by ``get_calendar('NYSE')``

- ``cachesize`` (default ``365``)

Number of days to cache in advance for lookup

See also:

- https://github.com/rsheftel/pandas_market_calendars

- http://pandas-market-calendars.readthedocs.io/

'''
params = (
('calendar', None), # A pandas_market_calendars instance or exch name
279
backtrader’s documentation Version-1.9.58.122

('cachesize', 365), # Number of days to cache in advance


)

TradingCalendar

This implementation allows to construct a calendar using self-gathered


information by specifying the holidays, early days, non-trading weekdays and the
opening and closing session times:

class TradingCalendar(TradingCalendarBase):
'''
Wrapper of ``pandas_market_calendars`` for a trading calendar. The package
``pandas_market_calendar`` must be installed

Params:

- ``open`` (default ``time.min``)

Regular start of the session

- ``close`` (default ``time.max``)

Regular end of the session

- ``holidays`` (default ``[]``)

List of non-trading days (``datetime.datetime`` instances)

- ``earlydays`` (default ``[]``)

List of tuples determining the date and opening/closing times of days


which do not conform to the regular trading hours where each tuple has
(``datetime.datetime``, ``datetime.time``, ``datetime.time`` )

- ``offdays`` (default ``ISOWEEKEND``)

A list of weekdays in ISO format (Monday: 1 -> Sunday: 7) in which the


market doesn't trade. This is usually Saturday and Sunday and hence the
default

'''
params = (
('open', time.min),
('close', _time_max),
280
backtrader’s documentation Version-1.9.58.122

('holidays', []), # list of non trading days (date)


('earlydays', []), # list of tuples (date, opentime, closetime)
('offdays', ISOWEEKEND), # list of non trading (isoweekdays)
)

Usage pattern

Global Trading Calendar

Via Cerebro one can add a global calendar which is the default for all data
feeds, unless one is specified for the data feed:

def addcalendar(self, cal):


'''Adds a global trading calendar to the system. Individual data feeds
may have separate calendars which override the global one

``cal`` can be an instance of ``TradingCalendar`` a string or an


instance of ``pandas_market_calendars``. A string will be will be
instantiated as a ``PandasMarketCalendar`` (which needs the module
``pandas_market_calendar`` installed in the system.

If a subclass of `TradingCalendarBase` is passed (not an instance) it


will be instantiated
'''

Per data-feed

By specifying a calendar parameter following the same conventions as described


above in addcalendar.

For example:

...
data = bt.feeds.YahooFinanceData(dataname='YHOO', calendar='NYSE', ...)
cerebro.adddata(data)
...

281
backtrader’s documentation Version-1.9.58.122

Examples

Daily to Weekly

Let’s have a look at a sample run of the code that can be found below. In 2016,
Easter Friday (2016-03-25) was a holiday also in the NYSE. If the sample is run
with no trading calendar, let’s see what happens around that date.

In this case the resampling is being done from daily to weekly (using YHOO and
the daily data for 2016):

$ ./tcal.py

...
Strategy len 56 datetime 2016-03-23 Data0 len 56 datetime 2016-03-23 Data1 len 11
datetime 2016-03-18
Strategy len 57 datetime 2016-03-24 Data0 len 57 datetime 2016-03-24 Data1 len 11
datetime 2016-03-18
Strategy len 58 datetime 2016-03-28 Data0 len 58 datetime 2016-03-28 Data1 len 12
datetime 2016-03-24
...

In this output, the 1st date is the accounting made by the strategy. The 2nd date
is that of the daily

The week ends, as expected, on 2016-03-24 (Thursday), but without a trading


calendar the resampling code cannot know it and the resampled bar with date 2016-
03-18 (the week before) is delivered. When the trading moves on to 2016-03-28
(Monday), the resampler detects the week change and delivers a resampled bar for
2016-03-24.

The same but run with a PandasMarketCalendar for NYSE (and adding a plot)

$ ./tcal.py --plot --pandascal NYSE

...
Strategy len 56 datetime 2016-03-23 Data0 len 56 datetime 2016-03-23 Data1 len 11
datetime 2016-03-18
Strategy len 57 datetime 2016-03-24 Data0 len 57 datetime 2016-03-24 Data1 len 12
datetime 2016-03-24
Strategy len 58 datetime 2016-03-28 Data0 len 58 datetime 2016-03-28 Data1 len 12
datetime 2016-03-24
...
282
backtrader’s documentation Version-1.9.58.122

There is a change! Thanks to the calendar the resampler knows the week is over on
2016-03-24 and delivers the corresponding weekly resampled bar for 2016-03-24 on
the same day.

And the plot.

Since the information may not necessarily be available for each and every market,
one can also craft the calendar. For the NYSE and 2016 it looks like:

class NYSE_2016(bt.TradingCalendar):
params = dict(
holidays=[
datetime.date(2016, 1, 1),
datetime.date(2016, 1, 18),
datetime.date(2016, 2, 15),
datetime.date(2016, 3, 25),
datetime.date(2016, 5, 30),
datetime.date(2016, 7, 4),
283
backtrader’s documentation Version-1.9.58.122

datetime.date(2016, 9, 5),
datetime.date(2016, 11, 24),
datetime.date(2016, 12, 26),
]
)

Easter Friday (2016-03-25) is listed as one of the holidays. Running the sample
now:

$ ./tcal.py --plot --owncal

...
Strategy len 56 datetime 2016-03-23 Data0 len 56 datetime 2016-03-23 Data1 len 11
datetime 2016-03-18
Strategy len 57 datetime 2016-03-24 Data0 len 57 datetime 2016-03-24 Data1 len 12
datetime 2016-03-24
Strategy len 58 datetime 2016-03-28 Data0 len 58 datetime 2016-03-28 Data1 len 12
datetime 2016-03-24
...

And the same result has been obtained with the crafted calendar definition.

Minutes to Daily

Using some private intraday data and the knowledge that the market closed early
on 2016-11-25 (the day after Thanksgiving the market closed at 13:00 in
US/Eastern timezone), another test run, this time with the 2nd sample.

Note

The source data is taken directly from the displayed data and is in CET timeozone
even if the asset in question, YHOO trades in the USA. The tzinput='CET' and
tz='US/Eastern' is used in the code for the data feed to let the platform
appropriately transform the input and display the output

First with no trading calendar

$ ./tcal-intra.py

...
Strategy len 6838 datetime 2016-11-25 18:00:00 Data0 len 6838 datetime 2016-11-25
13:00:00 Data1 len 21 datetime 2016-11-23 16:00:00
Strategy len 6839 datetime 2016-11-25 18:01:00 Data0 len 6839 datetime 2016-11-25
13:01:00 Data1 len 21 datetime 20 16-11-23 16:00:00
284
backtrader’s documentation Version-1.9.58.122

Strategy len 6840 datetime 2016-11-28 14:31:00 Data0 len 6840 datetime 2016-11-28
09:31:00 Data1 len 22 datetime 2016-11-25 16:00:00
Strategy len 6841 datetime 2016-11-28 14:32:00 Data0 len 6841 datetime 2016-11-28
09:32:00 Data1 len 22 datetime 2016-11-25 16:00:00
...

As expected the day closes early at 13:00, but the resampler doesn’t know it
(the official session ends at 16:00) and keeps on delivering the resampled daily
bar from the previous day (2016-11-23) and the new resampled daily bar is first
delivered during the next trading day (2016-11-28) with a date of 2016-11-25.

Note

The data has an extra minute bar at 13:01 which is probably due to the auction
process offering a last price after the market closing time.

We could add a filter to the stream to filter out the bars outside of the session
times (the filter would find it out from the trading calendar)

But that’s not the point of this sample.

The same run with a PandasMarketCalendar instance:

$ ./tcal-intra.py --pandascal NYSE

...
Strategy len 6838 datetime 2016-11-25 18:00:00 Data0 len 6838 datetime 2016-11-25
13:00:00 Data1 len 15 datetime 2016-11-25 13:00:00
Strategy len 6839 datetime 2016-11-25 18:01:00 Data0 len 6839 datetime 2016-11-25
13:01:00 Data1 len 15 datetime 2016-11-25 13:00:00
Strategy len 6840 datetime 2016-11-28 14:31:00 Data0 len 6840 datetime 2016-11-28
09:31:00 Data1 len 15 datetime 2016-11-25 13:00:00
Strategy len 6841 datetime 2016-11-28 14:32:00 Data0 len 6841 datetime 2016-11-28
09:32:00 Data1 len 15 datetime 2016-11-25 13:00:00
...

And now the daily bar for 2016-11-25 is delivered when the intraday 1-minute feed
hits 2016-11-25 at 13:00 (let’s disregard the 13:01 bar), because the trading
calendar is telling the resampling code that the day is over.

Let’s add a crafted definition. The same as before but extending it with some
earlydays

class NYSE_2016(bt.TradingCalendar):
params = dict(

285
backtrader’s documentation Version-1.9.58.122

holidays=[
datetime.date(2016, 1, 1),
datetime.date(2016, 1, 18),
datetime.date(2016, 2, 15),
datetime.date(2016, 3, 25),
datetime.date(2016, 5, 30),
datetime.date(2016, 7, 4),
datetime.date(2016, 9, 5),
datetime.date(2016, 11, 24),
datetime.date(2016, 12, 26),
],
earlydays=[
(datetime.date(2016, 11, 25),
datetime.time(9, 30), datetime.time(13, 1))
],
open=datetime.time(9, 30),
close=datetime.time(16, 0),
)

The run:

$ ./tcal-intra.py --owncal

...
Strategy len 6838 datetime 2016-11-25 18:00:00 Data0 len 6838 datetime 2016-11-25
13:00:00 Data1 len 15 datetime 2016-11-23 16:00:00
Strategy len 6839 datetime 2016-11-25 18:01:00 Data0 len 6839 datetime 2016-11-25
13:01:00 Data1 len 16 datetime 2016-11-25 13:01:00
Strategy len 6840 datetime 2016-11-28 14:31:00 Data0 len 6840 datetime 2016-11-28
09:31:00 Data1 len 16 datetime 2016-11-25 13:01:00
Strategy len 6841 datetime 2016-11-28 14:32:00 Data0 len 6841 datetime 2016-11-28
09:32:00 Data1 len 16 datetime 2016-11-25 13:01:00
...

The avid reader will notice that the crafted definition contains has defined
13:01 (with datetime.time(13, 1)) as the end of session for our short day of
2016-11-25. This is only to show how the crafted TradingCalendar can help into
fitting things.

Now the daily resampled bar for 2016-11-25 is delivered together with the 1-
minute bar at 13:01.

286
backtrader’s documentation Version-1.9.58.122

Extra bonus for the strategy

The first datetime, the one belonging to the strategy, is always in a different
timezone which is actually UTC. Also with this release 1.9.42.116 this can be
synchronized. The following parameter has been added to Cerebro (use either
during instantiation or with cerebro.run

- ``tz`` (default: ``None``)

Adds a global timezone for strategies. The argument ``tz`` can be

- ``None``: in this case the datetime displayed by strategies will be


in UTC, which has been always the standard behavior

- ``pytz`` instance. It will be used as such to convert UTC times to


the chosen timezone

- ``string``. Instantiating a ``pytz`` instance will be attempted.

- ``integer``. Use, for the strategy, the same timezone as the


corresponding ``data`` in the ``self.datas`` iterable (``0`` would
use the timezone from ``data0``)

It is also supported with the cerebro.addtz method:

def addtz(self, tz):


'''
This can also be done with the parameter ``tz``

Adds a global timezone for strategies. The argument ``tz`` can be

- ``None``: in this case the datetime displayed by strategies will be


in UTC, which has been always the standard behavior

- ``pytz`` instance. It will be used as such to convert UTC times to


the chosen timezone

- ``string``. Instantiating a ``pytz`` instance will be attempted.

- ``integer``. Use, for the strategy, the same timezone as the


corresponding ``data`` in the ``self.datas`` iterable (``0`` would
use the timezone from ``data0``)

'''
287
backtrader’s documentation Version-1.9.58.122

Repeating the last run of the intraday sample and using 0 for tz (synchronize
with the timezone of data0) the following is the output focusing on the same
dates and times as above:

$ ./tcal-intra.py --owncal --cerebro tz=0

...
Strategy len 6838 datetime 2016-11-25 13:00:00 Data0 len 6838 datetime 2016-11-25
13:00:00 Data1 len 15 datetime 2016-11-23 16:00:00
Strategy len 6839 datetime 2016-11-25 13:01:00 Data0 len 6839 datetime 2016-11-25
13:01:00 Data1 len 16 datetime 2016-11-25 13:01:00
Strategy len 6840 datetime 2016-11-28 09:31:00 Data0 len 6840 datetime 2016-11-28
09:31:00 Data1 len 16 datetime 2016-11-25 13:01:00
Strategy len 6841 datetime 2016-11-28 09:32:00 Data0 len 6841 datetime 2016-11-28
09:32:00 Data1 len 16 datetime 2016-11-25 13:01:00
...

The timestamp is now timezone aligned.

Sample usage (tcal.py)

$ ./tcal.py --help
usage: tcal.py [-h] [--data0 DATA0] [--offline] [--fromdate FROMDATE]
[--todate TODATE] [--cerebro kwargs] [--broker kwargs]
[--sizer kwargs] [--strat kwargs] [--plot [kwargs]]
[--pandascal PANDASCAL | --owncal]
[--timeframe {Weeks,Months,Years}]

Trading Calendar Sample

optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to read in (default: YHOO)
--offline Read from disk with same name as ticker (default:
False)
--fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default:
2016-01-01)
--todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default:
2016-12-31)
--cerebro kwargs kwargs in key=value format (default: )
--broker kwargs kwargs in key=value format (default: )
--sizer kwargs kwargs in key=value format (default: )
--strat kwargs kwargs in key=value format (default: )
--plot [kwargs] kwargs in key=value format (default: )
288
backtrader’s documentation Version-1.9.58.122

--pandascal PANDASCAL
Name of trading calendar to use (default: )
--owncal Apply custom NYSE 2016 calendar (default: False)
--timeframe {Weeks,Months,Years}
Timeframe to resample to (default: Weeks)

Sample usage (tcal-intra.py)

$ ./tcal-intra.py --help
usage: tcal-intra.py [-h] [--data0 DATA0] [--fromdate FROMDATE]
[--todate TODATE] [--cerebro kwargs] [--broker kwargs]
[--sizer kwargs] [--strat kwargs] [--plot [kwargs]]
[--pandascal PANDASCAL | --owncal] [--timeframe {Days}]

Trading Calendar Sample

optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to read in (default: yhoo-2016-11.csv)
--fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default:
2016-01-01)
--todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default:
2016-12-31)
--cerebro kwargs kwargs in key=value format (default: )
--broker kwargs kwargs in key=value format (default: )
--sizer kwargs kwargs in key=value format (default: )
--strat kwargs kwargs in key=value format (default: )
--plot [kwargs] kwargs in key=value format (default: )
--pandascal PANDASCAL
Name of trading calendar to use (default: )
--owncal Apply custom NYSE 2016 calendar (default: False)
--timeframe {Days} Timeframe to resample to (default: Days)

Sample Code (tcal.py)

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
import datetime

import backtrader as bt

289
backtrader’s documentation Version-1.9.58.122

class NYSE_2016(bt.TradingCalendar):
params = dict(
holidays=[
datetime.date(2016, 1, 1),
datetime.date(2016, 1, 18),
datetime.date(2016, 2, 15),
datetime.date(2016, 3, 25),
datetime.date(2016, 5, 30),
datetime.date(2016, 7, 4),
datetime.date(2016, 9, 5),
datetime.date(2016, 11, 24),
datetime.date(2016, 12, 26),
]
)

class St(bt.Strategy):
params = dict(
)

def __init__(self):
pass

def start(self):
self.t0 = datetime.datetime.utcnow()

def stop(self):
t1 = datetime.datetime.utcnow()
print('Duration:', t1 - self.t0)

def prenext(self):
self.next()

def next(self):
print('Strategy len {} datetime {}'.format(
len(self), self.datetime.date()), end=' ')

print('Data0 len {} datetime {}'.format(


len(self.data0), self.data0.datetime.date()), end=' ')

if len(self.data1):
print('Data1 len {} datetime {}'.format(
len(self.data1), self.data1.datetime.date()))
290
backtrader’s documentation Version-1.9.58.122

else:
print()

def runstrat(args=None):
args = parse_args(args)

cerebro = bt.Cerebro()

# Data feed kwargs


kwargs = dict()

# Parse from/to-date
dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
if a:
strpfmt = dtfmt + tmfmt * ('T' in a)
kwargs[d] = datetime.datetime.strptime(a, strpfmt)

YahooData = bt.feeds.YahooFinanceData
if args.offline:
YahooData = bt.feeds.YahooFinanceCSVData # change to read file

# Data feed
data0 = YahooData(dataname=args.data0, **kwargs)
cerebro.adddata(data0)

d1 = cerebro.resampledata(data0,
timeframe=getattr(bt.TimeFrame, args.timeframe))
d1.plotinfo.plotmaster = data0
d1.plotinfo.sameaxis = True

if args.pandascal:
cerebro.addcalendar(args.pandascal)
elif args.owncal:
cerebro.addcalendar(NYSE_2016)

# Broker
cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))

# Sizer
cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))

# Strategy
291
backtrader’s documentation Version-1.9.58.122

cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))

# Execute
cerebro.run(**eval('dict(' + args.cerebro + ')'))

if args.plot: # Plot if requested to


cerebro.plot(**eval('dict(' + args.plot + ')'))

def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=(
'Trading Calendar Sample'
)
)

parser.add_argument('--data0', default='YHOO',
required=False, help='Data to read in')

parser.add_argument('--offline', required=False, action='store_true',


help='Read from disk with same name as ticker')

# Defaults for dates


parser.add_argument('--fromdate', required=False, default='2016-01-01',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

parser.add_argument('--todate', required=False, default='2016-12-31',


help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

parser.add_argument('--cerebro', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--broker', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--sizer', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--strat', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--plot', required=False, default='',


nargs='?', const='{}',
292
backtrader’s documentation Version-1.9.58.122

metavar='kwargs', help='kwargs in key=value format')

pgroup = parser.add_mutually_exclusive_group(required=False)
pgroup.add_argument('--pandascal', required=False, action='store',
default='', help='Name of trading calendar to use')

pgroup.add_argument('--owncal', required=False, action='store_true',


help='Apply custom NYSE 2016 calendar')

parser.add_argument('--timeframe', required=False, action='store',


default='Weeks', choices=['Weeks', 'Months', 'Years'],
help='Timeframe to resample to')

return parser.parse_args(pargs)

if __name__ == '__main__':
runstrat()

Sample Code (tcal-intra.py)

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
import datetime

import backtrader as bt

class NYSE_2016(bt.TradingCalendar):
params = dict(
holidays=[
datetime.date(2016, 1, 1),
datetime.date(2016, 1, 18),
datetime.date(2016, 2, 15),
datetime.date(2016, 3, 25),
datetime.date(2016, 5, 30),
datetime.date(2016, 7, 4),
datetime.date(2016, 9, 5),
datetime.date(2016, 11, 24),
datetime.date(2016, 12, 26),
],

293
backtrader’s documentation Version-1.9.58.122

earlydays=[
(datetime.date(2016, 11, 25),
datetime.time(9, 30), datetime.time(13, 1))
],
open=datetime.time(9, 30),
close=datetime.time(16, 0),
)

class St(bt.Strategy):
params = dict(
)

def __init__(self):
pass

def prenext(self):
self.next()

def next(self):
print('Strategy len {} datetime {}'.format(
len(self), self.datetime.datetime()), end=' ')

print('Data0 len {} datetime {}'.format(


len(self.data0), self.data0.datetime.datetime()), end=' ')

if len(self.data1):
print('Data1 len {} datetime {}'.format(
len(self.data1), self.data1.datetime.datetime()))
else:
print()

def runstrat(args=None):
args = parse_args(args)

cerebro = bt.Cerebro()

# Data feed kwargs


# kwargs = dict(tz='US/Eastern')
# import pytz
# tz = tzinput = pytz.timezone('Europe/Berlin')
tzinput = 'Europe/Berlin'
# tz = tzinput
294
backtrader’s documentation Version-1.9.58.122

tz = 'US/Eastern'
kwargs = dict(tzinput=tzinput, tz=tz)

# Parse from/to-date
dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
if a:
strpfmt = dtfmt + tmfmt * ('T' in a)
kwargs[d] = datetime.datetime.strptime(a, strpfmt)

# Data feed
data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)
cerebro.adddata(data0)

d1 = cerebro.resampledata(data0,
timeframe=getattr(bt.TimeFrame, args.timeframe))
# d1.plotinfo.plotmaster = data0
# d1.plotinfo.sameaxis = False

if args.pandascal:
cerebro.addcalendar(args.pandascal)
elif args.owncal:
cerebro.addcalendar(NYSE_2016()) # or NYSE_2016() to pass an instance

# Broker
cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))

# Sizer
cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))

# Strategy
cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))

# Execute
cerebro.run(**eval('dict(' + args.cerebro + ')'))

if args.plot: # Plot if requested to


cerebro.plot(**eval('dict(' + args.plot + ')'))

def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=(
295
backtrader’s documentation Version-1.9.58.122

'Trading Calendar Sample'


)
)

parser.add_argument('--data0', default='yhoo-2016-11.csv',
required=False, help='Data to read in')

# Defaults for dates


parser.add_argument('--fromdate', required=False, default='2016-01-01',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

parser.add_argument('--todate', required=False, default='2016-12-31',


help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

parser.add_argument('--cerebro', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--broker', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--sizer', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--strat', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--plot', required=False, default='',


nargs='?', const='{}',
metavar='kwargs', help='kwargs in key=value format')

pgroup = parser.add_mutually_exclusive_group(required=False)
pgroup.add_argument('--pandascal', required=False, action='store',
default='', help='Name of trading calendar to use')

pgroup.add_argument('--owncal', required=False, action='store_true',


help='Apply custom NYSE 2016 calendar')

parser.add_argument('--timeframe', required=False, action='store',


default='Days', choices=['Days'],
help='Timeframe to resample to')

return parser.parse_args(pargs)

296
backtrader’s documentation Version-1.9.58.122

if __name__ == '__main__':
runstrat()

Data Resampling

When data is only available in a single timeframe and the analysis has to be done
for a different timeframe, it’s time to do some resampling.

“Resampling” should actually be called “Upsampling” given that one goes from
a source timeframe to a larger time frame (for example: days to weeks)

backtrader has built-in support for resampling by passing the original data
through a filter object. Although there are several ways to achieve this, a
straightforward interface exists to achieve this:

• Instead of using cerebro.adddata(data) to put a data into the system use

cerebro.resampledata(data, **kwargs)

There are two main options that can be controlled

• Change the timeframe


• Compress bars

To do so, use the following parameters when calling resampledata:

• timeframe (default: bt.TimeFrame.Days)

Destination timeframe which to be useful has to be equal or larger than the


source

• compression (default: 1)

Compress the selected value “n” to 1 bar

Let’s see an example from Daily to Weekly with a handcrafted script:

$ ./resampling-example.py --timeframe weekly --compression 1

The output:

297
backtrader’s documentation Version-1.9.58.122

We can compare it to the original daily data:

$ ./resampling-example.py --timeframe daily --compression 1

The output:

298
backtrader’s documentation Version-1.9.58.122

The magic is done by executing the following steps:

• Loading the data as usual


• Feeding the data into cerebro with resampledata with the desired parameters:
o timeframe
o compression

The code in the sample (the entire script at the bottom).

# Load the Data


datapath = args.dataname or '../../datas/2006-day-001.txt'
data = btfeeds.BacktraderCSVData(dataname=datapath)

# Handy dictionary for the argument timeframe conversion


tframes = dict(
daily=bt.TimeFrame.Days,
weekly=bt.TimeFrame.Weeks,
monthly=bt.TimeFrame.Months)
299
backtrader’s documentation Version-1.9.58.122

# Add the resample data instead of the original


cerebro.resampledata(data,
timeframe=tframes[args.timeframe],
compression=args.compression)

A last example in which we first change the time frame from daily to weekly and
then apply a 3 to 1 compression:

$ ./resampling-example.py --timeframe weekly --compression 3

The output:

From the original 256 daily bars we end up with 18 3-week bars. The breakdown:

• 52 weeks
• 52 / 3 = 17.33 and therefore 18 bars
300
backtrader’s documentation Version-1.9.58.122

It doesn’t take much more. Of course intraday data can also be resampled.

The resampling filter supports additional parameters, which in most cases should
not be touched:

• bar2edge (default: True)

resamples using time boundaries as the target. For example with a “ticks
-> 5 seconds” the resulting 5 seconds bars will be aligned to xx:00,
xx:05, xx:10 ...

• adjbartime (default: True)

Use the time at the boundary to adjust the time of the delivered resampled
bar instead of the last seen timestamp. If resampling to “5 seconds” the
time of the bar will be adjusted for example to hh:mm:05 even if the last
seen timestamp was hh:mm:04.33

Note

Time will only be adjusted if “bar2edge” is True. It wouldn’t make sense


to adjust the time if the bar has not been aligned to a boundary

• rightedge (default: True)

Use the right edge of the time boundaries to set the time.

If False and compressing to 5 seconds the time of a resampled bar for


seconds between hh:mm:00 and hh:mm:04 will be hh:mm:00 (the starting
boundary

If True the used boundary for the time will be hh:mm:05 (the ending
boundary)

• boundoff (default: 0)

Push the boundary for resampling/replaying by an amount of units.

If for example the resampling is from 1 minute to 15 minutes, the default


behavior is to take the 1-minute bars from 00:01:00 until 00:15:00 to
produce a 15-minutes replayed/resampled bar.

If boundoff is set to 1, then the boundary is pushed 1 unit forward. In


this case the original unit is a 1-minute bar. Consequently the
resampling/replaying will now:

301
backtrader’s documentation Version-1.9.58.122

o Use the bars from 00:00:00 to 00:14:00 for the generation of the 15-minutes bar

The sample code for the resampling test script.

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse

import backtrader as bt
import backtrader.feeds as btfeeds

def runstrat():
args = parse_args()

# Create a cerebro entity


cerebro = bt.Cerebro(stdstats=False)

# Add a strategy
cerebro.addstrategy(bt.Strategy)

# Load the Data


datapath = args.dataname or '../../datas/2006-day-001.txt'
data = btfeeds.BacktraderCSVData(dataname=datapath)

# Handy dictionary for the argument timeframe conversion


tframes = dict(
daily=bt.TimeFrame.Days,
weekly=bt.TimeFrame.Weeks,
monthly=bt.TimeFrame.Months)

# Add the resample data instead of the original


cerebro.resampledata(data,
timeframe=tframes[args.timeframe],
compression=args.compression)

# Run over everything


cerebro.run()

# Plot the result


cerebro.plot(style='bar')

302
backtrader’s documentation Version-1.9.58.122

def parse_args():
parser = argparse.ArgumentParser(
description='Pandas test script')

parser.add_argument('--dataname', default='', required=False,


help='File Data to Load')

parser.add_argument('--timeframe', default='weekly', required=False,


choices=['daily', 'weekly', 'monhtly'],
help='Timeframe to resample to')

parser.add_argument('--compression', default=1, required=False, type=int,


help='Compress n bars into 1')

return parser.parse_args()

if __name__ == '__main__':
runstrat()

Data - Multiple Timeframes

Sometimes investing decisions are taken using different timeframes:

• Weekly to evaluate the trend


• Daily to execute the entry

Or 5 minutes vs 60 minutes.

That implies that combining datas of multiple timeframes in backtrader is needed


to support such combinations.

Native support for it is already built-in. The end user must only follow these
rules:

• The data with the smallest timeframe (and thus the larger number of bars) must be the 1st one to be
added to the Cerebro instance
• The datas must be properly date-time aligned for the platform to make any sense out of them

Beyond that, the end-user is free to apply indicators as wished on the


shorter/larger timeframes. Of course:

• Indicators applied to larger timeframes will produce less bars

303
backtrader’s documentation Version-1.9.58.122

The platform will also have the following into account

• The minimum period for larger timeframes

Minimum period which will probably have the side effect of having to consume
several orders of magnitude of the smaller timeframe bars before a Strategy added
to Cerebro kicks into action.

The built-in cerebro.resample is going to be used to create a larger timeframe.

Some examples below, but first the sauce of the test script.

# Load the Data


datapath = args.dataname or '../../datas/2006-day-001.txt'
data = btfeeds.BacktraderCSVData(dataname=datapath)
cerebro.adddata(data) # First add the original data - smaller timeframe

tframes = dict(daily=bt.TimeFrame.Days, weekly=bt.TimeFrame.Weeks,


monthly=bt.TimeFrame.Months)

# Handy dictionary for the argument timeframe conversion


# Resample the data
if args.noresample:
datapath = args.dataname2 or '../../datas/2006-week-001.txt'
data2 = btfeeds.BacktraderCSVData(dataname=datapath)
# And then the large timeframe
cerebro.adddata(data2)
else:
cerebro.resampledata(data, timeframe=tframes[args.timeframe],
compression=args.compression)

# Run over everything


cerebro.run()

The steps:

• Load a data
• Resample it according to the user specified arguments

The script also allows for loading a 2nd data

• Add the data to cerebro


• Add the resampled data (larger timeframe) to cerebro
• run
304
backtrader’s documentation Version-1.9.58.122

Example 1 - Daily and Weekly

The invocation of the script:

$ ./multitimeframe-example.py --timeframe weekly --compression 1

And the output chart:

Example 2 - Daily and Daily Compression (2 bars to 1)

The invocation of the script:

$ ./multitimeframe-example.py --timeframe daily --compression 2

And the output chart:

305
backtrader’s documentation Version-1.9.58.122

Example 3 - Strategy with SMA

Although plotting is nice, the key issue here is showing how the larger timeframe
influences the system, especially when it comes down to the starting point

The script can take a --indicators to add a strategy which creates simple moving
averages of period 10 on the smaller an larger timeframe datas.

If only the smaller timeframe was taken into account:

• next would be called first after 10 bars, which is the time the Simple
Moving Average needs to produce a value

Note

Remember that Strategy monitors created indicators and only calls next when
all indicators have produced a value. The rationale is that the end user
306
backtrader’s documentation Version-1.9.58.122

has added the indicators to use them in the logic and thus no logic should
take place if the indicators have produced no values

But in this case the larger timeframe (weekly) delays the invocation of next
until the Simple Moving Average oon the weekly data has produced a value, which
takes ... 10 weeks.

The script overrides nextstart which is only called once and which defaults to
calling next to show when it is first called.

Invocation 1:

Only the smaller timeframe, daily, gets a Simple Moving Average

The command line and output

$ ./multitimeframe-example.py --timeframe weekly --compression 1 --indicators --


onlydaily
--------------------------------------------------
nextstart called with len 10
--------------------------------------------------

And the chart.

307
backtrader’s documentation Version-1.9.58.122

Invocation 2:

Both timeframes get a Simple Moving Average

The command line:

$ ./multitimeframe-example.py --timeframe weekly --compression 1 --indicators


--------------------------------------------------
nextstart called with len 50
--------------------------------------------------
--------------------------------------------------
nextstart called with len 51
--------------------------------------------------
--------------------------------------------------
nextstart called with len 52
--------------------------------------------------
--------------------------------------------------
308
backtrader’s documentation Version-1.9.58.122

nextstart called with len 53


--------------------------------------------------
--------------------------------------------------
nextstart called with len 54
--------------------------------------------------

Two things to notice here:

• Instead of being called after 10 periods, the strategy is 1st called after
50 periods.

It is so because the Simple Moving Average applied on the larger (weekly)


timeframe produces a value after 10 weeks ... and that is 10 weeks * 5 days
/ week ... 50 days

• nextstart gets called 5 times rather than only 1.

This is a natural side effect of having mixed the timeframe and having (in
this case only one) indicators applied to the larger timeframe.

The larger timeframe Simple Moving Average produces 5 times the same value
whilst 5 daily bars are being consumed.

And because the start of the period is being controlled by the larger
timeframe nextstart gets called 5 times.

And the chart.

309
backtrader’s documentation Version-1.9.58.122

Conclusion

Multiple Timeframe Datas can be used in backtrader with no special objects or


tweaking: just add the smaller timeframes first.

The test script.

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse

import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind

310
backtrader’s documentation Version-1.9.58.122

class SMAStrategy(bt.Strategy):
params = (
('period', 10),
('onlydaily', False),
)

def __init__(self):
self.sma_small_tf = btind.SMA(self.data, period=self.p.period)
if not self.p.onlydaily:
self.sma_large_tf = btind.SMA(self.data1, period=self.p.period)

def nextstart(self):
print('--------------------------------------------------')
print('nextstart called with len', len(self))
print('--------------------------------------------------')

super(SMAStrategy, self).nextstart()

def runstrat():
args = parse_args()

# Create a cerebro entity


cerebro = bt.Cerebro(stdstats=False)

# Add a strategy
if not args.indicators:
cerebro.addstrategy(bt.Strategy)
else:
cerebro.addstrategy(
SMAStrategy,

# args for the strategy


period=args.period,
onlydaily=args.onlydaily,
)

# Load the Data


datapath = args.dataname or '../../datas/2006-day-001.txt'
data = btfeeds.BacktraderCSVData(dataname=datapath)
cerebro.adddata(data) # First add the original data - smaller timeframe

tframes = dict(daily=bt.TimeFrame.Days, weekly=bt.TimeFrame.Weeks,


monthly=bt.TimeFrame.Months)
311
backtrader’s documentation Version-1.9.58.122

# Handy dictionary for the argument timeframe conversion


# Resample the data
if args.noresample:
datapath = args.dataname2 or '../../datas/2006-week-001.txt'
data2 = btfeeds.BacktraderCSVData(dataname=datapath)
# And then the large timeframe
cerebro.adddata(data2)
else:
cerebro.resampledata(data, timeframe=tframes[args.timeframe],
compression=args.compression)

# Run over everything


cerebro.run()

# Plot the result


cerebro.plot(style='bar')

def parse_args():
parser = argparse.ArgumentParser(
description='Multitimeframe test')

parser.add_argument('--dataname', default='', required=False,


help='File Data to Load')

parser.add_argument('--dataname2', default='', required=False,


help='Larger timeframe file to load')

parser.add_argument('--noresample', action='store_true',
help='Do not resample, rather load larger timeframe')

parser.add_argument('--timeframe', default='weekly', required=False,


choices=['daily', 'weekly', 'monhtly'],
help='Timeframe to resample to')

parser.add_argument('--compression', default=1, required=False, type=int,


help='Compress n bars into 1')

parser.add_argument('--indicators', action='store_true',
help='Wether to apply Strategy with indicators')

parser.add_argument('--onlydaily', action='store_true',
help='Indicator only to be applied to daily timeframe')
312
backtrader’s documentation Version-1.9.58.122

parser.add_argument('--period', default=10, required=False, type=int,


help='Period to apply to indicator')

return parser.parse_args()

if __name__ == '__main__':
runstrat()

Data - Replay

The time is gone and testing a strategy against a fully formed and closed bar is
good, but it could be better.

This is where Data Replay comes in to help. If:

• The strategy operates on data with a timeframe X (example: daily)

and

• Data for a smaller timeframe Y (example: 1 minute) is available

Data replay does exactly what the name implies:

Replay a daily bar using the 1 minute data

This is of course not exactly how the market developed, but it is far better than
looking at the daily fully formed and closed bar in isolation:

If the strategy operates in realtime during the formation of the daily bar,
the approximation of the formation of the bar gives a chance to replicate the
actual behavior of the strategy under real conditions

Putting Data Replay into action follows the regular usage patterns of backtrader

• Load a data feed


• Pass the data to cerebro with replaydata
• Add a strategy

Note

Preloading is not supported when data is being replayed because each bar is
actually built in real-time. It will automatically disabled in any Cerebro
instance.
313
backtrader’s documentation Version-1.9.58.122

Parameters which can be passed to replaydata:

• timeframe (default: bt.TimeFrame.Days)

Destination timeframe which to be useful has to be equal or larger than the


source

• compression (default: 1)

Compress the selected value “n” to 1 bar

Extended parameters (do not touch if not really needed):

• bar2edge (default: True)

replays using time boundaries as the target of the closed bar. For example
with a “ticks -> 5 seconds” the resulting 5 seconds bars will be aligned
to xx:00, xx:05, xx:10 ...

• adjbartime (default: False)

Use the time at the boundary to adjust the time of the delivered resampled
bar instead of the last seen timestamp. If resampling to “5 seconds” the
time of the bar will be adjusted for example to hh:mm:05 even if the last
seen timestamp was hh:mm:04.33

Note

Time will only be adjusted if “bar2edge” is True. It wouldn’t make sense


to adjust the time if the bar has not been aligned to a boundary

• rightedge (default: True)

Use the right edge of the time boundaries to set the time.

If False and compressing to 5 seconds the time of a resampled bar for


seconds between hh:mm:00 and hh:mm:04 will be hh:mm:00 (the starting
boundary

If True the used boundary for the time will be hh:mm:05 (the ending
boundary)

For the sake of working with a example the standard 2006 daily data will be
replayed on a weekly basis. Which means:

• There will finally be 52 bars, one for each week


314
backtrader’s documentation Version-1.9.58.122

• Cerebro will call prenext and next a total of 255 times, which is the original count of daily bars

The trick:

• When a weekly bar is forming, the length (len(self)) of the strategy will remain unchanged.
• With each new week the length will increase by one

Some examples below, but first the sauce of the test script in which the data is
loaded and passed to cerebro with replaydata ... and then run.

# Load the Data


datapath = args.dataname or '../../datas/2006-day-001.txt'
data = btfeeds.BacktraderCSVData(dataname=datapath)

# Handy dictionary for the argument timeframe conversion


tframes = dict(
daily=bt.TimeFrame.Days,
weekly=bt.TimeFrame.Weeks,
monthly=bt.TimeFrame.Months)

# First add the original data - smaller timeframe


cerebro.replaydata(data,
timeframe=tframes[args.timeframe],
compression=args.compression)

Example - Replay Daily to Weekly

The invocation of the script:

$ ./replay-example.py --timeframe weekly --compression 1

The chart cannot unfortunately show us the real thing happening in the
background, so let’s have a look at the console output:

prenext len 1 - counter 1


prenext len 1 - counter 2
prenext len 1 - counter 3
prenext len 1 - counter 4
prenext len 1 - counter 5
prenext len 2 - counter 6
...
...
prenext len 9 - counter 44
315
backtrader’s documentation Version-1.9.58.122

prenext len 9 - counter 45


---next len 10 - counter 46
---next len 10 - counter 47
---next len 10 - counter 48
---next len 10 - counter 49
---next len 10 - counter 50
---next len 11 - counter 51
---next len 11 - counter 52
---next len 11 - counter 53
...
...
---next len 51 - counter 248
---next len 51 - counter 249
---next len 51 - counter 250
---next len 51 - counter 251
---next len 51 - counter 252
---next len 52 - counter 253
---next len 52 - counter 254
---next len 52 - counter 255

As we see the internal self.counter variable is keeping track of each call to


either prenext or next. The former being called before the applied Simple Moving
Average produces a value. The latter called when the Simple Moving Average is
producing values.

The key:

• The length (len(self)) of the strategy changes every 5 bars (5 trading days in the week)

The strategy is effectively seeing:

• How the weekly bar developed in 5 shots.

This, again, doesn’t replicate the actual tick-by-tick (and not even
minute, hour) development of the market, but it is better than actually
seeing a bar.

The visual output is that of the weekly chart which is the final outcome the
system is being tested again.

316
backtrader’s documentation Version-1.9.58.122

Example 2 - Daily to Daily with Compression

Of course “Replaying” can be applied to the same timeframe but with a


compression.

The console:

$ ./replay-example.py --timeframe daily --compression 2


prenext len 1 - counter 1
prenext len 1 - counter 2
prenext len 2 - counter 3
prenext len 2 - counter 4
prenext len 3 - counter 5
prenext len 3 - counter 6
prenext len 4 - counter 7
...
...
317
backtrader’s documentation Version-1.9.58.122

---next len 125 - counter 250


---next len 126 - counter 251
---next len 126 - counter 252
---next len 127 - counter 253
---next len 127 - counter 254
---next len 128 - counter 255

This time we got half the bars as expected because of the factor 2 requested
compression.

The chart:

Conclusion

A reconstruction of the market development is possible. Usually a smaller


timeframe set of data is available and can be used to discretely replay the
timeframe which the system operates on.
318
backtrader’s documentation Version-1.9.58.122

The test script.

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse

import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind

class SMAStrategy(bt.Strategy):
params = (
('period', 10),
('onlydaily', False),
)

def __init__(self):
self.sma = btind.SMA(self.data, period=self.p.period)

def start(self):
self.counter = 0

def prenext(self):
self.counter += 1
print('prenext len %d - counter %d' % (len(self), self.counter))

def next(self):
self.counter += 1
print('---next len %d - counter %d' % (len(self), self.counter))

def runstrat():
args = parse_args()

# Create a cerebro entity


cerebro = bt.Cerebro(stdstats=False)

cerebro.addstrategy(
SMAStrategy,
# args for the strategy
period=args.period,
)

319
backtrader’s documentation Version-1.9.58.122

# Load the Data


datapath = args.dataname or '../../datas/2006-day-001.txt'
data = btfeeds.BacktraderCSVData(dataname=datapath)

# Handy dictionary for the argument timeframe conversion


tframes = dict(
daily=bt.TimeFrame.Days,
weekly=bt.TimeFrame.Weeks,
monthly=bt.TimeFrame.Months)

# First add the original data - smaller timeframe


cerebro.replaydata(data,
timeframe=tframes[args.timeframe],
compression=args.compression)

# Run over everything


cerebro.run()

# Plot the result


cerebro.plot(style='bar')

def parse_args():
parser = argparse.ArgumentParser(
description='Pandas test script')

parser.add_argument('--dataname', default='', required=False,


help='File Data to Load')

parser.add_argument('--timeframe', default='weekly', required=False,


choices=['daily', 'weekly', 'monhtly'],
help='Timeframe to resample to')

parser.add_argument('--compression', default=1, required=False, type=int,


help='Compress n bars into 1')

parser.add_argument('--period', default=10, required=False, type=int,


help='Period to apply to indicator')

return parser.parse_args()

if __name__ == '__main__':
320
backtrader’s documentation Version-1.9.58.122

runstrat()

Rolling over Futures

Not every provider offers a continuous future for the instruments with which one
can trade. Sometimes the data offered is that of the still valid expiration
dates, i.e.: those still being traded

This is not so helpful when it comes to backtesting because the data is scattered
over several different instruments which additionally ... overlap in time.

Being able to properly join the data of those instruments, from the past, into a
continuous stream alleviates the pain. The problem:

• There is no law as to how best join the different expiration dates into a continuous future

Some literature, courtesy of SierraChart at:

• http://www.sierrachart.com/index.php?page=doc/ChangingFuturesContract.html

The RollOver Data Feed

backtrader has added with 1.8.10.99` the possibility to join futures’ data from
different expiration dates into a continuous future:

import backtrader as bt

cerebro = bt.Cerebro()
data0 = bt.feeds.MyFeed(dataname='Expiry0')
data1 = bt.feeds.MyFeed(dataname='Expiry1')
...
dataN = bt.feeds.MyFeed(dataname='ExpiryN')

drollover = cerebro.rolloverdata(data0, data1, ..., dataN, name='MyRoll',


**kwargs)

cerebro.run()

Note

The possible **kwargs are explained below

It can also be done by directly accessing the RollOver feed (which is helpful if
subclassing is done):
321
backtrader’s documentation Version-1.9.58.122

import backtrader as bt

cerebro = bt.Cerebro()
data0 = bt.feeds.MyFeed(dataname='Expiry0')
data1 = bt.feeds.MyFeed(dataname='Expiry1')
...
dataN = bt.feeds.MyFeed(dataname='ExpiryN')

drollover = bt.feeds.RollOver(data0, data1, ..., dataN, dataname='MyRoll',


**kwargs)
cerebro.adddata(drollover)

cerebro.run()

Note

The possible **kwargs are explained below

Note

When using RollOver the name is assigned using dataname. This is the standard
parameter used for all data feeds to pass the name/ticker. In this case it is
reused to assign a common name to the complete set of rolled over futures.

In the case of cerebro.rolloverdata, the name is assigned to a feed using name,


which is already one named argument of that method

Bottomline:

• Data Feeds are created as usual but ARE NOT added to cerebro
• Those data feeds are given as input to bt.feeds.RollOver

A dataname is also given, mostly for identification purposes.

• This roll over data feed is then added to cerebro

Options for the Roll-Over

Two parameters are provided to control the roll-over process

• checkdate (default: None)

This must be a callable with the following signature:

checkdate(dt, d):
322
backtrader’s documentation Version-1.9.58.122

Where:

o dt is a datetime.datetime object
o d is the current data feed for the active future

Expected Return Values:

o True: as long as the callable returns this, a switchover can happen


to the next future

If a commodity expires on the 3rd Friday of March, checkdate could


return True for the entire week in which the expiration takes place.

o False: the expiration cannot take place


• checkcondition (default: None)

Note: This will only be called if checkdate has returned True

If None this will evaluate to True (execute roll over) internally

Else this must be a callable with this signature:

checkcondition(d0, d1)

Where:

o d0 is the current data feed for the active future


o d1 is the data feed for the next expiration

Expected Return Values:

o True: roll-over to the next future

Following with the example from checkdate, this could say that the
roll-over can only happend if the volume from d0 is already less than
the volume from d1

o False: the expiration cannot take place

Subclassing RollOver

If specifying the callables isn’t enough, there is always the chance to subclass
RollOver. The methods to subclass:

• def _checkdate(self, dt, d):


323
backtrader’s documentation Version-1.9.58.122

Which matches the signature of the parameter of the same name above. The
expected return values are also the saame.

• def _checkcondition(self, d0, d1)

Which matches the signature of the parameter of the same name above. The
expected return values are also the saame.

Let’s Roll

Note

The default behavior in the sample is to use cerebro.rolloverdata. This can be


changed by passing the -no-cerebro flag. In this case the sample uses RollOver
and cerebro.adddata

The implementation includes a sample which is available in the backtrader


sources.

Futures concatenation

Let’s start by looking at a pure concatenation by running the sample with no


arguments.

$ ./rollover.py

Len, Name, RollName, Datetime, WeekDay, Open, High, Low, Close, Volume,
OpenInterest
0001, FESX, 199FESXM4, 2013-09-26, Thu, 2829.0, 2843.0, 2829.0, 2843.0, 3.0,
1000.0
0002, FESX, 199FESXM4, 2013-09-27, Fri, 2842.0, 2842.0, 2832.0, 2841.0, 16.0,
1101.0
...
0176, FESX, 199FESXM4, 2014-06-20, Fri, 3315.0, 3324.0, 3307.0, 3322.0, 134777.0,
520978.0
0177, FESX, 199FESXU4, 2014-06-23, Mon, 3301.0, 3305.0, 3265.0, 3285.0, 730211.0,
3003692.0
...
0241, FESX, 199FESXU4, 2014-09-19, Fri, 3287.0, 3308.0, 3286.0, 3294.0, 144692.0,
566249.0
0242, FESX, 199FESXZ4, 2014-09-22, Mon, 3248.0, 3263.0, 3231.0, 3240.0, 582077.0,
2976624.0
...
324
backtrader’s documentation Version-1.9.58.122

0306, FESX, 199FESXZ4, 2014-12-19, Fri, 3196.0, 3202.0, 3131.0, 3132.0, 226415.0,
677924.0
0307, FESX, 199FESXH5, 2014-12-22, Mon, 3151.0, 3177.0, 3139.0, 3168.0, 547095.0,
2952769.0
...
0366, FESX, 199FESXH5, 2015-03-20, Fri, 3680.0, 3698.0, 3672.0, 3695.0, 147632.0,
887205.0
0367, FESX, 199FESXM5, 2015-03-23, Mon, 3654.0, 3655.0, 3608.0, 3618.0, 802344.0,
3521988.0
...
0426, FESX, 199FESXM5, 2015-06-18, Thu, 3398.0, 3540.0, 3373.0, 3465.0,
1173246.0, 811805.0
0427, FESX, 199FESXM5, 2015-06-19, Fri, 3443.0, 3499.0, 3440.0, 3488.0, 104096.0,
516792.0

This uses cerebro.chaindata and the result should be clear:

• Whenever a data feed is over the next one takes over


• This happens always between a Friday and Monday: the futures in the samples always expire on Friday

Futures roll-over with no checks

Let’s execute with --rollover

$ ./rollover.py --rollover --plot

Len, Name, RollName, Datetime, WeekDay, Open, High, Low, Close, Volume,
OpenInterest
0001, FESX, 199FESXM4, 2013-09-26, Thu, 2829.0, 2843.0, 2829.0, 2843.0, 3.0,
1000.0
0002, FESX, 199FESXM4, 2013-09-27, Fri, 2842.0, 2842.0, 2832.0, 2841.0, 16.0,
1101.0
...
0176, FESX, 199FESXM4, 2014-06-20, Fri, 3315.0, 3324.0, 3307.0, 3322.0, 134777.0,
520978.0
0177, FESX, 199FESXU4, 2014-06-23, Mon, 3301.0, 3305.0, 3265.0, 3285.0, 730211.0,
3003692.0
...
0241, FESX, 199FESXU4, 2014-09-19, Fri, 3287.0, 3308.0, 3286.0, 3294.0, 144692.0,
566249.0
0242, FESX, 199FESXZ4, 2014-09-22, Mon, 3248.0, 3263.0, 3231.0, 3240.0, 582077.0,
2976624.0
...

325
backtrader’s documentation Version-1.9.58.122

0306, FESX, 199FESXZ4, 2014-12-19, Fri, 3196.0, 3202.0, 3131.0, 3132.0, 226415.0,
677924.0
0307, FESX, 199FESXH5, 2014-12-22, Mon, 3151.0, 3177.0, 3139.0, 3168.0, 547095.0,
2952769.0
...
0366, FESX, 199FESXH5, 2015-03-20, Fri, 3680.0, 3698.0, 3672.0, 3695.0, 147632.0,
887205.0
0367, FESX, 199FESXM5, 2015-03-23, Mon, 3654.0, 3655.0, 3608.0, 3618.0, 802344.0,
3521988.0
...
0426, FESX, 199FESXM5, 2015-06-18, Thu, 3398.0, 3540.0, 3373.0, 3465.0,
1173246.0, 811805.0
0427, FESX, 199FESXM5, 2015-06-19, Fri, 3443.0, 3499.0, 3440.0, 3488.0, 104096.0,
516792.0

The same behavior. It can clearly be seen that contract changes are being made on
the 3rd Friday of either Mar, Jun, Sep, Dec.

But this is mostly WRONG. backtradr cannot know it, but the author knows that the
EuroStoxx 50 futures stop trading at 12:00 CET. So even if there is a daily bar
for the 3rd Friday of the expiration month, the change is happening too late.

326
backtrader’s documentation Version-1.9.58.122

Changing during the Week

A checkdate callabe is implemented in the sample, which calculates the date of


expiration for the currently active contract.

checkdate will allow a roll over as soon as the week of the 3rd Friday of the
month is reached (it may be Tuesday if for example Monday is a bank holiday)

$ ./rollover.py --rollover --checkdate --plot

Len, Name, RollName, Datetime, WeekDay, Open, High, Low, Close, Volume,
OpenInterest
0001, FESX, 199FESXM4, 2013-09-26, Thu, 2829.0, 2843.0, 2829.0, 2843.0, 3.0,
1000.0
0002, FESX, 199FESXM4, 2013-09-27, Fri, 2842.0, 2842.0, 2832.0, 2841.0, 16.0,
1101.0
...
327
backtrader’s documentation Version-1.9.58.122

0171, FESX, 199FESXM4, 2014-06-13, Fri, 3283.0, 3292.0, 3253.0, 3276.0, 734907.0,
2715357.0
0172, FESX, 199FESXU4, 2014-06-16, Mon, 3261.0, 3275.0, 3252.0, 3262.0, 180608.0,
844486.0
...
0236, FESX, 199FESXU4, 2014-09-12, Fri, 3245.0, 3247.0, 3220.0, 3232.0, 650314.0,
2726874.0
0237, FESX, 199FESXZ4, 2014-09-15, Mon, 3209.0, 3224.0, 3203.0, 3221.0, 153448.0,
983793.0
...
0301, FESX, 199FESXZ4, 2014-12-12, Fri, 3127.0, 3143.0, 3038.0, 3042.0,
1409834.0, 2934179.0
0302, FESX, 199FESXH5, 2014-12-15, Mon, 3041.0, 3089.0, 2963.0, 2980.0, 329896.0,
904053.0
...
0361, FESX, 199FESXH5, 2015-03-13, Fri, 3657.0, 3680.0, 3627.0, 3670.0, 867678.0,
3499116.0
0362, FESX, 199FESXM5, 2015-03-16, Mon, 3594.0, 3641.0, 3588.0, 3629.0, 250445.0,
1056099.0
...
0426, FESX, 199FESXM5, 2015-06-18, Thu, 3398.0, 3540.0, 3373.0, 3465.0,
1173246.0, 811805.0
0427, FESX, 199FESXM5, 2015-06-19, Fri, 3443.0, 3499.0, 3440.0, 3488.0, 104096.0,
516792.0

Much better. The roll over is now happening 5 days before. A quick visual
inspection of the Len indices show it. For example:

• 199FESXM4 to 199FESXU4 happens at len 171-172. Without checkdate it happened at 176-177

The roll over is happening on the Monday before the 3rd Friday of the expiration
month.

328
backtrader’s documentation Version-1.9.58.122

Adding a volume condition

Even with the improvement, the situation can be further improved in that not only
the date but also de negotiated volume will be taken into account. Do switch when
the new contract trades more volume than the currently active one.

Let’s add a checkcondition to the mix and run.

$ ./rollover.py --rollover --checkdate --checkcondition --plot

Len, Name, RollName, Datetime, WeekDay, Open, High, Low, Close, Volume,
OpenInterest
0001, FESX, 199FESXM4, 2013-09-26, Thu, 2829.0, 2843.0, 2829.0, 2843.0, 3.0,
1000.0
0002, FESX, 199FESXM4, 2013-09-27, Fri, 2842.0, 2842.0, 2832.0, 2841.0, 16.0,
1101.0
...
329
backtrader’s documentation Version-1.9.58.122

0175, FESX, 199FESXM4, 2014-06-19, Thu, 3307.0, 3330.0, 3300.0, 3321.0, 717979.0,
759122.0
0176, FESX, 199FESXU4, 2014-06-20, Fri, 3309.0, 3318.0, 3290.0, 3298.0, 711627.0,
2957641.0
...
0240, FESX, 199FESXU4, 2014-09-18, Thu, 3249.0, 3275.0, 3243.0, 3270.0, 846600.0,
803202.0
0241, FESX, 199FESXZ4, 2014-09-19, Fri, 3273.0, 3293.0, 3250.0, 3252.0,
1042294.0, 3021305.0
...
0305, FESX, 199FESXZ4, 2014-12-18, Thu, 3095.0, 3175.0, 3085.0, 3172.0,
1309574.0, 889112.0
0306, FESX, 199FESXH5, 2014-12-19, Fri, 3195.0, 3200.0, 3106.0, 3147.0,
1329040.0, 2964538.0
...
0365, FESX, 199FESXH5, 2015-03-19, Thu, 3661.0, 3691.0, 3646.0, 3668.0,
1271122.0, 1054639.0
0366, FESX, 199FESXM5, 2015-03-20, Fri, 3607.0, 3664.0, 3595.0, 3646.0,
1182235.0, 3407004.0
...
0426, FESX, 199FESXM5, 2015-06-18, Thu, 3398.0, 3540.0, 3373.0, 3465.0,
1173246.0, 811805.0
0427, FESX, 199FESXM5, 2015-06-19, Fri, 3443.0, 3499.0, 3440.0, 3488.0, 104096.0,
516792.0

Even better*. We have moved the switch date to the Thursday before the well known
3rd Friday of the expiration month

This should come to no surprise because the expiring future trades a lot less
hours on that Friday and the volume must be small.

Note

The roll over date could have also been set to that Thursday by the checkdate
callable. But that isn’t the point of the sample.

330
backtrader’s documentation Version-1.9.58.122

Concluding

backtrader includes now a flexible mechanism to allow rolling over futures to


create a continuous stream.

Sample Usage

$ ./rollover.py --help
usage: rollover.py [-h] [--no-cerebro] [--rollover] [--checkdate]
[--checkcondition] [--plot [kwargs]]

Sample for Roll Over of Futures

optional arguments:
-h, --help show this help message and exit
--no-cerebro Use RollOver Directly (default: False)
331
backtrader’s documentation Version-1.9.58.122

--rollover
--checkdate Change during expiration week (default: False)
--checkcondition Change when a given condition is met (default: False)
--plot [kwargs], -p [kwargs]
Plot the read data applying any kwargs passed For
example: --plot style="candle" (to plot candles)
(default: None)

Sample Code

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
import bisect
import calendar
import datetime

import backtrader as bt

class TheStrategy(bt.Strategy):
def start(self):
header = ['Len', 'Name', 'RollName', 'Datetime', 'WeekDay', 'Open',
'High', 'Low', 'Close', 'Volume', 'OpenInterest']
print(', '.join(header))

def next(self):
txt = list()
txt.append('%04d' % len(self.data0))
txt.append('{}'.format(self.data0._dataname))
# Internal knowledge ... current expiration in use is in _d
txt.append('{}'.format(self.data0._d._dataname))
txt.append('{}'.format(self.data.datetime.date()))
txt.append('{}'.format(self.data.datetime.date().strftime('%a')))
txt.append('{}'.format(self.data.open[0]))
txt.append('{}'.format(self.data.high[0]))
txt.append('{}'.format(self.data.low[0]))
txt.append('{}'.format(self.data.close[0]))
txt.append('{}'.format(self.data.volume[0]))
txt.append('{}'.format(self.data.openinterest[0]))
print(', '.join(txt))

332
backtrader’s documentation Version-1.9.58.122

def checkdate(dt, d):


# Check if the date is in the week where the 3rd friday of Mar/Jun/Sep/Dec

# EuroStoxx50 expiry codes: MY


# M -> H, M, U, Z (Mar, Jun, Sep, Dec)
# Y -> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 -> year code. 5 -> 2015
MONTHS = dict(H=3, M=6, U=9, Z=12)

M = MONTHS[d._dataname[-2]]

centuria, year = divmod(dt.year, 10)


decade = centuria * 10

YCode = int(d._dataname[-1])
Y = decade + YCode
if Y < dt.year: # Example: year 2019 ... YCode is 0 for 2020
Y += 10

exp_day = 21 - (calendar.weekday(Y, M, 1) + 2) % 7
exp_dt = datetime.datetime(Y, M, exp_day)

# Get the year, week numbers


exp_year, exp_week, _ = exp_dt.isocalendar()
dt_year, dt_week, _ = dt.isocalendar()

# print('dt {} vs {} exp_dt'.format(dt, exp_dt))


# print('dt_week {} vs {} exp_week'.format(dt_week, exp_week))

# can switch if in same week


return (dt_year, dt_week) == (exp_year, exp_week)

def checkvolume(d0, d1):


return d0.volume[0] < d1.volume[0] # Switch if volume from d0 < d1

def runstrat(args=None):
args = parse_args(args)

cerebro = bt.Cerebro()

fcodes = ['199FESXM4', '199FESXU4', '199FESXZ4', '199FESXH5', '199FESXM5']


333
backtrader’s documentation Version-1.9.58.122

store = bt.stores.VChartFile()
ffeeds = [store.getdata(dataname=x) for x in fcodes]

rollkwargs = dict()
if args.checkdate:
rollkwargs['checkdate'] = checkdate

if args.checkcondition:
rollkwargs['checkcondition'] = checkvolume

if not args.no_cerebro:
if args.rollover:
cerebro.rolloverdata(name='FESX', *ffeeds, **rollkwargs)
else:
cerebro.chaindata(name='FESX', *ffeeds)
else:
drollover = bt.feeds.RollOver(*ffeeds, dataname='FESX', **rollkwargs)
cerebro.adddata(drollover)

cerebro.addstrategy(TheStrategy)
cerebro.run(stdstats=False)

if args.plot:
pkwargs = dict(style='bar')
if args.plot is not True: # evals to True but is not True
npkwargs = eval('dict(' + args.plot + ')') # args were passed
pkwargs.update(npkwargs)

cerebro.plot(**pkwargs)

def parse_args(pargs=None):

parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Sample for Roll Over of Futures')

parser.add_argument('--no-cerebro', required=False, action='store_true',


help='Use RollOver Directly')

parser.add_argument('--rollover', required=False, action='store_true')

parser.add_argument('--checkdate', required=False, action='store_true',


help='Change during expiration week')
334
backtrader’s documentation Version-1.9.58.122

parser.add_argument('--checkcondition', required=False,
action='store_true',
help='Change when a given condition is met')

# Plot options
parser.add_argument('--plot', '-p', nargs='?', required=False,
metavar='kwargs', const=True,
help=('Plot the read data applying any kwargs passed\n'
'\n'
'For example:\n'
'\n'
' --plot style="candle" (to plot candles)\n'))

if pargs is not None:


return parser.parse_args(pargs)

return parser.parse_args()

if __name__ == '__main__':
runstrat()

Filters

This functionality is a relatively late addition to backtrader and had to be


fitted to the already existing internals. This makes it to be not as flexible and
100% feature full as wished, but it can still serve the purpose in many cases.

Although the implementation tried to allow plug and play filter chaining, the
pre-existing internals made it difficult to ensure that could always be achieved.
As such, some filters may be chained and some others may not.

Purpose

• Transform the values provided by a data feed to deliver a different data feed

The implementation was started to simplify the implementation of the two obvious
filters which can be directly used via the cerebro API. These are:

• Resampling (cerebro.resampledata)

335
backtrader’s documentation Version-1.9.58.122

Here the filter transforms the timeframe and compression of the incoming
data feed. For example:

(Seconds, 1) -> (Days, 1)

That means that the original data feed is delivery bars with a resolution
of 1 Second. The Resampling filter intercepts the data and buffers it until
it can deliver a 1 Day bar. This will happen when a 1 Second bar from the
next day is seen.

• Replaying (cerebro.replaydata)

For the same timeframes as above, the filter would use the 1 Second
resolution bars to rebuild the 1 Day bar.

That means that the 1 Day bar is delivered as many times as 1 Second bars
are seen, updated to contain the latest information.

This simulates, for example, how an actual trading day has developed.

Note

the length of the data, len(data) and therefore the length of the strategy
remain unchanged as long as the day doesn’t change.

Filters at work

Given an existing data feed/source you use the addfilter method of the data feed:

data = MyDataFeed(dataname=myname)
data.addfilter(filter, *args, **kwargs)
cerebro.addata(data)

And even if it happens to be compatible to the resample/replay filter the


following can also be done:

data = MyDataFeed(dataname=myname)
data.addfilter(filter, *args, **kwargs)
cerebro.replaydata(data)

Filter Interface

A filter must conform to a given interface, being this:

336
backtrader’s documentation Version-1.9.58.122

• A callable which accepts this signature:


• callable(data, *args, **kwargs)

or

• A class which can be instantiated and called


o During instantiation the __init__ method must support the signature:
o def __init__(self, data, *args, **kwargs)
o The __call__ method bears this signature:
o def __call__(self, data, *args, **kwargs)

The instance will be called for each new incoming values from the
data feed. The *args and *kwargs are the same passed to __init__

RETURN VALUES:

True: the inner data fetching loop of the data feed must retry fetching data from the
feed, becaue the length of the stream was manipulated
False even if data may have been edited (example: changed close price), the length of
the stream has remain untouched

In the case of a class based filter 2 additional methods can be implemented

o last with the following signature:


o def last(self, data, *args, **kwargs)

This will be called when the data feed is over, allowing the filter
to deliver data it may have for example buffered. A typical case is
resampling, because a bar is buffered until data from the next time
period is seen. When the data feed is over, there is no new data to
push the buffered data out.

last offers the chance to push the buffered data out.

Note

It is obvious that if the filter supports no arguments at all and will be added
without any, the signatures can be simplified as in:

def __init__(self, data, *args, **kwargs) -> def __init__(self, data)

A Sample Filter

A very quick filter implementation:

337
backtrader’s documentation Version-1.9.58.122

class SessionFilter(object):
def __init__(self, data):
pass

def __call__(self, data):


if data.p.sessionstart <= data.datetime.time() <= data.p.sessionend:
# bar is in the session
return False # tell outer data loop the bar can be processed

# bar outside of the regular session times


data.backwards() # remove bar from data stack
return True # tell outer data loop to fetch a new bar

This filter:

• Uses data.p.sessionstart and data.p.sessionend (standard data feed


parameters) to decide if a bar is in the session.
• If in-the-session the return value is False to indicate nothing was done
and the processing of the current bar can continue
• If not-in-the-session, the bar is removed from the stream and True is
returned to indicate a new bar must be fetched.

Note

the data.backwards() makes uses of the LineBuffer interface. This digs deep
into the internals of backtrader.

The use of this filter:

• Some data feeds contain out of regular trading hours data, which may not be of interest to the trader.
With this filter only in-session bars will be considered.

Data Pseudo-API for Filters

In the example above it has been shown how the filter invokes data.backwards() to
remove the current bar from the stream. Useful calls from the data feed objects
which are meant as a pseudo-API for Filters are:

• data.backwards(size=1, force=False): removes size bars from the data stream


(default is 1) by moving the logical pointer backwards. If force=True, then
the physical storage is also removed.

Removing the physical storage is a delicate operation and is only meant as


a hack for internal operations.
338
backtrader’s documentation Version-1.9.58.122

• data.forward(value=float('NaN'), size=1): moves size bars the storage


forward, increasing the physical storage if needed be and fills with value
• data._addtostack(bar, stash=False): adds bar to a stack for later
processing. bar is an iterable containing as many values as lines has the
data feed.

If stash=False the bar added to the stack will be consumed immediately by


the system at the beginning of the next iteration.

If stash=True the bar will undergo the entire loop processing including
potentially being reparsed by filters

• data._save2stack(erase=False, force=False): saves the current data bar to


the stack for later processing. If erase=True then data.backwards will be
invoked and will receive the parameter force
• data._updatebar(bar, forward=False, ago=0): uses the values in the iterable
bar to overwrite the values in the data stream ago positions. With the
default ago=0 the current bar will updated. With -1, the previous one.

Another example: Pinkfish Filter

This is an example of a filter that can be chained, and is meant so, to another
filter, namely the replay filter. The Pinkfish name is from the library which
describes the idea in its main page: using daily data to execute operations which
would only be possible with intraday data.

To achieve the effect:

• A daily bar will be broken in 2 componentes: OHL and then C.


• Those 2 pieces are chained with replay to have the following happening in
the stream:
• With Len X -> OHL
• With Len X -> OHLC
• With Len X + 1 -> OHL
• With Len X + 1 -> OHLC
• With Len X + 2 -> OHL
• With Len X + 2 -> OHLC
• ...

Logic:

• When an OHLC bar is received it is copied into an interable and broken down to become:
o An OHL bar. Because this concept doesn’t actually exist the closing price is replaced with the
opening price to really form an OHLO bar.

339
backtrader’s documentation Version-1.9.58.122

o An C bar whic also doesn’t exist. The reality is that it will be delivered like a tick CCCC
o The volume if distributed between the 2 parts
o The current bar is removed from the stream
o The OHLO part is put onto the stack for immediate processing
o The CCCC part is put into the stash for processing in the next round
o Because the stack has something for immediate processing the filter can return False to indicate
it.

This filter works together with:

• The replay filter which puts together the OHLO and CCCC parts to finally deliver an OHLC bar.

The use case:

• Seeing something like if the maximum today is the highest maximum in the last 20 sessions an issuing a
Close order which gets executed with the 2nd tick.

The code:

class DaySplitter_Close(bt.with_metaclass(bt.MetaParams, object)):


'''
Splits a daily bar in two parts simulating 2 ticks which will be used to
replay the data:

- First tick: ``OHLX``

The ``Close`` will be replaced by the *average* of ``Open``, ``High``


and ``Low``

The session opening time is used for this tick

and

- Second tick: ``CCCC``

The ``Close`` price will be used for the four components of the price

The session closing time is used for this tick

The volume will be split amongst the 2 ticks using the parameters:

- ``closevol`` (default: ``0.5``) The value indicate which percentage, in


absolute terms from 0.0 to 1.0, has to be assigned to the *closing*
tick. The rest will be assigned to the ``OHLX`` tick.

340
backtrader’s documentation Version-1.9.58.122

**This filter is meant to be used together with** ``cerebro.replaydata``

'''
params = (
('closevol', 0.5), # 0 -> 1 amount of volume to keep for close
)

# replaying = True

def __init__(self, data):


self.lastdt = None

def __call__(self, data):


# Make a copy of the new bar and remove it from stream
datadt = data.datetime.date() # keep the date

if self.lastdt == datadt:
return False # skip bars that come again in the filter

self.lastdt = datadt # keep ref to last seen bar

# Make a copy of current data for ohlbar


ohlbar = [data.lines[i][0] for i in range(data.size())]
closebar = ohlbar[:] # Make a copy for the close

# replace close price with o-h-l average


ohlprice = ohlbar[data.Open] + ohlbar[data.High] + ohlbar[data.Low]
ohlbar[data.Close] = ohlprice / 3.0

vol = ohlbar[data.Volume] # adjust volume


ohlbar[data.Volume] = vohl = int(vol * (1.0 - self.p.closevol))

oi = ohlbar[data.OpenInterest] # adjust open interst


ohlbar[data.OpenInterest] = 0

# Adjust times
dt = datetime.datetime.combine(datadt, data.p.sessionstart)
ohlbar[data.DateTime] = data.date2num(dt)

# Ajust closebar to generate a single tick -> close price


closebar[data.Open] = cprice = closebar[data.Close]
closebar[data.High] = cprice
closebar[data.Low] = cprice
closebar[data.Volume] = vol - vohl
341
backtrader’s documentation Version-1.9.58.122

ohlbar[data.OpenInterest] = oi

# Adjust times
dt = datetime.datetime.combine(datadt, data.p.sessionend)
closebar[data.DateTime] = data.date2num(dt)

# Update stream
data.backwards(force=True) # remove the copied bar from stream
data._add2stack(ohlbar) # add ohlbar to stack
# Add 2nd part to stash to delay processing to next round
data._add2stack(closebar, stash=True)

return False # initial tick can be further processed from stack

Using Indicators

Indicators can be used in two places in the platform:

• Inside Strategies
• Inside other Indicators

Indicators in action

1. Indicators are always instantiated during __init__ in the Strategy


2. Indicator values (or values thereof derived) are used/checked during next

There is an important axiom to take into account:

• Any Indicator (or value thereof derived) declared during __init__ will be precalculated before next is
called.

Let’s go for the differences an operation modes.

__init__ vs next

Things works as follows:

• Any operation involving lines objects during __init__ generates another lines object
• Any operation involving lines objects during next yields regular Python types like floats and bools.

342
backtrader’s documentation Version-1.9.58.122

During __init__

Example of an operation during __init__:

hilo_diff = self.data.high - self.data.low

The variable hilo_diff holds a reference to a lines object which is precalculated


before calling next and can be accessed using the standard array notation []

It does obviously contains for each bar of the data feed the difference between
the high and the low.

This also works when mixing simple lines (like those in the self.data Data Feed)
and complex ones like indicators:

sma = bt.SimpleMovingAverage(self.data.close)
close_sma_diff = self.data.close - sma

Now close_sma_diff contains again a line object.

Using logical operatorss:

close_over_sma = self.data.close > sma

Now the generated lines object will contain an array of booleans.

During next

Example of an operation (logical operator):

close_over_sma = self.data.close > self.sma

Using the equivalent array (index 0 based notation):

close_over_sma = self.data.close[0] > self.sma[0]

In this case close_over_sma yields a boolen which is the result of comparing two
floating point values, the ones returned by the [0] operator applied to
self.data.close and self.sma

343
backtrader’s documentation Version-1.9.58.122

The __init__ vs next why

Logic simplification (and with it ease of use) is the key. Calculations and most
of the associated logic can be declared during __init__ keeping the actual
operational logic to a minimum during next.

There is actually a side benefit: speed (due to the precalculation explained at


the beginning)

A complete example which generates a buy signal during __init__:

class MyStrategy(bt.Strategy):

def __init__(self):

sma1 = btind.SimpleMovingAverage(self.data)
ema1 = btind.ExponentialMovingAverage()

close_over_sma = self.data.close > sma1


close_over_ema = self.data.close > ema1
sma_ema_diff = sma - ema

buy_sig = bt.And(close_over_sma, close_over_ema, sma_ema_diff > 0)

def next(self):

if buy_sig:
self.buy()

Note

Python’s and operator cannot be overriden, forcing the platform to define its
own And. The same applies to other constructs like Or and If

It should be obvious that the “declarative” approach during __init__ keeps the
bloating of next (where the actual strategy work happens) to a minimum.

(Don’t forget there is also a speed up factor)

Note

When the logic gets really complicated and involves several operations it is
usually much better to encapsulate that inside an Indicator.

344
backtrader’s documentation Version-1.9.58.122

Some notes

In the example above there are two things which have been simplified in
backtrader when compared to other platforms:

• Declared Indicators are neither getting a parent parameter (like the


strategy in which they are being created nor is any kind of “register”
method/function being called.

And in spite of it the strategy will kick the calculation of the Indicators
and any lines object generated because of operations (like sma - ema)

• ExponentialMovingAverage is being instantiated without self.data

This is intentional. If no data is passed, the 1st data of the parent (in
this case the Strategy in which is being created) will be automatically
passed in the background

Indicator Plotting

First and foremost:

• Declared Indicators get automatically plotted (if cerebro.plot is called)


• lines objects from operations DO NOT GET plotted (like close_over_sma =
self.data.close > self.sma)

There is an auxiliary LinePlotterIndicator which plots such operations if


wished with the following approach:

close_over_sma = self.data.close > self.sma


LinePlotterIndicator(close_over_sma, name='Close_over_SMA)

The name parameter gives name to the single line held by this indicator.

Controlling plotting

During the development of an Indicator a plotinfo declaration can be added. It


can be a tuple of tuples (2 elements), a dict or an OrderedDict. It looks like:

class MyIndicator(bt.Indicator):

....
plotinfo = dict(subplot=False)
345
backtrader’s documentation Version-1.9.58.122

....

The value can be later accessed (and set) as follows (if needed):

myind = MyIndicator(self.data, someparam=value)


myind.plotinfo.subplot = True

The value can even be set during instantiation:

myind = MyIndicator(self.data, someparams=value, subplot=True)

The subplot=True will be passed to the (behind the scenes) intantiated member
variable plotinfo for the indicator.

The plotinfo offers the following parameters to control plotting behavior:

• plot (default: True)

Whether the indicator is to be plotted or not

• subplot (default: True)

Whether to plot the indicator in a different window. For indicators like


moving averages the default is changed to False

• plotname (default: '')

Sets the plotname to show on the plot. The empty value means the canonical
name of the indicator (class.__name__) will be used. This has some
limitations because Python identifiers cannot use for example arithmetic
operators.

An indicator like DI+ will be declared as follows:

class DIPlus(bt.Indicator):
plotinfo=dict(plotname='DI+')

Making the plot “nicer”

• plotabove (default: False)

Indicators are usually plotted (those with subplot=True) below the data
they have operated on. Setting this to True will make the indicator be
plotted above the data.

• plotlinelabels (default: False)


346
backtrader’s documentation Version-1.9.58.122

Meant for “indicators” on “indicators”. If one calculates the


SimpleMovingAverage of the RSI the plot will usually show the name
“SimpleMovingAverage” for the corresponding plotted line. This is the
name of the “Indicator” and not the actual line being plotted.

This default behavior makes sense because the user wants to usually see
that a SimpleMovingAverage has been created using the RSI.

if the value is set to True the actual name of the line inside the
SimpleMovingAverage will be used.

• plotymargin (default: 0.0)

Amount of margin to leave at the top and bottom of the indicator (0.15 ->
15%). Sometimes the matplotlib plots go too far to the top/bottom of the
axis and a margin may be wished

• plotyticks (default: [])

Used to control the drawn y scale ticks

If an empty list is passed the “y ticks” will be automatically


calculated. For something like a Stochastic it may make sense to set this
to well-known idustry standards like: [20.0, 50.0, 80.0]

Some indicators offer parameters like upperband and lowerband that are
actually used to manipulate the y ticks

• plothlines (default: [])

Used to control the drawing of horizontal lines along the indicator axis.

If an empty list is passed no horizontal lines will drawn.

For something like a Stochastic it may make sense to draw lines for well-
known idustry standards like: [20.0, 80.0]

Some indicators offer parameters like upperband and lowerband that are
actually used to manipulate the horizontal lines

• plotyhlines (default: [])

Used to simultaneously control plotyticks and plothlines using a single


parameter.

• plotforce (default: False)


347
backtrader’s documentation Version-1.9.58.122

If for some reason you believe an indicator should be plotting and it is


not plotting ... set this to True as a last resort.

TA-Lib

Even if backtrader offers an already high number of built-in indicators and


developing an indicator is mostly a matter of defining the inputs, outputs and
writing the formula in a natural manner, some people want to use TA-LIB. Some of
the reasons:

• Indicator X is in the library and not in backtrader (the author would gladly accept a request)
• TA-LIB behavior is well known and people trust good old things

In order to satisfy each and every taste, TA-LIB integration is offered.

Requirements

• Python wrapper for TA-Lib


• Any dependencies needed by it (for example numpy)

The installation details are on the GitHub repository

Using ta-lib

As easy as using any of the indicators already built-in in backtrader. Example of


a Simple Moving Average. First the backtrader one:

import backtrader as bt

class MyStrategy(bt.Strategy):
params = (('period', 20),)

def __init__(self):
self.sma = bt.indicators.SMA(self.data, period=self.p.period)
...

...

Now the ta-lib example:

import backtrader as bt

348
backtrader’s documentation Version-1.9.58.122

class MyStrategy(bt.Strategy):
params = (('period', 20),)

def __init__(self):
self.sma = bt.talib.SMA(self.data, timeperiod=self.p.period)
...

...

Et voilá! Of course the params for the ta-lib indicators are defined by the
library itself and not by backtrader. In this case the SMA in ta-lib takes a
parameter named timeperiod to defined the size of the operating window.

For indicators that require more than one input, for example the Stochastic:

import backtrader as bt

class MyStrategy(bt.Strategy):
params = (('period', 20),)

def __init__(self):
self.stoc = bt.talib.STOCH(self.data.high, self.data.low,
self.data.close,
fastk_period=14, slowk_period=3,
slowd_period=3)

...

...

Notice how high, low and close have been individually passed. One could always
pass open instead of low (or any other data series) and experiment.

The ta-lib indicator documentation is automatically parsed and added to the


backtrader docs. You may also check the ta-lib source code/docs. Or adittionally
do:

print(bt.talib.SMA.__doc__)

Which in this case outputs:

SMA([input_arrays], [timeperiod=30])

Simple Moving Average (Overlap Studies)

349
backtrader’s documentation Version-1.9.58.122

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 30
Outputs:
real

Which offers some information:

• Which Input is to be expected (DISREGARD the ``ndarray`` comment because backtrader manages the
conversions in the background)
• Which parameters and which default values
• Which output lines the indicator actually offers

Moving Averages and MA_Type

To select a specific moving average for indicators like bt.talib.STOCH, the


standard ta-lib MA_Type is accesible with backtrader.talib.MA_Type. For example:

import backtrader as bt
print('SMA:', bt.talib.MA_Type.SMA)
print('T3:', bt.talib.MA_Type.T3)

Plotting ta-lib indicators

Just as with regular usage, there is nothing special to do to plot the ta-lib
indicators.

Note

Indicators which output a CANDLE (all those looking for a candlestick pattern)
deliver a binary output: either 0 or 100. In order to avoid adding a subplot to
the chart, there is an automated plotting translation to plot them over the data
at the point in time in which the pattern was recognized.

Examples and comparisons

The following are plots comparing the outputs of some ta-lib indicators against
the equivalent built-in indicators in backtrader. To consider:

• The ta-lib indicators get a TA_ prefix on the plot. This is specifically done by the sample to help the user
spot which is which

350
backtrader’s documentation Version-1.9.58.122

• Moving Averages (if both deliver the same result) will be plotted ON top of the other existing Moving
Average. The two indicators cannot be seen separately and the test is a pass if that’s the case.
• All samples include a CDLDOJI indicator as a reference

KAMA (Kaufman Moving Average)

This is the 1st example because it is the only (from all indicators which the
sample directly compare) that has a difference:

• The initial values of the the samples are not the same
• At some point in time, the values converge and both KAMA implementations have the same behavior.

After having analyzed the ta-lib source code:

• The implementation in ta-lib makes a non-industry standard choice for the


1st values of the KAMA.

The choice can be seen in the source code quoting from the source code):
The yesterday price is used here as the previous KAMA.

backtrader does the usual choice which is the same as for example the one from
Stockcharts:

• KAMA at StockCharts

Since we need an initial value to start the calculation, the first KAMA is
just a simple moving average

Hence the difference. Furthermore:

• The ta-lib KAMA implementation doesn’t allow specifying the fast and slow periods for the adjustment of
the scalable constant defined by Kaufman.

Sample execution:

$ ./talibtest.py --plot --ind kama

Output

351
backtrader’s documentation Version-1.9.58.122

SMA

$ ./talibtest.py --plot --ind sma

Output

352
backtrader’s documentation Version-1.9.58.122

EMA

$ ./talibtest.py --plot --ind ema

Output

353
backtrader’s documentation Version-1.9.58.122

Stochastic

$ ./talibtest.py --plot --ind stoc

Output

354
backtrader’s documentation Version-1.9.58.122

RSI

$ ./talibtest.py --plot --ind rsi

Output

355
backtrader’s documentation Version-1.9.58.122

MACD

$ ./talibtest.py --plot --ind macd

Output

356
backtrader’s documentation Version-1.9.58.122

Bollinger Bands

$ ./talibtest.py --plot --ind bollinger

Output

357
backtrader’s documentation Version-1.9.58.122

AROON

Note that ta-lib chooses to put the down line first and the colours are inverted
when compared with the backtrader built-in indicator.

$ ./talibtest.py --plot --ind aroon

Output

358
backtrader’s documentation Version-1.9.58.122

Ultimate Oscillator

$ ./talibtest.py --plot --ind ultimate

Output

359
backtrader’s documentation Version-1.9.58.122

Trix

$ ./talibtest.py --plot --ind trix

Output

360
backtrader’s documentation Version-1.9.58.122

ADXR

Here backtrader offers both the ADX and ADXR lines.

$ ./talibtest.py --plot --ind adxr

Output

361
backtrader’s documentation Version-1.9.58.122

DEMA

$ ./talibtest.py --plot --ind dema

Output

362
backtrader’s documentation Version-1.9.58.122

TEMA

$ ./talibtest.py --plot --ind tema

Output

363
backtrader’s documentation Version-1.9.58.122

PPO

Here backtrader offers not only the ppo line, but a more traditional macd
approach.

$ ./talibtest.py --plot --ind ppo

Output

364
backtrader’s documentation Version-1.9.58.122

WilliamsR

$ ./talibtest.py --plot --ind williamsr

Output

365
backtrader’s documentation Version-1.9.58.122

ROC

All indicators show have exactly the same shape, but how to track momentum or
rate of change has several definitions

$ ./talibtest.py --plot --ind roc

Output

366
backtrader’s documentation Version-1.9.58.122

Sample Usage

$ ./talibtest.py --help
usage: talibtest.py [-h] [--data0 DATA0] [--fromdate FROMDATE]
[--todate TODATE]
[--ind
{sma,ema,stoc,rsi,macd,bollinger,aroon,ultimate,trix,kama,adxr,dema,tema,ppo,will
iamsr,roc}]
[--no-doji] [--use-next] [--plot [kwargs]]

Sample for ta-lib

optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to be read in (default:
../../datas/yhoo-1996-2015.txt)

367
backtrader’s documentation Version-1.9.58.122

--fromdate FROMDATE Starting date in YYYY-MM-DD format (default:


2005-01-01)
--todate TODATE Ending date in YYYY-MM-DD format (default: 2006-12-31)
--ind
{sma,ema,stoc,rsi,macd,bollinger,aroon,ultimate,trix,kama,adxr,dema,tema,ppo,will
iamsr,roc}
Which indicator pair to show together (default: sma)
--no-doji Remove Doji CandleStick pattern checker (default:
False)
--use-next Use next (step by step) instead of once (batch)
(default: False)
--plot [kwargs], -p [kwargs]
Plot the read data applying any kwargs passed For
example (escape the quotes if needed): --plot
style="candle" (to plot candles) (default: None)

Sample Code

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
import datetime

import backtrader as bt

class TALibStrategy(bt.Strategy):
params = (('ind', 'sma'), ('doji', True),)

INDS = ['sma', 'ema', 'stoc', 'rsi', 'macd', 'bollinger', 'aroon',


'ultimate', 'trix', 'kama', 'adxr', 'dema', 'ppo', 'tema',
'roc', 'williamsr']

def __init__(self):
if self.p.doji:
bt.talib.CDLDOJI(self.data.open, self.data.high,
self.data.low, self.data.close)

if self.p.ind == 'sma':
bt.talib.SMA(self.data.close, timeperiod=25, plotname='TA_SMA')
bt.indicators.SMA(self.data, period=25)
elif self.p.ind == 'ema':

368
backtrader’s documentation Version-1.9.58.122

bt.talib.EMA(timeperiod=25, plotname='TA_SMA')
bt.indicators.EMA(period=25)
elif self.p.ind == 'stoc':
bt.talib.STOCH(self.data.high, self.data.low, self.data.close,
fastk_period=14, slowk_period=3, slowd_period=3,
plotname='TA_STOCH')

bt.indicators.Stochastic(self.data)

elif self.p.ind == 'macd':


bt.talib.MACD(self.data, plotname='TA_MACD')
bt.indicators.MACD(self.data)
bt.indicators.MACDHisto(self.data)
elif self.p.ind == 'bollinger':
bt.talib.BBANDS(self.data, timeperiod=25,
plotname='TA_BBANDS')
bt.indicators.BollingerBands(self.data, period=25)

elif self.p.ind == 'rsi':


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

elif self.p.ind == 'aroon':


bt.talib.AROON(self.data.high, self.data.low, plotname='TA_AROON')
bt.indicators.AroonIndicator(self.data)

elif self.p.ind == 'ultimate':


bt.talib.ULTOSC(self.data.high, self.data.low, self.data.close,
plotname='TA_ULTOSC')
bt.indicators.UltimateOscillator(self.data)

elif self.p.ind == 'trix':


bt.talib.TRIX(self.data, timeperiod=25, plotname='TA_TRIX')
bt.indicators.Trix(self.data, period=25)

elif self.p.ind == 'adxr':


bt.talib.ADXR(self.data.high, self.data.low, self.data.close,
plotname='TA_ADXR')
bt.indicators.ADXR(self.data)

elif self.p.ind == 'kama':


bt.talib.KAMA(self.data, timeperiod=25, plotname='TA_KAMA')
bt.indicators.KAMA(self.data, period=25)

369
backtrader’s documentation Version-1.9.58.122

elif self.p.ind == 'dema':


bt.talib.DEMA(self.data, timeperiod=25, plotname='TA_DEMA')
bt.indicators.DEMA(self.data, period=25)

elif self.p.ind == 'ppo':


bt.talib.PPO(self.data, plotname='TA_PPO')
bt.indicators.PPO(self.data, _movav=bt.indicators.SMA)

elif self.p.ind == 'tema':


bt.talib.TEMA(self.data, timeperiod=25, plotname='TA_TEMA')
bt.indicators.TEMA(self.data, period=25)

elif self.p.ind == 'roc':


bt.talib.ROC(self.data, timeperiod=12, plotname='TA_ROC')
bt.talib.ROCP(self.data, timeperiod=12, plotname='TA_ROCP')
bt.talib.ROCR(self.data, timeperiod=12, plotname='TA_ROCR')
bt.talib.ROCR100(self.data, timeperiod=12, plotname='TA_ROCR100')
bt.indicators.ROC(self.data, period=12)
bt.indicators.Momentum(self.data, period=12)
bt.indicators.MomentumOscillator(self.data, period=12)

elif self.p.ind == 'williamsr':


bt.talib.WILLR(self.data.high, self.data.low, self.data.close,
plotname='TA_WILLR')
bt.indicators.WilliamsR(self.data)

def runstrat(args=None):
args = parse_args(args)

cerebro = bt.Cerebro()

dkwargs = dict()
if args.fromdate:
fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
dkwargs['fromdate'] = fromdate

if args.todate:
todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
dkwargs['todate'] = todate

data0 = bt.feeds.YahooFinanceCSVData(dataname=args.data0, **dkwargs)


cerebro.adddata(data0)

370
backtrader’s documentation Version-1.9.58.122

cerebro.addstrategy(TALibStrategy, ind=args.ind, doji=not args.no_doji)

cerebro.run(runcone=not args.use_next, stdstats=False)


if args.plot:
pkwargs = dict(style='candle')
if args.plot is not True: # evals to True but is not True
npkwargs = eval('dict(' + args.plot + ')') # args were passed
pkwargs.update(npkwargs)

cerebro.plot(**pkwargs)

def parse_args(pargs=None):

parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Sample for sizer')

parser.add_argument('--data0', required=False,
default='../../datas/yhoo-1996-2015.txt',
help='Data to be read in')

parser.add_argument('--fromdate', required=False,
default='2005-01-01',
help='Starting date in YYYY-MM-DD format')

parser.add_argument('--todate', required=False,
default='2006-12-31',
help='Ending date in YYYY-MM-DD format')

parser.add_argument('--ind', required=False, action='store',


default=TALibStrategy.INDS[0],
choices=TALibStrategy.INDS,
help=('Which indicator pair to show together'))

parser.add_argument('--no-doji', required=False, action='store_true',


help=('Remove Doji CandleStick pattern checker'))

parser.add_argument('--use-next', required=False, action='store_true',


help=('Use next (step by step) '
'instead of once (batch)'))

# Plot options
parser.add_argument('--plot', '-p', nargs='?', required=False,
371
backtrader’s documentation Version-1.9.58.122

metavar='kwargs', const=True,
help=('Plot the read data applying any kwargs passed\n'
'\n'
'For example (escape the quotes if needed):\n'
'\n'
' --plot style="candle" (to plot candles)\n'))

if pargs is not None:


return parser.parse_args(pargs)

return parser.parse_args()

if __name__ == '__main__':
runstrat()

Mixing Timeframes in Indicators

Release 1.3.0.92 brings up the possibility to have data (from either data feeds
and/or indicators) from different timeframes mixed.

Background: Indicators are smart dumb objects.

• They are smart because they can make complex calculations.


• They are dumb because they operate with no knowledge of what sources are providing the data for the
calculations

As such:

• If the data sources providing the values have different timeframes, different lengths inside the Cerebro
engine, the indicator will break.

Example of a calculation, in which data0 has a timeframe of days and data1 has a
timeframe of months:

pivotpoint = btind.PivotPoint(self.data1)
sellsignal = self.data0.close < pivotpoint.s1

Here a sell signal is sought when the close is below the s1 line (1st support)

Note

PivotPoint works in a larger timeframe by definition

This will in the past with the following error:


372
backtrader’s documentation Version-1.9.58.122

return self.array[self.idx + ago]


IndexError: array index out of range

And for a good reason: self.data.close provides values from the very 1st instant,
but PivotPoint (and hence the s1 line) will only deliver values once a full month
has gone by, which is roughly equivalent to 22 values of self.data0.close. During
this 22 closes there isn’t yet a value for s1 and the attempt to fetch it from
the underlying array fails.

Lines objects support the (ago) operator (__call__ special method in Python) for
deliver a delayed version of itself:

close1 = self.data.close(-1)

In this example the object close1 (when accessed via [0]) always contains the
previous value (-1) delivered by close. The syntax has been reused to accomodate
adapting timeframes. Let’s rewrite the above pivotpoint snippet:

pivotpoint = btind.PivotPoint(self.data1)
sellsignal = self.data0.close < pivotpoint.s1()

See how the () is executed with no arguments (in the background a None is being
supplied). The following is happening:

• pivotpoint.s1() is returning an internal LinesCoupler object which follows the rhythm of the larger scope.
This coupler fills itself with the latest delivered value from the real s1 (starting with a default value of
NaN)

But something extra is needed to make the magic work. Cerebro has to be created
with:

cerebro = bt.Cerebro(runonce=False)

or executed with:

cerebro.run(runonce=False)

In this mode the indicators and late-evaluated automatic lines objects are
executed step by step rather than in tight loops. This makes the entire operation
slower, but it makes it possible

The sample script at the bottom which was breaking above, now runs:

$ ./mixing-timeframes.py

With output:
373
backtrader’s documentation Version-1.9.58.122

0021,0021,0001,2005-01-31,2984.75,2935.96,0.00
0022,0022,0001,2005-02-01,3008.85,2935.96,0.00
...
0073,0073,0003,2005-04-15,3013.89,3010.76,0.00
0074,0074,0003,2005-04-18,2947.79,3010.76,1.00
...

At line 74 the 1st instance of close < s1 takes place.

The script also provides insight into the additional possiblity: couple all lines
of an indicator. Before we had:

self.sellsignal = self.data0.close < pp.s1()

Being the alternative:

pp1 = pp()
self.sellsignal = self.data0.close < pp1.s1

Now the entire PivotPoint indicator has been coupled and any of its lines can be
accessed (namely p, r1, r2, s1, s2). The script has only interest in s1 and the
access is direct.:

$ ./mixing-timeframes.py --multi

The output:

0021,0021,0001,2005-01-31,2984.75,2935.96,0.00
0022,0022,0001,2005-02-01,3008.85,2935.96,0.00
...
0073,0073,0003,2005-04-15,3013.89,3010.76,0.00
0074,0074,0003,2005-04-18,2947.79,3010.76,1.00
...

No surprises here. The same as before. The “coupled” object can even be
plotted:

$ ./mixing-timeframes.py --multi --plot

374
backtrader’s documentation Version-1.9.58.122

Full coupling syntax

For lines objects with multiple lines (for example Indicators like PivotPoint):

• obj(clockref=None, line=-1)
o clockref If clockref is None, the surrounding object (in the examples
a Strategy) will be the reference to adapt larger timeframes (for
example: Months) to smaller/faster timeframes (for example: Days)

Another reference can be used if desired

• line
o If the default -1 is given, all lines are coupled.
o If another integer (for example, 0 or 1) a single line will be
coupled and fetched by index (from obj.lines[x])
o If a string is passed, the line will be fetched by name.

375
backtrader’s documentation Version-1.9.58.122

In the sample, the following could have been done:

coupled_s1 = pp(line='s1')

For lines objects with a single line (for example line s1 from the indicator
PivotPoint):

• obj(clockref=None) (see above for clockref)

Conclusion

Integrated in the regular () syntax, data feeds from different timeframes can be
mixed in indicators, always taking into account that cerebro needs to be
instantiated or created with runonce=False.

Script Code and Usage

Available as sample in the sources of backtrader. Usage:

$ ./mixing-timeframes.py --help
usage: mixing-timeframes.py [-h] [--data DATA] [--multi] [--plot]

Sample for pivot point and cross plotting

optional arguments:
-h, --help show this help message and exit
--data DATA Data to be read in (default: ../../datas/2005-2006-day-001.txt)
--multi Couple all lines of the indicator (default: False)
--plot Plot the result (default: False)

The code:

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse

import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind
import backtrader.utils.flushfile

376
backtrader’s documentation Version-1.9.58.122

class St(bt.Strategy):
params = dict(multi=True)

def __init__(self):
self.pp = pp = btind.PivotPoint(self.data1)
pp.plotinfo.plot = False # deactivate plotting

if self.p.multi:
pp1 = pp() # couple the entire indicators
self.sellsignal = self.data0.close < pp1.s1
else:
self.sellsignal = self.data0.close < pp.s1()

def next(self):
txt = ','.join(
['%04d' % len(self),
'%04d' % len(self.data0),
'%04d' % len(self.data1),
self.data.datetime.date(0).isoformat(),
'%.2f' % self.data0.close[0],
'%.2f' % self.pp.s1[0],
'%.2f' % self.sellsignal[0]])

print(txt)

def runstrat():
args = parse_args()

cerebro = bt.Cerebro()
data = btfeeds.BacktraderCSVData(dataname=args.data)
cerebro.adddata(data)
cerebro.resampledata(data, timeframe=bt.TimeFrame.Months)

cerebro.addstrategy(St, multi=args.multi)

cerebro.run(stdstats=False, runonce=False)
if args.plot:
cerebro.plot(style='bar')

def parse_args():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
377
backtrader’s documentation Version-1.9.58.122

description='Sample for pivot point and cross plotting')

parser.add_argument('--data', required=False,
default='../../datas/2005-2006-day-001.txt',
help='Data to be read in')

parser.add_argument('--multi', required=False, action='store_true',


help='Couple all lines of the indicator')

parser.add_argument('--plot', required=False, action='store_true',


help=('Plot the result'))

return parser.parse_args()

if __name__ == '__main__':
runstrat()

Indicator Development

If anything (besides one or more winning Strategies) must ever be developed, this
something is a custom Indicator.

Such development within the platform is, according to the author, easy.

The following is needed:

• A class derived from Indicator (either directly or from an already existing


subclass)
• Define the lines it will hold

An indicator must at least have 1 line. If deriving from an existing one,


the line(s) may have already be defined

• Optionally define parameters which can alter the behavior


• Optionally provided/customize some of the elements which enable sensible
plotting of the indicators
• Provide a fully defined operation in __init__ with a binding (assignment)
to the line(s) of the indicator or else provide next and (optionally) once
methods

If an indicator can be fully defined with logic/arithmetic operations


during initialization and the result is assigned to the line: done

378
backtrader’s documentation Version-1.9.58.122

Be it not the case, at least a next has to be provided where the indicator
must assign a value to the line(s) at index 0

Optimization of the calculation for the runonce mode (batch operation) can
be achieved by providing a once method.

Important note: Idempotence

Indicators produce an output for each bar they receive. No assumption has to be
made about how many times the same bar will be sent. Operations have to be
idempotent.

The rationale behind this:

• The same bar (index-wise) can be sent many times with changing values (namely the changing value is the
closing price)

This enables, for example, “replaying” a daily session but using intraday data
which could be made of 5 minutes bars.

It could also allow the platform to get values from a live feed.

A dummy (but functional) indicator

So can it be:

class DummyInd(bt.Indicator):
lines = ('dummyline',)

params = (('value', 5),)

def __init__(self):
self.lines.dummyline = bt.Max(0.0, self.params.value)

Done! The indicator will output always the same value: either 0.0 or
self.params.value if it happens to be greater than 0.0.

The same indicator but using the next method:

class DummyInd(bt.Indicator):
lines = ('dummyline',)

params = (('value', 5),)

379
backtrader’s documentation Version-1.9.58.122

def next(self):
self.lines.dummyline[0] = max(0.0, self.params.value)

Done! Same behavior.

Note

Notice how in the __init__ version bt.Max is used to assign to the Line object
self.lines.dummyline.

bt.Max returns an lines object that is automatically iterated for each bar passed
to the indicator.

Had max been used instead, the assigment would have been pointless, because
instead of a line, the indicator would have a member variable with a fixed value.

During next the work is done directly with floating point values and the standard
max built-in can be used

Let’s recall that self.lines.dummyline is the long notation and that it can be
shortened to:

• self.l.dummyline

and even to:

• self.dummyline

The latter being only possible if the code has not obscured this with a member
attribute.

The 3rd and last version provides an additional once method to optimize the
calculation:

class DummyInd(bt.Indicator):
lines = ('dummyline',)

params = (('value', 5),)

def next(self):
self.lines.dummyline[0] = max(0.0, self.params.value)

def once(self, start, end):


dummy_array = self.lines.dummyline.array

for i in xrange(start, end):


380
backtrader’s documentation Version-1.9.58.122

dummy_array[i] = max(0.0, self.params.value)

A lot more effective but developing the once method has forced to scratch beyond
the surface. Actually the guts have been looked into.

The __init__ version is in any case the best:

• Everything is confined to the initialization


• next and once (both optimized, because bt.Max already has them) are provided automatically with no
need to play with indices and/or formulas

Be it needed for development, the indicator can also override the methods
associated to next and once:

• prenext and nexstart


• preonce and oncestart

Manual/Automatic Minimum Period

If possible the platform will calculate it, but manual action may be needed.

Here is a potential implementation of a Simple Moving Average:

class SimpleMovingAverage1(Indicator):
lines = ('sma',)
params = (('period', 20),)

def next(self):
datasum = math.fsum(self.data.get(size=self.p.period))
self.lines.sma[0] = datasum / self.p.period

Although it seems sound, the platform doesn’t know what the minimum period is,
even if the parameter is named “period” (the name could be misleading and some
indicators receive several “period”s which have different usages)

In this case next would be called already for the 1st bar and everthing would
explode because get cannot return the needed self.p.period.

Before solving the situation something has to be taken into account:

• The data feeds passed to the indicators may already carry a minimum period

The sample SimpleMovingAverage may be done on for example:

• A regular data feed


381
backtrader’s documentation Version-1.9.58.122

This has a default mininum period of 1 (just wait for the 1st bar that
enters the system)

• Another Moving Average ... and this in turn already has a period

If this is 20 and again our sample moving average has also 20, we end up
with a minimum period of 40 bars

Actually the internal calculation says 39 ... because as soon as the first
moving average has produced a bar this counts for the next moving average,
which creates an overlapping bar, thus 39 are needed.

• Other indicators/objects which also carry periods

Alleviating the situation is done as follows:

class SimpleMovingAverage1(Indicator):
lines = ('sma',)
params = (('period', 20),)

def __init__(self):
self.addminperiod(self.params.period)

def next(self):
datasum = math.fsum(self.data.get(size=self.p.period))
self.lines.sma[0] = datasum / self.p.period

The addminperiod method is telling the system to take into account the extra
period bars needed by this indicator to whatever minimum period there may be in
existence.

Sometimes this is absolutely not needed, if all calculations are done with
objects which already communicate its period needs to the system.

A quick MACD implementation with Histogram:

from backtrader.indicators import EMA

class MACD(Indicator):
lines = ('macd', 'signal', 'histo',)
params = (('period_me1', 12), ('period_me2', 26), ('period_signal', 9),)

def __init__(self):
me1 = EMA(self.data, period=self.p.period_me1)
me2 = EMA(self.data, period=self.p.period_me2)

382
backtrader’s documentation Version-1.9.58.122

self.l.macd = me1 - me2


self.l.signal = EMA(self.l.macd, period=self.p.period_signal)
self.l.histo = self.l.macd - self.l.signal

Done! No need to think about mininum periods.

• EMA stands for Exponential Moving Average (a platform built-in alias)

And this one (already in the platform) already states what it needs

• The named lines of the indicator “macd” and “signal” are being assigned
objects which already carry declared (behind the scenes) periods
o macd takes the period from the operation “me1 - me2” which has in turn take the maximum
from the periods of me1 and me2 (which are both exponential moving averages with different
periods)
o signal takes directly the period of the Exponential Moving Average over macd. This EMA also
takes into account the already existing macd period and the needed amount of samples
(period_signal) to calculate itself
o histo takes the maximum of the two operands “signal - macd”. Once both are ready can histo also
produce a value

A full custom indicator

Let’s develop a simple custom indicator which “indicates” if a moving average


(which can be modified with a parameter) is above the given data:

import backtrader as bt
import backtrader.indicators as btind

class OverUnderMovAv(bt.Indicator):
lines = ('overunder',)
params = dict(period=20, movav=btind.MovAv.Simple)

def __init__(self):
movav = self.p.movav(self.data, period=self.p.period)
self.l.overunder = bt.Cmp(movav, self.data)

Done! The indicator will have a value of “1” if the average is above the data
and “-1” if below.

Be the data a regular data feed the 1s and -1s would be produced comparing with
the close price.

383
backtrader’s documentation Version-1.9.58.122

Although more can be seen in the Plotting section and to have a behaved and nice
citizen in the plotting world, a couple of things can be added:

import backtrader as bt
import backtrader.indicators as btind

class OverUnderMovAv(bt.Indicator):
lines = ('overunder',)
params = dict(period=20, movav=bt.ind.MovAv.Simple)

plotinfo = dict(
# Add extra margins above and below the 1s and -1s
plotymargin=0.15,

# Plot a reference horizontal line at 1.0 and -1.0


plothlines=[1.0, -1.0],

# Simplify the y scale to 1.0 and -1.0


plotyticks=[1.0, -1.0])

# Plot the line "overunder" (the only one) with dash style
# ls stands for linestyle and is directly passed to matplotlib
plotlines = dict(overunder=dict(ls='--'))

def _plotlabel(self):
# This method returns a list of labels that will be displayed
# behind the name of the indicator on the plot

# The period must always be there


plabels = [self.p.period]

# Put only the moving average if it's not the default one
plabels += [self.p.movav] * self.p.notdefault('movav')

return plabels

def __init__(self):
movav = self.p.movav(self.data, period=self.p.period)
self.l.overunder = bt.Cmp(movav, self.data)

Observers and Statistics

Strategies running inside the backtrader do mostly deal with data feeds and
indicators.
384
backtrader’s documentation Version-1.9.58.122

Data feeds are added to Cerebro instances and end up being part of the input of
strategies (parsed and served as attributes of the instance) whereas Indicators
are declared and managed by the Strategy itself.

All backtrader sample charts have so far had 3 things plotted which seem to be
taken for granted because they are not declared anywhere:

• Cash and Value (what’s happening with the money in the broker)
• Trades (aka Operations)
• Buy/Sell Orders

They are Observers and exist within the submodule backtrader.observers. They are
there because Cerebro supports a parameter to automatically add (or not) them to
the Strategy:

• stdstats (default: True)

If the default is respected Cerebro executes the following equivalent user code:

import backtrader as bt

...

cerebro = bt.Cerebro() # default kwarg: stdstats=True

cerebro.addobserver(bt.observers.Broker)
cerebro.addobserver(bt.observers.Trades)
cerebro.addobserver(bt.observers.BuySell)

Let’s see the usual chart with those 3 default observers (even if no order is
issued and therefore no trade happens and there is no change to the cash and
portfolio value)

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import backtrader as bt
import backtrader.feeds as btfeeds

if __name__ == '__main__':
cerebro = bt.Cerebro(stdstats=False)
cerebro.addstrategy(bt.Strategy)

data = bt.feeds.BacktraderCSVData(dataname='../../datas/2006-day-001.txt')
cerebro.adddata(data)
385
backtrader’s documentation Version-1.9.58.122

cerebro.run()
cerebro.plot()

Now let’s change the value of stdstats to False when creating the Cerebro
instance (can also be done when invoking run):

cerebro = bt.Cerebro(stdstats=False)

The chart is different now.

386
backtrader’s documentation Version-1.9.58.122

Accesing the Observers

The Observers as seen above are already there in the default case and collecting
information which can be used for statistical purposes and that’s why acess to
the observers can be done through an attribute of the strategy called:

• stats

It is simply a placeholder. If we recall the addition of one of the default


Observers as laid out above:

...
cerebro.addobserver(backtrader.observers.Broker)
...

387
backtrader’s documentation Version-1.9.58.122

The obvious question would be how to access the Broker observer. Here for example
how it’s done from the next method of a strategy:

class MyStrategy(bt.Strategy):

def next(self):

if self.stats.broker.value[0] < 1000.0:


print('WHITE FLAG ... I LOST TOO MUCH')
elif self.stats.broker.value[0] > 10000000.0:
print('TIME FOR THE VIRGIN ISLANDS ....!!!')

The Broker observer just like a Data, an Indicator and the Strategy itself is
also a Lines objects. In this case the Broker has 2 lines:

• cash
• value

Observer Implementation

The implementation is very similar to that of an Indicator:

class Broker(Observer):
alias = ('CashValue',)
lines = ('cash', 'value')

plotinfo = dict(plot=True, subplot=True)

def next(self):
self.lines.cash[0] = self._owner.broker.getcash()
self.lines.value[0] = value = self._owner.broker.getvalue()

Steps:

• Derive from Observer (and not from Indicator)


• Declare lines and params as needed (Broker has 2 lines but no params)
• There will be an automatic attribute _owner which is the strategy holding the observer

Observers come in action:

• After all Indicators have been calculated


• After the Strategy next method has been executed
• That means: at the end of the cycle ... they observe what has happened

388
backtrader’s documentation Version-1.9.58.122

In the Broker case it’s simply blindly recording the broker cash and portfolio
values at each point in time.

Adding Observers to the Strategy

As already pointed out above, Cerebro is using the stdstats parameter to decide
whether to add 3 default Observers, alleviating the work of the end user.

Adding other Observers to the mix is possible, be it along the stdstats or


removing those.

Let’s go for the usual strategy which buys when the close price goes above a
SimpleMovingAverage and sells if the opposite is true.

With one “addition”:

• DrawDown which is an already existing observer in the backtrader ecosystem

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
import datetime
import os.path
import time
import sys

import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind

class MyStrategy(bt.Strategy):
params = (('smaperiod', 15),)

def log(self, txt, dt=None):


''' Logging function fot this strategy'''
dt = dt or self.data.datetime[0]
if isinstance(dt, float):
dt = bt.num2date(dt)
print('%s, %s' % (dt.isoformat(), txt))

def __init__(self):
389
backtrader’s documentation Version-1.9.58.122

The visual output shows the evolution of the drawdown

And part of the text output:

...
2006-12-14T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-15T23:59:59+00:00, DrawDown: 0.22
2006-12-15T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-18T23:59:59+00:00, DrawDown: 0.00
2006-12-18T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-19T23:59:59+00:00, DrawDown: 0.00
2006-12-19T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-20T23:59:59+00:00, DrawDown: 0.10
2006-12-20T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-21T23:59:59+00:00, DrawDown: 0.39
2006-12-21T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-22T23:59:59+00:00, DrawDown: 0.21
390
backtrader’s documentation Version-1.9.58.122

2006-12-22T23:59:59+00:00, MaxDrawDown: 2.62


2006-12-27T23:59:59+00:00, DrawDown: 0.28
2006-12-27T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-28T23:59:59+00:00, DrawDown: 0.65
2006-12-28T23:59:59+00:00, MaxDrawDown: 2.62
2006-12-29T23:59:59+00:00, DrawDown: 0.06
2006-12-29T23:59:59+00:00, MaxDrawDown: 2.62

Note

As seen in the text output and in the code, the DrawDown observer has actually 2
lines:

• drawdown
• maxdrawdown

The choice is not to plot the maxdrawdown line, but make it is still available to
the user.

Actually the last value of maxdrawdown is also available in a direct attribute


(not a line) with the name of maxdd

Developing Observers

The implementation of the Broker observer was shown above. To produce a


meaningful observer, the implementation can use the following information:

• self._owner is the currently strategy being executed

As such anything within the strategy is available to the observer

• Default internal things available in the strategy which may be useful:


o broker -> attribute giving access to the broker instance the strategy
creates order against

As seen in Broker, cash and portfolio values are collected by


invoking the methods getcash and getvalue

• _orderspending -> list orders created by the strategy and for which the
broker has notified an event to the strategy.

The BuySell observer traverses the list looking for orders which have
executed (totally or partially) to create an average execution price for
the given point in time (index 0)

391
backtrader’s documentation Version-1.9.58.122

• _tradespending -> list of trades (a set of completed buy/sell or sell/buy


pairs) which is compiled from the buy/sell orders

An Observer can obviously access other observers over the self._owner.stats path.

Custom OrderObserver

The standard BuySell observer does only care about operations which have
executed. We can create an observer which shows when orders where created and if
they expired.

For the sake of visibility the display will not be plotted along the price but on
a separate axis.

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import math

import backtrader as bt

class OrderObserver(bt.observer.Observer):
lines = ('created', 'expired',)

plotinfo = dict(plot=True, subplot=True, plotlinelabels=True)

plotlines = dict(
created=dict(marker='*', markersize=8.0, color='lime', fillstyle='full'),
expired=dict(marker='s', markersize=8.0, color='red', fillstyle='full')
)

def next(self):
for order in self._owner._orderspending:
if order.data is not self.data:
continue

if not order.isbuy():
continue

# Only interested in "buy" orders, because the sell orders


# in the strategy are Market orders and will be immediately
# executed

392
backtrader’s documentation Version-1.9.58.122

if order.status in [bt.Order.Accepted, bt.Order.Submitted]:


self.lines.created[0] = order.created.price

elif order.status in [bt.Order.Expired]:


self.lines.expired[0] = order.created.price

The custom observer only cares about buy orders, because this is a strategy which
only buys to try to make a profit. Sell orders are Market orders and will be
executed immediately.

The Close-SMA CrossOver strategy is changed to:

• Create a Limit order with a price below 1.0% the close price at the moment of the signal
• A validity for the order of 7 (calendar) days

The resulting chart.

393
backtrader’s documentation Version-1.9.58.122

Several orders have expired as can be seen in the new subchart (red squares) and
we can also appreciate that between “creation” and “execution” several days
happen to be.

Finally the code for this strategy which applies the new observer

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import datetime

import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind

from orderobserver import OrderObserver

class MyStrategy(bt.Strategy):
params = (
('smaperiod', 15),
('limitperc', 1.0),
('valid', 7),
)

def log(self, txt, dt=None):


''' Logging function fot this strategy'''
dt = dt or self.data.datetime[0]
if isinstance(dt, float):
dt = bt.num2date(dt)
print('%s, %s' % (dt.isoformat(), txt))

def notify_order(self, order):


if order.status in [order.Submitted, order.Accepted]:
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
self.log('ORDER ACCEPTED/SUBMITTED', dt=order.created.dt)
self.order = order
return

if order.status in [order.Expired]:
self.log('BUY EXPIRED')

elif order.status in [order.Completed]:


if order.isbuy():

394
backtrader’s documentation Version-1.9.58.122

self.log(
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))

else: # Sell
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))

# Sentinel to None: new orders allowed


self.order = None

def __init__(self):
# SimpleMovingAverage on main data
# Equivalent to -> sma = btind.SMA(self.data, period=self.p.smaperiod)
sma = btind.SMA(period=self.p.smaperiod)

# CrossOver (1: up, -1: down) close / sma


self.buysell = btind.CrossOver(self.data.close, sma, plot=True)

# Sentinel to None: new ordersa allowed


self.order = None

def next(self):
if self.order:
# pending order ... do nothing
return

# Check if we are in the market


if self.position:
if self.buysell < 0:
self.log('SELL CREATE, %.2f' % self.data.close[0])
self.sell()

elif self.buysell > 0:


plimit = self.data.close[0] * (1.0 - self.p.limitperc / 100.0)
valid = self.data.datetime.date(0) + \
datetime.timedelta(days=self.p.valid)
self.log('BUY CREATE, %.2f' % plimit)
self.buy(exectype=bt.Order.Limit, price=plimit, valid=valid)

395
backtrader’s documentation Version-1.9.58.122

def runstrat():
cerebro = bt.Cerebro()

data = bt.feeds.BacktraderCSVData(dataname='../../datas/2006-day-001.txt')
cerebro.adddata(data)

cerebro.addobserver(OrderObserver)

cerebro.addstrategy(MyStrategy)
cerebro.run()

cerebro.plot()

if __name__ == '__main__':
runstrat()

Saving/Keeping the statistics

As of now backtrader has not implemented any mechanism to track the values of
observers storing them into files. The best way to do it:

• Open a file in the start method of the strategy


• Write the values down in the next method of the strategy

Considering the DrawDown observer, it could be done like this

class MyStrategy(bt.Strategy):

def start(self):

self.mystats = open('mystats.csv', 'wb')


self.mystats.write('datetime,drawdown, maxdrawdown\n')

def next(self):
self.mystats.write(self.data.datetime.date(0).strftime('%Y-%m-%d'))
self.mystats.write(',%.2f' % self.stats.drawdown.drawdown[-1])
self.mystats.write(',%.2f' % self.stats.drawdown.maxdrawdown-1])
self.mystats.write('\n')

To save the values of index 0, once all observers have been processed a custom
observer which writes to a file could be added as the last observer to the system
to save values to a csv file.
396
backtrader’s documentation Version-1.9.58.122

Note

The Writer functionality can automate this task.

Benchmarking

Ticket #89 is about adding benchmarking against an asset. Sensible as one may
actually have a strategy that even if positive is below what simply tracking the
asset would have delivered.

backtrader includes 2 different types of objects which can aid with tracking:

• Observers
• Analyzers

In the realm of Analyzers there was already a TimeReturn object which tracks the
evolution of the returns of the entire portfolio value (i.e: including cash)

This could have also obviously been an Observer, so whilst adding some
benchmarking some work has also gone into being able to plug together an Observer
and an Analyzer which are meant to track the same thing.

Note

The major difference between Observers and Analyzers is the lines nature of
observers, which record every value and this makes them suitable for plotting and
real time querying. This of course consumes memory.

Analyzers on the other hand return a set of results via get_analysis and the
implementation may not deliver any result until the very end of a run.

Analyzers - Benchmarking

The standard TimeReturn analyzer has been extended to support tracking a data
feed. The 2 major parameters invoved:

• timeframe (default: None) If None then the complete return over the entire
backtested period will be reported

Pass TimeFrame.NoTimeFrame to consider the entire dataset with no time


constraints

• data (default: None)

397
backtrader’s documentation Version-1.9.58.122

Reference asset to track instead of the portfolio value.

Note

this data must have been added to a cerebro instance with addata,
resampledata or replaydata

For more details and parameters: Analyzers Reference

As such, the returns of the porftolio on a yearly basis can be tracked like this

import backtrader as bt

cerebro = bt.Cerebro()
cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years)

... # add datas, strategies ...

results = cerebro.run()
strat0 = results[0]

# If no name has been specified, the name is the class name lowercased
tret_analyzer = strat0.analyzers.getbyname('timereturn')
print(tret_analyzer.get_analysis())

If we wanted to track the returns of a data

import backtrader as bt

cerebro = bt.Cerebro()

data = bt.feeds.OneOfTheFeeds(dataname='abcde', ...)


cerebro.adddata(data)

cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years,
data=data)

... # add strategies ...

results = cerebro.run()
strat0 = results[0]

# If no name has been specified, the name is the class name lowercased
tret_analyzer = strat0.analyzers.getbyname('timereturn')
print(tret_analyzer.get_analysis())
398
backtrader’s documentation Version-1.9.58.122

If both are to be tracked, the best is to assign names to the analyzers

import backtrader as bt

cerebro = bt.Cerebro()

data = bt.feeds.OneOfTheFeeds(dataname='abcde', ...)


cerebro.adddata(data)

cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years,
data=data, _name='datareturns')

cerebro.addanalyzer(bt.analyzers.TimeReturn, timeframe=bt.TimeFrame.Years)
_name='timereturns')

... # add strategies ...

results = cerebro.run()
strat0 = results[0]

# If no name has been specified, the name is the class name lowercased
tret_analyzer = strat0.analyzers.getbyname('timereturns')
print(tret_analyzer.get_analysis())
tdata_analyzer = strat0.analyzers.getbyname('datareturns')
print(tdata_analyzer.get_analysis())

Observers - Benchmarking

Thanks to the background machinery allowing the usage of Analyzers inside


Observers, 2 new observers have been added:

• TimeReturn
• Benchmark

Both use the bt.analyzers.TimeReturn analyzer to collect results.

Rather than having code snippets like above, a full sample with some runs to show
their functionality.

Observing TimeReturn

Execution:

399
backtrader’s documentation Version-1.9.58.122

$ ./observer-benchmark.py --plot --timereturn --timeframe notimeframe

Notice the execution options:

• --timereturn telling the sample to do just that


• --timeframe notimeframe telling the analyzer to consider the entire data set disregarding timeframe
boundaries.

The last plotted value is -0.26.

• The starting cash (obvious from the chart) are 50K monetary units and the strategy ends up with 36,970
monetary units and hence a -26% value decrement.

Observing Benchmarking

Because benchmarking will also display the timereturn results, let’s run the
same thing but with benchmarking active:

400
backtrader’s documentation Version-1.9.58.122

$ ./observer-benchmark.py --plot --timeframe notimeframe

Hey, hey hey!!!

• The strategy is better than the asset: -0.26 vs -0.33

It shouldn’t be a matter for celebration but at least is clear the


strategy is not even as bad as the asset.

Moving down to track things on a yearly basis:

$ ./observer-benchmark.py --plot --timeframe years

401
backtrader’s documentation Version-1.9.58.122

Watch out!

• The strategy last value has changed very slightly from -0.26 to -0.27
• The asset on the on the other hand shows a last value of -0.35 (versus -0.33 above)

The reason for values so close to each other is that when moving from 2005 to
2006, both the strategy and the benchmarking asset were almost at the starting
level from the beginning of 2005.

Switching to a lower timeframe like weeks, the entire picture changes:

$ ./observer-benchmark.py --plot --timeframe weeks

.. thumbnail:: 04-benchmarking-weeks.png

Now:

402
backtrader’s documentation Version-1.9.58.122

• The Benchmark observer shows a much more nervous aspect. Things move up and down, because now
weekly returns for both the portfolio and the data are being tracked
• And because no trade was active in the last week of the year and the asset barely moved, the last
displayed values are 0.00 (The last closing value before the last week was 25.54 and the sample data
closed at 25.55, and the difference is felt first at the 4th decimal point)

Observing Benchmarking - Another data

The sample allows to benchmark against a different data. The default is to


benchmark against Oracle when using --benchdata1. Considering the entire dataset
with --timeframe notimeframe:

$ ./observer-benchmark.py --plot --timeframe notimeframe --benchdata1

It’s clear now why there was no reason for celebration above:

• The results of the strategy have not changed for notimeframe and remain at -26% (-0.26)
• But when benchmarking against another data, this data has a +23% (0.23 ) in the same period
403
backtrader’s documentation Version-1.9.58.122

Either the strategy needs a change or another asset better be traded.

Concluding

There are now two ways, using the same underlying code/calculations, to track the
TimeReturn and Benchmark

• Observers (TimeReturn and Benchmark)

and

• Analyzer (TimeReturn and TimeReturn with a data parameter)

Of course benchmarking doesn’t guarantee profit, just comparison.

Usage of the sample:

$ ./observer-benchmark.py --help
usage: observer-benchmark.py [-h] [--data0 DATA0] [--data1 DATA1]
[--benchdata1] [--fromdate FROMDATE]
[--todate TODATE] [--printout] [--cash CASH]
[--period PERIOD] [--stake STAKE] [--timereturn]
[--timeframe
{months,days,notimeframe,years,None,weeks}]
[--plot [kwargs]]

Benchmark/TimeReturn Observers Sample

optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data0 to be read in (default:
../../datas/yhoo-1996-2015.txt)
--data1 DATA1 Data1 to be read in (default:
../../datas/orcl-1995-2014.txt)
--benchdata1 Benchmark against data1 (default: False)
--fromdate FROMDATE Starting date in YYYY-MM-DD format (default:
2005-01-01)
--todate TODATE Ending date in YYYY-MM-DD format (default: 2006-12-31)
--printout Print data lines (default: False)
--cash CASH Cash to start with (default: 50000)
--period PERIOD Period for the crossover moving average (default: 30)
--stake STAKE Stake to apply for the buy operations (default: 1000)
--timereturn Use TimeReturn observer instead of Benchmark (default:
None)
404
backtrader’s documentation Version-1.9.58.122

--timeframe {months,days,notimeframe,years,None,weeks}
TimeFrame to apply to the Observer (default: None)
--plot [kwargs], -p [kwargs]
Plot the read data applying any kwargs passed For
example: --plot style="candle" (to plot candles)
(default: None)

The code

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
import datetime
import random

import backtrader as bt

class St(bt.Strategy):
params = (
('period', 10),
('printout', False),
('stake', 1000),
)

def __init__(self):
sma = bt.indicators.SMA(self.data, period=self.p.period)
self.crossover = bt.indicators.CrossOver(self.data, sma)

def start(self):
if self.p.printout:
txtfields = list()
txtfields.append('Len')
txtfields.append('Datetime')
txtfields.append('Open')
txtfields.append('High')
txtfields.append('Low')
txtfields.append('Close')
txtfields.append('Volume')
txtfields.append('OpenInterest')
print(','.join(txtfields))

405
backtrader’s documentation Version-1.9.58.122

def next(self):
if self.p.printout:
# Print only 1st data ... is just a check that things are running
txtfields = list()
txtfields.append('%04d' % len(self))
txtfields.append(self.data.datetime.datetime(0).isoformat())
txtfields.append('%.2f' % self.data0.open[0])
txtfields.append('%.2f' % self.data0.high[0])
txtfields.append('%.2f' % self.data0.low[0])
txtfields.append('%.2f' % self.data0.close[0])
txtfields.append('%.2f' % self.data0.volume[0])
txtfields.append('%.2f' % self.data0.openinterest[0])
print(','.join(txtfields))

if self.position:
if self.crossover < 0.0:
if self.p.printout:
print('CLOSE {} @%{}'.format(size,
self.data.close[0]))
self.close()

else:
if self.crossover > 0.0:
self.buy(size=self.p.stake)
if self.p.printout:
print('BUY {} @%{}'.format(self.p.stake,
self.data.close[0]))

TIMEFRAMES = {
None: None,
'days': bt.TimeFrame.Days,
'weeks': bt.TimeFrame.Weeks,
'months': bt.TimeFrame.Months,
'years': bt.TimeFrame.Years,
'notimeframe': bt.TimeFrame.NoTimeFrame,
}

def runstrat(args=None):
args = parse_args(args)

cerebro = bt.Cerebro()
406
backtrader’s documentation Version-1.9.58.122

cerebro.broker.set_cash(args.cash)

dkwargs = dict()
if args.fromdate:
fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
dkwargs['fromdate'] = fromdate

if args.todate:
todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
dkwargs['todate'] = todate

data0 = bt.feeds.YahooFinanceCSVData(dataname=args.data0, **dkwargs)


cerebro.adddata(data0, name='Data0')

cerebro.addstrategy(St,
period=args.period,
stake=args.stake,
printout=args.printout)

if args.timereturn:
cerebro.addobserver(bt.observers.TimeReturn,
timeframe=TIMEFRAMES[args.timeframe])
else:
benchdata = data0
if args.benchdata1:
data1 = bt.feeds.YahooFinanceCSVData(dataname=args.data1, **dkwargs)
cerebro.adddata(data1, name='Data1')
benchdata = data1

cerebro.addobserver(bt.observers.Benchmark,
data=benchdata,
timeframe=TIMEFRAMES[args.timeframe])

cerebro.run()

if args.plot:
pkwargs = dict()
if args.plot is not True: # evals to True but is not True
pkwargs = eval('dict(' + args.plot + ')') # args were passed

cerebro.plot(**pkwargs)

def parse_args(pargs=None):
407
backtrader’s documentation Version-1.9.58.122

parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Benchmark/TimeReturn Observers Sample')

parser.add_argument('--data0', required=False,
default='../../datas/yhoo-1996-2015.txt',
help='Data0 to be read in')

parser.add_argument('--data1', required=False,
default='../../datas/orcl-1995-2014.txt',
help='Data1 to be read in')

parser.add_argument('--benchdata1', required=False, action='store_true',


help=('Benchmark against data1'))

parser.add_argument('--fromdate', required=False,
default='2005-01-01',
help='Starting date in YYYY-MM-DD format')

parser.add_argument('--todate', required=False,
default='2006-12-31',
help='Ending date in YYYY-MM-DD format')

parser.add_argument('--printout', required=False, action='store_true',


help=('Print data lines'))

parser.add_argument('--cash', required=False, action='store',


type=float, default=50000,
help=('Cash to start with'))

parser.add_argument('--period', required=False, action='store',


type=int, default=30,
help=('Period for the crossover moving average'))

parser.add_argument('--stake', required=False, action='store',


type=int, default=1000,
help=('Stake to apply for the buy operations'))

parser.add_argument('--timereturn', required=False, action='store_true',


default=None,
help=('Use TimeReturn observer instead of Benchmark'))

parser.add_argument('--timeframe', required=False, action='store',


408
backtrader’s documentation Version-1.9.58.122

default=None, choices=TIMEFRAMES.keys(),
help=('TimeFrame to apply to the Observer'))

# Plot options
parser.add_argument('--plot', '-p', nargs='?', required=False,
metavar='kwargs', const=True,
help=('Plot the read data applying any kwargs passed\n'
'\n'
'For example:\n'
'\n'
' --plot style="candle" (to plot candles)\n'))

if pargs:
return parser.parse_args(pargs)

return parser.parse_args()

if __name__ == '__main__':
runstrat()

Analyzers

Be it backtesting or trading, being able to analyze the performance of the


trading system is key to understanding if not only profit has been attained, but
also if it has been achieved with too much risk or if it was really worth the
effort when compared with a reference asset (or a risk-free asset)

That’s where the family of Analyzer objects comes in: provide an analysis of
what’s happened or even of what’s actually happening.

Nature of analyzers

The interface is modeled after that of Lines objects, feature for example a next
method but there is a major difference:

• Analyzers do not hold lines.

That means they are not expensive in terms of memory because even after
having analyzed thousands of price bars they may still simply hold a single
result in memory.

409
backtrader’s documentation Version-1.9.58.122

Location in the ecosystem

Analyzer objects are (like strategies, observers and datas) added to the system
through a cerebro instance:

• addanalyzer(ancls, *args, **kwargs)

But when it comes to operation during cerebro.run the following will happen for
each strategy present in the system

• ancls will be instantiated with *args and **kwargs during a cerebro.run


• The ancls instance will be attached to the strategy

That means:

• If the backtesting run contains for example 3 strategies then 3 instances of ancls will be created and each
of them will be attached to a different strategy.

Bottomline: an analyzer analyzes the performance of a single strategy and not the
performance of an entires system

Additional Location

Some Analyzer objects may actually use other analyzers to complete its work. For
example: SharpeRatio uses the output of TimeReturn for the calculations.

These sub-analyzers or slave-analyzers will also be inserted into the same


strategy as the one creating them. But they are completely invisible to the user.

Attributes

To carry out the intended work, Analyzer objects are provided with some default
attributes which are automagically passed and set in the instance for ease of
use:

• self.strategy: reference to the strategy subclass in which the analyzer object is operating. Anything
accessible by the strategy can also be accessd by the analyzer
• self.datas[x]: the array of data feeds present in the strategy. Although this could be accesed over the
strategy reference, the shortcut makes work more comfortable.
• self.data: shortcut to self.datas[0] for extra comfort.
• self.dataX: shortcuts to the different self.datas[x]

Some other aliases are available although they are probably an overkill:
410
backtrader’s documentation Version-1.9.58.122

• self.dataX_Y where X is a reference to self.datas[X] and Y refers to the line, finally pointing to:
self.datas[X].lines[Y]

If the line has a name, the following is also available:

• self.dataX_Name which resolves to self.datas[X].Name returning the line by name rather than by index

For the first data, the last two shortcuts are available without the initial X
numeric reference. For example:

• self.data_2 refers to self.datas[0].lines[2]

And

• self.data_close refers to self.datas[0].close

Returning the analysis

The Analyzer base class creates a self.rets (of type collections.OrderedDict)


member attribute to return the analysis. This is done in the method
create_analysis which can be overriden by subclasses if creating custom
analyzers.

Modus operandi

Although Analyzer objects are not Lines objects and therefore do not iterate over
lines, they have been designed to follow the same operation pattern.

1. Instantiated before the system is put into motion (therefore calling


__init__)
2. Signaled the begin of operations with start
3. prenext / nextstart / next will be invoked following the calculated minimum
period of the strategy the indicator is working in.

The default behaviour of prenext and nextstart is to invoke next, because


an analyzer may be analyzing from the very first moment the system is
alive.

It may be customary to call len(self) in Lines objects to check the actual


amount of bars. This also works in Analyzers by returning the value for
self.strategy

4. Orders and trades will be notified just like they are to the strategy via
notify_order and notify_trade
411
backtrader’s documentation Version-1.9.58.122

5. Cash and value will also be notified like it is done with the strategy over
the notify_cashvalue method
6. Cash, value and fundvalue and fund shares will also be notified like it is
done with the strategy over the notify_fund method
7. stop will be invoked to signal the end of operations

Once the regular operations cycle has been completed, the analyzers featuring
additional methods for extracting/outputting information

• get_analysis: which ideally (not enforced) returnes a dict -like object containing the analysis results.
• print uses a standard backtrader.WriterFile (unless overriden) to write the analysis result from
get_analysis.
• pprint (pretty print) uses the Python pprint module to print the get_analysis resutls.

And finally:

• get_analysis creates a member attribute self.ret (of type


collections.OrderedDict) to which analyzers write the analysis results.

Subclasses of Analyzer can override this method to change this behavior

Analyzer Patterns

Development of Analyzer objects in the backtrader platform have revealed 2


different usage patterns for the generation of the analysis:

1. During execution by gathering information in the notify_xxx and next


methods, and generating the current information of the analysis in next

The TradeAnalyzer, for example, uses just the notify_trade method to


generate the statistics.

2. Gather (or not) the information as above, but generate the analysis in a
single pass during the stop method

The SQN (System Quality Number) gathers trade information during


notify_trade but generates the statistic during the stop method

A quick example

As easy as it can be:

from __future__ import (absolute_import, division, print_function,


unicode_literals)
412
backtrader’s documentation Version-1.9.58.122

import datetime

import backtrader as bt
import backtrader.analyzers as btanalyzers
import backtrader.feeds as btfeeds
import backtrader.strategies as btstrats

cerebro = bt.Cerebro()

# data
dataname = '../datas/sample/2005-2006-day-001.txt'
data = btfeeds.BacktraderCSVData(dataname=dataname)

cerebro.adddata(data)

# strategy
cerebro.addstrategy(btstrats.SMA_CrossOver)

# Analyzer
cerebro.addanalyzer(btanalyzers.SharpeRatio, _name='mysharpe')

thestrats = cerebro.run()
thestrat = thestrats[0]

print('Sharpe Ratio:', thestrat.analyzers.mysharpe.get_analysis())

Executing it (having stored it in analyzer-test.py:

$ ./analyzer-test.py
Sharpe Ratio: {'sharperatio': 11.647332609673256}

There is no plotting, because the SharpeRatio is a single value at the end of the
calculation.

Forensic Analysis of an Analyzer

Let’s repeat that Analyzers are not Lines objects, but to seamlessly integrate
them into the backtrader ecosystem, the internal API conventions of several Lines
object are followed (actually a mixture of them)

Note

413
backtrader’s documentation Version-1.9.58.122

The code for the SharpeRatio has evolved to take for example into account
annualization and the version here should only be a reference.

Please check the Analyzers Reference

There is additionally a SharpeRatio_A which provides the value directly in


annualized form regardless of the sought timeframe

Code for SharpeRatio to serve as a basis (a simplified version)

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import operator

from backtrader.utils.py3 import map


from backtrader import Analyzer, TimeFrame
from backtrader.mathsupport import average, standarddev
from backtrader.analyzers import AnnualReturn

class SharpeRatio(Analyzer):
params = (('timeframe', TimeFrame.Years), ('riskfreerate', 0.01),)

def __init__(self):
super(SharpeRatio, self).__init__()
self.anret = AnnualReturn()

def start(self):
# Not needed ... but could be used
pass

def next(self):
# Not needed ... but could be used
pass

def stop(self):
retfree = [self.p.riskfreerate] * len(self.anret.rets)
retavg = average(list(map(operator.sub, self.anret.rets, retfree)))
retdev = standarddev(self.anret.rets)

self.ratio = retavg / retdev

def get_analysis(self):
return dict(sharperatio=self.ratio)
414
backtrader’s documentation Version-1.9.58.122

The code can be broken down into:

• params declaration

Although the declared ones are not used (meant as an example), Analyzers
like most other objects in backtrader support parameters

• __init__ method

Just like Strategies declare Indicators in __init__, the same do analyzers


with support objects.

In this case: the SharpeRatio is calculated using Annual Returns. The


calculation will be automatic and will be available to SharpeRatio for its
own calculations.

Note

The actual implementation of SharpeRatio uses the more generic and later
developed TimeReturn analyzer

• next method

SharpeRatio doesn’t need it, but this method will be called after each
invocation of the parent strategy next

• start method

Called right before the backtesting starts. Can be used for extra
initialization tasks. Sharperatio doesn’t need it

• stop method

Called right after the backtesting ends. Like SharpeRatio does, it can be
used to finish/make the calculation

• get_analysis method (returns a dictionary)

Access for external callers to the produced analysis

Returns: a dictionary with the analysis.

Reference

class backtrader.Analyzer

415
backtrader’s documentation Version-1.9.58.122

Analyzer base class. All analyzers are subclass of this one

An Analyzer instance operates in the frame of a strategy and provides an


analysis for that strategy.

Automagically set member attributes:

• self.strategy (giving access to the strategy and anything accessible from it)
• self.datas[x] giving access to the array of data feeds present in the the system, which could also
be accessed via the strategy reference
• self.data, giving access to self.datas[0]
• self.dataX -> self.datas[X]
• self.dataX_Y -> self.datas[X].lines[Y]
• self.dataX_name -> self.datas[X].name
• self.data_name -> self.datas[0].name
• self.data_Y -> self.datas[0].lines[Y]

This is not a Lines object, but the methods and operation follow the same
design

• __init__ during instantiation and initial setup


• start / stop to signal the begin and end of operations
• prenext / nextstart / next family of methods that follow the calls made to the same methods in
the strategy
• notify_trade / notify_order / notify_cashvalue / notify_fund which receive the same notifications
as the equivalent methods of the strategy

The mode of operation is open and no pattern is preferred. As such the


analysis can be generated with the next calls, at the end of operations
during stop and even with a single method like notify_trade

The important thing is to override get_analysis to return a dict-like


object containing the results of the analysis (the actual format is
implementation dependent)

start()

Invoked to indicate the start of operations, giving the analyzer time to


setup up needed things

stop()

Invoked to indicate the end of operations, giving the analyzer time to shut
down needed things

416
backtrader’s documentation Version-1.9.58.122

prenext()

Invoked for each prenext invocation of the strategy, until the minimum
period of the strategy has been reached

The default behavior for an analyzer is to invoke next

nextstart()

Invoked exactly once for the nextstart invocation of the strategy, when the
minimum period has been first reached

next()

Invoked for each next invocation of the strategy, once the minum preiod of
the strategy has been reached

notify_cashvalue(cash, value)

Receives the cash/value notification before each next cycle

notify_fund(cash, value, fundvalue, shares)

Receives the current cash, value, fundvalue and fund shares

notify_order(order)

Receives order notifications before each next cycle

notify_trade(trade)

Receives trade notifications before each next cycle

get_analysis()

Returns a dict-like object with the results of the analysis

The keys and format of analysis results in the dictionary is implementation


dependent.

It is not even enforced that the result is a dict-like object, just the
convention

The default implementation returns the default OrderedDict rets created by


the default create_analysis method

417
backtrader’s documentation Version-1.9.58.122

create_analysis()

Meant to be overriden by subclasses. Gives a chance to create the


structures that hold the analysis.

The default behaviour is to create a OrderedDict named rets

print(*args, **kwargs)

Prints the results returned by get_analysis via a standard Writerfile


object, which defaults to writing things to standard output

pprint(*args, **kwargs)

Prints the results returned by get_analysis using the pretty print Python
module (pprint)

__len__()¶

Support for invoking len on analyzers by actually returning the current


length of the strategy the analyzer operates on

PyFolio Overview

Note

As of (at least) 2017-07-25 the pyfolio APIs have changed and


create_full_tear_sheet no longer has a gross_lev as a named argument.

Consequently the sample for integration doesn’t work

Quoting from the main pyfolio page at http://quantopian.github.io/pyfolio/:

pyfolio is a Python library for performance and risk analysis of financial


portfolios developed by Quantopian Inc. It works well with the Zipline open
source backtesting library

And now it works also well with backtrader. What’s needed:

• pyfolio obviously
• And its dependencies (things like pandas, seaborn ...)

Note

418
backtrader’s documentation Version-1.9.58.122

During the integration with version 0.5.1, an update to the most recent
packages of the dependencies was needed, like seaborn from the previously
installed 0.7.0-dev to 0.7.1, apparently due to the absence of the method
swarmplot

Usage

1. Add the PyFolio analyzer to the cerebro mix:


2. cerebro.addanalyzer(bt.analyzers.PyFolio)
3. Run and retrieve the 1st strategy:
4. strats = cerebro.run()
5. strat0 = strats[0]
6. Retrieve the analyzer using whatever name you gave to it or the default
name it will be given to it: pyfolio. For example:
7. pyfolio = strats.analyzers.getbyname('pyfolio')
8. Use the analyzer method get_pf_items to retrieve the 4 components later
needed for pyfolio:
9. returns, positions, transactions, gross_lev = pyfoliozer.get_pf_items()

Note

The integration was done looking at test samples available with pyfolio and
the same headers (or absence of) has been replicated

10.Work with pyfolio (this is already outside of the backtrader ecosystem)

Some usage notes not directly related to backtrader

• pyfolio automatic plotting works outside of a Jupyter Notebook, but it works best inside
• pyfolio data tables’ output seems to barely work outside of a Jupyter Notebook. It works inside the
Notebook

The conclusion is easy if working with pyfolio is wished: work inside a Jupyter
Notebook

Sample Code

The code would look like this:

...
cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')
...
results = cerebro.run()

419
backtrader’s documentation Version-1.9.58.122

strat = results[0]
pyfoliozer = strat.analyzers.getbyname('pyfolio')
returns, positions, transactions, gross_lev = pyfoliozer.get_pf_items()
...
...
# pyfolio showtime
import pyfolio as pf
pf.create_full_tear_sheet(
returns,
positions=positions,
transactions=transactions,
gross_lev=gross_lev,
live_start_date='2005-05-01', # This date is sample specific
round_trips=True)

# At this point tables and chart will show up

Reference

Look into the Analyzers Reference for the PyFolio analyzer and which analyzers it
uses internally

Pyfolio Integration

The integration of a portfolio tool, namely pyfolio, came up with in Ticket #108.

A first look at the tutorial deemed it as difficult, given the tight integration
amongst zipline and pyfolio, but the sample test data available with pyfolio for
some other uses is actually pretty useful to decode what’s running behind the
scenes and hence the wonder of integration.

Most of the pieces were already in-place in backtrader:

• Analyzer infrastructure
• Children analyzer
• A TimeReturn analyzer

Only a main PyFolio analyzer and 3 easy children analyzer are needed. Plus a
method that relies on one of the dependencies already needed by pyfolio which is
pandas.

The most challenging part ... “getting all the dependencies right”.

420
backtrader’s documentation Version-1.9.58.122

• Update of pandas
• Update of numpy
• Update of scikit-lean
• Update of seaborn

Under Unix-like environments with a C compiler it’s all about time. Under
Windows and even with the specific Microsoft compiler installed (in this case the
chain for Python 2.7) things failed. But a well known site with a collection of
up-to-date packages for Windows helped. Visit it if you ever need it:

• http://www.lfd.uci.edu/~gohlke/pythonlibs/

The integration wouldn’t be complete if it wasn’t tested and that’s why the
usual sample is as always present.

No PyFolio

The sample uses random.randint to decide when to buy/sell, so this is simply a


check that things are working:

$ ./pyfoliotest.py --printout --no-pyfolio --plot

Output:

Len,Datetime,Open,High,Low,Close,Volume,OpenInterest
0001,2005-01-03T23:59:59,38.36,38.90,37.65,38.18,25482800.00,0.00
BUY 1000 @%23.58
0002,2005-01-04T23:59:59,38.45,38.54,36.46,36.58,26625300.00,0.00
BUY 1000 @%36.58
SELL 500 @%22.47
0003,2005-01-05T23:59:59,36.69,36.98,36.06,36.13,18469100.00,0.00
...
SELL 500 @%37.51
0502,2006-12-28T23:59:59,25.62,25.72,25.30,25.36,11908400.00,0.00
0503,2006-12-29T23:59:59,25.42,25.82,25.33,25.54,16297800.00,0.00
SELL 250 @%17.14
SELL 250 @%37.01

421
backtrader’s documentation Version-1.9.58.122

There a 3 datas and several buy and sell operations are randomly chosen and
scattered over the 2 year default life of the test run

A PyFolio run

pyfolio things work well when running inside a Jupyter Notebook including inline
plotting. Here is the notebook

Note

runstrat gets here [] as argument to run with default arguments and skip
arguments passed by the notebook itself

%matplotlib inline
from __future__ import (absolute_import, division, print_function,
unicode_literals)

422
backtrader’s documentation Version-1.9.58.122

import argparse
import datetime
import random

import backtrader as bt

class St(bt.Strategy):
params = (
('printout', False),
('stake', 1000),
)

def __init__(self):
pass

def start(self):
if self.p.printout:
txtfields = list()
txtfields.append('Len')
txtfields.append('Datetime')
txtfields.append('Open')
txtfields.append('High')
txtfields.append('Low')
txtfields.append('Close')
txtfields.append('Volume')
txtfields.append('OpenInterest')
print(','.join(txtfields))

def next(self):
if self.p.printout:
# Print only 1st data ... is just a check that things are running
txtfields = list()
txtfields.append('%04d' % len(self))
txtfields.append(self.data.datetime.datetime(0).isoformat())
txtfields.append('%.2f' % self.data0.open[0])
txtfields.append('%.2f' % self.data0.high[0])
txtfields.append('%.2f' % self.data0.low[0])
txtfields.append('%.2f' % self.data0.close[0])
txtfields.append('%.2f' % self.data0.volume[0])
txtfields.append('%.2f' % self.data0.openinterest[0])
print(','.join(txtfields))

423
backtrader’s documentation Version-1.9.58.122

# Data 0
for data in self.datas:
toss = random.randint(1, 10)
curpos = self.getposition(data)
if curpos.size:
if toss > 5:
size = curpos.size // 2
self.sell(data=data, size=size)
if self.p.printout:
print('SELL {} @%{}'.format(size, data.close[0]))

elif toss < 5:


self.buy(data=data, size=self.p.stake)
if self.p.printout:
print('BUY {} @%{}'.format(self.p.stake, data.close[0]))

def runstrat(args=None):
args = parse_args(args)

cerebro = bt.Cerebro()
cerebro.broker.set_cash(args.cash)

dkwargs = dict()
if args.fromdate:
fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
dkwargs['fromdate'] = fromdate

if args.todate:
todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')
dkwargs['todate'] = todate

data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **dkwargs)


cerebro.adddata(data0, name='Data0')

data1 = bt.feeds.BacktraderCSVData(dataname=args.data1, **dkwargs)


cerebro.adddata(data1, name='Data1')

data2 = bt.feeds.BacktraderCSVData(dataname=args.data2, **dkwargs)


cerebro.adddata(data2, name='Data2')

cerebro.addstrategy(St, printout=args.printout)
if not args.no_pyfolio:
cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')
424
backtrader’s documentation Version-1.9.58.122

results = cerebro.run()
if not args.no_pyfolio:
strat = results[0]
pyfoliozer = strat.analyzers.getbyname('pyfolio')

returns, positions, transactions, gross_lev = pyfoliozer.get_pf_items()


if args.printout:
print('-- RETURNS')
print(returns)
print('-- POSITIONS')
print(positions)
print('-- TRANSACTIONS')
print(transactions)
print('-- GROSS LEVERAGE')
print(gross_lev)

import pyfolio as pf
pf.create_full_tear_sheet(
returns,
positions=positions,
transactions=transactions,
gross_lev=gross_lev,
live_start_date='2005-05-01',
round_trips=True)

if args.plot:
cerebro.plot(style=args.plot_style)

def parse_args(args=None):

parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Sample for pivot point and cross plotting')

parser.add_argument('--data0', required=False,
default='../../datas/yhoo-1996-2015.txt',
help='Data to be read in')

parser.add_argument('--data1', required=False,
default='../../datas/orcl-1995-2014.txt',
help='Data to be read in')

425
backtrader’s documentation Version-1.9.58.122

parser.add_argument('--data2', required=False,
default='../../datas/nvda-1999-2014.txt',
help='Data to be read in')

parser.add_argument('--fromdate', required=False,
default='2005-01-01',
help='Starting date in YYYY-MM-DD format')

parser.add_argument('--todate', required=False,
default='2006-12-31',
help='Ending date in YYYY-MM-DD format')

parser.add_argument('--printout', required=False, action='store_true',


help=('Print data lines'))

parser.add_argument('--cash', required=False, action='store',


type=float, default=50000,
help=('Cash to start with'))

parser.add_argument('--plot', required=False, action='store_true',


help=('Plot the result'))

parser.add_argument('--plot-style', required=False, action='store',


default='bar', choices=['bar', 'candle', 'line'],
help=('Plot style'))

parser.add_argument('--no-pyfolio', required=False, action='store_true',


help=('Do not do pyfolio things'))

import sys
aargs = args if args is not None else sys.argv[1:]
return parser.parse_args(aargs)
runstrat([])
Entire data start date: 2005-01-03
Entire data end date: 2006-12-29

Out-of-Sample Months: 20
Backtest Months: 3
Performance statistics All history Backtest Out of sample
annual_return 0.06 -0.05 0.08
annual_volatility 0.09 0.09 0.10
sharpe_ratio 0.62 -0.55 0.83

426
backtrader’s documentation Version-1.9.58.122

Performance statistics All history Backtest Out of sample


calmar_ratio 0.78 -1.13 1.09
stability_of_timeseries 0.75 -0.47 0.70
max_drawdown -0.07 -0.04 -0.07
omega_ratio 1.16 0.88 1.22
sortino_ratio 0.97 -0.76 1.33
skew 1.24 0.35 1.37
kurtosis 12.72 5.66 13.59
tail_ratio 0.87 0.46 0.91
common_sense_ratio 0.91 0.43 0.98
information_ratio -0.02 0.03 -0.04
alpha 0.03 -0.02 0.03
beta 0.31 0.25 0.33
Worst Drawdown Periods net drawdown in % peak date valley date recovery date duration
0 7.06 2005-07-11 2006-04-17 2006-05-24 228
1 5.53 2005-02-18 2005-05-11 2005-05-16 62
2 3.33 2006-07-03 2006-07-13 2006-09-21 59
3 2.11 2006-09-25 2006-10-03 2006-10-24 22
4 2.11 2006-10-31 2006-12-07 2006-12-19 36
[-0.012 -0.025]

427
backtrader’s documentation Version-1.9.58.122

428
backtrader’s documentation Version-1.9.58.122

Stress Events mean min max


Low Volatility Bull Market 0.02% -2.68% 4.85%

429
backtrader’s documentation Version-1.9.58.122

430
backtrader’s documentation Version-1.9.58.122

Top 10 long positions of all time max


Data2 93.59%
Data0 80.42%
Data1 34.47%
Top 10 short positions of all time max

Top 10 positions of all time max


Data2 93.59%
Data0 80.42%
Data1 34.47%
All positions ever held max
93.59
Data2
%
80.42
Data0
%
34.47
Data1
%

431
backtrader’s documentation Version-1.9.58.122

432
backtrader’s documentation Version-1.9.58.122

D:drobinWinPython-64bit-2.7.10.3python-2.7.10.amd64libsite-
packagespyfolioplotting.py:1210: FutureWarning: .resample() is now a deferred
operation
use .resample(...).mean() instead of .resample(...)
**kwargs)

433
backtrader’s documentation Version-1.9.58.122

434
backtrader’s documentation Version-1.9.58.122

Summary stats All trades Long trades


Total number of round_trips 661.00 661.00
Percent profitable 0.53 0.53
Winning round_trips 350.00 350.00
Losing round_trips 305.00 305.00
Even round_trips 6.00 6.00
Long
PnL stats All trades
trades
Total profit $5675.87 $5675.87
Gross profit $21571.73 $21571.73
Gross loss $-15895.86 $-15895.86
Profit factor $1.36 $1.36
Avg. trade net profit $8.59 $8.59
Avg. winning trade $61.63 $61.63
Avg. losing trade $-52.12 $-52.12
Ratio Avg. Win:Avg. Loss $1.18 $1.18
Largest winning trade $1024.99 $1024.99
Largest losing trade $-1155.00 $-1155.00

Duration stats All trades Long trades


Avg duration 17 days 00:00:00.001512 17 days 00:00:00.001512
Median duration 16 days 00:00:00 16 days 00:00:00
Avg # round_trips per day 11.80 11.80
Avg # round_trips per month 247.88 247.88
Return stats All trades Long trades
Avg returns all round_trips 0.02% 0.02%
Avg returns winning 0.12% 0.12%
Avg returns losing -0.10% -0.10%
Median returns all round_trips 0.00% 0.00%
Median returns winning 0.02% 0.02%
Median returns losing -0.02% -0.02%
Largest winning trade 2.11% 2.11%
Largest losing trade -2.37% -2.37%

Symbol stats Data0 Data1 Data2


Avg returns all round_trips -0.02% 0.01% 0.06%

435
backtrader’s documentation Version-1.9.58.122

Symbol stats Data0 Data1 Data2


Avg returns winning 0.12% 0.05% 0.19%
Avg returns losing -0.14% -0.04% -0.14%
Median returns all round_trips -0.00% 0.00% 0.01%
Median returns winning 0.03% 0.01% 0.05%
Median returns losing -0.02% -0.01% -0.04%
Largest winning trade 1.91% 0.71% 2.11%
Largest losing trade -2.37% -0.64% -0.99%
Profitability (PnL / PnL total) per name pnl
symbol
Data2 1.11%
Data1 0.14%
-
Data0
0.25%
<matplotlib.figure.Figure at 0x23982b70>

436
backtrader’s documentation Version-1.9.58.122

437
backtrader’s documentation Version-1.9.58.122

Usage of the sample:

$ ./pyfoliotest.py --help
usage: pyfoliotest.py [-h] [--data0 DATA0] [--data1 DATA1] [--data2 DATA2]
[--fromdate FROMDATE] [--todate TODATE] [--printout]
[--cash CASH] [--plot] [--plot-style {bar,candle,line}]
[--no-pyfolio]

Sample for pivot point and cross plotting

optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to be read in (default:
../../datas/yhoo-1996-2015.txt)
--data1 DATA1 Data to be read in (default:
../../datas/orcl-1995-2014.txt)
--data2 DATA2 Data to be read in (default:
../../datas/nvda-1999-2014.txt)
--fromdate FROMDATE Starting date in YYYY-MM-DD format (default:
2005-01-01)
--todate TODATE Ending date in YYYY-MM-DD format (default: 2006-12-31)
--printout Print data lines (default: False)
--cash CASH Cash to start with (default: 50000)
--plot Plot the result (default: False)
--plot-style {bar,candle,line}
Plot style (default: bar)
--no-pyfolio Do not do pyfolio things (default: False)

Writer

Write out to a stream the following contents:

• csv stream with data feeds, strategies, indicators and observers

Which objects actually go into the csv stream can be controlled with the
csv attribute of each object (defaults to True for data feeds and observers
/ False for indicators)

• A summary of the properties of


o Data Feeds
o Strategies (lines and parameters)
o Indicators/Observers: (lines and parameters)
o Analyzers: (parameters and analysis outcome)

438
backtrader’s documentation Version-1.9.58.122

There is only a single Writer defined called WriterFile, which can be added to
the system:

• By setting the writer parameter of cerebro to True

A standard WriterFile will be instantiated

• By calling Cerebro.addwriter(writerclass, **kwargs)

writerclass will be instantiated during backtesting execution with the


givenn kwargs

Given that a standard WriterFile does not ouput csv as a default, the
following addwriter invocation would take care of it:

cerebro.addwriter(bt.WriterFile, csv=True)

Reference

class backtrader.WriterFile

The system wide writer class.

It can be parametrized with:

• out (default: sys.stdout): output stream to write to

If a string is passed a filename with the content of the parameter


will be used

• close_out (default: False)

If out is a stream whether it has to be explicitly closed by the


writer

• csv (default: False)

If a csv stream of the data feeds, strategies, observers and


indicators has to be written to the stream during execution

Which objects actually go into the csv stream can be controlled with
the csv attribute of each object (defaults to True for data feeds and
observers / False for indicators)

439
backtrader’s documentation Version-1.9.58.122

• csv_filternan (default: True) whether nan values have to be purged


out of the csv stream (replaced by an empty field)
• csv_counter (default: True) if the writer shall keep and print out a
counter of the lines actually output
• indent (default: 2) indentation spaces for each level
• separators (default: ['=', '-', '+', '*', '.', '~', '"', '^', '#'])

Characters used for line separators across section/sub(sub)sections

• seplen (default: 79)

total length of a line separator including indentation

• rounding (default: None)

Number of decimal places to round floats down to. With None no


rounding is performed

Commissions: Stocks vs Futures

Agnosticity

Before going forward let’s remember that backtrader tries to remain agnostic as
to what the data represents. Different commission schemes can be applied to the
same data set.

Let’s see how it can be done.

Using the broker shortcuts

This keeps the end user away from CommissionInfo objects because a commission
scheme can be created/set with a single function call. Within the regular cerebro
creation/set-up process, just add a call to setcommission over the broker member
attribute. The following call sets a usual commission scheme for Eurostoxx50
futures when working with Interactive Brokers:

cerebro.broker.setcommission(commission=2.0, margin=2000.0, mult=10.0)

Since most users will usually just test a single instrument, that’s all that’s
down to it. If you have given a name to your data feed, because several
instruments are being considered simultaneously on a chart, this call can be
slightly extended to look as follows:

440
backtrader’s documentation Version-1.9.58.122

cerebro.broker.setcommission(commission=2.0, margin=2000.0, mult=10.0,


name='Eurostoxxx50')

In this case this on-the-fly commission scheme will only applied to instruments
whose name matches Eurostoxx50.

The meaning of the setcommission parameters

• commission (default: 0.0)

Monetary units in absolute or percentage terms each action costs.

In the above example it is 2.0 euros per contract for a buy and again 2.0
euros per contract for a sell.

The important issue here is when to use absolute or percentage values.

o If margin evaluates to False (it is False, 0 or None for example) then it will be considered that
commission expresses a percentage of the price times size operatin value
o If margin is something else, it is considered the operations are happenning on a futures like
intstrument and commission is a fixed price per size contracts
• margin (default: None)

Margin money needed when operating with futures like instruments. As


expressed above

o If a no margin is set, the commission will be understood to be indicated in percentage and


applied to price * size components of a buy or sell operation
o If a margin is set, the commission will be understood to be a fixed value which is multiplied by the
size component of buy or sell operation
• mult (default: 1.0)

For future like instruments this determines the multiplicator to apply to


profit and loss calculations.

This is what makes futures attractive and risky at the same time.

• name (default: None)

Limit the application of the commission scheme to instruments matching name

This can be set during the creation of a data feed.

If left unset, the scheme will apply to any data present in the system.

441
backtrader’s documentation Version-1.9.58.122

Two examples now: stocks vs futures

The futures example from above:

cerebro.broker.setcommission(commission=2.0, margin=2000.0, mult=10.0)

A example for stocks:

cerebro.broker.setcommission(commission=0.005) # 0.5% of the operation value

Note

The 2nd syntax doesn’t set margin and mult and backtrader attempts a smart
approach by considering the commission to be % based.

To fully specify commission schemes, a subclass of CommissionInfo needs to be


created

Creating permanent Commission schemes

A more permanent commission scheme can be created by working directly with


CommissionInfo classes. The user could choose to have this definition somewhere:

import backtrader as bt

commEurostoxx50 = bt.CommissionInfo(commission=2.0, margin=2000.0, mult=10.0)

To later apply it in another Python module with addcommissioninfo:

from mycomm import commEurostoxx50

...

cerebro.broker.addcommissioninfo(commEuroStoxx50, name='Eurostoxxx50')

CommissionInfo is an object which uses a params declaration just like other


objects in the backtrader environment. As such the above can be also expressed
as:

import backtrader as bt

class CommEurostoxx50(bt.CommissionInfo):
params = dict(commission=2.0, margin=2000.0, mult=10.0)

442
backtrader’s documentation Version-1.9.58.122

And later:

from mycomm import CommEurostoxx50

...

cerebro.broker.addcommissioninfoCommEuroStoxx50(), name='Eurostoxxx50')

Now a “real” comparison with a SMA Crossover

Using a SimpleMovingAverage crossover as the entry/exit signal the same data set
is going to be tested with a futures like commission scheme and then with a
stocks like one.

Note

Futures positions could also not only be given the enter/exit behavior but a
reversal behavior on each occassion. But this example is about comparing the
commission schemes.

The code (see at the bottom for the full strategy) is the same and the scheme can
be chosen before the strategy is defined.

futures_like = True

if futures_like:
commission, margin, mult = 2.0, 2000.0, 10.0
else:
commission, margin, mult = 0.005, None, 1

Just set futures_like to false to run with the stocks like scheme.

Some logging code has been added to evaluate the impact of the differrent
commission schemes. Let’s concentrate on just the 2 first operations.

For futures:

2006-03-09, BUY CREATE, 3757.59


2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 2000.00, Comm 2.00
2006-04-11, SELL CREATE, 3788.81
2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 2000.00, Comm 2.00
2006-04-12, OPERATION PROFIT, GROSS 328.00, NET 324.00
2006-04-20, BUY CREATE, 3860.00

443
backtrader’s documentation Version-1.9.58.122

2006-04-21, BUY EXECUTED, Price: 3863.57, Cost: 2000.00, Comm 2.00


2006-04-28, SELL CREATE, 3839.90
2006-05-02, SELL EXECUTED, Price: 3839.24, Cost: 2000.00, Comm 2.00
2006-05-02, OPERATION PROFIT, GROSS -243.30, NET -247.30

For stocks:

2006-03-09, BUY CREATE, 3757.59


2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 3754.13, Comm 18.77
2006-04-11, SELL CREATE, 3788.81
2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 3786.93, Comm 18.93
2006-04-12, OPERATION PROFIT, GROSS 32.80, NET -4.91
2006-04-20, BUY CREATE, 3860.00
2006-04-21, BUY EXECUTED, Price: 3863.57, Cost: 3863.57, Comm 19.32
2006-04-28, SELL CREATE, 3839.90
2006-05-02, SELL EXECUTED, Price: 3839.24, Cost: 3839.24, Comm 19.20
2006-05-02, OPERATION PROFIT, GROSS -24.33, NET -62.84

The 1st operation has the following prices:

• BUY (Execution) -> 3754.13 / SELL (Execution) -> 3786.93


o Futures Profit & Loss (with commission): 324.0
o Stocks Profit & Loss (with commission): -4.91

Hey!! Commission has fully eaten up any profit on the stocks operation but has
only meant a small dent to the futures one.

The 2nd operation:

• BUY (Execution) -> 3863.57 / SELL (Execution) -> 3389.24


o Futures Profit & Loss (with commission): -247.30
o Stocks Profit & Loss (with commission): -62.84

The bite has been sensibly larger for this negative operation with futures

But:

• Futures accumulated net profit & loss: 324.00 + (-247.30) = 76.70


• Stocks accumulated net profit & loss: (-4.91) + (-62.84) = -67.75

The accumulated effect can be seen on the charts below, where it can also be seen
that at the end of the full year, futures have produced a larger profit, but have
also suffered a larger drawdown (were deeper underwater)

But the important thing: whether futures or stocks ... it can be backtested.

444
backtrader’s documentation Version-1.9.58.122

Commissions for futures

445
backtrader’s documentation Version-1.9.58.122

Commissions for stocks

The code

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind

futures_like = True

if futures_like:

446
backtrader’s documentation Version-1.9.58.122

commission, margin, mult = 2.0, 2000.0, 10.0


else:
commission, margin, mult = 0.005, None, 1

class SMACrossOver(bt.Strategy):
def log(self, txt, dt=None):
''' Logging function fot this strategy'''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))

def notify(self, order):


if order.status in [order.Submitted, order.Accepted]:
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
return

# Check if an order has been completed


# Attention: broker could reject order if not enougth cash
if order.status in [order.Completed, order.Canceled, order.Margin]:
if order.isbuy():
self.log(
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))

self.buyprice = order.executed.price
self.buycomm = order.executed.comm
self.opsize = order.executed.size
else: # Sell
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))

gross_pnl = (order.executed.price - self.buyprice) * \


self.opsize

if margin:
gross_pnl *= mult

net_pnl = gross_pnl - self.buycomm - order.executed.comm


self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
(gross_pnl, net_pnl))
447
backtrader’s documentation Version-1.9.58.122

def __init__(self):
sma = btind.SMA(self.data)
# > 0 crossing up / < 0 crossing down
self.buysell_sig = btind.CrossOver(self.data, sma)

def next(self):
if self.buysell_sig > 0:
self.log('BUY CREATE, %.2f' % self.data.close[0])
self.buy() # keep order ref to avoid 2nd orders

elif self.position and self.buysell_sig < 0:


self.log('SELL CREATE, %.2f' % self.data.close[0])
self.sell()

if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()

# Add a strategy
cerebro.addstrategy(SMACrossOver)

# Create a Data Feed


datapath = ('../../datas/2006-day-001.txt')
data = bt.feeds.BacktraderCSVData(dataname=datapath)

# Add the Data Feed to Cerebro


cerebro.adddata(data)

# set commission scheme -- CHANGE HERE TO PLAY


cerebro.broker.setcommission(
commission=commission, margin=margin, mult=mult)

# Run over everything


cerebro.run()

# Plot the result


cerebro.plot()

Reference

class backtrader.CommInfoBase

448
backtrader’s documentation Version-1.9.58.122

Base Class for the Commission Schemes.

Params:

• commission (def: 0.0): base commission value in percentage or


monetary units
• mult (def 1.0): multiplier applied to the asset for value/profit
• margin (def: None): amount of monetary units needed to open/hold an
operation. It only applies if the final _stocklike attribute in the
class is set to False
• automargin (def: False): Used by the method get_margin to
automatically calculate the margin/guarantees needed with the
following policy
o Use param margin if param automargin evaluates to False
o Use param mult * price if automargin < 0
o Use param automargin * price if automargin > 0
• commtype (def: None): Supported values are CommInfoBase.COMM_PERC
(commission to be understood as %) and CommInfoBase.COMM_FIXED
(commission to be understood as monetary units)

The default value of None is a supported value to retain


compatibility with the legacy CommissionInfo object. If commtype is
set to None, then the following applies:

o margin is None: Internal _commtype is set to COMM_PERC and _stocklike is set to True
(Operating %-wise with Stocks)
o margin is not None: _commtype set to COMM_FIXED and _stocklike set to False
(Operating with fixed rount-trip commission with Futures)

If this param is set to something else than None, then it will be


passed to the internal _commtype attribute and the same will be done
with the param stocklike and the internal attribute _stocklike

• stocklike (def: False): Indicates if the instrument is Stock-like or


Futures-like (see the commtype discussion above)
• percabs (def: False): when commtype is set to COMM_PERC, whether the
parameter commission has to be understood as XX% or 0.XX

If this param is True: 0.XX If this param is False: XX%

• interest (def: 0.0)

If this is non-zero, this is the yearly interest charged for holding


a short selling position. This is mostly meant for stock short-
selling
449
backtrader’s documentation Version-1.9.58.122

The formula: days * price * abs(size) * (interest / 365)

It must be specified in absolute terms: 0.05 -> 5%

Note

the behavior can be changed by overriding the method:


_get_credit_interest

• interest_long (def: False)

Some products like ETFs get charged on interest for short and long
positions. If ths is True and interest is non-zero the interest will
be charged on both directions

• leverage (def: 1.0)

Amount of leverage for the asset with regards to the needed cash

- ``_stocklike``

Final value to use for Stock-like/Futures-like behavior

- ``_commtype``

Final value to use for PERC vs FIXED commissions

This two are used internally instead of the declared params to enable the
compatibility check described above for the legacy ``CommissionInfo``
object
class backtrader.CommissionInfo

Base Class for the actual Commission Schemes.

CommInfoBase was created to keep suppor for the original, incomplete,


support provided by backtrader. New commission schemes derive from this
class which subclasses CommInfoBase.

The default value of percabs is also changed to True

Params:

• percabs (def: True): when commtype is set to COMM_PERC, whether the


parameter commission has to be understood as XX% or 0.XX

If this param is True: 0.XX If this param is False: XX%


450
backtrader’s documentation Version-1.9.58.122

get_leverage()

Returns the level of leverage allowed for this comission scheme

getsize(price, cash)

Returns the needed size to meet a cash operation at a given price

getoperationcost(size, price)

Returns the needed amount of cash an operation would cost

getvaluesize(size, price)

Returns the value of size for given a price. For future-like objects it is
fixed at size * margin

getvalue(position, price)

Returns the value of a position given a price. For future-like objects it


is fixed at size * margin

get_margin(price)

Returns the actual margin/guarantees needed for a single item of the asset
at the given price. The default implementation has this policy:

• Use param margin if param automargin evaluates to False


• Use param mult * price if automargin < 0
• Use param automargin * price if automargin > 0

getcommission(size, price)

Calculates the commission of an operation at a given price

_getcommission(size, price, pseudoexec)

Calculates the commission of an operation at a given price

pseudoexec: if True the operation has not yet been executed

profitandloss(size, price, newprice)

Return actual profit and loss a position has

cashadjust(size, price, newprice)

451
backtrader’s documentation Version-1.9.58.122

Calculates cash adjustment for a given price difference

get_credit_interest(data, pos, dt)

Calculates the credit due for short selling or product specific

_get_credit_interest(data, size, price, days, dt0, dt1)

This method returns the cost in terms of credit interest charged by the
broker.

In the case of size > 0 this method will only be called if the parameter to
the class interest_long is True

The formulat for the calculation of the credit interest rate is:

The formula: days * price * abs(size) * (interest / 365)


Params:

• data: data feed for which interest is charged


• size: current position size. > 0 for long positions and < 0 for short positions (this parameter will
not be 0)
• price: current position price
• days: number of days elapsed since last credit calculation (this is (dt0 - dt1).days)
• dt0: (datetime.datetime) current datetime
• dt1: (datetime.datetime) datetime of previous calculation

dt0 and dt1 are not used in the default implementation and are provided as
extra input for overridden methods

Extending Commissions

Commissions and asociated functionality were managed by a single class


CommissionInfo which was mostly instantiated by calling broker.setcommission.

The concept was limited to futures with margin and a fixed commission per
contract and stocks with a price/size percentage based commission. Not the most
flexible of schemes even if it has served its purpose.

A request for enhancement on GitHub #29 led to some rework in order to:

• Keep CommissionInfo and broker.setcommission compatible with the original behavior


• Do some clean up of the code
• Make the Commission scheme flexible to support the enhancement request and further possibilities

452
backtrader’s documentation Version-1.9.58.122

The actual work before getting to the sample

class CommInfoBase(with_metaclass(MetaParams)):
COMM_PERC, COMM_FIXED = range(2)

params = (
('commission', 0.0), ('mult', 1.0), ('margin', None),
('commtype', None),
('stocklike', False),
('percabs', False),
)

A base class for CommissionInfo has been introduced which add new parameters to
the mix:

• commtype (default: None)

This is the key to compatibility. If the value is None, the behavior of the
CommissionInfo object and broker.setcommission will work as before. Being
that:

o If margin is set then the commission scheme is for futures with a fixed commission per contract
o If margin is not set, the commission scheme is for stocks with a percentage based approach

If the value is COMM_PERC or COMM_FIXED (or any other from derived classes)
this obviously decides if the commission if fixed or percent based

• stocklike (default: False)

As explained above, the actual behavior in the old CommissionInfo object is


determined by the parameter margin

As above if commtype is set to something else than None, then this value
indicates whether the asset is a futures-like asset (margin will be used
and bar based cash adjustment will be performed9 or else this a stocks-like
asset

• percabs (default: False)

If False then the percentage must be passed in relative terms (xx%)

If True the percentage has to be passed as an absolute value (0.xx)

CommissionInfo is subclassed from CommInfoBase changing the default value


of this parameter to True to keep the compatible behavior

453
backtrader’s documentation Version-1.9.58.122

All these parameters can also be used in broker.setcommission which now looks
like this:

def setcommission(self,
commission=0.0, margin=None, mult=1.0,
commtype=None, percabs=True, stocklike=False,
name=None):

Notice the following:

• percabs is True to keep the behavior compatible with the old call as mentioned above for the
CommissionInfo object

The old sample to test commissions-schemes has been reworked to support command
line arguments and the new behavior. The usage help:

$ ./commission-schemes.py --help
usage: commission-schemes.py [-h] [--data DATA] [--fromdate FROMDATE]
[--todate TODATE] [--stake STAKE]
[--period PERIOD] [--cash CASH] [--comm COMM]
[--mult MULT] [--margin MARGIN]
[--commtype {none,perc,fixed}] [--stocklike]
[--percrel] [--plot] [--numfigs NUMFIGS]

Commission schemes

optional arguments:
-h, --help show this help message and exit
--data DATA, -d DATA data to add to the system (default:
../../datas/2006-day-001.txt)
--fromdate FROMDATE, -f FROMDATE
Starting date in YYYY-MM-DD format (default:
2006-01-01)
--todate TODATE, -t TODATE
Starting date in YYYY-MM-DD format (default:
2006-12-31)
--stake STAKE Stake to apply in each operation (default: 1)
--period PERIOD Period to apply to the Simple Moving Average (default:
30)
--cash CASH Starting Cash (default: 10000.0)
--comm COMM Commission factor for operation, either apercentage or
a per stake unit absolute value (default: 2.0)
--mult MULT Multiplier for operations calculation (default: 10)
--margin MARGIN Margin for futures-like operations (default: 2000.0)
--commtype {none,perc,fixed}
454
backtrader’s documentation Version-1.9.58.122

Commission - choose none for the old CommissionInfo


behavior (default: none)
--stocklike If the operation is for stock-like assets orfuture-
like assets (default: False)
--percrel If perc is expressed in relative xx{'const': True,
'help': u'If perc is expressed in relative xx%
ratherthan absolute value 0.xx', 'option_strings': [u'
--percrel'], 'dest': u'percrel', 'required': False,
'nargs': 0, 'choices': None, 'default': False, 'prog':
'commission-schemes.py', 'container':
<argparse._ArgumentGroup object at
0x0000000007EC9828>, 'type': None, 'metavar':
None}atherthan absolute value 0.xx (default: False)
--plot, -p Plot the read data (default: False)
--numfigs NUMFIGS, -n NUMFIGS
Plot using numfigs figures (default: 1)

Let’s do some runs to recreate the original behavior of the original commission
schemes posts.

Commissions for futures (fixed and with margin)

The execution and chart:

$ ./commission-schemes.py --comm 2.0 --margin 2000.0 --mult 10 --plot

455
backtrader’s documentation Version-1.9.58.122

And the output showing a fixed commission of 2.0 monetary units (default stake is
1):

2006-03-09, BUY CREATE, 3757.59


2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 2000.00, Comm 2.00
2006-04-11, SELL CREATE, 3788.81
2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 2000.00, Comm 2.00
2006-04-12, TRADE PROFIT, GROSS 328.00, NET 324.00
...

Commissions for stocks (perc and withoout margin)

The execution and chart:

$ ./commission-schemes.py --comm 0.005 --margin 0 --mult 1 --plot

456
backtrader’s documentation Version-1.9.58.122

To improve readability a relative % value can be used:

$ ./commission-schemes.py --percrel --comm 0.5 --margin 0 --mult 1 --plot

Now 0.5 means directly 0.5%

Being the output in both cases:

2006-03-09, BUY CREATE, 3757.59


2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 3754.13, Comm 18.77
2006-04-11, SELL CREATE, 3788.81
2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 3754.13, Comm 18.93
2006-04-12, TRADE PROFIT, GROSS 32.80, NET -4.91
...

457
backtrader’s documentation Version-1.9.58.122

Commissions for futures (perc and with margin)

Using the new parameters, futures on a perc based scheme:

$ ./commission-schemes.py --commtype perc --percrel --comm 0.5 --margin 2000 --


mult 10 --plot

It should come to no surprise that by changing the commission ... the final
result has changed

The output shows that the commission is variable now:

2006-03-09, BUY CREATE, 3757.59


2006-03-10, BUY EXECUTED, Price: 3754.13, Cost: 2000.00, Comm 18.77
2006-04-11, SELL CREATE, 3788.81
2006-04-12, SELL EXECUTED, Price: 3786.93, Cost: 2000.00, Comm 18.93
2006-04-12, TRADE PROFIT, GROSS 328.00, NET 290.29
...
458
backtrader’s documentation Version-1.9.58.122

Being in the previous run set a 2.0 monetary units (for the default stake of 1)

Another post will details the new classes and the implementation of a homme
cooked commission scheme.

The code for the sample

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
import datetime

import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind

class SMACrossOver(bt.Strategy):
params = (
('stake', 1),
('period', 30),
)

def log(self, txt, dt=None):


''' Logging function fot this strategy'''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))

def notify_order(self, order):


if order.status in [order.Submitted, order.Accepted]:
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
return

# Check if an order has been completed


# Attention: broker could reject order if not enougth cash
if order.status in [order.Completed, order.Canceled, order.Margin]:
if order.isbuy():
self.log(
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
else: # Sell
459
backtrader’s documentation Version-1.9.58.122

self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %


(order.executed.price,
order.executed.value,
order.executed.comm))

def notify_trade(self, trade):


if trade.isclosed:
self.log('TRADE PROFIT, GROSS %.2f, NET %.2f' %
(trade.pnl, trade.pnlcomm))

def __init__(self):
sma = btind.SMA(self.data, period=self.p.period)
# > 0 crossing up / < 0 crossing down
self.buysell_sig = btind.CrossOver(self.data, sma)

def next(self):
if self.buysell_sig > 0:
self.log('BUY CREATE, %.2f' % self.data.close[0])
self.buy(size=self.p.stake) # keep order ref to avoid 2nd orders

elif self.position and self.buysell_sig < 0:


self.log('SELL CREATE, %.2f' % self.data.close[0])
self.sell(size=self.p.stake)

def runstrategy():
args = parse_args()

# Create a cerebro
cerebro = bt.Cerebro()

# Get the dates from the args


fromdate = datetime.datetime.strptime(args.fromdate, '%Y-%m-%d')
todate = datetime.datetime.strptime(args.todate, '%Y-%m-%d')

# Create the 1st data


data = btfeeds.BacktraderCSVData(
dataname=args.data,
fromdate=fromdate,
todate=todate)

# Add the 1st data to cerebro


cerebro.adddata(data)

460
backtrader’s documentation Version-1.9.58.122

# Add a strategy
cerebro.addstrategy(SMACrossOver, period=args.period, stake=args.stake)

# Add the commission - only stocks like a for each operation


cerebro.broker.setcash(args.cash)

commtypes = dict(
none=None,
perc=bt.CommInfoBase.COMM_PERC,
fixed=bt.CommInfoBase.COMM_FIXED)

# Add the commission - only stocks like a for each operation


cerebro.broker.setcommission(commission=args.comm,
mult=args.mult,
margin=args.margin,
percabs=not args.percrel,
commtype=commtypes[args.commtype],
stocklike=args.stocklike)

# And run it
cerebro.run()

# Plot if requested
if args.plot:
cerebro.plot(numfigs=args.numfigs, volume=False)

def parse_args():
parser = argparse.ArgumentParser(
description='Commission schemes',
formatter_class=argparse.ArgumentDefaultsHelpFormatter,)

parser.add_argument('--data', '-d',
default='../../datas/2006-day-001.txt',
help='data to add to the system')

parser.add_argument('--fromdate', '-f',
default='2006-01-01',
help='Starting date in YYYY-MM-DD format')

parser.add_argument('--todate', '-t',
default='2006-12-31',
help='Starting date in YYYY-MM-DD format')

461
backtrader’s documentation Version-1.9.58.122

parser.add_argument('--stake', default=1, type=int,


help='Stake to apply in each operation')

parser.add_argument('--period', default=30, type=int,


help='Period to apply to the Simple Moving Average')

parser.add_argument('--cash', default=10000.0, type=float,


help='Starting Cash')

parser.add_argument('--comm', default=2.0, type=float,


help=('Commission factor for operation, either a'
'percentage or a per stake unit absolute value'))

parser.add_argument('--mult', default=10, type=int,


help='Multiplier for operations calculation')

parser.add_argument('--margin', default=2000.0, type=float,


help='Margin for futures-like operations')

parser.add_argument('--commtype', required=False, default='none',


choices=['none', 'perc', 'fixed'],
help=('Commission - choose none for the old'
' CommissionInfo behavior'))

parser.add_argument('--stocklike', required=False, action='store_true',


help=('If the operation is for stock-like assets or'
'future-like assets'))

parser.add_argument('--percrel', required=False, action='store_true',


help=('If perc is expressed in relative xx% rather'
'than absolute value 0.xx'))

parser.add_argument('--plot', '-p', action='store_true',


help='Plot the read data')

parser.add_argument('--numfigs', '-n', default=1,


help='Plot using numfigs figures')

return parser.parse_args()

if __name__ == '__main__':
runstrategy()

462
backtrader’s documentation Version-1.9.58.122

User Defined Commissions

The most important part of reworking the CommInfo object to the actual
incarnation involved:

• Retaining the original CommissionInfo class and behavior


• Opening the door for easy creation of user defined commissions
• Making the format xx% the default for new commission schemes instead of 0.xx (just a matter of taste),
keeping the behavior configurable

Note

See below the docstring of CommInfoBase for parameters reference

Defining a Commission Scheme

It involves 1 or 2 steps

1. Subclassing CommInfoBase

Simply changing the default parameters may be enough. backtrader already


does this with some definitions present in the module
backtrader.commissions. The regular industry standard for Futures is a
fixed amount per contract and per round. The definition can be done as:

class CommInfo_Futures_Fixed(CommInfoBase):
params = (
('stocklike', False),
('commtype', CommInfoBase.COMM_FIXED),
)

For stocks and perc-wise commissions:

class CommInfo_Stocks_Perc(CommInfoBase):
params = (
('stocklike', True),
('commtype', CommInfoBase.COMM_PERC),
)

As stated above the default for the interpretation of the percentage here
(passed as parameter commission) is that of: xx%. Should the old/other
behavior be wished 0.xx, it can be easily done:

class CommInfo_Stocks_PercAbs(CommInfoBase):
463
backtrader’s documentation Version-1.9.58.122

params = (
('stocklike', True),
('commtype', CommInfoBase.COMM_PERC),
('percabs', True),
)

2. Overriding (if needed be) the _getcommission method

Defined as:

def _getcommission(self, size, price, pseudoexec):


'''Calculates the commission of an operation at a given price

pseudoexec: if True the operation has not yet been executed


'''

More details in a practical example below

How to apply this to the platform

Once a CommInfoBase subclass is in place the trick is to use


broker.addcommissioninfo rather than the usual broker.setcommission. The latter
will internally use the legacy CommissionInfoObject.

Easier done than said:

...

comminfo = CommInfo_Stocks_PercAbs(commission=0.005) # 0.5%


cerebro.broker.addcommissioninfo(comminfo)

The addcommissioninfo method is defined as follows:

def addcommissioninfo(self, comminfo, name=None):


self.comminfo[name] = comminfo

Setting name means that the comminfo object will only apply to assets with that
name. The default value of None means it applies to all assets in the system.

A practical example

Ticket #45 asks about a commission scheme which applies to Futures, is percentage
wise and uses the commission percentage on the entire “virtual” value of the
contract. ie: includes the future multiplier in the commission calculation.
464
backtrader’s documentation Version-1.9.58.122

It should be easy:

import backtrader as bt

class CommInfo_Fut_Perc_Mult(bt.CommInfoBase):
params = (
('stocklike', False), # Futures
('commtype', bt.CommInfoBase.COMM_PERC), # Apply % Commission
# ('percabs', False), # pass perc as xx% which is the default
)

def _getcommission(self, size, price, pseudoexec):


return size * price * self.p.commission * self.p.mult

Putting it into the system:

comminfo = CommInfo_Fut_Perc_Mult(
commission=0.1, # 0.1%
mult=10,
margin=2000 # Margin is needed for futures-like instruments
)

cerebro.addcommissioninfo(comminfo)

If the format 0.xx is preferred as the default, just set param percabs to True:

class CommInfo_Fut_Perc_Mult(bt.CommInfoBase):
params = (
('stocklike', False), # Futures
('commtype', bt.CommInfoBase.COMM_PERC), # Apply % Commission
('percabs', True), # pass perc as 0.xx
)

comminfo = CommInfo_Fut_Perc_Mult(
commission=0.001, # 0.1%
mult=10,
margin=2000 # Margin is needed for futures-like instruments
)

cerebro.addcommissioninfo(comminfo)

This all should do the trick.

465
backtrader’s documentation Version-1.9.58.122

Explaining pseudoexec

Let’s recall the definition of _getcommission:

def _getcommission(self, size, price, pseudoexec):


'''Calculates the commission of an operation at a given price

pseudoexec: if True the operation has not yet been executed


'''

The purpose of the pseudoexec arg may seem obscure but it serves a purpose.

• The platform may call this method to do precalculation of available cash and some other tasks
• This means that the method may (and it actually will) be called more than once with the same parameters

pseudoexec indicates whether the call corresponds to the actual execution of an


order. Although at first sight this may not seem “relevant”, it is if scenarios
like the following are considered:

• A broker offers a 50% discount on futures round-trip commission once the


amount of negotiated contracts has exceeeded 5000 units

In such case and if pseudoexec was not there, the multiple non-execution
calls to the method would quickly trigger the assumption that the discount
is in place.

Putting the scenario to work:

import backtrader as bt

class CommInfo_Fut_Discount(bt.CommInfoBase):
params = (
('stocklike', False), # Futures
('commtype', bt.CommInfoBase.COMM_FIXED), # Apply Commission

# Custom params for the discount


('discount_volume', 5000), # minimum contracts to achieve discount
('discount_perc', 50.0), # 50.0% discount
)

negotiated_volume = 0 # attribute to keep track of the actual volume

def _getcommission(self, size, price, pseudoexec):


if self.negotiated_volume > self.p.discount_volume:
466
backtrader’s documentation Version-1.9.58.122

actual_discount = self.p.discount_perc / 100.0


else:
actual_discount = 0.0

commission = self.p.commission * (1.0 - actual_discount)


commvalue = size * price * commission

if not pseudoexec:
# keep track of actual real executed size for future discounts
self.negotiated_volume += size

return commvalue

The purpose and being of pseudoexec are hopefully clear now.

CommInfoBase docstring and params

See Commissions: Stocks vs Futures for the reference of CommInfoBase

Commissions: Credit

In some situations, the cash amount in real brokers may be decreased because the
operation on assets includes an interest rate. Examples:

• Short selling of stocks


• ETFs both long and short

The charge goes directly against the cash balance in the broker account. But it
can still be seen as part of a commission scheme. And as such it has been modeled
in backtrader.

The CommInfoBase class (and with it also the CommissionInfo main interface
object) has been extended with:

• Two (2) new parameters that allow setting the interest rate and determining if should be applied only to
the short side or to both long and short

Parameters

• interest (def: 0.0)

467
backtrader’s documentation Version-1.9.58.122

If this is non-zero, this is the yearly interest charged for holding a


short selling position. This is mostly meant for stock short-selling

The default formula applied: days * price * size * (interest / 365)

It must be specified in absolute terms: 0.05 -> 5%

Note

the behavior can be changed by overriding the method: get_credit_interest

• interest_long (def: False)

Some products like ETFs get charged on interest for short and long
positions. If ths is True and interest is non-zero the interest will be
charged on both directions

The formula

The default implementation will use the following formula:

days * abs(size) * price * (interest / 365)

Where:

• days: number of days elapsed since position was opened or the last credit interest calculation took place

Overriding the formula

In order to change the formula subclassing of CommissionInfo is needed. The


method to be overridden is:

def _get_credit_interest(self, size, price, days, dt0, dt1):


'''
This method returns the cost in terms of credit interest charged by
the broker.

In the case of ``size > 0`` this method will only be called if the
parameter to the class ``interest_long`` is ``True``

The formulat for the calculation of the credit interest rate is:

The formula: ``days * price * abs(size) * (interest / 365)``

468
backtrader’s documentation Version-1.9.58.122

Params:
- ``data``: data feed for which interest is charged

- ``size``: current position size. > 0 for long positions and < 0 for
short positions (this parameter will not be ``0``)

- ``price``: current position price

- ``days``: number of days elapsed since last credit calculation


(this is (dt0 - dt1).days)

- ``dt0``: (datetime.datetime) current datetime

- ``dt1``: (datetime.datetime) datetime of previous calculation

``dt0`` and ``dt1`` are not used in the default implementation and are
provided as extra input for overridden methods
'''

It might be that the broker doesn’t consider weekends or bank holidays when
calculating the interest rate. In this case this subclass would do the trick

import backtrader as bt

class MyCommissionInfo(bt.CommInfo):

def _get_credit_interest(self, size, price, days, dt0, dt1):


return 1.0 * abs(size) * price * (self.p.interest / 365.0)

In this case, in the formula:

• days has been replaced by 1.0

Because if weekends/bank holidays do not count, the next calculation will always
happen 1 trading da after the previous calculation

Position

Position on an asset is usually checked from within a Strategy with:

• position (a property) or getposition(data=None, broker=None)

469
backtrader’s documentation Version-1.9.58.122

Which will return the position on datas[0] of the strategy in the default
broker provided by cerebro

A position is simply the indication of:

• An asset is being held with size


• The average price is price

It serves as a status and can for example be used in deciding if an order has to
be issued or not (example: long positions are only entered if no position is
open)

Reference: Position

class backtrader.position.Position(size=0, price=0.0)

Keeps and updates the size and price of a position. The object has no
relationship to any asset. It only keeps size and price.

Member Attributes:

• size (int): current size of the position


• price (float): current price of the position

The Position instances can be tested using len(position) to see if size is


not null

Trade

Definition of a trade:

• A Trade is open when the a position in a instrument goes from 0 to a size X which may positive/negative
for long/short positions)
• A Trade is closed when a position goes from X to 0.

The followig two actions:

• positive to negative
• negative to positive

Are actually seen as:

1. A trade has been closed (position went to 0 from X)


2. A new trade has been open (position goes from 0 to Y)
470
backtrader’s documentation Version-1.9.58.122

Trades are only informative and have no user callable methods.

Reference: Trade

class backtrader.trade.Trade(data=None, tradeid=0, historyon=False, size=0, price=0.0, value=0.0,


commission=0.0)

Keeps track of the life of an trade: size, price, commission (and value?)

An trade starts at 0 can be increased and reduced and can be considered


closed if it goes back to 0.

The trade can be long (positive size) or short (negative size)

An trade is not meant to be reversed (no support in the logic for it)

Member Attributes:

• ref: unique trade identifier


• status (int): one of Created, Open, Closed
• tradeid: grouping tradeid passed to orders during creation The
default in orders is 0
• size (int): current size of the trade
• price (float): current price of the trade
• value (float): current value of the trade
• commission (float): current accumulated commission
• pnl (float): current profit and loss of the trade (gross pnl)
• pnlcomm (float): current profit and loss of the trade minus
commission (net pnl)
• isclosed (bool): records if the last update closed (set size to null
the trade
• isopen (bool): records if any update has opened the trade
• justopened (bool): if the trade was just opened
• baropen (int): bar in which this trade was opened
• dtopen (float): float coded datetime in which the trade was closed
o Use method open_datetime to get a Python datetime.datetime or use the platform
provided num2date method
• barclose (int): bar in which this trade was closed
• dtclose (float): float coded datetime in which the trade was closed
o Use method close_datetime to get a Python datetime.datetime or use the platform
provided num2date method
• barlen (int): number of bars this trade was open
• historyon (bool): whether history has to be recorded

471
backtrader’s documentation Version-1.9.58.122

• history (list): holds a list updated with each “update” event


containing the resulting status and parameters used in the update

The first entry in the history is the Opening Event The last entry in
the history is the Closing Event

Plotting

Although backtesting is meant to be an automated process based on mathematical


calculations, it is often the case that one wants to actually visualize what’s
going on. Be it with an existing algorithm which has undergone a backtesting run
or looking at what really indicators (built-in or custom) deliver with the data.

And because everything has a human being behind it, charting the data feeds,
indicators, operations, evolution of cash and portfolio value can help the humans
to better appreciate what’s going on, discard/modify/create ideas and whatever
the human looking at the chart may do with the visual information.

That’s why backtrader, using the facilities provided by matplotlib, provides


built-in charting facilities.

How to plot

Any backtesting run can be plotted with the invocation of a single method:

cerebro.plot()

Of course this is usually the last command issued like in this simple code which
uses one of the sample data from the backtrader sources.

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import backtrader as bt

class St(bt.Strategy):
def __init__(self):
self.sma = bt.indicators.SimpleMovingAverage(self.data)

data = bt.feeds.BacktraderCSVData(dataname='../../datas/2005-2006-day-001.txt')

472
backtrader’s documentation Version-1.9.58.122

cerebro = bt.Cerebro()
cerebro.adddata(data)
cerebro.addstrategy(St)
cerebro.run()
cerebro.plot()

And this yields the following chart.

The chart includes 3 Observers which in this case and given the lack of any
trading are mostly pointless

• A CashValue observer which as the name implies keeps track of the Cash and
total portolio Value (including cash) during the life of the backtesting
run
• A Trade Observer which shows, at the end of a trade, the actual Profit and
Loss

473
backtrader’s documentation Version-1.9.58.122

A trade is defined as opening a position and taking the position back to 0


(directly or crossing over from long to short or short to long)

• A BuySell observer which plots (on top of the prices) where buy and sell
operations have taken place

These 3 Observers are automatically added by cerebro, and are controlled with the
stdstats parameter (default: True). Do the following to disable them if you wish:

cerebro = bt.Cerebro(stdstats=False)

or later when running as in:

cerebro = bt.Cerebro()
...
cerebro.run(stdstats=False)

Plotted Elements

Although the Observers have already been mentioned above in the introduction,
they are not the only elements to get plotted. These 3 things get plotted:

• Data Feeds added to Cerebro with adddata, replaydata and resampledata


• Indicators declared at strategy level (or added to cerebro with
addindicator which is purely meant for experimentation purposes and has the
indicator added to a dummy strategy)
• Observers added to cerebro with addobserver

The Observers are lines objects which run in sync with the strategy and
have access to the entire ecosystem, to be able to track things like Cash
and Value

Plotting Options

Indicators and Observers have several options that control how they have to be
plotted on the chart. There are 3 big groups:

• Options affecting the plotting behavior of the entire object


• Options affecting the plotting behavior of individual lines
• Options affecting the SYSTEM wide plotting options

474
backtrader’s documentation Version-1.9.58.122

Object-wide plotting options

These are controlled by this data set in Indicators and Observers:

plotinfo = dict(plot=True,
subplot=True,
plotname='',
plotskip=False,
plotabove=False,
plotlinelabels=False,
plotlinevalues=True,
plotvaluetags=True,
plotymargin=0.0,
plotyhlines=[],
plotyticks=[],
plothlines=[],
plotforce=False,
plotmaster=None,
plotylimited=True,
)

Although plotinfo is shown as a dict during class definition, the metaclass


machinery of backtrader turns that into an object which is inherited and can
undergo even multiple inheritance. Than means:

• If a subclass changes for example a value like subplot=True to subplot=False, subclasses further down the
hierarchy will have the latter as the default value for subplot

There are 2 methods of giving value to these parameters. Let’s look at a


SimpleMovingAverage instantiation for the 1st method:

sma = bt.indicators.SimpleMovingAverage(self.data, period=15, plotname='mysma')

As can be inferred from the example, any **kwargs not consumed by the
SimpleMovingAverage constructor will be parsed (if possible) as plotinfo values.
The SimpleMovingAverage has a single parameter defined which is period. And this
means that plotname will be matched against the parameter of the same name in
plotinfo.

The 2nd method:

sma = bt.indicators.SimpleMovingAverage(self.data, period=15)


sma.plotinfo.plotname = 'mysma'

475
backtrader’s documentation Version-1.9.58.122

The plotinfo object instantiated along the SimpleMovingAverage can be accessed


and the parameters inside can also be accessed with the standard Python dot
notation. Easy and possibly clearer than the syntax abve.

The meaning of the options

• plot: whether the object has to be plotted


• subplot: whether to plot along the data or in an independent subchart.
Moving Averages are an example of plotting over the data. Stochastic and
RSI are examples of things plotted in a subchart on a different scale.
• plotname: name to use on the chart instead of the class name. As in the
example above mysma instead of SimpleMovingAverage
• plotskip (deprecated): and old alias of plot
• plotabove: whether to plot above the data. Else plot below. This has only
effect if subplot=True
• plotlinelabels: whether to plot the names of the individudal lines along
the data in the legend on the chart when subplot=False

Example: The Bollinger Bands have 3 lines but the indicator is plotted on
top of the data. It seems sensible to have the legend only display a single
name like BollingerBands rather than having the name of the 3 individual
lines displayed (mid, top, bot)

A use case for this is the BuySell observer for which it makes sense to
display the name of the 2 lines and its markers: Buy and Sell to make it
clear for the end user what is what.

• plotlinevalues: controls whether the legend for the lines in indicators and
observers has the last plotted value. Can be controlled on a per-line basis
with _plotvalue for each line
• plotvaluetags: controls whether a value tag with the last value is plotted
on the right hand side of the line. Can be controlled on a per-line basis
with _plotvaluetag for each line
• plotymargin: margin to add to the top and bottom of individual subcharts on
the graph

It is a percentage but 1 based. For example: 0.05 -> 5%

• plothlines: an iterable containing values (within the scale) at which


horizontal lines have to be plotted.

This for example helps for the classical indicators with overbought,
oversold areas like the RSI which usually has lines plotted at 70 and 30

476
backtrader’s documentation Version-1.9.58.122

• plotyticks: an iterable containing values (within the scale) at which value


ticks have to specifically be placed on the scale

For example to force the scale to have a 50 to identify the mid point of
the scale. Although this seems obvious, the indicators use an auto-scaling
mechanism and the 50 may not be obviously be in the centre if an indicator
with a 0-100 scale moves between 30-95 on a regular basis.

• plotyhlines: an iterable containing values (within the scale) at which


horizontal lines have to be plotted.

This can take over both plothlines and plotyticks.

If none of the above are defined, then where to place horizontal lines and
ticks will be entirely controlled by this value

If any of the above are defined they have precedence over the values
present in this option

• plotforce: sometimes and thus the complex process of matching data feeds to
indicators and bla, bla, bla ... a custom indicator may fail to plot. This
is a last resort mechanism to try to enforce plotting.

Use it if all else fails

• plotmaster: an Indicator/Observer has a master which is the data on which


is working. In some cases plotting it with a different master may be wished
needed.

A use case is the PivotPoint indicator which is calculated on Monthly data


but is meant for Daily data. It only makes sense to plot it on the daily
data which is where the indicator makes sense.

• plotylimited: currently only applies to data feeds. If True (default),


other lines on the data plot don’t change the scale. Example: Bollinger
Bands (top and bottom) may be far away from the actual absolute
minimum/maximum of the data feed. With `plotlimited=True, those bands
remain out of the chart, because the data controls the scaling. If set to
False, the bands affects the y-scale and become visible on the chart

A use case is the PivotPoint indicator which is calculated on Monthly data


but is meant for Daily data. It only makes sense to plot it on the daily
data which is where the indicator makes sense.

477
backtrader’s documentation Version-1.9.58.122

Line specific plotting options

Indicators/Observers have lines and how this lines are plotted can be influenced
with the plotlines object. Most of options specified in plotlines are meant to be
directly passed over to matplotlib when plotting. The documentation relies
therefore on examples of things that have been done.

IMPORTANT: The options are specified on a per-line basis.

Some of the options are controlled directly by backtrader. These all start with
an underscore (_):

• _plotskip (boolean) which indicates that plotting of a specific line has to


be skipped if set to True
• _plotvalue (boolean) to control if the legend of this line will contain the
last plotted value (default is True)
• _plotvaluetag (boolean) to control if a righ hand side tag with the last
value is plotted (default is True)
• _name (string) which changes the plot name of a specific line
• _samecolor (boolean) this forces the next line to have the same color as
the previous one avoiding the matplotlib default mechanism of cycling
trough a color map for each new plotted element
• _method (string) which chooses the plotting method matplotlib will use for
the element. If this is not specified, then the most basic plot method will
be chosen.

Example from MACDHisto. Here the histo line is plotted as a bar which is
the industry de-facto standard. The following definition can be found in
the definition of MACDHisto:

lines = ('histo',)
plotlines = dict(histo=dict(_method='bar', alpha=0.50, width=1.0))

alpha and width are options for matplotlib

• _fill_gt / _fill_lt

Allow filling between the given line and:

o Another line
o A numeric value

The arguments is an iterable of 2 elements in which:

478
backtrader’s documentation Version-1.9.58.122

o The 1st argument is a string (name of reference line) or a numeric


value

The filling will be done in between the own values and the values of
the line or the numeric value

o The 2nd argument is either:


A string with a colour name (matplotlib compatible) or hex specification (see matloplit
examples)

or

An iterable where the 1st element is the string/hex value for the colour and the second
element is a numeric value specifying the alpha transparency (default: 0.20 controlled
with fillalpha in a plotting scheme)

Examples:

# Fill for myline when above other_line with colour red


plotlines = dict(
myline=dict(_fill_gt('other_line', 'red'))
)

# Fill for myline when above 50 with colour red


plotlines = dict(
myline=dict(_fill_gt(50, 'red))
)

# Fill for myline when above other_line with colour red and 50%
# transparency (1.0 means "no transparency")

plotlines = dict(
myline=dict(_fill_gt('other_line', ('red', 0.50)))
)

Passing options to a not yet known line

• Ue the name _X where X stands for a digit in a zero-based index. This means that the options are for line X

A use case from OscillatorMixIn:

plotlines = dict(_0=dict(_name='osc'))

479
backtrader’s documentation Version-1.9.58.122

As the name implies, this is a mixin class intended to be used in multiple


inheritance schemes (specifically on the right hand side). The mixin has no
knowledge of the actual name of the 1st line (index is zero-based) from the other
indicator that will be part of the multiple inheritance mix.

And that’s why the options are specified to be for: _0. After the subclassing
has taken place the 1st line of the resulting class will have the name osc in
plot.

Some plotlines examples

The BuySell observer has the following:

plotlines = dict(
buy=dict(marker='^', markersize=8.0, color='lime', fillstyle='full'),
sell=dict(marker='v', markersize=8.0, color='red', fillstyle='full')
)

The buy and sell lines have options which are passed directly to matplotlib to
define marker, markersize, color and fillstyle. All these options are defined in
matplotlib

The Trades observer has the following:

...
lines = ('pnlplus', 'pnlminus')
...

plotlines = dict(
pnlplus=dict(_name='Positive',
marker='o', color='blue',
markersize=8.0, fillstyle='full'),
pnlminus=dict(_name='Negative',
marker='o', color='red',
markersize=8.0, fillstyle='full')
)

Here the names of the lines have been redefined from for example pnlplus to
Positive by using _name. The rest of the options are for matplotlib

The DrawDown observer:

lines = ('drawdown', 'maxdrawdown',)

480
backtrader’s documentation Version-1.9.58.122

...

plotlines = dict(maxdrawdown=dict(_plotskip='True',))

This one defines two lines to let the end users access not only the value of the
current drawdown but also its maximum value (maxdrawdown). But the latter is not
plotted due to _plotskip=True

The BollingerBands indicator:

plotlines = dict(
mid=dict(ls='--'),
top=dict(_samecolor=True),
bot=dict(_samecolor=True),
)

Here the mid line will have a dashed style and the top and bot lines will have
the same color as the mid line.

The Stochastic (defined in _StochasticBase and inherited):

lines = ('percK', 'percD',)


...
plotlines = dict(percD=dict(_name='%D', ls='--'),
percK=dict(_name='%K'))

The slower line percD is plotted with a dashed style. And the names of the lines
are changed to include fancy % signs (%K and %D) which cannot be used in name
definitions in Python

Methods controlling plotting

When dealing with Indicators and Observers the following methods are supported to
further control plotting:

• _plotlabel(self)

Which should return a list of things to conform the labels which will be
placed in between parentheses after the name of the Indicators or Observer

An example from the RSI indicator:

def _plotlabel(self):
plabels = [self.p.period]
plabels += [self.p.movav] * self.p.notdefault('movav')
481
backtrader’s documentation Version-1.9.58.122

return plabels

As can be seen this method returns:

o An int which indicates the period configured for the RSI and if the
default moving average has been changed, the specific class

In the background both will be converted to a string. In the case of


the class an effort will be made to just print the name of the class
rather than the complete module.name combination.

• _plotinit(self)

Which is called at the beginning of plotting to do whatever specific


initialization the indicator may need. Again, an example from RSI:

def _plotinit(self):
self.plotinfo.plotyhlines = [self.p.upperband, self.p.lowerband]

Here the code assigns a value to plotyhlines to have horizontal lines (the
hlines part) plotted at specific y values.

The values of the parameters upperband and lowerband are used for this,
which cannot be known in advance, because the parameters can be changed by
the end user

System-wide plotting options

First the signature of plot within cerebro:

def plot(self, plotter=None, numfigs=1, iplot=True, **kwargs):

Which means:

• plotter: an object/class containing as attributes the options controlling


the system wide plotting

If None is passed a default PlotScheme object (see below) will be


instantiated

• numfigs: in how many independent charts a plot has to be broken

Sometimes a chart contains too many bars and will not be easily readable if
packed in a single figure. This breaks it down in as many pieces as
requested
482
backtrader’s documentation Version-1.9.58.122

• iplot: automatically plot inline if running inside a Jupyter Notebook


• **kwargs: the args will be used to change the values of the attributes of
plotter or the default PlotScheme object created if no plotter is passed.

PlotScheme

This object contains all the options that contol system-wide plotting. The
options are documented in the code:

class PlotScheme(object):
def __init__(self):
# to have a tight packing on the chart wether only the x axis or also
# the y axis have (see matplotlib)
self.ytight = False

# y-margin (top/bottom) for the subcharts. This will not overrule the
# option plotinfo.plotymargin
self.yadjust = 0.0
# Each new line is in z-order below the previous one. change it False
# to have lines paint above the previous line
self.zdown = True
# Rotation of the date labes on the x axis
self.tickrotation = 15

# How many "subparts" takes a major chart (datas) in the overall chart
# This is proportional to the total number of subcharts
self.rowsmajor = 5

# How many "subparts" takes a minor chart (indicators/observers) in the


# overall chart. This is proportional to the total number of subcharts
# Together with rowsmajor, this defines a proportion ratio betwen data
# charts and indicators/observers charts
self.rowsminor = 1

# Distance in between subcharts


self.plotdist = 0.0

# Have a grid in the background of all charts


self.grid = True

# Default plotstyle for the OHLC bars which (line -> line on close)
# Other options: 'bar' and 'candle'
self.style = 'line'

483
backtrader’s documentation Version-1.9.58.122

# Default color for the 'line on close' plot


self.loc = 'black'
# Default color for a bullish bar/candle (0.75 -> intensity of gray)
self.barup = '0.75'
# Default color for a bearish bar/candle
self.bardown = 'red'
# Level of transparency to apply to bars/cancles (NOT USED)
self.bartrans = 1.0

# Wether the candlesticks have to be filled or be transparent


self.barupfill = True
self.bardownfill = True

# Wether the candlesticks have to be filled or be transparent


self.fillalpha = 0.20

# Wether to plot volume or not. Note: if the data in question has no


# volume values, volume plotting will be skipped even if this is True
self.volume = True

# Wether to overlay the volume on the data or use a separate subchart


self.voloverlay = True
# Scaling of the volume to the data when plotting as overlay
self.volscaling = 0.33
# Pushing overlay volume up for better visibiliy. Experimentation
# needed if the volume and data overlap too much
self.volpushup = 0.00

# Default colour for the volume of a bullish day


self.volup = '#aaaaaa' # 0.66 of gray
# Default colour for the volume of a bearish day
self.voldown = '#cc6073' # (204, 96, 115)
# Transparency to apply to the volume when overlaying
self.voltrans = 0.50

# Transparency for text labels (NOT USED CURRENTLY)


self.subtxttrans = 0.66
# Default font text size for labels on the chart
self.subtxtsize = 9

# Transparency for the legend (NOT USED CURRENTLY)


self.legendtrans = 0.25
# Wether indicators have a leged displaey in their charts
self.legendind = True
484
backtrader’s documentation Version-1.9.58.122

# Location of the legend for indicators (see matplotlib)


self.legendindloc = 'upper left'

# Plot the last value of a line after the Object name


self.linevalues = True

# Plot a tag at the end of each line with the last value
self.valuetags = True

# Default color for horizontal lines (see plotinfo.plothlines)


self.hlinescolor = '0.66' # shade of gray
# Default style for horizontal lines
self.hlinesstyle = '--'
# Default width for horizontal lines
self.hlineswidth = 1.0

# Default color scheme: Tableau 10


self.lcolors = tableau10

# strftime Format string for the display of ticks on the x axis


self.fmt_x_ticks = None

# strftime Format string for the display of data points values


self.fmt_x_data = None

Colors in PlotScheme

The PlotScheme class defines a method which can be overriden in subclasses which
returns the next color to be used:

def color(self, idx)

Where idx is the current index to the line being plotted on a individual
subchart. The MACD for example plots 3 lines and hence the idx variable will only
have the following values: 0, 1 and 2. The next chart (maybe another indicator)
will star the count again at 0.

The default color scheme used in backtrader uses (as seen above) is the Tableau
10 Color Palette with the index modified to be:

tab10_index = [3, 0, 2, 1, 2, 4, 5, 6, 7, 8, 9]

By overriding the color method or passing a lcolors variable to plot (or in a


subclass of PlotScheme) the colouring can be completely changed.
485
backtrader’s documentation Version-1.9.58.122

The source code contains also the defintions for the Tableau 10 Light and the
Tableau 20 color palettes.

Plotting Date Ranges

The release, 1.9.31.x added the capability to make partial plots.

• Either with indices to the full length array of timestamps kept in strategy instances
• Or with actual datetime.date or datetime.datetime instances that limit what has to be plotted.

Everything still over the standard cerebro.plot. Example:

cerebro.plot(start=datetime.date(2005, 7, 1), end=datetime.date(2006, 1, 31))

Being that the straightforward way to do it for humans. Humans with extended
capabilities can actually try indices to the datetime timestamps as in:

cerebro.plot(start=75, end=185)

A very standard sample containing a Simple Moving Average (on-data plotting), a


Stochastic (independent plotting) and a CrossOver of the Stochastic lines is
presented below. The arguments to cerebro.plot are passed as command line
arguments.

An execution with the date approach:

./partial-plot.py --plot 'start=datetime.date(2005, 7, 1),end=datetime.date(2006,


1, 31)'

The eval magic in python allows to directly write datetime.date in the command
line and map actually that to something sensible. The output chart

486
backtrader’s documentation Version-1.9.58.122

Let’s compare it with the full plot to see that data was actually skipped from
both ends:

./partial-plot.py --plot

The eval magic in python allows to directly write datetime.date in the command
line and map actually that to something sensible. The output chart

487
backtrader’s documentation Version-1.9.58.122

Sample Usage

$ ./partial-plot.py --help
usage: partial-plot.py [-h] [--data0 DATA0] [--fromdate FROMDATE]
[--todate TODATE] [--cerebro kwargs] [--broker kwargs]
[--sizer kwargs] [--strat kwargs] [--plot [kwargs]]

Sample for partial plotting

optional arguments:
-h, --help show this help message and exit
--data0 DATA0 Data to read in (default:
../../datas/2005-2006-day-001.txt)
--fromdate FROMDATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--todate TODATE Date[time] in YYYY-MM-DD[THH:MM:SS] format (default: )
--cerebro kwargs kwargs in key=value format (default: )

488
backtrader’s documentation Version-1.9.58.122

--broker kwargs kwargs in key=value format (default: )


--sizer kwargs kwargs in key=value format (default: )
--strat kwargs kwargs in key=value format (default: )
--plot [kwargs] kwargs in key=value format (default: )

Sample Code

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
import datetime

import backtrader as bt

class St(bt.Strategy):
params = (
)

def __init__(self):
bt.ind.SMA()
stoc = bt.ind.Stochastic()
bt.ind.CrossOver(stoc.lines.percK, stoc.lines.percD)

def next(self):
pass

def runstrat(args=None):
args = parse_args(args)

cerebro = bt.Cerebro()

# Data feed kwargs


kwargs = dict()

# Parse from/to-date
dtfmt, tmfmt = '%Y-%m-%d', 'T%H:%M:%S'
for a, d in ((getattr(args, x), x) for x in ['fromdate', 'todate']):
if a:
strpfmt = dtfmt + tmfmt * ('T' in a)

489
backtrader’s documentation Version-1.9.58.122

kwargs[d] = datetime.datetime.strptime(a, strpfmt)

# Data feed
data0 = bt.feeds.BacktraderCSVData(dataname=args.data0, **kwargs)
cerebro.adddata(data0)

# Broker
cerebro.broker = bt.brokers.BackBroker(**eval('dict(' + args.broker + ')'))

# Sizer
cerebro.addsizer(bt.sizers.FixedSize, **eval('dict(' + args.sizer + ')'))

# Strategy
cerebro.addstrategy(St, **eval('dict(' + args.strat + ')'))

# Execute
cerebro.run(**eval('dict(' + args.cerebro + ')'))

if args.plot: # Plot if requested to


cerebro.plot(**eval('dict(' + args.plot + ')'))

def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=(
'Sample for partial plotting'
)
)

parser.add_argument('--data0', default='../../datas/2005-2006-day-001.txt',
required=False, help='Data to read in')

# Defaults for dates


parser.add_argument('--fromdate', required=False, default='',
help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

parser.add_argument('--todate', required=False, default='',


help='Date[time] in YYYY-MM-DD[THH:MM:SS] format')

parser.add_argument('--cerebro', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--broker', required=False, default='',


490
backtrader’s documentation Version-1.9.58.122

metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--sizer', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--strat', required=False, default='',


metavar='kwargs', help='kwargs in key=value format')

parser.add_argument('--plot', required=False, default='',


nargs='?', const='{}',
metavar='kwargs', help='kwargs in key=value format')

return parser.parse_args(pargs)

if __name__ == '__main__':
runstrat()

Plotting on the same axis

The previous post future-vs-spot, was plotting the original data and the slightly
(randomly) modified data on the same space, but not on the same axis.

Recovering the 1st picture from that post.

491
backtrader’s documentation Version-1.9.58.122

One can see:

• There are different scales on the left and right hand sides of the chart
• This is most obvious when looking at the swinging red line (the randomized
data) which oscillates +- 50 points around the original data.

On the chart the visual impression is that this randomized data is mostly
always above the original data. Which is only a visual impression due to
the different scales.

Although release 1.9.32.116 already had some initial support to fully plot on the
same axis, the legend labels would be duplicated (only the labels, not the data)
which was really confusing.

Release 1.9.33.116 cures that effect and allows full plotting on the same axis.
The usage pattern is like the one to decide with which other data to plot. From
the previous post.

492
backtrader’s documentation Version-1.9.58.122

import backtrader as bt

cerebro = bt.Cerebro()

data0 = bt.feeds.MyFavouriteDataFeed(dataname='futurename')
cerebro.adddata(data0)

data1 = bt.feeds.MyFavouriteDataFeed(dataname='spotname')
data1.compensate(data0) # let the system know ops on data1 affect data0
data1.plotinfo.plotmaster = data0
data1.plotinfo.sameaxis = True
cerebro.adddata(data1)

...

cerebro.run()

data1 gets some plotinfo values to:

• Plot on the same space as plotmaster which is data0


• Get the indication to use the sameaxis

The reason for this indication is that the platform cannot know in advance
if the scales for each data will be compatible. That’s why it will plot
them on independent scales

The previous sample gets an additional option to plot on the sameaxis. A sample
execution:

$ ./future-spot.py --sameaxis

And the resulting chart

493
backtrader’s documentation Version-1.9.58.122

To notice:

• Only one scale on the right hand side


• And now the randomized data seems to clearly oscillate around the original data which is the expected
visual behavior

Sample Usage

$ ./future-spot.py --help
usage: future-spot.py [-h] [--no-comp] [--sameaxis]

Compensation example

optional arguments:
-h, --help show this help message and exit
--no-comp
--sameaxis
494
backtrader’s documentation Version-1.9.58.122

Sample Code

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
import random
import backtrader as bt

# The filter which changes the close price


def close_changer(data, *args, **kwargs):
data.close[0] += 50.0 * random.randint(-1, 1)
return False # length of stream is unchanged

# override the standard markers


class BuySellArrows(bt.observers.BuySell):
plotlines = dict(buy=dict(marker='$\u21E7$', markersize=12.0),
sell=dict(marker='$\u21E9$', markersize=12.0))

class St(bt.Strategy):
def __init__(self):
bt.obs.BuySell(self.data0, barplot=True) # done here for
BuySellArrows(self.data1, barplot=True) # different markers per data

def next(self):
if not self.position:
if random.randint(0, 1):
self.buy(data=self.data0)
self.entered = len(self)

else: # in the market


if (len(self) - self.entered) >= 10:
self.sell(data=self.data1)

def runstrat(args=None):
args = parse_args(args)
cerebro = bt.Cerebro()

dataname = '../../datas/2006-day-001.txt' # data feed

495
backtrader’s documentation Version-1.9.58.122

data0 = bt.feeds.BacktraderCSVData(dataname=dataname, name='data0')


cerebro.adddata(data0)

data1 = bt.feeds.BacktraderCSVData(dataname=dataname, name='data1')


data1.addfilter(close_changer)
if not args.no_comp:
data1.compensate(data0)
data1.plotinfo.plotmaster = data0
if args.sameaxis:
data1.plotinfo.sameaxis = True
cerebro.adddata(data1)

cerebro.addstrategy(St) # sample strategy

cerebro.addobserver(bt.obs.Broker) # removed below with stdstats=False


cerebro.addobserver(bt.obs.Trades) # removed below with stdstats=False

cerebro.broker.set_coc(True)
cerebro.run(stdstats=False) # execute
cerebro.plot(volume=False) # and plot

def parse_args(pargs=None):
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description=('Compensation example'))

parser.add_argument('--no-comp', required=False, action='store_true')


parser.add_argument('--sameaxis', required=False, action='store_true')
return parser.parse_args(pargs)

if __name__ == '__main__':
runstrat()

Optimization improvements

Version 1.8.12.99 of backtrader includes an improvement in how data feeds and


results are managed during multiprocessing.

Note

The behavior for both has been made


496
backtrader’s documentation Version-1.9.58.122

The behavior of these options can be controlled through two new Cerebro
parameters:

• optdatas (default: True)

If True and optimizing (and the system can preload and use runonce, data
preloading will be done only once in the main process to save time and
resources.

• optreturn (default: True)

If True the optimization results will not be full Strategy objects (and all
datas, indicators, observers ...) but and object with the following
attributes (same as in Strategy):

o params (or p) the strategy had for the execution


o analyzers the strategy has executed

In most occassions, only the analyzers and with which params are the things
needed to evaluate a the performance of a strategy. If detailed analysis of
the generated values for (for example) indicators is needed, turn this off

Data Feed Management

In a Optimization scenario this is a likely combination of Cerebro parameters:

• preload=True (default)

Data Feeeds will be preloaded before running any backtesting code

• runonce=True (default)

Indicators will be calculated in batch mode a tight for loop, instead of


step by step.

If both conditions are True and optdatas=True, then:

• The Data Feeds will be preloaded in the main process before spawning new subprocesses (the ones in
charge of executing the backtesting)

Results management

In a Optimization scenario two things should play the most important role when
evaluating the different parameters with which each *Strategy was run:
497
backtrader’s documentation Version-1.9.58.122

• strategy.params (or strategy.p)

The actual set of values used for the backtesting

• strategy.analyzers

The objects in charge of providing the evaluation of how the Strategy has
actually performed. Example:

SharpeRatio_A (the annualized SharpeRatio)

When optreturn=True, instead of returning full strategy instances, placeholder


objects will be created which carry the two attributes aforementioned to let the
evaluation take place.

This avoids passing back lots of generated data like for example the values
generated by indicators during the backtesting

Should the full strategy objects be wished, simply set optreturn=False during
cerebro instantiation or when doing cerebro.run.

Some test runs

The optimization sample in the backtrader sources has been extended to add
control for optdatas and optreturn (actually to disable them)

Single Core Run

As a reference what happens when the amount of CPUs is limited to 1 and the
multiprocessing module is not used:

$ ./optimization.py --maxcpus 1
==================================================
**************************************************
--------------------------------------------------
OrderedDict([(u'smaperiod', 10), (u'macdperiod1', 12), (u'macdperiod2', 26),
(u'macdperiod3', 9)])
**************************************************
--------------------------------------------------
OrderedDict([(u'smaperiod', 10), (u'macdperiod1', 13), (u'macdperiod2', 26),
(u'macdperiod3', 9)])
...
...

498
backtrader’s documentation Version-1.9.58.122

OrderedDict([(u'smaperiod', 29), (u'macdperiod1', 19), (u'macdperiod2', 29),


(u'macdperiod3', 14)])
==================================================
Time used: 184.922727833

Multiple Core Runs

Without limiting the number of CPUs, the Python multiprocessing module will try
to use all of them. optdatas and optreturn will be disabled

Both optdata and optreturn active

The default behavior:

$ ./optimization.py
...
...
...
==================================================
Time used: 56.5889185394

The total improvement by having multicore and the data feed and results
improvements means going down from 184.92 to 56.58 seconds.

Take into account that the sample is using 252 bars and the indicators generate
only values with a length of 252 points. This is just an example.

The real question is how much of this is attributable to the new behavior.

optreturn deactivated

Let’s pass full strategy objects back to the caller:

$ ./optimization.py --no-optreturn
...
...
...
==================================================
Time used: 67.056914007

The execution time has increased 18.50% (or a speed-up of 15.62%) is in place.

499
backtrader’s documentation Version-1.9.58.122

optdatas deactivated

Each subproccess is forced to load its own set of values for the data feeds:

$ ./optimization.py --no-optdatas
...
...
...
==================================================
Time used: 72.7238112637

The execution time has increased 28.52% (or a speed-up of 22.19%) is in place.

Both deactivated

Still using multicore but with the old non-improved behavior:

$ ./optimization.py --no-optdatas --no-optreturn


...
...
...
==================================================
Time used: 83.6246643786

The execution time has increased 47.79% (or a speed-up of 32.34%) is in place.

This shows that the used of multiple cores is the major contributor to the time
improvement.

Note

The executions have been done in a Laptop with a i7-4710HQ (4-core / 8 logical)
with 16 GBytes of RAM under Windows 10 64bit. The mileage may vary under other
conditions

Concluding

• The greatest factor in time reduction during optimization is the use of the multiple cores
• The sample runs with optdatas and optreturn show speed-ups of around 22.19% and 15.62% each
(32.34% both together in the test)

500
backtrader’s documentation Version-1.9.58.122

Sample Usage

$ ./optimization.py --help
usage: optimization.py [-h] [--data DATA] [--fromdate FROMDATE]
[--todate TODATE] [--maxcpus MAXCPUS] [--no-runonce]
[--exactbars EXACTBARS] [--no-optdatas]
[--no-optreturn] [--ma_low MA_LOW] [--ma_high MA_HIGH]
[--m1_low M1_LOW] [--m1_high M1_HIGH] [--m2_low M2_LOW]
[--m2_high M2_HIGH] [--m3_low M3_LOW]
[--m3_high M3_HIGH]

Optimization

optional arguments:
-h, --help show this help message and exit
--data DATA, -d DATA data to add to the system
--fromdate FROMDATE, -f FROMDATE
Starting date in YYYY-MM-DD format
--todate TODATE, -t TODATE
Starting date in YYYY-MM-DD format
--maxcpus MAXCPUS, -m MAXCPUS
Number of CPUs to use in the optimization
- 0 (default): use all available CPUs
- 1 -> n: use as many as specified
--no-runonce Run in next mode
--exactbars EXACTBARS
Use the specified exactbars still compatible with preload
0 No memory savings
-1 Moderate memory savings
-2 Less moderate memory savings
--no-optdatas Do not optimize data preloading in optimization
--no-optreturn Do not optimize the returned values to save time
--ma_low MA_LOW SMA range low to optimize
--ma_high MA_HIGH SMA range high to optimize
--m1_low M1_LOW MACD Fast MA range low to optimize
--m1_high M1_HIGH MACD Fast MA range high to optimize
--m2_low M2_LOW MACD Slow MA range low to optimize
--m2_high M2_HIGH MACD Slow MA range high to optimize
--m3_low M3_LOW MACD Signal range low to optimize
--m3_high M3_HIGH MACD Signal range high to optimize

501
backtrader’s documentation Version-1.9.58.122

Automating BackTesting

So far all backtrader examples and working samples have started from scratch
creating a main Python module which loads datas, strategies, observers and
prepares cash and commission schemes.

One of the goals of algorithmic trading is the automation of trading and given
that bactrader is a backtesting platform intented to check trading algorithms
(hence is an algotrading platform), automating the use of backtrader was an
obvious goal.

When installed backtrader provides 2 entry points in the form of


scripts/executables which which automates most tasks:

• bt-run-py a script which uses the codebase from the next item

and

• btrun (executable)

Entry point created by setuptools during packaging. The executable offers


advantages under Windows where in theory no errors about “path/file not
found” will happen.

The description below applies equally to both tools.

btrun allows the end user to:

• Say which data feeds have to be loaded


• Set the format to load the datas
• Specify the date range for the datas
• Pass parameters to Cerebro - Disable standard observers

This was an original extra switch before the “Cerebro” parameters were
implemented. As such and if a parameter to cerebro with regards to Standard
Observers is passed, this will be ignored (parameter stdstats to Cerebro)

• Load one or more observers (example: DrawDown) from the built-in ones or
from a python module
• Set the cash and commission scheme parameters for the broker (commission,
margin, mult)
• Enable plotting, controlling the amount of charts and style to present the
data
• Add a parametrized writer to the system

502
backtrader’s documentation Version-1.9.58.122

And finally what should be the core competence:

• Load a strategy (a built-in one or from a Python module)


• Pass parameters to the loaded strategy

See below for the Usage of the script.

Applying a User Defined Strategy

Let’s consider the following strategy which:

• Simply loads a SimpleMovingAverage (default period 15)


• Prints outs
• Is in a file named mymod.py

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import backtrader as bt
import backtrader.indicators as btind

class MyTest(bt.Strategy):
params = (('period', 15),)

def log(self, txt, dt=None):


''' Logging function fot this strategy'''
dt = dt or self.data.datetime[0]
if isinstance(dt, float):
dt = bt.num2date(dt)
print('%s, %s' % (dt.isoformat(), txt))

def __init__(self):
sma = btind.SMA(period=self.p.period)

def next(self):
ltxt = '%d, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f'

self.log(ltxt %
(len(self),
self.data.open[0], self.data.high[0],
self.data.low[0], self.data.close[0],
self.data.volume[0], self.data.openinterest[0]))
503
backtrader’s documentation Version-1.9.58.122

Executing the strategy with the usual testing sample is easy: easy:

btrun --csvformat btcsv \


--data ../../datas/2006-day-001.txt \
--strategy mymod.py

The chart output

The console output:

2006-01-20T23:59:59+00:00, 15, 3593.16, 3612.37, 3550.80, 3550.80, 0.00, 0.00


2006-01-23T23:59:59+00:00, 16, 3550.24, 3550.24, 3515.07, 3544.31, 0.00, 0.00
2006-01-24T23:59:59+00:00, 17, 3544.78, 3553.16, 3526.37, 3532.68, 0.00, 0.00
2006-01-25T23:59:59+00:00, 18, 3532.72, 3578.00, 3532.72, 3578.00, 0.00, 0.00
...
...
2006-12-22T23:59:59+00:00, 252, 4109.86, 4109.86, 4072.62, 4073.50, 0.00, 0.00
504
backtrader’s documentation Version-1.9.58.122

2006-12-27T23:59:59+00:00, 253, 4079.70, 4134.86, 4079.70, 4134.86, 0.00, 0.00


2006-12-28T23:59:59+00:00, 254, 4137.44, 4142.06, 4125.14, 4130.66, 0.00, 0.00
2006-12-29T23:59:59+00:00, 255, 4130.12, 4142.01, 4119.94, 4119.94, 0.00, 0.00

Same strategy but:

• Setting the parameter period to 50

The command line:

btrun --csvformat btcsv \


--data ../../datas/2006-day-001.txt \
--plot \
--strategy mymod.py:period=50

The chart output.

505
backtrader’s documentation Version-1.9.58.122

Note

if no .py extension is given, bt-run will add it.

Using a built-in Strategy

backtrader will slowly be including sample (textbook) strategies. Along with the
bt-run.py script a standard Simple Moving Average CrossOver strategy is included.
The name:

• SMA_CrossOver
• Parameters
o fast (default 10) period of the fast moving average
o slow (default 30) period of the slow moving average

The strategy buys if the fast moving average crosses up the fast and sells (only
if it has bought before) upon the fast moving average crossing down the slow
moving average.

The code

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import backtrader as bt
import backtrader.indicators as btind

class SMA_CrossOver(bt.Strategy):

params = (('fast', 10), ('slow', 30))

def __init__(self):

sma_fast = btind.SMA(period=self.p.fast)
sma_slow = btind.SMA(period=self.p.slow)

self.buysig = btind.CrossOver(sma_fast, sma_slow)

def next(self):
if self.position.size:
if self.buysig < 0:
self.sell()
506
backtrader’s documentation Version-1.9.58.122

elif self.buysig > 0:


self.buy()

Standard execution:

btrun --csvformat btcsv \


--data ../../datas/2006-day-001.txt \
--plot \
--strategy :SMA_CrossOver

Notice the :. The standard notation (see below) to load a strategy is:

• module:stragegy:kwargs

With the following rules:

• If module is there and strategy is specified, then that strategy will be used
• If module is there but no strategy is specified, the 1st strategy found in the module will be returned
• If no module is specified, “strategy” is assumed to refer to a strategy in the backtrader package
• If module and/or strategy are there, if kwargs are present they will be passed to the corresponding
strategy

Note

The same notation and rules apply to --observer, --analyzer and --indicator
options

Obviously for the corresponding object types

The output

507
backtrader’s documentation Version-1.9.58.122

One last example adding commission schemes, cash and changing the parameters:

btrun --csvformat btcsv \


--data ../../datas/2006-day-001.txt \
--plot \
--cash 20000 \
--commission 2.0 \
--mult 10 \
--margin 2000 \
--strategy :SMA_CrossOver:fast=5,slow=20

The output

508
backtrader’s documentation Version-1.9.58.122

We have backtested the strategy:

• Changing the moving average periods


• Setting a new starting cash
• Putting a commission scheme in place for a futures-like instrument

See the continuous variations in cash with each bar, as cash is adjusted
for the futures-like instrument daily changes

Using no Strategy

This is a an over-statement. A strategy will be applied, but you can ommit any
kind of strategy and a default backtrader.Strategy will be added.

Analyzers, Observers and Indicators will be automatically injected in the


strategy.
509
backtrader’s documentation Version-1.9.58.122

An example:

btrun --csvformat btcsv \


--data ../../datas/2006-day-001.txt \
--cash 20000 \
--commission 2.0 \
--mult 10 \
--margin 2000 \
--nostdstats \
--observer :Broker

This will do not much but serves the purpose:

• A default backtrader.Strategy is added in the background


• Cerebro will not instantiate the regular stdstats observers (Broker, BuySell, Trades)
• A Broker observer is added manually

As mentioned above, the nostdstats is a legacy parameter. Newer versions of btrun


can pass parameters directly to Cerebro. An equivalent invocation would be:

btrun --csvformat btcsv \


--data ../../datas/2006-day-001.txt \
--cash 20000 \
--commission 2.0 \
--mult 10 \
--margin 2000 \
--cerebro stdstats=False \
--observer :Broker

Adding Analyzers

btrun also supports adding Analyzers with the same syntax used for the strategies
to choose between internal/external analyzers.

Example with a SharpeRatio analysis for the years 2005-2006:

btrun --csvformat btcsv \


--data ../../datas/2005-2006-day-001.txt \
--strategy :SMA_CrossOver \
--analyzer :SharpeRatio

The console output is nothing.

If a printout of the Analyzer results is wished, it must be specified with:

510
backtrader’s documentation Version-1.9.58.122

• --pranalyzer which defaults to calling the next one (unless the Analyzer has overriden the proper method)
• --ppranalyzer which uses the pprint module to print the results

Note

The two printing options were implemented before writers were part of backtrader.
Adding a writer without csv output will achieve the same (and the output has been
improved)

Extending the example from above:

btrun --csvformat btcsv \


--data ../../datas/2005-2006-day-001.txt \
--strategy :SMA_CrossOver \
--analyzer :SharpeRatio \
--plot \
--pranalyzer

====================
== Analyzers
====================
##########
sharperatio
##########
{'sharperatio': 11.647332609673256}

Good strategy!!! (Pure luck for the example actually which also bears no
commissions)

The chart (which simply shows the Analyzer is not in the plot, because Analyzers
cannot be plotted, they aren’t lines objects)

511
backtrader’s documentation Version-1.9.58.122

The same example but using a writer argument:

btrun --csvformat btcsv \


--data ../../datas/2005-2006-day-001.txt \
--strategy :SMA_CrossOver \
--analyzer :SharpeRatio \
--plot \
--writer

===============================================================================
Cerebro:
-----------------------------------------------------------------------------
- Datas:
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- Data0:
- Name: 2005-2006-day-001
- Timeframe: Days

512
backtrader’s documentation Version-1.9.58.122

- Compression: 1
-----------------------------------------------------------------------------
- Strategies:
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- SMA_CrossOver:
*************************************************************************
- Params:
- fast: 10
- slow: 30
- _movav: SMA
*************************************************************************
- Indicators:
.......................................................................
- SMA:
- Lines: sma
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Params:
- period: 30
.......................................................................
- CrossOver:
- Lines: crossover
- Params: None
*************************************************************************
- Observers:
.......................................................................
- Broker:
- Lines: cash, value
- Params: None
.......................................................................
- BuySell:
- Lines: buy, sell
- Params: None
.......................................................................
- Trades:
- Lines: pnlplus, pnlminus
- Params: None
*************************************************************************
- Analyzers:
.......................................................................
- Value:
- Begin: 10000.0
- End: 10496.68
.......................................................................
- SharpeRatio:
513
backtrader’s documentation Version-1.9.58.122

- Params: None
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Analysis:
- sharperatio: 11.6473326097

Adding Indicators and Observers

As with Strategies and Analyzers btrun can also add:

• Indicators

and

• Observers

The syntax is exactly the same as seen above when adding a Broker observer.

Let’s repeat the example but adding a Stochastic, the Broker and having a look
at the plot (we’ll change some parameters):

btrun --csvformat btcsv \


--data ../../datas/2006-day-001.txt \
--nostdstats \
--observer :Broker \
--indicator :Stochastic:period_dslow=5 \
--plot

The chart

514
backtrader’s documentation Version-1.9.58.122

Plotting Control

Most of the above examples have used the following option:

• --plot which has activated the creation a default plot

More control can be achieved by adding kwargs to the --plot option

• --plot style="candle" for example to plot with candlesticks instead of plotting with a LineOnClose style
(which is the plotting default)

The invocation:

btrun --csvformat btcsv \


--data ../../datas/2006-day-001.txt \
--nostdstats \
--observer :Broker \
515
backtrader’s documentation Version-1.9.58.122

--indicator :Stochastic:period_dslow=5 \
--plot style=\"candle\"

Note

The quotes around candle are quoted with backslashed \ because the example is
being run in a bash shell which removes that before passing the arguments to the
script.

Backslash quoting is needed in this case to ensure “bar” makes it to the script
and can be evaluated as a string

The chart

Usage of the script

Directly from the script:


516
backtrader’s documentation Version-1.9.58.122

$ btrun --help
usage: btrun-script.py [-h] --data DATA [--cerebro [kwargs]] [--nostdstats]
[--format
{yahoocsv_unreversed,vchart,vchartcsv,yahoo,mt4csv,ibdata,sierracsv,yahoocsv,btcs
v,vcdata}]
[--fromdate FROMDATE] [--todate TODATE]
[--timeframe
{microseconds,seconds,weeks,months,minutes,days,years}]
[--compression COMPRESSION]
[--resample RESAMPLE | --replay REPLAY]
[--strategy module:name:kwargs]
[--signal module:signaltype:name:kwargs]
[--observer module:name:kwargs]
[--analyzer module:name:kwargs]
[--pranalyzer | --ppranalyzer]
[--indicator module:name:kwargs] [--writer [kwargs]]
[--cash CASH] [--commission COMMISSION]
[--margin MARGIN] [--mult MULT] [--interest INTEREST]
[--interest_long] [--slip_perc SLIP_PERC]
[--slip_fixed SLIP_FIXED] [--slip_open]
[--no-slip_match] [--slip_out] [--flush]
[--plot [kwargs]]

Backtrader Run Script

optional arguments:
-h, --help show this help message and exit
--resample RESAMPLE, -rs RESAMPLE
resample with timeframe:compression values
--replay REPLAY, -rp REPLAY
replay with timeframe:compression values
--pranalyzer, -pralyzer
Automatically print analyzers
--ppranalyzer, -ppralyzer
Automatically PRETTY print analyzers
--plot [kwargs], -p [kwargs]
Plot the read data applying any kwargs passed

For example:

--plot style="candle" (to plot candlesticks)

Data options:
--data DATA, -d DATA Data files to be added to the system
517
backtrader’s documentation Version-1.9.58.122

Cerebro options:
--cerebro [kwargs], -cer [kwargs]
The argument can be specified with the following form:

- kwargs

Example: "preload=True" which set its to True

The passed kwargs will be passed directly to the cerebro


instance created for the execution

The available kwargs to cerebro are:


- preload (default: True)
- runonce (default: True)
- maxcpus (default: None)
- stdstats (default: True)
- live (default: False)
- exactbars (default: False)
- preload (default: True)
- writer (default False)
- oldbuysell (default False)
- tradehistory (default False)
--nostdstats Disable the standard statistics observers
--format
{yahoocsv_unreversed,vchart,vchartcsv,yahoo,mt4csv,ibdata,sierracsv,yahoocsv,btcs
v,vcdata}, --csvformat
{yahoocsv_unreversed,vchart,vchartcsv,yahoo,mt4csv,ibdata,sierracsv,yahoocsv,btcs
v,vcdata}, -c
{yahoocsv_unreversed,vchart,vchartcsv,yahoo,mt4csv,ibdata,sierracsv,yahoocsv,btcs
v,vcdata}
CSV Format
--fromdate FROMDATE, -f FROMDATE
Starting date in YYYY-MM-DD[THH:MM:SS] format
--todate TODATE, -t TODATE
Ending date in YYYY-MM-DD[THH:MM:SS] format
--timeframe {microseconds,seconds,weeks,months,minutes,days,years}, -tf
{microseconds,seconds,weeks,months,minutes,days,years}
Ending date in YYYY-MM-DD[THH:MM:SS] format
--compression COMPRESSION, -cp COMPRESSION
Ending date in YYYY-MM-DD[THH:MM:SS] format

Strategy options:
--strategy module:name:kwargs, -st module:name:kwargs
518
backtrader’s documentation Version-1.9.58.122

This option can be specified multiple times.

The argument can be specified with the following form:

- module:classname:kwargs

Example: mymod:myclass:a=1,b=2

kwargs is optional

If module is omitted then class name will be sought in


the built-in strategies module. Such as in:

- :name:kwargs or :name

If name is omitted, then the 1st strategy found in the


mod
will be used. Such as in:

- module or module::kwargs

Signals:
--signal module:signaltype:name:kwargs, -sig module:signaltype:name:kwargs
This option can be specified multiple times.

The argument can be specified with the following form:

- signaltype:module:signaltype:classname:kwargs

Example: longshort+mymod:myclass:a=1,b=2

signaltype may be ommited: longshort will be used

Example: mymod:myclass:a=1,b=2

kwargs is optional

signaltype will be uppercased to match the defintions


fromt the backtrader.signal module

If module is omitted then class name will be sought in


the built-in signals module. Such as in:

- LONGSHORT::name:kwargs or :name
519
backtrader’s documentation Version-1.9.58.122

If name is omitted, then the 1st signal found in the mod


will be used. Such as in:

- module or module:::kwargs

Observers and statistics:


--observer module:name:kwargs, -ob module:name:kwargs
This option can be specified multiple times.

The argument can be specified with the following form:

- module:classname:kwargs

Example: mymod:myclass:a=1,b=2

kwargs is optional

If module is omitted then class name will be sought in


the built-in observers module. Such as in:

- :name:kwargs or :name

If name is omitted, then the 1st observer found in the


will be used. Such as in:

- module or module::kwargs

Analyzers:
--analyzer module:name:kwargs, -an module:name:kwargs
This option can be specified multiple times.

The argument can be specified with the following form:

- module:classname:kwargs

Example: mymod:myclass:a=1,b=2

kwargs is optional

If module is omitted then class name will be sought in


the built-in analyzers module. Such as in:

- :name:kwargs or :name
520
backtrader’s documentation Version-1.9.58.122

If name is omitted, then the 1st analyzer found in the


will be used. Such as in:

- module or module::kwargs

Indicators:
--indicator module:name:kwargs, -ind module:name:kwargs
This option can be specified multiple times.

The argument can be specified with the following form:

- module:classname:kwargs

Example: mymod:myclass:a=1,b=2

kwargs is optional

If module is omitted then class name will be sought in


the built-in analyzers module. Such as in:

- :name:kwargs or :name

If name is omitted, then the 1st analyzer found in the


will be used. Such as in:

- module or module::kwargs

Writers:
--writer [kwargs], -wr [kwargs]
This option can be specified multiple times.

The argument can be specified with the following form:

- kwargs

Example: a=1,b=2

kwargs is optional

It creates a system wide writer which outputs run data

Please see the documentation for the available kwargs

521
backtrader’s documentation Version-1.9.58.122

Cash and Commission Scheme Args:


--cash CASH, -cash CASH
Cash to set to the broker
--commission COMMISSION, -comm COMMISSION
Commission value to set
--margin MARGIN, -marg MARGIN
Margin type to set
--mult MULT, -mul MULT
Multiplier to use
--interest INTEREST Credit Interest rate to apply (0.0x)
--interest_long Apply credit interest to long positions
--slip_perc SLIP_PERC
Enable slippage with a percentage value
--slip_fixed SLIP_FIXED
Enable slippage with a fixed point value
--slip_open enable slippage for when matching opening prices
--no-slip_match Disable slip_match, ie: matching capped at
high-low if slippage goes over those limits
--slip_out with slip_match enabled, match outside high-low
--flush flush the output - useful under win32 systems

Saving Memory

Release 1.3.1.92 has reworked and fully implemented the memory saving schemes
that were previously in place, although not much touted and less used.

backtrader was (and will be further) developed in machines with nice amounts of
RAM and that put together with the fact that visual feedback through plotting is
a nice to have and almost a must have, mde it easy for a design decision: keep
everything in memory.

This decision has some drawbacks:

• array.array which is used for data storage has to allocate and move data when some bounds are exceeded
• Machines with low amounts of RAM may suffer
• Connection to a live data feed which can be online for weeks/months feeded thousands of
seconds/minutes resolution ticks into the system

The latter being even more important than the 1st due to another design decision
which was made for backtrader:

• Be pure Python to allow to run in embedded systems if needed be

522
backtrader’s documentation Version-1.9.58.122

A scenario in the future could have backtrader connected to a 2nd machine


which provides the live feed, whilst backtrader itself runs inside a
Raspberry Pi or something even more limited like an ADSL Router (AVM
Frit!Box 7490 with a Freetz image)

Hence the need to have backtrader support dynamic memory schemes. Now Cerebro can
be instantiated or run with the following semantics:

• exactbars (default: False)

With the default False value each and every value stored in a line is kept
in memory

Possible values:

o True or 1: all “lines” objects reduce memory usage to the


automatically calculated minimum period.

If a Simple Moving Average has a period of 30, the underlying data


will have always a running buffer of 30 bars to allow the calculation
of the Simple Moving Average

This setting will deactivate preload and runonce


Using this setting also deactivates plotting
o -1: datas and indicators/operations at strategy level will keep all
data in memory.

For example: a RSI internally uses the indicator UpDay to make


calculations. This subindicator will not keep all data in memory

This allows to keep plotting and preloading active.


runonce will be deactivated
o -2: datas and indicators kept as attributes of the strategy will keep
all data in memory.

For example: a RSI internally uses the indicator UpDay to make


calculations. This subindicator will not keep all data in memory

If in the __init__ something like a = self.data.close -


self.data.high is defined, then a will not keep all data in memory

This allows to keep plotting and preloading active.


runonce will be deactivated

523
backtrader’s documentation Version-1.9.58.122

As always, an example is worth a thousand words. A sample script shows the


differences. It runs against the Yahoo daily data for the years 1996 to 2015, for
a total of 4965 days.

Note

This is a small sample. The EuroStoxx50 future which trades 14 hours a day, would
produce approximately 18000 1-minute bars in just 1 month of trading.

The script 1st executed to see how many memory positions are used when no memory
savings are requested:

$ ./memory-savings.py --save 0
Total memory cells used: 506430

For level 1 (total savings):

$ ./memory-savings.py --save 1
Total memory cells used: 2041

OMG!!! Down from half-a-million to 2041. Indeed. Each an every lines object in
the system uses a collections.deque as buffer (instead of array.array) and is
length-bounding to the absolute needed minimum for the requested operations.
Example:

• A Strategy using a SimpleMovingAverage of period 30 on the data feed.

In this case the following adjustments would be made:

• The data feed will have a buffer of 30 positions, the amount needed by the SimpleMovingAverage to
produce the next value
• The SimpleMovingAverage will have a buffer of 1 position, because unless needed by other indicator
(which would rely on the moving average) there is no need to keep a larger buffer in place.

Note

The most attractive and probably important feature of this mode is that the
amount of memory used remains constant throughout the entire life of a script.

Regardless of the size of the data feed.

This would be of great use if for example connected to a live feed for a long
period of time.

But take into account:

524
backtrader’s documentation Version-1.9.58.122

1. Plotting is not available


2. There are other sources of memory consumption which would accumulate over
time like orders generated by the strategy.
3. This mode can only be used with runonce=False in cerebro. This would also
be compulsory for a live data feed, but in case of simple backtesting this
is slower than runonce=True.

There is for sure a trade off point from which memory management is more
expensive than the step-by-step execution of the backtesting, but this can
only be judged by the end-user of the platform on a case by case basis.

Now the negative levels. These are meant to keep plotting available whilst still
saving a decent amount of memory. First level -1:

$ ./memory-savings.py --save -1
Total memory cells used: 184623

In this case the 1st level of indicators (those declared in the strategy) keep
its full length buffers. But if this indicators rely on others (which is the
case) to do its work, the subobjects will be length-bounded. In this case we have
gone from:

• 506430 memory positions to -> 184623

Over 50% savings.

Note

Of course array.array objects have been traded for collections.deque which are
more expensive in memory terms although faster in operation terms. But the
collection.deque objects are rather small and the savings approach the roughly
counted memory positions used.

Level -2 now, which is meant to also save on the indicators declared at the
strategy level which have been marked as no to be plotted:

$ ./memory-savings.py --save -2
Total memory cells used: 174695

Not much has been saved now. This being because a single indicator has been
tagged as not be plotted: TestInd().plotinfo.plot = False

Let’s see the plotting from this last example:

$ ./memory-savings.py --save -2 --plot


Total memory cells used: 174695
525
backtrader’s documentation Version-1.9.58.122

For the interested reader, the sample script can produce a detailed analysis of
each lines object traversed in the hierarchy of indicators. Running with plotting
enabled (saving at -1):

$ ./memory-savings.py --save -1 --lendetails


-- Evaluating Datas
---- Data 0 Total Cells 34755 - Cells per Line 4965
-- Evaluating Indicators
---- Indicator 1.0 Average Total Cells 30 - Cells per line 30
---- SubIndicators Total Cells 1
---- Indicator 1.1 _LineDelay Total Cells 1 - Cells per line 1
---- SubIndicators Total Cells 1
...
---- Indicator 0.5 TestInd Total Cells 9930 - Cells per line 4965
---- SubIndicators Total Cells 0
-- Evaluating Observers
---- Observer 0 Total Cells 9930 - Cells per Line 4965

526
backtrader’s documentation Version-1.9.58.122

---- Observer 1 Total Cells 9930 - Cells per Line 4965


---- Observer 2 Total Cells 9930 - Cells per Line 4965
Total memory cells used: 184623

The same but with maximum savings (1) enabled:

$ ./memory-savings.py --save 1 --lendetails


-- Evaluating Datas
---- Data 0 Total Cells 266 - Cells per Line 38
-- Evaluating Indicators
---- Indicator 1.0 Average Total Cells 30 - Cells per line 30
---- SubIndicators Total Cells 1
...
---- Indicator 0.5 TestInd Total Cells 2 - Cells per line 1
---- SubIndicators Total Cells 0
-- Evaluating Observers
---- Observer 0 Total Cells 2 - Cells per Line 1
---- Observer 1 Total Cells 2 - Cells per Line 1
---- Observer 2 Total Cells 2 - Cells per Line 1

The 2nd output immediately shows how the lines in the data feed have been capped
to 38 memory positions instead of the 4965 which comprises the full data source
length.

And indicators and observers* have been when possible capped to 1 as seen in the
last lines of the output.

Script Code and Usage

Available as sample in the sources of backtrader. Usage:

$ ./memory-savings.py --help
usage: memory-savings.py [-h] [--data DATA] [--save SAVE] [--datalines]
[--lendetails] [--plot]

Check Memory Savings

optional arguments:
-h, --help show this help message and exit
--data DATA Data to be read in (default: ../../datas/yhoo-1996-2015.txt)
--save SAVE Memory saving level [1, 0, -1, -2] (default: 0)
--datalines Print data lines (default: False)
--lendetails Print individual items memory usage (default: False)
--plot Plot the result (default: False)
527
backtrader’s documentation Version-1.9.58.122

The code:

from __future__ import (absolute_import, division, print_function,


unicode_literals)

import argparse
import sys

import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind
import backtrader.utils.flushfile

class TestInd(bt.Indicator):
lines = ('a', 'b')

def __init__(self):
self.lines.a = b = self.data.close - self.data.high
self.lines.b = btind.SMA(b, period=20)

class St(bt.Strategy):
params = (
('datalines', False),
('lendetails', False),
)

def __init__(self):
btind.SMA()
btind.Stochastic()
btind.RSI()
btind.MACD()
btind.CCI()
TestInd().plotinfo.plot = False

def next(self):
if self.p.datalines:
txt = ','.join(
['%04d' % len(self),
'%04d' % len(self.data0),
self.data.datetime.date(0).isoformat()]
)

528
backtrader’s documentation Version-1.9.58.122

print(txt)

def loglendetails(self, msg):


if self.p.lendetails:
print(msg)

def stop(self):
super(St, self).stop()

tlen = 0
self.loglendetails('-- Evaluating Datas')
for i, data in enumerate(self.datas):
tdata = 0
for line in data.lines:
tdata += len(line.array)
tline = len(line.array)

tlen += tdata
logtxt = '---- Data {} Total Cells {} - Cells per Line {}'
self.loglendetails(logtxt.format(i, tdata, tline))

self.loglendetails('-- Evaluating Indicators')


for i, ind in enumerate(self.getindicators()):
tlen += self.rindicator(ind, i, 0)

self.loglendetails('-- Evaluating Observers')


for i, obs in enumerate(self.getobservers()):
tobs = 0
for line in obs.lines:
tobs += len(line.array)
tline = len(line.array)

tlen += tdata
logtxt = '---- Observer {} Total Cells {} - Cells per Line {}'
self.loglendetails(logtxt.format(i, tobs, tline))

print('Total memory cells used: {}'.format(tlen))

def rindicator(self, ind, i, deep):


tind = 0
for line in ind.lines:
tind += len(line.array)
tline = len(line.array)

529
backtrader’s documentation Version-1.9.58.122

thisind = tind

tsub = 0
for j, sind in enumerate(ind.getindicators()):
tsub += self.rindicator(sind, j, deep + 1)

iname = ind.__class__.__name__.split('.')[-1]

logtxt = '---- Indicator {}.{} {} Total Cells {} - Cells per line {}'
self.loglendetails(logtxt.format(deep, i, iname, tind, tline))
logtxt = '---- SubIndicators Total Cells {}'
self.loglendetails(logtxt.format(deep, i, iname, tsub))

return tind + tsub

def runstrat():
args = parse_args()

cerebro = bt.Cerebro()
data = btfeeds.YahooFinanceCSVData(dataname=args.data)
cerebro.adddata(data)
cerebro.addstrategy(
St, datalines=args.datalines, lendetails=args.lendetails)

cerebro.run(runonce=False, exactbars=args.save)
if args.plot:
cerebro.plot(style='bar')

def parse_args():
parser = argparse.ArgumentParser(
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
description='Check Memory Savings')

parser.add_argument('--data', required=False,
default='../../datas/yhoo-1996-2015.txt',
help='Data to be read in')

parser.add_argument('--save', required=False, type=int, default=0,


help=('Memory saving level [1, 0, -1, -2]'))

parser.add_argument('--datalines', required=False, action='store_true',


help=('Print data lines'))
530
backtrader’s documentation Version-1.9.58.122

parser.add_argument('--lendetails', required=False, action='store_true',


help=('Print individual items memory usage'))

parser.add_argument('--plot', required=False, action='store_true',


help=('Plot the result'))

return parser.parse_args()

if __name__ == '__main__':
runstrat()

DateTime Management

Up until release 1.5.0, backtrader used a direct approach to time management in


that whatever datetime was calculated by data sources was simply used at face
value.

And the same for any user input like in the case of the parameter fromdate (or
sessionstart) which can be given to any data source

The approach was fine given the direct control over frozen data sources for
backtesting. It was easy to assume that the input datetimes had already been
taken care of before they entered the system.

But with 1.5.0, live data sources are supported and this forces to take into
account datetime management. Such management would not be needed if the following
were always true:

• A trader in New York trades the ES-Mini. The time zone for both in US/Eastern (or one of the aliases)
• A trader in Berlin trades the DAX future. In this case for both the CET (or Europe/Berling) timezone applies

The direct input-output datetime approach from above would work, becase the
trader, in Berlin for example, could always do something like this:

class Strategy(bt.Strategy):

def next(self):

# The DAX future opens at 08:00 CET


if self.data.datetime.time() < datetime.time(8, 30):
# don't operate until the market has been running 30 minutes
return #
531
backtrader’s documentation Version-1.9.58.122

The problem with the direct approach surfaces when the same trader in Berlin
decides to trade the ES-Mini. Because the change to from DST happens at different
point in time in the year and this causes the time difference to be out of sync a
couple of weeks during the year. The following wouldn’t always work:

class Strategy(bt.Strategy):

def next(self):

# The SPX opens at 09:30 US/Eastern all year long


# This is most of the year 15:30 CET
# But it is sometimes 16:30 CET or 14:30 CET if a DST switch on-off
# has happened in the USA and not in Europe

# That's why the code below is unreliable

if self.data.datetime.time() < datetime.time(16, 0):


# don't operate until the market has been running 30 minutes
return #

Operation with timezones

To solve the aforementioned situations and still remain compatible with the
direct input-output time approach, backtrader offers the end user the following

Datetime Input

• As a default the platform will not touch the datetime provided by a data
source
o The end-user can override this input by:
Providing a tzinput parameter to the data source. This must be an object compatible
with the datetime.tzinfo interface. Most likely the user will provide a pytz.timezone
instance

With this decision the time used internally by backtrader is considered to


be in UTC-like format, ie:

o If the data source has already stored it in UTC format


o After a conversion through tzinput
o It’s not really UTC but it’s the reference for the user, hence UTC-like

532
backtrader’s documentation Version-1.9.58.122

Datetime output

• If the data feed can automatically determine the timezone for the output,
this will be the default

This makes sense in the case of live-feeds and especially in use cases like
the one in which a trader in Berlin (CET timezone), trades products with
US/Eastern timezone.

Because the trader gets always the right time and in the example above the
opening time remains constant at 09:30 US/Eastern, rather than 15:30 CET
most of the year, but sometimes 16:30 CET and sometimes 14:30 CET.

• If it cannot be determined, then the output will be whatever was determined


during input (the UTC-like) time
• The end user can override and determine the actual timezone for the output
o Providing a tz parameter to the data source. This must be an object compatible with the
datetime.tzinfo interface. Most likely the user will provide a pytz.timezone instance

Note

Input fromt the user like for example the parameters fromdate or sessionstart are
expected to be in sync with the actual tz, be it automatically calculated by the
data source, supplied by the user or left as default (None, which means direct
input-output of datetime)

With all that in mind let’s recall the Berlin trader, trading in US/Eastern:

import pytz

import bt

data = bt.feeds.MyFeed('ES-Mini', tz=pytz.timezone('US/Eastern'))

class Strategy(bt.Strategy):

def next(self):

# This will work all year round.


# The data source will return in the frame of the 'US/Eastern' time
# zone and the user is quoting '10:00' as reference time
# Because in the 'US/Eastern' timezone the SPX index always starts
# trading at 09:30, this will always work

533
backtrader’s documentation Version-1.9.58.122

if self.data.datetime.time() < datetime.time(10, 0):


# don't operate until the market has been running 30 minutes
return #

In the case of a data source which can automatically determine the output
timezone:

import bt

data = bt.feeds.MyFeedAutoTZ('ES-Mini')

class Strategy(bt.Strategy):

def next(self):

# This will work all year round.


# The data source will return in the frame of the 'US/Eastern' time
# zone and the user is quoting '10:00' as reference time
# Because in the 'US/Eastern' timezone the SPX index always starts
# trading at 09:30, this will always work

if self.data.datetime.time() < datetime.time(10, 0):


# don't operate until the market has been running 30 minutes
return #

Even less work than above.

Obviously MyFeed and MyFeedAuto in the example above are just dummy names.

Note

At the time of writing the only data source included in the distribution which
can automatically determine the timezone is the one connecting to Interactive
Brokers

Live Data Feeds and Live Trading

Starting with release 1.5.0 backtrader supports live data and live trading.

534
backtrader’s documentation Version-1.9.58.122

Interactive Brokers

Visual Chart

Oanda

Data Feeds Reference

AbstractDataBase

Lines:

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)

BacktraderCSVData

Parses a self-defined CSV Data used for testing.

535
backtrader’s documentation Version-1.9.58.122

Specific parameters:

• dataname: The filename to parse or a file-like object

Lines:

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)
• headers (True)
• separator (,)

CSVDataBase

Base class for classes implementing CSV DataFeeds

The class takes care of opening the file, reading the lines and tokenizing them.

Subclasses do only need to override:

• _loadline(tokens)

The return value of _loadline (True/False) will be the return value of _load
which has been overriden by this base class

536
backtrader’s documentation Version-1.9.58.122

Lines:

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)
• headers (True)
• separator (,)

Chainer

Class that chains datas

Lines:

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:

537
backtrader’s documentation Version-1.9.58.122

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)

DataClone

Lines:

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)

538
backtrader’s documentation Version-1.9.58.122

DataFiller

This class will fill gaps in the source data using the following information bits
from the underlying data source

• timeframe and compression to dimension the output bars


• sessionstart and sessionend

If a data feed has missing bars in between 10:31 and 10:34 and the timeframe is
minutes, the output will be filled with bars for minutes 10:32 and 10:33 using
the closing price of the last bar (10:31)

Bars can be missinga amongst other things because

Params:

• fill_price (def: None): if None (or evaluates to False),the closing price will be used, else the passed
value (which can be for example ‘NaN’ to have a missing bar in terms of evaluation but present in
terms of time
• fill_vol (def: NaN): used to fill the volume of missing bars
• fill_oi (def: NaN): used to fill the openinterest of missing bars

Lines:

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)

539
backtrader’s documentation Version-1.9.58.122

• tzinput (None)
• qcheck (0.0)
• calendar (None)
• fill_price (None)
• fill_vol (nan)
• fill_oi (nan)

DataFilter

This class filters out bars from a given data source. In addition to the standard
parameters of a DataBase it takes a funcfilter parameter which can be any
callable

Logic:

• funcfilter will be called with the underlying data source

It can be any callable

o Return value True: current data source bar values will used
o Return value False: current data source bar values will discarded

Lines:

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)

540
backtrader’s documentation Version-1.9.58.122

• tzinput (None)
• qcheck (0.0)
• calendar (None)
• funcfilter (None)

GenericCSVData

Parses a CSV file according to the order and field presence defined by the
parameters

Specific parameters (or specific meaning):

• dataname: The filename to parse or a file-like object


• The lines parameters (datetime, open, high ...) take numeric values

A value of -1 indicates absence of that field in the CSV source

• If time is present (parameter time >=0) the source contains separated


fields for date and time, which will be combined
• nullvalue

Value that will be used if a value which should be there is missing (the
CSV field is empty)

• dtformat: Format used to parse the datetime CSV field. See the python
strptime/strftime documentation for the format.

If a numeric value is specified, it will be interpreted as follows

o 1: The value is a Unix timestamp of type int representing the number of seconds since Jan 1st,
1970
o 2: The value is a Unix timestamp of type float

If a callable is passed

o it will accept a string and return a datetime.datetime python instance


• tmformat: Format used to parse the time CSV field if “present” (the
default for the “time” CSV field is not to be present)

Lines:

• close
• low
• high
• open
541
backtrader’s documentation Version-1.9.58.122

• volume
• openinterest
• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)
• headers (True)
• separator (,)
• nullvalue (nan)
• dtformat (%Y-%m-%d %H:%M:%S)
• tmformat (%H:%M:%S)
• datetime (0)
• time (-1)
• open (1)
• high (2)
• low (3)
• close (4)
• volume (5)
• openinterest (6)

IBData

Interactive Brokers Data Feed.

Supports the following contract specifications in parameter dataname:

• TICKER # Stock type and SMART exchange


• TICKER-STK # Stock and SMART exchange
• TICKER-STK-EXCHANGE # Stock
• TICKER-STK-EXCHANGE-CURRENCY # Stock
• TICKER-CFD # CFD and SMART exchange
542
backtrader’s documentation Version-1.9.58.122

• TICKER-CFD-EXCHANGE # CFD
• TICKER-CDF-EXCHANGE-CURRENCY # Stock
• TICKER-IND-EXCHANGE # Index
• TICKER-IND-EXCHANGE-CURRENCY # Index
• TICKER-YYYYMM-EXCHANGE # Future
• TICKER-YYYYMM-EXCHANGE-CURRENCY # Future
• TICKER-YYYYMM-EXCHANGE-CURRENCY-MULT # Future
• TICKER-FUT-EXCHANGE-CURRENCY-YYYYMM-MULT # Future
• TICKER-YYYYMM-EXCHANGE-CURRENCY-STRIKE-RIGHT # FOP
• TICKER-YYYYMM-EXCHANGE-CURRENCY-STRIKE-RIGHT-MULT # FOP
• TICKER-FOP-EXCHANGE-CURRENCY-YYYYMM-STRIKE-RIGHT # FOP
• TICKER-FOP-EXCHANGE-CURRENCY-YYYYMM-STRIKE-RIGHT-MULT # FOP
• CUR1.CUR2-CASH-IDEALPRO # Forex
• TICKER-YYYYMMDD-EXCHANGE-CURRENCY-STRIKE-RIGHT # OPT
• TICKER-YYYYMMDD-EXCHANGE-CURRENCY-STRIKE-RIGHT-MULT # OPT
• TICKER-OPT-EXCHANGE-CURRENCY-YYYYMMDD-STRIKE-RIGHT # OPT
• TICKER-OPT-EXCHANGE-CURRENCY-YYYYMMDD-STRIKE-RIGHT-MULT # OPT

Params:

• sectype (default: STK)

Default value to apply as security type if not provided in the dataname


specification

• exchange (default: SMART)

Default value to apply as exchange if not provided in the dataname


specification

• currency (default: '')

Default value to apply as currency if not provided in the dataname


specification

• historical (default: False)

If set to True the data feed will stop after doing the first download of
data.

The standard data feed parameters fromdate and todate will be used as
reference.

543
backtrader’s documentation Version-1.9.58.122

The data feed will make multiple requests if the requested duration is
larger than the one allowed by IB given the timeframe/compression chosen
for the data.

• what (default: None)

If None the default for different assets types will be used for historical
data requests:

o ‘BID’ for CASH assets


o ‘TRADES’ for any other

Check the IB API docs if another value is wished

• rtbar (default: False)

If True the 5 Seconds Realtime bars provided by Interactive Brokers will be


used as the smalles tick. According to the documentation they correspond to
real-time values (once collated and curated by IB)

If False then the RTVolume prices will be used, which are based on
receiving ticks. In the case of CASH assets (like for example EUR.JPY)
RTVolume will always be used and from it the bid price (industry de-facto
standard with IB according to the literature scattered over the Internet)

Even if set to True, if the data is resampled/kept to a


timeframe/compression below Seconds/5, no real time bars will be used,
because IB doesn’t serve them below that level

• qcheck (default: 0.5)

Time in seconds to wake up if no data is received to give a chance to


resample/replay packets properly and pass notifications up the chain

• backfill_start (default: True)

Perform backfilling at the start. The maximum possible historical data will
be fetched in a single request.

• backfill (default: True)

Perform backfilling after a disconnection/reconnection cycle. The gap


duration will be used to download the smallest possible amount of data

• backfill_from (default: None)

544
backtrader’s documentation Version-1.9.58.122

An additional data source can be passed to do an initial layer of


backfilling. Once the data source is depleted and if requested, backfilling
from IB will take place. This is ideally meant to backfill from already
stored sources like a file on disk, but not limited to.

• latethrough (default: False)

If the data source is resampled/replayed, some ticks may come in too late
for the already delivered resampled/replayed bar. If this is True those
ticks will bet let through in any case.

Check the Resampler documentation to see who to take those ticks into
account.

This can happen especially if timeoffset is set to False in the IBStore


instance and the TWS server time is not in sync with that of the local
computer

• tradename (default: None) Useful for some specific cases like CFD in which
prices are offered by one asset and trading happens in a different onel
o SPY-STK-SMART-USD -> SP500 ETF (will be specified as dataname)
o SPY-CFD-SMART-USD -> which is the corresponding CFD which offers not price tracking but in this
case will be the trading asset (specified as tradename)

The default values in the params are the to allow things like `TICKER, to which
the parameter sectype (default: STK) and exchange (default: SMART) are applied.

Some assets like AAPL need full specification including currency (default: ‘’)
whereas others like TWTR can be simply passed as it is.

• AAPL-STK-SMART-USD would be the full specification for dataname

Or else: IBData as IBData(dataname='AAPL', currency='USD') which uses the


default values (STK and SMART) and overrides the currency to be USD

Lines:

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:
545
backtrader’s documentation Version-1.9.58.122

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.5)
• calendar (None)
• sectype (STK)
• exchange (SMART)
• currency ()
• rtbar (False)
• historical (False)
• what (None)
• useRTH (False)
• backfill_start (True)
• backfill (True)
• backfill_from (None)
• latethrough (False)
• tradename (None)

InfluxDB

Lines:

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
546
backtrader’s documentation Version-1.9.58.122

• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)
• host (127.0.0.1)
• port (8086)
• username (None)
• password (None)
• database (None)
• startdate (None)
• high (high_p)
• low (low_p)
• open (open_p)
• close (close_p)
• volume (volume)
• ointerest (oi)

MT4CSVData

Parses a Metatrader4 History center CSV exported file.

Specific parameters (or specific meaning):

• dataname: The filename to parse or a file-like object


• Uses GenericCSVData and simply modifies the params

Lines:

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:

• dataname (None)

547
backtrader’s documentation Version-1.9.58.122

• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)
• headers (True)
• separator (,)
• nullvalue (nan)
• dtformat (%Y.%m.%d)
• tmformat (%H:%M)
• datetime (0)
• time (1)
• open (2)
• high (3)
• low (4)
• close (5)
• volume (6)
• openinterest (-1)

OandaData

Oanda Data Feed.

Params:

• qcheck (default: 0.5)

Time in seconds to wake up if no data is received to give a chance to


resample/replay packets properly and pass notifications up the chain

• historical (default: False)

If set to True the data feed will stop after doing the first download of
data.

The standard data feed parameters fromdate and todate will be used as
reference.
548
backtrader’s documentation Version-1.9.58.122

The data feed will make multiple requests if the requested duration is
larger than the one allowed by IB given the timeframe/compression chosen
for the data.

• backfill_start (default: True)

Perform backfilling at the start. The maximum possible historical data will
be fetched in a single request.

• backfill (default: True)

Perform backfilling after a disconnection/reconnection cycle. The gap


duration will be used to download the smallest possible amount of data

• backfill_from (default: None)

An additional data source can be passed to do an initial layer of


backfilling. Once the data source is depleted and if requested, backfilling
from IB will take place. This is ideally meant to backfill from already
stored sources like a file on disk, but not limited to.

• bidask (default: True)

If True, then the historical/backfilling requests will request bid/ask


prices from the server

If False, then midpoint will be requested

• useask (default: False)

If True the ask part of the bidask prices will be used instead of the
default use of bid

• includeFirst (default: True)

Influence the delivery of the 1st bar of a historical/backfilling request


by setting the parameter directly to the Oanda API calls

• reconnect (default: True)

Reconnect when network connection is down

• reconnections (default: -1)

Number of times to attempt reconnections: -1 means forever

549
backtrader’s documentation Version-1.9.58.122

• reconntimeout (default: 5.0)

Time in seconds to wait in between reconnection attemps

This data feed supports only this mapping of timeframe and compression, which
comply with the definitions in the OANDA API Developer’s Guid:

(TimeFrame.Seconds, 5): 'S5',


(TimeFrame.Seconds, 10): 'S10',
(TimeFrame.Seconds, 15): 'S15',
(TimeFrame.Seconds, 30): 'S30',
(TimeFrame.Minutes, 1): 'M1',
(TimeFrame.Minutes, 2): 'M3',
(TimeFrame.Minutes, 3): 'M3',
(TimeFrame.Minutes, 4): 'M4',
(TimeFrame.Minutes, 5): 'M5',
(TimeFrame.Minutes, 10): 'M5',
(TimeFrame.Minutes, 15): 'M5',
(TimeFrame.Minutes, 30): 'M5',
(TimeFrame.Minutes, 60): 'H1',
(TimeFrame.Minutes, 120): 'H2',
(TimeFrame.Minutes, 180): 'H3',
(TimeFrame.Minutes, 240): 'H4',
(TimeFrame.Minutes, 360): 'H6',
(TimeFrame.Minutes, 480): 'H8',
(TimeFrame.Days, 1): 'D',
(TimeFrame.Weeks, 1): 'W',
(TimeFrame.Months, 1): 'M',

Any other combination will be rejected

Lines:

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:

• dataname (None)
• name ()

550
backtrader’s documentation Version-1.9.58.122

• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.5)
• calendar (None)
• historical (False)
• backfill_start (True)
• backfill (True)
• backfill_from (None)
• bidask (True)
• useask (False)
• includeFirst (True)
• reconnect (True)
• reconnections (-1)
• reconntimeout (5.0)

PandasData

Uses a Pandas DataFrame as the feed source, using indices into column names
(which can be “numeric”)

This means that all parameters related to lines must have numeric values as
indices into the tuples

Params:

• nocase (default True) case insensitive match of column names

Note:

• The dataname parameter is a Pandas DataFrame


• Values possible for datetime
o None: the index contains the datetime
o -1: no index, autodetect column
o >= 0 or string: specific colum identifier
• For other lines parameters
o None: column not present
o -1: autodetect
551
backtrader’s documentation Version-1.9.58.122

o >= 0 or string: specific colum identifier

Lines:

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)
• nocase (True)
• datetime (None)
• open (-1)
• high (-1)
• low (-1)
• close (-1)
• volume (-1)
• openinterest (-1)

PandasDirectData

Uses a Pandas DataFrame as the feed source, iterating directly over the tuples
returned by “itertuples”.

This means that all parameters related to lines must have numeric values as
indices into the tuples

552
backtrader’s documentation Version-1.9.58.122

Note:

• The dataname parameter is a Pandas DataFrame


• A negative value in any of the parameters for the Data lines indicates it’s not present in the DataFrame it
is

Lines:

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)
• datetime (0)
• open (1)
• high (2)
• low (3)
• close (4)
• volume (5)
• openinterest (6)

Quandl

Executes a direct download of data from Quandl servers for the given time range.

Specific parameters (or specific meaning):


553
backtrader’s documentation Version-1.9.58.122

• dataname

The ticker to download (‘YHOO’ for example)

• baseurl

The server url. Someone might decide to open a Quandl compatible service in
the future.

• proxies

A dict indicating which proxy to go through for the download as in


{‘http’: ‘http://myproxy.com‘} or {‘http’: ‘http://127.0.0.1:8080‘}

• buffered

If True the entire socket connection wil be buffered locally before parsing
starts.

• reverse

Quandl returns the value in descending order (newest first). If this is


True (the default), the request will tell Quandl to return in ascending
(oldest to newest) format

• adjclose

Whether to use the dividend/split adjusted close and adjust all values
according to it.

• apikey

apikey identification in case it may be needed

• dataset

string identifying the dataset to query. Defaults to WIKI

Lines:

• close
• low
• high
• open
• volume
• openinterest
554
backtrader’s documentation Version-1.9.58.122

• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)
• headers (True)
• separator (,)
• reverse (True)
• adjclose (True)
• round (False)
• decimals (2)
• baseurl (https://www.quandl.com/api/v3/datasets)
• proxies ({})
• buffered (True)
• apikey (None)
• dataset (WIKI)

QuandlCSV

Parses pre-downloaded Quandl CSV Data Feeds (or locally generated if they comply
to the Quandl format)

Specific parameters:

• dataname: The filename to parse or a file-like object


• reverse (default: False)

It is assumed that locally stored files have already been reversed during
the download process

• adjclose (default: True)

555
backtrader’s documentation Version-1.9.58.122

Whether to use the dividend/split adjusted close and adjust all values
according to it.

• round (default: False)

Whether to round the values to a specific number of decimals after having


adjusted the close

• decimals (default: 2)

Number of decimals to round to

Lines:

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)
• headers (True)
• separator (,)
• reverse (False)
• adjclose (True)
• round (False)
• decimals (2)

556
backtrader’s documentation Version-1.9.58.122

RollOver

Class that rolls over to the next future when a condition is met

Params:

• checkdate (default: None)

This must be a callable with the following signature:

checkdate(dt, d):

Where:

o dt is a datetime.datetime object
o d is the current data feed for the active future

Expected Return Values:

o True: as long as the callable returns this, a switchover can happen to the next future

If a commodity expires on the 3rd Friday of March, checkdate could return True
for the entire week in which the expiration takes place.

• False: the expiration cannot take place

• checkcondition (default: None)

Note: This will only be called if checkdate has returned True

If None this will evaluate to True (execute roll over) internally

Else this must be a callable with this signature:

checkcondition(d0, d1)

Where:

o d0 is the current data feed for the active future


o d1 is the data feed for the next expiration

Expected Return Values:

o True: roll-over to the next future

557
backtrader’s documentation Version-1.9.58.122

Following with the example from checkdate, this could say that the roll-over can
only happend if the volume from d0 is already less than the volume from d1

• False: the expiration cannot take place

Lines:

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)
• checkdate (None)
• checkcondition (None)

SierraChartCSVData

Parses a SierraChart CSV exported file.

Specific parameters (or specific meaning):

• dataname: The filename to parse or a file-like object


• Uses GenericCSVData and simply modifies the dateformat (dtformat) to

Lines:

558
backtrader’s documentation Version-1.9.58.122

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)
• headers (True)
• separator (,)
• nullvalue (nan)
• dtformat (%Y/%m/%d)
• tmformat (%H:%M:%S)
• datetime (0)
• time (-1)
• open (1)
• high (2)
• low (3)
• close (4)
• volume (5)
• openinterest (6)

VCData

VisualChart Data Feed.

Params:

559
backtrader’s documentation Version-1.9.58.122

• qcheck (default: 0.5) Default timeout for waking up to let a


resampler/replayer that the current bar can be check for due delivery

The value is only used if a resampling/replaying filter has been inserted


in the data

• historical (default: False) If no todate parameter is supplied (defined in


the base class), this will force a historical only download if set to True

If todate is supplied the same effect is achieved

• milliseconds (default: True) The bars constructed by Visual Chart have this
aspect: HH:MM:59.999000

If this parameter is True a millisecond will be added to this time to make


it look like: HH::MM + 1:00.000000

• tradename (default: None) Continous futures cannot be traded but are ideal
for data tracking. If this parameter is supplied it will be the name of the
current future which will be the trading asset. Example:
o 001ES -> ES-Mini continuous supplied as dataname
o ESU16 -> ES-Mini 2016-09. If this is supplied in tradename it will be the trading asset.
• usetimezones (default: True) For most markets the time offset information
provided by Visual Chart allows for datetime to be converted to market time
(backtrader choice for representation)

Some markets are special (096) and need special internal coverage and
timezone support to display in the user expected market time.

If this parameter is set to True importing pytz will be attempted to use


timezones (default)

Disabling it will remove timezone usage (may help if the load is excesive)

Lines:

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:

560
backtrader’s documentation Version-1.9.58.122

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.5)
• calendar (None)
• historical (False)
• millisecond (True)
• tradename (None)
• usetimezones (True)

VChartCSVData

Parses a VisualChart CSV exported file.

Specific parameters (or specific meaning):

• dataname: The filename to parse or a file-like object

Lines:

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)

561
backtrader’s documentation Version-1.9.58.122

• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)
• headers (True)
• separator (,)

VChartData

Support for Visual Chart binary on-disk files for both daily and intradaily
formats.

Note:

• dataname: to file or open file-like object

If a file-like object is passed, the timeframe parameter will be used to


determine which is the actual timeframe.

Else the file extension (.fd for daily and .min for intraday) will be used.

Lines:

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)

562
backtrader’s documentation Version-1.9.58.122

• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)

VChartFile

Support for Visual Chart binary on-disk files for both daily and intradaily
formats.

Note:

• dataname: Market code displayed by Visual Chart. Example: 015ES for EuroStoxx 50 continuous future

Lines:

• close
• low
• high
• open
• volume
• openinterest
• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)

563
backtrader’s documentation Version-1.9.58.122

YahooFinanceCSVData

Parses pre-downloaded Yahoo CSV Data Feeds (or locally generated if they comply
to the Yahoo format)

Specific parameters:

• dataname: The filename to parse or a file-like object


• reverse (default: False)

It is assumed that locally stored files have already been reversed during
the download process

• adjclose (default: True)

Whether to use the dividend/split adjusted close and adjust all values
according to it.

• round (default: True)

Whether to round the values to a specific number of decimals after having


adjusted the close

• decimals (default: 2)

Number of decimals to round to

• version (default: v7)

In May-2017 Yahoo discontinued the original ichart API for downloads and
moved to a new API, which contains a v7 string. This is the default
version.

Set it to the empty string to get the original behavior

• swapcloses (default: False)

If True the allegedly adjusted close and non-adjusted close will be


swapped. The downloads with the new v7 API show at random times the closing
prices swapped. There is no known pattern

Lines:

• close
• low
564
backtrader’s documentation Version-1.9.58.122

• high
• open
• volume
• openinterest
• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)
• headers (True)
• separator (,)
• reverse (False)
• adjclose (True)
• round (True)
• decimals (2)
• version (v7)
• swapcloses (False)

YahooFinanceData

Executes a direct download of data from Yahoo servers for the given time range.

Specific parameters (or specific meaning):

• dataname

The ticker to download (‘YHOO’ for Yahoo own stock quotes)

• baseurl

The server url. Someone might decide to open a Yahoo compatible service in
the future.
565
backtrader’s documentation Version-1.9.58.122

• proxies

A dict indicating which proxy to go through for the download as in


{‘http’: ‘http://myproxy.com‘} or {‘http’: ‘http://127.0.0.1:8080‘}

• period

The timeframe to download data in. Pass ‘w’ for weekly and ‘m’ for
monthly.

• buffered

If True the entire socket connection wil be buffered locally before parsing
starts.

• reverse

Yahoo returns the data with last dates first (against all industry
standards) and it must be reversed for it to work. Should this Yahoo
standard change, the parameter is available.

• adjclose

Whether to use the dividend/split adjusted close and adjust all values
according to it.

• urlhist

The url of the historical quotes in Yahoo Finance used to gather a crumb
authorization cookie for the download

• urldown

The url of the actual download server

• retries

Number of times (each) to try to get a crumb cookie and download the data

Lines:

• close
• low
• high
• open
• volume
566
backtrader’s documentation Version-1.9.58.122

• openinterest
• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)
• headers (True)
• separator (,)
• reverse (True)
• adjclose (True)
• round (True)
• decimals (2)
• version (v7)
• swapcloses (False)
• baseurl (http://ichart.yahoo.com/table.csv?)
• proxies ({})
• period (d)
• buffered (True)
• urlhist (https://finance.yahoo.com/quote/{}/history)
• urldown (https://query1.finance.yahoo.com/v7/finance/download)
• retries (3)

YahooLegacyCSV

This is intended to load files which were downloaded before Yahoo discontinued
the original service in May-2017

Lines:

• close
• low
• high
567
backtrader’s documentation Version-1.9.58.122

• open
• volume
• openinterest
• datetime

Params:

• dataname (None)
• name ()
• compression (1)
• timeframe (5)
• fromdate (None)
• todate (None)
• sessionstart (None)
• sessionend (None)
• filters ([])
• tz (None)
• tzinput (None)
• qcheck (0.0)
• calendar (None)
• headers (True)
• separator (,)
• reverse (False)
• adjclose (True)
• round (True)
• decimals (2)
• version ()
• swapcloses (False)

Yahoo Data Feed Notes

In May 2017 Yahoo discontinued the existing API for historical data downloads in
csv format.

A new API (here named v7) was quickly standardized and has been implemented.

This also brought a change to the actual CSV download format.

Using the v7 API/format

Starting with version 1.9.49.116 this is the default behavior. Choose simply from
568
backtrader’s documentation Version-1.9.58.122

• YahooFinanceData for online downloads


• YahooFinanceCSVData for offline downloaded files

Using the legacy API/format

To use the old API/format

1. Instantiate the online Yahoo data feed as:


2. data = bt.feeds.YahooFinanceData(
3. ...
4. version='',
5. ...
6. )

of the offline Yahoo data feed as:

data = bt.feeds.YahooFinanceCSVData(
...
version='',
...
)

It might be that the online service comes back (the service was
discontinued without any announcement ... it might as well come back)

or

2. Only for Offline files downloaded before the change happened, the following
can also be done:
3. data = bt.feeds.YahooLegacyCSV(
4. ...
5. ...
6. )

The new YahooLegacyCSV simply automates using version=''

Indicator Reference

AccelerationDecelerationOscillator

Alias:

• AccDeOsc
569
backtrader’s documentation Version-1.9.58.122

Acceleration/Deceleration Technical Indicator (AC) measures acceleration and


deceleration of the current driving force. This indicator will change direction
before any changes in the driving force, which, it its turn, will change its
direction before the price.

Formula:

• AcdDecOsc = AwesomeOscillator - SMA(AwesomeOscillator, period)

See:

• https://www.metatrader5.com/en/terminal/help/indicators/bw_indicators/ao
• https://www.ifcmarkets.com/en/ntx-indicators/ntx-indicators-accelerator-decelerator-oscillator

Lines:

• accde

Params:

• period (5)
• movav (SMA)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• accde: - width (1.0) - alpha (0.5) - _method (bar)

570
backtrader’s documentation Version-1.9.58.122

Accum

Alias:

• CumSum, CumulativeSum

Cummulative sum of the data values

Formula:

• accum += data

Lines:

• accum

Params:

• seed (0.0)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• accum:

571
backtrader’s documentation Version-1.9.58.122

AdaptiveMovingAverage

Alias:

• KAMA, MovingAverageAdaptive

Defined by Perry Kaufman in his book “Smarter Trading”.

It is A Moving Average with a continuosly scaled smoothing factor by taking into


account market direction and volatility. The smoothing factor is calculated from
2 ExponetialMovingAverages smoothing factors, a fast one and slow one.

If the market trends the value will tend to the fast ema smoothing period. If the
market doesn’t trend it will move towards the slow EMA smoothing period.

It is a subclass of SmoothingMovingAverage, overriding once to account for the


live nature of the smoothing factor

Formula:

• direction = close - close_period


• volatility = sumN(abs(close - close_n), period)
• effiency_ratio = abs(direction / volatility)
• fast = 2 / (fast_period + 1)
• slow = 2 / (slow_period + 1)
• smfactor = squared(efficienty_ratio * (fast - slow) + slow)
• smfactor1 = 1.0 - smfactor
• The initial seed value is a SimpleMovingAverage

See also:

• http://fxcodebase.com/wiki/index.php/Kaufman’s_Adaptive_Moving_Average_(KAMA)
• http://www.metatrader5.com/en/terminal/help/analytics/indicators/trend_indicators/ama
• http://help.cqg.com/cqgic/default.htm#!Documents/adaptivemovingaverag2.htm

Lines:

• kama

Params:

• period (30)
• fast (2)
• slow (30)

572
backtrader’s documentation Version-1.9.58.122

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• kama:

AdaptiveMovingAverageEnvelope

Alias:

• KAMAEnvelope, MovingAverageAdaptiveEnvelope

AdaptiveMovingAverage and envelope bands separated “perc” from it

Formula:

• kama (from AdaptiveMovingAverage)


• top = kama * (1 + perc)
• bot = kama * (1 - perc)

See also:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_average_
envelopes

Lines:

• kama
• top
573
backtrader’s documentation Version-1.9.58.122

• bot

Params:

• period (30)
• fast (2)
• slow (30)
• perc (2.5)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• kama:
• top: - _samecolor (True)
• bot: - _samecolor (True)

AdaptiveMovingAverageOscillator

Alias:

• AdaptiveMovingAverageOsc, KAMAOscillator, KAMAOsc, MovingAverageAdaptiveOscillator,


MovingAverageAdaptiveOsc

Oscillation of a AdaptiveMovingAverage around its data

Lines:

• kama
574
backtrader’s documentation Version-1.9.58.122

Params:

• period (30)
• fast (2)
• slow (30)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• kama:
• _0: - _name (osc)

AllN

Has a value of True (stored as 1.0 in the lines) if all of the values in the
period evaluates to non-zero (ie: True)

Uses the built-in all for the calculation

Formula:

• alln = all(data, period)

Lines:

• alln

Params:
575
backtrader’s documentation Version-1.9.58.122

• period (1)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• alln:

AnyN

Has a value of True (stored as 1.0 in the lines) if any of the values in the
period evaluates to non-zero (ie: True)

Uses the built-in any for the calculation

Formula:

• anyn = any(data, period)

Lines:

• anyn

Params:

• period (1)

PlotInfo:

576
backtrader’s documentation Version-1.9.58.122

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• anyn:

ApplyN

Calculates func for a given period

Formula:

• line = func(data, period)

Lines:

• apply

Params:

• period (1)
• func (None)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
577
backtrader’s documentation Version-1.9.58.122

• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• apply:

AroonDown

This is the AroonDown from the indicator AroonUpDown developed by Tushar Chande
in 1995.

Formula:

• down = 100 * (period - distance to lowest low) / period

Note:

The lines oscillate between 0 and 100. That means that the “distance” to
the last highest or lowest must go from 0 to period so that the formula can
yield 0 and 100.

Hence the lookback period is period + 1, because the current bar is also
taken into account. And therefore this indicator needs an effective
lookback period of period + 1.

See:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:aroon

Lines:

• aroondown

Params:

• period (14)
• upperband (70)
578
backtrader’s documentation Version-1.9.58.122

• lowerband (30)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([0, 100])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.05)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• aroondown:

AroonOscillator

Alias:

• AroonOsc

It is a variation of the AroonUpDown indicator which shows the current difference


between the AroonUp and AroonDown value, trying to present a visualization which
indicates which is stronger (greater than 0 -> AroonUp and less than 0 ->
AroonDown)

Formula:

• aroonosc = aroonup - aroondown

See:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:aroon

Lines:

579
backtrader’s documentation Version-1.9.58.122

• aroonosc

Params:

• period (14)
• upperband (70)
• lowerband (30)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([0, 100])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.05)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• aroonosc:

AroonUp

This is the AroonUp from the indicator AroonUpDown developed by Tushar Chande in
1995.

Formula:

• up = 100 * (period - distance to highest high) / period

Note:

The lines oscillate between 0 and 100. That means that the “distance” to
the last highest or lowest must go from 0 to period so that the formula can
yield 0 and 100.

580
backtrader’s documentation Version-1.9.58.122

Hence the lookback period is period + 1, because the current bar is also
taken into account. And therefore this indicator needs an effective
lookback period of period + 1.

See:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:aroon

Lines:

• aroonup

Params:

• period (14)
• upperband (70)
• lowerband (30)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([0, 100])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.05)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• aroonup:

AroonUpDown

Alias:

• AroonIndicator
581
backtrader’s documentation Version-1.9.58.122

Developed by Tushar Chande in 1995.

It tries to determine if a trend exists or not by calculating how far away within
a given period the last highs/lows are (AroonUp/AroonDown)

Formula:

• up = 100 * (period - distance to highest high) / period


• down = 100 * (period - distance to lowest low) / period

Note:

The lines oscillate between 0 and 100. That means that the “distance” to
the last highest or lowest must go from 0 to period so that the formula can
yield 0 and 100.

Hence the lookback period is period + 1, because the current bar is also
taken into account. And therefore this indicator needs an effective
lookback period of period + 1.

See:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:aroon

Lines:

• aroonup
• aroondown

Params:

• period (14)
• upperband (70)
• lowerband (30)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([0, 100])
582
backtrader’s documentation Version-1.9.58.122

• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.05)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• aroonup:
• aroondown:

AroonUpDownOscillator

Alias:

• AroonUpDownOsc

Presents together the indicators AroonUpDown and AroonOsc

Formula:
(None, uses the aforementioned indicators)
See:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:aroon

Lines:

• aroonup
• aroondown
• aroonosc

Params:

• period (14)
• upperband (70)
• lowerband (30)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
583
backtrader’s documentation Version-1.9.58.122

• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([0, 100])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.05)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• aroonup:
• aroondown:
• aroonosc:

Average

Alias:

• ArithmeticMean, Mean

Averages a given data arithmetically over a period

Formula:

• av = data(period) / period

See also:

• https://en.wikipedia.org/wiki/Arithmetic_mean

Lines:

• av

Params:

• period (1)

PlotInfo:

• plot (True)
• legendloc (None)
584
backtrader’s documentation Version-1.9.58.122

• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• av:

AverageDirectionalMovementIndex

Alias:

• ADX

Defined by J. Welles Wilder, Jr. in 1978 in his book “New Concepts in Technical
Trading Systems”.

Intended to measure trend strength

This indicator only shows ADX:

• Use PlusDirectionalIndicator (PlusDI) to get +DI


• Use MinusDirectionalIndicator (MinusDI) to get -DI
• Use Directional Indicator (DI) to get +DI, -DI
• Use AverageDirectionalIndexRating (ADXR) to get ADX, ADXR
• Use DirectionalMovementIndex (DMI) to get ADX, +DI, -DI
• Use DirectionalMovement (DM) to get ADX, ADXR, +DI, -DI

Formula:

• upmove = high - high(-1)


• downmove = low(-1) - low
• +dm = upmove if upmove > downmove and upmove > 0 else 0
• -dm = downmove if downmove > upmove and downmove > 0 else 0

585
backtrader’s documentation Version-1.9.58.122

• +di = 100 * MovingAverage(+dm, period) / atr(period)


• -di = 100 * MovingAverage(-dm, period) / atr(period)
• dx = 100 * abs(+di - -di) / (+di + -di)
• adx = MovingAverage(dx, period)

The moving average used is the one originally defined by Wilder, the
SmoothedMovingAverage

See:

• https://en.wikipedia.org/wiki/Average_directional_movement_index

Lines:

• adx

Params:

• period (14)
• movav (SmoothedMovingAverage)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• minusDI: - _name (-DI)


• plusDI: - _name (+DI)
• adx: - _name (ADX)

586
backtrader’s documentation Version-1.9.58.122

AverageDirectionalMovementIndexRating

Alias:

• ADXR

Defined by J. Welles Wilder, Jr. in 1978 in his book “New Concepts in Technical
Trading Systems”.

Intended to measure trend strength.

ADXR is the average of ADX with a value period bars ago

This indicator shows the ADX and ADXR:

• Use PlusDirectionalIndicator (PlusDI) to get +DI


• Use MinusDirectionalIndicator (MinusDI) to get -DI
• Use Directional Indicator (DI) to get +DI, -DI
• Use AverageDirectionalIndex (ADX) to get ADX
• Use DirectionalMovementIndex (DMI) to get ADX, +DI, -DI
• Use DirectionalMovement (DM) to get ADX, ADXR, +DI, -DI

Formula:

• upmove = high - high(-1)


• downmove = low(-1) - low
• +dm = upmove if upmove > downmove and upmove > 0 else 0
• -dm = downmove if downmove > upmove and downmove > 0 else 0
• +di = 100 * MovingAverage(+dm, period) / atr(period)
• -di = 100 * MovingAverage(-dm, period) / atr(period)
• dx = 100 * abs(+di - -di) / (+di + -di)
• adx = MovingAverage(dx, period)
• adxr = (adx + adx(-period)) / 2

The moving average used is the one originally defined by Wilder, the
SmoothedMovingAverage

See:

• https://en.wikipedia.org/wiki/Average_directional_movement_index

Lines:

• adx
• adxr
587
backtrader’s documentation Version-1.9.58.122

Params:

• period (14)
• movav (SmoothedMovingAverage)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• minusDI: - _name (-DI)


• plusDI: - _name (+DI)
• adx: - _name (ADX)
• adxr: - _name (ADXR)

AverageTrueRange

Alias:

• ATR

Defined by J. Welles Wilder, Jr. in 1978 in his book “New Concepts in Technical
Trading Systems”.

The idea is to take the close into account to calculate the range if it yields a
larger range than the daily range (High - Low)

Formula:

• SmoothedMovingAverage(TrueRange, period)
588
backtrader’s documentation Version-1.9.58.122

See:

• http://en.wikipedia.org/wiki/Average_true_range

Lines:

• atr

Params:

• period (14)
• movav (SmoothedMovingAverage)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• atr:

AwesomeOscillator

Alias:

• AwesomeOsc, AO

Awesome Oscillator (AO) is a momentum indicator reflecting the precise changes in


the market driving force which helps to identify the trend’s strength up to the
points of formation and reversal.

589
backtrader’s documentation Version-1.9.58.122

Formula:

• median price = (high + low) / 2


• AO = SMA(median price, 5)- SMA(median price, 34)

See:

• https://www.metatrader5.com/en/terminal/help/indicators/bw_indicators/awesome
• https://www.ifcmarkets.com/en/ntx-indicators/awesome-oscillator

Lines:

• ao

Params:

• fast (5)
• slow (34)
• movav (SMA)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• ao: - width (1.0) - alpha (0.5) - _method (bar)

590
backtrader’s documentation Version-1.9.58.122

BaseApplyN

Base class for ApplyN and others which may take a func as a parameter but want to
define the lines in the indicator.

Calculates func for a given period where func is given as a parameter, aka named
argument or kwarg

Formula:

• lines[0] = func(data, period)

Any extra lines defined beyond the first (index 0) are not calculated

Params:

• period (1)
• func (None)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

BollingerBands

Alias:

• BBands

591
backtrader’s documentation Version-1.9.58.122

Defined by John Bollinger in the 80s. It measures volatility by defining upper


and lower bands at distance x standard deviations

Formula:

• midband = SimpleMovingAverage(close, period)


• topband = midband + devfactor * StandardDeviation(data, period)
• botband = midband - devfactor * StandardDeviation(data, period)

See:

• http://en.wikipedia.org/wiki/Bollinger_Bands

Lines:

• mid
• top
• bot

Params:

• period (20)
• devfactor (2.0)
• movav (MovingAverageSimple)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• top: - _samecolor (True)


592
backtrader’s documentation Version-1.9.58.122

• bot: - _samecolor (True)


• mid: - ls (–)

BollingerBandsPct

Extends the Bollinger Bands with a Percentage line

Lines:

• mid
• top
• bot
• pctb

Params:

• period (20)
• devfactor (2.0)
• movav (MovingAverageSimple)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• top: - _samecolor (True)


• bot: - _samecolor (True)
• mid: - ls (–)
• pctb: - _name (%B)

593
backtrader’s documentation Version-1.9.58.122

CointN

Calculates the score (coint_t) and pvalue for a given period for the data feeds

Uses pandas and statsmodels (for coint)

Lines:

• score
• pvalue

Params:

• period (10)
• regression (c)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• score:
• pvalue:

CommodityChannelIndex

Alias:

• CCI
594
backtrader’s documentation Version-1.9.58.122

Introduced by Donald Lambert in 1980 to measure variations of the “typical


price” (see below) from its mean to identify extremes and reversals

Formula:

• tp = typical_price = (high + low + close) / 3


• tpmean = MovingAverage(tp, period)
• deviation = tp - tpmean
• meandev = MeanDeviation(tp)
• cci = deviation / (meandeviation * factor)

See:

• https://en.wikipedia.org/wiki/Commodity_channel_index

Lines:

• cci

Params:

• period (20)
• factor (0.015)
• movav (MovingAverageSimple)
• upperband (100.0)
• lowerband (-100.0)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

595
backtrader’s documentation Version-1.9.58.122

PlotLines:

• cci:

CrossDown

This indicator gives a signal if the 1st provided data crosses over the 2nd
indicator upwards

It does need to look into the current time index (0) and the previous time index
(-1) of both the 1t and 2nd data

This indicator is not automatically plotted

Formula:

• upcross = data0(-1) < data1(-1) and data0(0) > data1(0)

Lines:

• cross

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([0.0, 1.0])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.05)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• cross:

596
backtrader’s documentation Version-1.9.58.122

CrossOver

This indicator gives a signal if the provided datas (2) cross up or down.

• 1.0 if the 1st data crosses the 2nd data upwards


• -1.0 if the 1st data crosses the 2nd data downwards

It does need to look into the current time index (0) and the previous time index
(-1) of both the 1t and 2nd data

This indicator is not automatically plotted

Formula:

• upcross = data0(-1) < data1(-1) and data0(0) > data1(0)


• downcross = data0(-1) > data1(-1) and data0(0) < data1(0)
• crossover = upcross - downcross

Lines:

• crossover

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([-1.0, 1.0])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.05)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• crossover:

597
backtrader’s documentation Version-1.9.58.122

CrossUp

This indicator gives a signal if the 1st provided data crosses over the 2nd
indicator upwards

It does need to look into the current time index (0) and the previous time index
(-1) of both the 1t and 2nd data

This indicator is not automatically plotted

Formula:

• upcross = data0(-1) < data1(-1) and data0(0) > data1(0)

Lines:

• cross

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([0.0, 1.0])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.05)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• cross:

DV2

RSI(2) alternative Developed by David Varadi of


http://cssanalytics.wordpress.com/
598
backtrader’s documentation Version-1.9.58.122

This seems to be the Bounded version.

See also:

• http://web.archive.org/web/20131216100741/http://quantingdutchman.wordpress.com/2010/08/06/dv
2-indicator-for-amibroker/

Lines:

• dv2

Params:

• period (252)
• maperiod (2)
• _movav (SMA)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• dv2:

DemarkPivotPoint

Defines a level of significance by taking into account the average of price bar
componentes of the past period of a larget timeframe. For example when operating
with days, the values are taking from the already “past” month fixed prices.

599
backtrader’s documentation Version-1.9.58.122

Example of using this indicator:

data = btfeeds.ADataFeed(dataname=x, timeframe=bt.TimeFrame.Days) cerebro.adddata(data)


cerebro.resampledata(data, timeframe=bt.TimeFrame.Months)

In the __init__ method of the strategy:

pivotindicator = btind.DemarkPivotPoiont(self.data1) # the resampled data

The indicator will try to automatically plo to the non-resampled data. To disable
this behavior use the following during construction:

• _autoplot=False

Note:

The example shows days and months, but any combination of timeframes can be used. See the literature for
recommended combinations
Formula:

• if close < open x = high + (2 x low) + close


• if close > open x = (2 x high) + low + close
• if Close == open x = high + low + (2 x close)
• p=x/4
• support1 = x / 2 - high
• resistance1 = x / 2 - low

See:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:pivot_points

Lines:

• p
• s1
• r1

Params:

• open (False)
• close (False)
• _autoplot (True)
• level1 (0.382)
• level2 (0.618)
• level3 (1.0)

600
backtrader’s documentation Version-1.9.58.122

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• p:
• s1:
• r1:

DetrendedPriceOscillator

Alias:

• DPO

Defined by Joe DiNapoli in his book “Trading with DiNapoli levels”

It measures the price variations against a Moving Average (the trend) and
therefore removes the “trend” factor from the price.

Formula:

• movav = MovingAverage(close, period)


• dpo = close - movav(shifted period / 2 + 1)

See:

• http://en.wikipedia.org/wiki/Detrended_price_oscillator

Lines:
601
backtrader’s documentation Version-1.9.58.122

• dpo

Params:

• period (20)
• movav (MovingAverageSimple)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([0.0])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• dpo:

DicksonMovingAverage

Alias:

• DMA, DicksonMA

By Nathan Dickson

The Dickson Moving Average combines the ZeroLagIndicator (aka ErrorCorrecting or


EC) by Ehlers, and the HullMovingAverage to try to deliver a result close to that
of the Jurik Moving Averages

Formula:

• ec = ZeroLagIndicator(period, gainlimit)
• hma = HullMovingAverage(hperiod)
602
backtrader’s documentation Version-1.9.58.122

• dma = (ec + hma) / 2


• The default moving average for the ZeroLagIndicator is EMA, but can
be changed with the parameter _movav

Note

the passed moving average must calculate alpha (and 1 - alpha) and
make them available as attributes alpha and alpha1

• The 2nd moving averag can be changed from Hull to anything else with
the param _hma

See also:

• https://www.reddit.com/r/algotrading/comments/4xj3vh/dickson_moving_average

Lines:

• dma

Params:

• period (30)
• gainlimit (50)
• hperiod (7)
• _movav (EMA)
• _hma (HMA)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

603
backtrader’s documentation Version-1.9.58.122

PlotLines:

• dma:

DicksonMovingAverageEnvelope

Alias:

• DMAEnvelope, DicksonMAEnvelope

DicksonMovingAverage and envelope bands separated “perc” from it

Formula:

• dma (from DicksonMovingAverage)


• top = dma * (1 + perc)
• bot = dma * (1 - perc)

See also:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_average_
envelopes

Lines:

• dma
• top
• bot

Params:

• period (30)
• gainlimit (50)
• hperiod (7)
• _movav (EMA)
• _hma (HMA)
• perc (2.5)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
604
backtrader’s documentation Version-1.9.58.122

• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• dma:
• top: - _samecolor (True)
• bot: - _samecolor (True)

DicksonMovingAverageOscillator

Alias:

• DicksonMovingAverageOsc, DMAOscillator, DMAOsc, DicksonMAOscillator, DicksonMAOsc

Oscillation of a DicksonMovingAverage around its data

Lines:

• dma

Params:

• period (30)
• gainlimit (50)
• hperiod (7)
• _movav (EMA)
• _hma (HMA)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
605
backtrader’s documentation Version-1.9.58.122

• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• dma:
• _0: - _name (osc)

DirectionalIndicator

Alias:

• DI

Defined by J. Welles Wilder, Jr. in 1978 in his book “New Concepts in Technical
Trading Systems”.

Intended to measure trend strength

This indicator shows +DI, -DI:

• Use PlusDirectionalIndicator (PlusDI) to get +DI


• Use MinusDirectionalIndicator (MinusDI) to get -DI
• Use AverageDirectionalIndex (ADX) to get ADX
• Use AverageDirectionalIndexRating (ADXR) to get ADX, ADXR
• Use DirectionalMovementIndex (DMI) to get ADX, +DI, -DI
• Use DirectionalMovement (DM) to get ADX, ADXR, +DI, -DI

Formula:

• upmove = high - high(-1)


• downmove = low(-1) - low
• +dm = upmove if upmove > downmove and upmove > 0 else 0
• -dm = downmove if downmove > upmove and downmove > 0 else 0
• +di = 100 * MovingAverage(+dm, period) / atr(period)
• -di = 100 * MovingAverage(-dm, period) / atr(period)

606
backtrader’s documentation Version-1.9.58.122

The moving average used is the one originally defined by Wilder, the
SmoothedMovingAverage

See:

• https://en.wikipedia.org/wiki/Average_directional_movement_index

Lines:

• plusDI
• minusDI

Params:

• period (14)
• movav (SmoothedMovingAverage)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• minusDI:
• plusDI:

DirectionalMovement

Alias:

• DM
607
backtrader’s documentation Version-1.9.58.122

Defined by J. Welles Wilder, Jr. in 1978 in his book “New Concepts in Technical
Trading Systems”.

Intended to measure trend strength

This indicator shows ADX, ADXR, +DI, -DI.

• Use PlusDirectionalIndicator (PlusDI) to get +DI


• Use MinusDirectionalIndicator (MinusDI) to get -DI
• Use Directional Indicator (DI) to get +DI, -DI
• Use AverageDirectionalIndex (ADX) to get ADX
• Use AverageDirectionalIndexRating (ADXR) to get ADX, ADXR
• Use DirectionalMovementIndex (DMI) to get ADX, +DI, -DI

Formula:

• upmove = high - high(-1)


• downmove = low(-1) - low
• +dm = upmove if upmove > downmove and upmove > 0 else 0
• -dm = downmove if downmove > upmove and downmove > 0 else 0
• +di = 100 * MovingAverage(+dm, period) / atr(period)
• -di = 100 * MovingAverage(-dm, period) / atr(period)
• dx = 100 * abs(+di - -di) / (+di + -di)
• adx = MovingAverage(dx, period)

The moving average used is the one originally defined by Wilder, the
SmoothedMovingAverage

See:

• https://en.wikipedia.org/wiki/Average_directional_movement_index

Lines:

• adx
• adxr
• plusDI
• minusDI

Params:

• period (14)
• movav (SmoothedMovingAverage)

PlotInfo:

608
backtrader’s documentation Version-1.9.58.122

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• minusDI:
• plusDI:
• adx: - _name (ADX)
• adxr: - _name (ADXR)

DirectionalMovementIndex

Alias:

• DMI

Defined by J. Welles Wilder, Jr. in 1978 in his book “New Concepts in Technical
Trading Systems”.

Intended to measure trend strength

This indicator shows the ADX, +DI, -DI:

• Use PlusDirectionalIndicator (PlusDI) to get +DI


• Use MinusDirectionalIndicator (MinusDI) to get -DI
• Use Directional Indicator (DI) to get +DI, -DI
• Use AverageDirectionalIndex (ADX) to get ADX
• Use AverageDirectionalIndexRating (ADXRating) to get ADX, ADXR
• Use DirectionalMovement (DM) to get ADX, ADXR, +DI, -DI

Formula:

609
backtrader’s documentation Version-1.9.58.122

• upmove = high - high(-1)


• downmove = low(-1) - low
• +dm = upmove if upmove > downmove and upmove > 0 else 0
• -dm = downmove if downmove > upmove and downmove > 0 else 0
• +di = 100 * MovingAverage(+dm, period) / atr(period)
• -di = 100 * MovingAverage(-dm, period) / atr(period)
• dx = 100 * abs(+di - -di) / (+di + -di)
• adx = MovingAverage(dx, period)

The moving average used is the one originally defined by Wilder, the
SmoothedMovingAverage

See:

• https://en.wikipedia.org/wiki/Average_directional_movement_index

Lines:

• adx
• plusDI
• minusDI

Params:

• period (14)
• movav (SmoothedMovingAverage)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

610
backtrader’s documentation Version-1.9.58.122

PlotLines:

• minusDI:
• plusDI:
• adx: - _name (ADX)

DoubleExponentialMovingAverage

Alias:

• DEMA, MovingAverageDoubleExponential

DEMA was first time introduced in 1994, in the article “Smoothing Data with
Faster Moving Averages” by Patrick G. Mulloy in “Technical Analysis of Stocks &
Commodities” magazine.

It attempts to reduce the inherent lag associated to Moving Averages

Formula:

• dema = (2.0 - ema(data, period) - ema(ema(data, period), period)

See:
(None)
Lines:

• dema

Params:

• period (30)
• _movav (EMA)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
611
backtrader’s documentation Version-1.9.58.122

• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• dema:

DoubleExponentialMovingAverageEnvelope

Alias:

• DEMAEnvelope, MovingAverageDoubleExponentialEnvelope

DoubleExponentialMovingAverage and envelope bands separated “perc” from it

Formula:

• dema (from DoubleExponentialMovingAverage)


• top = dema * (1 + perc)
• bot = dema * (1 - perc)

See also:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_average_
envelopes

Lines:

• dema
• top
• bot

Params:

• period (30)
• _movav (EMA)
• perc (2.5)

PlotInfo:

• plot (True)
• legendloc (None)
612
backtrader’s documentation Version-1.9.58.122

• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• dema:
• top: - _samecolor (True)
• bot: - _samecolor (True)

DoubleExponentialMovingAverageOscillator

Alias:

• DoubleExponentialMovingAverageOsc, DEMAOscillator, DEMAOsc,


MovingAverageDoubleExponentialOscillator, MovingAverageDoubleExponentialOsc

Oscillation of a DoubleExponentialMovingAverage around its data

Lines:

• dema

Params:

• period (30)
• _movav (EMA)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
613
backtrader’s documentation Version-1.9.58.122

• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• dema:
• _0: - _name (osc)

DownDay

Defined by J. Welles Wilder, Jr. in 1978 in his book “New Concepts in Technical
Trading Systems” for the RSI

Recods days which have been “down”, i.e.: the close price has been lower than
the day before.

Formula:

• downday = max(close_prev - close, 0)

See:

• http://en.wikipedia.org/wiki/Relative_strength_index

Lines:

• downday

Params:

• period (1)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
614
backtrader’s documentation Version-1.9.58.122

• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• downday:

DownDayBool

Defined by J. Welles Wilder, Jr. in 1978 in his book “New Concepts in Technical
Trading Systems” for the RSI

Recods days which have been “down”, i.e.: the close price has been lower than
the day before.

Note:

• This version returns a bool rather than the difference

Formula:

• downday = close_prev > close

See:

• http://en.wikipedia.org/wiki/Relative_strength_index

Lines:

• downday

Params:

• period (1)

615
backtrader’s documentation Version-1.9.58.122

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• downday:

DownMove

Defined by J. Welles Wilder, Jr. in 1978 in his book “New Concepts in Technical
Trading Systems” as part of the Directional Move System to calculate Directional
Indicators.

Positive if the given data has moved lower than the previous day

Formula:

• downmove = data(-1) - data

See:

• https://en.wikipedia.org/wiki/Average_directional_movement_index

Lines:

• downmove

PlotInfo:

• plot (True)
616
backtrader’s documentation Version-1.9.58.122

• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• downmove:

Envelope

It creates envelopes bands separated from the source data by a given percentage

Formula:

• src = datasource
• top = src * (1 + perc)
• bot = src * (1 - perc)

See also:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_average_
envelopes

Lines:

• src
• top
• bot

Params:

• perc (2.5)

PlotInfo:
617
backtrader’s documentation Version-1.9.58.122

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• src: - _plotskip (True)


• top: - _samecolor (True)
• bot: - _samecolor (True)

ExponentialMovingAverage

Alias:

• EMA, MovingAverageExponential

A Moving Average that smoothes data exponentially over time.

It is a subclass of SmoothingMovingAverage.

• self.smfactor -> 2 / (1 + period)


• self.smfactor1 -> 1 - self.smfactor

Formula:

• movav = prev * (1.0 - smoothfactor) + newdata * smoothfactor

See also:

• http://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average

Lines:

618
backtrader’s documentation Version-1.9.58.122

• ema

Params:

• period (30)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• ema:

ExponentialMovingAverageEnvelope

Alias:

• EMAEnvelope, MovingAverageExponentialEnvelope

ExponentialMovingAverage and envelope bands separated “perc” from it

Formula:

• ema (from ExponentialMovingAverage)


• top = ema * (1 + perc)
• bot = ema * (1 - perc)

See also:

619
backtrader’s documentation Version-1.9.58.122

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_average_
envelopes

Lines:

• ema
• top
• bot

Params:

• period (30)
• perc (2.5)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• ema:
• top: - _samecolor (True)
• bot: - _samecolor (True)

ExponentialMovingAverageOscillator

Alias:

• ExponentialMovingAverageOsc, EMAOscillator, EMAOsc, MovingAverageExponentialOscillator,


MovingAverageExponentialOsc

620
backtrader’s documentation Version-1.9.58.122

Oscillation of a ExponentialMovingAverage around its data

Lines:

• ema

Params:

• period (30)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• ema:
• _0: - _name (osc)

ExponentialSmoothing

Alias:

• ExpSmoothing

Averages a given data over a period using exponential smoothing

A regular ArithmeticMean (Average) is used as the seed value considering the


first period values of data

Formula:
621
backtrader’s documentation Version-1.9.58.122

• av = prev * (1 - alpha) + data * alpha

See also:

• https://en.wikipedia.org/wiki/Exponential_smoothing

Lines:

• av

Params:

• period (1)
• alpha (None)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• av:

ExponentialSmoothingDynamic

Alias:

• ExpSmoothingDynamic

Averages a given data over a period using exponential smoothing

622
backtrader’s documentation Version-1.9.58.122

A regular ArithmeticMean (Average) is used as the seed value considering the


first period values of data

Note:

• alpha is an array of values which can be calculated dynamically

Formula:

• av = prev * (1 - alpha) + data * alpha

See also:

• https://en.wikipedia.org/wiki/Exponential_smoothing

Lines:

• av

Params:

• period (1)
• alpha (None)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• av:

623
backtrader’s documentation Version-1.9.58.122

FibonacciPivotPoint

Defines a level of significance by taking into account the average of price bar
componentes of the past period of a larget timeframe. For example when operating
with days, the values are taking from the already “past” month fixed prices.

Fibonacci levels (configurable) are used to define the support/resistance levels

Example of using this indicator:

data = btfeeds.ADataFeed(dataname=x, timeframe=bt.TimeFrame.Days) cerebro.adddata(data)


cerebro.resampledata(data, timeframe=bt.TimeFrame.Months)

In the __init__ method of the strategy:

pivotindicator = btind.FibonacciPivotPoiont(self.data1) # the resampled data

The indicator will try to automatically plo to the non-resampled data. To disable
this behavior use the following during construction:

• _autoplot=False

Note:

The example shows days and months, but any combination of timeframes can be used. See the literature for
recommended combinations
Formula:

• pivot = (h + l + c) / 3 # variants duplicate close or add open


• support1 = p - level1 * (high - low) # level1 0.382
• support2 = p - level2 * (high - low) # level1 0.618
• support2 = p - level3 * (high - low) # level1 1.000
• resistance1 = p + level1 * (high - low) # level1 0.382
• resistance2 = p + level2 * (high - low) # level1 0.618
• resistance2 = p + level3 * (high - low) # level1 1.000

See:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:pivot_points

Lines:

• p
• s1
• s2
624
backtrader’s documentation Version-1.9.58.122

• s3
• r1
• r2
• r3

Params:

• open (False)
• close (False)
• _autoplot (True)
• level1 (0.382)
• level2 (0.618)
• level3 (1.0)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• r1:
• r2:
• r3:
• s3:
• s2:
• s1:
• p:

625
backtrader’s documentation Version-1.9.58.122

FindFirstIndex

Returns the index of the last data that satisfies equality with the condition
generated by the parameter _evalfunc

Note:
Returned indexes look backwards. 0 is the current index and 1 is the previous bar.
Formula:

• index = first for which data[index] == _evalfunc(data)

Lines:

• index

Params:

• period (1)
• _evalfunc (None)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• index:

626
backtrader’s documentation Version-1.9.58.122

FindFirstIndexHighest

Returns the index of the first data that is the highest in the period

Note:
Returned indexes look backwards. 0 is the current index and 1 is the previous bar.
Formula:

• index = index of first data which is the highest

Lines:

• index

Params:

• period (1)
• _evalfunc (<built-in function max>)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• index:

FindFirstIndexLowest

Returns the index of the first data that is the lowest in the period
627
backtrader’s documentation Version-1.9.58.122

Note:
Returned indexes look backwards. 0 is the current index and 1 is the previous bar.
Formula:

• index = index of first data which is the lowest

Lines:

• index

Params:

• period (1)
• _evalfunc (<built-in function min>)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• index:

FindLastIndex

Returns the index of the last data that satisfies equality with the condition
generated by the parameter _evalfunc

Note:
Returned indexes look backwards. 0 is the current index and 1 is the previous bar.
Formula:
628
backtrader’s documentation Version-1.9.58.122

• index = last for which data[index] == _evalfunc(data)

Lines:

• index

Params:

• period (1)
• _evalfunc (None)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• index:

FindLastIndexHighest

Returns the index of the last data that is the highest in the period

Note:
Returned indexes look backwards. 0 is the current index and 1 is the previous bar.
Formula:

• index = index of last data which is the highest

Lines:

629
backtrader’s documentation Version-1.9.58.122

• index

Params:

• period (1)
• _evalfunc (<built-in function max>)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• index:

FindLastIndexLowest

Returns the index of the last data that is the lowest in the period

Note:
Returned indexes look backwards. 0 is the current index and 1 is the previous bar.
Formula:

• index = index of last data which is the lowest

Lines:

• index

Params:

630
backtrader’s documentation Version-1.9.58.122

• period (1)
• _evalfunc (<built-in function min>)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• index:

Fractal

References:
[Ref 1] http://www.investopedia.com/articles/trading/06/fractals.asp
Lines:

• fractal_bearish
• fractal_bullish

Params:

• period (5)
• bardist (0.015)
• shift_to_potential_fractal (2)

PlotInfo:

• plot (True)
• legendloc (None)

631
backtrader’s documentation Version-1.9.58.122

• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• fractal_bullish: - color (lightblue) - marker (v) - markersize (4.0) - ls () - fillstyle (full)


• fractal_bearish: - color (lightblue) - marker (^) - markersize (4.0) - ls () - fillstyle (full)

HeikinAshi

Heikin Ashi candlesticks in the forms of lines

Formula:
ha_open = (ha_open(-1) + ha_close(-1)) / 2 ha_high = max(hi, ha_open, ha_close) ha_low = min(lo, ha_open,
ha_close) ha_close = (open + high + low + close) / 4
See also:
https://en.wikipedia.org/wiki/Candlestick_chart#Heikin_Ashi_candlesticks
http://stockcharts.com/school/doku.php?id=chart_school:chart_analysis:heikin_ashi
Lines:

• ha_open
• ha_high
• ha_low
• ha_close

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)

632
backtrader’s documentation Version-1.9.58.122

• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• ha_close:
• ha_open:
• ha_low:
• ha_high:

Highest

Alias:

• MaxN

Calculates the highest value for the data in a given period

Uses the built-in max for the calculation

Formula:

• highest = max(data, period)

Lines:

• highest

Params:

• period (1)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
633
backtrader’s documentation Version-1.9.58.122

• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• highest:

HullMovingAverage

Alias:

• HMA, HullMA

By Alan Hull

The Hull Moving Average solves the age old dilemma of making a moving average
more responsive to current price activity whilst maintaining curve smoothness. In
fact the HMA almost eliminates lag altogether and manages to improve smoothing at
the same time.

Formula:

• hma = wma(2 * wma(data, period // 2) - wma(data, period), sqrt(period))

See also:

• http://alanhull.com/hull-moving-average

Note:

• Please note that the final minimum period is not the period passed with the
parameter period. A final moving average on moving average is done in which
the period is the square root of the original.

In the default case of 30 the final minimum period before the moving
average produces a non-NAN value is 34
634
backtrader’s documentation Version-1.9.58.122

Lines:

• hma

Params:

• period (30)
• _movav (WMA)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• hma:

HullMovingAverageEnvelope

Alias:

• HMAEnvelope, HullMAEnvelope

HullMovingAverage and envelope bands separated “perc” from it

Formula:

• hma (from HullMovingAverage)


• top = hma * (1 + perc)
• bot = hma * (1 - perc)

635
backtrader’s documentation Version-1.9.58.122

See also:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_average_
envelopes

Lines:

• hma
• top
• bot

Params:

• period (30)
• _movav (WMA)
• perc (2.5)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• hma:
• top: - _samecolor (True)
• bot: - _samecolor (True)

HullMovingAverageOscillator

Alias:

636
backtrader’s documentation Version-1.9.58.122

• HullMovingAverageOsc, HMAOscillator, HMAOsc, HullMAOscillator, HullMAOsc

Oscillation of a HullMovingAverage around its data

Lines:

• hma

Params:

• period (30)
• _movav (WMA)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• hma:
• _0: - _name (osc)

HurstExponent

Alias:

• Hurst

References:

• https://www.quantopian.com/posts/hurst-exponent
637
backtrader’s documentation Version-1.9.58.122

• https://www.quantopian.com/posts/some-code-from-ernie-chans-new-book-implemented-in-
python

Interpretation of the results

1. Geometric random walk (H=0.5)


2. Mean-reverting series (H<0.5)
3. Trending Series (H>0.5)

Important notes:

• The default period is 40, but experimentation by users has shown that it
would be advisable to have at least 2000 samples (i.e.: a period of at
least 2000) to have stable values.
• The lag_start and lag_end values will default to be 2 and self.p.period / 2
unless the parameters are specified.

Experimentation by users has also shown that values of around 10 and 500
produce good results

The original values (40, 2, self.p.period / 2) are kept for backwards


compatibility

Lines:

• hurst

Params:

• period (40)
• lag_start (None)
• lag_end (None)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
638
backtrader’s documentation Version-1.9.58.122

• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• hurst:

Ichimoku

Developed and published in his book in 1969 by journalist Goichi Hosoda

Formula:

• tenkan_sen = (Highest(High, tenkan) + Lowest(Low, tenkan)) / 2.0


• kijun_sen = (Highest(High, kijun) + Lowest(Low, kijun)) / 2.0

The next 2 are pushed 26 bars into the future

• senkou_span_a = (tenkan_sen + kijun_sen) / 2.0


• senkou_span_b = ((Highest(High, senkou) + Lowest(Low, senkou)) / 2.0

This is pushed 26 bars into the past

• chikou = close

The cloud (Kumo) is formed by the area between the senkou_spans

See:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:ichimoku_cloud

Lines:

• tenkan_sen
• kijun_sen
• senkou_span_a
• senkou_span_b
• chikou_span

Params:

• tenkan (9)
• kijun (26)
639
backtrader’s documentation Version-1.9.58.122

• senkou (52)
• senkou_lead (26)
• chikou (26)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• senkou_span_a: - _fill_gt ((u’senkou_span_b’, u’g’)) - _fill_lt ((u’senkou_span_b’, u’r’))


• senkou_span_b:
• chikou_span:
• tenkan_sen:
• kijun_sen:

KnowSureThing

Alias:

• KST

It is a “summed” momentum indicator. Developed by Martin Pring and published in


1992 in Stocks & Commodities.

Formula:

• rcma1 = MovAv(roc100(rp1), period)


• rcma2 = MovAv(roc100(rp2), period)
• rcma3 = MovAv(roc100(rp3), period)

640
backtrader’s documentation Version-1.9.58.122

• rcma4 = MovAv(roc100(rp4), period)


• kst = 1.0 * rcma1 + 2.0 * rcma2 + 3.0 * rcma3 + 4.0 * rcma4
• signal = MovAv(kst, speriod)

See:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:know_sure_thing
_kst

Params

• rma1, rma2, rma3, rma4: for the MovingAverages on ROCs


• rp1, rp2, rp3, rp4: for the ROCs
• rsig: for the MovingAverage for the signal line
• rfactors: list of factors to apply to the different MovAv(ROCs)
• _movav and _movavs, allows to change the Moving Average type applied for the calculation of kst and
signal

Lines:

• kst
• signal

Params:

• rp1 (10)
• rp2 (15)
• rp3 (20)
• rp4 (30)
• rma1 (10)
• rma2 (10)
• rma3 (10)
• rma4 (10)
• rsignal (9)
• rfactors ([1.0, 2.0, 3.0, 4.0])
• _rmovav (SMA)
• _smovav (SMA)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)

641
backtrader’s documentation Version-1.9.58.122

• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([0.0])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• kst:
• signal:

LaguerreFilter

Alias:

• LAGF

Defined by John F. Ehlers in Cybernetic Analysis for Stock and Futures, 2004,
published by Wiley. ISBN: 978-0-471-46307-8

gamma is meant to have values between 0.2 and 0.8, with the best balance found
theoretically at the default of 0.5

Lines:

• lfilter

Params:

• period (1)
• gamma (0.5)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
642
backtrader’s documentation Version-1.9.58.122

• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• lfilter:

LaguerreRSI

Alias:

• LRSI

Defined by John F. Ehlers in Cybernetic Analysis for Stock and Futures, 2004,
published by Wiley. ISBN: 978-0-471-46307-8

The Laguerre RSI tries to implements a better RSI by providing a sort of Time
Warp without Time Travel using a Laguerre filter. This provides for faster
reactions to price changes

gamma is meant to have values between 0.2 and 0.8, with the best balance found
theoretically at the default of 0.5

Lines:

• lrsi

Params:

• period (6)
• gamma (0.5)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
643
backtrader’s documentation Version-1.9.58.122

• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.15)
• plotlinelabels (False)
• plotyticks ([0.0, 0.2, 0.5, 0.8, 1.0])

PlotLines:

• lrsi:

LinePlotterIndicator

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

Lowest

Alias:

• MinN

Calculates the lowest value for the data in a given period


644
backtrader’s documentation Version-1.9.58.122

Uses the built-in min for the calculation

Formula:

• lowest = min(data, period)

Lines:

• lowest

Params:

• period (1)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• lowest:

MACD

Moving Average Convergence Divergence. Defined by Gerald Appel in the 70s.

It measures the distance of a short and a long term moving average to try to
identify the trend.

A second lagging moving average over the convergence-divergence should provide a


“signal” upon being crossed by the macd
645
backtrader’s documentation Version-1.9.58.122

Formula:

• macd = ema(data, me1_period) - ema(data, me2_period)


• signal = ema(macd, signal_period)

See:

• http://en.wikipedia.org/wiki/MACD

Lines:

• macd
• signal

Params:

• period_me1 (12)
• period_me2 (26)
• period_signal (9)
• movav (ExponentialMovingAverage)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([0.0])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• signal: - ls (–)
• macd:

646
backtrader’s documentation Version-1.9.58.122

MACDHisto

Alias:

• MACDHistogram

Subclass of MACD which adds a “histogram” of the difference between the macd
and signal lines

Formula:

• histo = macd - signal

See:

• http://en.wikipedia.org/wiki/MACD

Lines:

• macd
• signal
• histo

Params:

• period_me1 (12)
• period_me2 (26)
• period_signal (9)
• movav (ExponentialMovingAverage)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([0.0])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
647
backtrader’s documentation Version-1.9.58.122

• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• signal: - ls (–)
• macd:
• histo: - width (1.0) - alpha (0.5) - _method (bar)

MeanDeviation

Alias:

• MeanDev

MeanDeviation (alias MeanDev)

Calculates the Mean Deviation of the passed data for a given period

Note:

• If 2 datas are provided as parameters, the 2nd is considered to be the mean of the first

Formula:

• mean = MovingAverage(data, period) (or provided mean)


• absdeviation = abs(data - mean)
• meandev = MovingAverage(absdeviation, period)

See:

• https://en.wikipedia.org/wiki/Average_absolute_deviation

Lines:

• meandev

Params:

• period (20)
• movav (MovingAverageSimple)

PlotInfo:

• plot (True)

648
backtrader’s documentation Version-1.9.58.122

• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• meandev:

MinusDirectionalIndicator

Alias:

• MinusDI

Defined by J. Welles Wilder, Jr. in 1978 in his book “New Concepts in Technical
Trading Systems”.

Intended to measure trend strength

This indicator shows -DI:

• Use PlusDirectionalIndicator (PlusDI) to get +DI


• Use Directional Indicator (DI) to get +DI, -DI
• Use AverageDirectionalIndex (ADX) to get ADX
• Use AverageDirectionalIndexRating (ADXR) to get ADX, ADXR
• Use DirectionalMovementIndex (DMI) to get ADX, +DI, -DI
• Use DirectionalMovement (DM) to get ADX, ADXR, +DI, -DI

Formula:

• upmove = high - high(-1)


• downmove = low(-1) - low
• -dm = downmove if downmove > upmove and downmove > 0 else 0

649
backtrader’s documentation Version-1.9.58.122

• -di = 100 * MovingAverage(-dm, period) / atr(period)

The moving average used is the one originally defined by Wilder, the
SmoothedMovingAverage

See:

• https://en.wikipedia.org/wiki/Average_directional_movement_index

Lines:

• minusDI

Params:

• period (14)
• movav (SmoothedMovingAverage)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname (-DirectionalIndicator)
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• minusDI:
• plusDI: - _name (+DI)

Momentum

Measures the change in price by calculating the difference between the current
price and the price from a given period ago
650
backtrader’s documentation Version-1.9.58.122

Formula:

• momentum = data - data_period

See:

• http://en.wikipedia.org/wiki/Momentum_(technical_analysis)

Lines:

• momentum

Params:

• period (12)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([0.0])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• momentum:

MomentumOscillator

Alias:

• MomentumOsc

Measures the ratio of change in prices over a period


651
backtrader’s documentation Version-1.9.58.122

Formula:

• mosc = 100 * (data / data_period)

See:

• http://ta.mql4.com/indicators/oscillators/momentum

Lines:

• momosc

Params:

• period (12)
• band (100.0)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• momosc:

MovingAverageBase

Params:

• period (30)

652
backtrader’s documentation Version-1.9.58.122

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

MovingAverageSimple

Alias:

• SMA, SimpleMovingAverage

Non-weighted average of the last n periods

Formula:

• movav = Sum(data, period) / period

See also:

• http://en.wikipedia.org/wiki/Moving_average#Simple_moving_average

Lines:

• sma

Params:

• period (30)

PlotInfo:

• plot (True)
653
backtrader’s documentation Version-1.9.58.122

• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• sma:

MovingAverageSimpleEnvelope

Alias:

• SMAEnvelope, SimpleMovingAverageEnvelope

MovingAverageSimple and envelope bands separated “perc” from it

Formula:

• sma (from MovingAverageSimple)


• top = sma * (1 + perc)
• bot = sma * (1 - perc)

See also:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_average_
envelopes

Lines:

• sma
• top
• bot

Params:
654
backtrader’s documentation Version-1.9.58.122

• period (30)
• perc (2.5)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• sma:
• top: - _samecolor (True)
• bot: - _samecolor (True)

MovingAverageSimpleOscillator

Alias:

• MovingAverageSimpleOsc, SMAOscillator, SMAOsc, SimpleMovingAverageOscillator,


SimpleMovingAverageOsc

Oscillation of a MovingAverageSimple around its data

Lines:

• sma

Params:

• period (30)

PlotInfo:
655
backtrader’s documentation Version-1.9.58.122

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• sma:
• _0: - _name (osc)

OLS_BetaN

Calculates a regression of data1 on data0 using pandas.ols

Uses pandas

Lines:

• beta

Params:

• period (10)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()

656
backtrader’s documentation Version-1.9.58.122

• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• beta:

OLS_Slope_InterceptN

Calculates a linear regression using statsmodel.OLS (Ordinary least squares) of


data1 on data0

Uses pandas and statsmodels

Lines:

• slope
• intercept

Params:

• period (10)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)

657
backtrader’s documentation Version-1.9.58.122

• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• slope:
• intercept:

OLS_TransformationN

Calculates the zscore for data0 and data1. Although it doesn’t directly uses any
external package it relies on OLS_SlopeInterceptN which uses pandas and
statsmodels

Lines:

• spread
• spread_mean
• spread_std
• zscore

Params:

• period (10)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

658
backtrader’s documentation Version-1.9.58.122

• spread_mean:
• spread_std:
• zscore:
• spread:

OperationN

Calculates “func” for a given period

Serves as a base for classes that work with a period and can express the logic in
a callable object

Note:
Base classes must provide a “func” attribute which is a callable
Formula:

• line = func(data, period)

Params:

• period (1)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

Oscillator

Oscillation of a given data around another data

659
backtrader’s documentation Version-1.9.58.122

Datas:

This indicator can accept 1 or 2 datas for the calculation.

• If 1 data is provided, it must be a complex “Lines” object


(indicator) which also has “datas”. Example: A moving average

The calculated oscillation will be that of the Moving Average (in the
example) around the data that was used for the average calculation

• If 2 datas are provided the calculated oscillation will be that of


the 2nd data around the 1st data

Formula:

• 1 data -> osc = data.data - data


• 2 datas -> osc = data0 - data1

Lines:

• osc

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• osc:
• _0: - _name (osc)

660
backtrader’s documentation Version-1.9.58.122

OscillatorMixIn

MixIn class to create a subclass with another indicator. The main line of that
indicator will be substracted from the other base class main line creating an
oscillator

The usage is:

• Class XXXOscillator(XXX, OscillatorMixIn)

Formula:

• XXX calculates lines[0]


• osc = self.data - XXX.lines[0]

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• _0: - _name (osc)

ParabolicSAR

Alias:

• PSAR

661
backtrader’s documentation Version-1.9.58.122

Defined by J. Welles Wilder, Jr. in 1978 in his book “New Concepts in Technical
Trading Systems” for the RSI

SAR stands for Stop and Reverse and the indicator was meant as a signal for entry
(and reverse)

How to select the 1st signal is left unspecified in the book and the
increase/derease of bars

See:

• https://en.wikipedia.org/wiki/Parabolic_SAR
• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:parabolic_sar

Lines:

• psar

Params:

• period (2)
• af (0.02)
• afmax (0.2)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• psar: - color (black) - marker (.) - markersize (4.0) - ls () - fillstyle (full)

662
backtrader’s documentation Version-1.9.58.122

PercentChange

Alias:

• PctChange

Measures the perccentage change of the current value with respect to that of
period bars ago

Lines:

• pctchange

Params:

• period (30)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• pctchange: - _name (%change)

PercentRank

Alias:

• PctRank
663
backtrader’s documentation Version-1.9.58.122

Measures the percent rank of the current value with respect to that of period
bars ago

Lines:

• pctrank

Params:

• period (50)
• func (<function <lambda> at 0x00000000054FB908>)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• pctrank:

PercentagePriceOscillator

Alias:

• PPO, PercPriceOsc

Shows the difference between a short and long exponential moving averages
expressed in percentage. The MACD does the same but expressed in absolute points.

664
backtrader’s documentation Version-1.9.58.122

Expressing the difference in percentage allows to compare the indicator at


different points in time when the underlying value has significatnly different
values.

Formula:

• po = 100 * (ema(short) - ema(long)) / ema(long)

See:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:price_oscillators_
ppo

Lines:

• ppo
• signal
• histo

Params:

• period1 (12)
• period2 (26)
• _movav (ExponentialMovingAverage)
• period_signal (9)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([0.0])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

665
backtrader’s documentation Version-1.9.58.122

• signal:
• ppo:
• histo: - width (1.0) - alpha (0.5) - _method (bar)

PercentagePriceOscillatorShort

Alias:

• PPOShort, PercPriceOscShort

Shows the difference between a short and long exponential moving averages
expressed in percentage. The MACD does the same but expressed in absolute points.

Expressing the difference in percentage allows to compare the indicator at


different points in time when the underlying value has significatnly different
values.

Most on-line literature shows the percentage calculation having the long
exponential moving average as the denominator. Some sources like MetaStock use
the short one.

Formula:

• po = 100 * (ema(short) - ema(long)) / ema(short)

See:

• http://www.metastock.com/Customer/Resources/TAAZ/?c=3&p=94

Lines:

• ppo
• signal
• histo

Params:

• period1 (12)
• period2 (26)
• _movav (ExponentialMovingAverage)
• period_signal (9)

PlotInfo:

• plot (True)
666
backtrader’s documentation Version-1.9.58.122

• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([0.0])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• signal:
• ppo:
• histo: - width (1.0) - alpha (0.5) - _method (bar)

PeriodN

Base class for indicators which take a period (__init__ has to be called either
via super or explicitly)

This class has no defined lines

Params:

• period (1)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])

667
backtrader’s documentation Version-1.9.58.122

• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PivotPoint

Defines a level of significance by taking into account the average of price bar
componentes of the past period of a larget timeframe. For example when operating
with days, the values are taking from the already “past” month fixed prices.

Example of using this indicator:

data = btfeeds.ADataFeed(dataname=x, timeframe=bt.TimeFrame.Days) cerebro.adddata(data)


cerebro.resampledata(data, timeframe=bt.TimeFrame.Months)

In the __init__ method of the strategy:

pivotindicator = btind.PivotPoiont(self.data1) # the resampled data

The indicator will try to automatically plo to the non-resampled data. To disable
this behavior use the following during construction:

• _autoplot=False

Note:

The example shows days and months, but any combination of timeframes can be used. See the literature for
recommended combinations
Formula:

• pivot = (h + l + c) / 3 # variants duplicate close or add open


• support1 = 2.0 * pivot - high
• support2 = pivot - (high - low)
• resistance1 = 2.0 * pivot - low
• resistance2 = pivot + (high - low)

See:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:pivot_points
• https://en.wikipedia.org/wiki/Pivot_point_(technical_analysis)

Lines:

668
backtrader’s documentation Version-1.9.58.122

• p
• s1
• s2
• r1
• r2

Params:

• open (False)
• close (False)
• _autoplot (True)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• p:
• s1:
• r1:
• r2:
• s2:

PlusDirectionalIndicator

Alias:

• PlusDI

669
backtrader’s documentation Version-1.9.58.122

Defined by J. Welles Wilder, Jr. in 1978 in his book “New Concepts in Technical
Trading Systems”.

Intended to measure trend strength

This indicator shows +DI:

• Use MinusDirectionalIndicator (MinusDI) to get -DI


• Use Directional Indicator (DI) to get +DI, -DI
• Use AverageDirectionalIndex (ADX) to get ADX
• Use AverageDirectionalIndexRating (ADXR) to get ADX, ADXR
• Use DirectionalMovementIndex (DMI) to get ADX, +DI, -DI
• Use DirectionalMovement (DM) to get ADX, ADXR, +DI, -DI

Formula:

• upmove = high - high(-1)


• downmove = low(-1) - low
• +dm = upmove if upmove > downmove and upmove > 0 else 0
• +di = 100 * MovingAverage(+dm, period) / atr(period)

The moving average used is the one originally defined by Wilder, the
SmoothedMovingAverage

See:

• https://en.wikipedia.org/wiki/Average_directional_movement_index

Lines:

• plusDI

Params:

• period (14)
• movav (SmoothedMovingAverage)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname (+DirectionalIndicator)
670
backtrader’s documentation Version-1.9.58.122

• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• minusDI: - _name (-DI)


• plusDI:

PrettyGoodOscillator

Alias:

• PGO, PrettyGoodOsc

The “Pretty Good Oscillator” (PGO) by Mark Johnson measures the distance of the
current close from its simple moving average of period Average), expressed in
terms of an average true range (see Average True Range) over a similar period.

So for instance a PGO value of +2.5 would mean the current close is 2.5 average
days’ range above the SMA.

Johnson’s approach was to use it as a breakout system for longer term trades. If
the PGO rises above 3.0 then go long, or below -3.0 then go short, and in both
cases exit on returning to zero (which is a close back at the SMA).

Formula:

• pgo = (data.close - sma(data, period)) / atr(data, period)

See also:

• http://user42.tuxfamily.org/chart/manual/Pretty-Good-Oscillator.html

Lines:

• pgo

Params:

671
backtrader’s documentation Version-1.9.58.122

• period (14)
• _movav (MovingAverageSimple)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• pgo:

PriceOscillator

Alias:

• PriceOsc, AbsolutePriceOscillator, APO, AbsPriceOsc

Shows the difference between a short and long exponential moving averages
expressed in points.

Formula:

• po = ema(short) - ema(long)

See:

• http://www.metastock.com/Customer/Resources/TAAZ/?c=3&p=94

Lines:

• po
672
backtrader’s documentation Version-1.9.58.122

Params:

• period1 (12)
• period2 (26)
• _movav (ExponentialMovingAverage)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([0.0])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• po:

RSI_EMA

Uses an ExponentialMovingAverage as described in Wikipedia

See:

• http://en.wikipedia.org/wiki/Relative_strength_index

Lines:

• rsi

Params:

• period (14)
• movav (ExponentialMovingAverage)
• upperband (70.0)
673
backtrader’s documentation Version-1.9.58.122

• lowerband (30.0)
• safediv (False)
• safehigh (100.0)
• safelow (50.0)
• lookback (1)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• rsi:

RSI_SMA

Alias:

• RSI_Cutler

Uses a SimpleMovingAverage as described in Wikipedia and other soures

See:

• http://en.wikipedia.org/wiki/Relative_strength_index

Lines:

• rsi

Params:
674
backtrader’s documentation Version-1.9.58.122

• period (14)
• movav (MovingAverageSimple)
• upperband (70.0)
• lowerband (30.0)
• safediv (False)
• safehigh (100.0)
• safelow (50.0)
• lookback (1)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• rsi:

RSI_Safe

Subclass of RSI which changes parameers safediv to True as the default value

See:

• http://en.wikipedia.org/wiki/Relative_strength_index

Lines:

• rsi

Params:

675
backtrader’s documentation Version-1.9.58.122

• period (14)
• movav (SmoothedMovingAverage)
• upperband (70.0)
• lowerband (30.0)
• safediv (True)
• safehigh (100.0)
• safelow (50.0)
• lookback (1)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• rsi:

RateOfChange

Alias:

• ROC

Measures the ratio of change in prices over a period

Formula:

• roc = (data - data_period) / data_period

See:

676
backtrader’s documentation Version-1.9.58.122

• http://en.wikipedia.org/wiki/Momentum_(technical_analysis)

Lines:

• roc

Params:

• period (12)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• roc:

RateOfChange100

Alias:

• ROC100

Measures the ratio of change in prices over a period with base 100

This is for example how ROC is defined in stockcharts

Formula:

• roc = 100 * (data - data_period) / data_period


677
backtrader’s documentation Version-1.9.58.122

See:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:rate_of_change_r
oc_and_momentum

Lines:

• roc100

Params:

• period (12)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• roc100:

ReduceN

Calculates the Reduced value of the period data points applying function

Uses the built-in reduce for the calculation plus the func that subclassess
define

Formula:

• reduced = reduce(function(data, period)), initializer=initializer)


678
backtrader’s documentation Version-1.9.58.122

Notes:

• In order to mimic the python reduce, this indicator takes a function non-named argument as the 1st
argument, unlike other Indicators which take only named arguments

Lines:

• reduced

Params:

• period (1)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• reduced:

RelativeMomentumIndex

Alias:

• RMI

Description: The Relative Momentum Index was developed by Roger Altman and was
introduced in his article in the February, 1993 issue of Technical Analysis of
Stocks & Commodities magazine.

679
backtrader’s documentation Version-1.9.58.122

While your typical RSI counts up and down days from close to close, the Relative
Momentum Index counts up and down days from the close relative to a close x
number of days ago. The result is an RSI that is a bit smoother.

Usage: Use in the same way you would any other RSI . There are overbought and
oversold zones, and can also be used for divergence and trend analysis.

See:

• https://www.marketvolume.com/technicalanalysis/relativemomentumindex.asp
• https://www.tradingview.com/script/UCm7fIvk-FREE-INDICATOR-Relative-Momentum-Index-
RMI/
• https://www.prorealcode.com/prorealtime-indicators/relative-momentum-index-rmi/

Lines:

• rsi

Params:

• period (20)
• movav (SmoothedMovingAverage)
• upperband (70.0)
• lowerband (30.0)
• safediv (False)
• safehigh (100.0)
• safelow (50.0)
• lookback (5)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)

680
backtrader’s documentation Version-1.9.58.122

• plotyticks ([])

PlotLines:

• rsi: - _name (rmi)

RelativeStrengthIndex

Alias:

• RSI, RSI_SMMA, RSI_Wilder

Defined by J. Welles Wilder, Jr. in 1978 in his book “New Concepts in Technical
Trading Systems”.

It measures momentum by calculating the ration of higher closes and lower closes
after having been smoothed by an average, normalizing the result between 0 and
100

Formula:

• up = upday(data)
• down = downday(data)
• maup = movingaverage(up, period)
• madown = movingaverage(down, period)
• rs = maup / madown
• rsi = 100 - 100 / (1 + rs)

The moving average used is the one originally defined by Wilder, the
SmoothedMovingAverage

See:

• http://en.wikipedia.org/wiki/Relative_strength_index

Notes:

• safediv (default: False) If this parameter is True the division rs = maup / madown will be checked
for the special cases in which a 0 / 0 or x / 0 division will happen
• safehigh (default: 100.0) will be used as RSI value for the x / 0 case
• safelow (default: 50.0) will be used as RSI value for the 0 / 0 case

Lines:

• rsi
681
backtrader’s documentation Version-1.9.58.122

Params:

• period (14)
• movav (SmoothedMovingAverage)
• upperband (70.0)
• lowerband (30.0)
• safediv (False)
• safehigh (100.0)
• safelow (50.0)
• lookback (1)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• rsi:

Signal

Lines:

• signal

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)

682
backtrader’s documentation Version-1.9.58.122

• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• signal:

SmoothedMovingAverage

Alias:

• SMMA, WilderMA, MovingAverageSmoothed, MovingAverageWilder, ModifiedMovingAverage

Smoothing Moving Average used by Wilder in his 1978 book New Concepts in
Technical Trading

Defined in his book originally as:

• new_value = (old_value * (period - 1) + new_data) / period

Can be expressed as a SmoothingMovingAverage with the following factors:

• self.smfactor -> 1.0 / period


• self.smfactor1 -> 1.0 - self.smfactor

Formula:

• movav = prev * (1.0 - smoothfactor) + newdata * smoothfactor

See also:

• http://en.wikipedia.org/wiki/Moving_average#Modified_moving_average

Lines:

683
backtrader’s documentation Version-1.9.58.122

• smma

Params:

• period (30)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• smma:

SmoothedMovingAverageEnvelope

Alias:

• SMMAEnvelope, WilderMAEnvelope, MovingAverageSmoothedEnvelope,


MovingAverageWilderEnvelope, ModifiedMovingAverageEnvelope

SmoothedMovingAverage and envelope bands separated “perc” from it

Formula:

• smma (from SmoothedMovingAverage)


• top = smma * (1 + perc)
• bot = smma * (1 - perc)

See also:

684
backtrader’s documentation Version-1.9.58.122

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_average_
envelopes

Lines:

• smma
• top
• bot

Params:

• period (30)
• perc (2.5)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• smma:
• top: - _samecolor (True)
• bot: - _samecolor (True)

SmoothedMovingAverageOscillator

Alias:

• SmoothedMovingAverageOsc, SMMAOscillator, SMMAOsc, WilderMAOscillator, WilderMAOsc,


MovingAverageSmoothedOscillator, MovingAverageSmoothedOsc,

685
backtrader’s documentation Version-1.9.58.122

MovingAverageWilderOscillator, MovingAverageWilderOsc, ModifiedMovingAverageOscillator,


ModifiedMovingAverageOsc

Oscillation of a SmoothedMovingAverage around its data

Lines:

• smma

Params:

• period (30)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• smma:
• _0: - _name (osc)

StandardDeviation

Alias:

• StdDev

Calculates the standard deviation of the passed data for a given period

Note:
686
backtrader’s documentation Version-1.9.58.122

• If 2 datas are provided as parameters, the 2nd is considered to be the mean of the first
• safepow (default: False) If this parameter is True, the standard deviation will be calculated as
pow(abs(meansq - sqmean), 0.5) to safe guard for possible negative results of meansq - sqmean
caused by the floating point representation.

Formula:

• meansquared = SimpleMovingAverage(pow(data, 2), period)


• squaredmean = pow(SimpleMovingAverage(data, period), 2)
• stddev = pow(meansquared - squaredmean, 0.5) # square root

See:

• http://en.wikipedia.org/wiki/Standard_deviation

Lines:

• stddev

Params:

• period (20)
• movav (MovingAverageSimple)
• safepow (True)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• stddev:
687
backtrader’s documentation Version-1.9.58.122

Stochastic

Alias:

• StochasticSlow

The regular (or slow version) adds an additional moving average layer and thus:

• The percD line of the StochasticFast becomes the percK line


• percD becomes a moving average of period_dslow of the original percD

Formula:

• k=k
• d=d
• d = MovingAverage(d, period_dslow)

See:

• http://en.wikipedia.org/wiki/Stochastic_oscillator

Lines:

• percK
• percD

Params:

• period (14)
• period_dfast (3)
• movav (MovingAverageSimple)
• upperband (80.0)
• lowerband (20.0)
• safediv (False)
• safezero (0.0)
• period_dslow (3)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
688
backtrader’s documentation Version-1.9.58.122

• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• percK: - _name (%K)


• percD: - ls (–) - _name (%D)

StochasticFast

By Dr. George Lane in the 50s. It compares a closing price to the price range and
tries to show convergence if the closing prices are close to the extremes

• It will go up if closing prices are close to the highs


• It will roughly go down if closing prices are close to the lows

It shows divergence if the extremes keep on growign but closing prices do not in
the same manner (distance to the extremes grow)

Formula:

• hh = highest(data.high, period)
• ll = lowest(data.low, period)
• knum = data.close - ll
• kden = hh - ll
• k = 100 * (knum / kden)
• d = MovingAverage(k, period_dfast)

See:

• http://en.wikipedia.org/wiki/Stochastic_oscillator

Lines:

• percK
• percD

Params:
689
backtrader’s documentation Version-1.9.58.122

• period (14)
• period_dfast (3)
• movav (MovingAverageSimple)
• upperband (80.0)
• lowerband (20.0)
• safediv (False)
• safezero (0.0)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• percK: - _name (%K)


• percD: - ls (–) - _name (%D)

StochasticFull

This version displays the 3 possible lines:

• percK
• percD
• percSlow

Formula:

• k=d
• d = MovingAverage(k, period_dslow)
• dslow =

690
backtrader’s documentation Version-1.9.58.122

See:

• http://en.wikipedia.org/wiki/Stochastic_oscillator

Lines:

• percK
• percD
• percDSlow

Params:

• period (14)
• period_dfast (3)
• movav (MovingAverageSimple)
• upperband (80.0)
• lowerband (20.0)
• safediv (False)
• safezero (0.0)
• period_dslow (3)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• percK: - _name (%K)


• percD: - ls (–) - _name (%D)
• percDSlow: - _name (%DSlow)

691
backtrader’s documentation Version-1.9.58.122

SumN

Calculates the Sum of the data values over a given period

Uses math.fsum for the calculation rather than the built-in sum to avoid
precision errors

Formula:

• sumn = sum(data, period)

Lines:

• sumn

Params:

• period (1)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• sumn:

TripleExponentialMovingAverage

Alias:
692
backtrader’s documentation Version-1.9.58.122

• TEMA, MovingAverageTripleExponential

TEMA was first time introduced in 1994, in the article “Smoothing Data with
Faster Moving Averages” by Patrick G. Mulloy in “Technical Analysis of Stocks &
Commodities” magazine.

It attempts to reduce the inherent lag associated to Moving Averages

Formula:

• ema1 = ema(data, period)


• ema2 = ema(ema1, period)
• ema3 = ema(ema2, period)
• tema = 3 * ema1 - 3 * ema2 + ema3

See:
(None)
Lines:

• tema

Params:

• period (30)
• _movav (EMA)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

693
backtrader’s documentation Version-1.9.58.122

• tema:

TripleExponentialMovingAverageEnvelope

Alias:

• TEMAEnvelope, MovingAverageTripleExponentialEnvelope

TripleExponentialMovingAverage and envelope bands separated “perc” from it

Formula:

• tema (from TripleExponentialMovingAverage)


• top = tema * (1 + perc)
• bot = tema * (1 - perc)

See also:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_average_
envelopes

Lines:

• tema
• top
• bot

Params:

• period (30)
• _movav (EMA)
• perc (2.5)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
694
backtrader’s documentation Version-1.9.58.122

• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• tema:
• top: - _samecolor (True)
• bot: - _samecolor (True)

TripleExponentialMovingAverageOscillator

Alias:

• TripleExponentialMovingAverageOsc, TEMAOscillator, TEMAOsc,


MovingAverageTripleExponentialOscillator, MovingAverageTripleExponentialOsc

Oscillation of a TripleExponentialMovingAverage around its data

Lines:

• tema

Params:

• period (30)
• _movav (EMA)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
695
backtrader’s documentation Version-1.9.58.122

• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• tema:
• _0: - _name (osc)

Trix

Alias:

• TRIX

Defined by Jack Hutson in the 80s and shows the Rate of Change (%) or slope of a
triple exponentially smoothed moving average

Formula:

• ema1 = EMA(data, period)


• ema2 = EMA(ema1, period)
• ema3 = EMA(ema2, period)
• trix = 100 * (ema3 - ema3(-1)) / ema3(-1)

The final formula can be simplified to: 100 * (ema3 / ema3(-1) - 1)

The moving average used is the one originally defined by Wilder, the
SmoothedMovingAverage

See:

• https://en.wikipedia.org/wiki/Trix_(technical_analysis)
• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:trix

Lines:

• trix

Params:

• period (15)
• _rocperiod (1)
• _movav (EMA)

PlotInfo:
696
backtrader’s documentation Version-1.9.58.122

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([0.0])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• trix:

TrixSignal

Extension of Trix with a signal line (ala MACD)

Formula:

• trix = Trix(data, period)


• signal = EMA(trix, sigperiod)

See:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:trix

Lines:

• trix
• signal

Params:

• period (15)
• _rocperiod (1)
• _movav (EMA)
• sigperiod (9)

697
backtrader’s documentation Version-1.9.58.122

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([0.0])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• trix:
• signal:

TrueHigh

Defined by J. Welles Wilder, Jr. in 1978 in his book “New Concepts in Technical
Trading Systems” for the ATR

Records the “true high” which is the maximum of today’s high and yesterday’s
close

Formula:

• truehigh = max(high, close_prev)

See:

• http://en.wikipedia.org/wiki/Average_true_range

Lines:

• truehigh

PlotInfo:

698
backtrader’s documentation Version-1.9.58.122

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• truehigh:

TrueLow

Defined by J. Welles Wilder, Jr. in 1978 in his book “New Concepts in Technical
Trading Systems” for the ATR

Records the “true high” which is the maximum of today’s high and yesterday’s
close

Formula:

• truelow = min(high, close_prev)

See:

• http://en.wikipedia.org/wiki/Average_true_range

Lines:

• truelow

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
699
backtrader’s documentation Version-1.9.58.122

• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• truelow:

TrueRange

Alias:

• TR

Defined by J. Welles Wilder, Jr. in 1978 in his book New Concepts in Technical
Trading Systems.

Formula:

• max(high - low, abs(high - prev_close), abs(prev_close - low)

which can be simplified to

• max(high, prev_close) - min(low, prev_close)

See:

• http://en.wikipedia.org/wiki/Average_true_range

The idea is to take the previous close into account to calculate the range if it
yields a larger range than the daily range (High - Low)

Lines:

• tr

700
backtrader’s documentation Version-1.9.58.122

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• tr:

TrueStrengthIndicator

Alias:

• TSI

The True Strength Indicators was first introduced in Stocks & Commodities
Magazine by its author William Blau. It measures momentum with a double
exponential (default) of the prices.

It shows divergence if the extremes keep on growign but closing prices do not in
the same manner (distance to the extremes grow)

Formula:

• price_change = close - close(pchange periods ago)


• sm1_simple = EMA(price_close_change, period1)
• sm1_double = EMA(sm1_simple, period2)
• sm2_simple = EMA(abs(price_close_change), period1)
• sm2_double = EMA(sm2_simple, period2)
• tsi = 100.0 * sm1_double / sm2_double

701
backtrader’s documentation Version-1.9.58.122

See:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:true_strength_ind
ex

Params

• period1: the period for the 1st smoothing


• period2: the period for the 2nd smoothing
• pchange: the lookback period for the price change
• _movav: the moving average to apply for the smoothing

Lines:

• tsi

Params:

• period1 (25)
• period2 (13)
• pchange (1)
• _movav (EMA)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• tsi:

702
backtrader’s documentation Version-1.9.58.122

UltimateOscillator

Formula:

# Buying Pressure = Close - TrueLow BP = Close - Minimum(Low or Prior


Close)

# TrueRange = TrueHigh - TrueLow TR = Maximum(High or Prior Close) -


Minimum(Low or Prior Close)

Average7 = (7-period BP Sum) / (7-period TR Sum) Average14 = (14-period BP


Sum) / (14-period TR Sum) Average28 = (28-period BP Sum) / (28-period TR
Sum)

UO = 100 x [(4 x Average7)+(2 x Average14)+Average28]/(4+2+1)

See:

• https://en.wikipedia.org/wiki/Ultimate_oscillator
• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:ultimate_oscillator

Lines:

• uo

Params:

• p1 (7)
• p2 (14)
• p3 (28)
• upperband (70.0)
• lowerband (30.0)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
703
backtrader’s documentation Version-1.9.58.122

• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• uo:

UpDay

Defined by J. Welles Wilder, Jr. in 1978 in his book “New Concepts in Technical
Trading Systems” for the RSI

Recods days which have been “up”, i.e.: the close price has been higher than
the day before.

Formula:

• upday = max(close - close_prev, 0)

See:

• http://en.wikipedia.org/wiki/Relative_strength_index

Lines:

• upday

Params:

• period (1)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
704
backtrader’s documentation Version-1.9.58.122

• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• upday:

UpDayBool

Defined by J. Welles Wilder, Jr. in 1978 in his book “New Concepts in Technical
Trading Systems” for the RSI

Recods days which have been “up”, i.e.: the close price has been higher than
the day before.

Note:

• This version returns a bool rather than the difference

Formula:

• upday = close > close_prev

See:

• http://en.wikipedia.org/wiki/Relative_strength_index

Lines:

• upday

Params:

• period (1)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
705
backtrader’s documentation Version-1.9.58.122

• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• upday:

UpMove

Defined by J. Welles Wilder, Jr. in 1978 in his book “New Concepts in Technical
Trading Systems” as part of the Directional Move System to calculate Directional
Indicators.

Positive if the given data has moved higher than the previous day

Formula:

• upmove = data - data(-1)

See:

• https://en.wikipedia.org/wiki/Average_directional_movement_index

Lines:

• upmove

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
706
backtrader’s documentation Version-1.9.58.122

• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• upmove:

Vortex

See:

• http://www.vortexindicator.com/VFX_VORTEX.PDF

Lines:

• vi_plus
• vi_minus

Params:

• period (14)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)

707
backtrader’s documentation Version-1.9.58.122

• plotyticks ([])

PlotLines:

• vi_minus: - _name (-VI)


• vi_plus: - _name (+VI)

WeightedAverage

Alias:

• AverageWeighted

Calculates the weighted average of the given data over a period

The default weights (if none are provided) are linear to assigne more weight to
the most recent data

The result will be multiplied by a given “coef”

Formula:

• av = coef * sum(mul(data, period), weights)

See:

• https://en.wikipedia.org/wiki/Weighted_arithmetic_mean

Lines:

• av

Params:

• period (1)
• coef (1.0)
• weights (())

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)

708
backtrader’s documentation Version-1.9.58.122

• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• av:

WeightedMovingAverage

Alias:

• WMA, MovingAverageWeighted

A Moving Average which gives an arithmetic weighting to values with the newest
having the more weight

Formula:

• weights = range(1, period + 1)


• coef = 2 / (period * (period + 1))
• movav = coef * Sum(weight[i] * data[period - i] for i in range(period))

See also:

• http://en.wikipedia.org/wiki/Moving_average#Weighted_moving_average

Lines:

• wma

Params:

• period (30)

PlotInfo:

• plot (True)
709
backtrader’s documentation Version-1.9.58.122

• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• wma:

WeightedMovingAverageEnvelope

Alias:

• WMAEnvelope, MovingAverageWeightedEnvelope

WeightedMovingAverage and envelope bands separated “perc” from it

Formula:

• wma (from WeightedMovingAverage)


• top = wma * (1 + perc)
• bot = wma * (1 - perc)

See also:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_average_
envelopes

Lines:

• wma
• top
• bot

Params:
710
backtrader’s documentation Version-1.9.58.122

• period (30)
• perc (2.5)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• wma:
• top: - _samecolor (True)
• bot: - _samecolor (True)

WeightedMovingAverageOscillator

Alias:

• WeightedMovingAverageOsc, WMAOscillator, WMAOsc, MovingAverageWeightedOscillator,


MovingAverageWeightedOsc

Oscillation of a WeightedMovingAverage around its data

Lines:

• wma

Params:

• period (30)

PlotInfo:
711
backtrader’s documentation Version-1.9.58.122

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• wma:
• _0: - _name (osc)

WilliamsAD

By Larry Williams. It does cumulatively measure if the price is accumulating


(upwards) or distributing (downwards) by using the concept of UpDays and
DownDays.

Prices can go upwards but do so in a fashion that no longer shows accumulation


because updays and downdays are canceling out each other, creating a divergence.

See: - http://www.metastock.com/Customer/Resources/TAAZ/?p=125 -
http://ta.mql4.com/indicators/trends/williams_accumulation_distribution

Lines:

• ad

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)

712
backtrader’s documentation Version-1.9.58.122

• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• ad:

WilliamsR

Developed by Larry Williams to show the relation of closing prices to the


highest-lowest range of a given period.

Known as Williams %R (but % is not allowed in Python identifiers)

Formula:

• num = highest_period - close


• den = highestg_period - lowest_period
• percR = (num / den) * -100.0

See:

• http://en.wikipedia.org/wiki/Williams_%25R

Lines:

• percR

Params:

• period (14)
• upperband (-20.0)
• lowerband (-80.0)

PlotInfo:

• plot (True)
713
backtrader’s documentation Version-1.9.58.122

• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname (Williams R%)
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• percR: - _name (R%)

ZeroLagExponentialMovingAverage

Alias:

• ZLEMA, ZeroLagEma

The zero-lag exponential moving average (ZLEMA) is a variation of the EMA which
adds a momentum term aiming to reduce lag in the average so as to track current
prices more closely.

Formula:

• lag = (period - 1) / 2
• zlema = ema(2 * data - data(-lag))

See also:

• http://user42.tuxfamily.org/chart/manual/Zero_002dLag-Exponential-Moving-Average.html

Lines:

• zlema

Params:

• period (30)
714
backtrader’s documentation Version-1.9.58.122

• _movav (EMA)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• zlema:

ZeroLagExponentialMovingAverageEnvelope

Alias:

• ZLEMAEnvelope, ZeroLagEmaEnvelope

ZeroLagExponentialMovingAverage and envelope bands separated “perc” from it

Formula:

• zlema (from ZeroLagExponentialMovingAverage)


• top = zlema * (1 + perc)
• bot = zlema * (1 - perc)

See also:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_average_
envelopes

Lines:

715
backtrader’s documentation Version-1.9.58.122

• zlema
• top
• bot

Params:

• period (30)
• _movav (EMA)
• perc (2.5)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• zlema:
• top: - _samecolor (True)
• bot: - _samecolor (True)

ZeroLagExponentialMovingAverageOscillator

Alias:

• ZeroLagExponentialMovingAverageOsc, ZLEMAOscillator, ZLEMAOsc, ZeroLagEmaOscillator,


ZeroLagEmaOsc

Oscillation of a ZeroLagExponentialMovingAverage around its data

Lines:

716
backtrader’s documentation Version-1.9.58.122

• zlema

Params:

• period (30)
• _movav (EMA)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• zlema:
• _0: - _name (osc)

ZeroLagIndicator

Alias:

• ZLIndicator, ZLInd, EC, ErrorCorrecting

By John Ehlers and Ric Way

The zero-lag indicator (ZLIndicator) is a variation of the EMA which modifies the
EMA by trying to minimize the error (distance price - error correction) and thus
reduce the lag

Formula:

• EMA(data, period)
717
backtrader’s documentation Version-1.9.58.122

• For each iteration calculate a best-error-correction of the ema (see


the paper and/or the code) iterating over -bestgain -> +bestgain for
the error correction factor (both incl.)
• The default moving average is EMA, but can be changed with the
parameter _movav

Note

the passed moving average must calculate alpha (and 1 - alpha) and
make them available as attributes alpha and alpha1 in the instance

See also:

• http://www.mesasoftware.com/papers/ZeroLag.pdf

Lines:

• ec

Params:

• period (30)
• gainlimit (50)
• _movav (EMA)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• ec:
718
backtrader’s documentation Version-1.9.58.122

ZeroLagIndicatorEnvelope

Alias:

• ZLIndicatorEnvelope, ZLIndEnvelope, ECEnvelope, ErrorCorrectingEnvelope

ZeroLagIndicator and envelope bands separated “perc” from it

Formula:

• ec (from ZeroLagIndicator)
• top = ec * (1 + perc)
• bot = ec * (1 - perc)

See also:

• http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_average_
envelopes

Lines:

• ec
• top
• bot

Params:

• period (30)
• gainlimit (50)
• _movav (EMA)
• perc (2.5)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (False)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
719
backtrader’s documentation Version-1.9.58.122

• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• ec:
• top: - _samecolor (True)
• bot: - _samecolor (True)

ZeroLagIndicatorOscillator

Alias:

• ZeroLagIndicatorOsc, ZLIndicatorOscillator, ZLIndicatorOsc, ZLIndOscillator, ZLIndOsc, ECOscillator,


ECOsc, ErrorCorrectingOscillator, ErrorCorrectingOsc

Oscillation of a ZeroLagIndicator around its data

Lines:

• ec

Params:

• period (30)
• gainlimit (50)
• _movav (EMA)

PlotInfo:

• plot (True)
• legendloc (None)
• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
720
backtrader’s documentation Version-1.9.58.122

• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• ec:
• _0: - _name (osc)

haDelta

Alias:

• haD

Heikin Ashi Delta. Defined by Dan Valcu in his book “Heikin-Ashi: How to Trade
Without Candlestick Patterns ”.

This indicator measures difference between Heikin Ashi close and open of Heikin
Ashi candles, the body of the candle.

To get signals add haDelta smoothed by 3 period moving average.

For correct use, the data for the indicator must have been previously passed by
the Heikin Ahsi filter.

Formula:

• haDelta = Heikin Ashi close - Heikin Ashi open


• smoothed = movav(haDelta, period)

Lines:

• haDelta
• smoothed

Params:

• period (3)
• movav (SMA)
• autoheikin (True)

PlotInfo:

• plot (True)
• legendloc (None)
721
backtrader’s documentation Version-1.9.58.122

• plotmaster (None)
• subplot (True)
• plotvaluetags (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotlinevalues (True)
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotyticks ([])

PlotLines:

• smoothed: - color (grey) - _fill_gt ((0, u’green’)) - _fill_lt ((0, u’red’))


• haDelta: - color (red)

TA-Lib Indicator Reference

TA-Lib Indicator Reference

ACOS

ACOS([input_arrays])

Vector Trigonometric ACos (Math Transform)

Inputs:
price: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()

722
backtrader’s documentation Version-1.9.58.122

• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

AD

AD([input_arrays])

Chaikin A/D Line (Volume Indicators)

Inputs:
prices: [‘high’, ‘low’, ‘close’, ‘volume’]
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

723
backtrader’s documentation Version-1.9.58.122

ADD

ADD([input_arrays])

Vector Arithmetic Add (Math Operators)

Inputs:
price0: (any ndarray) price1: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

ADOSC

ADOSC([input_arrays], [fastperiod=3], [slowperiod=10])

Chaikin A/D Oscillator (Volume Indicators)

Inputs:
prices: [‘high’, ‘low’, ‘close’, ‘volume’]
Parameters:
fastperiod: 3 slowperiod: 10
Outputs:
real
Lines:
724
backtrader’s documentation Version-1.9.58.122

• real

Params:

• fastperiod (3)
• slowperiod (10)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

ADX

ADX([input_arrays], [timeperiod=14])

Average Directional Movement Index (Momentum Indicators)

Inputs:
prices: [‘high’, ‘low’, ‘close’]
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)

725
backtrader’s documentation Version-1.9.58.122

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

ADXR

ADXR([input_arrays], [timeperiod=14])

Average Directional Movement Index Rating (Momentum Indicators)

Inputs:
prices: [‘high’, ‘low’, ‘close’]
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
726
backtrader’s documentation Version-1.9.58.122

• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

APO

APO([input_arrays], [fastperiod=12], [slowperiod=26], [matype=0])

Absolute Price Oscillator (Momentum Indicators)

Inputs:
price: (any ndarray)
Parameters:
fastperiod: 12 slowperiod: 26 matype: 0 (Simple Moving Average)
Outputs:
real
Lines:

• real

Params:

• fastperiod (12)
• slowperiod (26)
• matype (0)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)

727
backtrader’s documentation Version-1.9.58.122

• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

AROON

AROON([input_arrays], [timeperiod=14])

Aroon (Momentum Indicators)

Inputs:
prices: [‘high’, ‘low’]
Parameters:
timeperiod: 14
Outputs:
aroondown aroonup
Lines:

• aroondown
• aroonup

Params:

• timeperiod (14)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

728
backtrader’s documentation Version-1.9.58.122

• aroondown: - ls (–)
• aroonup: - ls (-)

AROONOSC

AROONOSC([input_arrays], [timeperiod=14])

Aroon Oscillator (Momentum Indicators)

Inputs:
prices: [‘high’, ‘low’]
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

ASIN

ASIN([input_arrays])

729
backtrader’s documentation Version-1.9.58.122

Vector Trigonometric ASin (Math Transform)

Inputs:
price: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

ATAN

ATAN([input_arrays])

Vector Trigonometric ATan (Math Transform)

Inputs:
price: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
730
backtrader’s documentation Version-1.9.58.122

• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

ATR

ATR([input_arrays], [timeperiod=14])

Average True Range (Volatility Indicators)

Inputs:
prices: [‘high’, ‘low’, ‘close’]
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])

731
backtrader’s documentation Version-1.9.58.122

• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

AVGPRICE

AVGPRICE([input_arrays])

Average Price (Price Transform)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

BBANDS

BBANDS([input_arrays], [timeperiod=5], [nbdevup=2], [nbdevdn=2], [matype=0])


732
backtrader’s documentation Version-1.9.58.122

Bollinger Bands (Overlap Studies)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 5 nbdevup: 2 nbdevdn: 2 matype: 0 (Simple Moving Average)
Outputs:
upperband middleband lowerband
Lines:

• upperband
• middleband
• lowerband

Params:

• timeperiod (5)
• nbdevup (2)
• nbdevdn (2)
• matype (0)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• middleband: - _samecolor (True) - ls (-)


• upperband:
• lowerband: - _samecolor (True)

BETA

BETA([input_arrays], [timeperiod=5])

733
backtrader’s documentation Version-1.9.58.122

Beta (Statistic Functions)

Inputs:
price0: (any ndarray) price1: (any ndarray)
Parameters:
timeperiod: 5
Outputs:
real
Lines:

• real

Params:

• timeperiod (5)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

BOP

BOP([input_arrays])

Balance Of Power (Momentum Indicators)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
real
Lines:
734
backtrader’s documentation Version-1.9.58.122

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

CCI

CCI([input_arrays], [timeperiod=14])

Commodity Channel Index (Momentum Indicators)

Inputs:
prices: [‘high’, ‘low’, ‘close’]
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
735
backtrader’s documentation Version-1.9.58.122

• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

CDL2CROWS

CDL2CROWS([input_arrays])

Two Crows (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

736
backtrader’s documentation Version-1.9.58.122

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDL2CROWS)

CDL3BLACKCROWS

CDL3BLACKCROWS([input_arrays])

Three Black Crows (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDL3BLACKCROWS)

CDL3INSIDE

CDL3INSIDE([input_arrays])

Three Inside Up/Down (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
737
backtrader’s documentation Version-1.9.58.122

Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDL3INSIDE)

CDL3LINESTRIKE

CDL3LINESTRIKE([input_arrays])

Three-Line Strike (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
738
backtrader’s documentation Version-1.9.58.122

• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDL3LINESTRIKE)

CDL3OUTSIDE

CDL3OUTSIDE([input_arrays])

Three Outside Up/Down (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

739
backtrader’s documentation Version-1.9.58.122

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDL3OUTSIDE)

CDL3STARSINSOUTH

CDL3STARSINSOUTH([input_arrays])

Three Stars In The South (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDL3STARSINSOUTH)

CDL3WHITESOLDIERS

CDL3WHITESOLDIERS([input_arrays])

Three Advancing White Soldiers (Pattern Recognition)

740
backtrader’s documentation Version-1.9.58.122

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDL3WHITESOLDIERS)

CDLABANDONEDBABY

CDLABANDONEDBABY([input_arrays], [penetration=0.3])

Abandoned Baby (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Parameters:
penetration: 0.3
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

741
backtrader’s documentation Version-1.9.58.122

Params:

• penetration (0.3)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLABANDONEDBABY)

CDLADVANCEBLOCK

CDLADVANCEBLOCK([input_arrays])

Advance Block (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
742
backtrader’s documentation Version-1.9.58.122

• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLADVANCEBLOCK)

CDLBELTHOLD

CDLBELTHOLD([input_arrays])

Belt-hold (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

743
backtrader’s documentation Version-1.9.58.122

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLBELTHOLD)

CDLBREAKAWAY

CDLBREAKAWAY([input_arrays])

Breakaway (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLBREAKAWAY)

CDLCLOSINGMARUBOZU

CDLCLOSINGMARUBOZU([input_arrays])

Closing Marubozu (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
744
backtrader’s documentation Version-1.9.58.122

Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name
(CDLCLOSINGMARUBOZU)

CDLCONCEALBABYSWALL

CDLCONCEALBABYSWALL([input_arrays])

Concealing Baby Swallow (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
745
backtrader’s documentation Version-1.9.58.122

• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name
(CDLCONCEALBABYSWALL)

CDLCOUNTERATTACK

CDLCOUNTERATTACK([input_arrays])

Counterattack (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)

746
backtrader’s documentation Version-1.9.58.122

• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLCOUNTERATTACK)

CDLDARKCLOUDCOVER

CDLDARKCLOUDCOVER([input_arrays], [penetration=0.5])

Dark Cloud Cover (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Parameters:
penetration: 0.5
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

Params:

• penetration (0.5)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

747
backtrader’s documentation Version-1.9.58.122

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLDARKCLOUDCOVER)

CDLDOJI

CDLDOJI([input_arrays])

Doji (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLDOJI)

CDLDOJISTAR

CDLDOJISTAR([input_arrays])

Doji Star (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
748
backtrader’s documentation Version-1.9.58.122

Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLDOJISTAR)

CDLDRAGONFLYDOJI

CDLDRAGONFLYDOJI([input_arrays])

Dragonfly Doji (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
749
backtrader’s documentation Version-1.9.58.122

• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLDRAGONFLYDOJI)

CDLENGULFING

CDLENGULFING([input_arrays])

Engulfing Pattern (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

750
backtrader’s documentation Version-1.9.58.122

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLENGULFING)

CDLEVENINGDOJISTAR

CDLEVENINGDOJISTAR([input_arrays], [penetration=0.3])

Evening Doji Star (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Parameters:
penetration: 0.3
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

Params:

• penetration (0.3)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLEVENINGDOJISTAR)

751
backtrader’s documentation Version-1.9.58.122

CDLEVENINGSTAR

CDLEVENINGSTAR([input_arrays], [penetration=0.3])

Evening Star (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Parameters:
penetration: 0.3
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

Params:

• penetration (0.3)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLEVENINGSTAR)

CDLGAPSIDESIDEWHITE

CDLGAPSIDESIDEWHITE([input_arrays])

Up/Down-gap side-by-side white lines (Pattern Recognition)


752
backtrader’s documentation Version-1.9.58.122

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLGAPSIDESIDEWHITE)

CDLGRAVESTONEDOJI

CDLGRAVESTONEDOJI([input_arrays])

Gravestone Doji (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

753
backtrader’s documentation Version-1.9.58.122

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLGRAVESTONEDOJI)

CDLHAMMER

CDLHAMMER([input_arrays])

Hammer (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)

754
backtrader’s documentation Version-1.9.58.122

• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLHAMMER)

CDLHANGINGMAN

CDLHANGINGMAN([input_arrays])

Hanging Man (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLHANGINGMAN)

CDLHARAMI

CDLHARAMI([input_arrays])
755
backtrader’s documentation Version-1.9.58.122

Harami Pattern (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLHARAMI)

CDLHARAMICROSS

CDLHARAMICROSS([input_arrays])

Harami Cross Pattern (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

756
backtrader’s documentation Version-1.9.58.122

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLHARAMICROSS)

CDLHIGHWAVE

CDLHIGHWAVE([input_arrays])

High-Wave Candle (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)

757
backtrader’s documentation Version-1.9.58.122

• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLHIGHWAVE)

CDLHIKKAKE

CDLHIKKAKE([input_arrays])

Hikkake Pattern (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLHIKKAKE)

758
backtrader’s documentation Version-1.9.58.122

CDLHIKKAKEMOD

CDLHIKKAKEMOD([input_arrays])

Modified Hikkake Pattern (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLHIKKAKEMOD)

CDLHOMINGPIGEON

CDLHOMINGPIGEON([input_arrays])

Homing Pigeon (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:
759
backtrader’s documentation Version-1.9.58.122

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLHOMINGPIGEON)

CDLIDENTICAL3CROWS

CDLIDENTICAL3CROWS([input_arrays])

Identical Three Crows (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)

760
backtrader’s documentation Version-1.9.58.122

• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLIDENTICAL3CROWS)

CDLINNECK

CDLINNECK([input_arrays])

In-Neck Pattern (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

761
backtrader’s documentation Version-1.9.58.122

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLINNECK)

CDLINVERTEDHAMMER

CDLINVERTEDHAMMER([input_arrays])

Inverted Hammer (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLINVERTEDHAMMER)

CDLKICKING

CDLKICKING([input_arrays])

Kicking (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
762
backtrader’s documentation Version-1.9.58.122

Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLKICKING)

CDLKICKINGBYLENGTH

CDLKICKINGBYLENGTH([input_arrays])

Kicking - bull/bear determined by the longer marubozu (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
763
backtrader’s documentation Version-1.9.58.122

• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLKICKINGBYLENGTH)

CDLLADDERBOTTOM

CDLLADDERBOTTOM([input_arrays])

Ladder Bottom (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

764
backtrader’s documentation Version-1.9.58.122

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLLADDERBOTTOM)

CDLLONGLEGGEDDOJI

CDLLONGLEGGEDDOJI([input_arrays])

Long Legged Doji (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLLONGLEGGEDDOJI)

CDLLONGLINE

CDLLONGLINE([input_arrays])

Long Line Candle (Pattern Recognition)

765
backtrader’s documentation Version-1.9.58.122

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLLONGLINE)

CDLMARUBOZU

CDLMARUBOZU([input_arrays])

Marubozu (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

766
backtrader’s documentation Version-1.9.58.122

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLMARUBOZU)

CDLMATCHINGLOW

CDLMATCHINGLOW([input_arrays])

Matching Low (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)

767
backtrader’s documentation Version-1.9.58.122

• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLMATCHINGLOW)

CDLMATHOLD

CDLMATHOLD([input_arrays], [penetration=0.5])

Mat Hold (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Parameters:
penetration: 0.5
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

Params:

• penetration (0.5)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

768
backtrader’s documentation Version-1.9.58.122

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLMATHOLD)

CDLMORNINGDOJISTAR

CDLMORNINGDOJISTAR([input_arrays], [penetration=0.3])

Morning Doji Star (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Parameters:
penetration: 0.3
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

Params:

• penetration (0.3)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLMORNINGDOJISTAR)

CDLMORNINGSTAR

769
backtrader’s documentation Version-1.9.58.122

CDLMORNINGSTAR([input_arrays], [penetration=0.3])

Morning Star (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Parameters:
penetration: 0.3
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

Params:

• penetration (0.3)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLMORNINGSTAR)

CDLONNECK

CDLONNECK([input_arrays])

On-Neck Pattern (Pattern Recognition)

Inputs:
770
backtrader’s documentation Version-1.9.58.122

prices: [‘open’, ‘high’, ‘low’, ‘close’]


Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLONNECK)

CDLPIERCING

CDLPIERCING([input_arrays])

Piercing Pattern (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
771
backtrader’s documentation Version-1.9.58.122

• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLPIERCING)

CDLRICKSHAWMAN

CDLRICKSHAWMAN([input_arrays])

Rickshaw Man (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)

772
backtrader’s documentation Version-1.9.58.122

• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLRICKSHAWMAN)

CDLRISEFALL3METHODS

CDLRISEFALL3METHODS([input_arrays])

Rising/Falling Three Methods (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLRISEFALL3METHODS)

CDLSEPARATINGLINES

CDLSEPARATINGLINES([input_arrays])

773
backtrader’s documentation Version-1.9.58.122

Separating Lines (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLSEPARATINGLINES)

CDLSHOOTINGSTAR

CDLSHOOTINGSTAR([input_arrays])

Shooting Star (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

774
backtrader’s documentation Version-1.9.58.122

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLSHOOTINGSTAR)

CDLSHORTLINE

CDLSHORTLINE([input_arrays])

Short Line Candle (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)

775
backtrader’s documentation Version-1.9.58.122

• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLSHORTLINE)

CDLSPINNINGTOP

CDLSPINNINGTOP([input_arrays])

Spinning Top (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLSPINNINGTOP)

776
backtrader’s documentation Version-1.9.58.122

CDLSTALLEDPATTERN

CDLSTALLEDPATTERN([input_arrays])

Stalled Pattern (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLSTALLEDPATTERN)

CDLSTICKSANDWICH

CDLSTICKSANDWICH([input_arrays])

Stick Sandwich (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:
777
backtrader’s documentation Version-1.9.58.122

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLSTICKSANDWICH)

CDLTAKURI

CDLTAKURI([input_arrays])

Takuri (Dragonfly Doji with very long lower shadow) (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)

778
backtrader’s documentation Version-1.9.58.122

• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLTAKURI)

CDLTASUKIGAP

CDLTASUKIGAP([input_arrays])

Tasuki Gap (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

779
backtrader’s documentation Version-1.9.58.122

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLTASUKIGAP)

CDLTHRUSTING

CDLTHRUSTING([input_arrays])

Thrusting Pattern (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLTHRUSTING)

CDLTRISTAR

CDLTRISTAR([input_arrays])

Tristar Pattern (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
780
backtrader’s documentation Version-1.9.58.122

Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLTRISTAR)

CDLUNIQUE3RIVER

CDLUNIQUE3RIVER([input_arrays])

Unique 3 River (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
781
backtrader’s documentation Version-1.9.58.122

• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLUNIQUE3RIVER)

CDLUPSIDEGAP2CROWS

CDLUPSIDEGAP2CROWS([input_arrays])

Upside Gap Two Crows (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

782
backtrader’s documentation Version-1.9.58.122

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name (CDLUPSIDEGAP2CROWS)

CDLXSIDEGAP3METHODS

CDLXSIDEGAP3METHODS([input_arrays])

Upside/Downside Gap Three Methods (Pattern Recognition)

Inputs:
prices: [‘open’, ‘high’, ‘low’, ‘close’]
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer
• _candleplot

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (True)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - _plotskip (True)


• _candleplot: - marker (d) - fillstyle (full) - markersize (7.0) - ls () - _name
(CDLXSIDEGAP3METHODS)

CEIL

CEIL([input_arrays])

Vector Ceil (Math Transform)


783
backtrader’s documentation Version-1.9.58.122

Inputs:
price: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

CMO

CMO([input_arrays], [timeperiod=14])

Chande Momentum Oscillator (Momentum Indicators)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)
784
backtrader’s documentation Version-1.9.58.122

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

CORREL

CORREL([input_arrays], [timeperiod=30])

Pearson’s Correlation Coefficient (r) (Statistic Functions)

Inputs:
price0: (any ndarray) price1: (any ndarray)
Parameters:
timeperiod: 30
Outputs:
real
Lines:

• real

Params:

• timeperiod (30)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
785
backtrader’s documentation Version-1.9.58.122

• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

COS

COS([input_arrays])

Vector Trigonometric Cos (Math Transform)

Inputs:
price: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

786
backtrader’s documentation Version-1.9.58.122

COSH

COSH([input_arrays])

Vector Trigonometric Cosh (Math Transform)

Inputs:
price: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

DEMA

DEMA([input_arrays], [timeperiod=30])

Double Exponential Moving Average (Overlap Studies)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 30
Outputs:
real
Lines:
787
backtrader’s documentation Version-1.9.58.122

• real

Params:

• timeperiod (30)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

DIV

DIV([input_arrays])

Vector Arithmetic Div (Math Operators)

Inputs:
price0: (any ndarray) price1: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
788
backtrader’s documentation Version-1.9.58.122

• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

DX

DX([input_arrays], [timeperiod=14])

Directional Movement Index (Momentum Indicators)

Inputs:
prices: [‘high’, ‘low’, ‘close’]
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)

789
backtrader’s documentation Version-1.9.58.122

• plotyticks ([])

PlotLines:

• real: - ls (-)

EMA

EMA([input_arrays], [timeperiod=30])

Exponential Moving Average (Overlap Studies)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 30
Outputs:
real
Lines:

• real

Params:

• timeperiod (30)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

EXP
790
backtrader’s documentation Version-1.9.58.122

EXP([input_arrays])

Vector Arithmetic Exp (Math Transform)

Inputs:
price: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

FLOOR

FLOOR([input_arrays])

Vector Floor (Math Transform)

Inputs:
price: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:
791
backtrader’s documentation Version-1.9.58.122

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

HT_DCPERIOD

HT_DCPERIOD([input_arrays])

Hilbert Transform - Dominant Cycle Period (Cycle Indicators)

Inputs:
price: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

792
backtrader’s documentation Version-1.9.58.122

PlotLines:

• real: - ls (-)

HT_DCPHASE

HT_DCPHASE([input_arrays])

Hilbert Transform - Dominant Cycle Phase (Cycle Indicators)

Inputs:
price: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

HT_PHASOR

HT_PHASOR([input_arrays])

Hilbert Transform - Phasor Components (Cycle Indicators)

Inputs:
price: (any ndarray)
Outputs:
793
backtrader’s documentation Version-1.9.58.122

inphase quadrature
Lines:

• inphase
• quadrature

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• inphase: - ls (-)
• quadrature: - ls (–)

HT_SINE

HT_SINE([input_arrays])

Hilbert Transform - SineWave (Cycle Indicators)

Inputs:
price: (any ndarray)
Outputs:
sine leadsine
Lines:

• sine
• leadsine

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
794
backtrader’s documentation Version-1.9.58.122

• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• leadsine: - ls (–)
• sine: - ls (-)

HT_TRENDLINE

HT_TRENDLINE([input_arrays])

Hilbert Transform - Instantaneous Trendline (Overlap Studies)

Inputs:
price: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

795
backtrader’s documentation Version-1.9.58.122

• real: - ls (-)

HT_TRENDMODE

HT_TRENDMODE([input_arrays])

Hilbert Transform - Trend vs Cycle Mode (Cycle Indicators)

Inputs:
price: (any ndarray)
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - ls (-)

KAMA

KAMA([input_arrays], [timeperiod=30])

Kaufman Adaptive Moving Average (Overlap Studies)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 30
Outputs:
796
backtrader’s documentation Version-1.9.58.122

real
Lines:

• real

Params:

• timeperiod (30)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

LINEARREG

LINEARREG([input_arrays], [timeperiod=14])

Linear Regression (Statistic Functions)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

797
backtrader’s documentation Version-1.9.58.122

• timeperiod (14)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

LINEARREG_ANGLE

LINEARREG_ANGLE([input_arrays], [timeperiod=14])

Linear Regression Angle (Statistic Functions)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
798
backtrader’s documentation Version-1.9.58.122

• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

LINEARREG_INTERCEPT

LINEARREG_INTERCEPT([input_arrays], [timeperiod=14])

Linear Regression Intercept (Statistic Functions)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)

799
backtrader’s documentation Version-1.9.58.122

• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

LINEARREG_SLOPE

LINEARREG_SLOPE([input_arrays], [timeperiod=14])

Linear Regression Slope (Statistic Functions)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)
800
backtrader’s documentation Version-1.9.58.122

LN

LN([input_arrays])

Vector Log Natural (Math Transform)

Inputs:
price: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

LOG10

LOG10([input_arrays])

Vector Log10 (Math Transform)

Inputs:
price: (any ndarray)
Outputs:
real
Lines:

• real
801
backtrader’s documentation Version-1.9.58.122

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

MA

MA([input_arrays], [timeperiod=30], [matype=0])

Moving average (Overlap Studies)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 30 matype: 0 (Simple Moving Average)
Outputs:
real
Lines:

• real

Params:

• timeperiod (30)
• matype (0)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
802
backtrader’s documentation Version-1.9.58.122

• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

MACD

MACD([input_arrays], [fastperiod=12], [slowperiod=26], [signalperiod=9])

Moving Average Convergence/Divergence (Momentum Indicators)

Inputs:
price: (any ndarray)
Parameters:
fastperiod: 12 slowperiod: 26 signalperiod: 9
Outputs:
macd macdsignal macdhist
Lines:

• macd
• macdsignal
• macdhist

Params:

• fastperiod (12)
• slowperiod (26)
• signalperiod (9)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])

803
backtrader’s documentation Version-1.9.58.122

• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• macdsignal: - ls (–)
• macd: - ls (-)
• macdhist: - _method (bar)

MACDEXT

MACDEXT([input_arrays], [fastperiod=12], [fastmatype=0], [slowperiod=26],


[slowmatype=0], [signalperiod=9], [signalmatype=0])

MACD with controllable MA type (Momentum Indicators)

Inputs:
price: (any ndarray)
Parameters:
fastperiod: 12 fastmatype: 0 slowperiod: 26 slowmatype: 0 signalperiod: 9 signalmatype: 0
Outputs:
macd macdsignal macdhist
Lines:

• macd
• macdsignal
• macdhist

Params:

• fastperiod (12)
• fastmatype (0)
• slowperiod (26)
• slowmatype (0)
• signalperiod (9)
• signalmatype (0)

PlotInfo:

• subplot (True)
• plot (True)

804
backtrader’s documentation Version-1.9.58.122

• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• macdsignal: - ls (–)
• macd: - ls (-)
• macdhist: - _method (bar)

MACDFIX

MACDFIX([input_arrays], [signalperiod=9])

Moving Average Convergence/Divergence Fix 12/26 (Momentum Indicators)

Inputs:
price: (any ndarray)
Parameters:
signalperiod: 9
Outputs:
macd macdsignal macdhist
Lines:

• macd
• macdsignal
• macdhist

Params:

• signalperiod (9)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()

805
backtrader’s documentation Version-1.9.58.122

• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• macdsignal: - ls (–)
• macd: - ls (-)
• macdhist: - _method (bar)

MAMA

MAMA([input_arrays], [fastlimit=0.5], [slowlimit=0.05])

MESA Adaptive Moving Average (Overlap Studies)

Inputs:
price: (any ndarray)
Parameters:
fastlimit: 0.5 slowlimit: 0.05
Outputs:
mama fama
Lines:

• mama
• fama

Params:

• fastlimit (0.5)
• slowlimit (0.05)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])

806
backtrader’s documentation Version-1.9.58.122

• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• mama: - ls (-)
• fama: - ls (–)

MAVP

MAVP([input_arrays], [minperiod=2], [maxperiod=30], [matype=0])

Moving average with variable period (Overlap Studies)

Inputs:
price: (any ndarray) periods: (any ndarray)
Parameters:
minperiod: 2 maxperiod: 30 matype: 0 (Simple Moving Average)
Outputs:
real
Lines:

• real

Params:

• minperiod (2)
• maxperiod (30)
• matype (0)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)

807
backtrader’s documentation Version-1.9.58.122

• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

MAX

MAX([input_arrays], [timeperiod=30])

Highest value over a specified period (Math Operators)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 30
Outputs:
real
Lines:

• real

Params:

• timeperiod (30)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)
808
backtrader’s documentation Version-1.9.58.122

MAXINDEX

MAXINDEX([input_arrays], [timeperiod=30])

Index of highest value over a specified period (Math Operators)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 30
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer

Params:

• timeperiod (30)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - ls (-)

MEDPRICE

MEDPRICE([input_arrays])

Median Price (Price Transform)

Inputs:
809
backtrader’s documentation Version-1.9.58.122

prices: [‘high’, ‘low’]


Outputs:
real
Lines:

• real

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

MFI

MFI([input_arrays], [timeperiod=14])

Money Flow Index (Momentum Indicators)

Inputs:
prices: [‘high’, ‘low’, ‘close’, ‘volume’]
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)

810
backtrader’s documentation Version-1.9.58.122

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

MIDPOINT

MIDPOINT([input_arrays], [timeperiod=14])

MidPoint over period (Overlap Studies)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
811
backtrader’s documentation Version-1.9.58.122

• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

MIDPRICE

MIDPRICE([input_arrays], [timeperiod=14])

Midpoint Price over period (Overlap Studies)

Inputs:
prices: [‘high’, ‘low’]
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)

812
backtrader’s documentation Version-1.9.58.122

• plotyticks ([])

PlotLines:

• real: - ls (-)

MIN

MIN([input_arrays], [timeperiod=30])

Lowest value over a specified period (Math Operators)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 30
Outputs:
real
Lines:

• real

Params:

• timeperiod (30)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

MININDEX
813
backtrader’s documentation Version-1.9.58.122

MININDEX([input_arrays], [timeperiod=30])

Index of lowest value over a specified period (Math Operators)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 30
Outputs:
integer (values are -100, 0 or 100)
Lines:

• integer

Params:

• timeperiod (30)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• integer: - ls (-)

MINMAX

MINMAX([input_arrays], [timeperiod=30])

Lowest and highest values over a specified period (Math Operators)

Inputs:
price: (any ndarray)
Parameters:
814
backtrader’s documentation Version-1.9.58.122

timeperiod: 30
Outputs:
min max
Lines:

• min
• max

Params:

• timeperiod (30)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• max: - ls (-)
• min: - ls (-)

MINMAXINDEX

MINMAXINDEX([input_arrays], [timeperiod=30])

Indexes of lowest and highest values over a specified period (Math Operators)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 30
Outputs:
minidx maxidx
Lines:

815
backtrader’s documentation Version-1.9.58.122

• minidx
• maxidx

Params:

• timeperiod (30)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• maxidx: - ls (-)
• minidx: - ls (-)

MINUS_DI

MINUS_DI([input_arrays], [timeperiod=14])

Minus Directional Indicator (Momentum Indicators)

Inputs:
prices: [‘high’, ‘low’, ‘close’]
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)
816
backtrader’s documentation Version-1.9.58.122

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

MINUS_DM

MINUS_DM([input_arrays], [timeperiod=14])

Minus Directional Movement (Momentum Indicators)

Inputs:
prices: [‘high’, ‘low’]
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
817
backtrader’s documentation Version-1.9.58.122

• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

MOM

MOM([input_arrays], [timeperiod=10])

Momentum (Momentum Indicators)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 10
Outputs:
real
Lines:

• real

Params:

• timeperiod (10)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)

818
backtrader’s documentation Version-1.9.58.122

• plotyticks ([])

PlotLines:

• real: - ls (-)

MULT

MULT([input_arrays])

Vector Arithmetic Mult (Math Operators)

Inputs:
price0: (any ndarray) price1: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

NATR

NATR([input_arrays], [timeperiod=14])

Normalized Average True Range (Volatility Indicators)

Inputs:
819
backtrader’s documentation Version-1.9.58.122

prices: [‘high’, ‘low’, ‘close’]


Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

OBV

OBV([input_arrays])

On Balance Volume (Volume Indicators)

Inputs:
price: (any ndarray) prices: [‘volume’]
Outputs:
real
Lines:

• real

820
backtrader’s documentation Version-1.9.58.122

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

PLUS_DI

PLUS_DI([input_arrays], [timeperiod=14])

Plus Directional Indicator (Momentum Indicators)

Inputs:
prices: [‘high’, ‘low’, ‘close’]
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
821
backtrader’s documentation Version-1.9.58.122

• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

PLUS_DM

PLUS_DM([input_arrays], [timeperiod=14])

Plus Directional Movement (Momentum Indicators)

Inputs:
prices: [‘high’, ‘low’]
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)

822
backtrader’s documentation Version-1.9.58.122

• plotyticks ([])

PlotLines:

• real: - ls (-)

PPO

PPO([input_arrays], [fastperiod=12], [slowperiod=26], [matype=0])

Percentage Price Oscillator (Momentum Indicators)

Inputs:
price: (any ndarray)
Parameters:
fastperiod: 12 slowperiod: 26 matype: 0 (Simple Moving Average)
Outputs:
real
Lines:

• real

Params:

• fastperiod (12)
• slowperiod (26)
• matype (0)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)
823
backtrader’s documentation Version-1.9.58.122

ROC

ROC([input_arrays], [timeperiod=10])

Rate of change : ((price/prevPrice)-1)*100 (Momentum Indicators)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 10
Outputs:
real
Lines:

• real

Params:

• timeperiod (10)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

ROCP

ROCP([input_arrays], [timeperiod=10])

Rate of change Percentage: (price-prevPrice)/prevPrice (Momentum Indicators)

Inputs:
824
backtrader’s documentation Version-1.9.58.122

price: (any ndarray)


Parameters:
timeperiod: 10
Outputs:
real
Lines:

• real

Params:

• timeperiod (10)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

ROCR

ROCR([input_arrays], [timeperiod=10])

Rate of change ratio: (price/prevPrice) (Momentum Indicators)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 10
Outputs:
real
Lines:

825
backtrader’s documentation Version-1.9.58.122

• real

Params:

• timeperiod (10)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

ROCR100

ROCR100([input_arrays], [timeperiod=10])

Rate of change ratio 100 scale: (price/prevPrice)*100 (Momentum Indicators)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 10
Outputs:
real
Lines:

• real

Params:

• timeperiod (10)

PlotInfo:
826
backtrader’s documentation Version-1.9.58.122

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

RSI

RSI([input_arrays], [timeperiod=14])

Relative Strength Index (Momentum Indicators)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])

827
backtrader’s documentation Version-1.9.58.122

• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

SAR

SAR([input_arrays], [acceleration=0.02], [maximum=0.2])

Parabolic SAR (Overlap Studies)

Inputs:
prices: [‘high’, ‘low’]
Parameters:
acceleration: 0.02 maximum: 0.2
Outputs:
real
Lines:

• real

Params:

• acceleration (0.02)
• maximum (0.2)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)

828
backtrader’s documentation Version-1.9.58.122

• plotyticks ([])

PlotLines:

• real: - ls (-)

SAREXT

SAREXT([input_arrays], [startvalue=0], [offsetonreverse=0],


[accelerationinitlong=0.02], [accelerationlong=0.02], [accelerationmaxlong=0.2],
[accelerationinitshort=0.02], [accelerationshort=0.02],
[accelerationmaxshort=0.2])

Parabolic SAR - Extended (Overlap Studies)

Inputs:
prices: [‘high’, ‘low’]
Parameters:
startvalue: 0 offsetonreverse: 0 accelerationinitlong: 0.02 accelerationlong: 0.02 accelerationmaxlong: 0.2
accelerationinitshort: 0.02 accelerationshort: 0.02 accelerationmaxshort: 0.2
Outputs:
real
Lines:

• real

Params:

• startvalue (0)
• offsetonreverse (0)
• accelerationinitlong (0.02)
• accelerationlong (0.02)
• accelerationmaxlong (0.2)
• accelerationinitshort (0.02)
• accelerationshort (0.02)
• accelerationmaxshort (0.2)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])

829
backtrader’s documentation Version-1.9.58.122

• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

SIN

SIN([input_arrays])

Vector Trigonometric Sin (Math Transform)

Inputs:
price: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

SINH

830
backtrader’s documentation Version-1.9.58.122

SINH([input_arrays])

Vector Trigonometric Sinh (Math Transform)

Inputs:
price: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

SMA

SMA([input_arrays], [timeperiod=30])

Simple Moving Average (Overlap Studies)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 30
Outputs:
real
Lines:

• real
831
backtrader’s documentation Version-1.9.58.122

Params:

• timeperiod (30)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

SQRT

SQRT([input_arrays])

Vector Square Root (Math Transform)

Inputs:
price: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
832
backtrader’s documentation Version-1.9.58.122

• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

STDDEV

STDDEV([input_arrays], [timeperiod=5], [nbdev=1])

Standard Deviation (Statistic Functions)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 5 nbdev: 1
Outputs:
real
Lines:

• real

Params:

• timeperiod (5)
• nbdev (1)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

833
backtrader’s documentation Version-1.9.58.122

PlotLines:

• real: - ls (-)

STOCH

STOCH([input_arrays], [fastk_period=5], [slowk_period=3], [slowk_matype=0],


[slowd_period=3], [slowd_matype=0])

Stochastic (Momentum Indicators)

Inputs:
prices: [‘high’, ‘low’, ‘close’]
Parameters:
fastk_period: 5 slowk_period: 3 slowk_matype: 0 slowd_period: 3 slowd_matype: 0
Outputs:
slowk slowd
Lines:

• slowk
• slowd

Params:

• fastk_period (5)
• slowk_period (3)
• slowk_matype (0)
• slowd_period (3)
• slowd_matype (0)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

834
backtrader’s documentation Version-1.9.58.122

PlotLines:

• slowk: - ls (–)
• slowd: - ls (–)

STOCHF

STOCHF([input_arrays], [fastk_period=5], [fastd_period=3], [fastd_matype=0])

Stochastic Fast (Momentum Indicators)

Inputs:
prices: [‘high’, ‘low’, ‘close’]
Parameters:
fastk_period: 5 fastd_period: 3 fastd_matype: 0
Outputs:
fastk fastd
Lines:

• fastk
• fastd

Params:

• fastk_period (5)
• fastd_period (3)
• fastd_matype (0)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• fastk: - ls (-)
835
backtrader’s documentation Version-1.9.58.122

• fastd: - ls (-)

STOCHRSI

STOCHRSI([input_arrays], [timeperiod=14], [fastk_period=5], [fastd_period=3],


[fastd_matype=0])

Stochastic Relative Strength Index (Momentum Indicators)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 14 fastk_period: 5 fastd_period: 3 fastd_matype: 0
Outputs:
fastk fastd
Lines:

• fastk
• fastd

Params:

• timeperiod (14)
• fastk_period (5)
• fastd_period (3)
• fastd_matype (0)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• fastk: - ls (-)

836
backtrader’s documentation Version-1.9.58.122

• fastd: - ls (-)

SUB

SUB([input_arrays])

Vector Arithmetic Substraction (Math Operators)

Inputs:
price0: (any ndarray) price1: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

SUM

SUM([input_arrays], [timeperiod=30])

Summation (Math Operators)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 30
Outputs:
837
backtrader’s documentation Version-1.9.58.122

real
Lines:

• real

Params:

• timeperiod (30)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

T3

T3([input_arrays], [timeperiod=5], [vfactor=0.7])

Triple Exponential Moving Average (T3) (Overlap Studies)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 5 vfactor: 0.7
Outputs:
real
Lines:

• real

Params:

838
backtrader’s documentation Version-1.9.58.122

• timeperiod (5)
• vfactor (0.7)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

TAN

TAN([input_arrays])

Vector Trigonometric Tan (Math Transform)

Inputs:
price: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])

839
backtrader’s documentation Version-1.9.58.122

• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

TANH

TANH([input_arrays])

Vector Trigonometric Tanh (Math Transform)

Inputs:
price: (any ndarray)
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

TEMA

TEMA([input_arrays], [timeperiod=30])
840
backtrader’s documentation Version-1.9.58.122

Triple Exponential Moving Average (Overlap Studies)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 30
Outputs:
real
Lines:

• real

Params:

• timeperiod (30)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

TRANGE

TRANGE([input_arrays])

True Range (Volatility Indicators)

Inputs:
prices: [‘high’, ‘low’, ‘close’]
Outputs:
real
Lines:
841
backtrader’s documentation Version-1.9.58.122

• real

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

TRIMA

TRIMA([input_arrays], [timeperiod=30])

Triangular Moving Average (Overlap Studies)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 30
Outputs:
real
Lines:

• real

Params:

• timeperiod (30)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
842
backtrader’s documentation Version-1.9.58.122

• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

TRIX

TRIX([input_arrays], [timeperiod=30])

1-day Rate-Of-Change (ROC) of a Triple Smooth EMA (Momentum Indicators)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 30
Outputs:
real
Lines:

• real

Params:

• timeperiod (30)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)

843
backtrader’s documentation Version-1.9.58.122

• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

TSF

TSF([input_arrays], [timeperiod=14])

Time Series Forecast (Statistic Functions)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)
844
backtrader’s documentation Version-1.9.58.122

TYPPRICE

TYPPRICE([input_arrays])

Typical Price (Price Transform)

Inputs:
prices: [‘high’, ‘low’, ‘close’]
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

ULTOSC

ULTOSC([input_arrays], [timeperiod1=7], [timeperiod2=14], [timeperiod3=28])

Ultimate Oscillator (Momentum Indicators)

Inputs:
prices: [‘high’, ‘low’, ‘close’]
Parameters:
timeperiod1: 7 timeperiod2: 14 timeperiod3: 28
Outputs:
real
Lines:
845
backtrader’s documentation Version-1.9.58.122

• real

Params:

• timeperiod1 (7)
• timeperiod2 (14)
• timeperiod3 (28)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

VAR

VAR([input_arrays], [timeperiod=5], [nbdev=1])

Variance (Statistic Functions)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 5 nbdev: 1
Outputs:
real
Lines:

• real

Params:

• timeperiod (5)
846
backtrader’s documentation Version-1.9.58.122

• nbdev (1)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

WCLPRICE

WCLPRICE([input_arrays])

Weighted Close Price (Price Transform)

Inputs:
prices: [‘high’, ‘low’, ‘close’]
Outputs:
real
Lines:

• real

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)

847
backtrader’s documentation Version-1.9.58.122

• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

WILLR

WILLR([input_arrays], [timeperiod=14])

Williams’ %R (Momentum Indicators)

Inputs:
prices: [‘high’, ‘low’, ‘close’]
Parameters:
timeperiod: 14
Outputs:
real
Lines:

• real

Params:

• timeperiod (14)

PlotInfo:

• subplot (True)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

848
backtrader’s documentation Version-1.9.58.122

• real: - ls (-)

WMA

WMA([input_arrays], [timeperiod=30])

Weighted Moving Average (Overlap Studies)

Inputs:
price: (any ndarray)
Parameters:
timeperiod: 30
Outputs:
real
Lines:

• real

Params:

• timeperiod (30)

PlotInfo:

• subplot (False)
• plot (True)
• plotskip (False)
• plotname ()
• plotforce (False)
• plotyhlines ([])
• plothlines ([])
• plotabove (False)
• plotymargin (0.0)
• plotlinelabels (False)
• plotmaster (None)
• plotyticks ([])

PlotLines:

• real: - ls (-)

Strategies Reference

Reference for the built-in strategies

849
backtrader’s documentation Version-1.9.58.122

MA_CrossOver

Alias:

• SMA_CrossOver

This is a long-only strategy which operates on a moving average cross

Note:

• Although the default

Buy Logic:

• No position is open on the data


• The fast moving averagecrosses over the slow strategy to the upside.

Sell Logic:

• A position exists on the data


• The fast moving average crosses over the slow strategy to the downside

Order Execution Type:

• Market

Lines:

• datetime

Params:

• fast (10)
• slow (30)
• _movav (<class ‘backtrader.indicators.sma.SMA’>)

SignalStrategy

This subclass of Strategy is meant to to auto-operate using signals.

Signals are usually indicators and the expected output values:

• > 0 is a long indication


• < 0 is a short indication

850
backtrader’s documentation Version-1.9.58.122

There are 5 types of Signals, broken in 2 groups.

Main Group:

• LONGSHORT: both long and short indications from this signal are taken
• LONG: - long indications are taken to go long - short indications are taken
to close the long position. But:
o If a LONGEXIT (see below) signal is in the system it will be used to exit the long
o If a SHORT signal is available and no LONGEXIT is available , it will be used to close a long before
opening a short
• SHORT: - short indications are taken to go short - long indications are
taken to close the short position. But:
o If a SHORTEXIT (see below) signal is in the system it will be used to exit the short
o If a LONG signal is available and no SHORTEXIT is available , it will be used to close a short before
opening a long

Exit Group:

This 2 signals are meant to override others and provide criteria for exitins a
long/short position

• LONGEXIT: short indications are taken to exit long positions


• SHORTEXIT: long indications are taken to exit short positions

Order Issuing

Orders execution type is Market and validity is None (Good until Canceled)

Params:

• signals (default: []): a list/tuple of lists/tuples that allows the


instantiation of the signals and allocation to the right type

This parameter is expected to be managed through cerebro.add_signal

• _accumulate (default: False): allow to enter the market (long/short) even


if already in the market
• _concurrent (default: False): allow orders to be issued even if orders are
already pending execution
• _data (default: None): if multiple datas are present in the system which is
the target for orders. This can be
o None: The first data in the system will be used
o An int: indicating the data that was inserted at that position
o An str: name given to the data when creating it (parameter name) or when adding it cerebro with
cerebro.adddata(..., name=)

851
backtrader’s documentation Version-1.9.58.122

o A data instance

Lines:

• datetime

Params:

• signals ([])
• _accumulate (False)
• _concurrent (False)
• _data (None)

852
backtrader’s documentation Version-1.9.58.122

Analyzers Reference

AnnualReturn

Calmar

DrawDown

TimeDrawDown

GrossLeverage

PositionsValue

PyFolio

LogReturnsRolling

PeriodStats

Returns

SharpeRatio

SharpeRatio_A

SQN

TimeReturn

TradeAnalyzer

Transactions

VWR

853
backtrader’s documentation Version-1.9.58.122

Observers Reference

Benchmark

class backtrader.observers.Benchmark

This observer stores the returns of the strategy and the return of a
reference asset which is one of the datas passed to the system.

Params:

• timeframe (default: None) If None then the complete return over the
entire backtested period will be reported
• compression (default: None)

Only used for sub-day timeframes to for example work on an hourly


timeframe by specifying “TimeFrame.Minutes” and 60 as compression

• data (default: None)

Reference asset to track to allow for comparison.

Note

this data must have been added to a cerebro instance with addata,
resampledata or replaydata.

• _doprenext (default: False)

Benchmarking will take place from the point at which the strategy
kicks in (i.e.: when the minimum period of the strategy has been
met).

Setting this to True will record benchmarking values from the


starting point of the data feeds

• firstopen (default: False)

Keepint it as False ensures that the 1st comparison point between the
value and the benchmark starts at 0%, because the benchmark will not
use its opening price.

See the TimeReturn analyzer reference for a full explanation of the


meaning of the parameter
854
backtrader’s documentation Version-1.9.58.122

• fund (default: None)

If None the actual mode of the broker (fundmode - True/False) will be


autodetected to decide if the returns are based on the total net
asset value or on the fund value. See set_fundmode in the broker
documentation

Set it to True or False for a specific behavior

Remember that at any moment of a run the current values can be checked by
looking at the lines by name at index 0.

Broker

class backtrader.observers.Broker(*args, **kwargs)

This observer keeps track of the current cash amount and portfolio value in
the broker (including the cash)

Params: None

Broker - Cash

class backtrader.observers.Cash(*args, **kwargs)

This observer keeps track of the current amount of cash in the broker

Params: None

Broker - Value

class backtrader.observers.Value(*args, **kwargs)

This observer keeps track of the current portfolio value in the broker
including the cash

Params:

• fund (default: None)

If None the actual mode of the broker (fundmode - True/False) will be


autodetected to decide if the returns are based on the total net

855
backtrader’s documentation Version-1.9.58.122

asset value or on the fund value. See set_fundmode in the broker


documentation

Set it to True or False for a specific behavior

BuySell

class backtrader.observers.BuySell(*args, **kwargs)

This observer keeps track of the individual buy/sell orders (individual


executions) and will plot them on the chart along the data around the
execution price level

Params:

• barplot (default: False) Plot buy signals below the minimum and sell
signals above the maximum.

If False it will plot on the average price of executions during a bar

• bardist (default: 0.015 1.5%) Distance to max/min when barplot is


True

DrawDown

class backtrader.observers.DrawDown

This observer keeps track of the current drawdown level (plotted) and the
maxdrawdown (not plotted) levels

Params:

• fund (default: None)

If None the actual mode of the broker (fundmode - True/False) will be


autodetected to decide if the returns are based on the total net
asset value or on the fund value. See set_fundmode in the broker
documentation

Set it to True or False for a specific behavior

856
backtrader’s documentation Version-1.9.58.122

TimeReturn

class backtrader.observers.TimeReturn

This observer stores the returns of the strategy.

Params:

• timeframe (default: None) If None then the complete return over the
entire backtested period will be reported

Pass TimeFrame.NoTimeFrame to consider the entire dataset with no


time constraints

• compression (default: None)

Only used for sub-day timeframes to for example work on an hourly


timeframe by specifying “TimeFrame.Minutes” and 60 as compression

• fund (default: None)

If None the actual mode of the broker (fundmode - True/False) will be


autodetected to decide if the returns are based on the total net
asset value or on the fund value. See set_fundmode in the broker
documentation

Set it to True or False for a specific behavior

Remember that at any moment of a run the current values can be checked by
looking at the lines by name at index 0.

Trades

class backtrader.observers.Trades

This observer keeps track of full trades and plot the PnL level achieved
when a trade is closed.

A trade is open when a position goes from 0 (or crossing over 0) to X and
is then closed when it goes back to 0 (or crosses over 0 in the opposite
direction)

Params: None

857
backtrader’s documentation Version-1.9.58.122

LogReturns

class backtrader.observers.LogReturns

This observer stores the log returns of the strategy or a

Params:

• timeframe (default: None) If None then the complete return over the
entire backtested period will be reported

Pass TimeFrame.NoTimeFrame to consider the entire dataset with no


time constraints

• compression (default: None)

Only used for sub-day timeframes to for example work on an hourly


timeframe by specifying “TimeFrame.Minutes” and 60 as compression

• fund (default: None)

If None the actual mode of the broker (fundmode - True/False) will be


autodetected to decide if the returns are based on the total net
asset value or on the fund value. See set_fundmode in the broker
documentation

Set it to True or False for a specific behavior

Remember that at any moment of a run the current values can be checked by
looking at the lines by name at index 0.

LogReturns2

class backtrader.observers.LogReturns2

Extends the observer LogReturns to show two instruments

FundValue

class backtrader.observers.FundValue(*args, **kwargs)

This observer keeps track of the current fund-like value

858
backtrader’s documentation Version-1.9.58.122

Params: None

FundShares

class backtrader.observers.FundShares(*args, **kwargs)¶

This observer keeps track of the current fund-like shares

Params: None

Sizers Reference

FixedSize¶

class backtrader.sizers.FixedSize

This sizer simply returns a fixed size for any operation. Size can be
controlled by number of tranches that a system wishes to use to scale into
trades by specifying the tranches parameter.

Params:

• stake (default: 1)
• tranches (default: 1)

FixedReverser

class backtrader.sizers.FixedReverser

This sizer returns the needes fixed size to reverse an open position or the
fixed size to open one

• To open a position: return the param stake


• To reverse a position: return 2 * stake

Params:

• stake (default: 1)

859
backtrader’s documentation Version-1.9.58.122

PercentSizer

class backtrader.sizers.PercentSizer

This sizer return percents of available cash

Params:

• percents (default: 20)

AllInSizer

class backtrader.sizers.AllInSizer

This sizer return all available cash of broker

Params:

• percents (default: 100)

Filters Reference

SessionFilter

class backtrader.filters.SessionFilter(data)

This class can be applied to a data source as a filter and will filter out
intraday bars which fall outside of the regular session times (ie: pre/post
market data)

This is a “non-simple” filter and must manage the stack of the data
(passed during init and __call__)

It needs no “last” method because it has nothing to deliver

SessionFilterSimple

class backtrader.filters.SessionFilterSimple(data)

860
backtrader’s documentation Version-1.9.58.122

This class can be applied to a data source as a filter and will filter out
intraday bars which fall outside of the regular session times (ie: pre/post
market data)

This is a “simple” filter and must NOT manage the stack of the data
(passed during init and __call__)

It needs no “last” method because it has nothing to deliver

Bar Management will be done by the SimpleFilterWrapper class made which is


added durint the DataBase.addfilter_simple call

SessionFilller

class backtrader.filters.SessionFiller(data)

Bar Filler for a Data Source inside the declared session start/end times.

The fill bars are constructed using the declared Data Source timeframe and
compression (used to calculate the intervening missing times)

Params:

• fill_price (def: None):

If None is passed, the closing price of the previous bar will be


used. To end up with a bar which for example takes time but it is not
displayed in a plot ... use float(‘Nan’)

• fill_vol (def: float(‘NaN’)):

Value to use to fill the missing volume

• fill_oi (def: float(‘NaN’)):

Value to use to fill the missing Open Interest

• skip_first_fill (def: True):

Upon seeing the 1st valid bar do not fill from the sessionstart up to
that bar

861
backtrader’s documentation Version-1.9.58.122

CalendarDays

class backtrader.filters.CalendarDays(data)

Bar Filler to add missing calendar days to trading days

Params:

• fill_price (def: None):

> 0: The given value to fill 0 or None: Use the last known closing
price -1: Use the midpoint of the last bar (High-Low average)

• fill_vol (def: float(‘NaN’)):

Value to use to fill the missing volume

• fill_oi (def: float(‘NaN’)):

Value to use to fill the missing Open Interest

BarReplayer_Open

class backtrader.filters.BarReplayer_Open(data)

This filters splits a bar in two parts:

• Open: the opening price of the bar will be used to deliver an initial
price bar in which the four components (OHLC) are equal

The volume/openinterest fields are 0 for this initial bar

• OHLC: the original bar is delivered complete with the original


volume/openinterest

The split simulates a replay without the need to use the replay filter.

DaySplitter_Close

class backtrader.filters.DaySplitter_Close(data)

Splits a daily bar in two parts simulating 2 ticks which will be used to
replay the data:
862
backtrader’s documentation Version-1.9.58.122

• First tick: OHLX

The Close will be replaced by the average of Open, High and Low

The session opening time is used for this tick

and

• Second tick: CCCC

The Close price will be used for the four components of the price

The session closing time is used for this tick

The volume will be split amongst the 2 ticks using the parameters:

• closevol (default: 0.5) The value indicate which percentage, in absolute terms from 0.0 to 1.0, has
to be assigned to the closing tick. The rest will be assigned to the OHLX tick.

This filter is meant to be used together with cerebro.replaydata

HeikinAshi

class backtrader.filters.HeikinAshi(data)

The filter remodels the open, high, low, close to make HeikinAshi
candlesticks

See:

• https://en.wikipedia.org/wiki/Candlestick_chart#Heikin_Ashi_candlesticks
• http://stockcharts.com/school/doku.php?id=chart_school:chart_analysis:heikin_ashi

HeikinAshi

class backtrader.filters.Renko(data)

Modify the data stream to draw Renko bars (or bricks)

Params:

• hilo (default: False) Use high and low instead of close to decide if
a new brick is needed
• size (default: None) The size to consider for each brick
863
backtrader’s documentation Version-1.9.58.122

• autosize (default: 20.0) If size is None, this will be used to


autocalculate the size of the bricks (simply dividing the current
price by the given value)
• dynamic (default: False) If True and using autosize, the size of the
bricks will be recalculated when moving to a new brick. This will of
course eliminate the perfect alignment of Renko bricks.
• align (default: 1.0) Factor use to align the price boundaries of the
bricks. If the price is for example 3563.25 and align is 10.0, the
resulting aligned price will be 3560. The calculation:
o 3563.25 / 10.0 = 356.325
o round it and remove the decimals -> 356
o 356 * 10.0 -> 3560

See:

• http://stockcharts.com/school/doku.php?id=chart_school:chart_analysis:renko

864

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy