Get started

Requirements

Cryptrality is wrapped in a python package. So Python is required to use the software. Only Python3 (Python >= 3.4) is supported.

The library also depends on the talib C++ headers to be installed in the system

Installation

Note

It is strongly advised to use virtualenv to install the module locally.

From git with pip:

pip install git+https://git@github.com/Cryptrality/backtester.git

Conda environment:

name: cryptrality
channels:
  - conda-forge
  - defaults
dependencies:
  - python=3.8
  - ta-lib
  - sphinxcontrib-programoutput
  - sphinx_rtd_theme
  - pip:
    - "--editable=git+https://git@github.com/Cryptrality/backtester.git@main#egg=cryptrality"

After installation the cryptrality command line allows access to various modules:

$ cryptrality
Matplotlib is building the font cache; this may take a moment.
WARNING: proceeding without Binance client auth
usage: cryptrality [-h] {backtest,download_year,live} ...

Cryptrality backtest tool

positional arguments:
    backtest        Test a strategy code with historical data
    download_year   Download yearly historical data
    live            Run a strategy live on the exchangerm

optional arguments:
  -h, --help        show this help message and exit

This is version 0.01 - Francesco - 12/12/2021

Using the backtest sub-command

$ cryptrality backtest
error: the following arguments are required: strategy, -s/--start, -e/--end
WARNING: proceeding without Binance client auth
usage: cryptrality backtest -s START -e END [-o OUT] [--stats] [--plots]
                            [--chart_window {6h,24h,2d,7d,1M}]
                            [--exchange {binance_futures,binance_spot}]
                            [--hold_asset HOLD_ASSET]
                            strategy

positional arguments:
  strategy              strategy python source file

optional arguments:
  -s START, --start START
                        Start date in the format of dd-mm-yy
  -e END, --end END     End date in the format of dd-mm-yy
  -o OUT, --out OUT     Output folder
  --stats               Toggle on the report generation
  --plots               Toggle on the candlestick chart generation
  --chart_window {6h,24h,2d,7d,1M}
                        If plot is enabled, define the time window for the
                        candlestick data to be displayed
  --exchange {binance_futures,binance_spot}
                        Define the exchange to run the backtest
  --hold_asset HOLD_ASSET
                        Define the asset for the buy and hold comparison.
                        Default BTC-USD

Using the download_year sub-command

$ cryptrality download_year
error: the following arguments are required: symbol, -k/--kline, -y/--year
WARNING: proceeding without Binance client auth
usage: cryptrality download_year -k K -y [YEAR [YEAR ...]]
                                 [-e {binance_futures,binance_spot}]
                                 symbol

positional arguments:
  symbol                Symbol to download, eg ETHUSDT

optional arguments:
  -k K, --kline K       Granularity string, eg 1m 5m 1h.
  -y [YEAR [YEAR ...]], --year [YEAR [YEAR ...]]
                        Year of the data to download. full year needed eg 2020
  -e {binance_futures,binance_spot}, --exchange {binance_futures,binance_spot}
                        Supported exchanges

Example Run

This is an Test run with a simple EMA cross RSI example strategy:

import talib
from cryptrality.core import plot, plot_config
from cryptrality.misc import get_default_params

MARGIN_AMOUNT = 120
FUTURES_LEVERAGE = 2


BUY_VALUE = float(MARGIN_AMOUNT) * float(FUTURES_LEVERAGE)

TRADE_SYMBOLS = ["ETHUSDT", "BTCUSDT"]

CANDLE_PERIOD1 = "15m"
CANDLE_PERIOD2 = "1m"

MAX_DATA = 500

EMA_LONG = 40
EMA_SHORT = 10
RSI = 6


def initialize(state):
    plot_config(
        {
            "ema_long": {"plot": "root", "type": "line", "color": "black"},
            "ema_short": {"plot": "root", "type": "line", "color": "red"},
            "rsi": {"plot": "rsi", "type": "line", "color": "black"},
            "oversold": {"plot": "rsi", "type": "line", "color": "red"},
            "overbought": {"plot": "rsi", "type": "line", "color": "red"},
        }
    )
    state.params = {}
    state.balance_quoted = 0
    state.ema_values = {}
    state.params["DEFAULT"] = {
        "ema_long": EMA_LONG,
        "ema_short": EMA_SHORT,
        "rsi": RSI,
    }


