Merge pull request #3396 from freqtrade/fix/broken_getpairs
Use dict for symbol_is_pair
This commit is contained in:
commit
89b9a8cb1f
@ -14,7 +14,7 @@ from freqtrade.configuration import setup_utils_configuration
|
|||||||
from freqtrade.constants import USERPATH_HYPEROPTS, USERPATH_STRATEGIES
|
from freqtrade.constants import USERPATH_HYPEROPTS, USERPATH_STRATEGIES
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange import (available_exchanges, ccxt_exchanges,
|
from freqtrade.exchange import (available_exchanges, ccxt_exchanges,
|
||||||
market_is_active, symbol_is_pair)
|
market_is_active)
|
||||||
from freqtrade.misc import plural
|
from freqtrade.misc import plural
|
||||||
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
||||||
from freqtrade.state import RunMode
|
from freqtrade.state import RunMode
|
||||||
@ -163,7 +163,7 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
|
|||||||
tabular_data.append({'Id': v['id'], 'Symbol': v['symbol'],
|
tabular_data.append({'Id': v['id'], 'Symbol': v['symbol'],
|
||||||
'Base': v['base'], 'Quote': v['quote'],
|
'Base': v['base'], 'Quote': v['quote'],
|
||||||
'Active': market_is_active(v),
|
'Active': market_is_active(v),
|
||||||
**({'Is pair': symbol_is_pair(v['symbol'])}
|
**({'Is pair': exchange.market_is_tradable(v)}
|
||||||
if not pairs_only else {})})
|
if not pairs_only else {})})
|
||||||
|
|
||||||
if (args.get('print_one_column', False) or
|
if (args.get('print_one_column', False) or
|
||||||
|
@ -12,8 +12,7 @@ from freqtrade.exchange.exchange import (timeframe_to_seconds,
|
|||||||
timeframe_to_msecs,
|
timeframe_to_msecs,
|
||||||
timeframe_to_next_date,
|
timeframe_to_next_date,
|
||||||
timeframe_to_prev_date)
|
timeframe_to_prev_date)
|
||||||
from freqtrade.exchange.exchange import (market_is_active,
|
from freqtrade.exchange.exchange import (market_is_active)
|
||||||
symbol_is_pair)
|
|
||||||
from freqtrade.exchange.kraken import Kraken
|
from freqtrade.exchange.kraken import Kraken
|
||||||
from freqtrade.exchange.binance import Binance
|
from freqtrade.exchange.binance import Binance
|
||||||
from freqtrade.exchange.bibox import Bibox
|
from freqtrade.exchange.bibox import Bibox
|
||||||
|
@ -222,7 +222,7 @@ class Exchange:
|
|||||||
if quote_currencies:
|
if quote_currencies:
|
||||||
markets = {k: v for k, v in markets.items() if v['quote'] in quote_currencies}
|
markets = {k: v for k, v in markets.items() if v['quote'] in quote_currencies}
|
||||||
if pairs_only:
|
if pairs_only:
|
||||||
markets = {k: v for k, v in markets.items() if symbol_is_pair(v['symbol'])}
|
markets = {k: v for k, v in markets.items() if self.market_is_tradable(v)}
|
||||||
if active_only:
|
if active_only:
|
||||||
markets = {k: v for k, v in markets.items() if market_is_active(v)}
|
markets = {k: v for k, v in markets.items() if market_is_active(v)}
|
||||||
return markets
|
return markets
|
||||||
@ -246,6 +246,19 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
return self.markets.get(pair, {}).get('base', '')
|
return self.markets.get(pair, {}).get('base', '')
|
||||||
|
|
||||||
|
def market_is_tradable(self, market: Dict[str, Any]) -> bool:
|
||||||
|
"""
|
||||||
|
Check if the market symbol is tradable by Freqtrade.
|
||||||
|
By default, checks if it's splittable by `/` and both sides correspond to base / quote
|
||||||
|
"""
|
||||||
|
symbol_parts = market['symbol'].split('/')
|
||||||
|
return (len(symbol_parts) == 2 and
|
||||||
|
len(symbol_parts[0]) > 0 and
|
||||||
|
len(symbol_parts[1]) > 0 and
|
||||||
|
symbol_parts[0] == market.get('base') and
|
||||||
|
symbol_parts[1] == market.get('quote')
|
||||||
|
)
|
||||||
|
|
||||||
def klines(self, pair_interval: Tuple[str, str], copy: bool = True) -> DataFrame:
|
def klines(self, pair_interval: Tuple[str, str], copy: bool = True) -> DataFrame:
|
||||||
if pair_interval in self._klines:
|
if pair_interval in self._klines:
|
||||||
return self._klines[pair_interval].copy() if copy else self._klines[pair_interval]
|
return self._klines[pair_interval].copy() if copy else self._klines[pair_interval]
|
||||||
@ -1258,20 +1271,6 @@ def timeframe_to_next_date(timeframe: str, date: datetime = None) -> datetime:
|
|||||||
return datetime.fromtimestamp(new_timestamp, tz=timezone.utc)
|
return datetime.fromtimestamp(new_timestamp, tz=timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
def symbol_is_pair(market_symbol: str, base_currency: str = None,
|
|
||||||
quote_currency: str = None) -> bool:
|
|
||||||
"""
|
|
||||||
Check if the market symbol is a pair, i.e. that its symbol consists of the base currency and the
|
|
||||||
quote currency separated by '/' character. If base_currency and/or quote_currency is passed,
|
|
||||||
it also checks that the symbol contains appropriate base and/or quote currency part before
|
|
||||||
and after the separating character correspondingly.
|
|
||||||
"""
|
|
||||||
symbol_parts = market_symbol.split('/')
|
|
||||||
return (len(symbol_parts) == 2 and
|
|
||||||
(symbol_parts[0] == base_currency if base_currency else len(symbol_parts[0]) > 0) and
|
|
||||||
(symbol_parts[1] == quote_currency if quote_currency else len(symbol_parts[1]) > 0))
|
|
||||||
|
|
||||||
|
|
||||||
def market_is_active(market: Dict) -> bool:
|
def market_is_active(market: Dict) -> bool:
|
||||||
"""
|
"""
|
||||||
Return True if the market is active.
|
Return True if the market is active.
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
""" FTX exchange subclass """
|
""" FTX exchange subclass """
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
import ccxt
|
import ccxt
|
||||||
|
|
||||||
@ -20,6 +20,16 @@ class Ftx(Exchange):
|
|||||||
"ohlcv_candle_limit": 1500,
|
"ohlcv_candle_limit": 1500,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def market_is_tradable(self, market: Dict[str, Any]) -> bool:
|
||||||
|
"""
|
||||||
|
Check if the market symbol is tradable by Freqtrade.
|
||||||
|
Default checks + check if pair is spot pair (no futures trading yet).
|
||||||
|
"""
|
||||||
|
parent_check = super().market_is_tradable(market)
|
||||||
|
|
||||||
|
return (parent_check and
|
||||||
|
market.get('spot', False) is True)
|
||||||
|
|
||||||
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
|
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
|
||||||
"""
|
"""
|
||||||
Verify stop_loss against stoploss-order value (limit or price)
|
Verify stop_loss against stoploss-order value (limit or price)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
""" Kraken exchange subclass """
|
""" Kraken exchange subclass """
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict
|
from typing import Any, Dict
|
||||||
|
|
||||||
import ccxt
|
import ccxt
|
||||||
|
|
||||||
@ -22,6 +22,16 @@ class Kraken(Exchange):
|
|||||||
"trades_pagination_arg": "since",
|
"trades_pagination_arg": "since",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def market_is_tradable(self, market: Dict[str, Any]) -> bool:
|
||||||
|
"""
|
||||||
|
Check if the market symbol is tradable by Freqtrade.
|
||||||
|
Default checks + check if pair is darkpool pair.
|
||||||
|
"""
|
||||||
|
parent_check = super().market_is_tradable(market)
|
||||||
|
|
||||||
|
return (parent_check and
|
||||||
|
market.get('darkpool', False) is False)
|
||||||
|
|
||||||
@retrier
|
@retrier
|
||||||
def get_balances(self) -> dict:
|
def get_balances(self) -> dict:
|
||||||
if self._config['dry_run']:
|
if self._config['dry_run']:
|
||||||
|
@ -162,6 +162,11 @@ class IPairList(ABC):
|
|||||||
f"{self._exchange.name}. Removing it from whitelist..")
|
f"{self._exchange.name}. Removing it from whitelist..")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if not self._exchange.market_is_tradable(markets[pair]):
|
||||||
|
logger.warning(f"Pair {pair} is not tradable with Freqtrade."
|
||||||
|
"Removing it from whitelist..")
|
||||||
|
continue
|
||||||
|
|
||||||
if self._exchange.get_pair_quote_currency(pair) != self._config['stake_currency']:
|
if self._exchange.get_pair_quote_currency(pair) != self._config['stake_currency']:
|
||||||
logger.warning(f"Pair {pair} is not compatible with your stake currency "
|
logger.warning(f"Pair {pair} is not compatible with your stake currency "
|
||||||
f"{self._config['stake_currency']}. Removing it from whitelist..")
|
f"{self._config['stake_currency']}. Removing it from whitelist..")
|
||||||
|
@ -11,11 +11,12 @@ import ccxt
|
|||||||
import pytest
|
import pytest
|
||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
from freqtrade.exceptions import (DependencyException, InvalidOrderException, DDosProtection,
|
from freqtrade.exceptions import (DDosProtection, DependencyException,
|
||||||
OperationalException, TemporaryError)
|
InvalidOrderException, OperationalException,
|
||||||
|
TemporaryError)
|
||||||
from freqtrade.exchange import Binance, Exchange, Kraken
|
from freqtrade.exchange import Binance, Exchange, Kraken
|
||||||
from freqtrade.exchange.common import API_RETRY_COUNT, calculate_backoff
|
from freqtrade.exchange.common import API_RETRY_COUNT, calculate_backoff
|
||||||
from freqtrade.exchange.exchange import (market_is_active, symbol_is_pair,
|
from freqtrade.exchange.exchange import (market_is_active,
|
||||||
timeframe_to_minutes,
|
timeframe_to_minutes,
|
||||||
timeframe_to_msecs,
|
timeframe_to_msecs,
|
||||||
timeframe_to_next_date,
|
timeframe_to_next_date,
|
||||||
@ -2218,25 +2219,42 @@ def test_timeframe_to_next_date():
|
|||||||
assert timeframe_to_next_date("5m") > date
|
assert timeframe_to_next_date("5m") > date
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("market_symbol,base_currency,quote_currency,expected_result", [
|
@pytest.mark.parametrize("market_symbol,base,quote,exchange,add_dict,expected_result", [
|
||||||
("BTC/USDT", None, None, True),
|
("BTC/USDT", 'BTC', 'USDT', "binance", {}, True),
|
||||||
("USDT/BTC", None, None, True),
|
("USDT/BTC", 'USDT', 'BTC', "binance", {}, True),
|
||||||
("BTCUSDT", None, None, False),
|
("USDT/BTC", 'BTC', 'USDT', "binance", {}, False), # Reversed currencies
|
||||||
("BTC/USDT", None, "USDT", True),
|
("BTCUSDT", 'BTC', 'USDT', "binance", {}, False), # No seperating /
|
||||||
("USDT/BTC", None, "USDT", False),
|
("BTCUSDT", None, "USDT", "binance", {}, False), #
|
||||||
("BTCUSDT", None, "USDT", False),
|
("USDT/BTC", "BTC", None, "binance", {}, False),
|
||||||
("BTC/USDT", "BTC", None, True),
|
("BTCUSDT", "BTC", None, "binance", {}, False),
|
||||||
("USDT/BTC", "BTC", None, False),
|
("BTC/USDT", "BTC", "USDT", "binance", {}, True),
|
||||||
("BTCUSDT", "BTC", None, False),
|
("BTC/USDT", "USDT", "BTC", "binance", {}, False), # reversed currencies
|
||||||
("BTC/USDT", "BTC", "USDT", True),
|
("BTC/USDT", "BTC", "USD", "binance", {}, False), # Wrong quote currency
|
||||||
("BTC/USDT", "USDT", "BTC", False),
|
("BTC/", "BTC", 'UNK', "binance", {}, False),
|
||||||
("BTC/USDT", "BTC", "USD", False),
|
("/USDT", 'UNK', 'USDT', "binance", {}, False),
|
||||||
("BTCUSDT", "BTC", "USDT", False),
|
("BTC/EUR", 'BTC', 'EUR', "kraken", {"darkpool": False}, True),
|
||||||
("BTC/", None, None, False),
|
("EUR/BTC", 'EUR', 'BTC', "kraken", {"darkpool": False}, True),
|
||||||
("/USDT", None, None, False),
|
("EUR/BTC", 'BTC', 'EUR', "kraken", {"darkpool": False}, False), # Reversed currencies
|
||||||
|
("BTC/EUR", 'BTC', 'USD', "kraken", {"darkpool": False}, False), # wrong quote currency
|
||||||
|
("BTC/EUR", 'BTC', 'EUR', "kraken", {"darkpool": True}, False), # no darkpools
|
||||||
|
("BTC/EUR.d", 'BTC', 'EUR', "kraken", {"darkpool": True}, False), # no darkpools
|
||||||
|
("BTC/USD", 'BTC', 'USD', "ftx", {'spot': True}, True),
|
||||||
|
("USD/BTC", 'USD', 'BTC', "ftx", {'spot': True}, True),
|
||||||
|
("BTC/USD", 'BTC', 'USDT', "ftx", {'spot': True}, False), # Wrong quote currency
|
||||||
|
("BTC/USD", 'USD', 'BTC', "ftx", {'spot': True}, False), # Reversed currencies
|
||||||
|
("BTC/USD", 'BTC', 'USD', "ftx", {'spot': False}, False), # Can only trade spot markets
|
||||||
|
("BTC-PERP", 'BTC', 'USD', "ftx", {'spot': False}, False), # Can only trade spot markets
|
||||||
])
|
])
|
||||||
def test_symbol_is_pair(market_symbol, base_currency, quote_currency, expected_result) -> None:
|
def test_market_is_tradable(mocker, default_conf, market_symbol, base,
|
||||||
assert symbol_is_pair(market_symbol, base_currency, quote_currency) == expected_result
|
quote, add_dict, exchange, expected_result) -> None:
|
||||||
|
ex = get_patched_exchange(mocker, default_conf, id=exchange)
|
||||||
|
market = {
|
||||||
|
'symbol': market_symbol,
|
||||||
|
'base': base,
|
||||||
|
'quote': quote,
|
||||||
|
**(add_dict),
|
||||||
|
}
|
||||||
|
assert ex.market_is_tradable(market) == expected_result
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("market,expected_result", [
|
@pytest.mark.parametrize("market,expected_result", [
|
||||||
|
@ -468,7 +468,9 @@ def test_pairlist_class(mocker, whitelist_conf, markets, pairlist):
|
|||||||
# BCH/BTC not available
|
# BCH/BTC not available
|
||||||
(['ETH/BTC', 'TKN/BTC', 'BCH/BTC'], "is not compatible with exchange"),
|
(['ETH/BTC', 'TKN/BTC', 'BCH/BTC'], "is not compatible with exchange"),
|
||||||
# BTT/BTC is inactive
|
# BTT/BTC is inactive
|
||||||
(['ETH/BTC', 'TKN/BTC', 'BTT/BTC'], "Market is not active")
|
(['ETH/BTC', 'TKN/BTC', 'BTT/BTC'], "Market is not active"),
|
||||||
|
# XLTCUSDT is not a valid pair
|
||||||
|
(['ETH/BTC', 'TKN/BTC', 'XLTCUSDT'], "is not tradable with Freqtrade"),
|
||||||
])
|
])
|
||||||
def test__whitelist_for_active_markets(mocker, whitelist_conf, markets, pairlist, whitelist, caplog,
|
def test__whitelist_for_active_markets(mocker, whitelist_conf, markets, pairlist, whitelist, caplog,
|
||||||
log_message, tickers):
|
log_message, tickers):
|
||||||
|
Loading…
Reference in New Issue
Block a user