Package: backintime
backintime 1.6.3✨ Tool for testing trading strategies on historical data ✨Such testing does not guarantee the same results in real trading, but it gives a rough estimate of a strategy's success.
The smaller timeframe is used, the more accurate the results will be.Note: Margin trading is not supported as for now. Expected in 2.x releases.FeaturesUse CSV or Binance API as a data sourceThe same data can be represented in various timeframes(few short candles is used to represent longer one)Market, Limit, Take Profit, Take Profit Limit, Stop Loss, Stop Loss Limit orders managementBuiltin indicators. Seelist.Trading statistics (win rate, profit/loss, avg. profit, etc.) with FIFO, LIFO or AVCO Profit/Loss estimation algorithmsExport orders, trades and statistics to csvIndicatorsMA - Moving AverageEMA - Exponential Moving AverageMACD - Moving Average Convergence DivergenceBBANDS - Bollinger BandsRSI - Relative Strength IndexATR - Average True RangeADX - Average Directional Movement IndexDMI - Directional Movement IndicatorPIVOT - Pivot Points (Traditional, Fibonacci or Classic)Installationpython3-mpipinstallbackintimeHow to useSample with MACD strategy is provided below. The strategy buys when the MACD line crosses from below to above the signal line, (i.e. histogram becomes non-zero), and sells when MACD line crosses from above to below the signal line. Note that this strategy results in a high losses and only provided as a reference. Generally no one uses only one indicator in a trading strategy.fromdatetimeimportdatetimefrombackintimeimportTradingStrategy,run_backtestfrombackintime.trading_strategyimportTradingStrategyfrombackintime.timeframesimportTimeframesastffrombackintime.indicator_paramsimportMACDfrombackintime.data.binanceimportBinanceCandlesFactoryclassMacdStrategy(TradingStrategy):# (1)title="Sample MACD Strategy"indicators={MACD(tf.H1)}# (2)deftick(self):# (3)macd=self.analyser.macd(tf.H1)ifnotself.positionandmacd.crossover_up():self.buy()# (4)elifself.positionandmacd.crossover_down():self.sell()# (5)feed=BinanceCandlesFactory('BTCUSDT',tf.M15)# (6)since=datetime.fromisoformat("2020-01-01 00:00+00:00")until=datetime.fromisoformat("2021-01-01 00:00+00:00")result=run_backtest(MacdStrategy,feed,10_000,since,until,maker_fee='0.005',taker_fee='0.005')print(result)print(result.get_stats('FIFO'))result.export()To implement a strategy, you need to create a class derived fromTradingStrategy(1), specify which indicators is used (2) and provide algorithm implementation intickmethod (3).
When the corresponding conditions are met, the strategy callsbuy(4) orsell(5) methods, which are aliases for Market order submission.
Here the backtesting is done with candlesticks from Binance API on 15 minutes timeframe (6). However, consider using CSV file instead for better performance.The following sample with "SMA resistance breakout" strategy demonstrates more
advanced features: Limit orders, Take Profit and Stop Loss.
The strategy buys when the last close crosses from below to above SMA line and
sells at Take Profit or Stop Loss levels. MACD and DMI signals are used as filters.importtypingastfromdatetimeimportdatetimefromdecimalimportDecimalfrombackintimeimportTradingStrategy,run_backtestfrombackintime.timeframesimportTimeframesastffrombackintime.data.binanceimportBinanceCandlesFactoryfrombackintime.indicator_paramsimportSMA,MACD,DMI,PIVOTfrombackintime.analyser.indicators.dmiimportDMIResultSequencefrombackintime.analyser.indicators.macdimportMacdResultSequencefrombackintime.brokerimportTakeProfitOptions,StopLossOptionsdefmacd_hist_up(macd:MacdResultSequence)->bool:"""True, if MACD hist > 0."""returnmacd[-1].hist>0defdmi_buy_signal(dmi:DMIResultSequence)->bool:"""True, if +DI > -DI."""returndmi.positive_di[-1]>dmi.negative_di[-1]classSmaResistanceBreakout(TradingStrategy):title="SMA resistance breakout"candle_timeframes={tf.M15}indicators={SMA(tf.M15,55),MACD(tf.M15),DMI(tf.M15,14),PIVOT(tf.D1,15)}def__init__(self,broker,analyser,candles):self.prev_close:t.Optional[Decimal]=Noneself.curr_close:t.Optional[Decimal]=Nonesuper().__init__(broker,analyser,candles)deftick(self):self.prev_close=self.curr_closeself.curr_close=self.candles.get(tf.M15).closesma=self.analyser.sma(tf.M15,period=55)[-1]sma_crossover_up=self.curr_close>smaand\self.prev_closeandself.prev_close<=smaifself.broker.max_fiat_for_makerandsma_crossover_upand\macd_hist_up(self.analyser.macd(tf.M15))and\dmi_buy_signal(self.analyser.dmi(tf.M15,14)):# Calculate buy price: current SMA + 0.2%limit_price=Decimal(sma)*Decimal('1.002')# Set up TP at PIVOT' R2 levelpivot=self.analyser.pivot(tf.D1,15)take_profit_trigger=pivot[-1].r2take_profit=TakeProfitOptions(percentage_amount=Decimal('100.00'),trigger_price=take_profit_trigger)# Set up SL at current close price - 5%stop_loss_trigger=self.candles.get(tf.M15).close*Decimal('0.95')stop_loss=StopLossOptions(percentage_amount=Decimal('100.00'),trigger_price=stop_loss_trigger)# Submit limit buy with TP & SLself.limit_buy(order_price=limit_price,amount=self.broker.max_fiat_for_maker,take_profit=take_profit,stop_loss=stop_loss)feed=BinanceCandlesFactory('BTCUSDT',tf.M15)since=datetime.fromisoformat("2020-03-01 00:00+00:00")until=datetime.fromisoformat("2021-05-01 00:00+00:00")result=run_backtest(SmaResistanceBreakout,feed,10_000,since,until,maker_fee='0.005',taker_fee='0.005')print(result)print(result.get_stats('FIFO'))print(result.get_stats('LIFO'))print(result.get_stats('AVCO'))result.export()Core conceptsBacktestingBacktesting is done inrun_backtestfunction, which basically initializes required objects,
prefetches market data if needed and runs the similar loop:forcandleincandles:broker.update(candle)# Review whether orders can be executedanalyser.update(candle)# Store data for indicators calculationcandles.update(candle)# Update candles on required timeframesstrategy.tick()# Trading strategy logic hereTradingStrategyBase class for trading strategies.
Strategy must provide algorithm implementation intickmethod, which runs each time a new candle closes.BrokerBroker provides orders management in a simulated market environment.
The broker executes/activates orders whose conditions fits the market.
Supports Market, Limit, Take Profit, Take Profit Limit, Stop Loss, Stop Loss Limit orders.
Order execution policy of builtin broker:Market orders:
All market orders will be executed
when a new candle closes.
The price of execution is the candle's OPEN price.Limit orders:
Limit order will be executed at the limit price or better:
lower or equal price for BUY orders and higher or equal price
for SELL orders.
First, theorder_priceof each order will be compared to
the OPEN price of a new candle:BUY orders will be executed if `order_price` >= OPEN. 
SELL orders will be executed if `order_price` <= OPEN.Then, remaining BUYs will be compared to LOW,
and remaining SELLs - to HIGH.
Fill price is the first price that matched limit price.Take Profit/Stop Loss orders:TP/SL orders will be activated if thetrigger_priceis
within the price bounds of a candle.
This check is performed in two steps:1) For each order: activate if trigger_price == OPEN
2) For each order: activate if LOW <= trigger_price <= HIGHWhen a TP/SL order is triggered, it will be treated
as a market or limit order, depending on whetherorder_priceis set for the order.Limit, Take Profit and Stop Loss orders are reviewed
in the order of their submission (oldest first).AnalyserIndicators calculation. Seelistof supported indicators.CandlesProvides the last candle representation for various timeframes.
It is useful for checking properties of a candle on one timeframe (H1, for example), while having data on another (for instance, M1).DataProviderProvides candles in historical order.DataProvideris an iterable object that can be created for specific date range (since, until);
Yields OHLCV candle during iteration.BacktestingResultProvides export to CSV and stats such as Win Rate, Profit/Loss, Average Profit, Best/Worst deal, etc.
Supports estimation in FIFO (First-In-First-Out), LIFO (Last-In-First-Out) or AVCO (Average Cost) algorithms.
The algorithm name specifies the order in which BUYs must be considered to estimate profit or loss.All these algorithms produce the same result if SELL order always follows only one BUY.PrefetchingIndicators require a certain amount of data to get a correct result. For example, to calculate the SMA (simple moving average) with a period of 9, 9 values are required.
So, the strategy will get the wrong result of the SMA indicator, until all 9 candles are accumulated.In order for the strategy to get the correct values right from the start, prefetching of market data is used. You can configure this behavior by choosing from the following options and passing it asprefetch_optionargument to therun_backtestfunction:PREFETCH_UNTIL(default) - prefetch the required amount of data untilsincedate; the amount of data required is calculated automatically. In this case, the strategy backtesting will be started from thesincedate. This option is convenient when market data is requested from the exchange API, because situations when the exchange does not have enough historical data are quite rare.PREFETCH_SINCE- prefetch the required amount of data fromsincedate; the amount of data required for this is calculated automatically. In this case, the strategy backtesting will be launched starting from the dynamically calculated date. This option may be useful when working with a CSV file when you are not sure if it contains enough data before thesincedate.PREFETCH_NONE- Do not prefetch data.Wheresincedate is the value of the argumentsincepassed to therun_backtestfunction.Some thoughtsI plan to add support for margin trading (will allow testing of short and leveraged strategies)
and implement backtesting as an event based system using a queue for communication
(will allow running backtesting on distributed systems).DocsThere is no documentation yet because the code is unstable (but works). As for now, you can browse sources or typehelpin REPL.LicenseMITAuthorAkim Mukhtarov@akim_int80h