@schedule(interval=CANDLE_PERIOD1, symbols=TRADE_SYMBOLS, window_size=MAX_DATA)
def ema_5m(state, dataMap):
    for symbol, data in dataMap.items():
        strategy_logic_5m(state, data, symbol)


def strategy_logic_5m(state, data, symbol):

    if data is None:
        return

    params = get_default_params(state, symbol)
    ema_long_period = params["ema_long"]
    ema_short_period = params["ema_short"]
    ema_long = talib.EMA(data["close"], timeperiod=ema_long_period)
    ema_short = talib.EMA(data["close"], timeperiod=ema_short_period)
    state.ema_values[symbol] = [ema_long[-1], ema_short[-1]]


@schedule(interval=CANDLE_PERIOD2, symbols=TRADE_SYMBOLS, window_size=MAX_DATA)
def rsi_1m(state, dataMap):
    for symbol, data in dataMap.items():
        strategy_logic_rsi_1m(state, data, symbol)


def strategy_logic_rsi_1m(state, data, symbol):
    if data is None:
        return

    params = get_default_params(state, symbol)
    try:
        ema_long, ema_short = state.ema_values[symbol]
    except KeyError:
        ema_long, ema_short = [None, None]
    rsi_period = params["rsi"]
    rsi = talib.RSI(data["close"], timeperiod=rsi_period)
    data_plot = {key: values[-1] for key, values in data.items()}
    data_plot["rsi"] = rsi[-1]
    data_plot["ema_long"] = ema_long
    data_plot["ema_short"] = ema_short
    data_plot["oversold"] = 10
    data_plot["overbought"] = 70

    if len(data["close"]) < rsi_period * 2:
        return

    position = get_open_position(symbol, side="LONG")
    has_position = position is not None
    buy = False
    sell = False
    if has_position:
        sell_quantity = position.quantity
    if (
        ema_long
        and not has_position
        and ema_long <= ema_short
        and rsi[-1] < 10
    ):
        buy = True
    elif ema_long and has_position and ema_long <= ema_short and rsi[-1] >= 70:
        sell = True
    if buy:
        order_market_value(symbol, value=BUY_VALUE, leverage=FUTURES_LEVERAGE)
        logger.info("buy %s %s" % (symbol, data["close"][-1]))
    elif sell:
        order_market_amount(
            symbol, quantity=-1 * sell_quantity, leverage=FUTURES_LEVERAGE
        )
        logger.info("sell %s %s" % (symbol, data["close"][-1]))
    plot(symbol, data_plot)

The source code is used with the following command line:

$ cryptrality backtest -s 22-1-22 -e 25-1-22 ../example_strategies/multi_symbols_ema_rsi.py
INFO : 2023-07-25 22:43:46,659 : Writing bot logs to folder summary_reports
INFO : 2023-07-25 22:43:46,659 : Writing strategy logs to folder summary_reports
INFO : 2023-07-25 22:43:46,659 : Writing execution logs to folder summary_reports
INFO : 2023-07-25 22:43:46,665 : Caching 15m klines for ETHUSDT
INFO : 2023-07-25 22:43:46,732 : Caching 15m klines for BTCUSDT
INFO : 2023-07-25 22:43:46,795 : Caching 1m klines for ETHUSDT
INFO : 2023-07-25 22:43:48,222 : Caching 1m klines for BTCUSDT
INFO : 2023-07-25 22:43:49,893 : buy BTCUSDT 35291.3
INFO : 2023-07-25 22:43:49,898 : sell BTCUSDT 35211.95
INFO : 2023-07-25 22:43:49,925 : buy BTCUSDT 35038.12
INFO : 2023-07-25 22:43:49,957 : buy ETHUSDT 2414.4
INFO : 2023-07-25 22:43:49,961 : sell ETHUSDT 2431.47
INFO : 2023-07-25 22:43:49,966 : sell BTCUSDT 35513.46
INFO : 2023-07-25 22:43:50,018 : buy ETHUSDT 2515.69
INFO : 2023-07-25 22:43:50,023 : sell ETHUSDT 2505.56
INFO : 2023-07-25 22:43:50,047 : buy ETHUSDT 2481.03
INFO : 2023-07-25 22:43:50,137 : sell ETHUSDT 2537.94
INFO : 2023-07-25 22:43:50,160 : buy ETHUSDT 2465.6
INFO : 2023-07-25 22:43:50,160 : buy BTCUSDT 35678.89
INFO : 2023-07-25 22:43:50,171 : sell ETHUSDT 2456.92
INFO : 2023-07-25 22:43:50,171 : sell BTCUSDT 35637.71
INFO : 2023-07-25 22:43:50,173 : buy BTCUSDT 35381.06
INFO : 2023-07-25 22:43:50,176 : buy ETHUSDT 2432.21
INFO : 2023-07-25 22:43:50,326 : sell BTCUSDT 34840.88
INFO : 2023-07-25 22:43:50,334 : sell ETHUSDT 2388.62
INFO : 2023-07-25 22:43:50,344 : buy BTCUSDT 35808.05
INFO : 2023-07-25 22:43:50,350 : sell BTCUSDT 35937.67
INFO : 2023-07-25 22:43:50,376 : buy BTCUSDT 36551.21
INFO : 2023-07-25 22:43:50,377 : buy ETHUSDT 2427.0
INFO : 2023-07-25 22:43:50,380 : sell ETHUSDT 2425.56
INFO : 2023-07-25 22:43:50,383 : sell BTCUSDT 36467.53
WARNING: proceeding without Binance client auth
Creating The Singleton Runner
Total PnL: -5.715964
		Number of winning trades 4 / 12

