To start with, let’s summarize in two lines how the approach to backtrader works:
- It is like a construction kit with a basic building block (
Cerebro) into which many different pieces can be plugged in
- The basic distribution contains many pieces like Indicators, Analyzers, Observers, Sizers, Filters, Data Feeds, Brokers, Commission/Asset Info Schemes, …
- New building blocks can be easily constructed from scratch or based on existing building block
- The basic building block (
Cerebro) does already some automatic "plugging in" to make it easier to work with the framework without having to worry about all the details.
As such the framework is pre-configured to offer a behavior with defaults such as:
- Work with a single/main data feed
- 10,000 units of monetary currency
- Stock trading
This may of may not fit everyone, but the important thing is: it can be customized to the individual needs of each trader/programmer
Trading Stocks: Integers
As stated above, the default configuration is for stock trading and when one is trading stocks one buys/sells complete shares, (i.e.: 1, 2 … 50 … 1000, and not amounts like
This means that when a user does the following in the default configuration:
# Apply 50% of the portfolio to buy the main asset
The following happens:
- The system calculates how many shares of the asset are needed, so that the value in the portfolio of the given asset is as close as possible to
- But because the default configuration is to work with shares the resulting number of shares will be an whole number, i.e.: an integer
Notice that the default configuration is to work with a single/main data feed, and that’s why the actual data is not specified in the call to
order_percent_target. When operating with multiple data feeds, one has to specify which data to acquire/sell (unless the main data is meant)
Trading Cryptocurrencies: Fractions
It is obvious that when trading cryptocurrencies, with even 20 decimals, one can buy “half of a bitcoin”.
The good thing is that one can actually change the information pertaining to the asset. This is achieved through the
CommissionInfo family of pluggable pieces.
It has to be admitted that the name is unfortunate, because the schemes do not only contain information about commission, but also about other things.
In the fractional scenario, the interest is this method of the scheme:
getsize(price, cash), which has the following docstring
Returns the needed size to meet a cash operation at a given price
The schemes are intimately related to the broker and through the broker api, the schemes can be added in the system.
The broker docs are at: Docs — Broker — https://www.backtrader.com/docu/broker/
And the relevant method is:
addcommissioninfo(comminfo, name=None). Where in addition to adding a scheme which applies to all assets (when
None), one can set schemes which apply only to assets with specific names.
Implementing the fractional scheme
This can be easily achieved by extending the existing basis scheme, named
def getsize(self, price, cash):
'''Returns fractional size for cash operation @price'''
return self.p.leverage * (cash / price)
Ditto and done. Subclassing
CommissionInfo and writing a one line method, the objective is achieved. Because the original scheme definition supports
leverage, this is taken into account into the calculation, just in case cryptocurrencies can be bought with leverage (for which the default value is
1.0, i.e.: no leverage)
Later in the code, the scheme will be added (controlled via a command line parameter) like this
if args.fractional: # use the fractional scheme if requested
I.e.: an instance (notice the
() to instantiate) of the subclassed scheme is added. As explained above, the
name parameter is not set and this means it will apply to all assets in the system.
Testing the Beast
A full script implementing a trivial moving average crossover for long/short positions is provided below which can be directly used in the shell. The default data feed for the test is one of the data feeds from the backtrader repository.
Integer Run: No Fractions — No Fun
A short trade with a size of
16 units has been opened. The entire log, not shown for obvious reasons, contains many other operations all with trades with whole sizes.
After the hard subclassing and one-lining work for the fractions …
V for Victory. The short trade has been opened with the same crossover, but this time with a fractional size of
Notice that the final portfolio value in the charts is different and that is because the actual trades sizes are different.
Yes, backtrader can. With the pluggable/extensible construction kit approach, it is easy to customize the behavior to the particular needs of the trader programmer.