Trading Cryptocurrency Fractional Sizes in backtrader

Originally @: https://www.backtrader.com/blog/posts/2019-08-29-fractional-sizes/fractional-sizes/

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
  • 1-day timeframe/compression combination
  • 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 1.5 or 1001.7589shares.

This means that when a user does the following in the default configuration:

def next(self):
# Apply 50% of the portfolio to buy the main asset
self.order_target_percent(target=0.5)

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 50%
  • But because the default configuration is to work with shares the resulting number of shares will be an whole number, i.e.: an integer

Note

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.

Some documentation: Docs — Commission Schemes — https://www.backtrader.com/docu/commission-schemes/commission-schemes/

Note

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 name is 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 CommissionInfo.

class CommInfoFractional(bt.CommissionInfo):
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
cerebro.broker.addcommissioninfo(CommInfoFractional())

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.

Fractional Run

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 -16.457437774427774

Notice that the final portfolio value in the charts is different and that is because the actual trades sizes are different.

Conclusion

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.

The script

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store