AlgoQ/backtra

DEPRECATED - Simple to use backtesting framework

★ 0Forks 0PythonGitHub ↗Compare

README

DEPRECATED

Backtra

Backtra is an advanced futures backtesting framework where we loop through kline (ohlcv) data on a specific way to acheive more realistic results.

This is a futures backtesting framework but it can also be used for spot backtesting.

More in-depth documentation is coming soon!

Getting Started

Install

git clone https://github.com/AlgoQ/backtra
cd backtra
pip install -r requirements

We will be backtesting a simple golden cross strategy with a stoploss and take profit based on the ATR. The leverage is also based on the ATR and the max percentage you are willing to lose per trade.

Golden Cross

  1. Config

Rename the exampleConfig.json file to config.json and update the parameters if wanted.

{
    "capital": 10000,
    "makerFee": -0.0025,
    "takerFee": 0.0075,
    "reduceAmount": 0
}
  1. Strategy

Create a strategy named GoldenCross.py and add it into the strategies folder.

More info about creating a strategy will be added soon.

from strategies.BaseStrategy import BaseStrategy

import numpy as np
import pandas as pd

# TODO: Import needed indicators
from talib import SMA

class GoldenCross(BaseStrategy):
    def __init__(self, strategyName, symbol, params, timeFrames, ohlcvs, tradeLogs=True):
        # Changeable values
        self.strategyName = strategyName
        self.symbol       = symbol
        self.params       = params
        self.timeFrames   = timeFrames
        self.ohlcvs       = ohlcvs
        self.tradeLogs    = tradeLogs
        
        # Fixed values
        self.configData      = self._getConfigData()
        self.capital         = self.configData['capital']
        self.makerFee        = self.configData['makerFee']
        self.takerFee        = self.configData['takerFee']
        self.reduceAmount    = self.configData['reduceAmount']
        self.capitalFollowup = [[self.ohlcvs[self.timeFrames[0]].index[1], self.capital]]
        self.openTradesL     = {}
        self.closedTradesL   = []
    
    def run(self):
        print('Trading started...')
        minKlines = max(201, self._calcMinKlines())
        
        for i in range(minKlines, len(self.ohlcvs[self.timeFrames[0]])):
            tempDf = self.ohlcvs[self.timeFrames[0]].iloc[i-minKlines:i+1].copy()
            
            # Indicators
            sma1 = SMA(tempDf['close'], self.params['sma1'])
            sma2 = SMA(tempDf['close'], self.params['sma2'])

            if sma1[-2] < sma2[-2]: # Long condition 1
                prevClose = tempDf['close'][-1]
                tempDf.loc[tempDf.index[-1], 'close'] = tempDf['high'][-1]
                sma1 = SMA(tempDf['close'], self.params['sma1'])
                sma2 = SMA(tempDf['close'], self.params['sma2'])
                
                if sma1[-1] > sma2[-1]: # Long condition 2
                    tempDf.loc[tempDf.index[-1], 'close'] = prevClose
                    for i in np.arange(tempDf['open'][-1], tempDf['high'][-1] + self.ohlcvs['pip'], self.ohlcvs['pip']):
                        openPrice = round(i, self.ohlcvs['precision'])
                        tempDf.loc[tempDf.index[-1], 'close'] = openPrice

                        # Indicators
                        sma1 = SMA(tempDf['close'], self.params['sma1'])
                        sma2 = SMA(tempDf['close'], self.params['sma2'])

                        if sma1[-1] > sma2[-1]: # Long condition 2
                            if len(self.openTradesL) != 0:
                                self.closeTrade(
                                    id = '1',
                                    time = tempDf.index[-1],
                                    tradeType = 'market',
                                    closePrice = openPrice)
                            
                            self.openTrade(
                                id = '1',
                                time = tempDf.index[-1],
                                side = 'long',
                                tradeType = 'market',
                                leverage = self.params['leverage'],
                                amount = self.capital,
                                openPrice = openPrice)
                            break
            
            elif sma1[-2] > sma2[-2]: # Short condition 1
                prevClose = tempDf['close'][-1]
                tempDf.loc[tempDf.index[-1], 'close'] = tempDf['low'][-1]
                sma1 = SMA(tempDf['close'], self.params['sma1'])
                sma2 = SMA(tempDf['close'], self.params['sma2'])
                
                if sma1[-1] < sma2[-1]: # Short condition 2
                    tempDf.loc[tempDf.index[-1], 'close'] = prevClose
                    for i in np.arange(tempDf['open'][-1], tempDf['low'][-1] - self.ohlcvs['pip'], self.ohlcvs['pip'] * -1):
                        openPrice = round(i, self.ohlcvs['precision'])
                        tempDf.loc[tempDf.index[-1], 'close'] = openPrice
                        
                        # Indicators
                        sma1 = SMA(tempDf['close'], self.params['sma1'])
                        sma2 = SMA(tempDf['close'], self.params['sma2'])

                        if sma1[-1] < sma2[-1]: # Short condition 2
                            if len(self.openTradesL) != 0:
                                self.closeTrade(
                                    id='1',
                                    time = tempDf.index[-1],
                                    tradeType = 'market',
                                    closePrice = openPrice)
                            
                            self.openTrade(
                                id = '1',
                                time = tempDf.index[-1],
                                side = 'short',
                                tradeType = 'market',
                                leverage = self.params['leverage'],
                                amount = self.capital,
                                openPrice = openPrice)
                            break
  1. Main File

