diff --git a/tests/rpc/test_fiat_convert.py b/tests/rpc/test_fiat_convert.py index 717866cfd..049716414 100644 --- a/tests/rpc/test_fiat_convert.py +++ b/tests/rpc/test_fiat_convert.py @@ -13,10 +13,10 @@ from tests.conftest import log_has, log_has_re def test_fiat_convert_is_supported(mocker): fiat_convert = CryptoToFiatConverter() - assert fiat_convert._is_supported_fiat(fiat='USD') is True - assert fiat_convert._is_supported_fiat(fiat='usd') is True - assert fiat_convert._is_supported_fiat(fiat='abc') is False - assert fiat_convert._is_supported_fiat(fiat='ABC') is False + assert fiat_convert._is_supported_fiat(fiat="USD") is True + assert fiat_convert._is_supported_fiat(fiat="usd") is True + assert fiat_convert._is_supported_fiat(fiat="abc") is False + assert fiat_convert._is_supported_fiat(fiat="ABC") is False def test_fiat_convert_find_price(mocker): @@ -24,69 +24,72 @@ def test_fiat_convert_find_price(mocker): fiat_convert._coinlistings = {} fiat_convert._backoff = 0 - mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._load_cryptomap', - return_value=None) - assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='EUR') == 0.0 + mocker.patch( + "freqtrade.rpc.fiat_convert.CryptoToFiatConverter._load_cryptomap", return_value=None + ) + assert fiat_convert.get_price(crypto_symbol="BTC", fiat_symbol="EUR") == 0.0 - with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'): - fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='ABC') + with pytest.raises(ValueError, match=r"The fiat ABC is not supported."): + fiat_convert._find_price(crypto_symbol="BTC", fiat_symbol="ABC") - assert fiat_convert.get_price(crypto_symbol='XRP', fiat_symbol='USD') == 0.0 + assert fiat_convert.get_price(crypto_symbol="XRP", fiat_symbol="USD") == 0.0 - mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', - return_value=12345.0) - assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 12345.0 - assert fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='usd') == 12345.0 + mocker.patch( + "freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price", return_value=12345.0 + ) + assert fiat_convert.get_price(crypto_symbol="BTC", fiat_symbol="USD") == 12345.0 + assert fiat_convert.get_price(crypto_symbol="btc", fiat_symbol="usd") == 12345.0 - mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', - return_value=13000.2) - assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='EUR') == 13000.2 + mocker.patch( + "freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price", return_value=13000.2 + ) + assert fiat_convert.get_price(crypto_symbol="BTC", fiat_symbol="EUR") == 13000.2 def test_fiat_convert_unsupported_crypto(mocker, caplog): - mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._coinlistings', return_value=[]) + mocker.patch("freqtrade.rpc.fiat_convert.CryptoToFiatConverter._coinlistings", return_value=[]) fiat_convert = CryptoToFiatConverter() - assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0 - assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog) + assert fiat_convert._find_price(crypto_symbol="CRYPTO_123", fiat_symbol="EUR") == 0.0 + assert log_has("unsupported crypto-symbol CRYPTO_123 - returning 0.0", caplog) def test_fiat_convert_get_price(mocker): - find_price = mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', - return_value=28000.0) + find_price = mocker.patch( + "freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price", return_value=28000.0 + ) fiat_convert = CryptoToFiatConverter() - with pytest.raises(ValueError, match=r'The fiat us dollar is not supported.'): - fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='US Dollar') + with pytest.raises(ValueError, match=r"The fiat us dollar is not supported."): + fiat_convert.get_price(crypto_symbol="btc", fiat_symbol="US Dollar") # Check the value return by the method pair_len = len(fiat_convert._pair_price) assert pair_len == 0 - assert fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='usd') == 28000.0 - assert fiat_convert._pair_price['btc/usd'] == 28000.0 + assert fiat_convert.get_price(crypto_symbol="btc", fiat_symbol="usd") == 28000.0 + assert fiat_convert._pair_price["btc/usd"] == 28000.0 assert len(fiat_convert._pair_price) == 1 assert find_price.call_count == 1 # Verify the cached is used - fiat_convert._pair_price['btc/usd'] = 9867.543 - assert fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='usd') == 9867.543 + fiat_convert._pair_price["btc/usd"] = 9867.543 + assert fiat_convert.get_price(crypto_symbol="btc", fiat_symbol="usd") == 9867.543 assert find_price.call_count == 1 def test_fiat_convert_same_currencies(mocker): fiat_convert = CryptoToFiatConverter() - assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='USD') == 1.0 + assert fiat_convert.get_price(crypto_symbol="USD", fiat_symbol="USD") == 1.0 def test_fiat_convert_two_FIAT(mocker): fiat_convert = CryptoToFiatConverter() - assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='EUR') == 0.0 + assert fiat_convert.get_price(crypto_symbol="USD", fiat_symbol="EUR") == 0.0 def test_loadcryptomap(mocker): - fiat_convert = CryptoToFiatConverter() assert len(fiat_convert._coinlistings) == 2 @@ -97,7 +100,7 @@ def test_fiat_init_network_exception(mocker): # Because CryptoToFiatConverter is a Singleton we reset the listings listmock = MagicMock(side_effect=RequestException) mocker.patch.multiple( - 'freqtrade.rpc.fiat_convert.CoinGeckoAPI', + "freqtrade.rpc.fiat_convert.CoinGeckoAPI", get_coins_list=listmock, ) # with pytest.raises(RequestEsxception): @@ -117,7 +120,7 @@ def test_fiat_convert_without_network(mocker): CryptoToFiatConverter._coingecko = None assert fiat_convert._coingecko is None - assert fiat_convert._find_price(crypto_symbol='btc', fiat_symbol='usd') == 0.0 + assert fiat_convert._find_price(crypto_symbol="btc", fiat_symbol="usd") == 0.0 CryptoToFiatConverter._coingecko = cmc_temp @@ -126,7 +129,7 @@ def test_fiat_too_many_requests_response(mocker, caplog): req_exception = "429 Too Many Requests" listmock = MagicMock(return_value="{}", side_effect=RequestException(req_exception)) mocker.patch.multiple( - 'freqtrade.rpc.fiat_convert.CoinGeckoAPI', + "freqtrade.rpc.fiat_convert.CoinGeckoAPI", get_coins_list=listmock, ) # with pytest.raises(RequestEsxception): @@ -137,33 +140,32 @@ def test_fiat_too_many_requests_response(mocker, caplog): assert len(fiat_convert._coinlistings) == 0 assert fiat_convert._backoff > datetime.datetime.now().timestamp() assert log_has( - 'Too many requests for CoinGecko API, backing off and trying again later.', - caplog + "Too many requests for CoinGecko API, backing off and trying again later.", caplog ) def test_fiat_multiple_coins(mocker, caplog): fiat_convert = CryptoToFiatConverter() fiat_convert._coinlistings = [ - {'id': 'helium', 'symbol': 'hnt', 'name': 'Helium'}, - {'id': 'hymnode', 'symbol': 'hnt', 'name': 'Hymnode'}, - {'id': 'bitcoin', 'symbol': 'btc', 'name': 'Bitcoin'}, - {'id': 'ethereum', 'symbol': 'eth', 'name': 'Ethereum'}, - {'id': 'ethereum-wormhole', 'symbol': 'eth', 'name': 'Ethereum Wormhole'}, + {"id": "helium", "symbol": "hnt", "name": "Helium"}, + {"id": "hymnode", "symbol": "hnt", "name": "Hymnode"}, + {"id": "bitcoin", "symbol": "btc", "name": "Bitcoin"}, + {"id": "ethereum", "symbol": "eth", "name": "Ethereum"}, + {"id": "ethereum-wormhole", "symbol": "eth", "name": "Ethereum Wormhole"}, ] - assert fiat_convert._get_gecko_id('btc') == 'bitcoin' - assert fiat_convert._get_gecko_id('hnt') is None - assert fiat_convert._get_gecko_id('eth') == 'ethereum' + assert fiat_convert._get_gecko_id("btc") == "bitcoin" + assert fiat_convert._get_gecko_id("hnt") is None + assert fiat_convert._get_gecko_id("eth") == "ethereum" - assert log_has('Found multiple mappings in CoinGecko for hnt.', caplog) + assert log_has("Found multiple mappings in CoinGecko for hnt.", caplog) def test_fiat_invalid_response(mocker, caplog): # Because CryptoToFiatConverter is a Singleton we reset the listings listmock = MagicMock(return_value=None) mocker.patch.multiple( - 'freqtrade.rpc.fiat_convert.CoinGeckoAPI', + "freqtrade.rpc.fiat_convert.CoinGeckoAPI", get_coins_list=listmock, ) # with pytest.raises(RequestEsxception): @@ -172,31 +174,22 @@ def test_fiat_invalid_response(mocker, caplog): fiat_convert._load_cryptomap() assert len(fiat_convert._coinlistings) == 0 - assert log_has_re('Could not load FIAT Cryptocurrency map for the following problem: .*', - caplog) + assert log_has_re( + "Could not load FIAT Cryptocurrency map for the following problem: .*", caplog + ) def test_convert_amount(mocker): - mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter.get_price', return_value=12345.0) + mocker.patch("freqtrade.rpc.fiat_convert.CryptoToFiatConverter.get_price", return_value=12345.0) fiat_convert = CryptoToFiatConverter() - result = fiat_convert.convert_amount( - crypto_amount=1.23, - crypto_symbol="BTC", - fiat_symbol="USD" - ) + result = fiat_convert.convert_amount(crypto_amount=1.23, crypto_symbol="BTC", fiat_symbol="USD") assert result == 15184.35 - result = fiat_convert.convert_amount( - crypto_amount=1.23, - crypto_symbol="BTC", - fiat_symbol="BTC" - ) + result = fiat_convert.convert_amount(crypto_amount=1.23, crypto_symbol="BTC", fiat_symbol="BTC") assert result == 1.23 result = fiat_convert.convert_amount( - crypto_amount="1.23", - crypto_symbol="BTC", - fiat_symbol="BTC" + crypto_amount="1.23", crypto_symbol="BTC", fiat_symbol="BTC" ) assert result == 1.23 diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index a231cb06a..d421ba556 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -24,92 +24,108 @@ from tests.conftest import ( def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: gen_response = { - 'trade_id': 1, - 'pair': 'ETH/BTC', - 'base_currency': 'ETH', - 'quote_currency': 'BTC', - 'open_date': ANY, - 'open_timestamp': ANY, - 'open_fill_date': ANY, - 'open_fill_timestamp': ANY, - 'is_open': ANY, - 'fee_open': ANY, - 'fee_open_cost': ANY, - 'fee_open_currency': ANY, - 'fee_close': fee.return_value, - 'fee_close_cost': ANY, - 'fee_close_currency': ANY, - 'open_rate_requested': ANY, - 'open_trade_value': 0.0010025, - 'close_rate_requested': ANY, - 'exit_reason': ANY, - 'exit_order_status': ANY, - 'min_rate': ANY, - 'max_rate': ANY, - 'strategy': ANY, - 'enter_tag': ANY, - 'timeframe': 5, - 'close_date': None, - 'close_timestamp': None, - 'open_rate': 1.098e-05, - 'close_rate': None, - 'current_rate': 1.099e-05, - 'amount': 91.07468123, - 'amount_requested': 91.07468124, - 'stake_amount': 0.001, - 'max_stake_amount': None, - 'trade_duration': None, - 'trade_duration_s': None, - 'close_profit': None, - 'close_profit_pct': None, - 'close_profit_abs': None, - 'profit_ratio': -0.00408133, - 'profit_pct': -0.41, - 'profit_abs': -4.09e-06, - 'profit_fiat': ANY, - 'stop_loss_abs': 9.89e-06, - 'stop_loss_pct': -10.0, - 'stop_loss_ratio': -0.1, - 'stoploss_last_update': ANY, - 'stoploss_last_update_timestamp': ANY, - 'initial_stop_loss_abs': 9.89e-06, - 'initial_stop_loss_pct': -10.0, - 'initial_stop_loss_ratio': -0.1, - 'stoploss_current_dist': pytest.approx(-1.0999999e-06), - 'stoploss_current_dist_ratio': -0.10009099, - 'stoploss_current_dist_pct': -10.01, - 'stoploss_entry_dist': -0.00010402, - 'stoploss_entry_dist_ratio': -0.10376381, - 'open_orders': '', - 'realized_profit': 0.0, - 'realized_profit_ratio': None, - 'total_profit_abs': -4.09e-06, - 'total_profit_fiat': ANY, - 'total_profit_ratio': None, - 'exchange': 'binance', - 'leverage': 1.0, - 'interest_rate': 0.0, - 'liquidation_price': None, - 'is_short': False, - 'funding_fees': 0.0, - 'trading_mode': TradingMode.SPOT, - 'amount_precision': 8.0, - 'price_precision': 8.0, - 'precision_mode': 2, - 'contract_size': 1, - 'has_open_orders': False, - 'orders': [{ - 'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05, - 'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy', - 'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY, - 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, - 'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY, - 'remaining': ANY, 'status': ANY, 'ft_is_entry': True, 'ft_fee_base': None, - 'funding_fee': ANY, 'ft_order_tag': None, - }], + "trade_id": 1, + "pair": "ETH/BTC", + "base_currency": "ETH", + "quote_currency": "BTC", + "open_date": ANY, + "open_timestamp": ANY, + "open_fill_date": ANY, + "open_fill_timestamp": ANY, + "is_open": ANY, + "fee_open": ANY, + "fee_open_cost": ANY, + "fee_open_currency": ANY, + "fee_close": fee.return_value, + "fee_close_cost": ANY, + "fee_close_currency": ANY, + "open_rate_requested": ANY, + "open_trade_value": 0.0010025, + "close_rate_requested": ANY, + "exit_reason": ANY, + "exit_order_status": ANY, + "min_rate": ANY, + "max_rate": ANY, + "strategy": ANY, + "enter_tag": ANY, + "timeframe": 5, + "close_date": None, + "close_timestamp": None, + "open_rate": 1.098e-05, + "close_rate": None, + "current_rate": 1.099e-05, + "amount": 91.07468123, + "amount_requested": 91.07468124, + "stake_amount": 0.001, + "max_stake_amount": None, + "trade_duration": None, + "trade_duration_s": None, + "close_profit": None, + "close_profit_pct": None, + "close_profit_abs": None, + "profit_ratio": -0.00408133, + "profit_pct": -0.41, + "profit_abs": -4.09e-06, + "profit_fiat": ANY, + "stop_loss_abs": 9.89e-06, + "stop_loss_pct": -10.0, + "stop_loss_ratio": -0.1, + "stoploss_last_update": ANY, + "stoploss_last_update_timestamp": ANY, + "initial_stop_loss_abs": 9.89e-06, + "initial_stop_loss_pct": -10.0, + "initial_stop_loss_ratio": -0.1, + "stoploss_current_dist": pytest.approx(-1.0999999e-06), + "stoploss_current_dist_ratio": -0.10009099, + "stoploss_current_dist_pct": -10.01, + "stoploss_entry_dist": -0.00010402, + "stoploss_entry_dist_ratio": -0.10376381, + "open_orders": "", + "realized_profit": 0.0, + "realized_profit_ratio": None, + "total_profit_abs": -4.09e-06, + "total_profit_fiat": ANY, + "total_profit_ratio": None, + "exchange": "binance", + "leverage": 1.0, + "interest_rate": 0.0, + "liquidation_price": None, + "is_short": False, + "funding_fees": 0.0, + "trading_mode": TradingMode.SPOT, + "amount_precision": 8.0, + "price_precision": 8.0, + "precision_mode": 2, + "contract_size": 1, + "has_open_orders": False, + "orders": [ + { + "amount": 91.07468123, + "average": 1.098e-05, + "safe_price": 1.098e-05, + "cost": 0.0009999999999054, + "filled": 91.07468123, + "ft_order_side": "buy", + "order_date": ANY, + "order_timestamp": ANY, + "order_filled_date": ANY, + "order_filled_timestamp": ANY, + "order_type": "limit", + "price": 1.098e-05, + "is_open": False, + "pair": "ETH/BTC", + "order_id": ANY, + "remaining": ANY, + "status": ANY, + "ft_is_entry": True, + "ft_fee_base": None, + "funding_fee": ANY, + "ft_order_tag": None, + } + ], } freqtradebot = get_patched_freqtradebot(mocker, default_conf) - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) mocker.patch.multiple( EXMS, fetch_ticker=ticker, @@ -121,7 +137,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING - with pytest.raises(RPCException, match=r'.*no active trade*'): + with pytest.raises(RPCException, match=r".*no active trade*"): rpc._rpc_trade_status() freqtradebot.enter_positions() @@ -130,20 +146,20 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: results = rpc._rpc_trade_status() response_unfilled = deepcopy(gen_response) # Different from "filled" response: - response_unfilled.update({ - 'amount': 91.07468124, - 'profit_ratio': 0.0, - 'profit_pct': 0.0, - 'profit_abs': 0.0, - 'total_profit_abs': 0.0, - 'open_orders': '(limit buy rem=91.07468123)', - 'has_open_orders': True, - }) - response_unfilled['orders'][0].update({ - 'is_open': True, - 'filled': 0.0, - 'remaining': 91.07468123 - }) + response_unfilled.update( + { + "amount": 91.07468124, + "profit_ratio": 0.0, + "profit_pct": 0.0, + "profit_abs": 0.0, + "total_profit_abs": 0.0, + "open_orders": "(limit buy rem=91.07468123)", + "has_open_orders": True, + } + ) + response_unfilled["orders"][0].update( + {"is_open": True, "filled": 0.0, "remaining": 91.07468123} + ) assert results[0] == response_unfilled # Open order without remaining @@ -154,9 +170,11 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: results = rpc._rpc_trade_status() # Reuse above object, only remaining changed. - response_unfilled['orders'][0].update({ - 'remaining': None, - }) + response_unfilled["orders"][0].update( + { + "remaining": None, + } + ) assert results[0] == response_unfilled trade = Trade.get_open_trades()[0] @@ -171,179 +189,179 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: results = rpc._rpc_trade_status() response = deepcopy(gen_response) - response.update({ - 'max_stake_amount': 0.001, - 'total_profit_ratio': pytest.approx(-0.00409153), - 'has_open_orders': False, - }) + response.update( + { + "max_stake_amount": 0.001, + "total_profit_ratio": pytest.approx(-0.00409153), + "has_open_orders": False, + } + ) assert results[0] == response - mocker.patch(f'{EXMS}.get_rate', - MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available"))) + mocker.patch( + f"{EXMS}.get_rate", MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")) + ) results = rpc._rpc_trade_status() - assert isnan(results[0]['profit_ratio']) - assert isnan(results[0]['current_rate']) + assert isnan(results[0]["profit_ratio"]) + assert isnan(results[0]["current_rate"]) response_norate = deepcopy(gen_response) # Update elements that are NaN when no rate is available. - response_norate.update({ - 'stoploss_current_dist': ANY, - 'stoploss_current_dist_ratio': ANY, - 'stoploss_current_dist_pct': ANY, - 'max_stake_amount': 0.001, - 'profit_ratio': ANY, - 'profit_pct': ANY, - 'profit_abs': ANY, - 'total_profit_abs': ANY, - 'total_profit_ratio': ANY, - 'current_rate': ANY, - }) + response_norate.update( + { + "stoploss_current_dist": ANY, + "stoploss_current_dist_ratio": ANY, + "stoploss_current_dist_pct": ANY, + "max_stake_amount": 0.001, + "profit_ratio": ANY, + "profit_pct": ANY, + "profit_abs": ANY, + "total_profit_abs": ANY, + "total_profit_ratio": ANY, + "current_rate": ANY, + } + ) assert results[0] == response_norate def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None: mocker.patch.multiple( - 'freqtrade.rpc.fiat_convert.CoinGeckoAPI', - get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}), + "freqtrade.rpc.fiat_convert.CoinGeckoAPI", + get_price=MagicMock(return_value={"bitcoin": {"usd": 15000.0}}), ) - mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=15000.0) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) mocker.patch.multiple( EXMS, fetch_ticker=ticker, get_fee=fee, ) - del default_conf['fiat_display_currency'] + del default_conf["fiat_display_currency"] freqtradebot = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING - with pytest.raises(RPCException, match=r'.*no active trade*'): - rpc._rpc_status_table(default_conf['stake_currency'], 'USD') - mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False) + with pytest.raises(RPCException, match=r".*no active trade*"): + rpc._rpc_status_table(default_conf["stake_currency"], "USD") + mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=False) freqtradebot.enter_positions() - result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') + result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf["stake_currency"], "USD") assert "Since" in headers assert "Pair" in headers - assert 'now' == result[0][2] - assert 'ETH/BTC' in result[0][1] - assert '0.00 (0.00)' == result[0][3] - assert '0.00' == f'{fiat_profit_sum:.2f}' + assert "now" == result[0][2] + assert "ETH/BTC" in result[0][1] + assert "0.00 (0.00)" == result[0][3] + assert "0.00" == f"{fiat_profit_sum:.2f}" - mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True) + mocker.patch(f"{EXMS}._dry_is_price_crossed", return_value=True) freqtradebot.process() - result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') + result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf["stake_currency"], "USD") assert "Since" in headers assert "Pair" in headers - assert 'now' == result[0][2] - assert 'ETH/BTC' in result[0][1] - assert '-0.41% (-0.00)' == result[0][3] - assert '-0.00' == f'{fiat_profit_sum:.2f}' + assert "now" == result[0][2] + assert "ETH/BTC" in result[0][1] + assert "-0.41% (-0.00)" == result[0][3] + assert "-0.00" == f"{fiat_profit_sum:.2f}" # Test with fiat convert rpc._fiat_converter = CryptoToFiatConverter() - result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') + result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf["stake_currency"], "USD") assert "Since" in headers assert "Pair" in headers assert len(result[0]) == 4 - assert 'now' == result[0][2] - assert 'ETH/BTC' in result[0][1] - assert '-0.41% (-0.06)' == result[0][3] - assert '-0.06' == f'{fiat_profit_sum:.2f}' + assert "now" == result[0][2] + assert "ETH/BTC" in result[0][1] + assert "-0.41% (-0.06)" == result[0][3] + assert "-0.06" == f"{fiat_profit_sum:.2f}" - rpc._config['position_adjustment_enable'] = True - rpc._config['max_entry_position_adjustment'] = 3 - result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') + rpc._config["position_adjustment_enable"] = True + rpc._config["max_entry_position_adjustment"] = 3 + result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf["stake_currency"], "USD") assert "# Entries" in headers assert len(result[0]) == 5 # 4th column should be 1/4 - as 1 order filled (a total of 4 is possible) # 3 on top of the initial one. - assert result[0][4] == '1/4' + assert result[0][4] == "1/4" - mocker.patch(f'{EXMS}.get_rate', - MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available"))) - result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD') - assert 'now' == result[0][2] - assert 'ETH/BTC' in result[0][1] - assert 'nan%' == result[0][3] + mocker.patch( + f"{EXMS}.get_rate", MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")) + ) + result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf["stake_currency"], "USD") + assert "now" == result[0][2] + assert "ETH/BTC" in result[0][1] + assert "nan%" == result[0][3] assert isnan(fiat_profit_sum) def test__rpc_timeunit_profit( - default_conf_usdt, ticker, fee, markets, mocker, time_machine) -> None: - + default_conf_usdt, ticker, fee, markets, mocker, time_machine +) -> None: time_machine.move_to("2023-09-05 10:00:00 +00:00", tick=False) - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) mocker.patch.multiple( - EXMS, - fetch_ticker=ticker, - get_fee=fee, - markets=PropertyMock(return_value=markets) + EXMS, fetch_ticker=ticker, get_fee=fee, markets=PropertyMock(return_value=markets) ) freqtradebot = get_patched_freqtradebot(mocker, default_conf_usdt) create_mock_trades_usdt(fee) - stake_currency = default_conf_usdt['stake_currency'] - fiat_display_currency = default_conf_usdt['fiat_display_currency'] + stake_currency = default_conf_usdt["stake_currency"] + fiat_display_currency = default_conf_usdt["fiat_display_currency"] rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() # Try valid data days = rpc._rpc_timeunit_profit(7, stake_currency, fiat_display_currency) - assert len(days['data']) == 7 - assert days['stake_currency'] == default_conf_usdt['stake_currency'] - assert days['fiat_display_currency'] == default_conf_usdt['fiat_display_currency'] - for day in days['data']: + assert len(days["data"]) == 7 + assert days["stake_currency"] == default_conf_usdt["stake_currency"] + assert days["fiat_display_currency"] == default_conf_usdt["fiat_display_currency"] + for day in days["data"]: # {'date': datetime.date(2022, 6, 11), 'abs_profit': 13.8299999, # 'starting_balance': 1055.37, 'rel_profit': 0.0131044, # 'fiat_value': 0.0, 'trade_count': 2} - assert day['abs_profit'] in (0.0, pytest.approx(6.83), pytest.approx(-4.09)) - assert day['rel_profit'] in (0.0, pytest.approx(0.00642902), pytest.approx(-0.00383512)) - assert day['trade_count'] in (0, 1, 2) - assert day['starting_balance'] in (pytest.approx(1062.37), pytest.approx(1066.46)) - assert day['fiat_value'] in (0.0, ) + assert day["abs_profit"] in (0.0, pytest.approx(6.83), pytest.approx(-4.09)) + assert day["rel_profit"] in (0.0, pytest.approx(0.00642902), pytest.approx(-0.00383512)) + assert day["trade_count"] in (0, 1, 2) + assert day["starting_balance"] in (pytest.approx(1062.37), pytest.approx(1066.46)) + assert day["fiat_value"] in (0.0,) # ensure first day is current date - assert str(days['data'][0]['date']) == str(datetime.now(timezone.utc).date()) + assert str(days["data"][0]["date"]) == str(datetime.now(timezone.utc).date()) # Try invalid data - with pytest.raises(RPCException, match=r'.*must be an integer greater than 0*'): + with pytest.raises(RPCException, match=r".*must be an integer greater than 0*"): rpc._rpc_timeunit_profit(0, stake_currency, fiat_display_currency) -@pytest.mark.parametrize('is_short', [True, False]) +@pytest.mark.parametrize("is_short", [True, False]) def test_rpc_trade_history(mocker, default_conf, markets, fee, is_short): - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) - mocker.patch.multiple( - EXMS, - markets=PropertyMock(return_value=markets) - ) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets)) freqtradebot = get_patched_freqtradebot(mocker, default_conf) create_mock_trades(fee, is_short) rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() trades = rpc._rpc_trade_history(2) - assert len(trades['trades']) == 2 - assert trades['trades_count'] == 2 - assert isinstance(trades['trades'][0], dict) - assert isinstance(trades['trades'][1], dict) + assert len(trades["trades"]) == 2 + assert trades["trades_count"] == 2 + assert isinstance(trades["trades"][0], dict) + assert isinstance(trades["trades"][1], dict) trades = rpc._rpc_trade_history(0) - assert len(trades['trades']) == 2 - assert trades['trades_count'] == 2 + assert len(trades["trades"]) == 2 + assert trades["trades_count"] == 2 # The first closed trade is for ETC ... sorting is descending - assert trades['trades'][-1]['pair'] == 'ETC/BTC' - assert trades['trades'][0]['pair'] == 'XRP/BTC' + assert trades["trades"][-1]["pair"] == "ETC/BTC" + assert trades["trades"][0]["pair"] == "XRP/BTC" -@pytest.mark.parametrize('is_short', [True, False]) +@pytest.mark.parametrize("is_short", [True, False]) def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short): - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) stoploss_mock = MagicMock() cancel_mock = MagicMock() mocker.patch.multiple( @@ -354,57 +372,57 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short): ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - freqtradebot.strategy.order_types['stoploss_on_exchange'] = True + freqtradebot.strategy.order_types["stoploss_on_exchange"] = True create_mock_trades(fee, is_short) rpc = RPC(freqtradebot) - with pytest.raises(RPCException, match='invalid argument'): - rpc._rpc_delete('200') + with pytest.raises(RPCException, match="invalid argument"): + rpc._rpc_delete("200") trades = Trade.session.scalars(select(Trade)).all() trades[2].orders.append( Order( - ft_order_side='stoploss', + ft_order_side="stoploss", ft_pair=trades[2].pair, ft_is_open=True, ft_amount=trades[2].amount, ft_price=trades[2].stop_loss, - order_id='102', - status='open', + order_id="102", + status="open", ) ) assert len(trades) > 2 - res = rpc._rpc_delete('1') + res = rpc._rpc_delete("1") assert isinstance(res, dict) - assert res['result'] == 'success' - assert res['trade_id'] == '1' - assert res['cancel_order_count'] == 1 + assert res["result"] == "success" + assert res["trade_id"] == "1" + assert res["cancel_order_count"] == 1 assert cancel_mock.call_count == 1 assert stoploss_mock.call_count == 0 cancel_mock.reset_mock() stoploss_mock.reset_mock() - res = rpc._rpc_delete('5') + res = rpc._rpc_delete("5") assert isinstance(res, dict) assert stoploss_mock.call_count == 1 - assert res['cancel_order_count'] == 1 + assert res["cancel_order_count"] == 1 - stoploss_mock = mocker.patch(f'{EXMS}.cancel_stoploss_order', side_effect=InvalidOrderException) + stoploss_mock = mocker.patch(f"{EXMS}.cancel_stoploss_order", side_effect=InvalidOrderException) - res = rpc._rpc_delete('3') + res = rpc._rpc_delete("3") assert stoploss_mock.call_count == 1 stoploss_mock.reset_mock() - cancel_mock = mocker.patch(f'{EXMS}.cancel_order', side_effect=InvalidOrderException) + cancel_mock = mocker.patch(f"{EXMS}.cancel_order", side_effect=InvalidOrderException) - res = rpc._rpc_delete('4') + res = rpc._rpc_delete("4") assert cancel_mock.call_count == 1 assert stoploss_mock.call_count == 0 def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None: - mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=1.1) - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=1.1) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) mocker.patch.multiple( EXMS, fetch_ticker=ticker, @@ -412,79 +430,80 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None: ) freqtradebot = get_patched_freqtradebot(mocker, default_conf_usdt) - stake_currency = default_conf_usdt['stake_currency'] - fiat_display_currency = default_conf_usdt['fiat_display_currency'] + stake_currency = default_conf_usdt["stake_currency"] + fiat_display_currency = default_conf_usdt["fiat_display_currency"] rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() res = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) - assert res['trade_count'] == 0 - assert res['first_trade_date'] == '' - assert res['first_trade_timestamp'] == 0 - assert res['latest_trade_date'] == '' - assert res['latest_trade_timestamp'] == 0 - assert res['expectancy'] == 0 - assert res['expectancy_ratio'] == 100 + assert res["trade_count"] == 0 + assert res["first_trade_date"] == "" + assert res["first_trade_timestamp"] == 0 + assert res["latest_trade_date"] == "" + assert res["latest_trade_timestamp"] == 0 + assert res["expectancy"] == 0 + assert res["expectancy_ratio"] == 100 # Create some test data create_mock_trades_usdt(fee) stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) - assert pytest.approx(stats['profit_closed_coin']) == 2.74 - assert pytest.approx(stats['profit_closed_percent_mean']) == -1.67 - assert pytest.approx(stats['profit_closed_fiat']) == 3.014 - assert pytest.approx(stats['profit_all_coin']) == -57.40975881 - assert pytest.approx(stats['profit_all_percent_mean']) == -50.83 - assert pytest.approx(stats['profit_all_fiat']) == -63.150734691 - assert pytest.approx(stats['winrate']) == 0.666666667 - assert pytest.approx(stats['expectancy']) == 0.913333333 - assert pytest.approx(stats['expectancy_ratio']) == 0.223308883 - assert stats['trade_count'] == 7 - assert stats['first_trade_humanized'] == '2 days ago' - assert stats['latest_trade_humanized'] == '17 minutes ago' - assert stats['avg_duration'] in ('0:17:40') - assert stats['best_pair'] == 'XRP/USDT' - assert stats['best_rate'] == 10.0 + assert pytest.approx(stats["profit_closed_coin"]) == 2.74 + assert pytest.approx(stats["profit_closed_percent_mean"]) == -1.67 + assert pytest.approx(stats["profit_closed_fiat"]) == 3.014 + assert pytest.approx(stats["profit_all_coin"]) == -57.40975881 + assert pytest.approx(stats["profit_all_percent_mean"]) == -50.83 + assert pytest.approx(stats["profit_all_fiat"]) == -63.150734691 + assert pytest.approx(stats["winrate"]) == 0.666666667 + assert pytest.approx(stats["expectancy"]) == 0.913333333 + assert pytest.approx(stats["expectancy_ratio"]) == 0.223308883 + assert stats["trade_count"] == 7 + assert stats["first_trade_humanized"] == "2 days ago" + assert stats["latest_trade_humanized"] == "17 minutes ago" + assert stats["avg_duration"] in ("0:17:40") + assert stats["best_pair"] == "XRP/USDT" + assert stats["best_rate"] == 10.0 # Test non-available pair - mocker.patch(f'{EXMS}.get_rate', - MagicMock(side_effect=ExchangeError("Pair 'XRP/USDT' not available"))) + mocker.patch( + f"{EXMS}.get_rate", MagicMock(side_effect=ExchangeError("Pair 'XRP/USDT' not available")) + ) stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency) - assert stats['trade_count'] == 7 - assert stats['first_trade_humanized'] == '2 days ago' - assert stats['latest_trade_humanized'] == '17 minutes ago' - assert stats['avg_duration'] in ('0:17:40') - assert stats['best_pair'] == 'XRP/USDT' - assert stats['best_rate'] == 10.0 - assert isnan(stats['profit_all_coin']) + assert stats["trade_count"] == 7 + assert stats["first_trade_humanized"] == "2 days ago" + assert stats["latest_trade_humanized"] == "17 minutes ago" + assert stats["avg_duration"] in ("0:17:40") + assert stats["best_pair"] == "XRP/USDT" + assert stats["best_rate"] == 10.0 + assert isnan(stats["profit_all_coin"]) def test_rpc_balance_handle_error(default_conf, mocker): mock_balance = { - 'BTC': { - 'free': 10.0, - 'total': 12.0, - 'used': 2.0, + "BTC": { + "free": 10.0, + "total": 12.0, + "used": 2.0, + }, + "ETH": { + "free": 1.0, + "total": 5.0, + "used": 4.0, }, - 'ETH': { - 'free': 1.0, - 'total': 5.0, - 'used': 4.0, - } } # ETH will be skipped due to mocked Error below mocker.patch.multiple( - 'freqtrade.rpc.fiat_convert.CoinGeckoAPI', - get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}), + "freqtrade.rpc.fiat_convert.CoinGeckoAPI", + get_price=MagicMock(return_value={"bitcoin": {"usd": 15000.0}}), ) - mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=15000.0) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) mocker.patch.multiple( EXMS, get_balances=MagicMock(return_value=mock_balance), - get_tickers=MagicMock(side_effect=TemporaryError('Could not load ticker due to xxx')) + get_tickers=MagicMock(side_effect=TemporaryError("Could not load ticker due to xxx")), ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) @@ -492,26 +511,26 @@ def test_rpc_balance_handle_error(default_conf, mocker): rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() with pytest.raises(RPCException, match="Error getting current tickers."): - rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency']) + rpc._rpc_balance(default_conf["stake_currency"], default_conf["fiat_display_currency"]) def test_rpc_balance_handle(default_conf_usdt, mocker, tickers): mock_balance = { - 'BTC': { - 'free': 0.01, - 'total': 0.012, - 'used': 0.002, + "BTC": { + "free": 0.01, + "total": 0.012, + "used": 0.002, }, - 'ETH': { - 'free': 1.0, - 'total': 5.0, - 'used': 4.0, + "ETH": { + "free": 1.0, + "total": 5.0, + "used": 4.0, + }, + "USDT": { + "free": 50.0, + "total": 100.0, + "used": 5.0, }, - 'USDT': { - 'free': 50.0, - 'total': 100.0, - 'used': 5.0, - } } mock_pos = [ { @@ -533,17 +552,17 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers): "markPrice": 2896.41, "collateral": 20, "marginType": "isolated", - "side": 'short', - "percentage": None + "side": "short", + "percentage": None, } ] mocker.patch.multiple( - 'freqtrade.rpc.fiat_convert.CoinGeckoAPI', - get_price=MagicMock(return_value={'bitcoin': {'usd': 1.2}}), + "freqtrade.rpc.fiat_convert.CoinGeckoAPI", + get_price=MagicMock(return_value={"bitcoin": {"usd": 1.2}}), ) - mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=1.2) - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=1.2) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) mocker.patch.multiple( EXMS, validate_trading_mode_and_margin_mode=MagicMock(), @@ -551,96 +570,95 @@ def test_rpc_balance_handle(default_conf_usdt, mocker, tickers): fetch_positions=MagicMock(return_value=mock_pos), get_tickers=tickers, get_valid_pair_combination=MagicMock( - side_effect=lambda a, b: f"{b}/{a}" if a == "USDT" else f"{a}/{b}") + side_effect=lambda a, b: f"{b}/{a}" if a == "USDT" else f"{a}/{b}" + ), ) - default_conf_usdt['dry_run'] = False - default_conf_usdt['trading_mode'] = 'futures' + default_conf_usdt["dry_run"] = False + default_conf_usdt["trading_mode"] = "futures" freqtradebot = get_patched_freqtradebot(mocker, default_conf_usdt) patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) rpc._fiat_converter = CryptoToFiatConverter() result = rpc._rpc_balance( - default_conf_usdt['stake_currency'], default_conf_usdt['fiat_display_currency']) + default_conf_usdt["stake_currency"], default_conf_usdt["fiat_display_currency"] + ) - assert pytest.approx(result['total']) == 2824.83464 - assert pytest.approx(result['value']) == 2824.83464 * 1.2 + assert pytest.approx(result["total"]) == 2824.83464 + assert pytest.approx(result["value"]) == 2824.83464 * 1.2 assert tickers.call_count == 1 - assert tickers.call_args_list[0][1]['cached'] is True - assert 'USD' == result['symbol'] - assert result['currencies'] == [ + assert tickers.call_args_list[0][1]["cached"] is True + assert "USD" == result["symbol"] + assert result["currencies"] == [ { - 'currency': 'BTC', - 'free': 0.01, - 'balance': 0.012, - 'used': 0.002, - 'bot_owned': 0, - 'est_stake': 103.78464, - 'est_stake_bot': 0, - 'stake': 'USDT', - 'side': 'long', - 'leverage': 1, - 'position': 0, - 'is_bot_managed': False, - 'is_position': False + "currency": "BTC", + "free": 0.01, + "balance": 0.012, + "used": 0.002, + "bot_owned": 0, + "est_stake": 103.78464, + "est_stake_bot": 0, + "stake": "USDT", + "side": "long", + "leverage": 1, + "position": 0, + "is_bot_managed": False, + "is_position": False, }, { - 'currency': 'ETH', - 'free': 1.0, - 'balance': 5.0, - 'used': 4.0, - 'bot_owned': 0, - 'est_stake': 2651.05, - 'est_stake_bot': 0, - 'stake': 'USDT', - 'side': 'long', - 'leverage': 1, - 'position': 0, - 'is_bot_managed': False, - 'is_position': False + "currency": "ETH", + "free": 1.0, + "balance": 5.0, + "used": 4.0, + "bot_owned": 0, + "est_stake": 2651.05, + "est_stake_bot": 0, + "stake": "USDT", + "side": "long", + "leverage": 1, + "position": 0, + "is_bot_managed": False, + "is_position": False, }, { - 'currency': 'USDT', - 'free': 50.0, - 'balance': 100.0, - 'used': 5.0, - 'bot_owned': 49.5, - 'est_stake': 50.0, - 'est_stake_bot': 49.5, - 'stake': 'USDT', - 'side': 'long', - 'leverage': 1, - 'position': 0, - 'is_bot_managed': True, - 'is_position': False + "currency": "USDT", + "free": 50.0, + "balance": 100.0, + "used": 5.0, + "bot_owned": 49.5, + "est_stake": 50.0, + "est_stake_bot": 49.5, + "stake": "USDT", + "side": "long", + "leverage": 1, + "position": 0, + "is_bot_managed": True, + "is_position": False, }, { - 'currency': 'ETH/USDT:USDT', - 'free': 0, - 'balance': 0, - 'used': 0, - 'position': 10.0, - 'est_stake': 20, - 'est_stake_bot': 20, - 'stake': 'USDT', - 'leverage': 5.0, - 'side': 'short', - 'is_bot_managed': True, - 'is_position': True - } + "currency": "ETH/USDT:USDT", + "free": 0, + "balance": 0, + "used": 0, + "position": 10.0, + "est_stake": 20, + "est_stake_bot": 20, + "stake": "USDT", + "leverage": 5.0, + "side": "short", + "is_bot_managed": True, + "is_position": True, + }, ] - assert pytest.approx(result['total_bot']) == 69.5 - assert pytest.approx(result['total']) == 2824.83464 # ETH stake is missing. - assert result['starting_capital'] == 50 - assert result['starting_capital_ratio'] == pytest.approx(0.3899999) + assert pytest.approx(result["total_bot"]) == 69.5 + assert pytest.approx(result["total"]) == 2824.83464 # ETH stake is missing. + assert result["starting_capital"] == 50 + assert result["starting_capital_ratio"] == pytest.approx(0.3899999) def test_rpc_start(mocker, default_conf) -> None: - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) - mocker.patch.multiple( - EXMS, - fetch_ticker=MagicMock() - ) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) + mocker.patch.multiple(EXMS, fetch_ticker=MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtradebot) @@ -648,20 +666,17 @@ def test_rpc_start(mocker, default_conf) -> None: freqtradebot.state = State.STOPPED result = rpc._rpc_start() - assert {'status': 'starting trader ...'} == result + assert {"status": "starting trader ..."} == result assert freqtradebot.state == State.RUNNING result = rpc._rpc_start() - assert {'status': 'already running'} == result + assert {"status": "already running"} == result assert freqtradebot.state == State.RUNNING def test_rpc_stop(mocker, default_conf) -> None: - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) - mocker.patch.multiple( - EXMS, - fetch_ticker=MagicMock() - ) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) + mocker.patch.multiple(EXMS, fetch_ticker=MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtradebot) @@ -669,35 +684,32 @@ def test_rpc_stop(mocker, default_conf) -> None: freqtradebot.state = State.RUNNING result = rpc._rpc_stop() - assert {'status': 'stopping trader ...'} == result + assert {"status": "stopping trader ..."} == result assert freqtradebot.state == State.STOPPED result = rpc._rpc_stop() - assert {'status': 'already stopped'} == result + assert {"status": "already stopped"} == result assert freqtradebot.state == State.STOPPED def test_rpc_stopentry(mocker, default_conf) -> None: - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) - mocker.patch.multiple( - EXMS, - fetch_ticker=MagicMock() - ) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) + mocker.patch.multiple(EXMS, fetch_ticker=MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) freqtradebot.state = State.RUNNING - assert freqtradebot.config['max_open_trades'] != 0 + assert freqtradebot.config["max_open_trades"] != 0 result = rpc._rpc_stopentry() - assert {'status': 'No more entries will occur from now. Run /reload_config to reset.'} == result - assert freqtradebot.config['max_open_trades'] == 0 + assert {"status": "No more entries will occur from now. Run /reload_config to reset."} == result + assert freqtradebot.config["max_open_trades"] == 0 def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) cancel_order_mock = MagicMock() mocker.patch.multiple( @@ -706,157 +718,151 @@ def test_rpc_force_exit(default_conf, ticker, fee, mocker) -> None: cancel_order=cancel_order_mock, fetch_order=MagicMock( return_value={ - 'status': 'closed', - 'type': 'limit', - 'side': 'buy', - 'filled': 0.0, + "status": "closed", + "type": "limit", + "side": "buy", + "filled": 0.0, } ), _dry_is_price_crossed=MagicMock(return_value=True), get_fee=fee, ) - mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=1000) + mocker.patch("freqtrade.wallets.Wallets.get_free", return_value=1000) freqtradebot = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) freqtradebot.state = State.STOPPED - with pytest.raises(RPCException, match=r'.*trader is not running*'): + with pytest.raises(RPCException, match=r".*trader is not running*"): rpc._rpc_force_exit(None) freqtradebot.state = State.RUNNING - with pytest.raises(RPCException, match=r'.*invalid argument*'): + with pytest.raises(RPCException, match=r".*invalid argument*"): rpc._rpc_force_exit(None) - msg = rpc._rpc_force_exit('all') - assert msg == {'result': 'Created exit orders for all open trades.'} + msg = rpc._rpc_force_exit("all") + assert msg == {"result": "Created exit orders for all open trades."} freqtradebot.enter_positions() - msg = rpc._rpc_force_exit('all') - assert msg == {'result': 'Created exit orders for all open trades.'} + msg = rpc._rpc_force_exit("all") + assert msg == {"result": "Created exit orders for all open trades."} freqtradebot.enter_positions() - msg = rpc._rpc_force_exit('2') - assert msg == {'result': 'Created exit order for trade 2.'} + msg = rpc._rpc_force_exit("2") + assert msg == {"result": "Created exit order for trade 2."} freqtradebot.state = State.STOPPED - with pytest.raises(RPCException, match=r'.*trader is not running*'): + with pytest.raises(RPCException, match=r".*trader is not running*"): rpc._rpc_force_exit(None) - with pytest.raises(RPCException, match=r'.*trader is not running*'): - rpc._rpc_force_exit('all') + with pytest.raises(RPCException, match=r".*trader is not running*"): + rpc._rpc_force_exit("all") freqtradebot.state = State.RUNNING assert cancel_order_mock.call_count == 0 - mocker.patch(f'{EXMS}._dry_is_price_crossed', MagicMock(return_value=False)) + mocker.patch(f"{EXMS}._dry_is_price_crossed", MagicMock(return_value=False)) freqtradebot.enter_positions() # make an limit-buy open trade - trade = Trade.session.scalars(select(Trade).filter(Trade.id == '3')).first() + trade = Trade.session.scalars(select(Trade).filter(Trade.id == "3")).first() filled_amount = trade.amount / 2 # Fetch order - it's open first, and closed after cancel_order is called. mocker.patch( - f'{EXMS}.fetch_order', - side_effect=[{ - 'id': trade.orders[0].order_id, - 'status': 'open', - 'type': 'limit', - 'side': 'buy', - 'filled': filled_amount - }, { - 'id': trade.orders[0].order_id, - 'status': 'closed', - 'type': 'limit', - 'side': 'buy', - 'filled': filled_amount - }] + f"{EXMS}.fetch_order", + side_effect=[ + { + "id": trade.orders[0].order_id, + "status": "open", + "type": "limit", + "side": "buy", + "filled": filled_amount, + }, + { + "id": trade.orders[0].order_id, + "status": "closed", + "type": "limit", + "side": "buy", + "filled": filled_amount, + }, + ], ) # check that the trade is called, which is done by ensuring exchange.cancel_order is called # and trade amount is updated - rpc._rpc_force_exit('3') + rpc._rpc_force_exit("3") assert cancel_order_mock.call_count == 1 assert pytest.approx(trade.amount) == filled_amount mocker.patch( - f'{EXMS}.fetch_order', - return_value={ - 'status': 'open', - 'type': 'limit', - 'side': 'buy', - 'filled': filled_amount - }) + f"{EXMS}.fetch_order", + return_value={"status": "open", "type": "limit", "side": "buy", "filled": filled_amount}, + ) - freqtradebot.config['max_open_trades'] = 3 + freqtradebot.config["max_open_trades"] = 3 freqtradebot.enter_positions() cancel_order_mock.reset_mock() - trade = Trade.session.scalars(select(Trade).filter(Trade.id == '3')).first() + trade = Trade.session.scalars(select(Trade).filter(Trade.id == "3")).first() amount = trade.amount # make an limit-sell open order trade mocker.patch( - f'{EXMS}.fetch_order', + f"{EXMS}.fetch_order", return_value={ - 'status': 'open', - 'type': 'limit', - 'side': 'sell', - 'amount': amount, - 'remaining': amount, - 'filled': 0.0, - 'id': trade.orders[-1].order_id, - } + "status": "open", + "type": "limit", + "side": "sell", + "amount": amount, + "remaining": amount, + "filled": 0.0, + "id": trade.orders[-1].order_id, + }, ) cancel_order_3 = mocker.patch( - f'{EXMS}.cancel_order_with_result', + f"{EXMS}.cancel_order_with_result", return_value={ - 'status': 'canceled', - 'type': 'limit', - 'side': 'sell', - 'amount': amount, - 'remaining': amount, - 'filled': 0.0, - 'id': trade.orders[-1].order_id, - } + "status": "canceled", + "type": "limit", + "side": "sell", + "amount": amount, + "remaining": amount, + "filled": 0.0, + "id": trade.orders[-1].order_id, + }, ) - msg = rpc._rpc_force_exit('3') - assert msg == {'result': 'Created exit order for trade 3.'} + msg = rpc._rpc_force_exit("3") + assert msg == {"result": "Created exit order for trade 3."} # status quo, no exchange calls assert cancel_order_3.call_count == 1 assert cancel_order_mock.call_count == 0 - trade = Trade.session.scalars(select(Trade).filter(Trade.id == '4')).first() + trade = Trade.session.scalars(select(Trade).filter(Trade.id == "4")).first() amount = trade.amount # make an limit-buy open trade, if there is no 'filled', don't sell it mocker.patch( - f'{EXMS}.fetch_order', - return_value={ - 'status': 'open', - 'type': 'limit', - 'side': 'buy', - 'filled': None - } + f"{EXMS}.fetch_order", + return_value={"status": "open", "type": "limit", "side": "buy", "filled": None}, ) cancel_order_4 = mocker.patch( - f'{EXMS}.cancel_order_with_result', - return_value={ - 'status': 'canceled', - 'type': 'limit', - 'side': 'sell', - 'amount': amount, - 'remaining': 0.0, - 'filled': amount, - 'id': trade.orders[0].order_id, - } - ) + f"{EXMS}.cancel_order_with_result", + return_value={ + "status": "canceled", + "type": "limit", + "side": "sell", + "amount": amount, + "remaining": 0.0, + "filled": amount, + "id": trade.orders[0].order_id, + }, + ) # check that the trade is called, which is done by ensuring exchange.cancel_order is called - msg = rpc._rpc_force_exit('4') - assert msg == {'result': 'Created exit order for trade 4.'} + msg = rpc._rpc_force_exit("4") + assert msg == {"result": "Created exit order for trade 4."} assert cancel_order_4.call_count == 1 assert cancel_order_mock.call_count == 0 assert pytest.approx(trade.amount) == amount def test_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None: - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) mocker.patch.multiple( EXMS, get_balances=MagicMock(return_value=ticker), @@ -872,14 +878,13 @@ def test_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None: res = rpc._rpc_performance() assert len(res) == 3 - assert res[0]['pair'] == 'NEO/USDT' - assert res[0]['count'] == 1 - assert res[0]['profit_pct'] == 5.0 + assert res[0]["pair"] == "NEO/USDT" + assert res[0]["count"] == 1 + assert res[0]["profit_pct"] == 5.0 def test_enter_tag_performance_handle(default_conf, ticker, fee, mocker) -> None: - - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) mocker.patch.multiple( EXMS, get_balances=MagicMock(return_value=ticker), @@ -898,24 +903,21 @@ def test_enter_tag_performance_handle(default_conf, ticker, fee, mocker) -> None res = rpc._rpc_enter_tag_performance(None) assert len(res) == 3 - assert res[0]['enter_tag'] == 'TEST1' - assert res[0]['count'] == 1 - assert res[0]['profit_pct'] == 5.0 + assert res[0]["enter_tag"] == "TEST1" + assert res[0]["count"] == 1 + assert res[0]["profit_pct"] == 5.0 res = rpc._rpc_enter_tag_performance(None) assert len(res) == 3 - assert res[0]['enter_tag'] == 'TEST1' - assert res[0]['count'] == 1 - assert res[0]['profit_pct'] == 5.0 + assert res[0]["enter_tag"] == "TEST1" + assert res[0]["count"] == 1 + assert res[0]["profit_pct"] == 5.0 def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee): - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) - mocker.patch.multiple( - EXMS, - markets=PropertyMock(return_value=markets) - ) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets)) freqtradebot = get_patched_freqtradebot(mocker, default_conf) create_mock_trades(fee) @@ -924,23 +926,23 @@ def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee): res = rpc._rpc_enter_tag_performance(None) assert len(res) == 2 - assert res[0]['enter_tag'] == 'TEST1' - assert res[0]['count'] == 1 - assert pytest.approx(res[0]['profit_pct']) == 0.5 - assert res[1]['enter_tag'] == 'Other' - assert res[1]['count'] == 1 - assert pytest.approx(res[1]['profit_pct']) == 1.0 + assert res[0]["enter_tag"] == "TEST1" + assert res[0]["count"] == 1 + assert pytest.approx(res[0]["profit_pct"]) == 0.5 + assert res[1]["enter_tag"] == "Other" + assert res[1]["count"] == 1 + assert pytest.approx(res[1]["profit_pct"]) == 1.0 # Test for a specific pair - res = rpc._rpc_enter_tag_performance('ETC/BTC') + res = rpc._rpc_enter_tag_performance("ETC/BTC") assert len(res) == 1 - assert res[0]['count'] == 1 - assert res[0]['enter_tag'] == 'TEST1' - assert pytest.approx(res[0]['profit_pct']) == 0.5 + assert res[0]["count"] == 1 + assert res[0]["enter_tag"] == "TEST1" + assert pytest.approx(res[0]["profit_pct"]) == 0.5 def test_exit_reason_performance_handle(default_conf_usdt, ticker, fee, mocker) -> None: - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) mocker.patch.multiple( EXMS, get_balances=MagicMock(return_value=ticker), @@ -958,20 +960,17 @@ def test_exit_reason_performance_handle(default_conf_usdt, ticker, fee, mocker) res = rpc._rpc_exit_reason_performance(None) assert len(res) == 3 - assert res[0]['exit_reason'] == 'exit_signal' - assert res[0]['count'] == 1 - assert res[0]['profit_pct'] == 5.0 + assert res[0]["exit_reason"] == "exit_signal" + assert res[0]["count"] == 1 + assert res[0]["profit_pct"] == 5.0 - assert res[1]['exit_reason'] == 'roi' - assert res[2]['exit_reason'] == 'Other' + assert res[1]["exit_reason"] == "roi" + assert res[2]["exit_reason"] == "Other" def test_exit_reason_performance_handle_2(mocker, default_conf, markets, fee): - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) - mocker.patch.multiple( - EXMS, - markets=PropertyMock(return_value=markets) - ) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets)) freqtradebot = get_patched_freqtradebot(mocker, default_conf) create_mock_trades(fee) @@ -980,23 +979,23 @@ def test_exit_reason_performance_handle_2(mocker, default_conf, markets, fee): res = rpc._rpc_exit_reason_performance(None) assert len(res) == 2 - assert res[0]['exit_reason'] == 'sell_signal' - assert res[0]['count'] == 1 - assert pytest.approx(res[0]['profit_pct']) == 0.5 - assert res[1]['exit_reason'] == 'roi' - assert res[1]['count'] == 1 - assert pytest.approx(res[1]['profit_pct']) == 1.0 + assert res[0]["exit_reason"] == "sell_signal" + assert res[0]["count"] == 1 + assert pytest.approx(res[0]["profit_pct"]) == 0.5 + assert res[1]["exit_reason"] == "roi" + assert res[1]["count"] == 1 + assert pytest.approx(res[1]["profit_pct"]) == 1.0 # Test for a specific pair - res = rpc._rpc_exit_reason_performance('ETC/BTC') + res = rpc._rpc_exit_reason_performance("ETC/BTC") assert len(res) == 1 - assert res[0]['count'] == 1 - assert res[0]['exit_reason'] == 'sell_signal' - assert pytest.approx(res[0]['profit_pct']) == 0.5 + assert res[0]["count"] == 1 + assert res[0]["exit_reason"] == "sell_signal" + assert pytest.approx(res[0]["profit_pct"]) == 0.5 def test_mix_tag_performance_handle(default_conf, ticker, fee, mocker) -> None: - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) mocker.patch.multiple( EXMS, get_balances=MagicMock(return_value=ticker), @@ -1014,17 +1013,14 @@ def test_mix_tag_performance_handle(default_conf, ticker, fee, mocker) -> None: res = rpc._rpc_mix_tag_performance(None) assert len(res) == 3 - assert res[0]['mix_tag'] == 'TEST1 exit_signal' - assert res[0]['count'] == 1 - assert res[0]['profit_pct'] == 5.0 + assert res[0]["mix_tag"] == "TEST1 exit_signal" + assert res[0]["count"] == 1 + assert res[0]["profit_pct"] == 5.0 def test_mix_tag_performance_handle_2(mocker, default_conf, markets, fee): - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) - mocker.patch.multiple( - EXMS, - markets=PropertyMock(return_value=markets) - ) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets)) freqtradebot = get_patched_freqtradebot(mocker, default_conf) create_mock_trades(fee) @@ -1033,24 +1029,24 @@ def test_mix_tag_performance_handle_2(mocker, default_conf, markets, fee): res = rpc._rpc_mix_tag_performance(None) assert len(res) == 2 - assert res[0]['mix_tag'] == 'TEST1 sell_signal' - assert res[0]['count'] == 1 - assert pytest.approx(res[0]['profit_pct']) == 0.5 - assert res[1]['mix_tag'] == 'Other roi' - assert res[1]['count'] == 1 - assert pytest.approx(res[1]['profit_pct']) == 1.0 + assert res[0]["mix_tag"] == "TEST1 sell_signal" + assert res[0]["count"] == 1 + assert pytest.approx(res[0]["profit_pct"]) == 0.5 + assert res[1]["mix_tag"] == "Other roi" + assert res[1]["count"] == 1 + assert pytest.approx(res[1]["profit_pct"]) == 1.0 # Test for a specific pair - res = rpc._rpc_mix_tag_performance('ETC/BTC') + res = rpc._rpc_mix_tag_performance("ETC/BTC") assert len(res) == 1 - assert res[0]['count'] == 1 - assert res[0]['mix_tag'] == 'TEST1 sell_signal' - assert pytest.approx(res[0]['profit_pct']) == 0.5 + assert res[0]["count"] == 1 + assert res[0]["mix_tag"] == "TEST1 sell_signal" + assert pytest.approx(res[0]["profit_pct"]) == 0.5 def test_rpc_count(mocker, default_conf, ticker, fee) -> None: - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) mocker.patch.multiple( EXMS, get_balances=MagicMock(return_value=ticker), @@ -1072,104 +1068,104 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None: def test_rpc_force_entry(mocker, default_conf, ticker, fee, limit_buy_order_open) -> None: - default_conf['force_entry_enable'] = True - default_conf['max_open_trades'] = 0 - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + default_conf["force_entry_enable"] = True + default_conf["max_open_trades"] = 0 + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) buy_mm = MagicMock(return_value=limit_buy_order_open) mocker.patch.multiple( EXMS, get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, - create_order=buy_mm + create_order=buy_mm, ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) - pair = 'ETH/BTC' - with pytest.raises(RPCException, match='Maximum number of trades is reached.'): + pair = "ETH/BTC" + with pytest.raises(RPCException, match="Maximum number of trades is reached."): rpc._rpc_force_entry(pair, None) - freqtradebot.config['max_open_trades'] = 5 + freqtradebot.config["max_open_trades"] = 5 trade = rpc._rpc_force_entry(pair, None) assert isinstance(trade, Trade) assert trade.pair == pair - assert trade.open_rate == ticker()['bid'] + assert trade.open_rate == ticker()["bid"] # Test buy duplicate - with pytest.raises(RPCException, match=r'position for ETH/BTC already open - id: 1'): + with pytest.raises(RPCException, match=r"position for ETH/BTC already open - id: 1"): rpc._rpc_force_entry(pair, 0.0001) - pair = 'XRP/BTC' - trade = rpc._rpc_force_entry(pair, 0.0001, order_type='limit') + pair = "XRP/BTC" + trade = rpc._rpc_force_entry(pair, 0.0001, order_type="limit") assert isinstance(trade, Trade) assert trade.pair == pair assert trade.open_rate == 0.0001 - with pytest.raises(RPCException, - match=r'Symbol does not exist or market is not active.'): - rpc._rpc_force_entry('LTC/NOTHING', 0.0001) + with pytest.raises(RPCException, match=r"Symbol does not exist or market is not active."): + rpc._rpc_force_entry("LTC/NOTHING", 0.0001) # Test buy pair not with stakes - with pytest.raises(RPCException, - match=r'Wrong pair selected. Only pairs with stake-currency.*'): - rpc._rpc_force_entry('LTC/ETH', 0.0001) + with pytest.raises( + RPCException, match=r"Wrong pair selected. Only pairs with stake-currency.*" + ): + rpc._rpc_force_entry("LTC/ETH", 0.0001) # Test with defined stake_amount - pair = 'LTC/BTC' - trade = rpc._rpc_force_entry(pair, 0.0001, order_type='limit', stake_amount=0.05) + pair = "LTC/BTC" + trade = rpc._rpc_force_entry(pair, 0.0001, order_type="limit", stake_amount=0.05) assert trade.stake_amount == 0.05 - assert trade.buy_tag == 'force_entry' + assert trade.buy_tag == "force_entry" - assert trade.open_orders_ids[-1] == 'mocked_limit_buy' + assert trade.open_orders_ids[-1] == "mocked_limit_buy" freqtradebot.strategy.position_adjustment_enable = True - with pytest.raises(RPCException, match=r'position for LTC/BTC already open.*open order.*'): - rpc._rpc_force_entry(pair, 0.0001, order_type='limit', stake_amount=0.05) + with pytest.raises(RPCException, match=r"position for LTC/BTC already open.*open order.*"): + rpc._rpc_force_entry(pair, 0.0001, order_type="limit", stake_amount=0.05) # Test not buying - pair = 'XRP/BTC' + pair = "XRP/BTC" freqtradebot = get_patched_freqtradebot(mocker, default_conf) - freqtradebot.config['stake_amount'] = 0 + freqtradebot.config["stake_amount"] = 0 patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) - pair = 'TKN/BTC' + pair = "TKN/BTC" with pytest.raises(RPCException, match=r"Failed to enter position for TKN/BTC."): trade = rpc._rpc_force_entry(pair, None) def test_rpc_force_entry_stopped(mocker, default_conf) -> None: - default_conf['force_entry_enable'] = True - default_conf['initial_state'] = 'stopped' - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + default_conf["force_entry_enable"] = True + default_conf["initial_state"] = "stopped" + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) - pair = 'ETH/BTC' - with pytest.raises(RPCException, match=r'trader is not running'): + pair = "ETH/BTC" + with pytest.raises(RPCException, match=r"trader is not running"): rpc._rpc_force_entry(pair, None) def test_rpc_force_entry_disabled(mocker, default_conf) -> None: - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) - pair = 'ETH/BTC' - with pytest.raises(RPCException, match=r'Force_entry not enabled.'): + pair = "ETH/BTC" + with pytest.raises(RPCException, match=r"Force_entry not enabled."): rpc._rpc_force_entry(pair, None) def test_rpc_force_entry_wrong_mode(mocker, default_conf) -> None: - default_conf['force_entry_enable'] = True - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + default_conf["force_entry_enable"] = True + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) patch_get_signal(freqtradebot) rpc = RPC(freqtradebot) - pair = 'ETH/BTC' + pair = "ETH/BTC" with pytest.raises(RPCException, match="Can't go short on Spot markets."): rpc._rpc_force_entry(pair, None, order_side=SignalDirection.SHORT) @@ -1178,134 +1174,140 @@ def test_rpc_force_entry_wrong_mode(mocker, default_conf) -> None: def test_rpc_add_and_delete_lock(mocker, default_conf): freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc = RPC(freqtradebot) - pair = 'ETH/BTC' + pair = "ETH/BTC" - rpc._rpc_add_lock(pair, datetime.now(timezone.utc) + timedelta(minutes=4), '', '*') - rpc._rpc_add_lock(pair, datetime.now(timezone.utc) + timedelta(minutes=5), '', '*') - rpc._rpc_add_lock(pair, datetime.now(timezone.utc) + timedelta(minutes=10), '', '*') + rpc._rpc_add_lock(pair, datetime.now(timezone.utc) + timedelta(minutes=4), "", "*") + rpc._rpc_add_lock(pair, datetime.now(timezone.utc) + timedelta(minutes=5), "", "*") + rpc._rpc_add_lock(pair, datetime.now(timezone.utc) + timedelta(minutes=10), "", "*") locks = rpc._rpc_locks() - assert locks['lock_count'] == 3 - locks1 = rpc._rpc_delete_lock(lockid=locks['locks'][0]['id']) - assert locks1['lock_count'] == 2 + assert locks["lock_count"] == 3 + locks1 = rpc._rpc_delete_lock(lockid=locks["locks"][0]["id"]) + assert locks1["lock_count"] == 2 locks2 = rpc._rpc_delete_lock(pair=pair) - assert locks2['lock_count'] == 0 + assert locks2["lock_count"] == 0 def test_rpc_whitelist(mocker, default_conf) -> None: - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc = RPC(freqtradebot) ret = rpc._rpc_whitelist() - assert len(ret['method']) == 1 - assert 'StaticPairList' in ret['method'] - assert ret['whitelist'] == default_conf['exchange']['pair_whitelist'] + assert len(ret["method"]) == 1 + assert "StaticPairList" in ret["method"] + assert ret["whitelist"] == default_conf["exchange"]["pair_whitelist"] def test_rpc_whitelist_dynamic(mocker, default_conf) -> None: - default_conf['pairlists'] = [{'method': 'VolumePairList', - 'number_assets': 4, - }] - mocker.patch(f'{EXMS}.exchange_has', MagicMock(return_value=True)) - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + default_conf["pairlists"] = [ + { + "method": "VolumePairList", + "number_assets": 4, + } + ] + mocker.patch(f"{EXMS}.exchange_has", MagicMock(return_value=True)) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc = RPC(freqtradebot) ret = rpc._rpc_whitelist() - assert len(ret['method']) == 1 - assert 'VolumePairList' in ret['method'] - assert ret['length'] == 4 - assert ret['whitelist'] == default_conf['exchange']['pair_whitelist'] + assert len(ret["method"]) == 1 + assert "VolumePairList" in ret["method"] + assert ret["length"] == 4 + assert ret["whitelist"] == default_conf["exchange"]["pair_whitelist"] def test_rpc_blacklist(mocker, default_conf) -> None: - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc = RPC(freqtradebot) ret = rpc._rpc_blacklist(None) - assert len(ret['method']) == 1 - assert 'StaticPairList' in ret['method'] - assert len(ret['blacklist']) == 2 - assert ret['blacklist'] == default_conf['exchange']['pair_blacklist'] - assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC'] + assert len(ret["method"]) == 1 + assert "StaticPairList" in ret["method"] + assert len(ret["blacklist"]) == 2 + assert ret["blacklist"] == default_conf["exchange"]["pair_blacklist"] + assert ret["blacklist"] == ["DOGE/BTC", "HOT/BTC"] ret = rpc._rpc_blacklist(["ETH/BTC"]) - assert 'StaticPairList' in ret['method'] - assert len(ret['blacklist']) == 3 - assert ret['blacklist'] == default_conf['exchange']['pair_blacklist'] - assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC', 'ETH/BTC'] + assert "StaticPairList" in ret["method"] + assert len(ret["blacklist"]) == 3 + assert ret["blacklist"] == default_conf["exchange"]["pair_blacklist"] + assert ret["blacklist"] == ["DOGE/BTC", "HOT/BTC", "ETH/BTC"] ret = rpc._rpc_blacklist(["ETH/BTC"]) - assert 'errors' in ret - assert isinstance(ret['errors'], dict) - assert ret['errors']['ETH/BTC']['error_msg'] == 'Pair ETH/BTC already in pairlist.' + assert "errors" in ret + assert isinstance(ret["errors"], dict) + assert ret["errors"]["ETH/BTC"]["error_msg"] == "Pair ETH/BTC already in pairlist." ret = rpc._rpc_blacklist(["*/BTC"]) - assert 'StaticPairList' in ret['method'] - assert len(ret['blacklist']) == 3 - assert ret['blacklist'] == default_conf['exchange']['pair_blacklist'] - assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC', 'ETH/BTC'] - assert ret['blacklist_expanded'] == ['ETH/BTC'] - assert 'errors' in ret - assert isinstance(ret['errors'], dict) - assert ret['errors'] == {'*/BTC': {'error_msg': 'Pair */BTC is not a valid wildcard.'}} + assert "StaticPairList" in ret["method"] + assert len(ret["blacklist"]) == 3 + assert ret["blacklist"] == default_conf["exchange"]["pair_blacklist"] + assert ret["blacklist"] == ["DOGE/BTC", "HOT/BTC", "ETH/BTC"] + assert ret["blacklist_expanded"] == ["ETH/BTC"] + assert "errors" in ret + assert isinstance(ret["errors"], dict) + assert ret["errors"] == {"*/BTC": {"error_msg": "Pair */BTC is not a valid wildcard."}} ret = rpc._rpc_blacklist(["XRP/.*"]) - assert 'StaticPairList' in ret['method'] - assert len(ret['blacklist']) == 4 - assert ret['blacklist'] == default_conf['exchange']['pair_blacklist'] - assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC', 'ETH/BTC', 'XRP/.*'] - assert ret['blacklist_expanded'] == ['ETH/BTC', 'XRP/BTC', 'XRP/USDT'] - assert 'errors' in ret - assert isinstance(ret['errors'], dict) + assert "StaticPairList" in ret["method"] + assert len(ret["blacklist"]) == 4 + assert ret["blacklist"] == default_conf["exchange"]["pair_blacklist"] + assert ret["blacklist"] == ["DOGE/BTC", "HOT/BTC", "ETH/BTC", "XRP/.*"] + assert ret["blacklist_expanded"] == ["ETH/BTC", "XRP/BTC", "XRP/USDT"] + assert "errors" in ret + assert isinstance(ret["errors"], dict) - ret = rpc._rpc_blacklist_delete(["DOGE/BTC", 'HOT/BTC']) + ret = rpc._rpc_blacklist_delete(["DOGE/BTC", "HOT/BTC"]) - assert 'StaticPairList' in ret['method'] - assert len(ret['blacklist']) == 2 - assert ret['blacklist'] == default_conf['exchange']['pair_blacklist'] - assert ret['blacklist'] == ['ETH/BTC', 'XRP/.*'] - assert ret['blacklist_expanded'] == ['ETH/BTC', 'XRP/BTC', 'XRP/USDT'] - assert 'errors' in ret - assert isinstance(ret['errors'], dict) + assert "StaticPairList" in ret["method"] + assert len(ret["blacklist"]) == 2 + assert ret["blacklist"] == default_conf["exchange"]["pair_blacklist"] + assert ret["blacklist"] == ["ETH/BTC", "XRP/.*"] + assert ret["blacklist_expanded"] == ["ETH/BTC", "XRP/BTC", "XRP/USDT"] + assert "errors" in ret + assert isinstance(ret["errors"], dict) def test_rpc_edge_disabled(mocker, default_conf) -> None: - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc = RPC(freqtradebot) - with pytest.raises(RPCException, match=r'Edge is not enabled.'): + with pytest.raises(RPCException, match=r"Edge is not enabled."): rpc._rpc_edge() def test_rpc_edge_enabled(mocker, edge_conf) -> None: - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) - mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( - return_value={ - 'E/F': PairInfo(-0.02, 0.66, 3.71, 0.50, 1.71, 10, 60), - } - )) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) + mocker.patch( + "freqtrade.edge.Edge._cached_pairs", + mocker.PropertyMock( + return_value={ + "E/F": PairInfo(-0.02, 0.66, 3.71, 0.50, 1.71, 10, 60), + } + ), + ) freqtradebot = get_patched_freqtradebot(mocker, edge_conf) rpc = RPC(freqtradebot) ret = rpc._rpc_edge() assert len(ret) == 1 - assert ret[0]['Pair'] == 'E/F' - assert ret[0]['Winrate'] == 0.66 - assert ret[0]['Expectancy'] == 1.71 - assert ret[0]['Stoploss'] == -0.02 + assert ret[0]["Pair"] == "E/F" + assert ret[0]["Winrate"] == 0.66 + assert ret[0]["Expectancy"] == 1.71 + assert ret[0]["Stoploss"] == -0.02 def test_rpc_health(mocker, default_conf) -> None: - mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram", MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) set_startup_time() rpc = RPC(freqtradebot) result = rpc.health() - assert result['last_process'] is None - assert result['last_process_ts'] is None + assert result["last_process"] is None + assert result["last_process_ts"] is None diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index c5e7d9a89..ffb2408f1 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -1,6 +1,7 @@ """ Unit test file for rpc/api_server.py """ + import asyncio import logging import time @@ -52,19 +53,24 @@ _TEST_WS_TOKEN = "secret_Ws_t0ken" def botclient(default_conf, mocker): setup_logging_pre() setup_logging(default_conf) - default_conf['runmode'] = RunMode.DRY_RUN - default_conf.update({"api_server": {"enabled": True, - "listen_ip_address": "127.0.0.1", - "listen_port": 8080, - "CORS_origins": ['http://example.com'], - "username": _TEST_USER, - "password": _TEST_PASS, - "ws_token": _TEST_WS_TOKEN - }}) + default_conf["runmode"] = RunMode.DRY_RUN + default_conf.update( + { + "api_server": { + "enabled": True, + "listen_ip_address": "127.0.0.1", + "listen_port": 8080, + "CORS_origins": ["http://example.com"], + "username": _TEST_USER, + "password": _TEST_PASS, + "ws_token": _TEST_WS_TOKEN, + } + } + ) ftbot = get_patched_freqtradebot(mocker, default_conf) rpc = RPC(ftbot) - mocker.patch('freqtrade.rpc.api_server.ApiServer.start_api', MagicMock()) + mocker.patch("freqtrade.rpc.api_server.ApiServer.start_api", MagicMock()) apiserver = None try: apiserver = ApiServer(default_conf) @@ -81,47 +87,61 @@ def botclient(default_conf, mocker): def client_post(client: TestClient, url, data=None): - if data is None: data = {} - return client.post(url, - json=data, - headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS), - 'Origin': 'http://example.com', - 'content-type': 'application/json' - }) + return client.post( + url, + json=data, + headers={ + "Authorization": _basic_auth_str(_TEST_USER, _TEST_PASS), + "Origin": "http://example.com", + "content-type": "application/json", + }, + ) def client_patch(client: TestClient, url, data=None): - if data is None: data = {} - return client.patch(url, - json=data, - headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS), - 'Origin': 'http://example.com', - 'content-type': 'application/json' - }) + return client.patch( + url, + json=data, + headers={ + "Authorization": _basic_auth_str(_TEST_USER, _TEST_PASS), + "Origin": "http://example.com", + "content-type": "application/json", + }, + ) def client_get(client: TestClient, url): # Add fake Origin to ensure CORS kicks in - return client.get(url, headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS), - 'Origin': 'http://example.com'}) + return client.get( + url, + headers={ + "Authorization": _basic_auth_str(_TEST_USER, _TEST_PASS), + "Origin": "http://example.com", + }, + ) def client_delete(client: TestClient, url): # Add fake Origin to ensure CORS kicks in - return client.delete(url, headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS), - 'Origin': 'http://example.com'}) + return client.delete( + url, + headers={ + "Authorization": _basic_auth_str(_TEST_USER, _TEST_PASS), + "Origin": "http://example.com", + }, + ) def assert_response(response, expected_code=200, needs_cors=True): assert response.status_code == expected_code - assert response.headers.get('content-type') == "application/json" + assert response.headers.get("content-type") == "application/json" if needs_cors: - assert ('access-control-allow-credentials', 'true') in response.headers.items() - assert ('access-control-allow-origin', 'http://example.com') in response.headers.items() + assert ("access-control-allow-credentials", "true") in response.headers.items() + assert ("access-control-allow-origin", "http://example.com") in response.headers.items() def test_api_not_found(botclient): @@ -140,7 +160,7 @@ def test_api_ui_fallback(botclient, mocker): rc = client_get(client, "/fallback_file.html") assert rc.status_code == 200 - assert '`freqtrade install-ui`' in rc.text + assert "`freqtrade install-ui`" in rc.text # Forwarded to fallback_html or index.html (depending if it's installed or not) rc = client_get(client, "/something") @@ -150,45 +170,45 @@ def test_api_ui_fallback(botclient, mocker): assert rc.status_code == 200 # Test directory traversal without mock - rc = client_get(client, '%2F%2F%2Fetc/passwd') + rc = client_get(client, "%2F%2F%2Fetc/passwd") assert rc.status_code == 200 # Allow both fallback or real UI - assert '`freqtrade install-ui`' in rc.text or '' in rc.text + assert "`freqtrade install-ui`" in rc.text or "" in rc.text - mocker.patch.object(Path, 'is_file', MagicMock(side_effect=[True, False])) - rc = client_get(client, '%2F%2F%2Fetc/passwd') + mocker.patch.object(Path, "is_file", MagicMock(side_effect=[True, False])) + rc = client_get(client, "%2F%2F%2Fetc/passwd") assert rc.status_code == 200 - assert '`freqtrade install-ui`' in rc.text + assert "`freqtrade install-ui`" in rc.text def test_api_ui_version(botclient, mocker): _ftbot, client = botclient - mocker.patch('freqtrade.commands.deploy_commands.read_ui_version', return_value='0.1.2') + mocker.patch("freqtrade.commands.deploy_commands.read_ui_version", return_value="0.1.2") rc = client_get(client, "/ui_version") assert rc.status_code == 200 - assert rc.json()['version'] == '0.1.2' + assert rc.json()["version"] == "0.1.2" def test_api_auth(): with pytest.raises(ValueError): - create_token({'identity': {'u': 'Freqtrade'}}, 'secret1234', token_type="NotATokenType") + create_token({"identity": {"u": "Freqtrade"}}, "secret1234", token_type="NotATokenType") - token = create_token({'identity': {'u': 'Freqtrade'}}, 'secret1234') + token = create_token({"identity": {"u": "Freqtrade"}}, "secret1234") assert isinstance(token, str) - u = get_user_from_token(token, 'secret1234') - assert u == 'Freqtrade' + u = get_user_from_token(token, "secret1234") + assert u == "Freqtrade" with pytest.raises(HTTPException): - get_user_from_token(token, 'secret1234', token_type='refresh') + get_user_from_token(token, "secret1234", token_type="refresh") # Create invalid token - token = create_token({'identity': {'u1': 'Freqrade'}}, 'secret1234') + token = create_token({"identity": {"u1": "Freqrade"}}, "secret1234") with pytest.raises(HTTPException): - get_user_from_token(token, 'secret1234') + get_user_from_token(token, "secret1234") with pytest.raises(HTTPException): - get_user_from_token(b'not_a_token', 'secret1234') + get_user_from_token(b"not_a_token", "secret1234") def test_api_ws_auth(botclient): @@ -206,8 +226,8 @@ def test_api_ws_auth(botclient): with client.websocket_connect(url(good_token)) as websocket: pass - jwt_secret = ftbot.config['api_server'].get('jwt_secret_key', 'super-secret') - jwt_token = create_token({'identity': {'u': 'Freqtrade'}}, jwt_secret) + jwt_secret = ftbot.config["api_server"].get("jwt_secret_key", "super-secret") + jwt_token = create_token({"identity": {"u": "Freqtrade"}}, jwt_secret) with client.websocket_connect(url(jwt_token)) as websocket: pass @@ -216,50 +236,58 @@ def test_api_unauthorized(botclient): ftbot, client = botclient rc = client.get(f"{BASE_URI}/ping") assert_response(rc, needs_cors=False) - assert rc.json() == {'status': 'pong'} + assert rc.json() == {"status": "pong"} # Don't send user/pass information rc = client.get(f"{BASE_URI}/version") assert_response(rc, 401, needs_cors=False) - assert rc.json() == {'detail': 'Unauthorized'} + assert rc.json() == {"detail": "Unauthorized"} # Change only username - ftbot.config['api_server']['username'] = 'Ftrader' + ftbot.config["api_server"]["username"] = "Ftrader" rc = client_get(client, f"{BASE_URI}/version") assert_response(rc, 401) - assert rc.json() == {'detail': 'Unauthorized'} + assert rc.json() == {"detail": "Unauthorized"} # Change only password - ftbot.config['api_server']['username'] = _TEST_USER - ftbot.config['api_server']['password'] = 'WrongPassword' + ftbot.config["api_server"]["username"] = _TEST_USER + ftbot.config["api_server"]["password"] = "WrongPassword" rc = client_get(client, f"{BASE_URI}/version") assert_response(rc, 401) - assert rc.json() == {'detail': 'Unauthorized'} + assert rc.json() == {"detail": "Unauthorized"} - ftbot.config['api_server']['username'] = 'Ftrader' - ftbot.config['api_server']['password'] = 'WrongPassword' + ftbot.config["api_server"]["username"] = "Ftrader" + ftbot.config["api_server"]["password"] = "WrongPassword" rc = client_get(client, f"{BASE_URI}/version") assert_response(rc, 401) - assert rc.json() == {'detail': 'Unauthorized'} + assert rc.json() == {"detail": "Unauthorized"} def test_api_token_login(botclient): _ftbot, client = botclient - rc = client.post(f"{BASE_URI}/token/login", - data=None, - headers={'Authorization': _basic_auth_str('WRONG_USER', 'WRONG_PASS'), - 'Origin': 'http://example.com'}) + rc = client.post( + f"{BASE_URI}/token/login", + data=None, + headers={ + "Authorization": _basic_auth_str("WRONG_USER", "WRONG_PASS"), + "Origin": "http://example.com", + }, + ) assert_response(rc, 401) rc = client_post(client, f"{BASE_URI}/token/login") assert_response(rc) - assert 'access_token' in rc.json() - assert 'refresh_token' in rc.json() + assert "access_token" in rc.json() + assert "refresh_token" in rc.json() # test Authentication is working with JWT tokens too - rc = client.get(f"{BASE_URI}/count", - headers={'Authorization': f'Bearer {rc.json()["access_token"]}', - 'Origin': 'http://example.com'}) + rc = client.get( + f"{BASE_URI}/count", + headers={ + "Authorization": f'Bearer {rc.json()["access_token"]}', + "Origin": "http://example.com", + }, + ) assert_response(rc) @@ -267,13 +295,17 @@ def test_api_token_refresh(botclient): _ftbot, client = botclient rc = client_post(client, f"{BASE_URI}/token/login") assert_response(rc) - rc = client.post(f"{BASE_URI}/token/refresh", - data=None, - headers={'Authorization': f'Bearer {rc.json()["refresh_token"]}', - 'Origin': 'http://example.com'}) + rc = client.post( + f"{BASE_URI}/token/refresh", + data=None, + headers={ + "Authorization": f'Bearer {rc.json()["refresh_token"]}', + "Origin": "http://example.com", + }, + ) assert_response(rc) - assert 'access_token' in rc.json() - assert 'refresh_token' not in rc.json() + assert "access_token" in rc.json() + assert "refresh_token" not in rc.json() def test_api_stop_workflow(botclient): @@ -281,38 +313,43 @@ def test_api_stop_workflow(botclient): assert ftbot.state == State.RUNNING rc = client_post(client, f"{BASE_URI}/stop") assert_response(rc) - assert rc.json() == {'status': 'stopping trader ...'} + assert rc.json() == {"status": "stopping trader ..."} assert ftbot.state == State.STOPPED # Stop bot again rc = client_post(client, f"{BASE_URI}/stop") assert_response(rc) - assert rc.json() == {'status': 'already stopped'} + assert rc.json() == {"status": "already stopped"} # Start bot rc = client_post(client, f"{BASE_URI}/start") assert_response(rc) - assert rc.json() == {'status': 'starting trader ...'} + assert rc.json() == {"status": "starting trader ..."} assert ftbot.state == State.RUNNING # Call start again rc = client_post(client, f"{BASE_URI}/start") assert_response(rc) - assert rc.json() == {'status': 'already running'} + assert rc.json() == {"status": "already running"} def test_api__init__(default_conf, mocker): """ Test __init__() method """ - default_conf.update({"api_server": {"enabled": True, - "listen_ip_address": "127.0.0.1", - "listen_port": 8080, - "username": "TestUser", - "password": "testPass", - }}) - mocker.patch('freqtrade.rpc.telegram.Telegram._init') - mocker.patch('freqtrade.rpc.api_server.webserver.ApiServer.start_api', MagicMock()) + default_conf.update( + { + "api_server": { + "enabled": True, + "listen_ip_address": "127.0.0.1", + "listen_port": 8080, + "username": "TestUser", + "password": "testPass", + } + } + ) + mocker.patch("freqtrade.rpc.telegram.Telegram._init") + mocker.patch("freqtrade.rpc.api_server.webserver.ApiServer.start_api", MagicMock()) apiserver = ApiServer(default_conf) apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf))) assert apiserver._config == default_conf @@ -324,8 +361,8 @@ def test_api__init__(default_conf, mocker): def test_api_UvicornServer(mocker): - thread_mock = mocker.patch('freqtrade.rpc.api_server.uvicorn_threaded.threading.Thread') - s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1')) + thread_mock = mocker.patch("freqtrade.rpc.api_server.uvicorn_threaded.threading.Thread") + s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host="127.0.0.1")) assert thread_mock.call_count == 0 # Fake started to avoid sleeping forever @@ -338,9 +375,10 @@ def test_api_UvicornServer(mocker): def test_api_UvicornServer_run(mocker): - serve_mock = mocker.patch('freqtrade.rpc.api_server.uvicorn_threaded.UvicornServer.serve', - get_mock_coro(None)) - s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1')) + serve_mock = mocker.patch( + "freqtrade.rpc.api_server.uvicorn_threaded.UvicornServer.serve", get_mock_coro(None) + ) + s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host="127.0.0.1")) assert serve_mock.call_count == 0 # Fake started to avoid sleeping forever @@ -350,10 +388,11 @@ def test_api_UvicornServer_run(mocker): def test_api_UvicornServer_run_no_uvloop(mocker, import_fails): - serve_mock = mocker.patch('freqtrade.rpc.api_server.uvicorn_threaded.UvicornServer.serve', - get_mock_coro(None)) + serve_mock = mocker.patch( + "freqtrade.rpc.api_server.uvicorn_threaded.UvicornServer.serve", get_mock_coro(None) + ) asyncio.set_event_loop(asyncio.new_event_loop()) - s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1')) + s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host="127.0.0.1")) assert serve_mock.call_count == 0 # Fake started to avoid sleeping forever @@ -363,19 +402,24 @@ def test_api_UvicornServer_run_no_uvloop(mocker, import_fails): 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": "TestUser", - "password": "testPass", - }}) - mocker.patch('freqtrade.rpc.telegram.Telegram._init') + default_conf.update( + { + "api_server": { + "enabled": True, + "listen_ip_address": "127.0.0.1", + "listen_port": 8080, + "username": "TestUser", + "password": "testPass", + } + } + ) + mocker.patch("freqtrade.rpc.telegram.Telegram._init") server_inst_mock = MagicMock() server_inst_mock.run_in_thread = MagicMock() server_inst_mock.run = MagicMock() server_mock = MagicMock(return_value=server_inst_mock) - mocker.patch('freqtrade.rpc.api_server.webserver.UvicornServer', server_mock) + mocker.patch("freqtrade.rpc.api_server.webserver.UvicornServer", server_mock) apiserver = ApiServer(default_conf) apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf))) @@ -396,11 +440,16 @@ def test_api_run(default_conf, mocker, 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._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 @@ -411,12 +460,17 @@ def test_api_run(default_conf, mocker, caplog): 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("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) server_mock.reset_mock() @@ -432,8 +486,9 @@ def test_api_run(default_conf, mocker, caplog): # Test crashing API server caplog.clear() - mocker.patch('freqtrade.rpc.api_server.webserver.UvicornServer', - MagicMock(side_effect=Exception)) + mocker.patch( + "freqtrade.rpc.api_server.webserver.UvicornServer", MagicMock(side_effect=Exception) + ) apiserver.start_api() assert log_has("Api server failed to start.", caplog) apiserver.cleanup() @@ -441,17 +496,22 @@ def test_api_run(default_conf, mocker, caplog): def test_api_cleanup(default_conf, mocker, caplog): - default_conf.update({"api_server": {"enabled": True, - "listen_ip_address": "127.0.0.1", - "listen_port": 8080, - "username": "TestUser", - "password": "testPass", - }}) - mocker.patch('freqtrade.rpc.telegram.Telegram._init') + default_conf.update( + { + "api_server": { + "enabled": True, + "listen_ip_address": "127.0.0.1", + "listen_port": 8080, + "username": "TestUser", + "password": "testPass", + } + } + ) + mocker.patch("freqtrade.rpc.telegram.Telegram._init") server_mock = MagicMock() server_mock.cleanup = MagicMock() - mocker.patch('freqtrade.rpc.api_server.webserver.UvicornServer', server_mock) + mocker.patch("freqtrade.rpc.api_server.webserver.UvicornServer", server_mock) apiserver = ApiServer(default_conf) apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf))) @@ -467,35 +527,36 @@ def test_api_reloadconf(botclient): rc = client_post(client, f"{BASE_URI}/reload_config") assert_response(rc) - assert rc.json() == {'status': 'Reloading config ...'} + assert rc.json() == {"status": "Reloading config ..."} assert ftbot.state == State.RELOAD_CONFIG def test_api_stopentry(botclient): ftbot, client = botclient - assert ftbot.config['max_open_trades'] != 0 + assert ftbot.config["max_open_trades"] != 0 rc = client_post(client, f"{BASE_URI}/stopbuy") assert_response(rc) assert rc.json() == { - 'status': 'No more entries will occur from now. Run /reload_config to reset.'} - assert ftbot.config['max_open_trades'] == 0 + "status": "No more entries will occur from now. Run /reload_config to reset." + } + assert ftbot.config["max_open_trades"] == 0 rc = client_post(client, f"{BASE_URI}/stopentry") assert_response(rc) assert rc.json() == { - 'status': 'No more entries will occur from now. Run /reload_config to reset.'} - assert ftbot.config['max_open_trades'] == 0 + "status": "No more entries will occur from now. Run /reload_config to reset." + } + assert ftbot.config["max_open_trades"] == 0 def test_api_balance(botclient, mocker, rpc_balance, tickers): ftbot, client = botclient - ftbot.config['dry_run'] = False - mocker.patch(f'{EXMS}.get_balances', return_value=rpc_balance) - mocker.patch(f'{EXMS}.get_tickers', tickers) - mocker.patch(f'{EXMS}.get_valid_pair_combination', - side_effect=lambda a, b: f"{a}/{b}") + ftbot.config["dry_run"] = False + mocker.patch(f"{EXMS}.get_balances", return_value=rpc_balance) + mocker.patch(f"{EXMS}.get_tickers", tickers) + mocker.patch(f"{EXMS}.get_valid_pair_combination", side_effect=lambda a, b: f"{a}/{b}") ftbot.wallets.update() rc = client_get(client, f"{BASE_URI}/balance") @@ -503,30 +564,30 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers): response = rc.json() assert "currencies" in response assert len(response["currencies"]) == 5 - assert response['currencies'][0] == { - 'currency': 'BTC', - 'free': 12.0, - 'balance': 12.0, - 'used': 0.0, - 'bot_owned': pytest.approx(11.879999), - 'est_stake': 12.0, - 'est_stake_bot': pytest.approx(11.879999), - 'stake': 'BTC', - 'is_position': False, - 'leverage': 1.0, - 'position': 0.0, - 'side': 'long', - 'is_bot_managed': True, + assert response["currencies"][0] == { + "currency": "BTC", + "free": 12.0, + "balance": 12.0, + "used": 0.0, + "bot_owned": pytest.approx(11.879999), + "est_stake": 12.0, + "est_stake_bot": pytest.approx(11.879999), + "stake": "BTC", + "is_position": False, + "leverage": 1.0, + "position": 0.0, + "side": "long", + "is_bot_managed": True, } - assert response['total'] == 12.159513094 - assert response['total_bot'] == pytest.approx(11.879999) - assert 'starting_capital' in response - assert 'starting_capital_fiat' in response - assert 'starting_capital_pct' in response - assert 'starting_capital_ratio' in response + assert response["total"] == 12.159513094 + assert response["total_bot"] == pytest.approx(11.879999) + assert "starting_capital" in response + assert "starting_capital_fiat" in response + assert "starting_capital_pct" in response + assert "starting_capital_ratio" in response -@pytest.mark.parametrize('is_short', [True, False]) +@pytest.mark.parametrize("is_short", [True, False]) def test_api_count(botclient, mocker, ticker, fee, markets, is_short): ftbot, client = botclient patch_get_signal(ftbot) @@ -535,7 +596,7 @@ def test_api_count(botclient, mocker, ticker, fee, markets, is_short): get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, - markets=PropertyMock(return_value=markets) + markets=PropertyMock(return_value=markets), ) rc = client_get(client, f"{BASE_URI}/count") assert_response(rc) @@ -550,7 +611,7 @@ def test_api_count(botclient, mocker, ticker, fee, markets, is_short): assert rc.json()["current"] == 4 assert rc.json()["max"] == 1 - ftbot.config['max_open_trades'] = float('inf') + ftbot.config["max_open_trades"] = float("inf") rc = client_get(client, f"{BASE_URI}/count") assert rc.json()["max"] == -1 @@ -561,43 +622,47 @@ def test_api_locks(botclient): rc = client_get(client, f"{BASE_URI}/locks") assert_response(rc) - assert 'locks' in rc.json() + assert "locks" in rc.json() - assert rc.json()['lock_count'] == 0 - assert rc.json()['lock_count'] == len(rc.json()['locks']) + assert rc.json()["lock_count"] == 0 + assert rc.json()["lock_count"] == len(rc.json()["locks"]) - rc = client_post(client, f"{BASE_URI}/locks", [ - { - "pair": "ETH/BTC", - "until": f"{format_date(datetime.now(timezone.utc) + timedelta(minutes=4))}Z", - "reason": "randreason" - }, { - "pair": "XRP/BTC", - "until": f"{format_date(datetime.now(timezone.utc) + timedelta(minutes=20))}Z", - "reason": "deadbeef" - } - ]) + rc = client_post( + client, + f"{BASE_URI}/locks", + [ + { + "pair": "ETH/BTC", + "until": f"{format_date(datetime.now(timezone.utc) + timedelta(minutes=4))}Z", + "reason": "randreason", + }, + { + "pair": "XRP/BTC", + "until": f"{format_date(datetime.now(timezone.utc) + timedelta(minutes=20))}Z", + "reason": "deadbeef", + }, + ], + ) assert_response(rc) - assert rc.json()['lock_count'] == 2 + assert rc.json()["lock_count"] == 2 rc = client_get(client, f"{BASE_URI}/locks") assert_response(rc) - assert rc.json()['lock_count'] == 2 - assert rc.json()['lock_count'] == len(rc.json()['locks']) - assert 'ETH/BTC' in (rc.json()['locks'][0]['pair'], rc.json()['locks'][1]['pair']) - assert 'randreason' in (rc.json()['locks'][0]['reason'], rc.json()['locks'][1]['reason']) - assert 'deadbeef' in (rc.json()['locks'][0]['reason'], rc.json()['locks'][1]['reason']) + assert rc.json()["lock_count"] == 2 + assert rc.json()["lock_count"] == len(rc.json()["locks"]) + assert "ETH/BTC" in (rc.json()["locks"][0]["pair"], rc.json()["locks"][1]["pair"]) + assert "randreason" in (rc.json()["locks"][0]["reason"], rc.json()["locks"][1]["reason"]) + assert "deadbeef" in (rc.json()["locks"][0]["reason"], rc.json()["locks"][1]["reason"]) # Test deletions rc = client_delete(client, f"{BASE_URI}/locks/1") assert_response(rc) - assert rc.json()['lock_count'] == 1 + assert rc.json()["lock_count"] == 1 - rc = client_post(client, f"{BASE_URI}/locks/delete", - data={"pair": "XRP/BTC"}) + rc = client_post(client, f"{BASE_URI}/locks/delete", data={"pair": "XRP/BTC"}) assert_response(rc) - assert rc.json()['lock_count'] == 0 + assert rc.json()["lock_count"] == 0 def test_api_show_config(botclient): @@ -607,22 +672,22 @@ def test_api_show_config(botclient): rc = client_get(client, f"{BASE_URI}/show_config") assert_response(rc) response = rc.json() - assert 'dry_run' in response - assert response['exchange'] == 'binance' - assert response['timeframe'] == '5m' - assert response['timeframe_ms'] == 300000 - assert response['timeframe_min'] == 5 - assert response['state'] == 'running' - assert response['bot_name'] == 'freqtrade' - assert response['trading_mode'] == 'spot' - assert response['strategy_version'] is None - assert not response['trailing_stop'] - assert 'entry_pricing' in response - assert 'exit_pricing' in response - assert 'unfilledtimeout' in response - assert 'version' in response - assert 'api_version' in response - assert 2.1 <= response['api_version'] < 3.0 + assert "dry_run" in response + assert response["exchange"] == "binance" + assert response["timeframe"] == "5m" + assert response["timeframe_ms"] == 300000 + assert response["timeframe_min"] == 5 + assert response["state"] == "running" + assert response["bot_name"] == "freqtrade" + assert response["trading_mode"] == "spot" + assert response["strategy_version"] is None + assert not response["trailing_stop"] + assert "entry_pricing" in response + assert "exit_pricing" in response + assert "unfilledtimeout" in response + assert "version" in response + assert "api_version" in response + assert 2.1 <= response["api_version"] < 3.0 def test_api_daily(botclient, mocker, ticker, fee, markets): @@ -633,14 +698,14 @@ def test_api_daily(botclient, mocker, ticker, fee, markets): get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, - markets=PropertyMock(return_value=markets) + markets=PropertyMock(return_value=markets), ) rc = client_get(client, f"{BASE_URI}/daily") assert_response(rc) - assert len(rc.json()['data']) == 7 - assert rc.json()['stake_currency'] == 'BTC' - assert rc.json()['fiat_display_currency'] == 'USD' - assert rc.json()['data'][0]['date'] == str(datetime.now(timezone.utc).date()) + assert len(rc.json()["data"]) == 7 + assert rc.json()["stake_currency"] == "BTC" + assert rc.json()["fiat_display_currency"] == "USD" + assert rc.json()["data"][0]["date"] == str(datetime.now(timezone.utc).date()) def test_api_weekly(botclient, mocker, ticker, fee, markets, time_machine): @@ -651,17 +716,17 @@ def test_api_weekly(botclient, mocker, ticker, fee, markets, time_machine): get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, - markets=PropertyMock(return_value=markets) + markets=PropertyMock(return_value=markets), ) time_machine.move_to("2023-03-31 21:45:05 +00:00") rc = client_get(client, f"{BASE_URI}/weekly") assert_response(rc) - assert len(rc.json()['data']) == 4 - assert rc.json()['stake_currency'] == 'BTC' - assert rc.json()['fiat_display_currency'] == 'USD' + assert len(rc.json()["data"]) == 4 + assert rc.json()["stake_currency"] == "BTC" + assert rc.json()["fiat_display_currency"] == "USD" # Moved to monday - assert rc.json()['data'][0]['date'] == '2023-03-27' - assert rc.json()['data'][1]['date'] == '2023-03-20' + assert rc.json()["data"][0]["date"] == "2023-03-27" + assert rc.json()["data"][1]["date"] == "2023-03-20" def test_api_monthly(botclient, mocker, ticker, fee, markets, time_machine): @@ -672,50 +737,47 @@ def test_api_monthly(botclient, mocker, ticker, fee, markets, time_machine): get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, - markets=PropertyMock(return_value=markets) + markets=PropertyMock(return_value=markets), ) time_machine.move_to("2023-03-31 21:45:05 +00:00") rc = client_get(client, f"{BASE_URI}/monthly") assert_response(rc) - assert len(rc.json()['data']) == 3 - assert rc.json()['stake_currency'] == 'BTC' - assert rc.json()['fiat_display_currency'] == 'USD' - assert rc.json()['data'][0]['date'] == '2023-03-01' - assert rc.json()['data'][1]['date'] == '2023-02-01' + assert len(rc.json()["data"]) == 3 + assert rc.json()["stake_currency"] == "BTC" + assert rc.json()["fiat_display_currency"] == "USD" + assert rc.json()["data"][0]["date"] == "2023-03-01" + assert rc.json()["data"][1]["date"] == "2023-02-01" -@pytest.mark.parametrize('is_short', [True, False]) +@pytest.mark.parametrize("is_short", [True, False]) def test_api_trades(botclient, mocker, fee, markets, is_short): ftbot, client = botclient patch_get_signal(ftbot) - mocker.patch.multiple( - EXMS, - markets=PropertyMock(return_value=markets) - ) + mocker.patch.multiple(EXMS, markets=PropertyMock(return_value=markets)) rc = client_get(client, f"{BASE_URI}/trades") assert_response(rc) assert len(rc.json()) == 4 - assert rc.json()['trades_count'] == 0 - assert rc.json()['total_trades'] == 0 - assert rc.json()['offset'] == 0 + assert rc.json()["trades_count"] == 0 + assert rc.json()["total_trades"] == 0 + assert rc.json()["offset"] == 0 create_mock_trades(fee, is_short=is_short) Trade.session.flush() rc = client_get(client, f"{BASE_URI}/trades") assert_response(rc) - assert len(rc.json()['trades']) == 2 - assert rc.json()['trades_count'] == 2 - assert rc.json()['total_trades'] == 2 - assert rc.json()['trades'][0]['is_short'] == is_short + assert len(rc.json()["trades"]) == 2 + assert rc.json()["trades_count"] == 2 + assert rc.json()["total_trades"] == 2 + assert rc.json()["trades"][0]["is_short"] == is_short rc = client_get(client, f"{BASE_URI}/trades?limit=1") assert_response(rc) - assert len(rc.json()['trades']) == 1 - assert rc.json()['trades_count'] == 1 - assert rc.json()['total_trades'] == 2 + assert len(rc.json()["trades"]) == 1 + assert rc.json()["trades_count"] == 1 + assert rc.json()["total_trades"] == 2 -@pytest.mark.parametrize('is_short', [True, False]) +@pytest.mark.parametrize("is_short", [True, False]) def test_api_trade_single(botclient, mocker, fee, ticker, markets, is_short): ftbot, client = botclient patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short) @@ -726,18 +788,18 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets, is_short): ) rc = client_get(client, f"{BASE_URI}/trade/3") assert_response(rc, 404) - assert rc.json()['detail'] == 'Trade not found.' + assert rc.json()["detail"] == "Trade not found." Trade.rollback() create_mock_trades(fee, is_short=is_short) rc = client_get(client, f"{BASE_URI}/trade/3") assert_response(rc) - assert rc.json()['trade_id'] == 3 - assert rc.json()['is_short'] == is_short + assert rc.json()["trade_id"] == 3 + assert rc.json()["is_short"] == is_short -@pytest.mark.parametrize('is_short', [True, False]) +@pytest.mark.parametrize("is_short", [True, False]) def test_api_delete_trade(botclient, mocker, fee, markets, is_short): ftbot, client = botclient patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short) @@ -752,14 +814,14 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short): create_mock_trades(fee, is_short=is_short) - ftbot.strategy.order_types['stoploss_on_exchange'] = True + ftbot.strategy.order_types["stoploss_on_exchange"] = True trades = Trade.session.scalars(select(Trade)).all() Trade.commit() assert len(trades) > 2 rc = client_delete(client, f"{BASE_URI}/trades/1") assert_response(rc) - assert rc.json()['result_msg'] == 'Deleted trade 1. Closed 1 open orders.' + assert rc.json()["result_msg"] == "Deleted trade 1. Closed 1 open orders." assert len(trades) - 1 == len(Trade.session.scalars(select(Trade)).all()) assert cancel_mock.call_count == 1 @@ -772,7 +834,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short): assert len(trades) - 1 == len(Trade.session.scalars(select(Trade)).all()) rc = client_delete(client, f"{BASE_URI}/trades/5") assert_response(rc) - assert rc.json()['result_msg'] == 'Deleted trade 5. Closed 1 open orders.' + assert rc.json()["result_msg"] == "Deleted trade 5. Closed 1 open orders." assert len(trades) - 2 == len(Trade.session.scalars(select(Trade)).all()) assert stoploss_mock.call_count == 1 @@ -781,7 +843,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short): assert_response(rc, 502) -@pytest.mark.parametrize('is_short', [True, False]) +@pytest.mark.parametrize("is_short", [True, False]) def test_api_delete_open_order(botclient, mocker, fee, markets, ticker, is_short): ftbot, client = botclient patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short) @@ -797,29 +859,29 @@ def test_api_delete_open_order(botclient, mocker, fee, markets, ticker, is_short rc = client_delete(client, f"{BASE_URI}/trades/10/open-order") assert_response(rc, 502) - assert 'Invalid trade_id.' in rc.json()['error'] + assert "Invalid trade_id." in rc.json()["error"] create_mock_trades(fee, is_short=is_short) Trade.commit() rc = client_delete(client, f"{BASE_URI}/trades/5/open-order") assert_response(rc, 502) - assert 'No open order for trade_id' in rc.json()['error'] + assert "No open order for trade_id" in rc.json()["error"] trade = Trade.get_trades([Trade.id == 6]).first() - mocker.patch(f'{EXMS}.fetch_order', side_effect=ExchangeError) + mocker.patch(f"{EXMS}.fetch_order", side_effect=ExchangeError) rc = client_delete(client, f"{BASE_URI}/trades/6/open-order") assert_response(rc, 502) - assert 'Order not found.' in rc.json()['error'] + assert "Order not found." in rc.json()["error"] trade = Trade.get_trades([Trade.id == 6]).first() - mocker.patch(f'{EXMS}.fetch_order', return_value=trade.orders[-1].to_ccxt_object()) + mocker.patch(f"{EXMS}.fetch_order", return_value=trade.orders[-1].to_ccxt_object()) rc = client_delete(client, f"{BASE_URI}/trades/6/open-order") assert_response(rc) assert cancel_mock.call_count == 1 -@pytest.mark.parametrize('is_short', [True, False]) +@pytest.mark.parametrize("is_short", [True, False]) def test_api_trade_reload_trade(botclient, mocker, fee, markets, ticker, is_short): ftbot, client = botclient patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short) @@ -836,7 +898,7 @@ def test_api_trade_reload_trade(botclient, mocker, fee, markets, ticker, is_shor rc = client_post(client, f"{BASE_URI}/trades/10/reload") assert_response(rc, 502) - assert 'Could not find trade with id 10.' in rc.json()['error'] + assert "Could not find trade with id 10." in rc.json()["error"] assert ftbot.handle_onexchange_order.call_count == 0 create_mock_trades(fee, is_short=is_short) @@ -851,31 +913,31 @@ def test_api_logs(botclient): rc = client_get(client, f"{BASE_URI}/logs") assert_response(rc) assert len(rc.json()) == 2 - assert 'logs' in rc.json() + assert "logs" in rc.json() # Using a fixed comparison here would make this test fail! - assert rc.json()['log_count'] > 1 - assert len(rc.json()['logs']) == rc.json()['log_count'] + assert rc.json()["log_count"] > 1 + assert len(rc.json()["logs"]) == rc.json()["log_count"] - assert isinstance(rc.json()['logs'][0], list) + assert isinstance(rc.json()["logs"][0], list) # date - assert isinstance(rc.json()['logs'][0][0], str) + assert isinstance(rc.json()["logs"][0][0], str) # created_timestamp - assert isinstance(rc.json()['logs'][0][1], float) - assert isinstance(rc.json()['logs'][0][2], str) - assert isinstance(rc.json()['logs'][0][3], str) - assert isinstance(rc.json()['logs'][0][4], str) + assert isinstance(rc.json()["logs"][0][1], float) + assert isinstance(rc.json()["logs"][0][2], str) + assert isinstance(rc.json()["logs"][0][3], str) + assert isinstance(rc.json()["logs"][0][4], str) rc1 = client_get(client, f"{BASE_URI}/logs?limit=5") assert_response(rc1) assert len(rc1.json()) == 2 - assert 'logs' in rc1.json() + assert "logs" in rc1.json() # Using a fixed comparison here would make this test fail! - if rc1.json()['log_count'] < 5: + if rc1.json()["log_count"] < 5: # Help debugging random test failure print(f"rc={rc.json()}") print(f"rc1={rc1.json()}") - assert rc1.json()['log_count'] > 2 - assert len(rc1.json()['logs']) == rc1.json()['log_count'] + assert rc1.json()["log_count"] > 2 + assert len(rc1.json()["logs"]) == rc1.json()["log_count"] def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): @@ -886,64 +948,111 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets): get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, - markets=PropertyMock(return_value=markets) + markets=PropertyMock(return_value=markets), ) rc = client_get(client, f"{BASE_URI}/edge") assert_response(rc, 502) assert rc.json() == {"error": "Error querying /api/v1/edge: Edge is not enabled."} -@pytest.mark.parametrize('is_short,expected', [ - ( - True, - {'best_pair': 'ETC/BTC', 'best_rate': -0.5, 'best_pair_profit_ratio': -0.005, - 'profit_all_coin': 15.382312, - 'profit_all_fiat': 189894.6470718, 'profit_all_percent_mean': 49.62, - 'profit_all_ratio_mean': 0.49620917, 'profit_all_percent_sum': 198.48, - 'profit_all_ratio_sum': 1.98483671, 'profit_all_percent': 1.54, - 'profit_all_ratio': 0.01538214, 'profit_closed_coin': -0.00673913, - 'profit_closed_fiat': -83.19455985, 'profit_closed_ratio_mean': -0.0075, - 'profit_closed_percent_mean': -0.75, 'profit_closed_ratio_sum': -0.015, - 'profit_closed_percent_sum': -1.5, 'profit_closed_ratio': -6.739057628404269e-06, - 'profit_closed_percent': -0.0, 'winning_trades': 0, 'losing_trades': 2, - 'profit_factor': 0.0, 'winrate': 0.0, 'expectancy': -0.0033695635, - 'expectancy_ratio': -1.0, 'trading_volume': 75.945, - } - ), - ( - False, - {'best_pair': 'XRP/BTC', 'best_rate': 1.0, 'best_pair_profit_ratio': 0.01, - 'profit_all_coin': -15.46546305, - 'profit_all_fiat': -190921.14135225, 'profit_all_percent_mean': -49.62, - 'profit_all_ratio_mean': -0.49620955, 'profit_all_percent_sum': -198.48, - 'profit_all_ratio_sum': -1.9848382, 'profit_all_percent': -1.55, - 'profit_all_ratio': -0.0154654126, 'profit_closed_coin': 0.00073913, - 'profit_closed_fiat': 9.124559849999999, 'profit_closed_ratio_mean': 0.0075, - 'profit_closed_percent_mean': 0.75, 'profit_closed_ratio_sum': 0.015, - 'profit_closed_percent_sum': 1.5, 'profit_closed_ratio': 7.391275897987988e-07, - 'profit_closed_percent': 0.0, 'winning_trades': 2, 'losing_trades': 0, - 'profit_factor': None, 'winrate': 1.0, 'expectancy': 0.0003695635, - 'expectancy_ratio': 100, 'trading_volume': 75.945, - } - ), - ( - None, - {'best_pair': 'XRP/BTC', 'best_rate': 1.0, 'best_pair_profit_ratio': 0.01, - 'profit_all_coin': -14.87167525, - 'profit_all_fiat': -183590.83096125, 'profit_all_percent_mean': 0.13, - 'profit_all_ratio_mean': 0.0012538324, 'profit_all_percent_sum': 0.5, - 'profit_all_ratio_sum': 0.005015329, 'profit_all_percent': -1.49, - 'profit_all_ratio': -0.0148715350, 'profit_closed_coin': -0.00542913, - 'profit_closed_fiat': -67.02260985, 'profit_closed_ratio_mean': 0.0025, - 'profit_closed_percent_mean': 0.25, 'profit_closed_ratio_sum': 0.005, - 'profit_closed_percent_sum': 0.5, 'profit_closed_ratio': -5.429078808526421e-06, - 'profit_closed_percent': -0.0, 'winning_trades': 1, 'losing_trades': 1, - 'profit_factor': 0.02775724835771106, 'winrate': 0.5, - 'expectancy': -0.0027145635000000003, 'expectancy_ratio': -0.48612137582114445, - 'trading_volume': 75.945, - } - ) -]) +@pytest.mark.parametrize( + "is_short,expected", + [ + ( + True, + { + "best_pair": "ETC/BTC", + "best_rate": -0.5, + "best_pair_profit_ratio": -0.005, + "profit_all_coin": 15.382312, + "profit_all_fiat": 189894.6470718, + "profit_all_percent_mean": 49.62, + "profit_all_ratio_mean": 0.49620917, + "profit_all_percent_sum": 198.48, + "profit_all_ratio_sum": 1.98483671, + "profit_all_percent": 1.54, + "profit_all_ratio": 0.01538214, + "profit_closed_coin": -0.00673913, + "profit_closed_fiat": -83.19455985, + "profit_closed_ratio_mean": -0.0075, + "profit_closed_percent_mean": -0.75, + "profit_closed_ratio_sum": -0.015, + "profit_closed_percent_sum": -1.5, + "profit_closed_ratio": -6.739057628404269e-06, + "profit_closed_percent": -0.0, + "winning_trades": 0, + "losing_trades": 2, + "profit_factor": 0.0, + "winrate": 0.0, + "expectancy": -0.0033695635, + "expectancy_ratio": -1.0, + "trading_volume": 75.945, + }, + ), + ( + False, + { + "best_pair": "XRP/BTC", + "best_rate": 1.0, + "best_pair_profit_ratio": 0.01, + "profit_all_coin": -15.46546305, + "profit_all_fiat": -190921.14135225, + "profit_all_percent_mean": -49.62, + "profit_all_ratio_mean": -0.49620955, + "profit_all_percent_sum": -198.48, + "profit_all_ratio_sum": -1.9848382, + "profit_all_percent": -1.55, + "profit_all_ratio": -0.0154654126, + "profit_closed_coin": 0.00073913, + "profit_closed_fiat": 9.124559849999999, + "profit_closed_ratio_mean": 0.0075, + "profit_closed_percent_mean": 0.75, + "profit_closed_ratio_sum": 0.015, + "profit_closed_percent_sum": 1.5, + "profit_closed_ratio": 7.391275897987988e-07, + "profit_closed_percent": 0.0, + "winning_trades": 2, + "losing_trades": 0, + "profit_factor": None, + "winrate": 1.0, + "expectancy": 0.0003695635, + "expectancy_ratio": 100, + "trading_volume": 75.945, + }, + ), + ( + None, + { + "best_pair": "XRP/BTC", + "best_rate": 1.0, + "best_pair_profit_ratio": 0.01, + "profit_all_coin": -14.87167525, + "profit_all_fiat": -183590.83096125, + "profit_all_percent_mean": 0.13, + "profit_all_ratio_mean": 0.0012538324, + "profit_all_percent_sum": 0.5, + "profit_all_ratio_sum": 0.005015329, + "profit_all_percent": -1.49, + "profit_all_ratio": -0.0148715350, + "profit_closed_coin": -0.00542913, + "profit_closed_fiat": -67.02260985, + "profit_closed_ratio_mean": 0.0025, + "profit_closed_percent_mean": 0.25, + "profit_closed_ratio_sum": 0.005, + "profit_closed_percent_sum": 0.5, + "profit_closed_ratio": -5.429078808526421e-06, + "profit_closed_percent": -0.0, + "winning_trades": 1, + "losing_trades": 1, + "profit_factor": 0.02775724835771106, + "winrate": 0.5, + "expectancy": -0.0027145635000000003, + "expectancy_ratio": -0.48612137582114445, + "trading_volume": 75.945, + }, + ), + ], +) def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected): ftbot, client = botclient patch_get_signal(ftbot) @@ -952,12 +1061,12 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected) get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, - markets=PropertyMock(return_value=markets) + markets=PropertyMock(return_value=markets), ) rc = client_get(client, f"{BASE_URI}/profit") assert_response(rc, 200) - assert rc.json()['trade_count'] == 0 + assert rc.json()["trade_count"] == 0 create_mock_trades(fee, is_short=is_short) # Simulate fulfilled LIMIT_BUY order for trade @@ -966,53 +1075,53 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected) assert_response(rc) # raise ValueError(rc.json()) assert rc.json() == { - 'avg_duration': ANY, - 'best_pair': expected['best_pair'], - 'best_pair_profit_ratio': expected['best_pair_profit_ratio'], - 'best_rate': expected['best_rate'], - 'first_trade_date': ANY, - 'first_trade_humanized': ANY, - 'first_trade_timestamp': ANY, - 'latest_trade_date': ANY, - 'latest_trade_humanized': '5 minutes ago', - 'latest_trade_timestamp': ANY, - 'profit_all_coin': pytest.approx(expected['profit_all_coin']), - 'profit_all_fiat': pytest.approx(expected['profit_all_fiat']), - 'profit_all_percent_mean': pytest.approx(expected['profit_all_percent_mean']), - 'profit_all_ratio_mean': pytest.approx(expected['profit_all_ratio_mean']), - 'profit_all_percent_sum': pytest.approx(expected['profit_all_percent_sum']), - 'profit_all_ratio_sum': pytest.approx(expected['profit_all_ratio_sum']), - 'profit_all_percent': pytest.approx(expected['profit_all_percent']), - 'profit_all_ratio': pytest.approx(expected['profit_all_ratio']), - 'profit_closed_coin': pytest.approx(expected['profit_closed_coin']), - 'profit_closed_fiat': pytest.approx(expected['profit_closed_fiat']), - 'profit_closed_ratio_mean': pytest.approx(expected['profit_closed_ratio_mean']), - 'profit_closed_percent_mean': pytest.approx(expected['profit_closed_percent_mean']), - 'profit_closed_ratio_sum': pytest.approx(expected['profit_closed_ratio_sum']), - 'profit_closed_percent_sum': pytest.approx(expected['profit_closed_percent_sum']), - 'profit_closed_ratio': pytest.approx(expected['profit_closed_ratio']), - 'profit_closed_percent': pytest.approx(expected['profit_closed_percent']), - 'trade_count': 6, - 'closed_trade_count': 2, - 'winning_trades': expected['winning_trades'], - 'losing_trades': expected['losing_trades'], - 'profit_factor': expected['profit_factor'], - 'winrate': expected['winrate'], - 'expectancy': expected['expectancy'], - 'expectancy_ratio': expected['expectancy_ratio'], - 'max_drawdown': ANY, - 'max_drawdown_abs': ANY, - 'max_drawdown_start': ANY, - 'max_drawdown_start_timestamp': ANY, - 'max_drawdown_end': ANY, - 'max_drawdown_end_timestamp': ANY, - 'trading_volume': expected['trading_volume'], - 'bot_start_timestamp': 0, - 'bot_start_date': '', + "avg_duration": ANY, + "best_pair": expected["best_pair"], + "best_pair_profit_ratio": expected["best_pair_profit_ratio"], + "best_rate": expected["best_rate"], + "first_trade_date": ANY, + "first_trade_humanized": ANY, + "first_trade_timestamp": ANY, + "latest_trade_date": ANY, + "latest_trade_humanized": "5 minutes ago", + "latest_trade_timestamp": ANY, + "profit_all_coin": pytest.approx(expected["profit_all_coin"]), + "profit_all_fiat": pytest.approx(expected["profit_all_fiat"]), + "profit_all_percent_mean": pytest.approx(expected["profit_all_percent_mean"]), + "profit_all_ratio_mean": pytest.approx(expected["profit_all_ratio_mean"]), + "profit_all_percent_sum": pytest.approx(expected["profit_all_percent_sum"]), + "profit_all_ratio_sum": pytest.approx(expected["profit_all_ratio_sum"]), + "profit_all_percent": pytest.approx(expected["profit_all_percent"]), + "profit_all_ratio": pytest.approx(expected["profit_all_ratio"]), + "profit_closed_coin": pytest.approx(expected["profit_closed_coin"]), + "profit_closed_fiat": pytest.approx(expected["profit_closed_fiat"]), + "profit_closed_ratio_mean": pytest.approx(expected["profit_closed_ratio_mean"]), + "profit_closed_percent_mean": pytest.approx(expected["profit_closed_percent_mean"]), + "profit_closed_ratio_sum": pytest.approx(expected["profit_closed_ratio_sum"]), + "profit_closed_percent_sum": pytest.approx(expected["profit_closed_percent_sum"]), + "profit_closed_ratio": pytest.approx(expected["profit_closed_ratio"]), + "profit_closed_percent": pytest.approx(expected["profit_closed_percent"]), + "trade_count": 6, + "closed_trade_count": 2, + "winning_trades": expected["winning_trades"], + "losing_trades": expected["losing_trades"], + "profit_factor": expected["profit_factor"], + "winrate": expected["winrate"], + "expectancy": expected["expectancy"], + "expectancy_ratio": expected["expectancy_ratio"], + "max_drawdown": ANY, + "max_drawdown_abs": ANY, + "max_drawdown_start": ANY, + "max_drawdown_start_timestamp": ANY, + "max_drawdown_end": ANY, + "max_drawdown_end_timestamp": ANY, + "trading_volume": expected["trading_volume"], + "bot_start_timestamp": 0, + "bot_start_date": "", } -@pytest.mark.parametrize('is_short', [True, False]) +@pytest.mark.parametrize("is_short", [True, False]) def test_api_stats(botclient, mocker, ticker, fee, markets, is_short): ftbot, client = botclient patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short) @@ -1021,24 +1130,24 @@ def test_api_stats(botclient, mocker, ticker, fee, markets, is_short): get_balances=MagicMock(return_value=ticker), fetch_ticker=ticker, get_fee=fee, - markets=PropertyMock(return_value=markets) + markets=PropertyMock(return_value=markets), ) rc = client_get(client, f"{BASE_URI}/stats") assert_response(rc, 200) - assert 'durations' in rc.json() - assert 'exit_reasons' in rc.json() + assert "durations" in rc.json() + assert "exit_reasons" in rc.json() create_mock_trades(fee, is_short=is_short) rc = client_get(client, f"{BASE_URI}/stats") assert_response(rc, 200) - assert 'durations' in rc.json() - assert 'exit_reasons' in rc.json() + assert "durations" in rc.json() + assert "exit_reasons" in rc.json() - assert 'wins' in rc.json()['durations'] - assert 'losses' in rc.json()['durations'] - assert 'draws' in rc.json()['durations'] + assert "wins" in rc.json()["durations"] + assert "losses" in rc.json()["durations"] + assert "draws" in rc.json()["durations"] def test_api_performance(botclient, fee): @@ -1046,9 +1155,9 @@ def test_api_performance(botclient, fee): patch_get_signal(ftbot) trade = Trade( - pair='LTC/ETH', + pair="LTC/ETH", amount=1, - exchange='binance', + exchange="binance", stake_amount=1, open_rate=0.245441, is_open=False, @@ -1062,10 +1171,10 @@ def test_api_performance(botclient, fee): Trade.session.add(trade) trade = Trade( - pair='XRP/ETH', + pair="XRP/ETH", amount=5, stake_amount=1, - exchange='binance', + exchange="binance", open_rate=0.412, is_open=False, fee_close=fee.return_value, @@ -1082,10 +1191,24 @@ def test_api_performance(botclient, fee): rc = client_get(client, f"{BASE_URI}/performance") assert_response(rc) assert len(rc.json()) == 2 - assert rc.json() == [{'count': 1, 'pair': 'LTC/ETH', 'profit': 7.61, 'profit_pct': 7.61, - 'profit_ratio': 0.07609203, 'profit_abs': 0.0187228}, - {'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57, 'profit_pct': -5.57, - 'profit_ratio': -0.05570419, 'profit_abs': -0.1150375}] + assert rc.json() == [ + { + "count": 1, + "pair": "LTC/ETH", + "profit": 7.61, + "profit_pct": 7.61, + "profit_ratio": 0.07609203, + "profit_abs": 0.0187228, + }, + { + "count": 1, + "pair": "XRP/ETH", + "profit": -5.57, + "profit_pct": -5.57, + "profit_ratio": -0.05570419, + "profit_abs": -0.1150375, + }, + ] def test_api_entries(botclient, fee): @@ -1102,9 +1225,9 @@ def test_api_entries(botclient, fee): response = rc.json() assert len(response) == 2 resp = response[0] - assert resp['enter_tag'] == 'TEST1' - assert resp['count'] == 1 - assert resp['profit_pct'] == 0.5 + assert resp["enter_tag"] == "TEST1" + assert resp["count"] == 1 + assert resp["profit_pct"] == 0.5 def test_api_exits(botclient, fee): @@ -1121,9 +1244,9 @@ def test_api_exits(botclient, fee): response = rc.json() assert len(response) == 2 resp = response[0] - assert resp['exit_reason'] == 'sell_signal' - assert resp['count'] == 1 - assert resp['profit_pct'] == 0.5 + assert resp["exit_reason"] == "sell_signal" + assert resp["count"] == 1 + assert resp["profit_pct"] == 0.5 def test_api_mix_tag(botclient, fee): @@ -1140,17 +1263,18 @@ def test_api_mix_tag(botclient, fee): response = rc.json() assert len(response) == 2 resp = response[0] - assert resp['mix_tag'] == 'TEST1 sell_signal' - assert resp['count'] == 1 - assert resp['profit_pct'] == 0.5 + assert resp["mix_tag"] == "TEST1 sell_signal" + assert resp["count"] == 1 + assert resp["profit_pct"] == 0.5 @pytest.mark.parametrize( - 'is_short,current_rate,open_trade_value', - [(True, 1.098e-05, 15.0911775), - (False, 1.099e-05, 15.1668225)]) -def test_api_status(botclient, mocker, ticker, fee, markets, is_short, - current_rate, open_trade_value): + "is_short,current_rate,open_trade_value", + [(True, 1.098e-05, 15.0911775), (False, 1.099e-05, 15.1668225)], +) +def test_api_status( + botclient, mocker, ticker, fee, markets, is_short, current_rate, open_trade_value +): ftbot, client = botclient patch_get_signal(ftbot) mocker.patch.multiple( @@ -1171,87 +1295,88 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short, assert_response(rc) assert len(rc.json()) == 4 assert rc.json()[0] == { - 'amount': 123.0, - 'amount_requested': 123.0, - 'close_date': None, - 'close_timestamp': None, - 'close_profit': None, - 'close_profit_pct': None, - 'close_profit_abs': None, - 'close_rate': None, - 'profit_ratio': ANY, - 'profit_pct': ANY, - 'profit_abs': ANY, - 'profit_fiat': ANY, - 'total_profit_abs': ANY, - 'total_profit_fiat': ANY, - 'total_profit_ratio': ANY, - 'realized_profit': 0.0, - 'realized_profit_ratio': None, - 'current_rate': current_rate, - 'open_date': ANY, - 'open_timestamp': ANY, - 'open_fill_date': ANY, - 'open_fill_timestamp': ANY, - 'open_rate': 0.123, - 'pair': 'ETH/BTC', - 'base_currency': 'ETH', - 'quote_currency': 'BTC', - 'stake_amount': 0.001, - 'max_stake_amount': ANY, - 'stop_loss_abs': ANY, - 'stop_loss_pct': ANY, - 'stop_loss_ratio': ANY, - 'stoploss_last_update': ANY, - 'stoploss_last_update_timestamp': ANY, - 'initial_stop_loss_abs': 0.0, - 'initial_stop_loss_pct': ANY, - 'initial_stop_loss_ratio': ANY, - 'stoploss_current_dist': ANY, - 'stoploss_current_dist_ratio': ANY, - 'stoploss_current_dist_pct': ANY, - 'stoploss_entry_dist': ANY, - 'stoploss_entry_dist_ratio': ANY, - 'trade_id': 1, - 'close_rate_requested': ANY, - 'fee_close': 0.0025, - 'fee_close_cost': None, - 'fee_close_currency': None, - 'fee_open': 0.0025, - 'fee_open_cost': None, - 'fee_open_currency': None, - 'is_open': True, + "amount": 123.0, + "amount_requested": 123.0, + "close_date": None, + "close_timestamp": None, + "close_profit": None, + "close_profit_pct": None, + "close_profit_abs": None, + "close_rate": None, + "profit_ratio": ANY, + "profit_pct": ANY, + "profit_abs": ANY, + "profit_fiat": ANY, + "total_profit_abs": ANY, + "total_profit_fiat": ANY, + "total_profit_ratio": ANY, + "realized_profit": 0.0, + "realized_profit_ratio": None, + "current_rate": current_rate, + "open_date": ANY, + "open_timestamp": ANY, + "open_fill_date": ANY, + "open_fill_timestamp": ANY, + "open_rate": 0.123, + "pair": "ETH/BTC", + "base_currency": "ETH", + "quote_currency": "BTC", + "stake_amount": 0.001, + "max_stake_amount": ANY, + "stop_loss_abs": ANY, + "stop_loss_pct": ANY, + "stop_loss_ratio": ANY, + "stoploss_last_update": ANY, + "stoploss_last_update_timestamp": ANY, + "initial_stop_loss_abs": 0.0, + "initial_stop_loss_pct": ANY, + "initial_stop_loss_ratio": ANY, + "stoploss_current_dist": ANY, + "stoploss_current_dist_ratio": ANY, + "stoploss_current_dist_pct": ANY, + "stoploss_entry_dist": ANY, + "stoploss_entry_dist_ratio": ANY, + "trade_id": 1, + "close_rate_requested": ANY, + "fee_close": 0.0025, + "fee_close_cost": None, + "fee_close_currency": None, + "fee_open": 0.0025, + "fee_open_cost": None, + "fee_open_currency": None, + "is_open": True, "is_short": is_short, - 'max_rate': ANY, - 'min_rate': ANY, - 'open_rate_requested': ANY, - 'open_trade_value': open_trade_value, - 'exit_reason': None, - 'exit_order_status': None, - 'strategy': CURRENT_TEST_STRATEGY, - 'enter_tag': None, - 'timeframe': 5, - 'exchange': 'binance', - 'leverage': 1.0, - 'interest_rate': 0.0, - 'liquidation_price': None, - 'funding_fees': None, - 'trading_mode': ANY, - 'amount_precision': None, - 'price_precision': None, - 'precision_mode': None, - 'orders': [ANY], - 'has_open_orders': True, + "max_rate": ANY, + "min_rate": ANY, + "open_rate_requested": ANY, + "open_trade_value": open_trade_value, + "exit_reason": None, + "exit_order_status": None, + "strategy": CURRENT_TEST_STRATEGY, + "enter_tag": None, + "timeframe": 5, + "exchange": "binance", + "leverage": 1.0, + "interest_rate": 0.0, + "liquidation_price": None, + "funding_fees": None, + "trading_mode": ANY, + "amount_precision": None, + "price_precision": None, + "precision_mode": None, + "orders": [ANY], + "has_open_orders": True, } - mocker.patch(f'{EXMS}.get_rate', - MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available"))) + mocker.patch( + f"{EXMS}.get_rate", MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")) + ) rc = client_get(client, f"{BASE_URI}/status") assert_response(rc) resp_values = rc.json() assert len(resp_values) == 4 - assert resp_values[0]['profit_abs'] == 0.0 + assert resp_values[0]["profit_abs"] == 0.0 def test_api_version(botclient): @@ -1268,65 +1393,67 @@ def test_api_blacklist(botclient, mocker): rc = client_get(client, f"{BASE_URI}/blacklist") assert_response(rc) # DOGE and HOT are not in the markets mock! - assert rc.json() == {"blacklist": ["DOGE/BTC", "HOT/BTC"], - "blacklist_expanded": [], - "length": 2, - "method": ["StaticPairList"], - "errors": {}, - } + assert rc.json() == { + "blacklist": ["DOGE/BTC", "HOT/BTC"], + "blacklist_expanded": [], + "length": 2, + "method": ["StaticPairList"], + "errors": {}, + } # Add ETH/BTC to blacklist - rc = client_post(client, f"{BASE_URI}/blacklist", - data={"blacklist": ["ETH/BTC"]}) + rc = client_post(client, f"{BASE_URI}/blacklist", data={"blacklist": ["ETH/BTC"]}) assert_response(rc) - assert rc.json() == {"blacklist": ["DOGE/BTC", "HOT/BTC", "ETH/BTC"], - "blacklist_expanded": ["ETH/BTC"], - "length": 3, - "method": ["StaticPairList"], - "errors": {}, - } + assert rc.json() == { + "blacklist": ["DOGE/BTC", "HOT/BTC", "ETH/BTC"], + "blacklist_expanded": ["ETH/BTC"], + "length": 3, + "method": ["StaticPairList"], + "errors": {}, + } - rc = client_post(client, f"{BASE_URI}/blacklist", - data={"blacklist": ["XRP/.*"]}) + rc = client_post(client, f"{BASE_URI}/blacklist", data={"blacklist": ["XRP/.*"]}) assert_response(rc) - assert rc.json() == {"blacklist": ["DOGE/BTC", "HOT/BTC", "ETH/BTC", "XRP/.*"], - "blacklist_expanded": ["ETH/BTC", "XRP/BTC", "XRP/USDT"], - "length": 4, - "method": ["StaticPairList"], - "errors": {}, - } + assert rc.json() == { + "blacklist": ["DOGE/BTC", "HOT/BTC", "ETH/BTC", "XRP/.*"], + "blacklist_expanded": ["ETH/BTC", "XRP/BTC", "XRP/USDT"], + "length": 4, + "method": ["StaticPairList"], + "errors": {}, + } rc = client_delete(client, f"{BASE_URI}/blacklist?pairs_to_delete=DOGE/BTC") assert_response(rc) - assert rc.json() == {"blacklist": ["HOT/BTC", "ETH/BTC", "XRP/.*"], - "blacklist_expanded": ["ETH/BTC", "XRP/BTC", "XRP/USDT"], - "length": 3, - "method": ["StaticPairList"], - "errors": {}, - } + assert rc.json() == { + "blacklist": ["HOT/BTC", "ETH/BTC", "XRP/.*"], + "blacklist_expanded": ["ETH/BTC", "XRP/BTC", "XRP/USDT"], + "length": 3, + "method": ["StaticPairList"], + "errors": {}, + } rc = client_delete(client, f"{BASE_URI}/blacklist?pairs_to_delete=NOTHING/BTC") assert_response(rc) - assert rc.json() == {"blacklist": ["HOT/BTC", "ETH/BTC", "XRP/.*"], - "blacklist_expanded": ["ETH/BTC", "XRP/BTC", "XRP/USDT"], - "length": 3, - "method": ["StaticPairList"], - "errors": { - "NOTHING/BTC": { - "error_msg": "Pair NOTHING/BTC is not in the current blacklist." - } - }, + assert rc.json() == { + "blacklist": ["HOT/BTC", "ETH/BTC", "XRP/.*"], + "blacklist_expanded": ["ETH/BTC", "XRP/BTC", "XRP/USDT"], + "length": 3, + "method": ["StaticPairList"], + "errors": { + "NOTHING/BTC": {"error_msg": "Pair NOTHING/BTC is not in the current blacklist."} + }, } rc = client_delete( - client, - f"{BASE_URI}/blacklist?pairs_to_delete=HOT/BTC&pairs_to_delete=ETH/BTC") + client, f"{BASE_URI}/blacklist?pairs_to_delete=HOT/BTC&pairs_to_delete=ETH/BTC" + ) assert_response(rc) - assert rc.json() == {"blacklist": ["XRP/.*"], - "blacklist_expanded": ["XRP/BTC", "XRP/USDT"], - "length": 1, - "method": ["StaticPairList"], - "errors": {}, - } + assert rc.json() == { + "blacklist": ["XRP/.*"], + "blacklist_expanded": ["XRP/BTC", "XRP/USDT"], + "length": 1, + "method": ["StaticPairList"], + "errors": {}, + } def test_api_whitelist(botclient): @@ -1335,121 +1462,123 @@ def test_api_whitelist(botclient): rc = client_get(client, f"{BASE_URI}/whitelist") assert_response(rc) assert rc.json() == { - "whitelist": ['ETH/BTC', 'LTC/BTC', 'XRP/BTC', 'NEO/BTC'], + "whitelist": ["ETH/BTC", "LTC/BTC", "XRP/BTC", "NEO/BTC"], "length": 4, - "method": ["StaticPairList"] + "method": ["StaticPairList"], } -@pytest.mark.parametrize('endpoint', [ - 'forcebuy', - 'forceenter', -]) +@pytest.mark.parametrize( + "endpoint", + [ + "forcebuy", + "forceenter", + ], +) def test_api_force_entry(botclient, mocker, fee, endpoint): ftbot, client = botclient - rc = client_post(client, f"{BASE_URI}/{endpoint}", - data={"pair": "ETH/BTC"}) + rc = client_post(client, f"{BASE_URI}/{endpoint}", data={"pair": "ETH/BTC"}) assert_response(rc, 502) assert rc.json() == {"error": f"Error querying /api/v1/{endpoint}: Force_entry not enabled."} # enable forcebuy - ftbot.config['force_entry_enable'] = True + ftbot.config["force_entry_enable"] = True fbuy_mock = MagicMock(return_value=None) mocker.patch("freqtrade.rpc.rpc.RPC._rpc_force_entry", fbuy_mock) - rc = client_post(client, f"{BASE_URI}/{endpoint}", - data={"pair": "ETH/BTC"}) + rc = client_post(client, f"{BASE_URI}/{endpoint}", data={"pair": "ETH/BTC"}) assert_response(rc) assert rc.json() == {"status": "Error entering long trade for pair ETH/BTC."} # Test creating trade - fbuy_mock = MagicMock(return_value=Trade( - pair='ETH/BTC', - amount=1, - amount_requested=1, - exchange='binance', - stake_amount=1, - open_rate=0.245441, - open_date=datetime.now(timezone.utc), - is_open=False, - is_short=False, - fee_close=fee.return_value, - fee_open=fee.return_value, - close_rate=0.265441, - id=22, - timeframe=5, - strategy=CURRENT_TEST_STRATEGY, - trading_mode=TradingMode.SPOT - )) + fbuy_mock = MagicMock( + return_value=Trade( + pair="ETH/BTC", + amount=1, + amount_requested=1, + exchange="binance", + stake_amount=1, + open_rate=0.245441, + open_date=datetime.now(timezone.utc), + is_open=False, + is_short=False, + fee_close=fee.return_value, + fee_open=fee.return_value, + close_rate=0.265441, + id=22, + timeframe=5, + strategy=CURRENT_TEST_STRATEGY, + trading_mode=TradingMode.SPOT, + ) + ) mocker.patch("freqtrade.rpc.rpc.RPC._rpc_force_entry", fbuy_mock) - rc = client_post(client, f"{BASE_URI}/{endpoint}", - data={"pair": "ETH/BTC"}) + rc = client_post(client, f"{BASE_URI}/{endpoint}", data={"pair": "ETH/BTC"}) assert_response(rc) assert rc.json() == { - 'amount': 1.0, - 'amount_requested': 1.0, - 'trade_id': 22, - 'close_date': None, - 'close_timestamp': None, - 'close_rate': 0.265441, - 'open_date': ANY, - 'open_timestamp': ANY, - 'open_fill_date': ANY, - 'open_fill_timestamp': ANY, - 'open_rate': 0.245441, - 'pair': 'ETH/BTC', - 'base_currency': 'ETH', - 'quote_currency': 'BTC', - 'stake_amount': 1, - 'max_stake_amount': ANY, - 'stop_loss_abs': None, - 'stop_loss_pct': None, - 'stop_loss_ratio': None, - 'stoploss_last_update': None, - 'stoploss_last_update_timestamp': None, - 'initial_stop_loss_abs': None, - 'initial_stop_loss_pct': None, - 'initial_stop_loss_ratio': None, - 'close_profit': None, - 'close_profit_pct': None, - 'close_profit_abs': None, - 'close_rate_requested': None, - 'profit_ratio': None, - 'profit_pct': None, - 'profit_abs': None, - 'profit_fiat': None, - 'realized_profit': 0.0, - 'realized_profit_ratio': None, - 'fee_close': 0.0025, - 'fee_close_cost': None, - 'fee_close_currency': None, - 'fee_open': 0.0025, - 'fee_open_cost': None, - 'fee_open_currency': None, - 'is_open': False, - 'is_short': False, - 'max_rate': None, - 'min_rate': None, - 'open_rate_requested': None, - 'open_trade_value': 0.24605460, - 'exit_reason': None, - 'exit_order_status': None, - 'strategy': CURRENT_TEST_STRATEGY, - 'enter_tag': None, - 'timeframe': 5, - 'exchange': 'binance', - 'leverage': None, - 'interest_rate': None, - 'liquidation_price': None, - 'funding_fees': None, - 'trading_mode': 'spot', - 'amount_precision': None, - 'price_precision': None, - 'precision_mode': None, - 'has_open_orders': False, - 'orders': [], + "amount": 1.0, + "amount_requested": 1.0, + "trade_id": 22, + "close_date": None, + "close_timestamp": None, + "close_rate": 0.265441, + "open_date": ANY, + "open_timestamp": ANY, + "open_fill_date": ANY, + "open_fill_timestamp": ANY, + "open_rate": 0.245441, + "pair": "ETH/BTC", + "base_currency": "ETH", + "quote_currency": "BTC", + "stake_amount": 1, + "max_stake_amount": ANY, + "stop_loss_abs": None, + "stop_loss_pct": None, + "stop_loss_ratio": None, + "stoploss_last_update": None, + "stoploss_last_update_timestamp": None, + "initial_stop_loss_abs": None, + "initial_stop_loss_pct": None, + "initial_stop_loss_ratio": None, + "close_profit": None, + "close_profit_pct": None, + "close_profit_abs": None, + "close_rate_requested": None, + "profit_ratio": None, + "profit_pct": None, + "profit_abs": None, + "profit_fiat": None, + "realized_profit": 0.0, + "realized_profit_ratio": None, + "fee_close": 0.0025, + "fee_close_cost": None, + "fee_close_currency": None, + "fee_open": 0.0025, + "fee_open_cost": None, + "fee_open_currency": None, + "is_open": False, + "is_short": False, + "max_rate": None, + "min_rate": None, + "open_rate_requested": None, + "open_trade_value": 0.24605460, + "exit_reason": None, + "exit_order_status": None, + "strategy": CURRENT_TEST_STRATEGY, + "enter_tag": None, + "timeframe": 5, + "exchange": "binance", + "leverage": None, + "interest_rate": None, + "liquidation_price": None, + "funding_fees": None, + "trading_mode": "spot", + "amount_precision": None, + "price_precision": None, + "precision_mode": None, + "has_open_orders": False, + "orders": [], } @@ -1465,8 +1594,7 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets): ) patch_get_signal(ftbot) - rc = client_post(client, f"{BASE_URI}/forceexit", - data={"tradeid": "1"}) + rc = client_post(client, f"{BASE_URI}/forceexit", data={"tradeid": "1"}) assert_response(rc, 502) assert rc.json() == {"error": "Error querying /api/v1/forceexit: invalid argument"} Trade.rollback() @@ -1474,20 +1602,20 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets): create_mock_trades(fee) trade = Trade.get_trades([Trade.id == 5]).first() assert pytest.approx(trade.amount) == 123 - rc = client_post(client, f"{BASE_URI}/forceexit", - data={"tradeid": "5", "ordertype": "market", "amount": 23}) + rc = client_post( + client, f"{BASE_URI}/forceexit", data={"tradeid": "5", "ordertype": "market", "amount": 23} + ) assert_response(rc) - assert rc.json() == {'result': 'Created exit order for trade 5.'} + assert rc.json() == {"result": "Created exit order for trade 5."} Trade.rollback() trade = Trade.get_trades([Trade.id == 5]).first() assert pytest.approx(trade.amount) == 100 assert trade.is_open is True - rc = client_post(client, f"{BASE_URI}/forceexit", - data={"tradeid": "5"}) + rc = client_post(client, f"{BASE_URI}/forceexit", data={"tradeid": "5"}) assert_response(rc) - assert rc.json() == {'result': 'Created exit order for trade 5.'} + assert rc.json() == {"result": "Created exit order for trade 5."} Trade.rollback() trade = Trade.get_trades([Trade.id == 5]).first() @@ -1496,42 +1624,42 @@ def test_api_forceexit(botclient, mocker, ticker, fee, markets): def test_api_pair_candles(botclient, ohlcv_history): ftbot, client = botclient - timeframe = '5m' + timeframe = "5m" amount = 3 # No pair - rc = client_get(client, - f"{BASE_URI}/pair_candles?limit={amount}&timeframe={timeframe}") + rc = client_get(client, f"{BASE_URI}/pair_candles?limit={amount}&timeframe={timeframe}") assert_response(rc, 422) # No timeframe - rc = client_get(client, - f"{BASE_URI}/pair_candles?pair=XRP%2FBTC") + rc = client_get(client, f"{BASE_URI}/pair_candles?pair=XRP%2FBTC") assert_response(rc, 422) - rc = client_get(client, - f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}") + rc = client_get( + client, f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}" + ) assert_response(rc) - assert 'columns' in rc.json() - assert 'data_start_ts' in rc.json() - assert 'data_start' in rc.json() - assert 'data_stop' in rc.json() - assert 'data_stop_ts' in rc.json() - assert len(rc.json()['data']) == 0 - ohlcv_history['sma'] = ohlcv_history['close'].rolling(2).mean() - ohlcv_history['sma2'] = ohlcv_history['close'].rolling(2).mean() - ohlcv_history['enter_long'] = 0 - ohlcv_history.loc[1, 'enter_long'] = 1 - ohlcv_history['exit_long'] = 0 - ohlcv_history['enter_short'] = 0 - ohlcv_history['exit_short'] = 0 + assert "columns" in rc.json() + assert "data_start_ts" in rc.json() + assert "data_start" in rc.json() + assert "data_stop" in rc.json() + assert "data_stop_ts" in rc.json() + assert len(rc.json()["data"]) == 0 + ohlcv_history["sma"] = ohlcv_history["close"].rolling(2).mean() + ohlcv_history["sma2"] = ohlcv_history["close"].rolling(2).mean() + ohlcv_history["enter_long"] = 0 + ohlcv_history.loc[1, "enter_long"] = 1 + ohlcv_history["exit_long"] = 0 + ohlcv_history["enter_short"] = 0 + ohlcv_history["exit_short"] = 0 ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history, CandleType.SPOT) - for call in ('get', 'post'): - if call == 'get': + for call in ("get", "post"): + if call == "get": rc = client_get( client, - f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}") + f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}", + ) else: rc = client_post( client, @@ -1540,133 +1668,315 @@ def test_api_pair_candles(botclient, ohlcv_history): "pair": "XRP/BTC", "timeframe": timeframe, "limit": amount, - "columns": ['sma'], - } + "columns": ["sma"], + }, ) assert_response(rc) resp = rc.json() - assert 'strategy' in resp - assert resp['strategy'] == CURRENT_TEST_STRATEGY - assert 'columns' in resp - assert 'data_start_ts' in resp - assert 'data_start' in resp - assert 'data_stop' in resp - assert 'data_stop_ts' in resp - assert resp['data_start'] == '2017-11-26 08:50:00+00:00' - assert resp['data_start_ts'] == 1511686200000 - assert resp['data_stop'] == '2017-11-26 09:00:00+00:00' - assert resp['data_stop_ts'] == 1511686800000 - assert isinstance(resp['columns'], list) + assert "strategy" in resp + assert resp["strategy"] == CURRENT_TEST_STRATEGY + assert "columns" in resp + assert "data_start_ts" in resp + assert "data_start" in resp + assert "data_stop" in resp + assert "data_stop_ts" in resp + assert resp["data_start"] == "2017-11-26 08:50:00+00:00" + assert resp["data_start_ts"] == 1511686200000 + assert resp["data_stop"] == "2017-11-26 09:00:00+00:00" + assert resp["data_stop_ts"] == 1511686800000 + assert isinstance(resp["columns"], list) base_cols = { - 'date', 'open', 'high', 'low', 'close', 'volume', - 'sma', 'enter_long', 'exit_long', 'enter_short', 'exit_short', '__date_ts', - '_enter_long_signal_close', '_exit_long_signal_close', - '_enter_short_signal_close', '_exit_short_signal_close' - } - if call == 'get': - assert set(resp['columns']) == base_cols.union({'sma2'}) + "date", + "open", + "high", + "low", + "close", + "volume", + "sma", + "enter_long", + "exit_long", + "enter_short", + "exit_short", + "__date_ts", + "_enter_long_signal_close", + "_exit_long_signal_close", + "_enter_short_signal_close", + "_exit_short_signal_close", + } + if call == "get": + assert set(resp["columns"]) == base_cols.union({"sma2"}) else: - assert set(resp['columns']) == base_cols + assert set(resp["columns"]) == base_cols # All columns doesn't include the internal columns - assert set(resp['all_columns']) == { - 'date', 'open', 'high', 'low', 'close', 'volume', - 'sma', 'sma2', 'enter_long', 'exit_long', 'enter_short', 'exit_short' + assert set(resp["all_columns"]) == { + "date", + "open", + "high", + "low", + "close", + "volume", + "sma", + "sma2", + "enter_long", + "exit_long", + "enter_short", + "exit_short", } - assert 'pair' in resp - assert resp['pair'] == 'XRP/BTC' + assert "pair" in resp + assert resp["pair"] == "XRP/BTC" - assert 'data' in resp - assert len(resp['data']) == amount - if call == 'get': - assert len(resp['data'][0]) == 17 - assert resp['data'] == [ - ['2017-11-26T08:50:00Z', 8.794e-05, 8.948e-05, 8.794e-05, 8.88e-05, - 0.0877869, None, None, 0, 0, 0, 0, 1511686200000, None, None, None, None], - ['2017-11-26T08:55:00Z', 8.88e-05, 8.942e-05, 8.88e-05, 8.893e-05, 0.05874751, - 8.886500000000001e-05, 8.886500000000001e-05, 1, 0, 0, 0, 1511686500000, - 8.893e-05, None, None, None], - ['2017-11-26T09:00:00Z', 8.891e-05, 8.893e-05, 8.875e-05, 8.877e-05, - 0.7039405, 8.885e-05, 8.885e-05, 0, 0, 0, 0, 1511686800000, None, None, None, None - ] + assert "data" in resp + assert len(resp["data"]) == amount + if call == "get": + assert len(resp["data"][0]) == 17 + assert resp["data"] == [ + [ + "2017-11-26T08:50:00Z", + 8.794e-05, + 8.948e-05, + 8.794e-05, + 8.88e-05, + 0.0877869, + None, + None, + 0, + 0, + 0, + 0, + 1511686200000, + None, + None, + None, + None, + ], + [ + "2017-11-26T08:55:00Z", + 8.88e-05, + 8.942e-05, + 8.88e-05, + 8.893e-05, + 0.05874751, + 8.886500000000001e-05, + 8.886500000000001e-05, + 1, + 0, + 0, + 0, + 1511686500000, + 8.893e-05, + None, + None, + None, + ], + [ + "2017-11-26T09:00:00Z", + 8.891e-05, + 8.893e-05, + 8.875e-05, + 8.877e-05, + 0.7039405, + 8.885e-05, + 8.885e-05, + 0, + 0, + 0, + 0, + 1511686800000, + None, + None, + None, + None, + ], ] else: - assert len(resp['data'][0]) == 16 - assert resp['data'] == [ - ['2017-11-26T08:50:00Z', 8.794e-05, 8.948e-05, 8.794e-05, 8.88e-05, - 0.0877869, None, 0, 0, 0, 0, 1511686200000, None, None, None, None], - ['2017-11-26T08:55:00Z', 8.88e-05, 8.942e-05, 8.88e-05, 8.893e-05, 0.05874751, - 8.886500000000001e-05, 1, 0, 0, 0, 1511686500000, - 8.893e-05, None, None, None], - ['2017-11-26T09:00:00Z', 8.891e-05, 8.893e-05, 8.875e-05, 8.877e-05, - 0.7039405, 8.885e-05, 0, 0, 0, 0, 1511686800000, None, None, None, None - ] + assert len(resp["data"][0]) == 16 + assert resp["data"] == [ + [ + "2017-11-26T08:50:00Z", + 8.794e-05, + 8.948e-05, + 8.794e-05, + 8.88e-05, + 0.0877869, + None, + 0, + 0, + 0, + 0, + 1511686200000, + None, + None, + None, + None, + ], + [ + "2017-11-26T08:55:00Z", + 8.88e-05, + 8.942e-05, + 8.88e-05, + 8.893e-05, + 0.05874751, + 8.886500000000001e-05, + 1, + 0, + 0, + 0, + 1511686500000, + 8.893e-05, + None, + None, + None, + ], + [ + "2017-11-26T09:00:00Z", + 8.891e-05, + 8.893e-05, + 8.875e-05, + 8.877e-05, + 0.7039405, + 8.885e-05, + 0, + 0, + 0, + 0, + 1511686800000, + None, + None, + None, + None, + ], ] # prep for next test - ohlcv_history['exit_long'] = ohlcv_history['exit_long'].astype('float64') - ohlcv_history.at[0, 'exit_long'] = float('inf') - ohlcv_history['date1'] = ohlcv_history['date'] - ohlcv_history.at[0, 'date1'] = pd.NaT + ohlcv_history["exit_long"] = ohlcv_history["exit_long"].astype("float64") + ohlcv_history.at[0, "exit_long"] = float("inf") + ohlcv_history["date1"] = ohlcv_history["date"] + ohlcv_history.at[0, "date1"] = pd.NaT ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history, CandleType.SPOT) - rc = client_get(client, - f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}") + rc = client_get( + client, f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}" + ) assert_response(rc) - assert (rc.json()['data'] == - [['2017-11-26T08:50:00Z', 8.794e-05, 8.948e-05, 8.794e-05, 8.88e-05, 0.0877869, - None, None, 0, None, 0, 0, None, 1511686200000, None, None, None, None], - ['2017-11-26T08:55:00Z', 8.88e-05, 8.942e-05, 8.88e-05, - 8.893e-05, 0.05874751, 8.886500000000001e-05, 8.886500000000001e-05, 1, 0.0, 0, - 0, '2017-11-26T08:55:00Z', 1511686500000, 8.893e-05, None, None, None], - ['2017-11-26T09:00:00Z', 8.891e-05, 8.893e-05, 8.875e-05, 8.877e-05, - 0.7039405, 8.885e-05, 8.885e-05, 0, 0.0, 0, 0, '2017-11-26T09:00:00Z', - 1511686800000, None, None, None, None] - ]) + assert rc.json()["data"] == [ + [ + "2017-11-26T08:50:00Z", + 8.794e-05, + 8.948e-05, + 8.794e-05, + 8.88e-05, + 0.0877869, + None, + None, + 0, + None, + 0, + 0, + None, + 1511686200000, + None, + None, + None, + None, + ], + [ + "2017-11-26T08:55:00Z", + 8.88e-05, + 8.942e-05, + 8.88e-05, + 8.893e-05, + 0.05874751, + 8.886500000000001e-05, + 8.886500000000001e-05, + 1, + 0.0, + 0, + 0, + "2017-11-26T08:55:00Z", + 1511686500000, + 8.893e-05, + None, + None, + None, + ], + [ + "2017-11-26T09:00:00Z", + 8.891e-05, + 8.893e-05, + 8.875e-05, + 8.877e-05, + 0.7039405, + 8.885e-05, + 8.885e-05, + 0, + 0.0, + 0, + 0, + "2017-11-26T09:00:00Z", + 1511686800000, + None, + None, + None, + None, + ], + ] def test_api_pair_history(botclient, tmp_path, mocker): _ftbot, client = botclient - _ftbot.config['user_data_dir'] = tmp_path + _ftbot.config["user_data_dir"] = tmp_path - timeframe = '5m' - lfm = mocker.patch('freqtrade.strategy.interface.IStrategy.load_freqAI_model') + timeframe = "5m" + lfm = mocker.patch("freqtrade.strategy.interface.IStrategy.load_freqAI_model") # No pair - rc = client_get(client, - f"{BASE_URI}/pair_history?timeframe={timeframe}" - f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}") + rc = client_get( + client, + f"{BASE_URI}/pair_history?timeframe={timeframe}" + f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}", + ) assert_response(rc, 422) # No Timeframe - rc = client_get(client, - f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC" - f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}") + rc = client_get( + client, + f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC" + f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}", + ) assert_response(rc, 422) # No timerange - rc = client_get(client, - f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" - f"&strategy={CURRENT_TEST_STRATEGY}") + rc = client_get( + client, + f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" + f"&strategy={CURRENT_TEST_STRATEGY}", + ) assert_response(rc, 422) # No strategy - rc = client_get(client, - f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" - "&timerange=20180111-20180112") + rc = client_get( + client, + f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" + "&timerange=20180111-20180112", + ) assert_response(rc, 422) # Invalid strategy - rc = client_get(client, - f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" - "&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}11") + rc = client_get( + client, + f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" + "&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}11", + ) assert_response(rc, 502) # Working - for call in ('get', 'post'): - if call == 'get': - rc = client_get(client, - f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" - f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}") + for call in ("get", "post"): + if call == "get": + rc = client_get( + client, + f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" + f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}", + ) else: rc = client_post( client, @@ -1676,42 +1986,45 @@ def test_api_pair_history(botclient, tmp_path, mocker): "timeframe": timeframe, "timerange": "20180111-20180112", "strategy": CURRENT_TEST_STRATEGY, - "columns": ['rsi', 'fastd', 'fastk'], - }) + "columns": ["rsi", "fastd", "fastk"], + }, + ) assert_response(rc, 200) result = rc.json() - assert result['length'] == 289 - assert len(result['data']) == result['length'] - assert 'columns' in result - assert 'data' in result - data = result['data'] + assert result["length"] == 289 + assert len(result["data"]) == result["length"] + assert "columns" in result + assert "data" in result + data = result["data"] assert len(data) == 289 - col_count = 30 if call == 'get' else 18 + col_count = 30 if call == "get" else 18 # analyzed DF has 30 columns - assert len(result['columns']) == col_count - assert len(result['all_columns']) == 25 + assert len(result["columns"]) == col_count + assert len(result["all_columns"]) == 25 assert len(data[0]) == col_count - date_col_idx = [idx for idx, c in enumerate(result['columns']) if c == 'date'][0] - rsi_col_idx = [idx for idx, c in enumerate(result['columns']) if c == 'rsi'][0] + date_col_idx = [idx for idx, c in enumerate(result["columns"]) if c == "date"][0] + rsi_col_idx = [idx for idx, c in enumerate(result["columns"]) if c == "rsi"][0] - assert data[0][date_col_idx] == '2018-01-11T00:00:00Z' + assert data[0][date_col_idx] == "2018-01-11T00:00:00Z" assert data[0][rsi_col_idx] is not None assert data[0][rsi_col_idx] > 0 assert lfm.call_count == 1 - assert result['pair'] == 'UNITTEST/BTC' - assert result['strategy'] == CURRENT_TEST_STRATEGY - assert result['data_start'] == '2018-01-11 00:00:00+00:00' - assert result['data_start_ts'] == 1515628800000 - assert result['data_stop'] == '2018-01-12 00:00:00+00:00' - assert result['data_stop_ts'] == 1515715200000 + assert result["pair"] == "UNITTEST/BTC" + assert result["strategy"] == CURRENT_TEST_STRATEGY + assert result["data_start"] == "2018-01-11 00:00:00+00:00" + assert result["data_start_ts"] == 1515628800000 + assert result["data_stop"] == "2018-01-12 00:00:00+00:00" + assert result["data_stop_ts"] == 1515715200000 lfm.reset_mock() # No data found - if call == 'get': - rc = client_get(client, - f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" - f"&timerange=20200111-20200112&strategy={CURRENT_TEST_STRATEGY}") + if call == "get": + rc = client_get( + client, + f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}" + f"&timerange=20200111-20200112&strategy={CURRENT_TEST_STRATEGY}", + ) else: rc = client_post( client, @@ -1721,52 +2034,53 @@ def test_api_pair_history(botclient, tmp_path, mocker): "timeframe": timeframe, "timerange": "20200111-20200112", "strategy": CURRENT_TEST_STRATEGY, - "columns": ['rsi', 'fastd', 'fastk'], - }) + "columns": ["rsi", "fastd", "fastk"], + }, + ) assert_response(rc, 502) - assert rc.json()['detail'] == ("No data for UNITTEST/BTC, 5m in 20200111-20200112 found.") + assert rc.json()["detail"] == ("No data for UNITTEST/BTC, 5m in 20200111-20200112 found.") def test_api_plot_config(botclient, mocker, tmp_path): ftbot, client = botclient - ftbot.config['user_data_dir'] = tmp_path + ftbot.config["user_data_dir"] = tmp_path rc = client_get(client, f"{BASE_URI}/plot_config") assert_response(rc) assert rc.json() == {} ftbot.strategy.plot_config = { - 'main_plot': {'sma': {}}, - 'subplots': {'RSI': {'rsi': {'color': 'red'}}} + "main_plot": {"sma": {}}, + "subplots": {"RSI": {"rsi": {"color": "red"}}}, } rc = client_get(client, f"{BASE_URI}/plot_config") assert_response(rc) assert rc.json() == ftbot.strategy.plot_config - assert isinstance(rc.json()['main_plot'], dict) - assert isinstance(rc.json()['subplots'], dict) + assert isinstance(rc.json()["main_plot"], dict) + assert isinstance(rc.json()["subplots"], dict) - ftbot.strategy.plot_config = {'main_plot': {'sma': {}}} + ftbot.strategy.plot_config = {"main_plot": {"sma": {}}} rc = client_get(client, f"{BASE_URI}/plot_config") assert_response(rc) - assert isinstance(rc.json()['main_plot'], dict) - assert isinstance(rc.json()['subplots'], dict) + assert isinstance(rc.json()["main_plot"], dict) + assert isinstance(rc.json()["subplots"], dict) rc = client_get(client, f"{BASE_URI}/plot_config?strategy=freqai_test_classifier") assert_response(rc) res = rc.json() - assert 'target_roi' in res['subplots'] - assert 'do_predict' in res['subplots'] + assert "target_roi" in res["subplots"] + assert "do_predict" in res["subplots"] rc = client_get(client, f"{BASE_URI}/plot_config?strategy=HyperoptableStrategy") assert_response(rc) - assert rc.json()['subplots'] == {} + assert rc.json()["subplots"] == {} rc = client_get(client, f"{BASE_URI}/plot_config?strategy=NotAStrategy") assert_response(rc, 502) - assert rc.json()['detail'] is not None + assert rc.json()["detail"] is not None - mocker.patch('freqtrade.rpc.api_server.api_v1.get_rpc_optional', return_value=None) + mocker.patch("freqtrade.rpc.api_server.api_v1.get_rpc_optional", return_value=None) rc = client_get(client, f"{BASE_URI}/plot_config") assert_response(rc) @@ -1774,40 +2088,42 @@ def test_api_plot_config(botclient, mocker, tmp_path): def test_api_strategies(botclient, tmp_path): ftbot, client = botclient - ftbot.config['user_data_dir'] = tmp_path + ftbot.config["user_data_dir"] = tmp_path rc = client_get(client, f"{BASE_URI}/strategies") assert_response(rc) - assert rc.json() == {'strategies': [ - 'HyperoptableStrategy', - 'HyperoptableStrategyV2', - 'InformativeDecoratorTest', - 'StrategyTestV2', - 'StrategyTestV3', - 'StrategyTestV3CustomEntryPrice', - 'StrategyTestV3Futures', - 'freqai_rl_test_strat', - 'freqai_test_classifier', - 'freqai_test_multimodel_classifier_strat', - 'freqai_test_multimodel_strat', - 'freqai_test_strat', - 'strategy_test_v3_recursive_issue' - ]} + assert rc.json() == { + "strategies": [ + "HyperoptableStrategy", + "HyperoptableStrategyV2", + "InformativeDecoratorTest", + "StrategyTestV2", + "StrategyTestV3", + "StrategyTestV3CustomEntryPrice", + "StrategyTestV3Futures", + "freqai_rl_test_strat", + "freqai_test_classifier", + "freqai_test_multimodel_classifier_strat", + "freqai_test_multimodel_strat", + "freqai_test_strat", + "strategy_test_v3_recursive_issue", + ] + } def test_api_strategy(botclient, tmp_path, mocker): _ftbot, client = botclient - _ftbot.config['user_data_dir'] = tmp_path + _ftbot.config["user_data_dir"] = tmp_path rc = client_get(client, f"{BASE_URI}/strategy/{CURRENT_TEST_STRATEGY}") assert_response(rc) - assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY + assert rc.json()["strategy"] == CURRENT_TEST_STRATEGY data = (Path(__file__).parents[1] / "strategy/strats/strategy_test_v3.py").read_text() - assert rc.json()['code'] == data + assert rc.json()["code"] == data rc = client_get(client, f"{BASE_URI}/strategy/NoStrat") assert_response(rc, 404) @@ -1815,8 +2131,10 @@ def test_api_strategy(botclient, tmp_path, mocker): # Disallow base64 strategies rc = client_get(client, f"{BASE_URI}/strategy/xx:cHJpbnQoImhlbGxvIHdvcmxkIik=") assert_response(rc, 500) - mocker.patch('freqtrade.resolvers.strategy_resolver.StrategyResolver._load_strategy', - side_effect=Exception("Test")) + mocker.patch( + "freqtrade.resolvers.strategy_resolver.StrategyResolver._load_strategy", + side_effect=Exception("Test"), + ) rc = client_get(client, f"{BASE_URI}/strategy/NoStrat") assert_response(rc, 502) @@ -1828,149 +2146,142 @@ def test_api_exchanges(botclient): rc = client_get(client, f"{BASE_URI}/exchanges") assert_response(rc) response = rc.json() - assert isinstance(response['exchanges'], list) - assert len(response['exchanges']) > 20 - okx = [x for x in response['exchanges'] if x['name'] == 'okx'][0] + assert isinstance(response["exchanges"], list) + assert len(response["exchanges"]) > 20 + okx = [x for x in response["exchanges"] if x["name"] == "okx"][0] assert okx == { "name": "okx", "valid": True, "supported": True, "comment": "", "trade_modes": [ - { - "trading_mode": "spot", - "margin_mode": "" - }, - { - "trading_mode": "futures", - "margin_mode": "isolated" - } - ] + {"trading_mode": "spot", "margin_mode": ""}, + {"trading_mode": "futures", "margin_mode": "isolated"}, + ], } - mexc = [x for x in response['exchanges'] if x['name'] == 'mexc'][0] + mexc = [x for x in response["exchanges"] if x["name"] == "mexc"][0] assert mexc == { "name": "mexc", "valid": True, "supported": False, "comment": "", - "trade_modes": [ - { - "trading_mode": "spot", - "margin_mode": "" - } - ] + "trade_modes": [{"trading_mode": "spot", "margin_mode": ""}], } def test_api_freqaimodels(botclient, tmp_path, mocker): ftbot, client = botclient - ftbot.config['user_data_dir'] = tmp_path + ftbot.config["user_data_dir"] = tmp_path mocker.patch( "freqtrade.resolvers.freqaimodel_resolver.FreqaiModelResolver.search_all_objects", return_value=[ - {'name': 'LightGBMClassifier'}, - {'name': 'LightGBMClassifierMultiTarget'}, - {'name': 'LightGBMRegressor'}, - {'name': 'LightGBMRegressorMultiTarget'}, - {'name': 'ReinforcementLearner'}, - {'name': 'ReinforcementLearner_multiproc'}, - {'name': 'SKlearnRandomForestClassifier'}, - {'name': 'XGBoostClassifier'}, - {'name': 'XGBoostRFClassifier'}, - {'name': 'XGBoostRFRegressor'}, - {'name': 'XGBoostRegressor'}, - {'name': 'XGBoostRegressorMultiTarget'}, - ]) + {"name": "LightGBMClassifier"}, + {"name": "LightGBMClassifierMultiTarget"}, + {"name": "LightGBMRegressor"}, + {"name": "LightGBMRegressorMultiTarget"}, + {"name": "ReinforcementLearner"}, + {"name": "ReinforcementLearner_multiproc"}, + {"name": "SKlearnRandomForestClassifier"}, + {"name": "XGBoostClassifier"}, + {"name": "XGBoostRFClassifier"}, + {"name": "XGBoostRFRegressor"}, + {"name": "XGBoostRegressor"}, + {"name": "XGBoostRegressorMultiTarget"}, + ], + ) rc = client_get(client, f"{BASE_URI}/freqaimodels") assert_response(rc) - assert rc.json() == {'freqaimodels': [ - 'LightGBMClassifier', - 'LightGBMClassifierMultiTarget', - 'LightGBMRegressor', - 'LightGBMRegressorMultiTarget', - 'ReinforcementLearner', - 'ReinforcementLearner_multiproc', - 'SKlearnRandomForestClassifier', - 'XGBoostClassifier', - 'XGBoostRFClassifier', - 'XGBoostRFRegressor', - 'XGBoostRegressor', - 'XGBoostRegressorMultiTarget' - ]} + assert rc.json() == { + "freqaimodels": [ + "LightGBMClassifier", + "LightGBMClassifierMultiTarget", + "LightGBMRegressor", + "LightGBMRegressorMultiTarget", + "ReinforcementLearner", + "ReinforcementLearner_multiproc", + "SKlearnRandomForestClassifier", + "XGBoostClassifier", + "XGBoostRFClassifier", + "XGBoostRFRegressor", + "XGBoostRegressor", + "XGBoostRegressorMultiTarget", + ] + } def test_api_pairlists_available(botclient, tmp_path): ftbot, client = botclient - ftbot.config['user_data_dir'] = tmp_path + ftbot.config["user_data_dir"] = tmp_path rc = client_get(client, f"{BASE_URI}/pairlists/available") assert_response(rc, 503) - assert rc.json()['detail'] == 'Bot is not in the correct state.' + assert rc.json()["detail"] == "Bot is not in the correct state." - ftbot.config['runmode'] = RunMode.WEBSERVER + ftbot.config["runmode"] = RunMode.WEBSERVER rc = client_get(client, f"{BASE_URI}/pairlists/available") assert_response(rc) response = rc.json() - assert isinstance(response['pairlists'], list) - assert len(response['pairlists']) > 0 + assert isinstance(response["pairlists"], list) + assert len(response["pairlists"]) > 0 - assert len([r for r in response['pairlists'] if r['name'] == 'AgeFilter']) == 1 - assert len([r for r in response['pairlists'] if r['name'] == 'VolumePairList']) == 1 - assert len([r for r in response['pairlists'] if r['name'] == 'StaticPairList']) == 1 + assert len([r for r in response["pairlists"] if r["name"] == "AgeFilter"]) == 1 + assert len([r for r in response["pairlists"] if r["name"] == "VolumePairList"]) == 1 + assert len([r for r in response["pairlists"] if r["name"] == "StaticPairList"]) == 1 - volumepl = [r for r in response['pairlists'] if r['name'] == 'VolumePairList'][0] - assert volumepl['is_pairlist_generator'] is True - assert len(volumepl['params']) > 1 - age_pl = [r for r in response['pairlists'] if r['name'] == 'AgeFilter'][0] - assert age_pl['is_pairlist_generator'] is False - assert len(volumepl['params']) > 2 + volumepl = [r for r in response["pairlists"] if r["name"] == "VolumePairList"][0] + assert volumepl["is_pairlist_generator"] is True + assert len(volumepl["params"]) > 1 + age_pl = [r for r in response["pairlists"] if r["name"] == "AgeFilter"][0] + assert age_pl["is_pairlist_generator"] is False + assert len(volumepl["params"]) > 2 def test_api_pairlists_evaluate(botclient, tmp_path, mocker): ftbot, client = botclient - ftbot.config['user_data_dir'] = tmp_path + ftbot.config["user_data_dir"] = tmp_path rc = client_get(client, f"{BASE_URI}/pairlists/evaluate/randomJob") assert_response(rc, 503) - assert rc.json()['detail'] == 'Bot is not in the correct state.' + assert rc.json()["detail"] == "Bot is not in the correct state." - ftbot.config['runmode'] = RunMode.WEBSERVER + ftbot.config["runmode"] = RunMode.WEBSERVER rc = client_get(client, f"{BASE_URI}/pairlists/evaluate/randomJob") assert_response(rc, 404) - assert rc.json()['detail'] == 'Job not found.' + assert rc.json()["detail"] == "Job not found." body = { "pairlists": [ - {"method": "StaticPairList", }, + { + "method": "StaticPairList", + }, ], - "blacklist": [ - ], - "stake_currency": "BTC" + "blacklist": [], + "stake_currency": "BTC", } # Fail, already running ApiBG.pairlist_running = True rc = client_post(client, f"{BASE_URI}/pairlists/evaluate", body) assert_response(rc, 400) - assert rc.json()['detail'] == 'Pairlist evaluation is already running.' + assert rc.json()["detail"] == "Pairlist evaluation is already running." # should start the run ApiBG.pairlist_running = False rc = client_post(client, f"{BASE_URI}/pairlists/evaluate", body) assert_response(rc) - assert rc.json()['status'] == 'Pairlist evaluation started in background.' - job_id = rc.json()['job_id'] + assert rc.json()["status"] == "Pairlist evaluation started in background." + job_id = rc.json()["job_id"] rc = client_get(client, f"{BASE_URI}/background/RandomJob") assert_response(rc, 404) - assert rc.json()['detail'] == 'Job not found.' + assert rc.json()["detail"] == "Job not found." # Background list rc = client_get(client, f"{BASE_URI}/background") @@ -1978,42 +2289,47 @@ def test_api_pairlists_evaluate(botclient, tmp_path, mocker): response = rc.json() assert isinstance(response, list) assert len(response) == 1 - assert response[0]['job_id'] == job_id + assert response[0]["job_id"] == job_id # Get individual job rc = client_get(client, f"{BASE_URI}/background/{job_id}") assert_response(rc) response = rc.json() - assert response['job_id'] == job_id - assert response['job_category'] == 'pairlist' + assert response["job_id"] == job_id + assert response["job_category"] == "pairlist" rc = client_get(client, f"{BASE_URI}/pairlists/evaluate/{job_id}") assert_response(rc) response = rc.json() - assert response['result']['whitelist'] == ['ETH/BTC', 'LTC/BTC', 'XRP/BTC', 'NEO/BTC'] - assert response['result']['length'] == 4 + assert response["result"]["whitelist"] == ["ETH/BTC", "LTC/BTC", "XRP/BTC", "NEO/BTC"] + assert response["result"]["length"] == 4 # Restart with additional filter, reducing the list to 2 - body['pairlists'].append({"method": "OffsetFilter", "number_assets": 2}) + body["pairlists"].append({"method": "OffsetFilter", "number_assets": 2}) rc = client_post(client, f"{BASE_URI}/pairlists/evaluate", body) assert_response(rc) - assert rc.json()['status'] == 'Pairlist evaluation started in background.' - job_id = rc.json()['job_id'] + assert rc.json()["status"] == "Pairlist evaluation started in background." + job_id = rc.json()["job_id"] rc = client_get(client, f"{BASE_URI}/pairlists/evaluate/{job_id}") assert_response(rc) response = rc.json() - assert response['result']['whitelist'] == ['ETH/BTC', 'LTC/BTC', ] - assert response['result']['length'] == 2 + assert response["result"]["whitelist"] == [ + "ETH/BTC", + "LTC/BTC", + ] + assert response["result"]["length"] == 2 # Patch __run_pairlists - plm = mocker.patch('freqtrade.rpc.api_server.api_background_tasks.__run_pairlist', - return_value=None) + plm = mocker.patch( + "freqtrade.rpc.api_server.api_background_tasks.__run_pairlist", return_value=None + ) body = { "pairlists": [ - {"method": "StaticPairList", }, - ], - "blacklist": [ + { + "method": "StaticPairList", + }, ], + "blacklist": [], "stake_currency": "BTC", "exchange": "randomExchange", "trading_mode": "futures", @@ -2023,9 +2339,9 @@ def test_api_pairlists_evaluate(botclient, tmp_path, mocker): assert_response(rc) assert plm.call_count == 1 call_config = plm.call_args_list[0][0][1] - assert call_config['exchange']['name'] == 'randomExchange' - assert call_config['trading_mode'] == 'futures' - assert call_config['margin_mode'] == 'isolated' + assert call_config["exchange"]["name"] == "randomExchange" + assert call_config["trading_mode"] == "futures" + assert call_config["margin_mode"] == "isolated" def test_list_available_pairs(botclient): @@ -2034,38 +2350,36 @@ def test_list_available_pairs(botclient): rc = client_get(client, f"{BASE_URI}/available_pairs") assert_response(rc) - assert rc.json()['length'] == 12 - assert isinstance(rc.json()['pairs'], list) + assert rc.json()["length"] == 12 + assert isinstance(rc.json()["pairs"], list) rc = client_get(client, f"{BASE_URI}/available_pairs?timeframe=5m") assert_response(rc) - assert rc.json()['length'] == 12 + assert rc.json()["length"] == 12 rc = client_get(client, f"{BASE_URI}/available_pairs?stake_currency=ETH") assert_response(rc) - assert rc.json()['length'] == 1 - assert rc.json()['pairs'] == ['XRP/ETH'] - assert len(rc.json()['pair_interval']) == 2 + assert rc.json()["length"] == 1 + assert rc.json()["pairs"] == ["XRP/ETH"] + assert len(rc.json()["pair_interval"]) == 2 rc = client_get(client, f"{BASE_URI}/available_pairs?stake_currency=ETH&timeframe=5m") assert_response(rc) - assert rc.json()['length'] == 1 - assert rc.json()['pairs'] == ['XRP/ETH'] - assert len(rc.json()['pair_interval']) == 1 + assert rc.json()["length"] == 1 + assert rc.json()["pairs"] == ["XRP/ETH"] + assert len(rc.json()["pair_interval"]) == 1 - ftbot.config['trading_mode'] = 'futures' - rc = client_get( - client, f"{BASE_URI}/available_pairs?timeframe=1h") + ftbot.config["trading_mode"] = "futures" + rc = client_get(client, f"{BASE_URI}/available_pairs?timeframe=1h") assert_response(rc) - assert rc.json()['length'] == 1 - assert rc.json()['pairs'] == ['XRP/USDT:USDT'] + assert rc.json()["length"] == 1 + assert rc.json()["pairs"] == ["XRP/USDT:USDT"] - rc = client_get( - client, f"{BASE_URI}/available_pairs?timeframe=1h&candletype=mark") + rc = client_get(client, f"{BASE_URI}/available_pairs?timeframe=1h&candletype=mark") assert_response(rc) - assert rc.json()['length'] == 2 - assert rc.json()['pairs'] == ['UNITTEST/USDT:USDT', 'XRP/USDT:USDT'] - assert len(rc.json()['pair_interval']) == 2 + assert rc.json()["length"] == 2 + assert rc.json()["pairs"] == ["UNITTEST/USDT:USDT", "XRP/USDT:USDT"] + assert len(rc.json()["pair_interval"]) == 2 def test_sysinfo(botclient): @@ -2074,43 +2388,43 @@ def test_sysinfo(botclient): rc = client_get(client, f"{BASE_URI}/sysinfo") assert_response(rc) result = rc.json() - assert 'cpu_pct' in result - assert 'ram_pct' in result + assert "cpu_pct" in result + assert "ram_pct" in result def test_api_backtesting(botclient, mocker, fee, caplog, tmp_path): try: ftbot, client = botclient - mocker.patch(f'{EXMS}.get_fee', fee) + mocker.patch(f"{EXMS}.get_fee", fee) rc = client_get(client, f"{BASE_URI}/backtest") # Backtest prevented in default mode assert_response(rc, 503) - assert rc.json()['detail'] == 'Bot is not in the correct state.' + assert rc.json()["detail"] == "Bot is not in the correct state." - ftbot.config['runmode'] = RunMode.WEBSERVER + ftbot.config["runmode"] = RunMode.WEBSERVER # Backtesting not started yet rc = client_get(client, f"{BASE_URI}/backtest") assert_response(rc) result = rc.json() - assert result['status'] == 'not_started' - assert not result['running'] - assert result['status_msg'] == 'Backtest not yet executed' - assert result['progress'] == 0 + assert result["status"] == "not_started" + assert not result["running"] + assert result["status_msg"] == "Backtest not yet executed" + assert result["progress"] == 0 # Reset backtesting rc = client_delete(client, f"{BASE_URI}/backtest") assert_response(rc) result = rc.json() - assert result['status'] == 'reset' - assert not result['running'] - assert result['status_msg'] == 'Backtest reset' - ftbot.config['export'] = 'trades' - ftbot.config['backtest_cache'] = 'day' - ftbot.config['user_data_dir'] = tmp_path - ftbot.config['exportfilename'] = tmp_path / "backtest_results" - ftbot.config['exportfilename'].mkdir() + assert result["status"] == "reset" + assert not result["running"] + assert result["status_msg"] == "Backtest reset" + ftbot.config["export"] = "trades" + ftbot.config["backtest_cache"] = "day" + ftbot.config["user_data_dir"] = tmp_path + ftbot.config["exportfilename"] = tmp_path / "backtest_results" + ftbot.config["exportfilename"].mkdir() # start backtesting data = { @@ -2120,63 +2434,63 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmp_path): "max_open_trades": 3, "stake_amount": 100, "dry_run_wallet": 1000, - "enable_protections": False + "enable_protections": False, } rc = client_post(client, f"{BASE_URI}/backtest", data=data) assert_response(rc) result = rc.json() - assert result['status'] == 'running' - assert result['progress'] == 0 - assert result['running'] - assert result['status_msg'] == 'Backtest started' + assert result["status"] == "running" + assert result["progress"] == 0 + assert result["running"] + assert result["status_msg"] == "Backtest started" rc = client_get(client, f"{BASE_URI}/backtest") assert_response(rc) result = rc.json() - assert result['status'] == 'ended' - assert not result['running'] - assert result['status_msg'] == 'Backtest ended' - assert result['progress'] == 1 - assert result['backtest_result'] + assert result["status"] == "ended" + assert not result["running"] + assert result["status_msg"] == "Backtest ended" + assert result["progress"] == 1 + assert result["backtest_result"] rc = client_get(client, f"{BASE_URI}/backtest/abort") assert_response(rc) result = rc.json() - assert result['status'] == 'not_running' - assert not result['running'] - assert result['status_msg'] == 'Backtest ended' + assert result["status"] == "not_running" + assert not result["running"] + assert result["status_msg"] == "Backtest ended" # Simulate running backtest ApiBG.bgtask_running = True rc = client_get(client, f"{BASE_URI}/backtest/abort") assert_response(rc) result = rc.json() - assert result['status'] == 'stopping' - assert not result['running'] - assert result['status_msg'] == 'Backtest ended' + assert result["status"] == "stopping" + assert not result["running"] + assert result["status_msg"] == "Backtest ended" # Get running backtest... rc = client_get(client, f"{BASE_URI}/backtest") assert_response(rc) result = rc.json() - assert result['status'] == 'running' - assert result['running'] - assert result['step'] == "backtest" - assert result['status_msg'] == "Backtest running" + assert result["status"] == "running" + assert result["running"] + assert result["step"] == "backtest" + assert result["status_msg"] == "Backtest running" # Try delete with task still running rc = client_delete(client, f"{BASE_URI}/backtest") assert_response(rc) result = rc.json() - assert result['status'] == 'running' + assert result["status"] == "running" # Post to backtest that's still running rc = client_post(client, f"{BASE_URI}/backtest", data=data) assert_response(rc, 502) result = rc.json() - assert 'Bot Background task already running' in result['error'] + assert "Bot Background task already running" in result["error"] ApiBG.bgtask_running = False @@ -2184,32 +2498,34 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmp_path): rc = client_post(client, f"{BASE_URI}/backtest", data=data) assert_response(rc) result = rc.json() - assert log_has_re('Reusing result of previous backtest.*', caplog) + assert log_has_re("Reusing result of previous backtest.*", caplog) - data['stake_amount'] = 101 + data["stake_amount"] = 101 - mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy', - side_effect=DependencyException('DeadBeef')) + mocker.patch( + "freqtrade.optimize.backtesting.Backtesting.backtest_one_strategy", + side_effect=DependencyException("DeadBeef"), + ) rc = client_post(client, f"{BASE_URI}/backtest", data=data) assert log_has("Backtesting caused an error: DeadBeef", caplog) rc = client_get(client, f"{BASE_URI}/backtest") assert_response(rc) result = rc.json() - assert result['status'] == 'error' - assert 'Backtest failed' in result['status_msg'] + assert result["status"] == "error" + assert "Backtest failed" in result["status_msg"] # Delete backtesting to avoid leakage since the backtest-object may stick around. rc = client_delete(client, f"{BASE_URI}/backtest") assert_response(rc) result = rc.json() - assert result['status'] == 'reset' - assert not result['running'] - assert result['status_msg'] == 'Backtest reset' + assert result["status"] == "reset" + assert not result["running"] + assert result["status_msg"] == "Backtest reset" # Disallow base64 strategies - data['strategy'] = "xx:cHJpbnQoImhlbGxvIHdvcmxkIik=" + data["strategy"] = "xx:cHJpbnQoImhlbGxvIHdvcmxkIik=" rc = client_post(client, f"{BASE_URI}/backtest", data=data) assert_response(rc, 500) finally: @@ -2218,37 +2534,39 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmp_path): def test_api_backtest_history(botclient, mocker, testdatadir): ftbot, client = botclient - mocker.patch('freqtrade.data.btanalysis._get_backtest_files', - return_value=[ - testdatadir / 'backtest_results/backtest-result_multistrat.json', - testdatadir / 'backtest_results/backtest-result.json' - ]) + mocker.patch( + "freqtrade.data.btanalysis._get_backtest_files", + return_value=[ + testdatadir / "backtest_results/backtest-result_multistrat.json", + testdatadir / "backtest_results/backtest-result.json", + ], + ) rc = client_get(client, f"{BASE_URI}/backtest/history") assert_response(rc, 503) - assert rc.json()['detail'] == 'Bot is not in the correct state.' + assert rc.json()["detail"] == "Bot is not in the correct state." - ftbot.config['user_data_dir'] = testdatadir - ftbot.config['runmode'] = RunMode.WEBSERVER + ftbot.config["user_data_dir"] = testdatadir + ftbot.config["runmode"] = RunMode.WEBSERVER rc = client_get(client, f"{BASE_URI}/backtest/history") assert_response(rc) result = rc.json() assert len(result) == 3 - fn = result[0]['filename'] + fn = result[0]["filename"] assert fn == "backtest-result_multistrat" - assert result[0]['notes'] == '' - strategy = result[0]['strategy'] + assert result[0]["notes"] == "" + strategy = result[0]["strategy"] rc = client_get(client, f"{BASE_URI}/backtest/history/result?filename={fn}&strategy={strategy}") assert_response(rc) result2 = rc.json() assert result2 - assert result2['status'] == 'ended' - assert not result2['running'] - assert result2['progress'] == 1 + assert result2["status"] == "ended" + assert not result2["running"] + assert result2["progress"] == 1 # Only one strategy loaded - even though we use multiresult - assert len(result2['backtest_result']['strategy']) == 1 - assert result2['backtest_result']['strategy'][strategy] + assert len(result2["backtest_result"]["strategy"]) == 1 + assert result2["backtest_result"]["strategy"][strategy] def test_api_delete_backtest_history_entry(botclient, tmp_path: Path): @@ -2259,18 +2577,18 @@ def test_api_delete_backtest_history_entry(botclient, tmp_path: Path): bt_results_base.mkdir() file_path = bt_results_base / "test.json" file_path.touch() - meta_path = file_path.with_suffix('.meta.json') + meta_path = file_path.with_suffix(".meta.json") meta_path.touch() rc = client_delete(client, f"{BASE_URI}/backtest/history/randomFile.json") assert_response(rc, 503) - assert rc.json()['detail'] == 'Bot is not in the correct state.' + assert rc.json()["detail"] == "Bot is not in the correct state." - ftbot.config['user_data_dir'] = tmp_path - ftbot.config['runmode'] = RunMode.WEBSERVER + ftbot.config["user_data_dir"] = tmp_path + ftbot.config["runmode"] = RunMode.WEBSERVER rc = client_delete(client, f"{BASE_URI}/backtest/history/randomFile.json") assert rc.status_code == 404 - assert rc.json()['detail'] == 'File not found.' + assert rc.json()["detail"] == "File not found." rc = client_delete(client, f"{BASE_URI}/backtest/history/{file_path.name}") assert rc.status_code == 200 @@ -2287,65 +2605,85 @@ def test_api_patch_backtest_history_entry(botclient, tmp_path: Path): bt_results_base.mkdir() file_path = bt_results_base / "test.json" file_path.touch() - meta_path = file_path.with_suffix('.meta.json') - with meta_path.open('w') as metafile: - rapidjson.dump({ - CURRENT_TEST_STRATEGY: { - "run_id": "6e542efc8d5e62cef6e5be0ffbc29be81a6e751d", - "backtest_start_time": 1690176003} - }, metafile) + meta_path = file_path.with_suffix(".meta.json") + with meta_path.open("w") as metafile: + rapidjson.dump( + { + CURRENT_TEST_STRATEGY: { + "run_id": "6e542efc8d5e62cef6e5be0ffbc29be81a6e751d", + "backtest_start_time": 1690176003, + } + }, + metafile, + ) def read_metadata(): - with meta_path.open('r') as metafile: + with meta_path.open("r") as metafile: return rapidjson.load(metafile) rc = client_patch(client, f"{BASE_URI}/backtest/history/randomFile.json") assert_response(rc, 503) - ftbot.config['user_data_dir'] = tmp_path - ftbot.config['runmode'] = RunMode.WEBSERVER + ftbot.config["user_data_dir"] = tmp_path + ftbot.config["runmode"] = RunMode.WEBSERVER - rc = client_patch(client, f"{BASE_URI}/backtest/history/randomFile.json", { - "strategy": CURRENT_TEST_STRATEGY, - }) + rc = client_patch( + client, + f"{BASE_URI}/backtest/history/randomFile.json", + { + "strategy": CURRENT_TEST_STRATEGY, + }, + ) assert rc.status_code == 404 # Nonexisting strategy - rc = client_patch(client, f"{BASE_URI}/backtest/history/{file_path.name}", { - "strategy": f"{CURRENT_TEST_STRATEGY}xxx", - }) + rc = client_patch( + client, + f"{BASE_URI}/backtest/history/{file_path.name}", + { + "strategy": f"{CURRENT_TEST_STRATEGY}xxx", + }, + ) assert rc.status_code == 400 - assert rc.json()['detail'] == 'Strategy not in metadata.' + assert rc.json()["detail"] == "Strategy not in metadata." # no Notes - rc = client_patch(client, f"{BASE_URI}/backtest/history/{file_path.name}", { - "strategy": CURRENT_TEST_STRATEGY, - }) + rc = client_patch( + client, + f"{BASE_URI}/backtest/history/{file_path.name}", + { + "strategy": CURRENT_TEST_STRATEGY, + }, + ) assert rc.status_code == 200 res = rc.json() assert isinstance(res, list) assert len(res) == 1 - assert res[0]['strategy'] == CURRENT_TEST_STRATEGY - assert res[0]['notes'] == '' + assert res[0]["strategy"] == CURRENT_TEST_STRATEGY + assert res[0]["notes"] == "" fileres = read_metadata() - assert fileres[CURRENT_TEST_STRATEGY]['run_id'] == res[0]['run_id'] - assert fileres[CURRENT_TEST_STRATEGY]['notes'] == '' + assert fileres[CURRENT_TEST_STRATEGY]["run_id"] == res[0]["run_id"] + assert fileres[CURRENT_TEST_STRATEGY]["notes"] == "" - rc = client_patch(client, f"{BASE_URI}/backtest/history/{file_path.name}", { - "strategy": CURRENT_TEST_STRATEGY, - "notes": "FooBar", - }) + rc = client_patch( + client, + f"{BASE_URI}/backtest/history/{file_path.name}", + { + "strategy": CURRENT_TEST_STRATEGY, + "notes": "FooBar", + }, + ) assert rc.status_code == 200 res = rc.json() assert isinstance(res, list) assert len(res) == 1 - assert res[0]['strategy'] == CURRENT_TEST_STRATEGY - assert res[0]['notes'] == 'FooBar' + assert res[0]["strategy"] == CURRENT_TEST_STRATEGY + assert res[0]["notes"] == "FooBar" fileres = read_metadata() - assert fileres[CURRENT_TEST_STRATEGY]['run_id'] == res[0]['run_id'] - assert fileres[CURRENT_TEST_STRATEGY]['notes'] == 'FooBar' + assert fileres[CURRENT_TEST_STRATEGY]["run_id"] == res[0]["run_id"] + assert fileres[CURRENT_TEST_STRATEGY]["notes"] == "FooBar" def test_api_patch_backtest_market_change(botclient, tmp_path: Path): @@ -2355,20 +2693,22 @@ def test_api_patch_backtest_market_change(botclient, tmp_path: Path): bt_results_base = tmp_path / "backtest_results" bt_results_base.mkdir() file_path = bt_results_base / "test_22_market_change.feather" - df = pd.DataFrame({ - 'date': ['2018-01-01T00:00:00Z', '2018-01-01T00:05:00Z'], - 'count': [2, 4], - 'mean': [2555, 2556], - 'rel_mean': [0, 0.022], - }) - df['date'] = pd.to_datetime(df['date']) - df.to_feather(file_path, compression_level=9, compression='lz4') + df = pd.DataFrame( + { + "date": ["2018-01-01T00:00:00Z", "2018-01-01T00:05:00Z"], + "count": [2, 4], + "mean": [2555, 2556], + "rel_mean": [0, 0.022], + } + ) + df["date"] = pd.to_datetime(df["date"]) + df.to_feather(file_path, compression_level=9, compression="lz4") # Nonexisting file rc = client_get(client, f"{BASE_URI}/backtest/history/randomFile.json/market_change") assert_response(rc, 503) - ftbot.config['user_data_dir'] = tmp_path - ftbot.config['runmode'] = RunMode.WEBSERVER + ftbot.config["user_data_dir"] = tmp_path + ftbot.config["runmode"] = RunMode.WEBSERVER rc = client_get(client, f"{BASE_URI}/backtest/history/randomFile.json/market_change") assert_response(rc, 404) @@ -2376,11 +2716,11 @@ def test_api_patch_backtest_market_change(botclient, tmp_path: Path): rc = client_get(client, f"{BASE_URI}/backtest/history/test_22/market_change") assert_response(rc, 200) result = rc.json() - assert result['length'] == 2 - assert result['columns'] == ['date', 'count', 'mean', 'rel_mean', '__date_ts'] - assert result['data'] == [ - ['2018-01-01T00:00:00Z', 2, 2555, 0.0, 1514764800000], - ['2018-01-01T00:05:00Z', 4, 2556, 0.022, 1514765100000] + assert result["length"] == 2 + assert result["columns"] == ["date", "count", "mean", "rel_mean", "__date_ts"] + assert result["data"] == [ + ["2018-01-01T00:00:00Z", 2, 2555, 0.0, 1514764800000], + ["2018-01-01T00:05:00Z", 4, 2556, 0.022, 1514765100000], ] @@ -2399,17 +2739,17 @@ def test_api_ws_subscribe(botclient, mocker): _ftbot, client = botclient ws_url = f"/api/v1/message/ws?token={_TEST_WS_TOKEN}" - sub_mock = mocker.patch('freqtrade.rpc.api_server.ws.WebSocketChannel.set_subscriptions') + sub_mock = mocker.patch("freqtrade.rpc.api_server.ws.WebSocketChannel.set_subscriptions") with client.websocket_connect(ws_url) as ws: - ws.send_json({'type': 'subscribe', 'data': ['whitelist']}) + ws.send_json({"type": "subscribe", "data": ["whitelist"]}) time.sleep(0.2) # Check call count is now 1 as we sent a valid subscribe request assert sub_mock.call_count == 1 with client.websocket_connect(ws_url) as ws: - ws.send_json({'type': 'subscribe', 'data': 'whitelist'}) + ws.send_json({"type": "subscribe", "data": "whitelist"}) time.sleep(0.2) # Call count hasn't changed as the subscribe request was invalid @@ -2428,7 +2768,7 @@ def test_api_ws_requests(botclient, caplog): response = ws.receive_json() assert log_has_re(r"Request of type whitelist from.+", caplog) - assert response['type'] == "whitelist" + assert response["type"] == "whitelist" # Test analyzed_df request with client.websocket_connect(ws_url) as ws: @@ -2436,7 +2776,7 @@ def test_api_ws_requests(botclient, caplog): response = ws.receive_json() assert log_has_re(r"Request of type analyzed_df from.+", caplog) - assert response['type'] == "analyzed_df" + assert response["type"] == "analyzed_df" caplog.clear() # Test analyzed_df request with data @@ -2445,23 +2785,28 @@ def test_api_ws_requests(botclient, caplog): response = ws.receive_json() assert log_has_re(r"Request of type analyzed_df from.+", caplog) - assert response['type'] == "analyzed_df" + assert response["type"] == "analyzed_df" def test_api_ws_send_msg(default_conf, mocker, caplog): try: caplog.set_level(logging.DEBUG) - default_conf.update({"api_server": {"enabled": True, - "listen_ip_address": "127.0.0.1", - "listen_port": 8080, - "CORS_origins": ['http://example.com'], - "username": _TEST_USER, - "password": _TEST_PASS, - "ws_token": _TEST_WS_TOKEN - }}) - mocker.patch('freqtrade.rpc.telegram.Telegram._init') - mocker.patch('freqtrade.rpc.api_server.ApiServer.start_api') + default_conf.update( + { + "api_server": { + "enabled": True, + "listen_ip_address": "127.0.0.1", + "listen_port": 8080, + "CORS_origins": ["http://example.com"], + "username": _TEST_USER, + "password": _TEST_PASS, + "ws_token": _TEST_WS_TOKEN, + } + } + ) + mocker.patch("freqtrade.rpc.telegram.Telegram._init") + mocker.patch("freqtrade.rpc.api_server.ApiServer.start_api") apiserver = ApiServer(default_conf) apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf))) diff --git a/tests/rpc/test_rpc_emc.py b/tests/rpc/test_rpc_emc.py index 4cfa3e9db..678379e68 100644 --- a/tests/rpc/test_rpc_emc.py +++ b/tests/rpc/test_rpc_emc.py @@ -1,6 +1,7 @@ """ Unit test file for rpc/external_message_consumer.py """ + import asyncio import logging from datetime import datetime, timezone @@ -21,19 +22,16 @@ _TEST_WS_PORT = 9989 @pytest.fixture def patched_emc(default_conf, mocker): - default_conf.update({ - "external_message_consumer": { - "enabled": True, - "producers": [ - { - "name": "default", - "host": "null", - "port": 9891, - "ws_token": _TEST_WS_TOKEN - } - ] + default_conf.update( + { + "external_message_consumer": { + "enabled": True, + "producers": [ + {"name": "default", "host": "null", "port": 9891, "ws_token": _TEST_WS_TOKEN} + ], + } } - }) + ) dataprovider = DataProvider(default_conf, None, None, None) emc = ExternalMessageConsumer(default_conf, dataprovider) @@ -81,7 +79,7 @@ def test_emc_init(patched_emc): # Parametrize this? def test_emc_handle_producer_message(patched_emc, caplog, ohlcv_history): test_producer = {"name": "test", "url": "ws://test", "ws_token": "test"} - producer_name = test_producer['name'] + producer_name = test_producer["name"] invalid_msg = r"Invalid message .+" caplog.set_level(logging.DEBUG) @@ -92,7 +90,8 @@ def test_emc_handle_producer_message(patched_emc, caplog, ohlcv_history): assert log_has(f"Received message of type `whitelist` from `{producer_name}`", caplog) assert log_has( - f"Consumed message from `{producer_name}` of type `RPCMessageType.WHITELIST`", caplog) + f"Consumed message from `{producer_name}` of type `RPCMessageType.WHITELIST`", caplog + ) # Test handle analyzed_df single candle message df_message = { @@ -100,8 +99,8 @@ def test_emc_handle_producer_message(patched_emc, caplog, ohlcv_history): "data": { "key": ("BTC/USDT", "5m", "spot"), "df": ohlcv_history, - "la": datetime.now(timezone.utc) - } + "la": datetime.now(timezone.utc), + }, } patched_emc.handle_producer_message(test_producer, df_message) @@ -124,11 +123,7 @@ def test_emc_handle_producer_message(patched_emc, caplog, ohlcv_history): malformed_message = { "type": "analyzed_df", - "data": { - "key": "BTC/USDT", - "df": ohlcv_history, - "la": datetime.now(timezone.utc) - } + "data": {"key": "BTC/USDT", "df": ohlcv_history, "la": datetime.now(timezone.utc)}, } patched_emc.handle_producer_message(test_producer, malformed_message) @@ -138,13 +133,13 @@ def test_emc_handle_producer_message(patched_emc, caplog, ohlcv_history): # Empty dataframe malformed_message = { - "type": "analyzed_df", - "data": { - "key": ("BTC/USDT", "5m", "spot"), - "df": ohlcv_history.loc[ohlcv_history['open'] < 0], - "la": datetime.now(timezone.utc) - } - } + "type": "analyzed_df", + "data": { + "key": ("BTC/USDT", "5m", "spot"), + "df": ohlcv_history.loc[ohlcv_history["open"] < 0], + "la": datetime.now(timezone.utc), + }, + } patched_emc.handle_producer_message(test_producer, malformed_message) assert log_has(f"Received message of type `analyzed_df` from `{producer_name}`", caplog) @@ -166,29 +161,32 @@ def test_emc_handle_producer_message(patched_emc, caplog, ohlcv_history): async def test_emc_create_connection_success(default_conf, caplog, mocker): - default_conf.update({ - "external_message_consumer": { - "enabled": True, - "producers": [ - { - "name": "default", - "host": _TEST_WS_HOST, - "port": _TEST_WS_PORT, - "ws_token": _TEST_WS_TOKEN - } - ], - "wait_timeout": 60, - "ping_timeout": 60, - "sleep_timeout": 60 + default_conf.update( + { + "external_message_consumer": { + "enabled": True, + "producers": [ + { + "name": "default", + "host": _TEST_WS_HOST, + "port": _TEST_WS_PORT, + "ws_token": _TEST_WS_TOKEN, + } + ], + "wait_timeout": 60, + "ping_timeout": 60, + "sleep_timeout": 60, + } } - }) + ) - mocker.patch('freqtrade.rpc.external_message_consumer.ExternalMessageConsumer.start', - MagicMock()) + mocker.patch( + "freqtrade.rpc.external_message_consumer.ExternalMessageConsumer.start", MagicMock() + ) dp = DataProvider(default_conf, None, None, None) emc = ExternalMessageConsumer(default_conf, dp) - test_producer = default_conf['external_message_consumer']['producers'][0] + test_producer = default_conf["external_message_consumer"]["producers"][0] lock = asyncio.Lock() emc._running = True @@ -205,27 +203,27 @@ async def test_emc_create_connection_success(default_conf, caplog, mocker): emc.shutdown() -@pytest.mark.parametrize('host,port', [ - (_TEST_WS_HOST, -1), - ("10000.1241..2121/", _TEST_WS_PORT), -]) +@pytest.mark.parametrize( + "host,port", + [ + (_TEST_WS_HOST, -1), + ("10000.1241..2121/", _TEST_WS_PORT), + ], +) async def test_emc_create_connection_invalid_url(default_conf, caplog, mocker, host, port): - default_conf.update({ - "external_message_consumer": { - "enabled": True, - "producers": [ - { - "name": "default", - "host": host, - "port": port, - "ws_token": _TEST_WS_TOKEN - } - ], - "wait_timeout": 60, - "ping_timeout": 60, - "sleep_timeout": 60 + default_conf.update( + { + "external_message_consumer": { + "enabled": True, + "producers": [ + {"name": "default", "host": host, "port": port, "ws_token": _TEST_WS_TOKEN} + ], + "wait_timeout": 60, + "ping_timeout": 60, + "sleep_timeout": 60, + } } - }) + ) dp = DataProvider(default_conf, None, None, None) # Handle start explicitly to avoid messing with threading in tests @@ -242,25 +240,27 @@ async def test_emc_create_connection_invalid_url(default_conf, caplog, mocker, h async def test_emc_create_connection_error(default_conf, caplog, mocker): - default_conf.update({ - "external_message_consumer": { - "enabled": True, - "producers": [ - { - "name": "default", - "host": _TEST_WS_HOST, - "port": _TEST_WS_PORT, - "ws_token": _TEST_WS_TOKEN - } - ], - "wait_timeout": 60, - "ping_timeout": 60, - "sleep_timeout": 60 + default_conf.update( + { + "external_message_consumer": { + "enabled": True, + "producers": [ + { + "name": "default", + "host": _TEST_WS_HOST, + "port": _TEST_WS_PORT, + "ws_token": _TEST_WS_TOKEN, + } + ], + "wait_timeout": 60, + "ping_timeout": 60, + "sleep_timeout": 60, + } } - }) + ) # Test unexpected error - mocker.patch('websockets.connect', side_effect=RuntimeError) + mocker.patch("websockets.connect", side_effect=RuntimeError) dp = DataProvider(default_conf, None, None, None) emc = ExternalMessageConsumer(default_conf, dp) @@ -275,28 +275,31 @@ async def test_emc_create_connection_error(default_conf, caplog, mocker): async def test_emc_receive_messages_valid(default_conf, caplog, mocker): caplog.set_level(logging.DEBUG) - default_conf.update({ - "external_message_consumer": { - "enabled": True, - "producers": [ - { - "name": "default", - "host": _TEST_WS_HOST, - "port": _TEST_WS_PORT, - "ws_token": _TEST_WS_TOKEN - } - ], - "wait_timeout": 1, - "ping_timeout": 60, - "sleep_time": 60 + default_conf.update( + { + "external_message_consumer": { + "enabled": True, + "producers": [ + { + "name": "default", + "host": _TEST_WS_HOST, + "port": _TEST_WS_PORT, + "ws_token": _TEST_WS_TOKEN, + } + ], + "wait_timeout": 1, + "ping_timeout": 60, + "sleep_time": 60, + } } - }) + ) - mocker.patch('freqtrade.rpc.external_message_consumer.ExternalMessageConsumer.start', - MagicMock()) + mocker.patch( + "freqtrade.rpc.external_message_consumer.ExternalMessageConsumer.start", MagicMock() + ) lock = asyncio.Lock() - test_producer = default_conf['external_message_consumer']['producers'][0] + test_producer = default_conf["external_message_consumer"]["producers"][0] dp = DataProvider(default_conf, None, None, None) emc = ExternalMessageConsumer(default_conf, dp) @@ -319,28 +322,31 @@ async def test_emc_receive_messages_valid(default_conf, caplog, mocker): async def test_emc_receive_messages_invalid(default_conf, caplog, mocker): - default_conf.update({ - "external_message_consumer": { - "enabled": True, - "producers": [ - { - "name": "default", - "host": _TEST_WS_HOST, - "port": _TEST_WS_PORT, - "ws_token": _TEST_WS_TOKEN - } - ], - "wait_timeout": 1, - "ping_timeout": 60, - "sleep_time": 60 + default_conf.update( + { + "external_message_consumer": { + "enabled": True, + "producers": [ + { + "name": "default", + "host": _TEST_WS_HOST, + "port": _TEST_WS_PORT, + "ws_token": _TEST_WS_TOKEN, + } + ], + "wait_timeout": 1, + "ping_timeout": 60, + "sleep_time": 60, + } } - }) + ) - mocker.patch('freqtrade.rpc.external_message_consumer.ExternalMessageConsumer.start', - MagicMock()) + mocker.patch( + "freqtrade.rpc.external_message_consumer.ExternalMessageConsumer.start", MagicMock() + ) lock = asyncio.Lock() - test_producer = default_conf['external_message_consumer']['producers'][0] + test_producer = default_conf["external_message_consumer"]["producers"][0] dp = DataProvider(default_conf, None, None, None) emc = ExternalMessageConsumer(default_conf, dp) @@ -363,28 +369,31 @@ async def test_emc_receive_messages_invalid(default_conf, caplog, mocker): async def test_emc_receive_messages_timeout(default_conf, caplog, mocker): - default_conf.update({ - "external_message_consumer": { - "enabled": True, - "producers": [ - { - "name": "default", - "host": _TEST_WS_HOST, - "port": _TEST_WS_PORT, - "ws_token": _TEST_WS_TOKEN - } - ], - "wait_timeout": 0.1, - "ping_timeout": 1, - "sleep_time": 1 + default_conf.update( + { + "external_message_consumer": { + "enabled": True, + "producers": [ + { + "name": "default", + "host": _TEST_WS_HOST, + "port": _TEST_WS_PORT, + "ws_token": _TEST_WS_TOKEN, + } + ], + "wait_timeout": 0.1, + "ping_timeout": 1, + "sleep_time": 1, + } } - }) + ) - mocker.patch('freqtrade.rpc.external_message_consumer.ExternalMessageConsumer.start', - MagicMock()) + mocker.patch( + "freqtrade.rpc.external_message_consumer.ExternalMessageConsumer.start", MagicMock() + ) lock = asyncio.Lock() - test_producer = default_conf['external_message_consumer']['producers'][0] + test_producer = default_conf["external_message_consumer"]["producers"][0] dp = DataProvider(default_conf, None, None, None) emc = ExternalMessageConsumer(default_conf, dp) @@ -411,28 +420,31 @@ async def test_emc_receive_messages_timeout(default_conf, caplog, mocker): async def test_emc_receive_messages_handle_error(default_conf, caplog, mocker): - default_conf.update({ - "external_message_consumer": { - "enabled": True, - "producers": [ - { - "name": "default", - "host": _TEST_WS_HOST, - "port": _TEST_WS_PORT, - "ws_token": _TEST_WS_TOKEN - } - ], - "wait_timeout": 1, - "ping_timeout": 1, - "sleep_time": 1 + default_conf.update( + { + "external_message_consumer": { + "enabled": True, + "producers": [ + { + "name": "default", + "host": _TEST_WS_HOST, + "port": _TEST_WS_PORT, + "ws_token": _TEST_WS_TOKEN, + } + ], + "wait_timeout": 1, + "ping_timeout": 1, + "sleep_time": 1, + } } - }) + ) - mocker.patch('freqtrade.rpc.external_message_consumer.ExternalMessageConsumer.start', - MagicMock()) + mocker.patch( + "freqtrade.rpc.external_message_consumer.ExternalMessageConsumer.start", MagicMock() + ) lock = asyncio.Lock() - test_producer = default_conf['external_message_consumer']['producers'][0] + test_producer = default_conf["external_message_consumer"]["producers"][0] dp = DataProvider(default_conf, None, None, None) emc = ExternalMessageConsumer(default_conf, dp) diff --git a/tests/rpc/test_rpc_manager.py b/tests/rpc/test_rpc_manager.py index f0bb72fc9..2792fd082 100644 --- a/tests/rpc/test_rpc_manager.py +++ b/tests/rpc/test_rpc_manager.py @@ -11,7 +11,7 @@ from tests.conftest import get_patched_freqtradebot, log_has def test__init__(mocker, default_conf) -> None: - default_conf['telegram']['enabled'] = False + default_conf["telegram"]["enabled"] = False rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) assert rpc_manager.registered_modules == [] @@ -19,97 +19,91 @@ def test__init__(mocker, default_conf) -> None: def test_init_telegram_disabled(mocker, default_conf, caplog) -> None: caplog.set_level(logging.DEBUG) - default_conf['telegram']['enabled'] = False + default_conf["telegram"]["enabled"] = False rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) - assert not log_has('Enabling rpc.telegram ...', caplog) + assert not log_has("Enabling rpc.telegram ...", caplog) assert rpc_manager.registered_modules == [] def test_init_telegram_enabled(mocker, default_conf, caplog) -> None: caplog.set_level(logging.DEBUG) - default_conf['telegram']['enabled'] = True - mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) + default_conf["telegram"]["enabled"] = True + mocker.patch("freqtrade.rpc.telegram.Telegram._init", MagicMock()) rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) - assert log_has('Enabling rpc.telegram ...', caplog) + assert log_has("Enabling rpc.telegram ...", caplog) len_modules = len(rpc_manager.registered_modules) assert len_modules == 1 - assert 'telegram' in [mod.name for mod in rpc_manager.registered_modules] + assert "telegram" in [mod.name for mod in rpc_manager.registered_modules] def test_cleanup_telegram_disabled(mocker, default_conf, caplog) -> None: caplog.set_level(logging.DEBUG) - telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.cleanup', MagicMock()) - default_conf['telegram']['enabled'] = False + telegram_mock = mocker.patch("freqtrade.rpc.telegram.Telegram.cleanup", MagicMock()) + default_conf["telegram"]["enabled"] = False freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc_manager = RPCManager(freqtradebot) rpc_manager.cleanup() - assert not log_has('Cleaning up rpc.telegram ...', caplog) + assert not log_has("Cleaning up rpc.telegram ...", caplog) assert telegram_mock.call_count == 0 def test_cleanup_telegram_enabled(mocker, default_conf, caplog) -> None: caplog.set_level(logging.DEBUG) - default_conf['telegram']['enabled'] = True - mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) - telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.cleanup', MagicMock()) + default_conf["telegram"]["enabled"] = True + mocker.patch("freqtrade.rpc.telegram.Telegram._init", MagicMock()) + telegram_mock = mocker.patch("freqtrade.rpc.telegram.Telegram.cleanup", MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc_manager = RPCManager(freqtradebot) # Check we have Telegram as a registered modules - assert 'telegram' in [mod.name for mod in rpc_manager.registered_modules] + assert "telegram" in [mod.name for mod in rpc_manager.registered_modules] rpc_manager.cleanup() - assert log_has('Cleaning up rpc.telegram ...', caplog) - assert 'telegram' not in [mod.name for mod in rpc_manager.registered_modules] + assert log_has("Cleaning up rpc.telegram ...", caplog) + assert "telegram" not in [mod.name for mod in rpc_manager.registered_modules] assert telegram_mock.call_count == 1 def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None: - telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) - default_conf['telegram']['enabled'] = False + telegram_mock = mocker.patch("freqtrade.rpc.telegram.Telegram.send_msg", MagicMock()) + default_conf["telegram"]["enabled"] = False freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc_manager = RPCManager(freqtradebot) - rpc_manager.send_msg({ - 'type': RPCMessageType.STATUS, - 'status': 'test' - }) + rpc_manager.send_msg({"type": RPCMessageType.STATUS, "status": "test"}) assert log_has("Sending rpc message: {'type': status, 'status': 'test'}", caplog) assert telegram_mock.call_count == 0 def test_send_msg_telegram_error(mocker, default_conf, caplog) -> None: - mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) - mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', side_effect=ValueError()) - default_conf['telegram']['enabled'] = True + mocker.patch("freqtrade.rpc.telegram.Telegram._init", MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram.send_msg", side_effect=ValueError()) + default_conf["telegram"]["enabled"] = True freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc_manager = RPCManager(freqtradebot) - rpc_manager.send_msg({ - 'type': RPCMessageType.STATUS, - 'status': 'test' - }) + rpc_manager.send_msg({"type": RPCMessageType.STATUS, "status": "test"}) assert log_has("Sending rpc message: {'type': status, 'status': 'test'}", caplog) assert log_has("Exception occurred within RPC module telegram", caplog) def test_process_msg_queue(mocker, default_conf, caplog) -> None: - telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg') - default_conf['telegram']['enabled'] = True - default_conf['telegram']['allow_custom_messages'] = True - mocker.patch('freqtrade.rpc.telegram.Telegram._init') + telegram_mock = mocker.patch("freqtrade.rpc.telegram.Telegram.send_msg") + default_conf["telegram"]["enabled"] = True + default_conf["telegram"]["allow_custom_messages"] = True + mocker.patch("freqtrade.rpc.telegram.Telegram._init") freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc_manager = RPCManager(freqtradebot) queue = deque() - queue.append('Test message') - queue.append('Test message 2') + queue.append("Test message") + queue.append("Test message 2") rpc_manager.process_msg_queue(queue) assert log_has("Sending rpc strategy_msg: Test message", caplog) @@ -118,15 +112,12 @@ def test_process_msg_queue(mocker, default_conf, caplog) -> None: def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None: - default_conf['telegram']['enabled'] = True - telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg') - mocker.patch('freqtrade.rpc.telegram.Telegram._init') + default_conf["telegram"]["enabled"] = True + telegram_mock = mocker.patch("freqtrade.rpc.telegram.Telegram.send_msg") + mocker.patch("freqtrade.rpc.telegram.Telegram._init") freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc_manager = RPCManager(freqtradebot) - rpc_manager.send_msg({ - 'type': RPCMessageType.STATUS, - 'status': 'test' - }) + rpc_manager.send_msg({"type": RPCMessageType.STATUS, "status": "test"}) assert log_has("Sending rpc message: {'type': status, 'status': 'test'}", caplog) assert telegram_mock.call_count == 1 @@ -134,76 +125,73 @@ def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None: def test_init_webhook_disabled(mocker, default_conf, caplog) -> None: caplog.set_level(logging.DEBUG) - default_conf['telegram']['enabled'] = False - default_conf['webhook'] = {'enabled': False} + default_conf["telegram"]["enabled"] = False + default_conf["webhook"] = {"enabled": False} rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) - assert not log_has('Enabling rpc.webhook ...', caplog) + assert not log_has("Enabling rpc.webhook ...", caplog) assert rpc_manager.registered_modules == [] def test_init_webhook_enabled(mocker, default_conf, caplog) -> None: caplog.set_level(logging.DEBUG) - default_conf['telegram']['enabled'] = False - default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"} + default_conf["telegram"]["enabled"] = False + default_conf["webhook"] = {"enabled": True, "url": "https://DEADBEEF.com"} rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) - assert log_has('Enabling rpc.webhook ...', caplog) + assert log_has("Enabling rpc.webhook ...", caplog) assert len(rpc_manager.registered_modules) == 1 - assert 'webhook' in [mod.name for mod in rpc_manager.registered_modules] + assert "webhook" in [mod.name for mod in rpc_manager.registered_modules] def test_send_msg_webhook_CustomMessagetype(mocker, default_conf, caplog) -> None: caplog.set_level(logging.DEBUG) - default_conf['telegram']['enabled'] = False - default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"} - mocker.patch('freqtrade.rpc.webhook.Webhook.send_msg', - MagicMock(side_effect=NotImplementedError)) + default_conf["telegram"]["enabled"] = False + default_conf["webhook"] = {"enabled": True, "url": "https://DEADBEEF.com"} + mocker.patch( + "freqtrade.rpc.webhook.Webhook.send_msg", MagicMock(side_effect=NotImplementedError) + ) rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) - assert 'webhook' in [mod.name for mod in rpc_manager.registered_modules] - rpc_manager.send_msg({'type': RPCMessageType.STARTUP, - 'status': 'TestMessage'}) - assert log_has( - "Message type 'startup' not implemented by handler webhook.", - caplog) + assert "webhook" in [mod.name for mod in rpc_manager.registered_modules] + rpc_manager.send_msg({"type": RPCMessageType.STARTUP, "status": "TestMessage"}) + assert log_has("Message type 'startup' not implemented by handler webhook.", caplog) def test_startupmessages_telegram_enabled(mocker, default_conf) -> None: - default_conf['telegram']['enabled'] = True - telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) - mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) + default_conf["telegram"]["enabled"] = True + telegram_mock = mocker.patch("freqtrade.rpc.telegram.Telegram.send_msg", MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram._init", MagicMock()) freqtradebot = get_patched_freqtradebot(mocker, default_conf) rpc_manager = RPCManager(freqtradebot) rpc_manager.startup_messages(default_conf, freqtradebot.pairlists, freqtradebot.protections) assert telegram_mock.call_count == 3 - assert "*Exchange:* `binance`" in telegram_mock.call_args_list[1][0][0]['status'] + assert "*Exchange:* `binance`" in telegram_mock.call_args_list[1][0][0]["status"] telegram_mock.reset_mock() - default_conf['dry_run'] = True - default_conf['whitelist'] = {'method': 'VolumePairList', - 'config': {'number_assets': 20} - } - default_conf['protections'] = [{"method": "StoplossGuard", - "lookback_period": 60, "trade_limit": 2, "stop_duration": 60}] + default_conf["dry_run"] = True + default_conf["whitelist"] = {"method": "VolumePairList", "config": {"number_assets": 20}} + default_conf["protections"] = [ + {"method": "StoplossGuard", "lookback_period": 60, "trade_limit": 2, "stop_duration": 60} + ] freqtradebot = get_patched_freqtradebot(mocker, default_conf) - rpc_manager.startup_messages(default_conf, freqtradebot.pairlists, freqtradebot.protections) + rpc_manager.startup_messages(default_conf, freqtradebot.pairlists, freqtradebot.protections) assert telegram_mock.call_count == 4 - assert "Dry run is enabled." in telegram_mock.call_args_list[0][0][0]['status'] - assert 'StoplossGuard' in telegram_mock.call_args_list[-1][0][0]['status'] + assert "Dry run is enabled." in telegram_mock.call_args_list[0][0][0]["status"] + assert "StoplossGuard" in telegram_mock.call_args_list[-1][0][0]["status"] def test_init_apiserver_disabled(mocker, default_conf, caplog) -> None: caplog.set_level(logging.DEBUG) run_mock = MagicMock() - mocker.patch('freqtrade.rpc.api_server.ApiServer.start_api', run_mock) - default_conf['telegram']['enabled'] = False + mocker.patch("freqtrade.rpc.api_server.ApiServer.start_api", run_mock) + default_conf["telegram"]["enabled"] = False rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) - assert not log_has('Enabling rpc.api_server', caplog) + assert not log_has("Enabling rpc.api_server", caplog) assert rpc_manager.registered_modules == [] assert run_mock.call_count == 0 @@ -211,21 +199,22 @@ def test_init_apiserver_disabled(mocker, default_conf, caplog) -> None: def test_init_apiserver_enabled(mocker, default_conf, caplog) -> None: caplog.set_level(logging.DEBUG) run_mock = MagicMock() - mocker.patch('freqtrade.rpc.api_server.ApiServer.start_api', run_mock) + mocker.patch("freqtrade.rpc.api_server.ApiServer.start_api", run_mock) default_conf["telegram"]["enabled"] = False - default_conf["api_server"] = {"enabled": True, - "listen_ip_address": "127.0.0.1", - "listen_port": 8080, - "username": "TestUser", - "password": "TestPass", - } + default_conf["api_server"] = { + "enabled": True, + "listen_ip_address": "127.0.0.1", + "listen_port": 8080, + "username": "TestUser", + "password": "TestPass", + } rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf)) # Sleep to allow the thread to start time.sleep(0.5) - assert log_has('Enabling rpc.api_server', caplog) + assert log_has("Enabling rpc.api_server", caplog) assert len(rpc_manager.registered_modules) == 1 - assert 'apiserver' in [mod.name for mod in rpc_manager.registered_modules] + assert "apiserver" in [mod.name for mod in rpc_manager.registered_modules] assert run_mock.call_count == 1 ApiServer.shutdown() diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 5d339f71c..3063e644b 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -55,13 +55,13 @@ from tests.conftest import ( @pytest.fixture(autouse=True) def mock_exchange_loop(mocker): - mocker.patch('freqtrade.exchange.exchange.Exchange._init_async_loop') + mocker.patch("freqtrade.exchange.exchange.Exchange._init_async_loop") @pytest.fixture def default_conf(default_conf) -> dict: # Telegram is enabled by default - default_conf['telegram']['enabled'] = True + default_conf["telegram"]["enabled"] = True return default_conf @@ -81,6 +81,7 @@ def patch_eventloop_threading(telegrambot): telegrambot._loop = asyncio.new_event_loop() is_init = True telegrambot._loop.run_forever() + x = threading.Thread(target=thread_fuck, daemon=True) x.start() while not is_init: @@ -94,7 +95,7 @@ class DummyCls(Telegram): def __init__(self, rpc: RPC, config) -> None: super().__init__(rpc, config) - self.state = {'called': False} + self.state = {"called": False} def _init(self): pass @@ -104,21 +105,21 @@ class DummyCls(Telegram): """ Fake method that only change the state of the object """ - self.state['called'] = True + self.state["called"] = True @authorized_only async def dummy_exception(self, *args, **kwargs) -> None: """ Fake method that throw an exception """ - raise Exception('test') + raise Exception("test") def get_telegram_testobject(mocker, default_conf, mock=True, ftbot=None): msg_mock = AsyncMock() if mock: mocker.patch.multiple( - 'freqtrade.rpc.telegram.Telegram', + "freqtrade.rpc.telegram.Telegram", _init=MagicMock(), _send_msg=msg_mock, _start_thread=MagicMock(), @@ -134,7 +135,7 @@ def get_telegram_testobject(mocker, default_conf, mock=True, ftbot=None): def test_telegram__init__(default_conf, mocker) -> None: - mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram._init", MagicMock()) telegram, _, _ = get_telegram_testobject(mocker, default_conf) assert telegram._config == default_conf @@ -142,9 +143,9 @@ def test_telegram__init__(default_conf, mocker) -> None: def test_telegram_init(default_conf, mocker, caplog) -> None: app_mock = MagicMock() - mocker.patch('freqtrade.rpc.telegram.Telegram._start_thread', MagicMock()) - mocker.patch('freqtrade.rpc.telegram.Telegram._init_telegram_app', return_value=app_mock) - mocker.patch('freqtrade.rpc.telegram.Telegram._startup_telegram', AsyncMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram._start_thread", MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram._init_telegram_app", return_value=app_mock) + mocker.patch("freqtrade.rpc.telegram.Telegram._startup_telegram", AsyncMock()) telegram, _, _ = get_telegram_testobject(mocker, default_conf, mock=False) telegram._init() @@ -154,18 +155,20 @@ def test_telegram_init(default_conf, mocker, caplog) -> None: assert app_mock.add_handler.call_count > 0 # assert start_polling.start_polling.call_count == 1 - message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], " - "['balance'], ['start'], ['stop'], " - "['forceexit', 'forcesell', 'fx'], ['forcebuy', 'forcelong'], ['forceshort'], " - "['reload_trade'], ['trades'], ['delete'], ['cancel_open_order', 'coo'], " - "['performance'], ['buys', 'entries'], ['exits', 'sells'], ['mix_tags'], " - "['stats'], ['daily'], ['weekly'], ['monthly'], " - "['count'], ['locks'], ['delete_locks', 'unlock'], " - "['reload_conf', 'reload_config'], ['show_conf', 'show_config'], " - "['stopbuy', 'stopentry'], ['whitelist'], ['blacklist'], " - "['bl_delete', 'blacklist_delete'], " - "['logs'], ['edge'], ['health'], ['help'], ['version'], ['marketdir'], " - "['order'], ['list_custom_data']]") + message_str = ( + "rpc.telegram is listening for following commands: [['status'], ['profit'], " + "['balance'], ['start'], ['stop'], " + "['forceexit', 'forcesell', 'fx'], ['forcebuy', 'forcelong'], ['forceshort'], " + "['reload_trade'], ['trades'], ['delete'], ['cancel_open_order', 'coo'], " + "['performance'], ['buys', 'entries'], ['exits', 'sells'], ['mix_tags'], " + "['stats'], ['daily'], ['weekly'], ['monthly'], " + "['count'], ['locks'], ['delete_locks', 'unlock'], " + "['reload_conf', 'reload_config'], ['show_conf', 'show_config'], " + "['stopbuy', 'stopentry'], ['whitelist'], ['blacklist'], " + "['bl_delete', 'blacklist_delete'], " + "['logs'], ['edge'], ['health'], ['help'], ['version'], ['marketdir'], " + "['order'], ['list_custom_data']]" + ) assert log_has(message_str, caplog) @@ -176,7 +179,7 @@ async def test_telegram_startup(default_conf, mocker) -> None: app_mock.start = AsyncMock() app_mock.updater.start_polling = AsyncMock() app_mock.updater.running = False - sleep_mock = mocker.patch('freqtrade.rpc.telegram.asyncio.sleep', AsyncMock()) + sleep_mock = mocker.patch("freqtrade.rpc.telegram.asyncio.sleep", AsyncMock()) telegram, _, _ = get_telegram_testobject(mocker, default_conf) telegram._app = app_mock @@ -187,7 +190,10 @@ async def test_telegram_startup(default_conf, mocker) -> None: assert sleep_mock.call_count == 1 -async def test_telegram_cleanup(default_conf, mocker, ) -> None: +async def test_telegram_cleanup( + default_conf, + mocker, +) -> None: app_mock = MagicMock() app_mock.stop = AsyncMock() app_mock.initialize = AsyncMock() @@ -210,43 +216,43 @@ async def test_telegram_cleanup(default_conf, mocker, ) -> None: async def test_authorized_only(default_conf, mocker, caplog, update) -> None: patch_exchange(mocker) caplog.set_level(logging.DEBUG) - default_conf['telegram']['enabled'] = False + default_conf["telegram"]["enabled"] = False bot = FreqtradeBot(default_conf) rpc = RPC(bot) dummy = DummyCls(rpc, default_conf) patch_get_signal(bot) await dummy.dummy_handler(update=update, context=MagicMock()) - assert dummy.state['called'] is True - assert log_has('Executing handler: dummy_handler for chat_id: 0', caplog) - assert not log_has('Rejected unauthorized message from: 0', caplog) - assert not log_has('Exception occurred within Telegram module', caplog) + assert dummy.state["called"] is True + assert log_has("Executing handler: dummy_handler for chat_id: 0", caplog) + assert not log_has("Rejected unauthorized message from: 0", caplog) + assert not log_has("Exception occurred within Telegram module", caplog) async def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None: patch_exchange(mocker) caplog.set_level(logging.DEBUG) - chat = Chat(0xdeadbeef, 0) + chat = Chat(0xDEADBEEF, 0) message = Message(randint(1, 100), datetime.now(timezone.utc), chat) update = Update(randint(1, 100), message=message) - default_conf['telegram']['enabled'] = False + default_conf["telegram"]["enabled"] = False bot = FreqtradeBot(default_conf) rpc = RPC(bot) dummy = DummyCls(rpc, default_conf) patch_get_signal(bot) await dummy.dummy_handler(update=update, context=MagicMock()) - assert dummy.state['called'] is False - assert not log_has('Executing handler: dummy_handler for chat_id: 3735928559', caplog) - assert log_has('Rejected unauthorized message from: 3735928559', caplog) - assert not log_has('Exception occurred within Telegram module', caplog) + assert dummy.state["called"] is False + assert not log_has("Executing handler: dummy_handler for chat_id: 3735928559", caplog) + assert log_has("Rejected unauthorized message from: 3735928559", caplog) + assert not log_has("Exception occurred within Telegram module", caplog) async def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None: patch_exchange(mocker) - default_conf['telegram']['enabled'] = False + default_conf["telegram"]["enabled"] = False bot = FreqtradeBot(default_conf) rpc = RPC(bot) @@ -254,54 +260,58 @@ async def test_authorized_only_exception(default_conf, mocker, caplog, update) - patch_get_signal(bot) await dummy.dummy_exception(update=update, context=MagicMock()) - assert dummy.state['called'] is False - assert not log_has('Executing handler: dummy_handler for chat_id: 0', caplog) - assert not log_has('Rejected unauthorized message from: 0', caplog) - assert log_has('Exception occurred within Telegram module', caplog) + assert dummy.state["called"] is False + assert not log_has("Executing handler: dummy_handler for chat_id: 0", caplog) + assert not log_has("Rejected unauthorized message from: 0", caplog) + assert log_has("Exception occurred within Telegram module", caplog) async def test_telegram_status(default_conf, update, mocker) -> None: - default_conf['telegram']['enabled'] = False + default_conf["telegram"]["enabled"] = False status_table = MagicMock() - mocker.patch('freqtrade.rpc.telegram.Telegram._status_table', status_table) + mocker.patch("freqtrade.rpc.telegram.Telegram._status_table", status_table) mocker.patch.multiple( - 'freqtrade.rpc.rpc.RPC', - _rpc_trade_status=MagicMock(return_value=[{ - 'trade_id': 1, - 'pair': 'ETH/BTC', - 'base_currency': 'ETH', - 'quote_currency': 'BTC', - 'open_date': dt_now(), - 'close_date': None, - 'open_rate': 1.099e-05, - 'close_rate': None, - 'current_rate': 1.098e-05, - 'amount': 90.99181074, - 'stake_amount': 90.99181074, - 'max_stake_amount': 90.99181074, - 'buy_tag': None, - 'enter_tag': None, - 'close_profit_ratio': None, - 'profit': -0.0059, - 'profit_ratio': -0.0059, - 'profit_abs': -0.225, - 'realized_profit': 0.0, - 'total_profit_abs': -0.225, - 'initial_stop_loss_abs': 1.098e-05, - 'stop_loss_abs': 1.099e-05, - 'exit_order_status': None, - 'initial_stop_loss_ratio': -0.0005, - 'stoploss_current_dist': 1e-08, - 'stoploss_current_dist_ratio': -0.0002, - 'stop_loss_ratio': -0.0001, - 'open_order': '(limit buy rem=0.00000000)', - 'is_open': True, - 'is_short': False, - 'filled_entry_orders': [], - 'orders': [] - }]), + "freqtrade.rpc.rpc.RPC", + _rpc_trade_status=MagicMock( + return_value=[ + { + "trade_id": 1, + "pair": "ETH/BTC", + "base_currency": "ETH", + "quote_currency": "BTC", + "open_date": dt_now(), + "close_date": None, + "open_rate": 1.099e-05, + "close_rate": None, + "current_rate": 1.098e-05, + "amount": 90.99181074, + "stake_amount": 90.99181074, + "max_stake_amount": 90.99181074, + "buy_tag": None, + "enter_tag": None, + "close_profit_ratio": None, + "profit": -0.0059, + "profit_ratio": -0.0059, + "profit_abs": -0.225, + "realized_profit": 0.0, + "total_profit_abs": -0.225, + "initial_stop_loss_abs": 1.098e-05, + "stop_loss_abs": 1.099e-05, + "exit_order_status": None, + "initial_stop_loss_ratio": -0.0005, + "stoploss_current_dist": 1e-08, + "stoploss_current_dist_ratio": -0.0002, + "stop_loss_ratio": -0.0001, + "open_order": "(limit buy rem=0.00000000)", + "is_open": True, + "is_short": False, + "filled_entry_orders": [], + "orders": [], + } + ] + ), ) telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) @@ -318,8 +328,8 @@ async def test_telegram_status(default_conf, update, mocker) -> None: @pytest.mark.usefixtures("init_persistence") async def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None: - default_conf['telegram']['enabled'] = False - default_conf['position_adjustment_enable'] = True + default_conf["telegram"]["enabled"] = False + default_conf["position_adjustment_enable"] = True mocker.patch.multiple( EXMS, fetch_order=MagicMock(return_value=None), @@ -333,25 +343,26 @@ async def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> trade = trades[3] # Average may be empty on some exchanges trade.orders[0].average = 0 - trade.orders.append(Order( - order_id='5412vbb', - ft_order_side='buy', - ft_pair=trade.pair, - ft_is_open=False, - ft_amount=trade.amount, - ft_price=trade.open_rate, - status="closed", - symbol=trade.pair, - order_type="market", - side="buy", - price=trade.open_rate * 0.95, - average=0, - filled=trade.amount, - remaining=0, - cost=trade.amount, - order_date=trade.open_date, - order_filled_date=trade.open_date, - ) + trade.orders.append( + Order( + order_id="5412vbb", + ft_order_side="buy", + ft_pair=trade.pair, + ft_is_open=False, + ft_amount=trade.amount, + ft_price=trade.open_rate, + status="closed", + symbol=trade.pair, + order_type="market", + side="buy", + price=trade.open_rate * 0.95, + average=0, + filled=trade.amount, + remaining=0, + cost=trade.amount, + order_date=trade.open_date, + order_filled_date=trade.open_date, + ) ) trade.recalc_trade_from_orders() Trade.commit() @@ -359,15 +370,15 @@ async def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> await telegram._status(update=update, context=MagicMock()) assert msg_mock.call_count == 4 msg = msg_mock.call_args_list[3][0][0] - assert re.search(r'Number of Entries.*2', msg) - assert re.search(r'Number of Exits.*1', msg) - assert re.search(r'Close Date:', msg) is None - assert re.search(r'Close Profit:', msg) is None + assert re.search(r"Number of Entries.*2", msg) + assert re.search(r"Number of Exits.*1", msg) + assert re.search(r"Close Date:", msg) is None + assert re.search(r"Close Profit:", msg) is None @pytest.mark.usefixtures("init_persistence") async def test_telegram_status_closed_trade(default_conf, update, mocker, fee) -> None: - default_conf['position_adjustment_enable'] = True + default_conf["position_adjustment_enable"] = True mocker.patch.multiple( EXMS, fetch_order=MagicMock(return_value=None), @@ -383,12 +394,12 @@ async def test_telegram_status_closed_trade(default_conf, update, mocker, fee) - await telegram._status(update=update, context=context) assert msg_mock.call_count == 1 msg = msg_mock.call_args_list[0][0][0] - assert re.search(r'Close Date:', msg) - assert re.search(r'Close Profit:', msg) + assert re.search(r"Close Date:", msg) + assert re.search(r"Close Profit:", msg) async def test_order_handle(default_conf, update, ticker, fee, mocker) -> None: - default_conf['max_open_trades'] = 3 + default_conf["max_open_trades"] = 3 mocker.patch.multiple( EXMS, fetch_ticker=ticker, @@ -397,7 +408,7 @@ async def test_order_handle(default_conf, update, ticker, fee, mocker) -> None: ) status_table = MagicMock() mocker.patch.multiple( - 'freqtrade.rpc.telegram.Telegram', + "freqtrade.rpc.telegram.Telegram", _status_table=status_table, ) @@ -411,7 +422,7 @@ async def test_order_handle(default_conf, update, ticker, fee, mocker) -> None: # Create some test data freqtradebot.enter_positions() - mocker.patch('freqtrade.rpc.telegram.MAX_MESSAGE_LENGTH', 500) + mocker.patch("freqtrade.rpc.telegram.MAX_MESSAGE_LENGTH", 500) msg_mock.reset_mock() context = MagicMock() @@ -422,10 +433,10 @@ async def test_order_handle(default_conf, update, ticker, fee, mocker) -> None: msg1 = msg_mock.call_args_list[0][0][0] - assert 'Order List for Trade #*`2`' in msg1 + assert "Order List for Trade #*`2`" in msg1 msg_mock.reset_mock() - mocker.patch('freqtrade.rpc.telegram.MAX_MESSAGE_LENGTH', 50) + mocker.patch("freqtrade.rpc.telegram.MAX_MESSAGE_LENGTH", 50) context = MagicMock() context.args = ["2"] await telegram._order(update=update, context=context) @@ -435,14 +446,14 @@ async def test_order_handle(default_conf, update, ticker, fee, mocker) -> None: msg1 = msg_mock.call_args_list[0][0][0] msg2 = msg_mock.call_args_list[1][0][0] - assert 'Order List for Trade #*`2`' in msg1 - assert '*Order List for Trade #*`2` - continued' in msg2 + assert "Order List for Trade #*`2`" in msg1 + assert "*Order List for Trade #*`2` - continued" in msg2 @pytest.mark.usefixtures("init_persistence") async def test_telegram_order_multi_entry(default_conf, update, mocker, fee) -> None: - default_conf['telegram']['enabled'] = False - default_conf['position_adjustment_enable'] = True + default_conf["telegram"]["enabled"] = False + default_conf["position_adjustment_enable"] = True mocker.patch.multiple( EXMS, fetch_order=MagicMock(return_value=None), @@ -456,25 +467,26 @@ async def test_telegram_order_multi_entry(default_conf, update, mocker, fee) -> trade = trades[3] # Average may be empty on some exchanges trade.orders[0].average = 0 - trade.orders.append(Order( - order_id='5412vbb', - ft_order_side='buy', - ft_pair=trade.pair, - ft_is_open=False, - ft_amount=trade.amount, - ft_price=trade.open_rate, - status="closed", - symbol=trade.pair, - order_type="market", - side="buy", - price=trade.open_rate * 0.95, - average=0, - filled=trade.amount, - remaining=0, - cost=trade.amount, - order_date=trade.open_date, - order_filled_date=trade.open_date, - ) + trade.orders.append( + Order( + order_id="5412vbb", + ft_order_side="buy", + ft_pair=trade.pair, + ft_is_open=False, + ft_amount=trade.amount, + ft_price=trade.open_rate, + status="closed", + symbol=trade.pair, + order_type="market", + side="buy", + price=trade.open_rate * 0.95, + average=0, + filled=trade.amount, + remaining=0, + cost=trade.amount, + order_date=trade.open_date, + order_filled_date=trade.open_date, + ) ) trade.recalc_trade_from_orders() Trade.commit() @@ -482,12 +494,12 @@ async def test_telegram_order_multi_entry(default_conf, update, mocker, fee) -> await telegram._order(update=update, context=MagicMock()) assert msg_mock.call_count == 4 msg = msg_mock.call_args_list[3][0][0] - assert re.search(r'from 1st entry rate', msg) - assert re.search(r'Order Filled', msg) + assert re.search(r"from 1st entry rate", msg) + assert re.search(r"Order Filled", msg) async def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: - default_conf['max_open_trades'] = 3 + default_conf["max_open_trades"] = 3 mocker.patch.multiple( EXMS, fetch_ticker=ticker, @@ -496,7 +508,7 @@ async def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: ) status_table = MagicMock() mocker.patch.multiple( - 'freqtrade.rpc.telegram.Telegram', + "freqtrade.rpc.telegram.Telegram", _status_table=status_table, ) @@ -508,13 +520,13 @@ async def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: # Status is also enabled when stopped await telegram._status(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert 'no active trade' in msg_mock.call_args_list[0][0][0] + assert "no active trade" in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() freqtradebot.state = State.RUNNING await telegram._status(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert 'no active trade' in msg_mock.call_args_list[0][0][0] + assert "no active trade" in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() # Create some test data @@ -524,14 +536,14 @@ async def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: # close_rate should not be included in the message as the trade is not closed # and no line should be empty - lines = msg_mock.call_args_list[0][0][0].split('\n') - assert '' not in lines[:-1] - assert 'Close Rate' not in ''.join(lines) - assert 'Close Profit' not in ''.join(lines) + lines = msg_mock.call_args_list[0][0][0].split("\n") + assert "" not in lines[:-1] + assert "Close Rate" not in "".join(lines) + assert "Close Profit" not in "".join(lines) assert msg_mock.call_count == 3 - assert 'ETH/BTC' in msg_mock.call_args_list[0][0][0] - assert 'LTC/BTC' in msg_mock.call_args_list[1][0][0] + assert "ETH/BTC" in msg_mock.call_args_list[0][0][0] + assert "LTC/BTC" in msg_mock.call_args_list[1][0][0] msg_mock.reset_mock() context = MagicMock() @@ -539,15 +551,15 @@ async def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: await telegram._status(update=update, context=context) - lines = msg_mock.call_args_list[0][0][0].split('\n') - assert '' not in lines[:-1] - assert 'Close Rate' not in ''.join(lines) - assert 'Close Profit' not in ''.join(lines) + lines = msg_mock.call_args_list[0][0][0].split("\n") + assert "" not in lines[:-1] + assert "Close Rate" not in "".join(lines) + assert "Close Profit" not in "".join(lines) assert msg_mock.call_count == 2 - assert 'LTC/BTC' in msg_mock.call_args_list[0][0][0] + assert "LTC/BTC" in msg_mock.call_args_list[0][0][0] - mocker.patch('freqtrade.rpc.telegram.MAX_MESSAGE_LENGTH', 500) + mocker.patch("freqtrade.rpc.telegram.MAX_MESSAGE_LENGTH", 500) msg_mock.reset_mock() context = MagicMock() @@ -558,8 +570,8 @@ async def test_status_handle(default_conf, update, ticker, fee, mocker) -> None: msg1 = msg_mock.call_args_list[0][0][0] - assert 'Close Rate' not in msg1 - assert 'Trade ID:* `2`' in msg1 + assert "Close Rate" not in msg1 + assert "Trade ID:* `2`" in msg1 async def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None: @@ -569,7 +581,7 @@ async def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> get_fee=fee, ) - default_conf['stake_amount'] = 15.0 + default_conf["stake_amount"] = 15.0 telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) @@ -579,13 +591,13 @@ async def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> # Status table is also enabled when stopped await telegram._status_table(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert 'no active trade' in msg_mock.call_args_list[0][0][0] + assert "no active trade" in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() freqtradebot.state = State.RUNNING await telegram._status_table(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert 'no active trade' in msg_mock.call_args_list[0][0][0] + assert "no active trade" in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() # Create some test data @@ -593,21 +605,18 @@ async def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> await telegram._status_table(update=update, context=MagicMock()) - text = re.sub('', '', msg_mock.call_args_list[-1][0][0]) + text = re.sub("", "", msg_mock.call_args_list[-1][0][0]) line = text.split("\n") - fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ') + fields = re.sub("[ ]+", " ", line[2].strip()).split(" ") assert int(fields[0]) == 1 # assert 'L' in fields[1] - assert 'ETH/BTC' in fields[1] + assert "ETH/BTC" in fields[1] assert msg_mock.call_count == 1 async def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time_machine) -> None: - mocker.patch( - 'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', - return_value=1.1 - ) + mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=1.1) mocker.patch.multiple( EXMS, fetch_ticker=ticker, @@ -617,7 +626,7 @@ async def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time telegram, _freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt) # Move date to within day - time_machine.move_to('2022-06-11 08:00:00+00:00') + time_machine.move_to("2022-06-11 08:00:00+00:00") # Create some test data create_mock_trades_usdt(fee) @@ -628,13 +637,13 @@ async def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time await telegram._daily(update=update, context=context) assert msg_mock.call_count == 1 assert "Daily Profit over the last 2 days:" in msg_mock.call_args_list[0][0][0] - assert 'Day ' in msg_mock.call_args_list[0][0][0] + assert "Day " in msg_mock.call_args_list[0][0][0] assert str(datetime.now(timezone.utc).date()) in msg_mock.call_args_list[0][0][0] - assert ' 6.83 USDT' in msg_mock.call_args_list[0][0][0] - assert ' 7.51 USD' in msg_mock.call_args_list[0][0][0] - assert '(2)' in msg_mock.call_args_list[0][0][0] - assert '(2) 6.83 USDT 7.51 USD 0.64%' in msg_mock.call_args_list[0][0][0] - assert '(0)' in msg_mock.call_args_list[0][0][0] + assert " 6.83 USDT" in msg_mock.call_args_list[0][0][0] + assert " 7.51 USD" in msg_mock.call_args_list[0][0][0] + assert "(2)" in msg_mock.call_args_list[0][0][0] + assert "(2) 6.83 USDT 7.51 USD 0.64%" in msg_mock.call_args_list[0][0][0] + assert "(0)" in msg_mock.call_args_list[0][0][0] # Reset msg_mock msg_mock.reset_mock() @@ -643,13 +652,15 @@ async def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time assert msg_mock.call_count == 1 assert "Daily Profit over the last 7 days:" in msg_mock.call_args_list[0][0][0] assert str(datetime.now(timezone.utc).date()) in msg_mock.call_args_list[0][0][0] - assert str((datetime.now(timezone.utc) - timedelta(days=5)).date() - ) in msg_mock.call_args_list[0][0][0] - assert ' 6.83 USDT' in msg_mock.call_args_list[0][0][0] - assert ' 7.51 USD' in msg_mock.call_args_list[0][0][0] - assert '(2)' in msg_mock.call_args_list[0][0][0] - assert '(1)' in msg_mock.call_args_list[0][0][0] - assert '(0)' in msg_mock.call_args_list[0][0][0] + assert ( + str((datetime.now(timezone.utc) - timedelta(days=5)).date()) + in msg_mock.call_args_list[0][0][0] + ) + assert " 6.83 USDT" in msg_mock.call_args_list[0][0][0] + assert " 7.51 USD" in msg_mock.call_args_list[0][0][0] + assert "(2)" in msg_mock.call_args_list[0][0][0] + assert "(1)" in msg_mock.call_args_list[0][0][0] + assert "(0)" in msg_mock.call_args_list[0][0][0] # Reset msg_mock msg_mock.reset_mock() @@ -658,16 +669,13 @@ async def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time context = MagicMock() context.args = ["1"] await telegram._daily(update=update, context=context) - assert ' 6.83 USDT' in msg_mock.call_args_list[0][0][0] - assert ' 7.51 USD' in msg_mock.call_args_list[0][0][0] - assert '(2)' in msg_mock.call_args_list[0][0][0] + assert " 6.83 USDT" in msg_mock.call_args_list[0][0][0] + assert " 7.51 USD" in msg_mock.call_args_list[0][0][0] + assert "(2)" in msg_mock.call_args_list[0][0][0] async def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: - mocker.patch.multiple( - EXMS, - fetch_ticker=ticker - ) + mocker.patch.multiple(EXMS, fetch_ticker=ticker) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) @@ -680,7 +688,7 @@ async def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: context.args = ["-2"] await telegram._daily(update=update, context=context) assert msg_mock.call_count == 1 - assert 'must be an integer greater than 0' in msg_mock.call_args_list[0][0][0] + assert "must be an integer greater than 0" in msg_mock.call_args_list[0][0][0] # Try invalid data msg_mock.reset_mock() @@ -689,15 +697,12 @@ async def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None: context = MagicMock() context.args = ["today"] await telegram._daily(update=update, context=context) - assert 'Daily Profit over the last 7 days:' in msg_mock.call_args_list[0][0][0] + assert "Daily Profit over the last 7 days:" in msg_mock.call_args_list[0][0][0] async def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, time_machine) -> None: - default_conf_usdt['max_open_trades'] = 1 - mocker.patch( - 'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', - return_value=1.1 - ) + default_conf_usdt["max_open_trades"] = 1 + mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=1.1) mocker.patch.multiple( EXMS, fetch_ticker=ticker, @@ -706,7 +711,7 @@ async def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, tim telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt) # Move to saturday - so all trades are within that week - time_machine.move_to('2022-06-11') + time_machine.move_to("2022-06-11") create_mock_trades_usdt(fee) # Try valid data @@ -715,29 +720,33 @@ async def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, tim context.args = ["2"] await telegram._weekly(update=update, context=context) assert msg_mock.call_count == 1 - assert "Weekly Profit over the last 2 weeks (starting from Monday):" \ - in msg_mock.call_args_list[0][0][0] - assert 'Monday ' in msg_mock.call_args_list[0][0][0] + assert ( + "Weekly Profit over the last 2 weeks (starting from Monday):" + in msg_mock.call_args_list[0][0][0] + ) + assert "Monday " in msg_mock.call_args_list[0][0][0] today = datetime.now(timezone.utc).date() first_iso_day_of_current_week = today - timedelta(days=today.weekday()) assert str(first_iso_day_of_current_week) in msg_mock.call_args_list[0][0][0] - assert ' 2.74 USDT' in msg_mock.call_args_list[0][0][0] - assert ' 3.01 USD' in msg_mock.call_args_list[0][0][0] - assert '(3)' in msg_mock.call_args_list[0][0][0] - assert '(0)' in msg_mock.call_args_list[0][0][0] + assert " 2.74 USDT" in msg_mock.call_args_list[0][0][0] + assert " 3.01 USD" in msg_mock.call_args_list[0][0][0] + assert "(3)" in msg_mock.call_args_list[0][0][0] + assert "(0)" in msg_mock.call_args_list[0][0][0] # Reset msg_mock msg_mock.reset_mock() context.args = [] await telegram._weekly(update=update, context=context) assert msg_mock.call_count == 1 - assert "Weekly Profit over the last 8 weeks (starting from Monday):" \ - in msg_mock.call_args_list[0][0][0] - assert 'Weekly' in msg_mock.call_args_list[0][0][0] - assert ' 2.74 USDT' in msg_mock.call_args_list[0][0][0] - assert ' 3.01 USD' in msg_mock.call_args_list[0][0][0] - assert '(3)' in msg_mock.call_args_list[0][0][0] - assert '(0)' in msg_mock.call_args_list[0][0][0] + assert ( + "Weekly Profit over the last 8 weeks (starting from Monday):" + in msg_mock.call_args_list[0][0][0] + ) + assert "Weekly" in msg_mock.call_args_list[0][0][0] + assert " 2.74 USDT" in msg_mock.call_args_list[0][0][0] + assert " 3.01 USD" in msg_mock.call_args_list[0][0][0] + assert "(3)" in msg_mock.call_args_list[0][0][0] + assert "(0)" in msg_mock.call_args_list[0][0][0] # Try invalid data msg_mock.reset_mock() @@ -747,7 +756,7 @@ async def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, tim context.args = ["-3"] await telegram._weekly(update=update, context=context) assert msg_mock.call_count == 1 - assert 'must be an integer greater than 0' in msg_mock.call_args_list[0][0][0] + assert "must be an integer greater than 0" in msg_mock.call_args_list[0][0][0] # Try invalid data msg_mock.reset_mock() @@ -757,17 +766,14 @@ async def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, tim context.args = ["this week"] await telegram._weekly(update=update, context=context) assert ( - 'Weekly Profit over the last 8 weeks (starting from Monday):' + "Weekly Profit over the last 8 weeks (starting from Monday):" in msg_mock.call_args_list[0][0][0] ) async def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_machine) -> None: - default_conf_usdt['max_open_trades'] = 1 - mocker.patch( - 'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', - return_value=1.1 - ) + default_conf_usdt["max_open_trades"] = 1 + mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=1.1) mocker.patch.multiple( EXMS, fetch_ticker=ticker, @@ -776,7 +782,7 @@ async def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, ti telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt) # Move to day within the month so all mock trades fall into this week. - time_machine.move_to('2022-06-11') + time_machine.move_to("2022-06-11") create_mock_trades_usdt(fee) # Try valid data @@ -785,15 +791,15 @@ async def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, ti context.args = ["2"] await telegram._monthly(update=update, context=context) assert msg_mock.call_count == 1 - assert 'Monthly Profit over the last 2 months:' in msg_mock.call_args_list[0][0][0] - assert 'Month ' in msg_mock.call_args_list[0][0][0] + assert "Monthly Profit over the last 2 months:" in msg_mock.call_args_list[0][0][0] + assert "Month " in msg_mock.call_args_list[0][0][0] today = datetime.now(timezone.utc).date() current_month = f"{today.year}-{today.month:02} " assert current_month in msg_mock.call_args_list[0][0][0] - assert ' 2.74 USDT' in msg_mock.call_args_list[0][0][0] - assert ' 3.01 USD' in msg_mock.call_args_list[0][0][0] - assert '(3)' in msg_mock.call_args_list[0][0][0] - assert '(0)' in msg_mock.call_args_list[0][0][0] + assert " 2.74 USDT" in msg_mock.call_args_list[0][0][0] + assert " 3.01 USD" in msg_mock.call_args_list[0][0][0] + assert "(3)" in msg_mock.call_args_list[0][0][0] + assert "(0)" in msg_mock.call_args_list[0][0][0] # Reset msg_mock msg_mock.reset_mock() @@ -801,13 +807,13 @@ async def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, ti await telegram._monthly(update=update, context=context) assert msg_mock.call_count == 1 # Default to 6 months - assert 'Monthly Profit over the last 6 months:' in msg_mock.call_args_list[0][0][0] - assert 'Month ' in msg_mock.call_args_list[0][0][0] + assert "Monthly Profit over the last 6 months:" in msg_mock.call_args_list[0][0][0] + assert "Month " in msg_mock.call_args_list[0][0][0] assert current_month in msg_mock.call_args_list[0][0][0] - assert ' 2.74 USDT' in msg_mock.call_args_list[0][0][0] - assert ' 3.01 USD' in msg_mock.call_args_list[0][0][0] - assert '(3)' in msg_mock.call_args_list[0][0][0] - assert '(0)' in msg_mock.call_args_list[0][0][0] + assert " 2.74 USDT" in msg_mock.call_args_list[0][0][0] + assert " 3.01 USD" in msg_mock.call_args_list[0][0][0] + assert "(3)" in msg_mock.call_args_list[0][0][0] + assert "(0)" in msg_mock.call_args_list[0][0][0] # Reset msg_mock msg_mock.reset_mock() @@ -817,14 +823,14 @@ async def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, ti context.args = ["12"] await telegram._monthly(update=update, context=context) assert msg_mock.call_count == 1 - assert 'Monthly Profit over the last 12 months:' in msg_mock.call_args_list[0][0][0] - assert ' 2.74 USDT' in msg_mock.call_args_list[0][0][0] - assert ' 3.01 USD' in msg_mock.call_args_list[0][0][0] - assert '(3)' in msg_mock.call_args_list[0][0][0] + assert "Monthly Profit over the last 12 months:" in msg_mock.call_args_list[0][0][0] + assert " 2.74 USDT" in msg_mock.call_args_list[0][0][0] + assert " 3.01 USD" in msg_mock.call_args_list[0][0][0] + assert "(3)" in msg_mock.call_args_list[0][0][0] # The one-digit months should contain a zero, Eg: September 2021 = "2021-09" # Since we loaded the last 12 months, any month should appear - assert '-09' in msg_mock.call_args_list[0][0][0] + assert "-09" in msg_mock.call_args_list[0][0][0] # Try invalid data msg_mock.reset_mock() @@ -834,7 +840,7 @@ async def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, ti context.args = ["-3"] await telegram._monthly(update=update, context=context) assert msg_mock.call_count == 1 - assert 'must be an integer greater than 0' in msg_mock.call_args_list[0][0][0] + assert "must be an integer greater than 0" in msg_mock.call_args_list[0][0][0] # Try invalid data msg_mock.reset_mock() @@ -843,13 +849,13 @@ async def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, ti context = MagicMock() context.args = ["february"] await telegram._monthly(update=update, context=context) - assert 'Monthly Profit over the last 6 months:' in msg_mock.call_args_list[0][0][0] + assert "Monthly Profit over the last 6 months:" in msg_mock.call_args_list[0][0][0] async def test_telegram_profit_handle( - default_conf_usdt, update, ticker_usdt, ticker_sell_up, fee, - limit_sell_order_usdt, mocker) -> None: - mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=1.1) + default_conf_usdt, update, ticker_usdt, ticker_sell_up, fee, limit_sell_order_usdt, mocker +) -> None: + mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=1.1) mocker.patch.multiple( EXMS, fetch_ticker=ticker_usdt, @@ -861,7 +867,7 @@ async def test_telegram_profit_handle( await telegram._profit(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert 'No trades yet.' in msg_mock.call_args_list[0][0][0] + assert "No trades yet." in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() # Create some test data @@ -873,19 +879,22 @@ async def test_telegram_profit_handle( context.args = ["aaa"] await telegram._profit(update=update, context=context) assert msg_mock.call_count == 1 - assert 'No closed trade' in msg_mock.call_args_list[-1][0][0] - assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0] - mocker.patch('freqtrade.wallets.Wallets.get_starting_balance', return_value=1000) - assert ('∙ `0.298 USDT (0.50%) (0.03 \N{GREEK CAPITAL LETTER SIGMA}%)`' - in msg_mock.call_args_list[-1][0][0]) + assert "No closed trade" in msg_mock.call_args_list[-1][0][0] + assert "*ROI:* All trades" in msg_mock.call_args_list[-1][0][0] + mocker.patch("freqtrade.wallets.Wallets.get_starting_balance", return_value=1000) + assert ( + "∙ `0.298 USDT (0.50%) (0.03 \N{GREEK CAPITAL LETTER SIGMA}%)`" + in msg_mock.call_args_list[-1][0][0] + ) msg_mock.reset_mock() # Update the ticker with a market going up - mocker.patch(f'{EXMS}.fetch_ticker', ticker_sell_up) + mocker.patch(f"{EXMS}.fetch_ticker", ticker_sell_up) # Simulate fulfilled LIMIT_SELL order for trade trade = Trade.session.scalars(select(Trade)).first() oobj = Order.parse_from_ccxt_object( - limit_sell_order_usdt, limit_sell_order_usdt['symbol'], 'sell') + limit_sell_order_usdt, limit_sell_order_usdt["symbol"], "sell" + ) trade.orders.append(oobj) trade.update_trade(oobj) @@ -896,26 +905,30 @@ async def test_telegram_profit_handle( context.args = [3] await telegram._profit(update=update, context=context) assert msg_mock.call_count == 1 - assert '*ROI:* Closed trades' in msg_mock.call_args_list[-1][0][0] - assert ('∙ `5.685 USDT (9.45%) (0.57 \N{GREEK CAPITAL LETTER SIGMA}%)`' - in msg_mock.call_args_list[-1][0][0]) - assert '∙ `6.253 USD`' in msg_mock.call_args_list[-1][0][0] - assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0] - assert ('∙ `5.685 USDT (9.45%) (0.57 \N{GREEK CAPITAL LETTER SIGMA}%)`' - in msg_mock.call_args_list[-1][0][0]) - assert '∙ `6.253 USD`' in msg_mock.call_args_list[-1][0][0] + assert "*ROI:* Closed trades" in msg_mock.call_args_list[-1][0][0] + assert ( + "∙ `5.685 USDT (9.45%) (0.57 \N{GREEK CAPITAL LETTER SIGMA}%)`" + in msg_mock.call_args_list[-1][0][0] + ) + assert "∙ `6.253 USD`" in msg_mock.call_args_list[-1][0][0] + assert "*ROI:* All trades" in msg_mock.call_args_list[-1][0][0] + assert ( + "∙ `5.685 USDT (9.45%) (0.57 \N{GREEK CAPITAL LETTER SIGMA}%)`" + in msg_mock.call_args_list[-1][0][0] + ) + assert "∙ `6.253 USD`" in msg_mock.call_args_list[-1][0][0] - assert '*Best Performing:* `ETH/USDT: 9.45%`' in msg_mock.call_args_list[-1][0][0] - assert '*Max Drawdown:*' in msg_mock.call_args_list[-1][0][0] - assert '*Profit factor:*' in msg_mock.call_args_list[-1][0][0] - assert '*Winrate:*' in msg_mock.call_args_list[-1][0][0] - assert '*Expectancy (Ratio):*' in msg_mock.call_args_list[-1][0][0] - assert '*Trading volume:* `126 USDT`' in msg_mock.call_args_list[-1][0][0] + assert "*Best Performing:* `ETH/USDT: 9.45%`" in msg_mock.call_args_list[-1][0][0] + assert "*Max Drawdown:*" in msg_mock.call_args_list[-1][0][0] + assert "*Profit factor:*" in msg_mock.call_args_list[-1][0][0] + assert "*Winrate:*" in msg_mock.call_args_list[-1][0][0] + assert "*Expectancy (Ratio):*" in msg_mock.call_args_list[-1][0][0] + assert "*Trading volume:* `126 USDT`" in msg_mock.call_args_list[-1][0][0] -@pytest.mark.parametrize('is_short', [True, False]) +@pytest.mark.parametrize("is_short", [True, False]) async def test_telegram_stats(default_conf, update, ticker, fee, mocker, is_short) -> None: - mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) + mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=15000.0) mocker.patch.multiple( EXMS, fetch_ticker=ticker, @@ -926,7 +939,7 @@ async def test_telegram_stats(default_conf, update, ticker, fee, mocker, is_shor await telegram._stats(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert 'No trades yet.' in msg_mock.call_args_list[0][0][0] + assert "No trades yet." in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() # Create some test data @@ -934,20 +947,20 @@ async def test_telegram_stats(default_conf, update, ticker, fee, mocker, is_shor await telegram._stats(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert 'Exit Reason' in msg_mock.call_args_list[-1][0][0] - assert 'ROI' in msg_mock.call_args_list[-1][0][0] - assert 'Avg. Duration' in msg_mock.call_args_list[-1][0][0] + assert "Exit Reason" in msg_mock.call_args_list[-1][0][0] + assert "ROI" in msg_mock.call_args_list[-1][0][0] + assert "Avg. Duration" in msg_mock.call_args_list[-1][0][0] # Duration is not only N/A - assert '0:19:00' in msg_mock.call_args_list[-1][0][0] - assert 'N/A' in msg_mock.call_args_list[-1][0][0] + assert "0:19:00" in msg_mock.call_args_list[-1][0][0] + assert "N/A" in msg_mock.call_args_list[-1][0][0] msg_mock.reset_mock() async def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tickers) -> None: - default_conf['dry_run'] = False - mocker.patch(f'{EXMS}.get_balances', return_value=rpc_balance) - mocker.patch(f'{EXMS}.get_tickers', tickers) - mocker.patch(f'{EXMS}.get_valid_pair_combination', side_effect=lambda a, b: f"{a}/{b}") + default_conf["dry_run"] = False + mocker.patch(f"{EXMS}.get_balances", return_value=rpc_balance) + mocker.patch(f"{EXMS}.get_tickers", tickers) + mocker.patch(f"{EXMS}.get_valid_pair_combination", side_effect=lambda a, b: f"{a}/{b}") telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) @@ -959,40 +972,40 @@ async def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance result = msg_mock.call_args_list[0][0][0] result_full = msg_mock.call_args_list[1][0][0] assert msg_mock.call_count == 2 - assert '*BTC:*' in result - assert '*ETH:*' not in result - assert '*USDT:*' not in result - assert '*EUR:*' not in result - assert '*LTC:*' not in result + assert "*BTC:*" in result + assert "*ETH:*" not in result + assert "*USDT:*" not in result + assert "*EUR:*" not in result + assert "*LTC:*" not in result - assert '*LTC:*' in result_full - assert '*XRP:*' not in result - assert 'Balance:' in result - assert 'Est. BTC:' in result - assert 'BTC: 11' in result - assert 'BTC: 12' in result_full + assert "*LTC:*" in result_full + assert "*XRP:*" not in result + assert "Balance:" in result + assert "Est. BTC:" in result + assert "BTC: 11" in result + assert "BTC: 12" in result_full assert "*3 Other Currencies (< 0.0001 BTC):*" in result - assert 'BTC: 0.00000309' in result - assert '*Estimated Value*:' in result_full - assert '*Estimated Value (Bot managed assets only)*:' in result + assert "BTC: 0.00000309" in result + assert "*Estimated Value*:" in result_full + assert "*Estimated Value (Bot managed assets only)*:" in result async def test_balance_handle_empty_response(default_conf, update, mocker) -> None: - default_conf['dry_run'] = False - mocker.patch(f'{EXMS}.get_balances', return_value={}) + default_conf["dry_run"] = False + mocker.patch(f"{EXMS}.get_balances", return_value={}) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) - freqtradebot.config['dry_run'] = False + freqtradebot.config["dry_run"] = False await telegram._balance(update=update, context=MagicMock()) result = msg_mock.call_args_list[0][0][0] assert msg_mock.call_count == 1 - assert 'Starting capital: `0 BTC' in result + assert "Starting capital: `0 BTC" in result async def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None: - mocker.patch(f'{EXMS}.get_balances', return_value={}) + mocker.patch(f"{EXMS}.get_balances", return_value={}) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) @@ -1008,31 +1021,36 @@ async def test_balance_handle_too_large_response(default_conf, update, mocker) - balances = [] for i in range(100): curr = choice(ascii_uppercase) + choice(ascii_uppercase) + choice(ascii_uppercase) - balances.append({ - 'currency': curr, - 'free': 1.0, - 'used': 0.5, - 'balance': i, - 'bot_owned': 0.5, - 'est_stake': 1, - 'est_stake_bot': 1, - 'stake': 'BTC', - 'is_position': False, - 'leverage': 1.0, - 'position': 0.0, - 'side': 'long', - 'is_bot_managed': True, - }) - mocker.patch('freqtrade.rpc.rpc.RPC._rpc_balance', return_value={ - 'currencies': balances, - 'total': 100.0, - 'total_bot': 100.0, - 'symbol': 100.0, - 'value': 1000.0, - 'value_bot': 1000.0, - 'starting_capital': 1000, - 'starting_capital_fiat': 1000, - }) + balances.append( + { + "currency": curr, + "free": 1.0, + "used": 0.5, + "balance": i, + "bot_owned": 0.5, + "est_stake": 1, + "est_stake_bot": 1, + "stake": "BTC", + "is_position": False, + "leverage": 1.0, + "position": 0.0, + "side": "long", + "is_bot_managed": True, + } + ) + mocker.patch( + "freqtrade.rpc.rpc.RPC._rpc_balance", + return_value={ + "currencies": balances, + "total": 100.0, + "total_bot": 100.0, + "symbol": 100.0, + "value": 1000.0, + "value_bot": 1000.0, + "starting_capital": 1000, + "starting_capital_fiat": 1000, + }, + ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) @@ -1047,7 +1065,6 @@ async def test_balance_handle_too_large_response(default_conf, update, mocker) - async def test_start_handle(default_conf, update, mocker) -> None: - telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) freqtradebot.state = State.STOPPED @@ -1058,7 +1075,6 @@ async def test_start_handle(default_conf, update, mocker) -> None: async def test_start_handle_already_running(default_conf, update, mocker) -> None: - telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) freqtradebot.state = State.RUNNING @@ -1066,11 +1082,10 @@ async def test_start_handle_already_running(default_conf, update, mocker) -> Non await telegram._start(update=update, context=MagicMock()) assert freqtradebot.state == State.RUNNING assert msg_mock.call_count == 1 - assert 'already running' in msg_mock.call_args_list[0][0][0] + assert "already running" in msg_mock.call_args_list[0][0][0] async def test_stop_handle(default_conf, update, mocker) -> None: - telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) freqtradebot.state = State.RUNNING @@ -1078,11 +1093,10 @@ async def test_stop_handle(default_conf, update, mocker) -> None: await telegram._stop(update=update, context=MagicMock()) assert freqtradebot.state == State.STOPPED assert msg_mock.call_count == 1 - assert 'stopping trader' in msg_mock.call_args_list[0][0][0] + assert "stopping trader" in msg_mock.call_args_list[0][0][0] async def test_stop_handle_already_stopped(default_conf, update, mocker) -> None: - telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) freqtradebot.state = State.STOPPED @@ -1090,23 +1104,23 @@ async def test_stop_handle_already_stopped(default_conf, update, mocker) -> None await telegram._stop(update=update, context=MagicMock()) assert freqtradebot.state == State.STOPPED assert msg_mock.call_count == 1 - assert 'already stopped' in msg_mock.call_args_list[0][0][0] + assert "already stopped" in msg_mock.call_args_list[0][0][0] async def test_stopbuy_handle(default_conf, update, mocker) -> None: - telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) - assert freqtradebot.config['max_open_trades'] != 0 + assert freqtradebot.config["max_open_trades"] != 0 await telegram._stopentry(update=update, context=MagicMock()) - assert freqtradebot.config['max_open_trades'] == 0 + assert freqtradebot.config["max_open_trades"] == 0 assert msg_mock.call_count == 1 - assert 'No more entries will occur from now. Run /reload_config to reset.' \ + assert ( + "No more entries will occur from now. Run /reload_config to reset." in msg_mock.call_args_list[0][0][0] + ) async def test_reload_config_handle(default_conf, update, mocker) -> None: - telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) freqtradebot.state = State.RUNNING @@ -1114,14 +1128,15 @@ async def test_reload_config_handle(default_conf, update, mocker) -> None: await telegram._reload_config(update=update, context=MagicMock()) assert freqtradebot.state == State.RELOAD_CONFIG assert msg_mock.call_count == 1 - assert 'Reloading config' in msg_mock.call_args_list[0][0][0] + assert "Reloading config" in msg_mock.call_args_list[0][0][0] -async def test_telegram_forceexit_handle(default_conf, update, ticker, fee, - ticker_sell_up, mocker) -> None: - mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) - msg_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) - mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) +async def test_telegram_forceexit_handle( + default_conf, update, ticker, fee, ticker_sell_up, mocker +) -> None: + mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=15000.0) + msg_mock = mocker.patch("freqtrade.rpc.telegram.Telegram.send_msg", MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram._init", MagicMock()) patch_exchange(mocker) patch_whitelist(mocker, default_conf) mocker.patch.multiple( @@ -1143,7 +1158,7 @@ async def test_telegram_forceexit_handle(default_conf, update, ticker, fee, assert trade # Increase the price and sell it - mocker.patch(f'{EXMS}.fetch_ticker', ticker_sell_up) + mocker.patch(f"{EXMS}.fetch_ticker", ticker_sell_up) # /forceexit 1 context = MagicMock() @@ -1153,45 +1168,47 @@ async def test_telegram_forceexit_handle(default_conf, update, ticker, fee, assert msg_mock.call_count == 4 last_msg = msg_mock.call_args_list[-2][0][0] assert { - 'type': RPCMessageType.EXIT, - 'trade_id': 1, - 'exchange': 'Binance', - 'pair': 'ETH/BTC', - 'gain': 'profit', - 'leverage': 1.0, - 'limit': 1.173e-05, - 'order_rate': 1.173e-05, - 'amount': 91.07468123, - 'order_type': 'limit', - 'open_rate': 1.098e-05, - 'current_rate': 1.173e-05, - 'direction': 'Long', - 'profit_amount': 6.314e-05, - 'profit_ratio': 0.0629778, - 'stake_currency': 'BTC', - 'quote_currency': 'BTC', - 'base_currency': 'ETH', - 'fiat_currency': 'USD', - 'buy_tag': ANY, - 'enter_tag': ANY, - 'exit_reason': ExitType.FORCE_EXIT.value, - 'open_date': ANY, - 'close_date': ANY, - 'close_rate': ANY, - 'stake_amount': 0.0009999999999054, - 'sub_trade': False, - 'cumulative_profit': 0.0, - 'is_final_exit': False, - 'final_profit_ratio': None, + "type": RPCMessageType.EXIT, + "trade_id": 1, + "exchange": "Binance", + "pair": "ETH/BTC", + "gain": "profit", + "leverage": 1.0, + "limit": 1.173e-05, + "order_rate": 1.173e-05, + "amount": 91.07468123, + "order_type": "limit", + "open_rate": 1.098e-05, + "current_rate": 1.173e-05, + "direction": "Long", + "profit_amount": 6.314e-05, + "profit_ratio": 0.0629778, + "stake_currency": "BTC", + "quote_currency": "BTC", + "base_currency": "ETH", + "fiat_currency": "USD", + "buy_tag": ANY, + "enter_tag": ANY, + "exit_reason": ExitType.FORCE_EXIT.value, + "open_date": ANY, + "close_date": ANY, + "close_rate": ANY, + "stake_amount": 0.0009999999999054, + "sub_trade": False, + "cumulative_profit": 0.0, + "is_final_exit": False, + "final_profit_ratio": None, } == last_msg -async def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee, - ticker_sell_down, mocker) -> None: - mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', - return_value=15000.0) - msg_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) - mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) +async def test_telegram_force_exit_down_handle( + default_conf, update, ticker, fee, ticker_sell_down, mocker +) -> None: + mocker.patch( + "freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price", return_value=15000.0 + ) + msg_mock = mocker.patch("freqtrade.rpc.telegram.Telegram.send_msg", MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram._init", MagicMock()) patch_exchange(mocker) patch_whitelist(mocker, default_conf) @@ -1211,10 +1228,7 @@ async def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee freqtradebot.enter_positions() # Decrease the price and sell it - mocker.patch.multiple( - EXMS, - fetch_ticker=ticker_sell_down - ) + mocker.patch.multiple(EXMS, fetch_ticker=ticker_sell_down) trade = Trade.session.scalars(select(Trade)).first() assert trade @@ -1228,45 +1242,46 @@ async def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee last_msg = msg_mock.call_args_list[-2][0][0] assert { - 'type': RPCMessageType.EXIT, - 'trade_id': 1, - 'exchange': 'Binance', - 'pair': 'ETH/BTC', - 'gain': 'loss', - 'leverage': 1.0, - 'limit': 1.043e-05, - 'order_rate': 1.043e-05, - 'amount': 91.07468123, - 'order_type': 'limit', - 'open_rate': 1.098e-05, - 'current_rate': 1.043e-05, - 'direction': 'Long', - 'profit_amount': -5.497e-05, - 'profit_ratio': -0.05482878, - 'stake_currency': 'BTC', - 'quote_currency': 'BTC', - 'base_currency': 'ETH', - 'fiat_currency': 'USD', - 'buy_tag': ANY, - 'enter_tag': ANY, - 'exit_reason': ExitType.FORCE_EXIT.value, - 'open_date': ANY, - 'close_date': ANY, - 'close_rate': ANY, - 'stake_amount': 0.0009999999999054, - 'sub_trade': False, - 'cumulative_profit': 0.0, - 'is_final_exit': False, - 'final_profit_ratio': None, + "type": RPCMessageType.EXIT, + "trade_id": 1, + "exchange": "Binance", + "pair": "ETH/BTC", + "gain": "loss", + "leverage": 1.0, + "limit": 1.043e-05, + "order_rate": 1.043e-05, + "amount": 91.07468123, + "order_type": "limit", + "open_rate": 1.098e-05, + "current_rate": 1.043e-05, + "direction": "Long", + "profit_amount": -5.497e-05, + "profit_ratio": -0.05482878, + "stake_currency": "BTC", + "quote_currency": "BTC", + "base_currency": "ETH", + "fiat_currency": "USD", + "buy_tag": ANY, + "enter_tag": ANY, + "exit_reason": ExitType.FORCE_EXIT.value, + "open_date": ANY, + "close_date": ANY, + "close_rate": ANY, + "stake_amount": 0.0009999999999054, + "sub_trade": False, + "cumulative_profit": 0.0, + "is_final_exit": False, + "final_profit_ratio": None, } == last_msg async def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) -> None: patch_exchange(mocker) - mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', - return_value=15000.0) - msg_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) - mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) + mocker.patch( + "freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price", return_value=15000.0 + ) + msg_mock = mocker.patch("freqtrade.rpc.telegram.Telegram.send_msg", MagicMock()) + mocker.patch("freqtrade.rpc.telegram.Telegram._init", MagicMock()) patch_whitelist(mocker, default_conf) mocker.patch.multiple( EXMS, @@ -1274,7 +1289,7 @@ async def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) - get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=True), ) - default_conf['max_open_trades'] = 4 + default_conf["max_open_trades"] = 4 freqtradebot = FreqtradeBot(default_conf) rpc = RPC(freqtradebot) telegram = Telegram(rpc, default_conf) @@ -1293,42 +1308,43 @@ async def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) - assert msg_mock.call_count == 8 msg = msg_mock.call_args_list[0][0][0] assert { - 'type': RPCMessageType.EXIT, - 'trade_id': 1, - 'exchange': 'Binance', - 'pair': 'ETH/BTC', - 'gain': 'loss', - 'leverage': 1.0, - 'order_rate': 1.099e-05, - 'limit': 1.099e-05, - 'amount': 91.07468123, - 'order_type': 'limit', - 'open_rate': 1.098e-05, - 'current_rate': 1.099e-05, - 'direction': 'Long', - 'profit_amount': -4.09e-06, - 'profit_ratio': -0.00408133, - 'stake_currency': 'BTC', - 'quote_currency': 'BTC', - 'base_currency': 'ETH', - 'fiat_currency': 'USD', - 'buy_tag': ANY, - 'enter_tag': ANY, - 'exit_reason': ExitType.FORCE_EXIT.value, - 'open_date': ANY, - 'close_date': ANY, - 'close_rate': ANY, - 'stake_amount': 0.0009999999999054, - 'sub_trade': False, - 'cumulative_profit': 0.0, - 'is_final_exit': False, - 'final_profit_ratio': None, + "type": RPCMessageType.EXIT, + "trade_id": 1, + "exchange": "Binance", + "pair": "ETH/BTC", + "gain": "loss", + "leverage": 1.0, + "order_rate": 1.099e-05, + "limit": 1.099e-05, + "amount": 91.07468123, + "order_type": "limit", + "open_rate": 1.098e-05, + "current_rate": 1.099e-05, + "direction": "Long", + "profit_amount": -4.09e-06, + "profit_ratio": -0.00408133, + "stake_currency": "BTC", + "quote_currency": "BTC", + "base_currency": "ETH", + "fiat_currency": "USD", + "buy_tag": ANY, + "enter_tag": ANY, + "exit_reason": ExitType.FORCE_EXIT.value, + "open_date": ANY, + "close_date": ANY, + "close_rate": ANY, + "stake_amount": 0.0009999999999054, + "sub_trade": False, + "cumulative_profit": 0.0, + "is_final_exit": False, + "final_profit_ratio": None, } == msg async def test_forceexit_handle_invalid(default_conf, update, mocker) -> None: - mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price', - return_value=15000.0) + mocker.patch( + "freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price", return_value=15000.0 + ) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) @@ -1340,7 +1356,7 @@ async def test_forceexit_handle_invalid(default_conf, update, mocker) -> None: context.args = ["1"] await telegram._force_exit(update=update, context=context) assert msg_mock.call_count == 1 - assert 'not running' in msg_mock.call_args_list[0][0][0] + assert "not running" in msg_mock.call_args_list[0][0][0] # Invalid argument msg_mock.reset_mock() @@ -1350,18 +1366,18 @@ async def test_forceexit_handle_invalid(default_conf, update, mocker) -> None: context.args = ["123456"] await telegram._force_exit(update=update, context=context) assert msg_mock.call_count == 1 - assert 'invalid argument' in msg_mock.call_args_list[0][0][0] + assert "invalid argument" in msg_mock.call_args_list[0][0][0] async def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None: - default_conf['max_open_trades'] = 4 + default_conf["max_open_trades"] = 4 mocker.patch.multiple( EXMS, fetch_ticker=ticker, get_fee=fee, _dry_is_price_crossed=MagicMock(return_value=True), ) - femock = mocker.patch('freqtrade.rpc.rpc.RPC._rpc_force_exit') + femock = mocker.patch("freqtrade.rpc.rpc.RPC._rpc_force_exit") telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) @@ -1371,7 +1387,7 @@ async def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> context.args = [] await telegram._force_exit(update=update, context=context) # No pair - assert msg_mock.call_args_list[0][1]['msg'] == 'No open trade found.' + assert msg_mock.call_args_list[0][1]["msg"] == "No open trade found." # Create some test data freqtradebot.enter_positions() @@ -1379,12 +1395,12 @@ async def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> # /forceexit await telegram._force_exit(update=update, context=context) - keyboard = msg_mock.call_args_list[0][1]['keyboard'] + keyboard = msg_mock.call_args_list[0][1]["keyboard"] # 4 pairs + cancel assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 5 assert keyboard[-1][0].text == "Cancel" - assert keyboard[1][0].callback_data == 'force_exit__2 ' + assert keyboard[1][0].callback_data == "force_exit__2 " update = MagicMock() update.callback_query = AsyncMock() update.callback_query.data = keyboard[1][0].callback_data @@ -1392,7 +1408,7 @@ async def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> assert update.callback_query.answer.call_count == 1 assert update.callback_query.edit_message_text.call_count == 1 assert femock.call_count == 1 - assert femock.call_args_list[0][0][0] == '2' + assert femock.call_args_list[0][0][0] == "2" # Retry exiting - but cancel instead update.callback_query.reset_mock() @@ -1403,14 +1419,14 @@ async def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> query = update.callback_query assert query.answer.call_count == 1 assert query.edit_message_text.call_count == 1 - assert query.edit_message_text.call_args_list[-1][1]['text'] == "Force exit canceled." + assert query.edit_message_text.call_args_list[-1][1]["text"] == "Force exit canceled." async def test_force_enter_handle(default_conf, update, mocker) -> None: - mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) + mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=15000.0) fbuy_mock = MagicMock(return_value=None) - mocker.patch('freqtrade.rpc.rpc.RPC._rpc_force_entry', fbuy_mock) + mocker.patch("freqtrade.rpc.rpc.RPC._rpc_force_entry", fbuy_mock) telegram, freqtradebot, _ = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) @@ -1421,26 +1437,26 @@ async def test_force_enter_handle(default_conf, update, mocker) -> None: await telegram._force_enter(update=update, context=context, order_side=SignalDirection.LONG) assert fbuy_mock.call_count == 1 - assert fbuy_mock.call_args_list[0][0][0] == 'ETH/BTC' + assert fbuy_mock.call_args_list[0][0][0] == "ETH/BTC" assert fbuy_mock.call_args_list[0][0][1] is None - assert fbuy_mock.call_args_list[0][1]['order_side'] == SignalDirection.LONG + assert fbuy_mock.call_args_list[0][1]["order_side"] == SignalDirection.LONG # Reset and retry with specified price fbuy_mock = MagicMock(return_value=None) - mocker.patch('freqtrade.rpc.rpc.RPC._rpc_force_entry', fbuy_mock) + mocker.patch("freqtrade.rpc.rpc.RPC._rpc_force_entry", fbuy_mock) # /forcelong ETH/BTC 0.055 context = MagicMock() context.args = ["ETH/BTC", "0.055"] await telegram._force_enter(update=update, context=context, order_side=SignalDirection.LONG) assert fbuy_mock.call_count == 1 - assert fbuy_mock.call_args_list[0][0][0] == 'ETH/BTC' + assert fbuy_mock.call_args_list[0][0][0] == "ETH/BTC" assert isinstance(fbuy_mock.call_args_list[0][0][1], float) assert fbuy_mock.call_args_list[0][0][1] == 0.055 async def test_force_enter_handle_exception(default_conf, update, mocker) -> None: - mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) + mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=15000.0) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) patch_get_signal(freqtradebot) @@ -1448,14 +1464,14 @@ async def test_force_enter_handle_exception(default_conf, update, mocker) -> Non await telegram._force_enter(update=update, context=MagicMock(), order_side=SignalDirection.LONG) assert msg_mock.call_count == 1 - assert msg_mock.call_args_list[0][0][0] == 'Force_entry not enabled.' + assert msg_mock.call_args_list[0][0][0] == "Force_entry not enabled." async def test_force_enter_no_pair(default_conf, update, mocker) -> None: - mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0) + mocker.patch("freqtrade.rpc.rpc.CryptoToFiatConverter._find_price", return_value=15000.0) fbuy_mock = MagicMock(return_value=None) - mocker.patch('freqtrade.rpc.rpc.RPC._rpc_force_entry', fbuy_mock) + mocker.patch("freqtrade.rpc.rpc.RPC._rpc_force_entry", fbuy_mock) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) @@ -1467,29 +1483,28 @@ async def test_force_enter_no_pair(default_conf, update, mocker) -> None: assert fbuy_mock.call_count == 0 assert msg_mock.call_count == 1 - assert msg_mock.call_args_list[0][1]['msg'] == 'Which pair?' + assert msg_mock.call_args_list[0][1]["msg"] == "Which pair?" # assert msg_mock.call_args_list[0][1]['callback_query_handler'] == 'forcebuy' - keyboard = msg_mock.call_args_list[0][1]['keyboard'] + keyboard = msg_mock.call_args_list[0][1]["keyboard"] # One additional button - cancel assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 5 update = MagicMock() update.callback_query = AsyncMock() - update.callback_query.data = 'force_enter__XRP/USDT_||_long' + update.callback_query.data = "force_enter__XRP/USDT_||_long" await telegram._force_enter_inline(update, None) assert fbuy_mock.call_count == 1 fbuy_mock.reset_mock() update.callback_query = AsyncMock() - update.callback_query.data = 'force_enter__cancel' + update.callback_query.data = "force_enter__cancel" await telegram._force_enter_inline(update, None) assert fbuy_mock.call_count == 0 query = update.callback_query assert query.edit_message_text.call_count == 1 - assert query.edit_message_text.call_args_list[-1][1]['text'] == "Force enter canceled." + assert query.edit_message_text.call_args_list[-1][1]["text"] == "Force enter canceled." async def test_telegram_performance_handle(default_conf_usdt, update, ticker, fee, mocker) -> None: - mocker.patch.multiple( EXMS, fetch_ticker=ticker, @@ -1502,12 +1517,13 @@ async def test_telegram_performance_handle(default_conf_usdt, update, ticker, fe await telegram._performance(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert 'Performance' in msg_mock.call_args_list[0][0][0] - assert 'XRP/USDT\t2.842 USDT (10.00%) (1)' in msg_mock.call_args_list[0][0][0] + assert "Performance" in msg_mock.call_args_list[0][0][0] + assert "XRP/USDT\t2.842 USDT (10.00%) (1)" in msg_mock.call_args_list[0][0][0] async def test_telegram_entry_tag_performance_handle( - default_conf_usdt, update, ticker, fee, mocker) -> None: + default_conf_usdt, update, ticker, fee, mocker +) -> None: mocker.patch.multiple( EXMS, fetch_ticker=ticker, @@ -1521,16 +1537,17 @@ async def test_telegram_entry_tag_performance_handle( context = MagicMock() await telegram._enter_tag_performance(update=update, context=context) assert msg_mock.call_count == 1 - assert 'Entry Tag Performance' in msg_mock.call_args_list[0][0][0] - assert '`TEST1\t3.987 USDT (5.00%) (1)`' in msg_mock.call_args_list[0][0][0] + assert "Entry Tag Performance" in msg_mock.call_args_list[0][0][0] + assert "`TEST1\t3.987 USDT (5.00%) (1)`" in msg_mock.call_args_list[0][0][0] - context.args = ['XRP/USDT'] + context.args = ["XRP/USDT"] await telegram._enter_tag_performance(update=update, context=context) assert msg_mock.call_count == 2 msg_mock.reset_mock() - mocker.patch('freqtrade.rpc.rpc.RPC._rpc_enter_tag_performance', - side_effect=RPCException('Error')) + mocker.patch( + "freqtrade.rpc.rpc.RPC._rpc_enter_tag_performance", side_effect=RPCException("Error") + ) await telegram._enter_tag_performance(update=update, context=MagicMock()) assert msg_mock.call_count == 1 @@ -1538,7 +1555,8 @@ async def test_telegram_entry_tag_performance_handle( async def test_telegram_exit_reason_performance_handle( - default_conf_usdt, update, ticker, fee, mocker) -> None: + default_conf_usdt, update, ticker, fee, mocker +) -> None: mocker.patch.multiple( EXMS, fetch_ticker=ticker, @@ -1552,24 +1570,26 @@ async def test_telegram_exit_reason_performance_handle( context = MagicMock() await telegram._exit_reason_performance(update=update, context=context) assert msg_mock.call_count == 1 - assert 'Exit Reason Performance' in msg_mock.call_args_list[0][0][0] - assert '`roi\t2.842 USDT (10.00%) (1)`' in msg_mock.call_args_list[0][0][0] - context.args = ['XRP/USDT'] + assert "Exit Reason Performance" in msg_mock.call_args_list[0][0][0] + assert "`roi\t2.842 USDT (10.00%) (1)`" in msg_mock.call_args_list[0][0][0] + context.args = ["XRP/USDT"] await telegram._exit_reason_performance(update=update, context=context) assert msg_mock.call_count == 2 msg_mock.reset_mock() - mocker.patch('freqtrade.rpc.rpc.RPC._rpc_exit_reason_performance', - side_effect=RPCException('Error')) + mocker.patch( + "freqtrade.rpc.rpc.RPC._rpc_exit_reason_performance", side_effect=RPCException("Error") + ) await telegram._exit_reason_performance(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert "Error" in msg_mock.call_args_list[0][0][0] -async def test_telegram_mix_tag_performance_handle(default_conf_usdt, update, ticker, fee, - mocker) -> None: +async def test_telegram_mix_tag_performance_handle( + default_conf_usdt, update, ticker, fee, mocker +) -> None: mocker.patch.multiple( EXMS, fetch_ticker=ticker, @@ -1584,17 +1604,17 @@ async def test_telegram_mix_tag_performance_handle(default_conf_usdt, update, ti context = MagicMock() await telegram._mix_tag_performance(update=update, context=context) assert msg_mock.call_count == 1 - assert 'Mix Tag Performance' in msg_mock.call_args_list[0][0][0] - assert ('`TEST3 roi\t2.842 USDT (10.00%) (1)`' - in msg_mock.call_args_list[0][0][0]) + assert "Mix Tag Performance" in msg_mock.call_args_list[0][0][0] + assert "`TEST3 roi\t2.842 USDT (10.00%) (1)`" in msg_mock.call_args_list[0][0][0] - context.args = ['XRP/USDT'] + context.args = ["XRP/USDT"] await telegram._mix_tag_performance(update=update, context=context) assert msg_mock.call_count == 2 msg_mock.reset_mock() - mocker.patch('freqtrade.rpc.rpc.RPC._rpc_mix_tag_performance', - side_effect=RPCException('Error')) + mocker.patch( + "freqtrade.rpc.rpc.RPC._rpc_mix_tag_performance", side_effect=RPCException("Error") + ) await telegram._mix_tag_performance(update=update, context=MagicMock()) assert msg_mock.call_count == 1 @@ -1613,7 +1633,7 @@ async def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: freqtradebot.state = State.STOPPED await telegram._count(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert 'not running' in msg_mock.call_args_list[0][0][0] + assert "not running" in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() freqtradebot.state = State.RUNNING @@ -1622,11 +1642,10 @@ async def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: msg_mock.reset_mock() await telegram._count(update=update, context=MagicMock()) - msg = ('
  current    max    total stake\n---------  -----  -------------\n'
-           '        1      {}          {}
').format( - default_conf['max_open_trades'], - default_conf['stake_amount'] - ) + msg = ( + "
  current    max    total stake\n---------  -----  -------------\n"
+        "        1      {}          {}
" + ).format(default_conf["max_open_trades"], default_conf["stake_amount"]) assert msg in msg_mock.call_args_list[0][0][0] @@ -1640,107 +1659,118 @@ async def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) - patch_get_signal(freqtradebot) await telegram._locks(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert 'No active locks.' in msg_mock.call_args_list[0][0][0] + assert "No active locks." in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() - PairLocks.lock_pair('ETH/BTC', dt_now() + timedelta(minutes=4), 'randreason') - PairLocks.lock_pair('XRP/BTC', dt_now() + timedelta(minutes=20), 'deadbeef') + PairLocks.lock_pair("ETH/BTC", dt_now() + timedelta(minutes=4), "randreason") + PairLocks.lock_pair("XRP/BTC", dt_now() + timedelta(minutes=20), "deadbeef") await telegram._locks(update=update, context=MagicMock()) - assert 'Pair' in msg_mock.call_args_list[0][0][0] - assert 'Until' in msg_mock.call_args_list[0][0][0] - assert 'Reason\n' in msg_mock.call_args_list[0][0][0] - assert 'ETH/BTC' in msg_mock.call_args_list[0][0][0] - assert 'XRP/BTC' in msg_mock.call_args_list[0][0][0] - assert 'deadbeef' in msg_mock.call_args_list[0][0][0] - assert 'randreason' in msg_mock.call_args_list[0][0][0] + assert "Pair" in msg_mock.call_args_list[0][0][0] + assert "Until" in msg_mock.call_args_list[0][0][0] + assert "Reason\n" in msg_mock.call_args_list[0][0][0] + assert "ETH/BTC" in msg_mock.call_args_list[0][0][0] + assert "XRP/BTC" in msg_mock.call_args_list[0][0][0] + assert "deadbeef" in msg_mock.call_args_list[0][0][0] + assert "randreason" in msg_mock.call_args_list[0][0][0] context = MagicMock() - context.args = ['XRP/BTC'] + context.args = ["XRP/BTC"] msg_mock.reset_mock() await telegram._delete_locks(update=update, context=context) - assert 'ETH/BTC' in msg_mock.call_args_list[0][0][0] - assert 'randreason' in msg_mock.call_args_list[0][0][0] - assert 'XRP/BTC' not in msg_mock.call_args_list[0][0][0] - assert 'deadbeef' not in msg_mock.call_args_list[0][0][0] + assert "ETH/BTC" in msg_mock.call_args_list[0][0][0] + assert "randreason" in msg_mock.call_args_list[0][0][0] + assert "XRP/BTC" not in msg_mock.call_args_list[0][0][0] + assert "deadbeef" not in msg_mock.call_args_list[0][0][0] async def test_whitelist_static(default_conf, update, mocker) -> None: - telegram, _freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) await telegram._whitelist(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert ("Using whitelist `['StaticPairList']` with 4 pairs\n" - "`ETH/BTC, LTC/BTC, XRP/BTC, NEO/BTC`" in msg_mock.call_args_list[0][0][0]) + assert ( + "Using whitelist `['StaticPairList']` with 4 pairs\n" + "`ETH/BTC, LTC/BTC, XRP/BTC, NEO/BTC`" in msg_mock.call_args_list[0][0][0] + ) context = MagicMock() - context.args = ['sorted'] + context.args = ["sorted"] msg_mock.reset_mock() await telegram._whitelist(update=update, context=context) - assert ("Using whitelist `['StaticPairList']` with 4 pairs\n" - "`ETH/BTC, LTC/BTC, NEO/BTC, XRP/BTC`" in msg_mock.call_args_list[0][0][0]) + assert ( + "Using whitelist `['StaticPairList']` with 4 pairs\n" + "`ETH/BTC, LTC/BTC, NEO/BTC, XRP/BTC`" in msg_mock.call_args_list[0][0][0] + ) context = MagicMock() - context.args = ['baseonly'] + context.args = ["baseonly"] msg_mock.reset_mock() await telegram._whitelist(update=update, context=context) - assert ("Using whitelist `['StaticPairList']` with 4 pairs\n" - "`ETH, LTC, XRP, NEO`" in msg_mock.call_args_list[0][0][0]) + assert ( + "Using whitelist `['StaticPairList']` with 4 pairs\n" + "`ETH, LTC, XRP, NEO`" in msg_mock.call_args_list[0][0][0] + ) context = MagicMock() - context.args = ['baseonly', 'sorted'] + context.args = ["baseonly", "sorted"] msg_mock.reset_mock() await telegram._whitelist(update=update, context=context) - assert ("Using whitelist `['StaticPairList']` with 4 pairs\n" - "`ETH, LTC, NEO, XRP`" in msg_mock.call_args_list[0][0][0]) + assert ( + "Using whitelist `['StaticPairList']` with 4 pairs\n" + "`ETH, LTC, NEO, XRP`" in msg_mock.call_args_list[0][0][0] + ) async def test_whitelist_dynamic(default_conf, update, mocker) -> None: - mocker.patch(f'{EXMS}.exchange_has', return_value=True) - default_conf['pairlists'] = [{'method': 'VolumePairList', - 'number_assets': 4 - }] + mocker.patch(f"{EXMS}.exchange_has", return_value=True) + default_conf["pairlists"] = [{"method": "VolumePairList", "number_assets": 4}] telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) await telegram._whitelist(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert ("Using whitelist `['VolumePairList']` with 4 pairs\n" - "`ETH/BTC, LTC/BTC, XRP/BTC, NEO/BTC`" in msg_mock.call_args_list[0][0][0]) + assert ( + "Using whitelist `['VolumePairList']` with 4 pairs\n" + "`ETH/BTC, LTC/BTC, XRP/BTC, NEO/BTC`" in msg_mock.call_args_list[0][0][0] + ) context = MagicMock() - context.args = ['sorted'] + context.args = ["sorted"] msg_mock.reset_mock() await telegram._whitelist(update=update, context=context) - assert ("Using whitelist `['VolumePairList']` with 4 pairs\n" - "`ETH/BTC, LTC/BTC, NEO/BTC, XRP/BTC`" in msg_mock.call_args_list[0][0][0]) + assert ( + "Using whitelist `['VolumePairList']` with 4 pairs\n" + "`ETH/BTC, LTC/BTC, NEO/BTC, XRP/BTC`" in msg_mock.call_args_list[0][0][0] + ) context = MagicMock() - context.args = ['baseonly'] + context.args = ["baseonly"] msg_mock.reset_mock() await telegram._whitelist(update=update, context=context) - assert ("Using whitelist `['VolumePairList']` with 4 pairs\n" - "`ETH, LTC, XRP, NEO`" in msg_mock.call_args_list[0][0][0]) + assert ( + "Using whitelist `['VolumePairList']` with 4 pairs\n" + "`ETH, LTC, XRP, NEO`" in msg_mock.call_args_list[0][0][0] + ) context = MagicMock() - context.args = ['baseonly', 'sorted'] + context.args = ["baseonly", "sorted"] msg_mock.reset_mock() await telegram._whitelist(update=update, context=context) - assert ("Using whitelist `['VolumePairList']` with 4 pairs\n" - "`ETH, LTC, NEO, XRP`" in msg_mock.call_args_list[0][0][0]) + assert ( + "Using whitelist `['VolumePairList']` with 4 pairs\n" + "`ETH, LTC, NEO, XRP`" in msg_mock.call_args_list[0][0][0] + ) async def test_blacklist_static(default_conf, update, mocker) -> None: - telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) await telegram._blacklist(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert ("Blacklist contains 2 pairs\n`DOGE/BTC, HOT/BTC`" - in msg_mock.call_args_list[0][0][0]) + assert "Blacklist contains 2 pairs\n`DOGE/BTC, HOT/BTC`" in msg_mock.call_args_list[0][0][0] msg_mock.reset_mock() @@ -1749,8 +1779,10 @@ async def test_blacklist_static(default_conf, update, mocker) -> None: context.args = ["ETH/BTC"] await telegram._blacklist(update=update, context=context) assert msg_mock.call_count == 1 - assert ("Blacklist contains 3 pairs\n`DOGE/BTC, HOT/BTC, ETH/BTC`" - in msg_mock.call_args_list[0][0][0]) + assert ( + "Blacklist contains 3 pairs\n`DOGE/BTC, HOT/BTC, ETH/BTC`" + in msg_mock.call_args_list[0][0][0] + ) assert freqtradebot.pairlists.blacklist == ["DOGE/BTC", "HOT/BTC", "ETH/BTC"] msg_mock.reset_mock() @@ -1759,21 +1791,24 @@ async def test_blacklist_static(default_conf, update, mocker) -> None: await telegram._blacklist(update=update, context=context) assert msg_mock.call_count == 1 - assert ("Blacklist contains 4 pairs\n`DOGE/BTC, HOT/BTC, ETH/BTC, XRP/.*`" - in msg_mock.call_args_list[0][0][0]) + assert ( + "Blacklist contains 4 pairs\n`DOGE/BTC, HOT/BTC, ETH/BTC, XRP/.*`" + in msg_mock.call_args_list[0][0][0] + ) assert freqtradebot.pairlists.blacklist == ["DOGE/BTC", "HOT/BTC", "ETH/BTC", "XRP/.*"] msg_mock.reset_mock() context.args = ["DOGE/BTC"] await telegram._blacklist_delete(update=update, context=context) assert msg_mock.call_count == 1 - assert ("Blacklist contains 3 pairs\n`HOT/BTC, ETH/BTC, XRP/.*`" - in msg_mock.call_args_list[0][0][0]) + assert ( + "Blacklist contains 3 pairs\n`HOT/BTC, ETH/BTC, XRP/.*`" in msg_mock.call_args_list[0][0][0] + ) async def test_telegram_logs(default_conf, update, mocker) -> None: mocker.patch.multiple( - 'freqtrade.rpc.telegram.Telegram', + "freqtrade.rpc.telegram.Telegram", _init=MagicMock(), ) setup_logging(default_conf) @@ -1793,7 +1828,7 @@ async def test_telegram_logs(default_conf, update, mocker) -> None: msg_mock.reset_mock() # Test with changed MaxMessageLength - mocker.patch('freqtrade.rpc.telegram.MAX_MESSAGE_LENGTH', 200) + mocker.patch("freqtrade.rpc.telegram.MAX_MESSAGE_LENGTH", 200) context = MagicMock() context.args = [] await telegram._logs(update=update, context=context) @@ -1803,7 +1838,6 @@ async def test_telegram_logs(default_conf, update, mocker) -> None: async def test_edge_disabled(default_conf, update, mocker) -> None: - telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) await telegram._edge(update=update, context=MagicMock()) @@ -1812,34 +1846,36 @@ async def test_edge_disabled(default_conf, update, mocker) -> None: async def test_edge_enabled(edge_conf, update, mocker) -> None: - mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( - return_value={ - 'E/F': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60), - } - )) + mocker.patch( + "freqtrade.edge.Edge._cached_pairs", + mocker.PropertyMock( + return_value={ + "E/F": PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60), + } + ), + ) telegram, _, msg_mock = get_telegram_testobject(mocker, edge_conf) await telegram._edge(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert 'Edge only validated following pairs:\n
' in msg_mock.call_args_list[0][0][0]
-    assert 'Pair      Winrate    Expectancy    Stoploss' in msg_mock.call_args_list[0][0][0]
+    assert "Edge only validated following pairs:\n
" in msg_mock.call_args_list[0][0][0]
+    assert "Pair      Winrate    Expectancy    Stoploss" in msg_mock.call_args_list[0][0][0]
 
     msg_mock.reset_mock()
 
-    mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
-        return_value={}))
+    mocker.patch("freqtrade.edge.Edge._cached_pairs", mocker.PropertyMock(return_value={}))
     await telegram._edge(update=update, context=MagicMock())
     assert msg_mock.call_count == 1
-    assert 'Edge only validated following pairs:' in msg_mock.call_args_list[0][0][0]
-    assert 'Winrate' not in msg_mock.call_args_list[0][0][0]
+    assert "Edge only validated following pairs:" in msg_mock.call_args_list[0][0][0]
+    assert "Winrate" not in msg_mock.call_args_list[0][0][0]
 
 
-@pytest.mark.parametrize('is_short,regex_pattern',
-                         [(True, r"now[ ]*XRP\/BTC \(#3\)  -1.00% \("),
-                          (False, r"now[ ]*XRP\/BTC \(#3\)  1.00% \(")])
+@pytest.mark.parametrize(
+    "is_short,regex_pattern",
+    [(True, r"now[ ]*XRP\/BTC \(#3\)  -1.00% \("), (False, r"now[ ]*XRP\/BTC \(#3\)  1.00% \(")],
+)
 async def test_telegram_trades(mocker, update, default_conf, fee, is_short, regex_pattern):
-
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
 
     context = MagicMock()
@@ -1850,7 +1886,7 @@ async def test_telegram_trades(mocker, update, default_conf, fee, is_short, rege
     assert "
" not in msg_mock.call_args_list[0][0][0]
     msg_mock.reset_mock()
 
-    context.args = ['hello']
+    context.args = ["hello"]
     await telegram._trades(update=update, context=context)
     assert "0 recent trades:" in msg_mock.call_args_list[0][0][0]
     assert "
" not in msg_mock.call_args_list[0][0][0]
@@ -1869,9 +1905,8 @@ async def test_telegram_trades(mocker, update, default_conf, fee, is_short, rege
     assert bool(re.search(regex_pattern, msg_mock.call_args_list[0][0][0]))
 
 
-@pytest.mark.parametrize('is_short', [True, False])
+@pytest.mark.parametrize("is_short", [True, False])
 async def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short):
-
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
     context = MagicMock()
     context.args = []
@@ -1890,9 +1925,8 @@ async def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short
     assert "Please make sure to take care of this asset" in msg_mock.call_args_list[0][0][0]
 
 
-@pytest.mark.parametrize('is_short', [True, False])
+@pytest.mark.parametrize("is_short", [True, False])
 async def test_telegram_reload_trade_from_exchange(mocker, update, default_conf, fee, is_short):
-
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
     context = MagicMock()
     context.args = []
@@ -1909,9 +1943,8 @@ async def test_telegram_reload_trade_from_exchange(mocker, update, default_conf,
     assert "Status: `Reloaded from orders from exchange`" in msg_mock.call_args_list[0][0][0]
 
 
-@pytest.mark.parametrize('is_short', [True, False])
+@pytest.mark.parametrize("is_short", [True, False])
 async def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short, ticker):
-
     mocker.patch.multiple(
         EXMS,
         fetch_ticker=ticker,
@@ -1934,7 +1967,7 @@ async def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_
     msg_mock.reset_mock()
 
     trade = Trade.get_trades([Trade.id == 6]).first()
-    mocker.patch(f'{EXMS}.fetch_order', return_value=trade.orders[-1].to_ccxt_object())
+    mocker.patch(f"{EXMS}.fetch_order", return_value=trade.orders[-1].to_ccxt_object())
     context = MagicMock()
     context.args = [6]
     await telegram._cancel_open_order(update=update, context=context)
@@ -1947,611 +1980,655 @@ async def test_help_handle(default_conf, update, mocker) -> None:
 
     await telegram._help(update=update, context=MagicMock())
     assert msg_mock.call_count == 1
-    assert '*/help:* `This help message`' in msg_mock.call_args_list[0][0][0]
+    assert "*/help:* `This help message`" in msg_mock.call_args_list[0][0][0]
 
 
 async def test_version_handle(default_conf, update, mocker) -> None:
-
     telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
 
     await telegram._version(update=update, context=MagicMock())
     assert msg_mock.call_count == 1
-    assert f'*Version:* `{__version__}`' in msg_mock.call_args_list[0][0][0]
+    assert f"*Version:* `{__version__}`" in msg_mock.call_args_list[0][0][0]
 
     msg_mock.reset_mock()
-    freqtradebot.strategy.version = lambda: '1.1.1'
+    freqtradebot.strategy.version = lambda: "1.1.1"
 
     await telegram._version(update=update, context=MagicMock())
     assert msg_mock.call_count == 1
-    assert f'*Version:* `{__version__}`' in msg_mock.call_args_list[0][0][0]
-    assert '*Strategy version: * `1.1.1`' in msg_mock.call_args_list[0][0][0]
+    assert f"*Version:* `{__version__}`" in msg_mock.call_args_list[0][0][0]
+    assert "*Strategy version: * `1.1.1`" in msg_mock.call_args_list[0][0][0]
 
 
 async def test_show_config_handle(default_conf, update, mocker) -> None:
-
-    default_conf['runmode'] = RunMode.DRY_RUN
+    default_conf["runmode"] = RunMode.DRY_RUN
 
     telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
 
     await telegram._show_config(update=update, context=MagicMock())
     assert msg_mock.call_count == 1
-    assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
-    assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
-    assert f'*Strategy:* `{CURRENT_TEST_STRATEGY}`' in msg_mock.call_args_list[0][0][0]
-    assert '*Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
+    assert "*Mode:* `{}`".format("Dry-run") in msg_mock.call_args_list[0][0][0]
+    assert "*Exchange:* `binance`" in msg_mock.call_args_list[0][0][0]
+    assert f"*Strategy:* `{CURRENT_TEST_STRATEGY}`" in msg_mock.call_args_list[0][0][0]
+    assert "*Stoploss:* `-0.1`" in msg_mock.call_args_list[0][0][0]
 
     msg_mock.reset_mock()
-    freqtradebot.config['trailing_stop'] = True
+    freqtradebot.config["trailing_stop"] = True
     await telegram._show_config(update=update, context=MagicMock())
     assert msg_mock.call_count == 1
-    assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
-    assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
-    assert f'*Strategy:* `{CURRENT_TEST_STRATEGY}`' in msg_mock.call_args_list[0][0][0]
-    assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
+    assert "*Mode:* `{}`".format("Dry-run") in msg_mock.call_args_list[0][0][0]
+    assert "*Exchange:* `binance`" in msg_mock.call_args_list[0][0][0]
+    assert f"*Strategy:* `{CURRENT_TEST_STRATEGY}`" in msg_mock.call_args_list[0][0][0]
+    assert "*Initial Stoploss:* `-0.1`" in msg_mock.call_args_list[0][0][0]
 
 
-@pytest.mark.parametrize('message_type,enter,enter_signal,leverage', [
-    (RPCMessageType.ENTRY, 'Long', 'long_signal_01', None),
-    (RPCMessageType.ENTRY, 'Long', 'long_signal_01', 1.0),
-    (RPCMessageType.ENTRY, 'Long', 'long_signal_01', 5.0),
-    (RPCMessageType.ENTRY, 'Short', 'short_signal_01', 2.0)])
-def test_send_msg_enter_notification(default_conf, mocker, caplog, message_type,
-                                     enter, enter_signal, leverage) -> None:
-    default_conf['telegram']['notification_settings']['show_candle'] = 'ohlc'
-    df = DataFrame({
-        'open': [1.1],
-        'high': [2.2],
-        'low': [1.0],
-        'close': [1.5],
-    })
-    mocker.patch('freqtrade.data.dataprovider.DataProvider.get_analyzed_dataframe',
-                 return_value=(df, 1))
+@pytest.mark.parametrize(
+    "message_type,enter,enter_signal,leverage",
+    [
+        (RPCMessageType.ENTRY, "Long", "long_signal_01", None),
+        (RPCMessageType.ENTRY, "Long", "long_signal_01", 1.0),
+        (RPCMessageType.ENTRY, "Long", "long_signal_01", 5.0),
+        (RPCMessageType.ENTRY, "Short", "short_signal_01", 2.0),
+    ],
+)
+def test_send_msg_enter_notification(
+    default_conf, mocker, caplog, message_type, enter, enter_signal, leverage
+) -> None:
+    default_conf["telegram"]["notification_settings"]["show_candle"] = "ohlc"
+    df = DataFrame(
+        {
+            "open": [1.1],
+            "high": [2.2],
+            "low": [1.0],
+            "close": [1.5],
+        }
+    )
+    mocker.patch(
+        "freqtrade.data.dataprovider.DataProvider.get_analyzed_dataframe", return_value=(df, 1)
+    )
 
     msg = {
-        'type': message_type,
-        'trade_id': 1,
-        'enter_tag': enter_signal,
-        'exchange': 'Binance',
-        'pair': 'ETH/BTC',
-        'leverage': leverage,
-        'open_rate': 1.099e-05,
-        'order_type': 'limit',
-        'direction': enter,
-        'stake_amount': 0.01465333,
-        'stake_amount_fiat': 0.0,
-        'stake_currency': 'BTC',
-        'quote_currency': 'BTC',
-        'base_currency': 'ETH',
-        'fiat_currency': 'USD',
-        'sub_trade': False,
-        'current_rate': 1.099e-05,
-        'amount': 1333.3333333333335,
-        'analyzed_candle': {'open': 1.1, 'high': 2.2, 'low': 1.0, 'close': 1.5},
-        'open_date': dt_now() + timedelta(hours=-1)
+        "type": message_type,
+        "trade_id": 1,
+        "enter_tag": enter_signal,
+        "exchange": "Binance",
+        "pair": "ETH/BTC",
+        "leverage": leverage,
+        "open_rate": 1.099e-05,
+        "order_type": "limit",
+        "direction": enter,
+        "stake_amount": 0.01465333,
+        "stake_amount_fiat": 0.0,
+        "stake_currency": "BTC",
+        "quote_currency": "BTC",
+        "base_currency": "ETH",
+        "fiat_currency": "USD",
+        "sub_trade": False,
+        "current_rate": 1.099e-05,
+        "amount": 1333.3333333333335,
+        "analyzed_candle": {"open": 1.1, "high": 2.2, "low": 1.0, "close": 1.5},
+        "open_date": dt_now() + timedelta(hours=-1),
     }
     telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
 
     telegram.send_msg(msg)
-    leverage_text = f' ({leverage:.3g}x)' if leverage and leverage != 1.0 else ''
+    leverage_text = f" ({leverage:.3g}x)" if leverage and leverage != 1.0 else ""
 
     assert msg_mock.call_args[0][0] == (
-        f'\N{LARGE BLUE CIRCLE} *Binance (dry):* New Trade (#1)\n'
-        f'*Pair:* `ETH/BTC`\n'
-        '*Candle OHLC*: `1.1, 2.2, 1.0, 1.5`\n'
-        f'*Enter Tag:* `{enter_signal}`\n'
-        '*Amount:* `1333.33333333`\n'
-        f'*Direction:* `{enter}'
-        f'{leverage_text}`\n'
-        '*Open Rate:* `0.00001099 BTC`\n'
-        '*Current Rate:* `0.00001099 BTC`\n'
-        '*Total:* `0.01465333 BTC / 180.895 USD`'
+        f"\N{LARGE BLUE CIRCLE} *Binance (dry):* New Trade (#1)\n"
+        f"*Pair:* `ETH/BTC`\n"
+        "*Candle OHLC*: `1.1, 2.2, 1.0, 1.5`\n"
+        f"*Enter Tag:* `{enter_signal}`\n"
+        "*Amount:* `1333.33333333`\n"
+        f"*Direction:* `{enter}"
+        f"{leverage_text}`\n"
+        "*Open Rate:* `0.00001099 BTC`\n"
+        "*Current Rate:* `0.00001099 BTC`\n"
+        "*Total:* `0.01465333 BTC / 180.895 USD`"
     )
 
-    freqtradebot.config['telegram']['notification_settings'] = {'entry': 'off'}
+    freqtradebot.config["telegram"]["notification_settings"] = {"entry": "off"}
     caplog.clear()
     msg_mock.reset_mock()
     telegram.send_msg(msg)
     assert msg_mock.call_count == 0
     assert log_has("Notification 'entry' not sent.", caplog)
 
-    freqtradebot.config['telegram']['notification_settings'] = {'entry': 'silent'}
+    freqtradebot.config["telegram"]["notification_settings"] = {"entry": "silent"}
     caplog.clear()
     msg_mock.reset_mock()
 
     telegram.send_msg(msg)
     assert msg_mock.call_count == 1
-    assert msg_mock.call_args_list[0][1]['disable_notification'] is True
+    assert msg_mock.call_args_list[0][1]["disable_notification"] is True
 
 
-@pytest.mark.parametrize('message_type,enter_signal', [
-    (RPCMessageType.ENTRY_CANCEL, 'long_signal_01'),
-    (RPCMessageType.ENTRY_CANCEL, 'short_signal_01')])
+@pytest.mark.parametrize(
+    "message_type,enter_signal",
+    [
+        (RPCMessageType.ENTRY_CANCEL, "long_signal_01"),
+        (RPCMessageType.ENTRY_CANCEL, "short_signal_01"),
+    ],
+)
 def test_send_msg_enter_cancel_notification(
-        default_conf, mocker, message_type, enter_signal) -> None:
-
+    default_conf, mocker, message_type, enter_signal
+) -> None:
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
 
-    telegram.send_msg({
-        'type': message_type,
-        'enter_tag': enter_signal,
-        'trade_id': 1,
-        'exchange': 'Binance',
-        'pair': 'ETH/BTC',
-        'reason': CANCEL_REASON['TIMEOUT']
-    })
-    assert (msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Binance (dry):* '
-            'Cancelling enter Order for ETH/BTC (#1). '
-            'Reason: cancelled due to timeout.')
+    telegram.send_msg(
+        {
+            "type": message_type,
+            "enter_tag": enter_signal,
+            "trade_id": 1,
+            "exchange": "Binance",
+            "pair": "ETH/BTC",
+            "reason": CANCEL_REASON["TIMEOUT"],
+        }
+    )
+    assert (
+        msg_mock.call_args[0][0] == "\N{WARNING SIGN} *Binance (dry):* "
+        "Cancelling enter Order for ETH/BTC (#1). "
+        "Reason: cancelled due to timeout."
+    )
 
 
 def test_send_msg_protection_notification(default_conf, mocker, time_machine) -> None:
-
-    default_conf['telegram']['notification_settings']['protection_trigger'] = 'on'
+    default_conf["telegram"]["notification_settings"]["protection_trigger"] = "on"
 
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
     time_machine.move_to("2021-09-01 05:00:00 +00:00")
-    lock = PairLocks.lock_pair('ETH/BTC', dt_now() + timedelta(minutes=6), 'randreason')
+    lock = PairLocks.lock_pair("ETH/BTC", dt_now() + timedelta(minutes=6), "randreason")
     msg = {
-        'type': RPCMessageType.PROTECTION_TRIGGER,
+        "type": RPCMessageType.PROTECTION_TRIGGER,
     }
     msg.update(lock.to_json())
     telegram.send_msg(msg)
-    assert (msg_mock.call_args[0][0] == "*Protection* triggered due to randreason. "
-            "`ETH/BTC` will be locked until `2021-09-01 05:10:00`.")
+    assert (
+        msg_mock.call_args[0][0] == "*Protection* triggered due to randreason. "
+        "`ETH/BTC` will be locked until `2021-09-01 05:10:00`."
+    )
 
     msg_mock.reset_mock()
     # Test global protection
 
     msg = {
-        'type': RPCMessageType.PROTECTION_TRIGGER_GLOBAL,
+        "type": RPCMessageType.PROTECTION_TRIGGER_GLOBAL,
     }
-    lock = PairLocks.lock_pair('*', dt_now() + timedelta(minutes=100), 'randreason')
+    lock = PairLocks.lock_pair("*", dt_now() + timedelta(minutes=100), "randreason")
     msg.update(lock.to_json())
     telegram.send_msg(msg)
-    assert (msg_mock.call_args[0][0] == "*Protection* triggered due to randreason. "
-            "*All pairs* will be locked until `2021-09-01 06:45:00`.")
+    assert (
+        msg_mock.call_args[0][0] == "*Protection* triggered due to randreason. "
+        "*All pairs* will be locked until `2021-09-01 06:45:00`."
+    )
 
 
-@pytest.mark.parametrize('message_type,entered,enter_signal,leverage', [
-    (RPCMessageType.ENTRY_FILL, 'Long', 'long_signal_01', 1.0),
-    (RPCMessageType.ENTRY_FILL, 'Long', 'long_signal_02', 2.0),
-    (RPCMessageType.ENTRY_FILL, 'Short', 'short_signal_01', 2.0),
-])
-def test_send_msg_entry_fill_notification(default_conf, mocker, message_type, entered,
-                                          enter_signal, leverage) -> None:
-
-    default_conf['telegram']['notification_settings']['entry_fill'] = 'on'
+@pytest.mark.parametrize(
+    "message_type,entered,enter_signal,leverage",
+    [
+        (RPCMessageType.ENTRY_FILL, "Long", "long_signal_01", 1.0),
+        (RPCMessageType.ENTRY_FILL, "Long", "long_signal_02", 2.0),
+        (RPCMessageType.ENTRY_FILL, "Short", "short_signal_01", 2.0),
+    ],
+)
+def test_send_msg_entry_fill_notification(
+    default_conf, mocker, message_type, entered, enter_signal, leverage
+) -> None:
+    default_conf["telegram"]["notification_settings"]["entry_fill"] = "on"
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
 
-    telegram.send_msg({
-        'type': message_type,
-        'trade_id': 1,
-        'enter_tag': enter_signal,
-        'exchange': 'Binance',
-        'pair': 'ETH/BTC',
-        'leverage': leverage,
-        'stake_amount': 0.01465333,
-        'direction': entered,
-        'sub_trade': False,
-        'stake_currency': 'BTC',
-        'quote_currency': 'BTC',
-        'base_currency': 'ETH',
-        'fiat_currency': 'USD',
-        'open_rate': 1.099e-05,
-        'amount': 1333.3333333333335,
-        'open_date': dt_now() - timedelta(hours=1)
-    })
-    leverage_text = f' ({leverage:.3g}x)' if leverage != 1.0 else ''
+    telegram.send_msg(
+        {
+            "type": message_type,
+            "trade_id": 1,
+            "enter_tag": enter_signal,
+            "exchange": "Binance",
+            "pair": "ETH/BTC",
+            "leverage": leverage,
+            "stake_amount": 0.01465333,
+            "direction": entered,
+            "sub_trade": False,
+            "stake_currency": "BTC",
+            "quote_currency": "BTC",
+            "base_currency": "ETH",
+            "fiat_currency": "USD",
+            "open_rate": 1.099e-05,
+            "amount": 1333.3333333333335,
+            "open_date": dt_now() - timedelta(hours=1),
+        }
+    )
+    leverage_text = f" ({leverage:.3g}x)" if leverage != 1.0 else ""
     assert msg_mock.call_args[0][0] == (
-        f'\N{CHECK MARK} *Binance (dry):* New Trade filled (#1)\n'
-        f'*Pair:* `ETH/BTC`\n'
-        f'*Enter Tag:* `{enter_signal}`\n'
-        '*Amount:* `1333.33333333`\n'
-        f'*Direction:* `{entered}'
+        f"\N{CHECK MARK} *Binance (dry):* New Trade filled (#1)\n"
+        f"*Pair:* `ETH/BTC`\n"
+        f"*Enter Tag:* `{enter_signal}`\n"
+        "*Amount:* `1333.33333333`\n"
+        f"*Direction:* `{entered}"
         f"{leverage_text}`\n"
-        '*Open Rate:* `0.00001099 BTC`\n'
-        '*Total:* `0.01465333 BTC / 180.895 USD`'
+        "*Open Rate:* `0.00001099 BTC`\n"
+        "*Total:* `0.01465333 BTC / 180.895 USD`"
     )
 
     msg_mock.reset_mock()
-    telegram.send_msg({
-        'type': message_type,
-        'trade_id': 1,
-        'enter_tag': enter_signal,
-        'exchange': 'Binance',
-        'pair': 'ETH/BTC',
-        'leverage': leverage,
-        'stake_amount': 0.01465333,
-        'sub_trade': True,
-        'direction': entered,
-        'stake_currency': 'BTC',
-        'quote_currency': 'BTC',
-        'base_currency': 'ETH',
-        'fiat_currency': 'USD',
-        'open_rate': 1.099e-05,
-        'amount': 1333.3333333333335,
-        'open_date': dt_now() - timedelta(hours=1)
-    })
+    telegram.send_msg(
+        {
+            "type": message_type,
+            "trade_id": 1,
+            "enter_tag": enter_signal,
+            "exchange": "Binance",
+            "pair": "ETH/BTC",
+            "leverage": leverage,
+            "stake_amount": 0.01465333,
+            "sub_trade": True,
+            "direction": entered,
+            "stake_currency": "BTC",
+            "quote_currency": "BTC",
+            "base_currency": "ETH",
+            "fiat_currency": "USD",
+            "open_rate": 1.099e-05,
+            "amount": 1333.3333333333335,
+            "open_date": dt_now() - timedelta(hours=1),
+        }
+    )
 
     assert msg_mock.call_args[0][0] == (
-        f'\N{CHECK MARK} *Binance (dry):* Position increase filled (#1)\n'
-        f'*Pair:* `ETH/BTC`\n'
-        f'*Enter Tag:* `{enter_signal}`\n'
-        '*Amount:* `1333.33333333`\n'
-        f'*Direction:* `{entered}'
+        f"\N{CHECK MARK} *Binance (dry):* Position increase filled (#1)\n"
+        f"*Pair:* `ETH/BTC`\n"
+        f"*Enter Tag:* `{enter_signal}`\n"
+        "*Amount:* `1333.33333333`\n"
+        f"*Direction:* `{entered}"
         f"{leverage_text}`\n"
-        '*Open Rate:* `0.00001099 BTC`\n'
-        '*New Total:* `0.01465333 BTC / 180.895 USD`'
+        "*Open Rate:* `0.00001099 BTC`\n"
+        "*New Total:* `0.01465333 BTC / 180.895 USD`"
     )
 
 
 def test_send_msg_exit_notification(default_conf, mocker) -> None:
-
     with time_machine.travel("2022-09-01 05:00:00 +00:00", tick=False):
         telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
 
         old_convamount = telegram._rpc._fiat_converter.convert_amount
         telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812
-        telegram.send_msg({
-            'type': RPCMessageType.EXIT,
-            'trade_id': 1,
-            'exchange': 'Binance',
-            'pair': 'KEY/ETH',
-            'leverage': 1.0,
-            'direction': 'Long',
-            'gain': 'loss',
-            'order_rate': 3.201e-04,
-            'amount': 1333.3333333333335,
-            'order_type': 'market',
-            'open_rate': 7.5e-04,
-            'current_rate': 3.201e-04,
-            'profit_amount': -0.05746268,
-            'profit_ratio': -0.57405275,
-            'stake_currency': 'ETH',
-            'quote_currency': 'ETH',
-            'base_currency': 'KEY',
-            'fiat_currency': 'USD',
-            'enter_tag': 'buy_signal1',
-            'exit_reason': ExitType.STOP_LOSS.value,
-            'open_date': dt_now() - timedelta(hours=1),
-            'close_date': dt_now(),
-        })
+        telegram.send_msg(
+            {
+                "type": RPCMessageType.EXIT,
+                "trade_id": 1,
+                "exchange": "Binance",
+                "pair": "KEY/ETH",
+                "leverage": 1.0,
+                "direction": "Long",
+                "gain": "loss",
+                "order_rate": 3.201e-04,
+                "amount": 1333.3333333333335,
+                "order_type": "market",
+                "open_rate": 7.5e-04,
+                "current_rate": 3.201e-04,
+                "profit_amount": -0.05746268,
+                "profit_ratio": -0.57405275,
+                "stake_currency": "ETH",
+                "quote_currency": "ETH",
+                "base_currency": "KEY",
+                "fiat_currency": "USD",
+                "enter_tag": "buy_signal1",
+                "exit_reason": ExitType.STOP_LOSS.value,
+                "open_date": dt_now() - timedelta(hours=1),
+                "close_date": dt_now(),
+            }
+        )
         assert msg_mock.call_args[0][0] == (
-            '\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
-            '*Unrealized Profit:* `-57.41% (loss: -0.05746 ETH / -24.812 USD)`\n'
-            '*Enter Tag:* `buy_signal1`\n'
-            '*Exit Reason:* `stop_loss`\n'
-            '*Direction:* `Long`\n'
-            '*Amount:* `1333.33333333`\n'
-            '*Open Rate:* `0.00075 ETH`\n'
-            '*Current Rate:* `0.00032 ETH`\n'
-            '*Exit Rate:* `0.00032 ETH`\n'
-            '*Duration:* `1:00:00 (60.0 min)`'
+            "\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n"
+            "*Unrealized Profit:* `-57.41% (loss: -0.05746 ETH / -24.812 USD)`\n"
+            "*Enter Tag:* `buy_signal1`\n"
+            "*Exit Reason:* `stop_loss`\n"
+            "*Direction:* `Long`\n"
+            "*Amount:* `1333.33333333`\n"
+            "*Open Rate:* `0.00075 ETH`\n"
+            "*Current Rate:* `0.00032 ETH`\n"
+            "*Exit Rate:* `0.00032 ETH`\n"
+            "*Duration:* `1:00:00 (60.0 min)`"
         )
 
         msg_mock.reset_mock()
-        telegram.send_msg({
-            'type': RPCMessageType.EXIT,
-            'trade_id': 1,
-            'exchange': 'Binance',
-            'pair': 'KEY/ETH',
-            'direction': 'Long',
-            'gain': 'loss',
-            'order_rate': 3.201e-04,
-            'amount': 1333.3333333333335,
-            'order_type': 'market',
-            'open_rate': 7.5e-04,
-            'current_rate': 3.201e-04,
-            'cumulative_profit': -0.15746268,
-            'profit_amount': -0.05746268,
-            'profit_ratio': -0.57405275,
-            'stake_currency': 'ETH',
-            'quote_currency': 'ETH',
-            'base_currency': 'KEY',
-            'fiat_currency': 'USD',
-            'enter_tag': 'buy_signal1',
-            'exit_reason': ExitType.STOP_LOSS.value,
-            'open_date': dt_now() - timedelta(days=1, hours=2, minutes=30),
-            'close_date': dt_now(),
-            'stake_amount': 0.01,
-            'sub_trade': True,
-        })
+        telegram.send_msg(
+            {
+                "type": RPCMessageType.EXIT,
+                "trade_id": 1,
+                "exchange": "Binance",
+                "pair": "KEY/ETH",
+                "direction": "Long",
+                "gain": "loss",
+                "order_rate": 3.201e-04,
+                "amount": 1333.3333333333335,
+                "order_type": "market",
+                "open_rate": 7.5e-04,
+                "current_rate": 3.201e-04,
+                "cumulative_profit": -0.15746268,
+                "profit_amount": -0.05746268,
+                "profit_ratio": -0.57405275,
+                "stake_currency": "ETH",
+                "quote_currency": "ETH",
+                "base_currency": "KEY",
+                "fiat_currency": "USD",
+                "enter_tag": "buy_signal1",
+                "exit_reason": ExitType.STOP_LOSS.value,
+                "open_date": dt_now() - timedelta(days=1, hours=2, minutes=30),
+                "close_date": dt_now(),
+                "stake_amount": 0.01,
+                "sub_trade": True,
+            }
+        )
         assert msg_mock.call_args[0][0] == (
-            '\N{WARNING SIGN} *Binance (dry):* Partially exiting KEY/ETH (#1)\n'
-            '*Unrealized Sub Profit:* `-57.41% (loss: -0.05746 ETH / -24.812 USD)`\n'
-            '*Cumulative Profit:* `-0.15746 ETH / -24.812 USD`\n'
-            '*Enter Tag:* `buy_signal1`\n'
-            '*Exit Reason:* `stop_loss`\n'
-            '*Direction:* `Long`\n'
-            '*Amount:* `1333.33333333`\n'
-            '*Open Rate:* `0.00075 ETH`\n'
-            '*Current Rate:* `0.00032 ETH`\n'
-            '*Exit Rate:* `0.00032 ETH`\n'
-            '*Remaining:* `0.01 ETH / -24.812 USD`'
-            )
+            "\N{WARNING SIGN} *Binance (dry):* Partially exiting KEY/ETH (#1)\n"
+            "*Unrealized Sub Profit:* `-57.41% (loss: -0.05746 ETH / -24.812 USD)`\n"
+            "*Cumulative Profit:* `-0.15746 ETH / -24.812 USD`\n"
+            "*Enter Tag:* `buy_signal1`\n"
+            "*Exit Reason:* `stop_loss`\n"
+            "*Direction:* `Long`\n"
+            "*Amount:* `1333.33333333`\n"
+            "*Open Rate:* `0.00075 ETH`\n"
+            "*Current Rate:* `0.00032 ETH`\n"
+            "*Exit Rate:* `0.00032 ETH`\n"
+            "*Remaining:* `0.01 ETH / -24.812 USD`"
+        )
 
         msg_mock.reset_mock()
-        telegram.send_msg({
-            'type': RPCMessageType.EXIT,
-            'trade_id': 1,
-            'exchange': 'Binance',
-            'pair': 'KEY/ETH',
-            'direction': 'Long',
-            'gain': 'loss',
-            'order_rate': 3.201e-04,
-            'amount': 1333.3333333333335,
-            'order_type': 'market',
-            'open_rate': 7.5e-04,
-            'current_rate': 3.201e-04,
-            'profit_amount': -0.05746268,
-            'profit_ratio': -0.57405275,
-            'stake_currency': 'ETH',
-            'quote_currency': 'ETH',
-            'base_currency': 'KEY',
-            'fiat_currency': None,
-            'enter_tag': 'buy_signal1',
-            'exit_reason': ExitType.STOP_LOSS.value,
-            'open_date': dt_now() - timedelta(days=1, hours=2, minutes=30),
-            'close_date': dt_now(),
-        })
+        telegram.send_msg(
+            {
+                "type": RPCMessageType.EXIT,
+                "trade_id": 1,
+                "exchange": "Binance",
+                "pair": "KEY/ETH",
+                "direction": "Long",
+                "gain": "loss",
+                "order_rate": 3.201e-04,
+                "amount": 1333.3333333333335,
+                "order_type": "market",
+                "open_rate": 7.5e-04,
+                "current_rate": 3.201e-04,
+                "profit_amount": -0.05746268,
+                "profit_ratio": -0.57405275,
+                "stake_currency": "ETH",
+                "quote_currency": "ETH",
+                "base_currency": "KEY",
+                "fiat_currency": None,
+                "enter_tag": "buy_signal1",
+                "exit_reason": ExitType.STOP_LOSS.value,
+                "open_date": dt_now() - timedelta(days=1, hours=2, minutes=30),
+                "close_date": dt_now(),
+            }
+        )
         assert msg_mock.call_args[0][0] == (
-            '\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
-            '*Unrealized Profit:* `-57.41% (loss: -0.05746 ETH)`\n'
-            '*Enter Tag:* `buy_signal1`\n'
-            '*Exit Reason:* `stop_loss`\n'
-            '*Direction:* `Long`\n'
-            '*Amount:* `1333.33333333`\n'
-            '*Open Rate:* `0.00075 ETH`\n'
-            '*Current Rate:* `0.00032 ETH`\n'
-            '*Exit Rate:* `0.00032 ETH`\n'
-            '*Duration:* `1 day, 2:30:00 (1590.0 min)`'
+            "\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n"
+            "*Unrealized Profit:* `-57.41% (loss: -0.05746 ETH)`\n"
+            "*Enter Tag:* `buy_signal1`\n"
+            "*Exit Reason:* `stop_loss`\n"
+            "*Direction:* `Long`\n"
+            "*Amount:* `1333.33333333`\n"
+            "*Open Rate:* `0.00075 ETH`\n"
+            "*Current Rate:* `0.00032 ETH`\n"
+            "*Exit Rate:* `0.00032 ETH`\n"
+            "*Duration:* `1 day, 2:30:00 (1590.0 min)`"
         )
         # Reset singleton function to avoid random breaks
         telegram._rpc._fiat_converter.convert_amount = old_convamount
 
 
 async def test_send_msg_exit_cancel_notification(default_conf, mocker) -> None:
-
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
 
     old_convamount = telegram._rpc._fiat_converter.convert_amount
     telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812
-    telegram.send_msg({
-        'type': RPCMessageType.EXIT_CANCEL,
-        'trade_id': 1,
-        'exchange': 'Binance',
-        'pair': 'KEY/ETH',
-        'reason': 'Cancelled on exchange'
-    })
+    telegram.send_msg(
+        {
+            "type": RPCMessageType.EXIT_CANCEL,
+            "trade_id": 1,
+            "exchange": "Binance",
+            "pair": "KEY/ETH",
+            "reason": "Cancelled on exchange",
+        }
+    )
     assert msg_mock.call_args[0][0] == (
-        '\N{WARNING SIGN} *Binance (dry):* Cancelling exit Order for KEY/ETH (#1).'
-        ' Reason: Cancelled on exchange.')
+        "\N{WARNING SIGN} *Binance (dry):* Cancelling exit Order for KEY/ETH (#1)."
+        " Reason: Cancelled on exchange."
+    )
 
     msg_mock.reset_mock()
     # Test with live mode (no dry appendix)
-    telegram._config['dry_run'] = False
-    telegram.send_msg({
-        'type': RPCMessageType.EXIT_CANCEL,
-        'trade_id': 1,
-        'exchange': 'Binance',
-        'pair': 'KEY/ETH',
-        'reason': 'timeout'
-    })
+    telegram._config["dry_run"] = False
+    telegram.send_msg(
+        {
+            "type": RPCMessageType.EXIT_CANCEL,
+            "trade_id": 1,
+            "exchange": "Binance",
+            "pair": "KEY/ETH",
+            "reason": "timeout",
+        }
+    )
     assert msg_mock.call_args[0][0] == (
-        '\N{WARNING SIGN} *Binance:* Cancelling exit Order for KEY/ETH (#1). Reason: timeout.')
+        "\N{WARNING SIGN} *Binance:* Cancelling exit Order for KEY/ETH (#1). Reason: timeout."
+    )
     # Reset singleton function to avoid random breaks
     telegram._rpc._fiat_converter.convert_amount = old_convamount
 
 
-@pytest.mark.parametrize('direction,enter_signal,leverage', [
-    ('Long', 'long_signal_01', None),
-    ('Long', 'long_signal_01', 1.0),
-    ('Long', 'long_signal_01', 5.0),
-    ('Short', 'short_signal_01', 2.0)])
-def test_send_msg_exit_fill_notification(default_conf, mocker, direction,
-                                         enter_signal, leverage) -> None:
-
-    default_conf['telegram']['notification_settings']['exit_fill'] = 'on'
+@pytest.mark.parametrize(
+    "direction,enter_signal,leverage",
+    [
+        ("Long", "long_signal_01", None),
+        ("Long", "long_signal_01", 1.0),
+        ("Long", "long_signal_01", 5.0),
+        ("Short", "short_signal_01", 2.0),
+    ],
+)
+def test_send_msg_exit_fill_notification(
+    default_conf, mocker, direction, enter_signal, leverage
+) -> None:
+    default_conf["telegram"]["notification_settings"]["exit_fill"] = "on"
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
 
     with time_machine.travel("2022-09-01 05:00:00 +00:00", tick=False):
-        telegram.send_msg({
-            'type': RPCMessageType.EXIT_FILL,
-            'trade_id': 1,
-            'exchange': 'Binance',
-            'pair': 'KEY/ETH',
-            'leverage': leverage,
-            'direction': direction,
-            'gain': 'loss',
-            'limit': 3.201e-04,
-            'amount': 1333.3333333333335,
-            'order_type': 'market',
-            'open_rate': 7.5e-04,
-            'close_rate': 3.201e-04,
-            'profit_amount': -0.05746268,
-            'profit_ratio': -0.57405275,
-            'stake_currency': 'ETH',
-            'quote_currency': 'ETH',
-            'base_currency': 'KEY',
-            'fiat_currency': None,
-            'enter_tag': enter_signal,
-            'exit_reason': ExitType.STOP_LOSS.value,
-            'open_date': dt_now() - timedelta(days=1, hours=2, minutes=30),
-            'close_date': dt_now(),
-        })
+        telegram.send_msg(
+            {
+                "type": RPCMessageType.EXIT_FILL,
+                "trade_id": 1,
+                "exchange": "Binance",
+                "pair": "KEY/ETH",
+                "leverage": leverage,
+                "direction": direction,
+                "gain": "loss",
+                "limit": 3.201e-04,
+                "amount": 1333.3333333333335,
+                "order_type": "market",
+                "open_rate": 7.5e-04,
+                "close_rate": 3.201e-04,
+                "profit_amount": -0.05746268,
+                "profit_ratio": -0.57405275,
+                "stake_currency": "ETH",
+                "quote_currency": "ETH",
+                "base_currency": "KEY",
+                "fiat_currency": None,
+                "enter_tag": enter_signal,
+                "exit_reason": ExitType.STOP_LOSS.value,
+                "open_date": dt_now() - timedelta(days=1, hours=2, minutes=30),
+                "close_date": dt_now(),
+            }
+        )
 
-        leverage_text = f' ({leverage:.3g}x)`\n' if leverage and leverage != 1.0 else '`\n'
+        leverage_text = f" ({leverage:.3g}x)`\n" if leverage and leverage != 1.0 else "`\n"
         assert msg_mock.call_args[0][0] == (
-            '\N{WARNING SIGN} *Binance (dry):* Exited KEY/ETH (#1)\n'
-            '*Profit:* `-57.41% (loss: -0.05746 ETH)`\n'
-            f'*Enter Tag:* `{enter_signal}`\n'
-            '*Exit Reason:* `stop_loss`\n'
+            "\N{WARNING SIGN} *Binance (dry):* Exited KEY/ETH (#1)\n"
+            "*Profit:* `-57.41% (loss: -0.05746 ETH)`\n"
+            f"*Enter Tag:* `{enter_signal}`\n"
+            "*Exit Reason:* `stop_loss`\n"
             f"*Direction:* `{direction}"
             f"{leverage_text}"
-            '*Amount:* `1333.33333333`\n'
-            '*Open Rate:* `0.00075 ETH`\n'
-            '*Exit Rate:* `0.00032 ETH`\n'
-            '*Duration:* `1 day, 2:30:00 (1590.0 min)`'
+            "*Amount:* `1333.33333333`\n"
+            "*Open Rate:* `0.00075 ETH`\n"
+            "*Exit Rate:* `0.00032 ETH`\n"
+            "*Duration:* `1 day, 2:30:00 (1590.0 min)`"
         )
 
 
 def test_send_msg_status_notification(default_conf, mocker) -> None:
-
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
-    telegram.send_msg({
-        'type': RPCMessageType.STATUS,
-        'status': 'running'
-    })
-    assert msg_mock.call_args[0][0] == '*Status:* `running`'
+    telegram.send_msg({"type": RPCMessageType.STATUS, "status": "running"})
+    assert msg_mock.call_args[0][0] == "*Status:* `running`"
 
 
 async def test_warning_notification(default_conf, mocker) -> None:
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
-    telegram.send_msg({
-        'type': RPCMessageType.WARNING,
-        'status': 'message'
-    })
-    assert msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Warning:* `message`'
+    telegram.send_msg({"type": RPCMessageType.WARNING, "status": "message"})
+    assert msg_mock.call_args[0][0] == "\N{WARNING SIGN} *Warning:* `message`"
 
 
 def test_startup_notification(default_conf, mocker) -> None:
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
-    telegram.send_msg({
-        'type': RPCMessageType.STARTUP,
-        'status': '*Custom:* `Hello World`'
-    })
-    assert msg_mock.call_args[0][0] == '*Custom:* `Hello World`'
+    telegram.send_msg({"type": RPCMessageType.STARTUP, "status": "*Custom:* `Hello World`"})
+    assert msg_mock.call_args[0][0] == "*Custom:* `Hello World`"
 
 
 def test_send_msg_strategy_msg_notification(default_conf, mocker) -> None:
-
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
-    telegram.send_msg({
-        'type': RPCMessageType.STRATEGY_MSG,
-        'msg': 'hello world, Test msg'
-    })
-    assert msg_mock.call_args[0][0] == 'hello world, Test msg'
+    telegram.send_msg({"type": RPCMessageType.STRATEGY_MSG, "msg": "hello world, Test msg"})
+    assert msg_mock.call_args[0][0] == "hello world, Test msg"
 
 
 def test_send_msg_unknown_type(default_conf, mocker) -> None:
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
-    telegram.send_msg({
-        'type': None,
-    })
+    telegram.send_msg(
+        {
+            "type": None,
+        }
+    )
     assert msg_mock.call_count == 0
 
 
-@pytest.mark.parametrize('message_type,enter,enter_signal,leverage', [
-    (RPCMessageType.ENTRY, 'Long', 'long_signal_01', None),
-    (RPCMessageType.ENTRY, 'Long', 'long_signal_01', 2.0),
-    (RPCMessageType.ENTRY, 'Short', 'short_signal_01', 2.0)])
+@pytest.mark.parametrize(
+    "message_type,enter,enter_signal,leverage",
+    [
+        (RPCMessageType.ENTRY, "Long", "long_signal_01", None),
+        (RPCMessageType.ENTRY, "Long", "long_signal_01", 2.0),
+        (RPCMessageType.ENTRY, "Short", "short_signal_01", 2.0),
+    ],
+)
 def test_send_msg_buy_notification_no_fiat(
-        default_conf, mocker, message_type, enter, enter_signal, leverage) -> None:
-    del default_conf['fiat_display_currency']
-    default_conf['dry_run'] = False
+    default_conf, mocker, message_type, enter, enter_signal, leverage
+) -> None:
+    del default_conf["fiat_display_currency"]
+    default_conf["dry_run"] = False
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
 
-    telegram.send_msg({
-        'type': message_type,
-        'enter_tag': enter_signal,
-        'trade_id': 1,
-        'exchange': 'Binance',
-        'pair': 'ETH/BTC',
-        'leverage': leverage,
-        'open_rate': 1.099e-05,
-        'order_type': 'limit',
-        'direction': enter,
-        'sub_trade': False,
-        'stake_amount': 0.01465333,
-        'stake_amount_fiat': 0.0,
-        'stake_currency': 'BTC',
-        'quote_currency': 'BTC',
-        'base_currency': 'ETH',
-        'fiat_currency': None,
-        'current_rate': 1.099e-05,
-        'amount': 1333.3333333333335,
-        'open_date': dt_now() - timedelta(hours=1)
-    })
+    telegram.send_msg(
+        {
+            "type": message_type,
+            "enter_tag": enter_signal,
+            "trade_id": 1,
+            "exchange": "Binance",
+            "pair": "ETH/BTC",
+            "leverage": leverage,
+            "open_rate": 1.099e-05,
+            "order_type": "limit",
+            "direction": enter,
+            "sub_trade": False,
+            "stake_amount": 0.01465333,
+            "stake_amount_fiat": 0.0,
+            "stake_currency": "BTC",
+            "quote_currency": "BTC",
+            "base_currency": "ETH",
+            "fiat_currency": None,
+            "current_rate": 1.099e-05,
+            "amount": 1333.3333333333335,
+            "open_date": dt_now() - timedelta(hours=1),
+        }
+    )
 
-    leverage_text = f' ({leverage:.3g}x)' if leverage and leverage != 1.0 else ''
+    leverage_text = f" ({leverage:.3g}x)" if leverage and leverage != 1.0 else ""
     assert msg_mock.call_args[0][0] == (
-        f'\N{LARGE BLUE CIRCLE} *Binance:* New Trade (#1)\n'
-        '*Pair:* `ETH/BTC`\n'
-        f'*Enter Tag:* `{enter_signal}`\n'
-        '*Amount:* `1333.33333333`\n'
-        f'*Direction:* `{enter}'
-        f'{leverage_text}`\n'
-        '*Open Rate:* `0.00001099 BTC`\n'
-        '*Current Rate:* `0.00001099 BTC`\n'
-        '*Total:* `0.01465333 BTC`'
+        f"\N{LARGE BLUE CIRCLE} *Binance:* New Trade (#1)\n"
+        "*Pair:* `ETH/BTC`\n"
+        f"*Enter Tag:* `{enter_signal}`\n"
+        "*Amount:* `1333.33333333`\n"
+        f"*Direction:* `{enter}"
+        f"{leverage_text}`\n"
+        "*Open Rate:* `0.00001099 BTC`\n"
+        "*Current Rate:* `0.00001099 BTC`\n"
+        "*Total:* `0.01465333 BTC`"
     )
 
 
-@pytest.mark.parametrize('direction,enter_signal,leverage', [
-    ('Long', 'long_signal_01', None),
-    ('Long', 'long_signal_01', 1.0),
-    ('Long', 'long_signal_01', 5.0),
-    ('Short', 'short_signal_01', 2.0),
-])
+@pytest.mark.parametrize(
+    "direction,enter_signal,leverage",
+    [
+        ("Long", "long_signal_01", None),
+        ("Long", "long_signal_01", 1.0),
+        ("Long", "long_signal_01", 5.0),
+        ("Short", "short_signal_01", 2.0),
+    ],
+)
 def test_send_msg_exit_notification_no_fiat(
-        default_conf, mocker, direction, enter_signal, leverage, time_machine) -> None:
-    del default_conf['fiat_display_currency']
-    time_machine.move_to('2022-05-02 00:00:00 +00:00', tick=False)
+    default_conf, mocker, direction, enter_signal, leverage, time_machine
+) -> None:
+    del default_conf["fiat_display_currency"]
+    time_machine.move_to("2022-05-02 00:00:00 +00:00", tick=False)
     telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
 
-    telegram.send_msg({
-        'type': RPCMessageType.EXIT,
-        'trade_id': 1,
-        'exchange': 'Binance',
-        'pair': 'KEY/ETH',
-        'gain': 'loss',
-        'leverage': leverage,
-        'direction': direction,
-        'sub_trade': False,
-        'order_rate': 3.201e-04,
-        'amount': 1333.3333333333335,
-        'order_type': 'limit',
-        'open_rate': 7.5e-04,
-        'current_rate': 3.201e-04,
-        'profit_amount': -0.05746268,
-        'profit_ratio': -0.57405275,
-        'stake_currency': 'ETH',
-        'quote_currency': 'ETH',
-        'base_currency': 'KEY',
-        'fiat_currency': 'USD',
-        'enter_tag': enter_signal,
-        'exit_reason': ExitType.STOP_LOSS.value,
-        'open_date': dt_now() - timedelta(hours=2, minutes=35, seconds=3),
-        'close_date': dt_now(),
-    })
+    telegram.send_msg(
+        {
+            "type": RPCMessageType.EXIT,
+            "trade_id": 1,
+            "exchange": "Binance",
+            "pair": "KEY/ETH",
+            "gain": "loss",
+            "leverage": leverage,
+            "direction": direction,
+            "sub_trade": False,
+            "order_rate": 3.201e-04,
+            "amount": 1333.3333333333335,
+            "order_type": "limit",
+            "open_rate": 7.5e-04,
+            "current_rate": 3.201e-04,
+            "profit_amount": -0.05746268,
+            "profit_ratio": -0.57405275,
+            "stake_currency": "ETH",
+            "quote_currency": "ETH",
+            "base_currency": "KEY",
+            "fiat_currency": "USD",
+            "enter_tag": enter_signal,
+            "exit_reason": ExitType.STOP_LOSS.value,
+            "open_date": dt_now() - timedelta(hours=2, minutes=35, seconds=3),
+            "close_date": dt_now(),
+        }
+    )
 
-    leverage_text = f' ({leverage:.3g}x)' if leverage and leverage != 1.0 else ''
+    leverage_text = f" ({leverage:.3g}x)" if leverage and leverage != 1.0 else ""
     assert msg_mock.call_args[0][0] == (
-        '\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n'
-        '*Unrealized Profit:* `-57.41% (loss: -0.05746 ETH)`\n'
-        f'*Enter Tag:* `{enter_signal}`\n'
-        '*Exit Reason:* `stop_loss`\n'
-        f'*Direction:* `{direction}'
-        f'{leverage_text}`\n'
-        '*Amount:* `1333.33333333`\n'
-        '*Open Rate:* `0.00075 ETH`\n'
-        '*Current Rate:* `0.00032 ETH`\n'
-        '*Exit Rate:* `0.00032 ETH`\n'
-        '*Duration:* `2:35:03 (155.1 min)`'
+        "\N{WARNING SIGN} *Binance (dry):* Exiting KEY/ETH (#1)\n"
+        "*Unrealized Profit:* `-57.41% (loss: -0.05746 ETH)`\n"
+        f"*Enter Tag:* `{enter_signal}`\n"
+        "*Exit Reason:* `stop_loss`\n"
+        f"*Direction:* `{direction}"
+        f"{leverage_text}`\n"
+        "*Amount:* `1333.33333333`\n"
+        "*Open Rate:* `0.00075 ETH`\n"
+        "*Current Rate:* `0.00032 ETH`\n"
+        "*Exit Rate:* `0.00032 ETH`\n"
+        "*Duration:* `2:35:03 (155.1 min)`"
     )
 
 
-@pytest.mark.parametrize('msg,expected', [
-    ({'profit_ratio': 0.201, 'exit_reason': 'roi'}, "\N{ROCKET}"),
-    ({'profit_ratio': 0.051, 'exit_reason': 'roi'}, "\N{ROCKET}"),
-    ({'profit_ratio': 0.0256, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
-    ({'profit_ratio': 0.01, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
-    ({'profit_ratio': 0.0, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
-    ({'profit_ratio': -0.05, 'exit_reason': 'stop_loss'}, "\N{WARNING SIGN}"),
-    ({'profit_ratio': -0.02, 'exit_reason': 'sell_signal'}, "\N{CROSS MARK}"),
-])
+@pytest.mark.parametrize(
+    "msg,expected",
+    [
+        ({"profit_ratio": 0.201, "exit_reason": "roi"}, "\N{ROCKET}"),
+        ({"profit_ratio": 0.051, "exit_reason": "roi"}, "\N{ROCKET}"),
+        ({"profit_ratio": 0.0256, "exit_reason": "roi"}, "\N{EIGHT SPOKED ASTERISK}"),
+        ({"profit_ratio": 0.01, "exit_reason": "roi"}, "\N{EIGHT SPOKED ASTERISK}"),
+        ({"profit_ratio": 0.0, "exit_reason": "roi"}, "\N{EIGHT SPOKED ASTERISK}"),
+        ({"profit_ratio": -0.05, "exit_reason": "stop_loss"}, "\N{WARNING SIGN}"),
+        ({"profit_ratio": -0.02, "exit_reason": "sell_signal"}, "\N{CROSS MARK}"),
+    ],
+)
 def test__exit_emoji(default_conf, mocker, msg, expected):
-    del default_conf['fiat_display_currency']
+    del default_conf["fiat_display_currency"]
 
     telegram, _, _ = get_telegram_testobject(mocker, default_conf)
 
@@ -2559,7 +2636,7 @@ def test__exit_emoji(default_conf, mocker, msg, expected):
 
 
 async def test_telegram__send_msg(default_conf, mocker, caplog) -> None:
-    mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
+    mocker.patch("freqtrade.rpc.telegram.Telegram._init", MagicMock())
     bot = MagicMock()
     bot.send_message = AsyncMock()
     bot.edit_message_text = AsyncMock()
@@ -2567,64 +2644,68 @@ async def test_telegram__send_msg(default_conf, mocker, caplog) -> None:
     telegram._app = MagicMock()
     telegram._app.bot = bot
 
-    await telegram._send_msg('test')
+    await telegram._send_msg("test")
     assert len(bot.method_calls) == 1
 
     # Test update
     query = MagicMock()
     query.edit_message_text = AsyncMock()
-    await telegram._send_msg('test', callback_path="DeadBeef", query=query, reload_able=True)
+    await telegram._send_msg("test", callback_path="DeadBeef", query=query, reload_able=True)
     assert query.edit_message_text.call_count == 1
-    assert "Updated: " in query.edit_message_text.call_args_list[0][1]['text']
+    assert "Updated: " in query.edit_message_text.call_args_list[0][1]["text"]
 
     query.edit_message_text = AsyncMock(side_effect=BadRequest("not modified"))
-    await telegram._send_msg('test', callback_path="DeadBeef", query=query)
+    await telegram._send_msg("test", callback_path="DeadBeef", query=query)
     assert query.edit_message_text.call_count == 1
     assert not log_has_re(r"TelegramError: .*", caplog)
 
     query.edit_message_text = AsyncMock(side_effect=BadRequest(""))
-    await telegram._send_msg('test2', callback_path="DeadBeef", query=query)
+    await telegram._send_msg("test2", callback_path="DeadBeef", query=query)
     assert query.edit_message_text.call_count == 1
     assert log_has_re(r"TelegramError: .*", caplog)
 
     query.edit_message_text = AsyncMock(side_effect=TelegramError("DeadBEEF"))
-    await telegram._send_msg('test3', callback_path="DeadBeef", query=query)
+    await telegram._send_msg("test3", callback_path="DeadBeef", query=query)
 
     assert log_has_re(r"TelegramError: DeadBEEF! Giving up.*", caplog)
 
 
 async def test__send_msg_network_error(default_conf, mocker, caplog) -> None:
-    mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
+    mocker.patch("freqtrade.rpc.telegram.Telegram._init", MagicMock())
     bot = MagicMock()
-    bot.send_message = MagicMock(side_effect=NetworkError('Oh snap'))
+    bot.send_message = MagicMock(side_effect=NetworkError("Oh snap"))
     telegram, _, _ = get_telegram_testobject(mocker, default_conf, mock=False)
     telegram._app = MagicMock()
     telegram._app.bot = bot
 
-    telegram._config['telegram']['enabled'] = True
-    await telegram._send_msg('test')
+    telegram._config["telegram"]["enabled"] = True
+    await telegram._send_msg("test")
 
     # Bot should've tried to send it twice
     assert len(bot.method_calls) == 2
-    assert log_has('Telegram NetworkError: Oh snap! Trying one more time.', caplog)
+    assert log_has("Telegram NetworkError: Oh snap! Trying one more time.", caplog)
 
 
 @pytest.mark.filterwarnings("ignore:.*ChatPermissions")
 async def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
-    mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
+    mocker.patch("freqtrade.rpc.telegram.Telegram._init", MagicMock())
     bot = MagicMock()
     bot.send_message = AsyncMock()
     freqtradebot = get_patched_freqtradebot(mocker, default_conf)
     rpc = RPC(freqtradebot)
 
-    invalid_keys_list = [['/not_valid', '/profit'], ['/daily'], ['/alsoinvalid']]
-    default_keys_list = [['/daily', '/profit', '/balance'],
-                         ['/status', '/status table', '/performance'],
-                         ['/count', '/start', '/stop', '/help']]
+    invalid_keys_list = [["/not_valid", "/profit"], ["/daily"], ["/alsoinvalid"]]
+    default_keys_list = [
+        ["/daily", "/profit", "/balance"],
+        ["/status", "/status table", "/performance"],
+        ["/count", "/start", "/stop", "/help"],
+    ]
     default_keyboard = ReplyKeyboardMarkup(default_keys_list)
 
-    custom_keys_list = [['/daily', '/stats', '/balance', '/profit', '/profit 5'],
-                        ['/count', '/start', '/reload_config', '/help']]
+    custom_keys_list = [
+        ["/daily", "/stats", "/balance", "/profit", "/profit 5"],
+        ["/count", "/start", "/reload_config", "/help"],
+    ]
     custom_keyboard = ReplyKeyboardMarkup(custom_keys_list)
 
     def init_telegram(freqtradebot):
@@ -2634,31 +2715,39 @@ async def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
         return telegram
 
     # no keyboard in config -> default keyboard
-    freqtradebot.config['telegram']['enabled'] = True
+    freqtradebot.config["telegram"]["enabled"] = True
     telegram = init_telegram(freqtradebot)
-    await telegram._send_msg('test')
-    used_keyboard = bot.send_message.call_args[1]['reply_markup']
+    await telegram._send_msg("test")
+    used_keyboard = bot.send_message.call_args[1]["reply_markup"]
     assert used_keyboard == default_keyboard
 
     # invalid keyboard in config -> default keyboard
-    freqtradebot.config['telegram']['enabled'] = True
-    freqtradebot.config['telegram']['keyboard'] = invalid_keys_list
-    err_msg = re.escape("config.telegram.keyboard: Invalid commands for custom "
-                        "Telegram keyboard: ['/not_valid', '/alsoinvalid']"
-                        "\nvalid commands are: ") + r"*"
+    freqtradebot.config["telegram"]["enabled"] = True
+    freqtradebot.config["telegram"]["keyboard"] = invalid_keys_list
+    err_msg = (
+        re.escape(
+            "config.telegram.keyboard: Invalid commands for custom "
+            "Telegram keyboard: ['/not_valid', '/alsoinvalid']"
+            "\nvalid commands are: "
+        )
+        + r"*"
+    )
     with pytest.raises(OperationalException, match=err_msg):
         telegram = init_telegram(freqtradebot)
 
     # valid keyboard in config -> custom keyboard
-    freqtradebot.config['telegram']['enabled'] = True
-    freqtradebot.config['telegram']['keyboard'] = custom_keys_list
+    freqtradebot.config["telegram"]["enabled"] = True
+    freqtradebot.config["telegram"]["keyboard"] = custom_keys_list
     telegram = init_telegram(freqtradebot)
-    await telegram._send_msg('test')
-    used_keyboard = bot.send_message.call_args[1]['reply_markup']
+    await telegram._send_msg("test")
+    used_keyboard = bot.send_message.call_args[1]["reply_markup"]
     assert used_keyboard == custom_keyboard
-    assert log_has("using custom keyboard from config.json: "
-                   "[['/daily', '/stats', '/balance', '/profit', '/profit 5'], ['/count', "
-                   "'/start', '/reload_config', '/help']]", caplog)
+    assert log_has(
+        "using custom keyboard from config.json: "
+        "[['/daily', '/stats', '/balance', '/profit', '/profit 5'], ['/count', "
+        "'/start', '/reload_config', '/help']]",
+        caplog,
+    )
 
 
 async def test_change_market_direction(default_conf, mocker, update) -> None:
@@ -2675,7 +2764,6 @@ async def test_change_market_direction(default_conf, mocker, update) -> None:
 
 
 async def test_telegram_list_custom_data(default_conf_usdt, update, ticker, fee, mocker) -> None:
-
     mocker.patch.multiple(
         EXMS,
         fetch_ticker=ticker,
@@ -2689,11 +2777,11 @@ async def test_telegram_list_custom_data(default_conf_usdt, update, ticker, fee,
     context = MagicMock()
     await telegram._list_custom_data(update=update, context=context)
     assert msg_mock.call_count == 1
-    assert 'Trade-id not set.' in msg_mock.call_args_list[0][0][0]
+    assert "Trade-id not set." in msg_mock.call_args_list[0][0][0]
     msg_mock.reset_mock()
 
     #
-    context.args = ['1']
+    context.args = ["1"]
     await telegram._list_custom_data(update=update, context=context)
     assert msg_mock.call_count == 1
     assert (
@@ -2703,8 +2791,8 @@ async def test_telegram_list_custom_data(default_conf_usdt, update, ticker, fee,
 
     # Add some custom data
     trade1 = Trade.get_trades_proxy()[0]
-    trade1.set_custom_data('test_int', 1)
-    trade1.set_custom_data('test_dict', {'test': 'dict'})
+    trade1.set_custom_data("test_int", 1)
+    trade1.set_custom_data("test_dict", {"test": "dict"})
     Trade.commit()
     context.args = [f"{trade1.id}"]
     await telegram._list_custom_data(update=update, context=context)
@@ -2712,9 +2800,11 @@ async def test_telegram_list_custom_data(default_conf_usdt, update, ticker, fee,
     assert "Found custom-data entries: " in msg_mock.call_args_list[0][0][0]
     assert (
         "*Key:* `test_int`\n*ID:* `1`\n*Trade ID:* `1`\n*Type:* `int`\n"
-        "*Value:* `1`\n*Create Date:*") in msg_mock.call_args_list[1][0][0]
+        "*Value:* `1`\n*Create Date:*"
+    ) in msg_mock.call_args_list[1][0][0]
     assert (
-        '*Key:* `test_dict`\n*ID:* `2`\n*Trade ID:* `1`\n*Type:* `dict`\n'
-        '*Value:* `{"test": "dict"}`\n*Create Date:* `') in msg_mock.call_args_list[2][0][0]
+        "*Key:* `test_dict`\n*ID:* `2`\n*Trade ID:* `1`\n*Type:* `dict`\n"
+        '*Value:* `{"test": "dict"}`\n*Create Date:* `'
+    ) in msg_mock.call_args_list[2][0][0]
 
     msg_mock.reset_mock()
diff --git a/tests/rpc/test_rpc_webhook.py b/tests/rpc/test_rpc_webhook.py
index 7d88056e4..dc33f965f 100644
--- a/tests/rpc/test_rpc_webhook.py
+++ b/tests/rpc/test_rpc_webhook.py
@@ -26,47 +26,43 @@ def get_webhook_dict() -> dict:
             "value2": "limit {limit:8f}",
             "value3": "{stake_amount:8f} {stake_currency}",
             "value4": "leverage {leverage:.1f}",
-            "value5": "direction {direction}"
+            "value5": "direction {direction}",
         },
         "webhookentrycancel": {
             "value1": "Cancelling Open Buy Order for {pair}",
             "value2": "limit {limit:8f}",
             "value3": "{stake_amount:8f} {stake_currency}",
             "value4": "leverage {leverage:.1f}",
-            "value5": "direction {direction}"
+            "value5": "direction {direction}",
         },
         "webhookentryfill": {
             "value1": "Buy Order for {pair} filled",
             "value2": "at {open_rate:8f}",
             "value3": "{stake_amount:8f} {stake_currency}",
             "value4": "leverage {leverage:.1f}",
-            "value5": "direction {direction}"
+            "value5": "direction {direction}",
         },
         "webhookexit": {
             "value1": "Selling {pair}",
             "value2": "limit {limit:8f}",
-            "value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
+            "value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})",
         },
         "webhookexitcancel": {
             "value1": "Cancelling Open Sell Order for {pair}",
             "value2": "limit {limit:8f}",
-            "value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
+            "value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})",
         },
         "webhookexitfill": {
             "value1": "Sell Order for {pair} filled",
             "value2": "at {close_rate:8f}",
-            "value3": ""
+            "value3": "",
         },
-        "webhookstatus": {
-            "value1": "Status: {status}",
-            "value2": "",
-            "value3": ""
-        }
+        "webhookstatus": {"value1": "Status: {status}", "value2": "", "value3": ""},
     }
 
 
 def test__init__(mocker, default_conf):
-    default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"}
+    default_conf["webhook"] = {"enabled": True, "url": "https://DEADBEEF.com"}
     webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
     assert webhook._config == default_conf
 
@@ -80,255 +76,290 @@ def test_send_msg_webhook(default_conf, mocker):
     msg_mock = MagicMock()
     mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
     msg = {
-        'type': RPCMessageType.ENTRY,
-        'exchange': 'Binance',
-        'pair': 'ETH/BTC',
-        'leverage': 1.0,
-        'direction': 'Long',
-        'limit': 0.005,
-        'stake_amount': 0.8,
-        'stake_amount_fiat': 500,
-        'stake_currency': 'BTC',
-        'fiat_currency': 'EUR'
+        "type": RPCMessageType.ENTRY,
+        "exchange": "Binance",
+        "pair": "ETH/BTC",
+        "leverage": 1.0,
+        "direction": "Long",
+        "limit": 0.005,
+        "stake_amount": 0.8,
+        "stake_amount_fiat": 500,
+        "stake_currency": "BTC",
+        "fiat_currency": "EUR",
     }
     webhook.send_msg(msg=msg)
     assert msg_mock.call_count == 1
-    assert (msg_mock.call_args[0][0]["value1"] ==
-            default_conf["webhook"]["entry"]["value1"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value2"] ==
-            default_conf["webhook"]["entry"]["value2"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value3"] ==
-            default_conf["webhook"]["entry"]["value3"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value4"] ==
-            default_conf["webhook"]["entry"]["value4"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value5"] ==
-            default_conf["webhook"]["entry"]["value5"].format(**msg))
+    assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["entry"]["value1"].format(
+        **msg
+    )
+    assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["entry"]["value2"].format(
+        **msg
+    )
+    assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["entry"]["value3"].format(
+        **msg
+    )
+    assert msg_mock.call_args[0][0]["value4"] == default_conf["webhook"]["entry"]["value4"].format(
+        **msg
+    )
+    assert msg_mock.call_args[0][0]["value5"] == default_conf["webhook"]["entry"]["value5"].format(
+        **msg
+    )
     # Test short
     msg_mock.reset_mock()
 
     msg = {
-        'type': RPCMessageType.ENTRY,
-        'exchange': 'Binance',
-        'pair': 'ETH/BTC',
-        'leverage': 2.0,
-        'direction': 'Short',
-        'limit': 0.005,
-        'stake_amount': 0.8,
-        'stake_amount_fiat': 500,
-        'stake_currency': 'BTC',
-        'fiat_currency': 'EUR'
+        "type": RPCMessageType.ENTRY,
+        "exchange": "Binance",
+        "pair": "ETH/BTC",
+        "leverage": 2.0,
+        "direction": "Short",
+        "limit": 0.005,
+        "stake_amount": 0.8,
+        "stake_amount_fiat": 500,
+        "stake_currency": "BTC",
+        "fiat_currency": "EUR",
     }
     webhook.send_msg(msg=msg)
     assert msg_mock.call_count == 1
-    assert (msg_mock.call_args[0][0]["value1"] ==
-            default_conf["webhook"]["entry"]["value1"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value2"] ==
-            default_conf["webhook"]["entry"]["value2"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value3"] ==
-            default_conf["webhook"]["entry"]["value3"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value4"] ==
-            default_conf["webhook"]["entry"]["value4"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value5"] ==
-            default_conf["webhook"]["entry"]["value5"].format(**msg))
+    assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["entry"]["value1"].format(
+        **msg
+    )
+    assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["entry"]["value2"].format(
+        **msg
+    )
+    assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["entry"]["value3"].format(
+        **msg
+    )
+    assert msg_mock.call_args[0][0]["value4"] == default_conf["webhook"]["entry"]["value4"].format(
+        **msg
+    )
+    assert msg_mock.call_args[0][0]["value5"] == default_conf["webhook"]["entry"]["value5"].format(
+        **msg
+    )
     # Test buy cancel
     msg_mock.reset_mock()
 
     msg = {
-        'type': RPCMessageType.ENTRY_CANCEL,
-        'exchange': 'Binance',
-        'pair': 'ETH/BTC',
-        'leverage': 1.0,
-        'direction': 'Long',
-        'limit': 0.005,
-        'stake_amount': 0.8,
-        'stake_amount_fiat': 500,
-        'stake_currency': 'BTC',
-        'fiat_currency': 'EUR'
+        "type": RPCMessageType.ENTRY_CANCEL,
+        "exchange": "Binance",
+        "pair": "ETH/BTC",
+        "leverage": 1.0,
+        "direction": "Long",
+        "limit": 0.005,
+        "stake_amount": 0.8,
+        "stake_amount_fiat": 500,
+        "stake_currency": "BTC",
+        "fiat_currency": "EUR",
     }
     webhook.send_msg(msg=msg)
     assert msg_mock.call_count == 1
-    assert (msg_mock.call_args[0][0]["value1"] ==
-            default_conf["webhook"]["webhookentrycancel"]["value1"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value2"] ==
-            default_conf["webhook"]["webhookentrycancel"]["value2"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value3"] ==
-            default_conf["webhook"]["webhookentrycancel"]["value3"].format(**msg))
+    assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["webhookentrycancel"][
+        "value1"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["webhookentrycancel"][
+        "value2"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhookentrycancel"][
+        "value3"
+    ].format(**msg)
     # Test short cancel
     msg_mock.reset_mock()
 
     msg = {
-        'type': RPCMessageType.ENTRY_CANCEL,
-        'exchange': 'Binance',
-        'pair': 'ETH/BTC',
-        'leverage': 2.0,
-        'direction': 'Short',
-        'limit': 0.005,
-        'stake_amount': 0.8,
-        'stake_amount_fiat': 500,
-        'stake_currency': 'BTC',
-        'fiat_currency': 'EUR'
+        "type": RPCMessageType.ENTRY_CANCEL,
+        "exchange": "Binance",
+        "pair": "ETH/BTC",
+        "leverage": 2.0,
+        "direction": "Short",
+        "limit": 0.005,
+        "stake_amount": 0.8,
+        "stake_amount_fiat": 500,
+        "stake_currency": "BTC",
+        "fiat_currency": "EUR",
     }
     webhook.send_msg(msg=msg)
     assert msg_mock.call_count == 1
-    assert (msg_mock.call_args[0][0]["value1"] ==
-            default_conf["webhook"]["webhookentrycancel"]["value1"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value2"] ==
-            default_conf["webhook"]["webhookentrycancel"]["value2"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value3"] ==
-            default_conf["webhook"]["webhookentrycancel"]["value3"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value4"] ==
-            default_conf["webhook"]["webhookentrycancel"]["value4"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value5"] ==
-            default_conf["webhook"]["webhookentrycancel"]["value5"].format(**msg))
+    assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["webhookentrycancel"][
+        "value1"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["webhookentrycancel"][
+        "value2"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhookentrycancel"][
+        "value3"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value4"] == default_conf["webhook"]["webhookentrycancel"][
+        "value4"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value5"] == default_conf["webhook"]["webhookentrycancel"][
+        "value5"
+    ].format(**msg)
     # Test buy fill
     msg_mock.reset_mock()
 
     msg = {
-        'type': RPCMessageType.ENTRY_FILL,
-        'exchange': 'Binance',
-        'pair': 'ETH/BTC',
-        'leverage': 1.0,
-        'direction': 'Long',
-        'open_rate': 0.005,
-        'stake_amount': 0.8,
-        'stake_amount_fiat': 500,
-        'stake_currency': 'BTC',
-        'fiat_currency': 'EUR'
+        "type": RPCMessageType.ENTRY_FILL,
+        "exchange": "Binance",
+        "pair": "ETH/BTC",
+        "leverage": 1.0,
+        "direction": "Long",
+        "open_rate": 0.005,
+        "stake_amount": 0.8,
+        "stake_amount_fiat": 500,
+        "stake_currency": "BTC",
+        "fiat_currency": "EUR",
     }
     webhook.send_msg(msg=msg)
     assert msg_mock.call_count == 1
-    assert (msg_mock.call_args[0][0]["value1"] ==
-            default_conf["webhook"]["webhookentryfill"]["value1"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value2"] ==
-            default_conf["webhook"]["webhookentryfill"]["value2"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value3"] ==
-            default_conf["webhook"]["webhookentryfill"]["value3"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value4"] ==
-            default_conf["webhook"]["webhookentrycancel"]["value4"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value5"] ==
-            default_conf["webhook"]["webhookentrycancel"]["value5"].format(**msg))
+    assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["webhookentryfill"][
+        "value1"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["webhookentryfill"][
+        "value2"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhookentryfill"][
+        "value3"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value4"] == default_conf["webhook"]["webhookentrycancel"][
+        "value4"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value5"] == default_conf["webhook"]["webhookentrycancel"][
+        "value5"
+    ].format(**msg)
     # Test short fill
     msg_mock.reset_mock()
 
     msg = {
-        'type': RPCMessageType.ENTRY_FILL,
-        'exchange': 'Binance',
-        'pair': 'ETH/BTC',
-        'leverage': 2.0,
-        'direction': 'Short',
-        'open_rate': 0.005,
-        'stake_amount': 0.8,
-        'stake_amount_fiat': 500,
-        'stake_currency': 'BTC',
-        'fiat_currency': 'EUR'
+        "type": RPCMessageType.ENTRY_FILL,
+        "exchange": "Binance",
+        "pair": "ETH/BTC",
+        "leverage": 2.0,
+        "direction": "Short",
+        "open_rate": 0.005,
+        "stake_amount": 0.8,
+        "stake_amount_fiat": 500,
+        "stake_currency": "BTC",
+        "fiat_currency": "EUR",
     }
     webhook.send_msg(msg=msg)
     assert msg_mock.call_count == 1
-    assert (msg_mock.call_args[0][0]["value1"] ==
-            default_conf["webhook"]["webhookentryfill"]["value1"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value2"] ==
-            default_conf["webhook"]["webhookentryfill"]["value2"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value3"] ==
-            default_conf["webhook"]["webhookentryfill"]["value3"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value4"] ==
-            default_conf["webhook"]["webhookentrycancel"]["value4"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value5"] ==
-            default_conf["webhook"]["webhookentrycancel"]["value5"].format(**msg))
+    assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["webhookentryfill"][
+        "value1"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["webhookentryfill"][
+        "value2"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhookentryfill"][
+        "value3"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value4"] == default_conf["webhook"]["webhookentrycancel"][
+        "value4"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value5"] == default_conf["webhook"]["webhookentrycancel"][
+        "value5"
+    ].format(**msg)
     # Test sell
     msg_mock.reset_mock()
 
     msg = {
-        'type': RPCMessageType.EXIT,
-        'exchange': 'Binance',
-        'pair': 'ETH/BTC',
-        'gain': "profit",
-        'limit': 0.005,
-        'amount': 0.8,
-        'order_type': 'limit',
-        'open_rate': 0.004,
-        'current_rate': 0.005,
-        'profit_amount': 0.001,
-        'profit_ratio': 0.20,
-        'stake_currency': 'BTC',
-        'sell_reason': ExitType.STOP_LOSS.value
+        "type": RPCMessageType.EXIT,
+        "exchange": "Binance",
+        "pair": "ETH/BTC",
+        "gain": "profit",
+        "limit": 0.005,
+        "amount": 0.8,
+        "order_type": "limit",
+        "open_rate": 0.004,
+        "current_rate": 0.005,
+        "profit_amount": 0.001,
+        "profit_ratio": 0.20,
+        "stake_currency": "BTC",
+        "sell_reason": ExitType.STOP_LOSS.value,
     }
     webhook.send_msg(msg=msg)
     assert msg_mock.call_count == 1
-    assert (msg_mock.call_args[0][0]["value1"] ==
-            default_conf["webhook"]["webhookexit"]["value1"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value2"] ==
-            default_conf["webhook"]["webhookexit"]["value2"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value3"] ==
-            default_conf["webhook"]["webhookexit"]["value3"].format(**msg))
+    assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["webhookexit"][
+        "value1"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["webhookexit"][
+        "value2"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhookexit"][
+        "value3"
+    ].format(**msg)
     # Test sell cancel
     msg_mock.reset_mock()
     msg = {
-        'type': RPCMessageType.EXIT_CANCEL,
-        'exchange': 'Binance',
-        'pair': 'ETH/BTC',
-        'gain': "profit",
-        'limit': 0.005,
-        'amount': 0.8,
-        'order_type': 'limit',
-        'open_rate': 0.004,
-        'current_rate': 0.005,
-        'profit_amount': 0.001,
-        'profit_ratio': 0.20,
-        'stake_currency': 'BTC',
-        'sell_reason': ExitType.STOP_LOSS.value
+        "type": RPCMessageType.EXIT_CANCEL,
+        "exchange": "Binance",
+        "pair": "ETH/BTC",
+        "gain": "profit",
+        "limit": 0.005,
+        "amount": 0.8,
+        "order_type": "limit",
+        "open_rate": 0.004,
+        "current_rate": 0.005,
+        "profit_amount": 0.001,
+        "profit_ratio": 0.20,
+        "stake_currency": "BTC",
+        "sell_reason": ExitType.STOP_LOSS.value,
     }
     webhook.send_msg(msg=msg)
     assert msg_mock.call_count == 1
-    assert (msg_mock.call_args[0][0]["value1"] ==
-            default_conf["webhook"]["webhookexitcancel"]["value1"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value2"] ==
-            default_conf["webhook"]["webhookexitcancel"]["value2"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value3"] ==
-            default_conf["webhook"]["webhookexitcancel"]["value3"].format(**msg))
+    assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["webhookexitcancel"][
+        "value1"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["webhookexitcancel"][
+        "value2"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhookexitcancel"][
+        "value3"
+    ].format(**msg)
     # Test Sell fill
     msg_mock.reset_mock()
     msg = {
-        'type': RPCMessageType.EXIT_FILL,
-        'exchange': 'Binance',
-        'pair': 'ETH/BTC',
-        'gain': "profit",
-        'close_rate': 0.005,
-        'amount': 0.8,
-        'order_type': 'limit',
-        'open_rate': 0.004,
-        'current_rate': 0.005,
-        'profit_amount': 0.001,
-        'profit_ratio': 0.20,
-        'stake_currency': 'BTC',
-        'sell_reason': ExitType.STOP_LOSS.value
+        "type": RPCMessageType.EXIT_FILL,
+        "exchange": "Binance",
+        "pair": "ETH/BTC",
+        "gain": "profit",
+        "close_rate": 0.005,
+        "amount": 0.8,
+        "order_type": "limit",
+        "open_rate": 0.004,
+        "current_rate": 0.005,
+        "profit_amount": 0.001,
+        "profit_ratio": 0.20,
+        "stake_currency": "BTC",
+        "sell_reason": ExitType.STOP_LOSS.value,
     }
     webhook.send_msg(msg=msg)
     assert msg_mock.call_count == 1
-    assert (msg_mock.call_args[0][0]["value1"] ==
-            default_conf["webhook"]["webhookexitfill"]["value1"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value2"] ==
-            default_conf["webhook"]["webhookexitfill"]["value2"].format(**msg))
-    assert (msg_mock.call_args[0][0]["value3"] ==
-            default_conf["webhook"]["webhookexitfill"]["value3"].format(**msg))
+    assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["webhookexitfill"][
+        "value1"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["webhookexitfill"][
+        "value2"
+    ].format(**msg)
+    assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhookexitfill"][
+        "value3"
+    ].format(**msg)
 
-    for msgtype in [RPCMessageType.STATUS,
-                    RPCMessageType.WARNING,
-                    RPCMessageType.STARTUP]:
+    for msgtype in [RPCMessageType.STATUS, RPCMessageType.WARNING, RPCMessageType.STARTUP]:
         # Test notification
-        msg = {
-            'type': msgtype,
-            'status': 'Unfilled sell order for BTC cancelled due to timeout'
-        }
+        msg = {"type": msgtype, "status": "Unfilled sell order for BTC cancelled due to timeout"}
         msg_mock = MagicMock()
         mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
         webhook.send_msg(msg)
         assert msg_mock.call_count == 1
-        assert (msg_mock.call_args[0][0]["value1"] ==
-                default_conf["webhook"]["webhookstatus"]["value1"].format(**msg))
-        assert (msg_mock.call_args[0][0]["value2"] ==
-                default_conf["webhook"]["webhookstatus"]["value2"].format(**msg))
-        assert (msg_mock.call_args[0][0]["value3"] ==
-                default_conf["webhook"]["webhookstatus"]["value3"].format(**msg))
+        assert msg_mock.call_args[0][0]["value1"] == default_conf["webhook"]["webhookstatus"][
+            "value1"
+        ].format(**msg)
+        assert msg_mock.call_args[0][0]["value2"] == default_conf["webhook"]["webhookstatus"][
+            "value2"
+        ].format(**msg)
+        assert msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhookstatus"][
+            "value3"
+        ].format(**msg)
 
 
 def test_exception_send_msg(default_conf, mocker, caplog):
@@ -338,9 +369,8 @@ def test_exception_send_msg(default_conf, mocker, caplog):
     del default_conf["webhook"]["webhookentry"]
 
     webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
-    webhook.send_msg({'type': RPCMessageType.ENTRY})
-    assert log_has(f"Message type '{RPCMessageType.ENTRY}' not configured for webhooks",
-                   caplog)
+    webhook.send_msg({"type": RPCMessageType.ENTRY})
+    assert log_has(f"Message type '{RPCMessageType.ENTRY}' not configured for webhooks", caplog)
 
     default_conf["webhook"] = get_webhook_dict()
     default_conf["webhook"]["strategy_msg"] = {"value1": "{DEADBEEF:8f}"}
@@ -348,112 +378,103 @@ def test_exception_send_msg(default_conf, mocker, caplog):
     mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
     webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
     msg = {
-        'type': RPCMessageType.STRATEGY_MSG,
-        'msg': 'hello world',
+        "type": RPCMessageType.STRATEGY_MSG,
+        "msg": "hello world",
     }
     webhook.send_msg(msg)
-    assert log_has("Problem calling Webhook. Please check your webhook configuration. "
-                   "Exception: 'DEADBEEF'", caplog)
+    assert log_has(
+        "Problem calling Webhook. Please check your webhook configuration. "
+        "Exception: 'DEADBEEF'",
+        caplog,
+    )
 
     # Test no failure for not implemented but known messagetypes
     for e in RPCMessageType:
-        msg = {
-            'type': e,
-            'status': 'whatever'
-            }
+        msg = {"type": e, "status": "whatever"}
         webhook.send_msg(msg)
 
     # Test no failure for not implemented but known messagetypes
     for e in RPCMessageType:
-        msg = {
-            'type': e,
-            'status': 'whatever'
-            }
+        msg = {"type": e, "status": "whatever"}
         webhook.send_msg(msg)
 
 
 def test__send_msg(default_conf, mocker, caplog):
     default_conf["webhook"] = get_webhook_dict()
     webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
-    msg = {'value1': 'DEADBEEF',
-           'value2': 'ALIVEBEEF',
-           'value3': 'FREQTRADE'}
+    msg = {"value1": "DEADBEEF", "value2": "ALIVEBEEF", "value3": "FREQTRADE"}
     post = MagicMock()
     mocker.patch("freqtrade.rpc.webhook.post", post)
     webhook._send_msg(msg)
 
     assert post.call_count == 1
-    assert post.call_args[1] == {'data': msg, 'timeout': 10}
-    assert post.call_args[0] == (default_conf['webhook']['url'], )
+    assert post.call_args[1] == {"data": msg, "timeout": 10}
+    assert post.call_args[0] == (default_conf["webhook"]["url"],)
 
     post = MagicMock(side_effect=RequestException)
     mocker.patch("freqtrade.rpc.webhook.post", post)
     webhook._send_msg(msg)
-    assert log_has('Could not call webhook url. Exception: ', caplog)
+    assert log_has("Could not call webhook url. Exception: ", caplog)
 
 
 def test__send_msg_with_json_format(default_conf, mocker, caplog):
     default_conf["webhook"] = get_webhook_dict()
     default_conf["webhook"]["format"] = "json"
     webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
-    msg = {'text': 'Hello'}
+    msg = {"text": "Hello"}
     post = MagicMock()
     mocker.patch("freqtrade.rpc.webhook.post", post)
     webhook._send_msg(msg)
 
-    assert post.call_args[1] == {'json': msg, 'timeout': 10}
+    assert post.call_args[1] == {"json": msg, "timeout": 10}
 
 
 def test__send_msg_with_raw_format(default_conf, mocker, caplog):
     default_conf["webhook"] = get_webhook_dict()
     default_conf["webhook"]["format"] = "raw"
     webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
-    msg = {'data': 'Hello'}
+    msg = {"data": "Hello"}
     post = MagicMock()
     mocker.patch("freqtrade.rpc.webhook.post", post)
     webhook._send_msg(msg)
 
     assert post.call_args[1] == {
-        'data': msg['data'],
-        'headers': {'Content-Type': 'text/plain'},
-        'timeout': 10
+        "data": msg["data"],
+        "headers": {"Content-Type": "text/plain"},
+        "timeout": 10,
     }
 
 
 def test_send_msg_discord(default_conf, mocker):
-
-    default_conf["discord"] = {
-        'enabled': True,
-        'webhook_url': "https://webhookurl..."
-    }
+    default_conf["discord"] = {"enabled": True, "webhook_url": "https://webhookurl..."}
     msg_mock = MagicMock()
     mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
     discord = Discord(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
 
     msg = {
-        'type': RPCMessageType.EXIT_FILL,
-        'trade_id': 1,
-        'exchange': 'Binance',
-        'pair': 'ETH/BTC',
-        'direction': 'Long',
-        'gain': "profit",
-        'close_rate': 0.005,
-        'amount': 0.8,
-        'order_type': 'limit',
-        'open_date': datetime.now() - timedelta(days=1),
-        'close_date': datetime.now(),
-        'open_rate': 0.004,
-        'current_rate': 0.005,
-        'profit_amount': 0.001,
-        'profit_ratio': 0.20,
-        'stake_currency': 'BTC',
-        'enter_tag': 'enter_tagggg',
-        'exit_reason': ExitType.STOP_LOSS.value,
+        "type": RPCMessageType.EXIT_FILL,
+        "trade_id": 1,
+        "exchange": "Binance",
+        "pair": "ETH/BTC",
+        "direction": "Long",
+        "gain": "profit",
+        "close_rate": 0.005,
+        "amount": 0.8,
+        "order_type": "limit",
+        "open_date": datetime.now() - timedelta(days=1),
+        "close_date": datetime.now(),
+        "open_rate": 0.004,
+        "current_rate": 0.005,
+        "profit_amount": 0.001,
+        "profit_ratio": 0.20,
+        "stake_currency": "BTC",
+        "enter_tag": "enter_tagggg",
+        "exit_reason": ExitType.STOP_LOSS.value,
     }
     discord.send_msg(msg=msg)
 
     assert msg_mock.call_count == 1
-    assert 'embeds' in msg_mock.call_args_list[0][0][0]
-    assert 'title' in msg_mock.call_args_list[0][0][0]['embeds'][0]
-    assert 'color' in msg_mock.call_args_list[0][0][0]['embeds'][0]
-    assert 'fields' in msg_mock.call_args_list[0][0][0]['embeds'][0]
+    assert "embeds" in msg_mock.call_args_list[0][0][0]
+    assert "title" in msg_mock.call_args_list[0][0][0]["embeds"][0]
+    assert "color" in msg_mock.call_args_list[0][0][0]["embeds"][0]
+    assert "fields" in msg_mock.call_args_list[0][0][0]["embeds"][0]