def test_load_data_with_new_pair_1min(ticker_history_list, mocker, caplog, default_conf, testdatadir) -> None: """ Test load_pair_history() with 1 min ticker """ mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ticker_history_list) exchange = get_patched_exchange(mocker, default_conf) file = testdatadir / 'MEME_BTC-1m.json' _backup_file(file) # do not download a new pair if refresh_pairs isn't set history.load_pair_history(datadir=testdatadir, timeframe='1m', pair='MEME/BTC') assert not file.is_file() assert log_has( 'No history data for pair: "MEME/BTC", timeframe: 1m. ' 'Use `freqtrade download-data` to download the data', caplog ) # download a new pair if refresh_pairs is set history.load_pair_history(datadir=testdatadir, timeframe='1m', refresh_pairs=True, exchange=exchange, pair='MEME/BTC') assert file.is_file() assert log_has_re( 'Download history data for pair: "MEME/BTC", timeframe: 1m ' 'and store in .*', caplog ) with pytest.raises(OperationalException, match=r'Exchange needs to be initialized when.*'): history.load_pair_history(datadir=testdatadir, timeframe='1m', refresh_pairs=True, exchange=None, pair='MEME/BTC') _clean_test_file(file)
def test_save_results_saves_epochs(hyperopt, tmpdir, caplog) -> None: hyperopt.results_file = Path(tmpdir / 'ut_results.fthypt') hyperopt_epochs = HyperoptTools.load_filtered_results( hyperopt.results_file, {}) assert log_has_re("Hyperopt file .* not found.", caplog) assert hyperopt_epochs == ([], 0) # Test writing to temp dir and reading again epochs = create_results() caplog.set_level(logging.DEBUG) for epoch in epochs: hyperopt._save_result(epoch) assert log_has(f"1 epoch saved to '{hyperopt.results_file}'.", caplog) hyperopt._save_result(epochs[0]) assert log_has(f"2 epochs saved to '{hyperopt.results_file}'.", caplog) hyperopt_epochs = HyperoptTools.load_filtered_results( hyperopt.results_file, {}) assert len(hyperopt_epochs) == 2 assert hyperopt_epochs[1] == 2 assert len(hyperopt_epochs[0]) == 2 result_gen = HyperoptTools._read_results(hyperopt.results_file, 1) epoch = next(result_gen) assert len(epoch) == 1 assert epoch[0] == epochs[0] epoch = next(result_gen) assert len(epoch) == 1 epoch = next(result_gen) assert len(epoch) == 0 with pytest.raises(StopIteration): next(result_gen)
def test_process_temporary_deprecated_settings(mocker, default_conf, setting, caplog): patched_configuration_load_config_file(mocker, default_conf) # Create sections for new and deprecated settings # (they may not exist in the config) default_conf[setting[0]] = {} default_conf[setting[3]] = {} # Assign deprecated setting default_conf[setting[0]][setting[1]] = setting[2] # Assign new setting if setting[3]: default_conf[setting[3]][setting[4]] = setting[5] else: default_conf[setting[4]] = setting[5] # New and deprecated settings are conflicting ones with pytest.raises(OperationalException, match=r'DEPRECATED'): process_temporary_deprecated_settings(default_conf) caplog.clear() # Delete new setting if setting[3]: del default_conf[setting[3]][setting[4]] else: del default_conf[setting[4]] process_temporary_deprecated_settings(default_conf) assert log_has_re('DEPRECATED', caplog) # The value of the new setting shall have been set to the # value of the deprecated one if setting[3]: assert default_conf[setting[3]][setting[4]] == setting[2] else: assert default_conf[setting[4]] == setting[2]
def test_assert_df(ohlcv_history, caplog): df_len = len(ohlcv_history) - 1 ohlcv_history.loc[:, 'buy'] = 0 ohlcv_history.loc[:, 'sell'] = 0 # Ensure it's running when passed correctly _STRATEGY.assert_df(ohlcv_history, len(ohlcv_history), ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[df_len, 'date']) with pytest.raises(StrategyError, match=r"Dataframe returned from strategy.*length\."): _STRATEGY.assert_df(ohlcv_history, len(ohlcv_history) + 1, ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[df_len, 'date']) with pytest.raises(StrategyError, match=r"Dataframe returned from strategy.*last close price\."): _STRATEGY.assert_df(ohlcv_history, len(ohlcv_history), ohlcv_history.loc[df_len, 'close'] + 0.01, ohlcv_history.loc[df_len, 'date']) with pytest.raises(StrategyError, match=r"Dataframe returned from strategy.*last date\."): _STRATEGY.assert_df(ohlcv_history, len(ohlcv_history), ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[0, 'date']) with pytest.raises(StrategyError, match=r"No dataframe returned \(return statement missing\?\)."): _STRATEGY.assert_df(None, len(ohlcv_history), ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[0, 'date']) with pytest.raises(StrategyError, match="Buy column not set"): _STRATEGY.assert_df(ohlcv_history.drop('buy', axis=1), len(ohlcv_history), ohlcv_history.loc[df_len, 'close'], ohlcv_history.loc[0, 'date']) _STRATEGY.disable_dataframe_checks = True caplog.clear() _STRATEGY.assert_df(ohlcv_history, len(ohlcv_history), ohlcv_history.loc[2, 'close'], ohlcv_history.loc[0, 'date']) assert log_has_re(r"Dataframe returned from strategy.*last date\.", caplog) # reset to avoid problems in other tests due to test leakage _STRATEGY.disable_dataframe_checks = False
def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplog): default_conf['pairlists'] = [{'method': 'VolumePairList', 'number_assets': 10}, {'method': 'SpreadFilter', 'max_spread_ratio': 0.1}] mocker.patch.multiple('freqtrade.exchange.Exchange', markets=PropertyMock(return_value=markets), exchange_has=MagicMock(return_value=True), get_tickers=tickers ) ftbot = get_patched_freqtradebot(mocker, default_conf) ftbot.pairlists.refresh_pairlist() assert len(ftbot.pairlists.whitelist) == 5 tickers.return_value['ETH/BTC']['ask'] = 0.0 del tickers.return_value['TKN/BTC'] del tickers.return_value['LTC/BTC'] mocker.patch.multiple('freqtrade.exchange.Exchange', get_tickers=tickers) ftbot.pairlists.refresh_pairlist() assert log_has_re(r'Removed .* invalid ticker data.*', caplog) assert len(ftbot.pairlists.whitelist) == 2
def test_start_new_config(mocker, caplog, exchange): wt_mock = mocker.patch.object(Path, "write_text", MagicMock()) mocker.patch.object(Path, "exists", MagicMock(return_value=True)) unlink_mock = mocker.patch.object(Path, "unlink", MagicMock()) mocker.patch('freqtrade.commands.build_config_commands.ask_user_overwrite', return_value=True) sample_selections = { 'max_open_trades': 3, 'stake_currency': 'USDT', 'stake_amount': 100, 'fiat_display_currency': 'EUR', 'timeframe': '15m', 'dry_run': True, 'exchange_name': exchange, 'exchange_key': 'sampleKey', 'exchange_secret': 'Samplesecret', 'telegram': False, 'telegram_token': 'asdf1244', 'telegram_chat_id': '1144444', } mocker.patch('freqtrade.commands.build_config_commands.ask_user_config', return_value=sample_selections) args = [ "new-config", "--config", "coolconfig.json" ] start_new_config(get_args(args)) assert log_has_re("Writing config to .*", caplog) assert wt_mock.call_count == 1 assert unlink_mock.call_count == 1 result = rapidjson.loads(wt_mock.call_args_list[0][0][0], parse_mode=rapidjson.PM_COMMENTS | rapidjson.PM_TRAILING_COMMAS) assert result['exchange']['name'] == exchange assert result['timeframe'] == '15m'
def test_load_data_with_new_pair_1min(ohlcv_history_list, mocker, caplog, default_conf, tmpdir, candle_type) -> None: """ Test load_pair_history() with 1 min timeframe """ tmpdir1 = Path(tmpdir) mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ohlcv_history_list) exchange = get_patched_exchange(mocker, default_conf) file = tmpdir1 / 'MEME_BTC-1m.json' # do not download a new pair if refresh_pairs isn't set load_pair_history(datadir=tmpdir1, timeframe='1m', pair='MEME/BTC', candle_type=candle_type) assert not file.is_file() assert log_has( f"No history for MEME/BTC, {candle_type}, 1m found. " "Use `freqtrade download-data` to download the data", caplog) # download a new pair if refresh_pairs is set refresh_data(datadir=tmpdir1, timeframe='1m', pairs=['MEME/BTC'], exchange=exchange, candle_type=CandleType.SPOT) load_pair_history(datadir=tmpdir1, timeframe='1m', pair='MEME/BTC', candle_type=candle_type) assert file.is_file() assert log_has_re( r'Download history data for pair: "MEME/BTC" \(0/1\), timeframe: 1m, ' r'candle type: spot and store in .*', caplog)
def test_setup_optimize_configuration_without_arguments(mocker, default_conf, caplog) -> None: patched_configuration_load_config_file(mocker, default_conf) args = [ 'edge', '--config', 'config.json', '--strategy', 'DefaultStrategy', ] config = setup_optimize_configuration(get_args(args), RunMode.EDGE) assert config['runmode'] == RunMode.EDGE assert 'max_open_trades' in config assert 'stake_currency' in config assert 'stake_amount' in config assert 'exchange' in config assert 'pair_whitelist' in config['exchange'] assert 'datadir' in config assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert 'timeframe' in config assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog) assert 'timerange' not in config assert 'stoploss_range' not in config
def test_update_limit_order(limit_buy_order_usdt, limit_sell_order_usdt, fee, caplog): """ On this test we will buy and sell a crypto currency. fee: 0.25% quote open_rate: 2.00 quote close_rate: 2.20 quote amount: = 30.0 crypto stake_amount 60.0 quote borrowed 0 quote open_value: (amount * open_rate) + (amount * open_rate * fee) 30 * 2 + 30 * 2 * 0.0025 = 60.15 quote close_value: (amount * close_rate) - (amount * close_rate * fee) - interest (30.00 * 2.20) - (30.00 * 2.20 * 0.0025) = 65.835 total_profit: close_value - open_value 65.835 - 60.15 = 5.685 total_profit_ratio: ((close_value/open_value) - 1) * leverage ((65.835 / 60.15) - 1) * 1 = 0.0945137157107232 """ trade = Trade( id=2, pair='ADA/USDT', stake_amount=60.0, open_rate=2.0, amount=30.0, is_open=True, open_date=arrow.utcnow().datetime, fee_open=fee.return_value, fee_close=fee.return_value, exchange='binance', ) assert trade.open_order_id is None assert trade.close_profit is None assert trade.close_date is None trade.open_order_id = 'something' trade.update(limit_buy_order_usdt) assert trade.open_order_id is None assert trade.open_rate == 2.00 assert trade.close_profit is None assert trade.close_date is None assert log_has_re(r"LIMIT_BUY has been fulfilled for Trade\(id=2, " r"pair=ADA/USDT, amount=30.00000000, open_rate=2.00000000, open_since=.*\).", caplog) caplog.clear() trade.open_order_id = 'something' trade.update(limit_sell_order_usdt) assert trade.open_order_id is None assert trade.close_rate == 2.20 assert trade.close_profit == round(0.0945137157107232, 8) assert trade.close_date is not None assert log_has_re(r"LIMIT_SELL has been fulfilled for Trade\(id=2, " r"pair=ADA/USDT, amount=30.00000000, open_rate=2.00000000, open_since=.*\).", caplog) caplog.clear()
def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, shitcoinmarkets, tickers, ohlcv_history, pairlists, base_currency, whitelist_result, caplog) -> None: whitelist_conf['pairlists'] = pairlists whitelist_conf['stake_currency'] = base_currency ohlcv_data = { ('ETH/BTC', '1d'): ohlcv_history, ('TKN/BTC', '1d'): ohlcv_history, ('LTC/BTC', '1d'): ohlcv_history, ('XRP/BTC', '1d'): ohlcv_history, ('HOT/BTC', '1d'): ohlcv_history, } mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True)) if whitelist_result == 'static_in_the_middle': with pytest.raises(OperationalException, match=r"StaticPairList can only be used in the first position " r"in the list of Pairlist Handlers."): freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) return freqtrade = get_patched_freqtradebot(mocker, whitelist_conf) mocker.patch.multiple('freqtrade.exchange.Exchange', get_tickers=tickers, markets=PropertyMock(return_value=shitcoinmarkets) ) mocker.patch.multiple( 'freqtrade.exchange.Exchange', refresh_latest_ohlcv=MagicMock(return_value=ohlcv_data), ) # Provide for PerformanceFilter's dependency mocker.patch.multiple('freqtrade.persistence.Trade', get_overall_performance=MagicMock(return_value=[]) ) # Set whitelist_result to None if pairlist is invalid and should produce exception if whitelist_result == 'filter_at_the_beginning': with pytest.raises(OperationalException, match=r"This Pairlist Handler should not be used at the first position " r"in the list of Pairlist Handlers."): freqtrade.pairlists.refresh_pairlist() else: freqtrade.pairlists.refresh_pairlist() whitelist = freqtrade.pairlists.whitelist assert isinstance(whitelist, list) # Verify length of pairlist matches (used for ShuffleFilter without seed) if type(whitelist_result) is list: assert whitelist == whitelist_result else: len(whitelist) == whitelist_result for pairlist in pairlists: if pairlist['method'] == 'AgeFilter' and pairlist['min_days_listed'] and \ len(ohlcv_history) <= pairlist['min_days_listed']: assert log_has_re(r'^Removed .* from whitelist, because age .* is less than ' r'.* day.*', caplog) if pairlist['method'] == 'PrecisionFilter' and whitelist_result: assert log_has_re(r'^Removed .* from whitelist, because stop price .* ' r'would be <= stop limit.*', caplog) if pairlist['method'] == 'PriceFilter' and whitelist_result: assert (log_has_re(r'^Removed .* from whitelist, because 1 unit is .*%$', caplog) or log_has_re(r'^Removed .* from whitelist, ' r'because last price < .*%$', caplog) or log_has_re(r'^Removed .* from whitelist, ' r'because last price > .*%$', caplog) or log_has_re(r"^Removed .* from whitelist, because ticker\['last'\] " r"is empty.*", caplog)) if pairlist['method'] == 'VolumePairList': logmsg = ("DEPRECATED: using any key other than quoteVolume for " "VolumePairList is deprecated.") if pairlist['sort_key'] != 'quoteVolume': assert log_has(logmsg, caplog) else: assert not log_has(logmsg, caplog)
def test_update_with_bittrex(limit_buy_order, limit_sell_order, fee, caplog): """ On this test we will buy and sell a crypto currency. Buy - Buy: 90.99181073 Crypto at 0.00001099 BTC (90.99181073*0.00001099 = 0.0009999 BTC) - Buying fee: 0.25% - Total cost of buy trade: 0.001002500 BTC ((90.99181073*0.00001099) + ((90.99181073*0.00001099)*0.0025)) Sell - Sell: 90.99181073 Crypto at 0.00001173 BTC (90.99181073*0.00001173 = 0,00106733394 BTC) - Selling fee: 0.25% - Total cost of sell trade: 0.001064666 BTC ((90.99181073*0.00001173) - ((90.99181073*0.00001173)*0.0025)) Profit/Loss: +0.000062166 BTC (Sell:0.001064666 - Buy:0.001002500) Profit/Loss percentage: 0.0620 ((0.001064666/0.001002500)-1 = 6.20%) :param limit_buy_order: :param limit_sell_order: :return: """ trade = Trade( id=2, pair='ETH/BTC', stake_amount=0.001, open_rate=0.01, amount=5, is_open=True, open_date=arrow.utcnow().datetime, fee_open=fee.return_value, fee_close=fee.return_value, exchange='bittrex', ) assert trade.open_order_id is None assert trade.close_profit is None assert trade.close_date is None trade.open_order_id = 'something' trade.update(limit_buy_order) assert trade.open_order_id is None assert trade.open_rate == 0.00001099 assert trade.close_profit is None assert trade.close_date is None assert log_has_re( r"LIMIT_BUY has been fulfilled for Trade\(id=2, " r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001099, open_since=.*\).", caplog) caplog.clear() trade.open_order_id = 'something' trade.update(limit_sell_order) assert trade.open_order_id is None assert trade.close_rate == 0.00001173 assert trade.close_profit == 0.06201058 assert trade.close_date is not None assert log_has_re( r"LIMIT_SELL has been fulfilled for Trade\(id=2, " r"pair=ETH/BTC, amount=90.99181073, open_rate=0.00001099, open_since=.*\).", caplog)
def test_LowProfitPairs(mocker, default_conf, fee, caplog): default_conf['protections'] = [{ "method": "LowProfitPairs", "lookback_period": 400, "stop_duration": 60, "trade_limit": 2, "required_profit": 0.0, }] freqtrade = get_patched_freqtradebot(mocker, default_conf) message = r"Trading stopped due to .*" assert not freqtrade.protections.global_stop() assert not freqtrade.protections.stop_per_pair('XRP/BTC') assert not log_has_re(message, caplog) caplog.clear() Trade.session.add( generate_mock_trade( 'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, min_ago_open=800, min_ago_close=450, profit_rate=0.9, )) # Not locked with 1 trade assert not freqtrade.protections.global_stop() assert not freqtrade.protections.stop_per_pair('XRP/BTC') assert not PairLocks.is_pair_locked('XRP/BTC') assert not PairLocks.is_global_lock() Trade.session.add( generate_mock_trade( 'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, min_ago_open=200, min_ago_close=120, profit_rate=0.9, )) # Not locked with 1 trade (first trade is outside of lookback_period) assert not freqtrade.protections.global_stop() assert not freqtrade.protections.stop_per_pair('XRP/BTC') assert not PairLocks.is_pair_locked('XRP/BTC') assert not PairLocks.is_global_lock() # Add positive trade Trade.session.add( generate_mock_trade( 'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value, min_ago_open=20, min_ago_close=10, profit_rate=1.15, )) assert not freqtrade.protections.stop_per_pair('XRP/BTC') assert not PairLocks.is_pair_locked('XRP/BTC') Trade.session.add( generate_mock_trade( 'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, min_ago_open=110, min_ago_close=20, profit_rate=0.8, )) # Locks due to 2nd trade assert not freqtrade.protections.global_stop() assert freqtrade.protections.stop_per_pair('XRP/BTC') assert PairLocks.is_pair_locked('XRP/BTC') assert not PairLocks.is_global_lock()
def test_start_test_pairlist(mocker, caplog, tickers, default_conf, capsys): patch_exchange(mocker, mock_markets=True) mocker.patch.multiple( 'freqtrade.exchange.Exchange', exchange_has=MagicMock(return_value=True), get_tickers=tickers, ) default_conf['pairlists'] = [ { "method": "VolumePairList", "number_assets": 5, "sort_key": "quoteVolume", }, { "method": "PrecisionFilter" }, { "method": "PriceFilter", "low_price_ratio": 0.02 }, ] patched_configuration_load_config_file(mocker, default_conf) args = [ 'test-pairlist', '-c', 'config_examples/config_bittrex.example.json' ] start_test_pairlist(get_args(args)) assert log_has_re(r"^Using resolved pairlist VolumePairList.*", caplog) assert log_has_re(r"^Using resolved pairlist PrecisionFilter.*", caplog) assert log_has_re(r"^Using resolved pairlist PriceFilter.*", caplog) captured = capsys.readouterr() assert re.match(r"Pairs for .*", captured.out) assert re.match("['ETH/BTC', 'TKN/BTC', 'BLK/BTC', 'LTC/BTC', 'XRP/BTC']", captured.out) args = [ 'test-pairlist', '-c', 'config_examples/config_bittrex.example.json', '--one-column', ] start_test_pairlist(get_args(args)) captured = capsys.readouterr() assert re.match(r"ETH/BTC\nTKN/BTC\nBLK/BTC\nLTC/BTC\nXRP/BTC\n", captured.out) args = [ 'test-pairlist', '-c', 'config_examples/config_bittrex.example.json', '--print-json', ] start_test_pairlist(get_args(args)) captured = capsys.readouterr() try: json_pairs = json.loads(captured.out) assert 'ETH/BTC' in json_pairs assert 'TKN/BTC' in json_pairs assert 'BLK/BTC' in json_pairs assert 'LTC/BTC' in json_pairs assert 'XRP/BTC' in json_pairs except json.decoder.JSONDecodeError: pytest.fail( f'Expected well formed JSON, but failed to parse: {captured.out}')
def test_stoploss_guard(mocker, default_conf, fee, caplog): default_conf['protections'] = [{ "method": "StoplossGuard", "lookback_period": 60, "stop_duration": 40, "trade_limit": 3 }] freqtrade = get_patched_freqtradebot(mocker, default_conf) message = r"Trading stopped due to .*" assert not freqtrade.protections.global_stop() assert not log_has_re(message, caplog) caplog.clear() Trade.session.add( generate_mock_trade( 'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, min_ago_open=200, min_ago_close=30, )) assert not freqtrade.protections.global_stop() assert not log_has_re(message, caplog) caplog.clear() # This trade does not count, as it's closed too long ago Trade.session.add( generate_mock_trade( 'BCH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, min_ago_open=250, min_ago_close=100, )) Trade.session.add( generate_mock_trade( 'ETH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, min_ago_open=240, min_ago_close=30, )) # 3 Trades closed - but the 2nd has been closed too long ago. assert not freqtrade.protections.global_stop() assert not log_has_re(message, caplog) caplog.clear() Trade.session.add( generate_mock_trade( 'LTC/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, min_ago_open=180, min_ago_close=30, )) assert freqtrade.protections.global_stop() assert log_has_re(message, caplog) assert PairLocks.is_global_lock() # Test 5m after lock-period - this should try and relock the pair, but end-time # should be the previous end-time end_time = PairLocks.get_pair_longest_lock('*').lock_end_time + timedelta( minutes=5) assert freqtrade.protections.global_stop(end_time) assert not PairLocks.is_global_lock(end_time)
def test_check_exchange(default_conf, caplog) -> None: # Test an officially supported by Freqtrade team exchange default_conf['runmode'] = RunMode.DRY_RUN default_conf.get('exchange').update({'name': 'BITTREX'}) assert check_exchange(default_conf) assert log_has_re( r"Exchange .* is officially supported by the Freqtrade development team\.", caplog) caplog.clear() # Test an officially supported by Freqtrade team exchange default_conf.get('exchange').update({'name': 'binance'}) assert check_exchange(default_conf) assert log_has_re( r"Exchange .* is officially supported by the Freqtrade development team\.", caplog) caplog.clear() # Test an available exchange, supported by ccxt default_conf.get('exchange').update({'name': 'huobipro'}) assert check_exchange(default_conf) assert log_has_re( r"Exchange .* is known to the the ccxt library, available for the bot, " r"but not officially supported " r"by the Freqtrade development team\. .*", caplog) caplog.clear() # Test a 'bad' exchange, which known to have serious problems default_conf.get('exchange').update({'name': 'bitmex'}) with pytest.raises( OperationalException, match=r"Exchange .* is known to not work with the bot yet.*"): check_exchange(default_conf) caplog.clear() # Test a 'bad' exchange with check_for_bad=False default_conf.get('exchange').update({'name': 'bitmex'}) assert check_exchange(default_conf, False) assert log_has_re( r"Exchange .* is known to the the ccxt library, available for the bot, " r"but not officially supported " r"by the Freqtrade development team\. .*", caplog) caplog.clear() # Test an invalid exchange default_conf.get('exchange').update({'name': 'unknown_exchange'}) with pytest.raises( OperationalException, match= r'Exchange "unknown_exchange" is not known to the ccxt library ' r'and therefore not available for the bot.*'): check_exchange(default_conf) # Test no exchange... default_conf.get('exchange').update({'name': ''}) default_conf['runmode'] = RunMode.PLOT assert check_exchange(default_conf) # Test no exchange... default_conf.get('exchange').update({'name': ''}) default_conf['runmode'] = RunMode.UTIL_EXCHANGE with pytest.raises(OperationalException, match=r'This command requires a configured exchange.*'): check_exchange(default_conf)
def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair): default_conf['protections'] = [{ "method": "StoplossGuard", "lookback_period": 60, "trade_limit": 2, "stop_duration": 60, "only_per_pair": only_per_pair }] freqtrade = get_patched_freqtradebot(mocker, default_conf) message = r"Trading stopped due to .*" pair = 'XRP/BTC' assert not freqtrade.protections.stop_per_pair(pair) assert not freqtrade.protections.global_stop() assert not log_has_re(message, caplog) caplog.clear() Trade.session.add( generate_mock_trade( pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, min_ago_open=200, min_ago_close=30, profit_rate=0.9, )) assert not freqtrade.protections.stop_per_pair(pair) assert not freqtrade.protections.global_stop() assert not log_has_re(message, caplog) caplog.clear() # This trade does not count, as it's closed too long ago Trade.session.add( generate_mock_trade( pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, min_ago_open=250, min_ago_close=100, profit_rate=0.9, )) # Trade does not count for per pair stop as it's the wrong pair. Trade.session.add( generate_mock_trade( 'ETH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, min_ago_open=240, min_ago_close=30, profit_rate=0.9, )) # 3 Trades closed - but the 2nd has been closed too long ago. assert not freqtrade.protections.stop_per_pair(pair) assert freqtrade.protections.global_stop() != only_per_pair if not only_per_pair: assert log_has_re(message, caplog) else: assert not log_has_re(message, caplog) caplog.clear() # 2nd Trade that counts with correct pair Trade.session.add( generate_mock_trade( pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, min_ago_open=180, min_ago_close=30, profit_rate=0.9, )) assert freqtrade.protections.stop_per_pair(pair) assert freqtrade.protections.global_stop() != only_per_pair assert PairLocks.is_pair_locked(pair) assert PairLocks.is_global_lock() != only_per_pair
def test_download_trades_history(trades_history, mocker, default_conf, testdatadir, caplog) -> None: ght_mock = MagicMock( side_effect=lambda pair, *args, **kwargs: (pair, trades_history)) mocker.patch('freqtrade.exchange.Exchange.get_historic_trades', ght_mock) exchange = get_patched_exchange(mocker, default_conf) file1 = testdatadir / 'ETH_BTC-trades.json.gz' data_handler = get_datahandler(testdatadir, data_format='jsongz') _backup_file(file1) assert not file1.is_file() assert _download_trades_history(data_handler=data_handler, exchange=exchange, pair='ETH/BTC') assert log_has("New Amount of trades: 5", caplog) assert file1.is_file() ght_mock.reset_mock() since_time = int(trades_history[-3][0] // 1000) since_time2 = int(trades_history[-1][0] // 1000) timerange = TimeRange('date', None, since_time, 0) assert _download_trades_history(data_handler=data_handler, exchange=exchange, pair='ETH/BTC', timerange=timerange) assert ght_mock.call_count == 1 # Check this in seconds - since we had to convert to seconds above too. assert int(ght_mock.call_args_list[0][1]['since'] // 1000) == since_time2 - 5 assert ght_mock.call_args_list[0][1]['from_id'] is not None # clean files freshly downloaded _clean_test_file(file1) mocker.patch('freqtrade.exchange.Exchange.get_historic_trades', MagicMock(side_effect=ValueError)) assert not _download_trades_history( data_handler=data_handler, exchange=exchange, pair='ETH/BTC') assert log_has_re( 'Failed to download historic trades for pair: "ETH/BTC".*', caplog) file2 = testdatadir / 'XRP_ETH-trades.json.gz' _backup_file(file2, True) ght_mock.reset_mock() mocker.patch('freqtrade.exchange.Exchange.get_historic_trades', ght_mock) # Since before first start date since_time = int(trades_history[0][0] // 1000) - 500 timerange = TimeRange('date', None, since_time, 0) assert _download_trades_history(data_handler=data_handler, exchange=exchange, pair='XRP/ETH', timerange=timerange) assert ght_mock.call_count == 1 assert int(ght_mock.call_args_list[0][1]['since'] // 1000) == since_time assert ght_mock.call_args_list[0][1]['from_id'] is None assert log_has_re( r'Start earlier than available data. Redownloading trades for.*', caplog) _clean_test_file(file2)
def test_MaxDrawdown(mocker, default_conf, fee, caplog): default_conf['protections'] = [{ "method": "MaxDrawdown", "lookback_period": 1000, "stop_duration": 60, "trade_limit": 3, "max_allowed_drawdown": 0.15 }] freqtrade = get_patched_freqtradebot(mocker, default_conf) message = r"Trading stopped due to Max.*" assert not freqtrade.protections.global_stop() assert not freqtrade.protections.stop_per_pair('XRP/BTC') caplog.clear() Trade.session.add( generate_mock_trade( 'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, min_ago_open=1000, min_ago_close=900, profit_rate=1.1, )) Trade.session.add( generate_mock_trade( 'ETH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, min_ago_open=1000, min_ago_close=900, profit_rate=1.1, )) Trade.session.add( generate_mock_trade( 'NEO/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, min_ago_open=1000, min_ago_close=900, profit_rate=1.1, )) # No losing trade yet ... so max_drawdown will raise exception assert not freqtrade.protections.global_stop() assert not freqtrade.protections.stop_per_pair('XRP/BTC') Trade.session.add( generate_mock_trade( 'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, min_ago_open=500, min_ago_close=400, profit_rate=0.9, )) # Not locked with one trade assert not freqtrade.protections.global_stop() assert not freqtrade.protections.stop_per_pair('XRP/BTC') assert not PairLocks.is_pair_locked('XRP/BTC') assert not PairLocks.is_global_lock() Trade.session.add( generate_mock_trade( 'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, min_ago_open=1200, min_ago_close=1100, profit_rate=0.5, )) # Not locked with 1 trade (2nd trade is outside of lookback_period) assert not freqtrade.protections.global_stop() assert not freqtrade.protections.stop_per_pair('XRP/BTC') assert not PairLocks.is_pair_locked('XRP/BTC') assert not PairLocks.is_global_lock() assert not log_has_re(message, caplog) # Winning trade ... (should not lock, does not change drawdown!) Trade.session.add( generate_mock_trade( 'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value, min_ago_open=320, min_ago_close=410, profit_rate=1.5, )) assert not freqtrade.protections.global_stop() assert not PairLocks.is_global_lock() caplog.clear() # Add additional negative trade, causing a loss of > 15% Trade.session.add( generate_mock_trade( 'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value, min_ago_open=20, min_ago_close=10, profit_rate=0.8, )) assert not freqtrade.protections.stop_per_pair('XRP/BTC') # local lock not supported assert not PairLocks.is_pair_locked('XRP/BTC') assert freqtrade.protections.global_stop() assert PairLocks.is_global_lock() assert log_has_re(message, caplog)
def test_parse_args_None(caplog) -> None: with pytest.raises(SystemExit): main([]) assert log_has_re(r"Usage of Freqtrade requires a subcommand.*", caplog)
def test_api_run(default_conf, mocker, caplog): default_conf.update({ "api_server": { "enabled": True, "listen_ip_address": "127.0.0.1", "listen_port": 8080, "username": "******", "password": "******", } }) mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock()) server_mock = MagicMock() mocker.patch('freqtrade.rpc.api_server.webserver.UvicornServer', server_mock) apiserver = ApiServer(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf) assert server_mock.call_count == 1 assert apiserver._config == default_conf apiserver.start_api() assert server_mock.call_count == 2 assert server_mock.call_args_list[0][0][0].host == "127.0.0.1" assert server_mock.call_args_list[0][0][0].port == 8080 assert isinstance(server_mock.call_args_list[0][0][0].app, FastAPI) assert log_has("Starting HTTP Server at 127.0.0.1:8080", caplog) assert log_has("Starting Local Rest Server.", caplog) # Test binding to public caplog.clear() server_mock.reset_mock() apiserver._config.update({ "api_server": { "enabled": True, "listen_ip_address": "0.0.0.0", "listen_port": 8089, "password": "", } }) apiserver.start_api() assert server_mock.call_count == 1 assert server_mock.call_args_list[0][0][0].host == "0.0.0.0" assert server_mock.call_args_list[0][0][0].port == 8089 assert isinstance(server_mock.call_args_list[0][0][0].app, FastAPI) assert log_has("Starting HTTP Server at 0.0.0.0:8089", caplog) assert log_has("Starting Local Rest Server.", caplog) assert log_has( "SECURITY WARNING - Local Rest Server listening to external connections", caplog) assert log_has( "SECURITY WARNING - This is insecure please set to your loopback," "e.g 127.0.0.1 in config.json", caplog) assert log_has( "SECURITY WARNING - No password for local REST Server defined. " "Please make sure that this is intentional!", caplog) assert log_has_re( "SECURITY WARNING - `jwt_secret_key` seems to be default.*", caplog) # Test crashing API server caplog.clear() mocker.patch('freqtrade.rpc.api_server.webserver.UvicornServer', MagicMock(side_effect=Exception)) apiserver.start_api() assert log_has("Api server failed to start.", caplog)
def test_custom_exit(default_conf, fee, caplog) -> None: strategy = StrategyResolver.load_strategy(default_conf) trade = Trade( pair='ETH/BTC', stake_amount=0.01, amount=1, open_date=arrow.utcnow().shift(hours=-1).datetime, fee_open=fee.return_value, fee_close=fee.return_value, exchange='binance', open_rate=1, ) now = arrow.utcnow().datetime res = strategy.should_exit(trade, 1, now, enter=False, exit_=False, low=None, high=None) assert res.exit_flag is False assert res.exit_type == ExitType.NONE strategy.custom_exit = MagicMock(return_value=True) res = strategy.should_exit(trade, 1, now, enter=False, exit_=False, low=None, high=None) assert res.exit_flag is True assert res.exit_type == ExitType.CUSTOM_EXIT assert res.exit_reason == 'custom_exit' strategy.custom_exit = MagicMock(return_value='hello world') res = strategy.should_exit(trade, 1, now, enter=False, exit_=False, low=None, high=None) assert res.exit_type == ExitType.CUSTOM_EXIT assert res.exit_flag is True assert res.exit_reason == 'hello world' caplog.clear() strategy.custom_exit = MagicMock(return_value='h' * 100) res = strategy.should_exit(trade, 1, now, enter=False, exit_=False, low=None, high=None) assert res.exit_type == ExitType.CUSTOM_EXIT assert res.exit_flag is True assert res.exit_reason == 'h' * 64 assert log_has_re( 'Custom exit reason returned from custom_exit is too long.*', caplog)