feat: convert get_valid_pair_combination to generator

This commit is contained in:
Matthias
2024-12-08 16:54:12 +01:00
parent f07aec457a
commit 4d36aaff39
6 changed files with 53 additions and 40 deletions

View File

@@ -7,7 +7,7 @@ import asyncio
import inspect
import logging
import signal
from collections.abc import Coroutine
from collections.abc import Coroutine, Generator
from copy import deepcopy
from datetime import datetime, timedelta, timezone
from math import floor, isnan
@@ -705,14 +705,22 @@ class Exchange:
f"Available currencies are: {', '.join(quote_currencies)}"
)
def get_valid_pair_combination(self, curr_1: str, curr_2: str) -> str:
def get_valid_pair_combination(self, curr_1: str, curr_2: str) -> Generator[str, None, None]:
"""
Get valid pair combination of curr_1 and curr_2 by trying both combinations.
"""
for pair in [f"{curr_1}/{curr_2}", f"{curr_2}/{curr_1}"]:
yielded = False
for pair in (
f"{curr_1}/{curr_2}",
f"{curr_2}/{curr_1}",
f"{curr_1}/{curr_2}:{curr_2}",
f"{curr_2}/{curr_1}:{curr_1}",
):
if pair in self.markets and self.markets[pair].get("active"):
return pair
raise ValueError(f"Could not combine {curr_1} and {curr_2} to get a valid pair.")
yielded = True
yield pair
if not yielded:
raise ValueError(f"Could not combine {curr_1} and {curr_2} to get a valid pair.")
def validate_timeframes(self, timeframe: str | None) -> None:
"""
@@ -1868,26 +1876,25 @@ class Exchange:
return 1.0
tickers = self.get_tickers(cached=True)
try:
pair = self.get_valid_pair_combination(coin, currency)
for pair in self.get_valid_pair_combination(coin, currency):
ticker: Ticker | None = tickers.get(pair, None)
if not ticker:
tickers_other: Tickers = self.get_tickers(
cached=True,
market_type=(
TradingMode.SPOT
if self.trading_mode != TradingMode.SPOT
else TradingMode.FUTURES
),
)
ticker = tickers_other.get(pair, None)
if ticker:
rate: float | None = ticker.get("last", None)
if rate and pair.startswith(currency) and not pair.endswith(currency):
rate = 1.0 / rate
return rate
except ValueError:
return None
ticker: Ticker | None = tickers.get(pair, None)
if not ticker:
tickers_other: Tickers = self.get_tickers(
cached=True,
market_type=(
TradingMode.SPOT
if self.trading_mode != TradingMode.SPOT
else TradingMode.FUTURES
),
)
ticker = tickers_other.get(pair, None)
if ticker:
rate: float | None = ticker.get("last", None)
if rate and pair.startswith(currency) and not pair.endswith(currency):
rate = 1.0 / rate
return rate
return None
@retrier
@@ -2244,10 +2251,13 @@ class Exchange:
# If cost is None or 0.0 -> falsy, return None
return None
try:
comb = self.get_valid_pair_combination(fee_curr, self._config["stake_currency"])
tick = self.fetch_ticker(comb)
fee_to_quote_rate = safe_value_fallback2(tick, tick, "last", "ask")
for comb in self.get_valid_pair_combination(
fee_curr, self._config["stake_currency"]
):
tick = self.fetch_ticker(comb)
fee_to_quote_rate = safe_value_fallback2(tick, tick, "last", "ask")
if tick:
break
except (ValueError, ExchangeError):
fee_to_quote_rate = self._config["exchange"].get("unknown_fee_rate", None)
if not fee_to_quote_rate:

View File

@@ -2021,10 +2021,7 @@ def test_get_conversion_rate(default_conf_usdt, mocker, exchange_name):
},
}
tick2 = {
"XRP/USDT": {
"symbol": "XRP/USDT",
"bid": 0.5,
"ask": 1,
"ADA/USDT:USDT": {
"last": 2.5,
}
}
@@ -2044,7 +2041,7 @@ def test_get_conversion_rate(default_conf_usdt, mocker, exchange_name):
assert api_mock.fetch_tickers.call_count == 1
api_mock.fetch_tickers.reset_mock()
assert exchange.get_conversion_rate("XRP", "USDT") == 2.5
assert exchange.get_conversion_rate("ADA", "USDT") == 2.5
# Only the call to the "others" market
assert api_mock.fetch_tickers.call_count == 1
@@ -4122,10 +4119,16 @@ def test_get_valid_pair_combination(default_conf, mocker, markets):
)
ex = Exchange(default_conf)
assert ex.get_valid_pair_combination("ETH", "BTC") == "ETH/BTC"
assert ex.get_valid_pair_combination("BTC", "ETH") == "ETH/BTC"
assert next(ex.get_valid_pair_combination("ETH", "BTC")) == "ETH/BTC"
assert next(ex.get_valid_pair_combination("BTC", "ETH")) == "ETH/BTC"
multicombs = list(ex.get_valid_pair_combination("ETH", "USDT"))
assert len(multicombs) == 2
assert "ETH/USDT" in multicombs
assert "ETH/USDT:USDT" in multicombs
with pytest.raises(ValueError, match=r"Could not combine.* to get a valid pair."):
ex.get_valid_pair_combination("NOPAIR", "ETH")
for x in ex.get_valid_pair_combination("NOPAIR", "ETH"):
pass
@pytest.mark.parametrize(

View File

@@ -4021,7 +4021,7 @@ def test_get_real_amount_fees_order(
default_conf_usdt, market_buy_order_usdt_doublefee, fee, mocker
):
tfo_mock = mocker.patch(f"{EXMS}.get_trades_for_order", return_value=[])
mocker.patch(f"{EXMS}.get_valid_pair_combination", return_value="BNB/USDT")
mocker.patch(f"{EXMS}.get_valid_pair_combination", return_value=["BNB/USDT"])
mocker.patch(f"{EXMS}.fetch_ticker", return_value={"last": 200})
trade = Trade(
pair="LTC/USDT",

View File

@@ -586,7 +586,7 @@ 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

View File

@@ -556,7 +556,7 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers):
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}")
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")

View File

@@ -960,7 +960,7 @@ async def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance
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}")
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)
@@ -1049,7 +1049,7 @@ async def test_telegram_balance_handle_futures(
mocker.patch(f"{EXMS}.get_balances", return_value=rpc_balance)
mocker.patch(f"{EXMS}.fetch_positions", return_value=mock_pos)
mocker.patch(f"{EXMS}.get_tickers", tickers)
mocker.patch(f"{EXMS}.get_valid_pair_combination", side_effect=lambda a, b: f"{a}/{b}")
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)