This is another example run with a more complex strategy provided in the examples

$ cryptrality backtest -s 22-1-22 -e 25-1-22 ../example_strategies/bayes_bollinger_multicoins_cooldown.py
INFO : 2023-07-25 22:43:52,450 : Writing bot logs to folder summary_reports
INFO : 2023-07-25 22:43:52,450 : Writing strategy logs to folder summary_reports
INFO : 2023-07-25 22:43:52,450 : Writing execution logs to folder summary_reports
INFO : 2023-07-25 22:43:52,464 : Caching 1d klines for MATICUSDT
INFO : 2023-07-25 22:43:52,504 : Caching 1d klines for MANAUSDT
INFO : 2023-07-25 22:43:52,540 : Caching 1d klines for ZILUSDT
INFO : 2023-07-25 22:43:52,558 : Caching 1d klines for 1INCHUSDT
INFO : 2023-07-25 22:43:52,596 : Caching 1d klines for ZRXUSDT
INFO : 2023-07-25 22:43:52,633 : Caching 1h klines for MATICUSDT
INFO : 2023-07-25 22:43:52,673 : Caching 1h klines for MANAUSDT
INFO : 2023-07-25 22:43:52,713 : Caching 1h klines for ZILUSDT
INFO : 2023-07-25 22:43:52,733 : Caching 1h klines for 1INCHUSDT
INFO : 2023-07-25 22:43:52,774 : Caching 1h klines for ZRXUSDT
INFO : 2023-07-25 22:43:52,814 : Caching 15m klines for MATICUSDT
INFO : 2023-07-25 22:43:52,868 : Caching 15m klines for MANAUSDT
INFO : 2023-07-25 22:43:52,924 : Caching 15m klines for ZILUSDT
INFO : 2023-07-25 22:43:52,943 : Caching 15m klines for 1INCHUSDT
INFO : 2023-07-25 22:43:52,993 : Caching 15m klines for ZRXUSDT
WARNING: proceeding without Binance client auth
Creating The Singleton Runner
Traceback (most recent call last):
  File "/home/docs/checkouts/readthedocs.org/user_builds/cryptrality/checkouts/latest/src/cryptrality/cryptrality/exchanges/backtest_binance_spot.py", line 804, in sync_klines
    buffer_klines[buffer_key] = next(
StopIteration

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/docs/checkouts/readthedocs.org/user_builds/cryptrality/conda/latest/bin/cryptrality", line 33, in <module>
    sys.exit(load_entry_point('cryptrality', 'console_scripts', 'cryptrality')())
  File "/home/docs/checkouts/readthedocs.org/user_builds/cryptrality/checkouts/latest/src/cryptrality/cryptrality/commands.py", line 34, in main
    modules[args.module](subparsers, args.module, extra)
  File "/home/docs/checkouts/readthedocs.org/user_builds/cryptrality/checkouts/latest/src/cryptrality/cryptrality/subcommands/backtest.py", line 166, in backtest
    runner.run_forever()
  File "/home/docs/checkouts/readthedocs.org/user_builds/cryptrality/checkouts/latest/src/cryptrality/cryptrality/exchanges/backtest_binance_spot.py", line 606, in run_forever
    for k in self.klines:
RuntimeError: generator raised StopIteration