def test_backtest(default_conf, fee, mocker, testdatadir) -> None: default_conf['ask_strategy']['use_sell_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) patch_exchange(mocker) backtesting = Backtesting(default_conf) pair = 'UNITTEST/BTC' timerange = TimeRange('date', None, 1517227800, 0) data = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'], timerange=timerange) data_processed = backtesting.strategy.tickerdata_to_dataframe(data) min_date, max_date = get_timeframe(data_processed) results = backtesting.backtest({ 'stake_amount': default_conf['stake_amount'], 'processed': data_processed, 'max_open_trades': 10, 'position_stacking': False, 'start_date': min_date, 'end_date': max_date, }) assert not results.empty assert len(results) == 2 expected = pd.DataFrame({ 'pair': [pair, pair], 'profit_percent': [0.0, 0.0], 'profit_abs': [0.0, 0.0], 'open_time': pd.to_datetime([ Arrow(2018, 1, 29, 18, 40, 0).datetime, Arrow(2018, 1, 30, 3, 30, 0).datetime ], utc=True), 'close_time': pd.to_datetime([ Arrow(2018, 1, 29, 22, 35, 0).datetime, Arrow(2018, 1, 30, 4, 10, 0).datetime ], utc=True), 'open_index': [78, 184], 'close_index': [125, 192], 'trade_duration': [235, 40], 'open_at_end': [False, False], 'open_rate': [0.104445, 0.10302485], 'close_rate': [0.104969, 0.103541], 'sell_reason': [SellType.ROI, SellType.ROI] }) pd.testing.assert_frame_equal(results, expected) data_pair = data_processed[pair] for _, t in results.iterrows(): ln = data_pair.loc[data_pair["date"] == t["open_time"]] # Check open trade rate alignes to open rate assert ln is not None assert round(ln.iloc[0]["open"], 6) == round(t["open_rate"], 6) # check close trade rate alignes to close rate or is between high and low ln = data_pair.loc[data_pair["date"] == t["close_time"]] assert (round(ln.iloc[0]["open"], 6) == round(t["close_rate"], 6) or round(ln.iloc[0]["low"], 6) < round( t["close_rate"], 6) < round(ln.iloc[0]["high"], 6))
def load_bt_data(self) -> Tuple[Dict[str, DataFrame], TimeRange]: """ Loads backtest data and returns the data combined with the timerange as tuple. """ timerange = TimeRange.parse_timerange(None if self.config.get( 'timerange') is None else str(self.config.get('timerange'))) data = history.load_data( datadir=self.config['datadir'], pairs=self.pairlists.whitelist, timeframe=self.timeframe, timerange=timerange, startup_candles=self.required_startup, fail_without_data=True, data_format=self.config.get('dataformat_ohlcv', 'json'), ) min_date, max_date = history.get_timerange(data) logger.info(f'Loading data from {min_date.strftime(DATETIME_PRINT_FORMAT)} ' f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} ' f'({(max_date - min_date).days} days).') # Adjust startts forward if not enough data is available timerange.adjust_start_if_necessary(timeframe_to_seconds(self.timeframe), self.required_startup, min_date) return data, timerange
def test_generate_profit_graph(): filename = history.make_testdata_path(None) / "backtest-result_test.json" trades = load_backtest_data(filename) timerange = TimeRange.parse_timerange("20180110-20180112") pairs = ["POWR/BTC", "XLM/BTC"] tickers = history.load_data(datadir=None, pairs=pairs, ticker_interval='5m', timerange=timerange ) trades = trades[trades['pair'].isin(pairs)] fig = generate_profit_graph(pairs, tickers, trades) assert isinstance(fig, go.Figure) assert fig.layout.title.text == "Profit plot" figure = fig.layout.figure assert len(figure.data) == 4 avgclose = find_trace_in_fig_data(figure.data, "Avg close price") assert isinstance(avgclose, go.Scattergl) profit = find_trace_in_fig_data(figure.data, "Profit") assert isinstance(profit, go.Scattergl) for pair in pairs: profit_pair = find_trace_in_fig_data(figure.data, f"Profit {pair}") assert isinstance(profit_pair, go.Scattergl)
def init_plotscript(config): """ Initialize objects needed for plotting :return: Dict with tickers, trades and pairs """ if "pairs" in config: pairs = config["pairs"] else: pairs = config["exchange"]["pair_whitelist"] # Set timerange to use timerange = TimeRange.parse_timerange(config.get("timerange")) tickers = load_data( datadir=config.get("datadir"), pairs=pairs, timeframe=config.get('ticker_interval', '5m'), timerange=timerange, data_format=config.get('dataformat_ohlcv', 'json'), ) trades = load_trades( config['trade_source'], db_url=config.get('db_url'), exportfilename=config.get('exportfilename'), ) trades = trim_dataframe(trades, timerange, 'open_time') return { "tickers": tickers, "trades": trades, "pairs": pairs, }
def test_add_indicators(default_conf, caplog): pair = "UNITTEST/BTC" timerange = TimeRange(None, 'line', 0, -1000) data = history.load_pair_history(pair=pair, ticker_interval='1m', datadir=None, timerange=timerange) indicators1 = ["ema10"] indicators2 = ["macd"] # Generate buy/sell signals and indicators strat = DefaultStrategy(default_conf) data = strat.analyze_ticker(data, {'pair': pair}) fig = generage_empty_figure() # Row 1 fig1 = add_indicators(fig=deepcopy(fig), row=1, indicators=indicators1, data=data) figure = fig1.layout.figure ema10 = find_trace_in_fig_data(figure.data, "ema10") assert isinstance(ema10, go.Scatter) assert ema10.yaxis == "y" fig2 = add_indicators(fig=deepcopy(fig), row=3, indicators=indicators2, data=data) figure = fig2.layout.figure macd = find_trace_in_fig_data(figure.data, "macd") assert isinstance(macd, go.Scatter) assert macd.yaxis == "y3" # No indicator found fig3 = add_indicators(fig=deepcopy(fig), row=3, indicators=['no_indicator'], data=data) assert fig == fig3 assert log_has_re(r'Indicator "no_indicator" ignored\..*', caplog)
def test_hdf5datahandler_ohlcv_load_and_resave(testdatadir): dh = HDF5DataHandler(testdatadir) ohlcv = dh.ohlcv_load('UNITTEST/BTC', '5m') assert isinstance(ohlcv, DataFrame) assert len(ohlcv) > 0 file = testdatadir / 'UNITTEST_NEW-5m.h5' assert not file.is_file() dh.ohlcv_store('UNITTEST/NEW', '5m', ohlcv) assert file.is_file() assert not ohlcv[ohlcv['date'] < '2018-01-15'].empty # Data gores from 2018-01-10 - 2018-01-30 timerange = TimeRange.parse_timerange('20180115-20180119') # Call private function to ensure timerange is filtered in hdf5 ohlcv = dh._ohlcv_load('UNITTEST/BTC', '5m', timerange) ohlcv1 = dh._ohlcv_load('UNITTEST/NEW', '5m', timerange) assert len(ohlcv) == len(ohlcv1) assert ohlcv.equals(ohlcv1) assert ohlcv[ohlcv['date'] < '2018-01-15'].empty assert ohlcv[ohlcv['date'] > '2018-01-19'].empty _clean_test_file(file) # Try loading inexisting file ohlcv = dh.ohlcv_load('UNITTEST/NONEXIST', '5m') assert ohlcv.empty
def test_generate_candlestick_graph_no_signals_no_trades(default_conf, mocker, caplog): row_mock = mocker.patch('freqtrade.plot.plotting.add_indicators', MagicMock(side_effect=fig_generating_mock)) trades_mock = mocker.patch('freqtrade.plot.plotting.plot_trades', MagicMock(side_effect=fig_generating_mock)) pair = "UNITTEST/BTC" timerange = TimeRange(None, 'line', 0, -1000) data = history.load_pair_history(pair=pair, ticker_interval='1m', datadir=None, timerange=timerange) data['buy'] = 0 data['sell'] = 0 indicators1 = [] indicators2 = [] fig = generate_candlestick_graph(pair=pair, data=data, trades=None, indicators1=indicators1, indicators2=indicators2) assert isinstance(fig, go.Figure) assert fig.layout.title.text == pair figure = fig.layout.figure assert len(figure.data) == 2 # Candlesticks are plotted first candles = find_trace_in_fig_data(figure.data, "Price") assert isinstance(candles, go.Candlestick) volume = find_trace_in_fig_data(figure.data, "Volume") assert isinstance(volume, go.Bar) assert row_mock.call_count == 2 assert trades_mock.call_count == 1 assert log_has("No buy-signals found.", caplog) assert log_has("No sell-signals found.", caplog)
def test_convert_trades_to_ohlcv(mocker, default_conf, testdatadir, caplog): pair = 'XRP/ETH' file1 = testdatadir / 'XRP_ETH-1m.json' file5 = testdatadir / 'XRP_ETH-5m.json' # Compare downloaded dataset with converted dataset dfbak_1m = load_pair_history(datadir=testdatadir, timeframe="1m", pair=pair) dfbak_5m = load_pair_history(datadir=testdatadir, timeframe="5m", pair=pair) _backup_file(file1, copy_file=True) _backup_file(file5) tr = TimeRange.parse_timerange('20191011-20191012') convert_trades_to_ohlcv([pair], timeframes=['1m', '5m'], datadir=testdatadir, timerange=tr, erase=True) assert log_has("Deleting existing data for pair XRP/ETH, interval 1m.", caplog) # Load new data df_1m = load_pair_history(datadir=testdatadir, timeframe="1m", pair=pair) df_5m = load_pair_history(datadir=testdatadir, timeframe="5m", pair=pair) assert df_1m.equals(dfbak_1m) assert df_5m.equals(dfbak_5m) _clean_test_file(file1) _clean_test_file(file5) assert not log_has('Could not convert NoDatapair to OHLCV.', caplog) convert_trades_to_ohlcv(['NoDatapair'], timeframes=['1m', '5m'], datadir=testdatadir, timerange=tr, erase=True) assert log_has('Could not convert NoDatapair to OHLCV.', caplog)
def load_bt_data(self) -> Tuple[Dict[str, DataFrame], TimeRange]: timerange = TimeRange.parse_timerange(None if self.config.get( 'timerange') is None else str(self.config.get('timerange'))) data = history.load_data( datadir=self.config['datadir'], pairs=self.config['exchange']['pair_whitelist'], timeframe=self.timeframe, timerange=timerange, startup_candles=self.required_startup, fail_without_data=True, data_format=self.config.get('dataformat_ohlcv', 'json'), ) min_date, max_date = history.get_timerange(data) logger.info('Loading data from %s up to %s (%s days)..', min_date.isoformat(), max_date.isoformat(), (max_date - min_date).days) # Adjust startts forward if not enough data is available timerange.adjust_start_if_necessary( timeframe_to_seconds(self.timeframe), self.required_startup, min_date) return data, timerange
def test_backtest_1min_ticker_interval(default_conf, fee, mocker, testdatadir) -> None: default_conf['ask_strategy']['use_sell_signal'] = False mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) patch_exchange(mocker) backtesting = Backtesting(default_conf) # Run a backtesting for an exiting 1min timeframe timerange = TimeRange.parse_timerange('1510688220-1510700340') data = history.load_data(datadir=testdatadir, timeframe='1m', pairs=['UNITTEST/BTC'], timerange=timerange) processed = backtesting.strategy.tickerdata_to_dataframe(data) min_date, max_date = get_timeframe(processed) results = backtesting.backtest({ 'stake_amount': default_conf['stake_amount'], 'processed': processed, 'max_open_trades': 1, 'position_stacking': False, 'start_date': min_date, 'end_date': max_date, }) assert not results.empty assert len(results) == 1
def test_create_cum_profit1(testdatadir): filename = testdatadir / "backtest-result_test.json" bt_data = load_backtest_data(filename) # Move close-time to "off" the candle, to make sure the logic still works bt_data.loc[:, 'close_date'] = bt_data.loc[:, 'close_date'] + DateOffset( seconds=20) timerange = TimeRange.parse_timerange("20180110-20180112") df = load_pair_history(pair="TRX/BTC", timeframe='5m', datadir=testdatadir, timerange=timerange) cum_profits = create_cum_profit(df.set_index('date'), bt_data[bt_data["pair"] == 'TRX/BTC'], "cum_profits", timeframe="5m") assert "cum_profits" in cum_profits.columns assert cum_profits.iloc[0]['cum_profits'] == 0 assert cum_profits.iloc[-1]['cum_profits'] == 0.0798005 with pytest.raises(ValueError, match='Trade dataframe empty.'): create_cum_profit(df.set_index('date'), bt_data[bt_data["pair"] == 'NOTAPAIR'], "cum_profits", timeframe="5m")
def test_add_areas(default_conf, testdatadir, caplog): pair = "UNITTEST/BTC" timerange = TimeRange(None, 'line', 0, -1000) data = history.load_pair_history(pair=pair, timeframe='1m', datadir=testdatadir, timerange=timerange) indicators = { "macd": { "color": "red", "fill_color": "black", "fill_to": "macdhist", "fill_label": "MACD Fill" } } ind_no_label = {"macd": {"fill_color": "red", "fill_to": "macdhist"}} ind_plain = {"macd": {"fill_to": "macdhist"}} default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) # Generate buy/sell signals and indicators data = strategy.analyze_ticker(data, {'pair': pair}) fig = generate_empty_figure() # indicator mentioned in fill_to does not exist fig1 = add_areas(fig, 1, data, {'ema10': {'fill_to': 'no_fill_indicator'}}) assert fig == fig1 assert log_has_re(r'fill_to: "no_fill_indicator" ignored\..*', caplog) # indicator does not exist fig2 = add_areas(fig, 1, data, {'no_indicator': {'fill_to': 'ema10'}}) assert fig == fig2 assert log_has_re(r'Indicator "no_indicator" ignored\..*', caplog) # everythin given in plot config, row 3 fig3 = add_areas(fig, 3, data, indicators) figure = fig3.layout.figure fill_macd = find_trace_in_fig_data(figure.data, "MACD Fill") assert isinstance(fill_macd, go.Scatter) assert fill_macd.yaxis == "y3" assert fill_macd.fillcolor == "black" # label missing, row 1 fig4 = add_areas(fig, 1, data, ind_no_label) figure = fig4.layout.figure fill_macd = find_trace_in_fig_data(figure.data, "macd<>macdhist") assert isinstance(fill_macd, go.Scatter) assert fill_macd.yaxis == "y" assert fill_macd.fillcolor == "red" # fit_to only fig5 = add_areas(fig, 1, data, ind_plain) figure = fig5.layout.figure fill_macd = find_trace_in_fig_data(figure.data, "macd<>macdhist") assert isinstance(fill_macd, go.Scatter) assert fill_macd.yaxis == "y"
def load_pair_history(pair: str, ticker_interval: str, datadir: Optional[Path], timerange: TimeRange = TimeRange(None, None, 0, 0), refresh_pairs: bool = False, exchange: Optional[Exchange] = None, fill_up_missing: bool = True, drop_incomplete: bool = True) -> DataFrame: """ Loads cached ticker history for the given pair. :param pair: Pair to load data for :param ticker_interval: Ticker-interval (e.g. "5m") :param datadir: Path to the data storage location. :param timerange: Limit data to be loaded to this timerange :param refresh_pairs: Refresh pairs from exchange. (Note: Requires exchange to be passed as well.) :param exchange: Exchange object (needed when using "refresh_pairs") :param fill_up_missing: Fill missing values with "No action"-candles :param drop_incomplete: Drop last candle assuming it may be incomplete. :return: DataFrame with ohlcv data """ # The user forced the refresh of pairs if refresh_pairs: download_pair_history(datadir=datadir, exchange=exchange, pair=pair, ticker_interval=ticker_interval, timerange=timerange) pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange) if pairdata: if timerange.starttype == 'date' and pairdata[0][ 0] > timerange.startts * 1000: logger.warning( 'Missing data at start for pair %s, data starts at %s', pair, arrow.get(pairdata[0][0] // 1000).strftime('%Y-%m-%d %H:%M:%S')) if timerange.stoptype == 'date' and pairdata[-1][ 0] < timerange.stopts * 1000: logger.warning( 'Missing data at end for pair %s, data ends at %s', pair, arrow.get(pairdata[-1][0] // 1000).strftime('%Y-%m-%d %H:%M:%S')) return parse_ticker_dataframe(pairdata, ticker_interval, pair=pair, fill_missing=fill_up_missing, drop_incomplete=drop_incomplete) else: logger.warning( f'No history data for pair: "{pair}", interval: {ticker_interval}. ' 'Use --refresh-pairs-cached option or download_backtest_data.py ' 'script to download the data') return None
def start_download_data(args: Dict[str, Any]) -> None: """ Download data (former download_backtest_data.py script) """ config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) timerange = TimeRange() if 'days' in config: time_since = arrow.utcnow().shift(days=-config['days']).strftime("%Y%m%d") timerange = TimeRange.parse_timerange(f'{time_since}-') if 'pairs' not in config: raise OperationalException( "Downloading data requires a list of pairs. " "Please check the documentation on how to configure this.") logger.info(f'About to download pairs: {config["pairs"]}, ' f'intervals: {config["timeframes"]} to {config["datadir"]}') pairs_not_available: List[str] = [] # Init exchange exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config) try: if config.get('download_trades'): pairs_not_available = refresh_backtest_trades_data( exchange, pairs=config["pairs"], datadir=config['datadir'], timerange=timerange, erase=config.get("erase")) # Convert downloaded trade data to different timeframes convert_trades_to_ohlcv( pairs=config["pairs"], timeframes=config["timeframes"], datadir=config['datadir'], timerange=timerange, erase=config.get("erase")) else: pairs_not_available = refresh_backtest_ohlcv_data( exchange, pairs=config["pairs"], timeframes=config["timeframes"], datadir=config['datadir'], timerange=timerange, erase=config.get("erase")) except KeyboardInterrupt: sys.exit("SIGINT received, aborting ...") finally: if pairs_not_available: logger.info(f"Pairs [{','.join(pairs_not_available)}] not available " f"on exchange {exchange.name}.")
def test_ohlcvdata_to_dataframe(default_conf, testdatadir) -> None: default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) timerange = TimeRange.parse_timerange('1510694220-1510700340') data = load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange, fill_up_missing=True) processed = strategy.ohlcvdata_to_dataframe(data) assert len(processed['UNITTEST/BTC']) == 102 # partial candle was removed
def init_plotscript(config, markets: List, startup_candles: int = 0): """ Initialize objects needed for plotting :return: Dict with candle (OHLCV) data, trades and pairs """ if "pairs" in config: pairs = expand_pairlist(config['pairs'], markets) else: pairs = expand_pairlist(config['exchange']['pair_whitelist'], markets) # Set timerange to use timerange = TimeRange.parse_timerange(config.get('timerange')) data = load_data( datadir=config.get('datadir'), pairs=pairs, timeframe=config['timeframe'], timerange=timerange, startup_candles=startup_candles, data_format=config.get('dataformat_ohlcv', 'json'), ) if startup_candles and data: min_date, max_date = get_timerange(data) logger.info(f"Loading data from {min_date} to {max_date}") timerange.adjust_start_if_necessary( timeframe_to_seconds(config['timeframe']), startup_candles, min_date) no_trades = False filename = config.get('exportfilename') if config.get('no_trades', False): no_trades = True elif config['trade_source'] == 'file': if not filename.is_dir() and not filename.is_file(): logger.warning("Backtest file is missing skipping trades.") no_trades = True try: trades = load_trades( config['trade_source'], db_url=config.get('db_url'), exportfilename=filename, no_trades=no_trades, strategy=config.get('strategy'), ) except ValueError as e: raise OperationalException(e) from e if not trades.empty: trades = trim_dataframe(trades, timerange, 'open_date') return { "ohlcv": data, "trades": trades, "pairs": pairs, "timerange": timerange, }
def test_tickerdata_to_dataframe(default_conf, testdatadir) -> None: strategy = DefaultStrategy(default_conf) timerange = TimeRange.parse_timerange('1510694220-1510700340') tick = load_tickerdata_file(testdatadir, 'UNITTEST/BTC', '1m', timerange=timerange) tickerlist = {'UNITTEST/BTC': parse_ticker_dataframe(tick, '1m', pair="UNITTEST/BTC", fill_missing=True)} data = strategy.tickerdata_to_dataframe(tickerlist) assert len(data['UNITTEST/BTC']) == 102 # partial candle was removed
def test_advise_all_indicators(default_conf, testdatadir) -> None: strategy = StrategyResolver.load_strategy(default_conf) timerange = TimeRange.parse_timerange('1510694220-1510700340') data = load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange, fill_up_missing=True) processed = strategy.advise_all_indicators(data) assert len(processed['UNITTEST/BTC']) == 102 # partial candle was removed
def test_generate_profit_graph(testdatadir): filename = testdatadir / "backtest_results/backtest-result_new.json" trades = load_backtest_data(filename) timerange = TimeRange.parse_timerange("20180110-20180112") pairs = ["TRX/BTC", "XLM/BTC"] trades = trades[ trades['close_date'] < pd.Timestamp('2018-01-12', tz='UTC')] data = history.load_data(datadir=testdatadir, pairs=pairs, timeframe='5m', timerange=timerange) trades = trades[trades['pair'].isin(pairs)] fig = generate_profit_graph(pairs, data, trades, timeframe="5m", stake_currency='BTC') assert isinstance(fig, go.Figure) assert fig.layout.title.text == "Freqtrade Profit plot" assert fig.layout.yaxis.title.text == "Price" assert fig.layout.yaxis2.title.text == "Profit BTC" assert fig.layout.yaxis3.title.text == "Profit BTC" figure = fig.layout.figure assert len(figure.data) == 7 avgclose = find_trace_in_fig_data(figure.data, "Avg close price") assert isinstance(avgclose, go.Scatter) profit = find_trace_in_fig_data(figure.data, "Profit") assert isinstance(profit, go.Scatter) drawdown = find_trace_in_fig_data(figure.data, "Max drawdown 35.69%") assert isinstance(drawdown, go.Scatter) parallel = find_trace_in_fig_data(figure.data, "Parallel trades") assert isinstance(parallel, go.Scatter) underwater = find_trace_in_fig_data(figure.data, "Underwater Plot") assert isinstance(underwater, go.Scatter) for pair in pairs: profit_pair = find_trace_in_fig_data(figure.data, f"Profit {pair}") assert isinstance(profit_pair, go.Scatter) with pytest.raises(OperationalException, match=r"No trades found.*"): # Pair cannot be empty - so it's an empty dataframe. generate_profit_graph(pairs, data, trades.loc[trades['pair'].isnull()], timeframe="5m", stake_currency='BTC')
def test_ohlcvdata_to_dataframe_copy(mocker, default_conf, testdatadir) -> None: default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) aimock = mocker.patch('freqtrade.strategy.interface.IStrategy.advise_indicators') timerange = TimeRange.parse_timerange('1510694220-1510700340') data = load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange, fill_up_missing=True) strategy.ohlcvdata_to_dataframe(data) assert aimock.call_count == 1 # Ensure that a copy of the dataframe is passed to advice_indicators assert aimock.call_args_list[0][0][0] is not data
def test_generate_candlestick_graph_no_trades(default_conf, mocker, testdatadir): row_mock = mocker.patch('freqtrade.plot.plotting.add_indicators', MagicMock(side_effect=fig_generating_mock)) trades_mock = mocker.patch('freqtrade.plot.plotting.plot_trades', MagicMock(side_effect=fig_generating_mock)) pair = 'UNITTEST/BTC' timerange = TimeRange(None, 'line', 0, -1000) data = history.load_pair_history(pair=pair, timeframe='1m', datadir=testdatadir, timerange=timerange) default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) # Generate buy/sell signals and indicators data = strategy.analyze_ticker(data, {'pair': pair}) indicators1 = [] indicators2 = [] fig = generate_candlestick_graph(pair=pair, data=data, trades=None, indicators1=indicators1, indicators2=indicators2) assert isinstance(fig, go.Figure) assert fig.layout.title.text == pair figure = fig.layout.figure assert len(figure.data) == 6 # Candlesticks are plotted first candles = find_trace_in_fig_data(figure.data, "Price") assert isinstance(candles, go.Candlestick) volume = find_trace_in_fig_data(figure.data, "Volume") assert isinstance(volume, go.Bar) buy = find_trace_in_fig_data(figure.data, "buy") assert isinstance(buy, go.Scatter) # All buy-signals should be plotted assert int(data.buy.sum()) == len(buy.x) sell = find_trace_in_fig_data(figure.data, "sell") assert isinstance(sell, go.Scatter) # All buy-signals should be plotted assert int(data.sell.sum()) == len(sell.x) assert find_trace_in_fig_data(figure.data, "Bollinger Band") assert row_mock.call_count == 2 assert trades_mock.call_count == 1
def test_load_data_startup_candles(mocker, caplog, default_conf, testdatadir) -> None: ltfmock = mocker.patch('freqtrade.data.history.load_tickerdata_file', MagicMock(return_value=None)) timerange = TimeRange('date', None, 1510639620, 0) history.load_pair_history(pair='UNITTEST/BTC', timeframe='1m', datadir=testdatadir, timerange=timerange, startup_candles=20, ) assert ltfmock.call_count == 1 assert ltfmock.call_args_list[0][1]['timerange'] != timerange # startts is 20 minutes earlier assert ltfmock.call_args_list[0][1]['timerange'].startts == timerange.startts - 20 * 60
def test_load_partial_missing(testdatadir, caplog) -> None: # Make sure we start fresh - test missing data at start start = arrow.get('2018-01-01T00:00:00') end = arrow.get('2018-01-11T00:00:00') tickerdata = history.load_data(testdatadir, '5m', ['UNITTEST/BTC'], startup_candles=20, timerange=TimeRange('date', 'date', start.timestamp, end.timestamp)) assert log_has('Using indicator startup period: 20 ...', caplog) # timedifference in 5 minutes td = ((end - start).total_seconds() // 60 // 5) + 1 assert td != len(tickerdata['UNITTEST/BTC']) start_real = tickerdata['UNITTEST/BTC'].iloc[0, 0] assert log_has( f'Missing data at start for pair ' f'UNITTEST/BTC, data starts at {start_real.strftime("%Y-%m-%d %H:%M:%S")}', caplog) # Make sure we start fresh - test missing data at end caplog.clear() start = arrow.get('2018-01-10T00:00:00') end = arrow.get('2018-02-20T00:00:00') tickerdata = history.load_data(datadir=testdatadir, timeframe='5m', pairs=['UNITTEST/BTC'], timerange=TimeRange('date', 'date', start.timestamp, end.timestamp)) # timedifference in 5 minutes td = ((end - start).total_seconds() // 60 // 5) + 1 assert td != len(tickerdata['UNITTEST/BTC']) # Shift endtime with +5 - as last candle is dropped (partial candle) end_real = arrow.get(tickerdata['UNITTEST/BTC'].iloc[-1, 0]).shift(minutes=5) assert log_has( f'Missing data at end for pair ' f'UNITTEST/BTC, data ends at {end_real.strftime("%Y-%m-%d %H:%M:%S")}', caplog)
def start_download_data(args: Namespace) -> None: """ Download data (former download_backtest_data.py script) """ config = setup_utils_configuration(args, RunMode.OTHER) timerange = TimeRange() if 'days' in config: time_since = arrow.utcnow().shift( days=-config['days']).strftime("%Y%m%d") timerange = TimeRange.parse_timerange(f'{time_since}-') dl_path = Path(config['datadir']) logger.info(f'About to download pairs: {config["pairs"]}, ' f'intervals: {config["timeframes"]} to {dl_path}') pairs_not_available: List[str] = [] try: # Init exchange exchange = ExchangeResolver(config['exchange']['name'], config).exchange pairs_not_available = refresh_backtest_ohlcv_data( exchange, pairs=config["pairs"], timeframes=config["timeframes"], dl_path=Path(config['datadir']), timerange=timerange, erase=config.get("erase")) except KeyboardInterrupt: sys.exit("SIGINT received, aborting ...") finally: if pairs_not_available: logger.info( f"Pairs [{','.join(pairs_not_available)}] not available " f"on exchange {config['exchange']['name']}.")
def test_trim_dataframe(testdatadir) -> None: data = history.load_data( datadir=testdatadir, timeframe='1m', pairs=['UNITTEST/BTC'] )['UNITTEST/BTC'] min_date = int(data.iloc[0]['date'].timestamp()) max_date = int(data.iloc[-1]['date'].timestamp()) data_modify = data.copy() # Remove first 30 minutes (1800 s) tr = TimeRange('date', None, min_date + 1800, 0) data_modify = history.trim_dataframe(data_modify, tr) assert not data_modify.equals(data) assert len(data_modify) < len(data) assert len(data_modify) == len(data) - 30 assert all(data_modify.iloc[-1] == data.iloc[-1]) assert all(data_modify.iloc[0] == data.iloc[30]) data_modify = data.copy() # Remove last 30 minutes (1800 s) tr = TimeRange(None, 'date', 0, max_date - 1800) data_modify = history.trim_dataframe(data_modify, tr) assert not data_modify.equals(data) assert len(data_modify) < len(data) assert len(data_modify) == len(data) - 30 assert all(data_modify.iloc[0] == data.iloc[0]) assert all(data_modify.iloc[-1] == data.iloc[-31]) data_modify = data.copy() # Remove first 25 and last 30 minutes (1800 s) tr = TimeRange('date', 'date', min_date + 1500, max_date - 1800) data_modify = history.trim_dataframe(data_modify, tr) assert not data_modify.equals(data) assert len(data_modify) < len(data) assert len(data_modify) == len(data) - 55 # first row matches 25th original row assert all(data_modify.iloc[0] == data.iloc[25])
def load_data_test(what): timerange = TimeRange(None, 'line', 0, -101) pair = history.load_tickerdata_file(None, ticker_interval='1m', pair='UNITTEST/BTC', timerange=timerange) datalen = len(pair) base = 0.001 if what == 'raise': data = [ [ pair[x][0], # Keep old dates x * base, # But replace O,H,L,C x * base + 0.0001, x * base - 0.0001, x * base, pair[x][5], # Keep old volume ] for x in range(0, datalen) ] if what == 'lower': data = [ [ pair[x][0], # Keep old dates 1 - x * base, # But replace O,H,L,C 1 - x * base + 0.0001, 1 - x * base - 0.0001, 1 - x * base, pair[x][5] # Keep old volume ] for x in range(0, datalen) ] if what == 'sine': hz = 0.1 # frequency data = [ [ pair[x][0], # Keep old dates math.sin(x * hz) / 1000 + base, # But replace O,H,L,C math.sin(x * hz) / 1000 + base + 0.0001, math.sin(x * hz) / 1000 + base - 0.0001, math.sin(x * hz) / 1000 + base, pair[x][5] # Keep old volume ] for x in range(0, datalen) ] return { 'UNITTEST/BTC': parse_ticker_dataframe(data, '1m', pair="UNITTEST/BTC", fill_missing=True) }
def test_create_cum_profit(): filename = make_testdata_path(None) / "backtest-result_test.json" bt_data = load_backtest_data(filename) timerange = TimeRange.parse_timerange("20180110-20180112") df = load_pair_history(pair="POWR/BTC", ticker_interval='5m', datadir=None, timerange=timerange) cum_profits = create_cum_profit(df.set_index('date'), bt_data[bt_data["pair"] == 'POWR/BTC'], "cum_profits") assert "cum_profits" in cum_profits.columns assert cum_profits.iloc[0]['cum_profits'] == 0 assert cum_profits.iloc[-1]['cum_profits'] == 0.0798005
def __init__(self, config: Dict[str, Any], exchange, strategy) -> None: self.config = config self.exchange = exchange self.strategy: IStrategy = strategy self.edge_config = self.config.get('edge', {}) self._cached_pairs: Dict[str, Any] = {} # Keeps a list of pairs self._final_pairs: list = [] # checking max_open_trades. it should be -1 as with Edge # the number of trades is determined by position size if self.config['max_open_trades'] != float('inf'): logger.critical('max_open_trades should be -1 in config !') if self.config['stake_amount'] != UNLIMITED_STAKE_AMOUNT: raise OperationalException( 'Edge works only with unlimited stake amount') self._capital_ratio: float = self.config['tradable_balance_ratio'] self._allowed_risk: float = self.edge_config.get('allowed_risk') self._since_number_of_days: int = self.edge_config.get( 'calculate_since_number_of_days', 14) self._last_updated: int = 0 # Timestamp of pairs last updated time self._refresh_pairs = True self._stoploss_range_min = float( self.edge_config.get('stoploss_range_min', -0.01)) self._stoploss_range_max = float( self.edge_config.get('stoploss_range_max', -0.05)) self._stoploss_range_step = float( self.edge_config.get('stoploss_range_step', -0.001)) # calculating stoploss range self._stoploss_range = np.arange(self._stoploss_range_min, self._stoploss_range_max, self._stoploss_range_step) self._timerange: TimeRange = TimeRange.parse_timerange( "%s-" % arrow.now().shift( days=-1 * self._since_number_of_days).format('YYYYMMDD')) if config.get('fee'): self.fee = config['fee'] else: try: self.fee = self.exchange.get_fee(symbol=expand_pairlist( self.config['exchange']['pair_whitelist'], list(self.exchange.markets))[0]) except IndexError: self.fee = None
def test_data_to_dataframe_bt(default_conf, mocker, testdatadir) -> None: patch_exchange(mocker) timerange = TimeRange.parse_timerange('1510694220-1510700340') data = history.load_data(testdatadir, '1m', ['UNITTEST/BTC'], timerange=timerange, fill_up_missing=True) backtesting = Backtesting(default_conf) processed = backtesting.strategy.ohlcvdata_to_dataframe(data) assert len(processed['UNITTEST/BTC']) == 102 # Load strategy to compare the result between Backtesting function and strategy are the same default_conf.update({'strategy': 'DefaultStrategy'}) strategy = StrategyResolver.load_strategy(default_conf) processed2 = strategy.ohlcvdata_to_dataframe(data) assert processed['UNITTEST/BTC'].equals(processed2['UNITTEST/BTC'])
def test_create_cum_profit(testdatadir): filename = testdatadir / "backtest-result_test.json" bt_data = load_backtest_data(filename) timerange = TimeRange.parse_timerange("20180110-20180112") df = load_pair_history(pair="TRX/BTC", timeframe='5m', datadir=testdatadir, timerange=timerange) cum_profits = create_cum_profit(df.set_index('date'), bt_data[bt_data["pair"] == 'TRX/BTC'], "cum_profits", timeframe="5m") assert "cum_profits" in cum_profits.columns assert cum_profits.iloc[0]['cum_profits'] == 0 assert cum_profits.iloc[-1]['cum_profits'] == 0.0798005