Now we will create a main file that connects/runs our strategy. I this file we send all variable values through the params, select timeframe and link your data. Create mainGoldenCross.py in the main folder:

from strategies.GoldenCross import GoldenCross
from utils import jsonToOhlcv
import quantstats as qs

params = {'sma1': 50, 'sma2': 200, 'leverage': 1}

timeframe = '4h'

ohlcv = jsonToOhlcv(r'/media/kobe/D/feda/data/ohlcv_ftx_BTCUSD_784days.json', timeframe)

# precision = round(ohlcv['close'].apply(lambda x: len(str(x).split('.')[-1])).mean())

precision = 0
if precision > 0:
    pip = float('0.' + ('0' * (precision - 1)) + '1')
else:
    pip = 1

ohlcvs = {}
ohlcvs['precision'] = precision
ohlcvs['pip'] = pip
ohlcvs[timeframe] = ohlcv

goldenCross = GoldenCross(
    strategyName = 'Golden Cross',
    symbol = 'BTC/USD',
    params = params,
    ohlcvs = ohlcvs,
    timeFrames = [timeframe],
    tradeLogs = True
)

goldenCross.run()

results, percChange = goldenCross.calcResults()
goldenCross.showResults(results)

qs.plots.snapshot(percChange, title=f'Results', savefig=f'Results')
  1. Output
Strategy               Golden Cross
Symbol                 BTC/USD
Timeframes             ['4h']
Parameters             {'sma1': 50, 'sma2': 200, 'leverage': 1}
Start                  2019-08-20 16:00:00
End                    2021-10-12 12:00:00
Duration (days)        783
Equity Start [$]       10000
Equity Final [$]       58406.2285
Equity Max [$]         89791.4633
Return [%]             484.06
Max. Drawdown [%]      -39.86
Win rate [%]           54.17
Buy & Hold [%]         424.69
Total trades           24
Avg. trade [%]         12.52
Avg. winning trade [%] 31.36
Avg. losing trade [%]  -9.74
Avg. long trade [%]    24.64
Avg. short trade [%]   0.4

Snapshot: Golden Cross

If you don't have any ohlcv/kline data you can always fetch crypto kline data with FEDA.

Upcoming

  • More in-depth documentation
  • Slippage parameter in the config
  • Optimization

Contributors

AlgoQ

Issues