2018-12-05 19:44:56 +00:00
|
|
|
"""
|
2020-05-19 00:35:01 +00:00
|
|
|
PairList Handler base class
|
2020-05-17 11:13:26 +00:00
|
|
|
"""
|
2024-05-12 14:37:11 +00:00
|
|
|
|
2018-12-05 19:44:56 +00:00
|
|
|
import logging
|
2024-06-09 06:44:04 +00:00
|
|
|
from abc import ABC, abstractmethod
|
2020-05-20 10:27:07 +00:00
|
|
|
from copy import deepcopy
|
2024-06-21 12:01:25 +00:00
|
|
|
from enum import Enum
|
2023-04-19 19:08:44 +00:00
|
|
|
from typing import Any, Dict, List, Literal, Optional, TypedDict, Union
|
2018-12-05 19:44:56 +00:00
|
|
|
|
2022-09-18 11:31:52 +00:00
|
|
|
from freqtrade.constants import Config
|
2020-05-22 12:03:49 +00:00
|
|
|
from freqtrade.exceptions import OperationalException
|
2021-05-17 04:47:49 +00:00
|
|
|
from freqtrade.exchange import Exchange, market_is_active
|
2022-10-11 19:33:02 +00:00
|
|
|
from freqtrade.exchange.types import Ticker, Tickers
|
2020-10-13 05:11:08 +00:00
|
|
|
from freqtrade.mixins import LoggingMixin
|
2019-10-17 16:06:58 +00:00
|
|
|
|
2020-05-17 11:13:26 +00:00
|
|
|
|
2018-12-05 19:44:56 +00:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2023-04-20 05:03:27 +00:00
|
|
|
class __PairlistParameterBase(TypedDict):
|
2023-04-19 19:08:44 +00:00
|
|
|
description: str
|
|
|
|
help: str
|
|
|
|
|
|
|
|
|
2023-04-20 05:03:27 +00:00
|
|
|
class __NumberPairlistParameter(__PairlistParameterBase):
|
|
|
|
type: Literal["number"]
|
|
|
|
default: Union[int, float, None]
|
|
|
|
|
|
|
|
|
|
|
|
class __StringPairlistParameter(__PairlistParameterBase):
|
|
|
|
type: Literal["string"]
|
|
|
|
default: Union[str, None]
|
|
|
|
|
|
|
|
|
2023-04-21 17:30:32 +00:00
|
|
|
class __OptionPairlistParameter(__PairlistParameterBase):
|
|
|
|
type: Literal["option"]
|
|
|
|
default: Union[str, None]
|
|
|
|
options: List[str]
|
|
|
|
|
|
|
|
|
2023-04-20 05:03:27 +00:00
|
|
|
class __BoolPairlistParameter(__PairlistParameterBase):
|
|
|
|
type: Literal["boolean"]
|
|
|
|
default: Union[bool, None]
|
|
|
|
|
|
|
|
|
|
|
|
PairlistParameter = Union[
|
|
|
|
__NumberPairlistParameter,
|
|
|
|
__StringPairlistParameter,
|
2023-04-21 17:30:32 +00:00
|
|
|
__OptionPairlistParameter,
|
2024-05-12 14:37:11 +00:00
|
|
|
__BoolPairlistParameter,
|
|
|
|
]
|
2023-04-20 05:03:27 +00:00
|
|
|
|
|
|
|
|
2024-06-21 12:01:25 +00:00
|
|
|
class SupportsBacktesting(str, Enum):
|
|
|
|
"""
|
|
|
|
Enum to indicate if a Pairlist Handler supports backtesting.
|
|
|
|
"""
|
|
|
|
|
|
|
|
YES = "yes"
|
|
|
|
NO = "no"
|
|
|
|
NO_ACTION = "no_action"
|
2024-06-21 15:43:27 +00:00
|
|
|
BIASED = "biased"
|
2024-06-21 12:01:25 +00:00
|
|
|
|
|
|
|
|
2020-10-13 05:11:08 +00:00
|
|
|
class IPairList(LoggingMixin, ABC):
|
2023-04-20 16:09:47 +00:00
|
|
|
is_pairlist_generator = False
|
2024-06-21 12:01:25 +00:00
|
|
|
supports_backtesting: SupportsBacktesting = SupportsBacktesting.NO
|
2023-04-20 16:09:47 +00:00
|
|
|
|
2024-05-12 14:37:11 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
exchange: Exchange,
|
|
|
|
pairlistmanager,
|
|
|
|
config: Config,
|
|
|
|
pairlistconfig: Dict[str, Any],
|
|
|
|
pairlist_pos: int,
|
|
|
|
) -> None:
|
2019-11-09 14:04:04 +00:00
|
|
|
"""
|
|
|
|
:param exchange: Exchange instance
|
2020-05-19 00:35:01 +00:00
|
|
|
:param pairlistmanager: Instantiated Pairlist manager
|
2019-11-09 14:04:04 +00:00
|
|
|
:param config: Global bot configuration
|
2020-05-19 00:35:01 +00:00
|
|
|
:param pairlistconfig: Configuration for this Pairlist Handler - can be empty.
|
|
|
|
:param pairlist_pos: Position of the Pairlist Handler in the chain
|
2019-11-09 14:04:04 +00:00
|
|
|
"""
|
2020-05-20 10:41:00 +00:00
|
|
|
self._enabled = True
|
|
|
|
|
2021-05-17 04:47:49 +00:00
|
|
|
self._exchange: Exchange = exchange
|
2019-11-09 06:19:46 +00:00
|
|
|
self._pairlistmanager = pairlistmanager
|
2018-12-05 19:44:56 +00:00
|
|
|
self._config = config
|
2019-11-09 05:55:16 +00:00
|
|
|
self._pairlistconfig = pairlistconfig
|
2019-11-09 14:04:04 +00:00
|
|
|
self._pairlist_pos = pairlist_pos
|
2024-05-12 14:37:11 +00:00
|
|
|
self.refresh_period = self._pairlistconfig.get("refresh_period", 1800)
|
2020-10-13 05:11:08 +00:00
|
|
|
LoggingMixin.__init__(self, logger, self.refresh_period)
|
2018-12-05 19:44:56 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def name(self) -> str:
|
|
|
|
"""
|
|
|
|
Gets name of the class
|
|
|
|
-> no need to overwrite in subclasses
|
|
|
|
"""
|
|
|
|
return self.__class__.__name__
|
|
|
|
|
2024-06-09 06:44:04 +00:00
|
|
|
@property
|
|
|
|
@abstractmethod
|
2019-11-09 06:05:17 +00:00
|
|
|
def needstickers(self) -> bool:
|
|
|
|
"""
|
|
|
|
Boolean property defining if tickers are necessary.
|
2020-11-24 19:24:51 +00:00
|
|
|
If no Pairlist requires tickers, an empty Dict is passed
|
2019-11-09 06:05:17 +00:00
|
|
|
as tickers argument to filter_pairlist
|
|
|
|
"""
|
2023-05-28 16:21:23 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
@abstractmethod
|
|
|
|
def description() -> str:
|
|
|
|
"""
|
|
|
|
Return description of this Pairlist Handler
|
|
|
|
-> Please overwrite in subclasses
|
|
|
|
"""
|
|
|
|
return ""
|
2019-11-09 06:05:17 +00:00
|
|
|
|
2023-04-19 19:08:44 +00:00
|
|
|
@staticmethod
|
|
|
|
def available_parameters() -> Dict[str, PairlistParameter]:
|
|
|
|
"""
|
|
|
|
Return parameters used by this Pairlist Handler, and their type
|
|
|
|
contains a dictionary with the parameter name as key, and a dictionary
|
|
|
|
with the type and default value.
|
|
|
|
-> Please overwrite in subclasses
|
|
|
|
"""
|
|
|
|
return {}
|
|
|
|
|
|
|
|
@staticmethod
|
2023-04-20 04:58:05 +00:00
|
|
|
def refresh_period_parameter() -> Dict[str, PairlistParameter]:
|
2023-04-19 19:08:44 +00:00
|
|
|
return {
|
|
|
|
"refresh_period": {
|
|
|
|
"type": "number",
|
|
|
|
"default": 1800,
|
|
|
|
"description": "Refresh period",
|
|
|
|
"help": "Refresh period in seconds",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-05 19:44:56 +00:00
|
|
|
@abstractmethod
|
|
|
|
def short_desc(self) -> str:
|
|
|
|
"""
|
|
|
|
Short whitelist method description - used for startup-messages
|
|
|
|
-> Please overwrite in subclasses
|
|
|
|
"""
|
|
|
|
|
2022-10-11 19:33:02 +00:00
|
|
|
def _validate_pair(self, pair: str, ticker: Optional[Ticker]) -> bool:
|
2020-05-20 10:27:07 +00:00
|
|
|
"""
|
|
|
|
Check one pair against Pairlist Handler's specific conditions.
|
|
|
|
|
|
|
|
Either implement it in the Pairlist Handler or override the generic
|
|
|
|
filter_pairlist() method.
|
|
|
|
|
2020-12-15 07:36:42 +00:00
|
|
|
:param pair: Pair that's currently validated
|
2022-10-10 11:54:13 +00:00
|
|
|
:param ticker: ticker dict as returned from ccxt.fetch_ticker
|
2020-05-20 10:27:07 +00:00
|
|
|
:return: True if the pair can stay, false if it should be removed
|
|
|
|
"""
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
2022-10-11 19:33:02 +00:00
|
|
|
def gen_pairlist(self, tickers: Tickers) -> List[str]:
|
2020-05-22 12:03:49 +00:00
|
|
|
"""
|
2020-05-25 19:49:57 +00:00
|
|
|
Generate the pairlist.
|
|
|
|
|
|
|
|
This method is called once by the pairlistmanager in the refresh_pairlist()
|
|
|
|
method to supply the starting pairlist for the chain of the Pairlist Handlers.
|
|
|
|
Pairlist Filters (those Pairlist Handlers that cannot be used at the first
|
|
|
|
position in the chain) shall not override this base implementation --
|
|
|
|
it will raise the exception if a Pairlist Handler is used at the first
|
|
|
|
position in the chain.
|
|
|
|
|
2022-10-10 11:54:13 +00:00
|
|
|
:param tickers: Tickers (from exchange.get_tickers). May be cached.
|
2020-05-22 12:03:49 +00:00
|
|
|
:return: List of pairs
|
|
|
|
"""
|
2024-05-12 14:37:11 +00:00
|
|
|
raise OperationalException(
|
|
|
|
"This Pairlist Handler should not be used "
|
|
|
|
"at the first position in the list of Pairlist Handlers."
|
|
|
|
)
|
2020-05-22 12:03:49 +00:00
|
|
|
|
2022-10-11 19:33:02 +00:00
|
|
|
def filter_pairlist(self, pairlist: List[str], tickers: Tickers) -> List[str]:
|
2018-12-05 19:44:56 +00:00
|
|
|
"""
|
2019-11-09 05:55:16 +00:00
|
|
|
Filters and sorts pairlist and returns the whitelist again.
|
2020-05-20 10:27:07 +00:00
|
|
|
|
2019-11-09 05:55:16 +00:00
|
|
|
Called on each bot iteration - please use internal caching if necessary
|
2020-05-20 10:27:07 +00:00
|
|
|
This generic implementation calls self._validate_pair() for each pair
|
|
|
|
in the pairlist.
|
|
|
|
|
|
|
|
Some Pairlist Handlers override this generic implementation and employ
|
|
|
|
own filtration.
|
|
|
|
|
2019-11-09 05:55:16 +00:00
|
|
|
:param pairlist: pairlist to filter or sort
|
2022-10-10 11:54:13 +00:00
|
|
|
:param tickers: Tickers (from exchange.get_tickers). May be cached.
|
2019-11-09 05:55:16 +00:00
|
|
|
:return: new whitelist
|
2018-12-05 19:44:56 +00:00
|
|
|
"""
|
2020-05-20 10:41:00 +00:00
|
|
|
if self._enabled:
|
|
|
|
# Copy list since we're modifying this list
|
|
|
|
for p in deepcopy(pairlist):
|
|
|
|
# Filter out assets
|
2022-10-11 19:33:02 +00:00
|
|
|
if not self._validate_pair(p, tickers[p] if p in tickers else None):
|
2020-05-20 10:41:00 +00:00
|
|
|
pairlist.remove(p)
|
2020-05-20 10:27:07 +00:00
|
|
|
|
|
|
|
return pairlist
|
2018-12-05 19:44:56 +00:00
|
|
|
|
2020-05-19 20:51:39 +00:00
|
|
|
def verify_blacklist(self, pairlist: List[str], logmethod) -> List[str]:
|
2019-11-09 06:23:34 +00:00
|
|
|
"""
|
|
|
|
Proxy method to verify_blacklist for easy access for child classes.
|
2020-03-09 10:30:13 +00:00
|
|
|
:param pairlist: Pairlist to validate
|
2020-05-19 20:51:39 +00:00
|
|
|
:param logmethod: Function that'll be called, `logger.info` or `logger.warning`.
|
2020-03-09 10:30:13 +00:00
|
|
|
:return: pairlist - blacklisted pairs
|
|
|
|
"""
|
2020-05-19 20:51:39 +00:00
|
|
|
return self._pairlistmanager.verify_blacklist(pairlist, logmethod)
|
2019-11-09 06:23:34 +00:00
|
|
|
|
2024-05-12 14:37:11 +00:00
|
|
|
def verify_whitelist(
|
|
|
|
self, pairlist: List[str], logmethod, keep_invalid: bool = False
|
|
|
|
) -> List[str]:
|
2021-01-12 00:13:58 +00:00
|
|
|
"""
|
|
|
|
Proxy method to verify_whitelist for easy access for child classes.
|
|
|
|
:param pairlist: Pairlist to validate
|
2021-01-14 23:13:11 +00:00
|
|
|
:param logmethod: Function that'll be called, `logger.info` or `logger.warning`
|
|
|
|
:param keep_invalid: If sets to True, drops invalid pairs silently while expanding regexes.
|
2021-01-12 00:13:58 +00:00
|
|
|
:return: pairlist - whitelisted pairs
|
|
|
|
"""
|
2021-01-14 23:13:11 +00:00
|
|
|
return self._pairlistmanager.verify_whitelist(pairlist, logmethod, keep_invalid)
|
2021-01-12 00:13:58 +00:00
|
|
|
|
2019-11-09 13:44:39 +00:00
|
|
|
def _whitelist_for_active_markets(self, pairlist: List[str]) -> List[str]:
|
2018-12-05 19:44:56 +00:00
|
|
|
"""
|
|
|
|
Check available markets and remove pair from whitelist if necessary
|
2021-01-14 23:13:11 +00:00
|
|
|
:param pairlist: the sorted list of pairs the user might want to trade
|
2019-03-11 20:10:22 +00:00
|
|
|
:return: the list of pairs the user wants to trade without those unavailable or
|
2018-12-05 19:44:56 +00:00
|
|
|
black_listed
|
|
|
|
"""
|
2019-11-09 05:55:16 +00:00
|
|
|
markets = self._exchange.markets
|
2020-06-10 17:57:59 +00:00
|
|
|
if not markets:
|
|
|
|
raise OperationalException(
|
2024-05-12 14:37:11 +00:00
|
|
|
"Markets not loaded. Make sure that exchange is initialized correctly."
|
|
|
|
)
|
2018-12-05 19:44:56 +00:00
|
|
|
|
2019-10-30 14:59:52 +00:00
|
|
|
sanitized_whitelist: List[str] = []
|
2019-11-09 13:44:39 +00:00
|
|
|
for pair in pairlist:
|
2019-11-09 05:55:16 +00:00
|
|
|
# pair is not in the generated dynamic market or has the wrong stake currency
|
2019-11-12 08:27:53 +00:00
|
|
|
if pair not in markets:
|
2024-05-12 14:37:11 +00:00
|
|
|
self.log_once(
|
|
|
|
f"Pair {pair} is not compatible with exchange "
|
|
|
|
f"{self._exchange.name}. Removing it from whitelist..",
|
|
|
|
logger.warning,
|
|
|
|
)
|
2018-12-05 19:44:56 +00:00
|
|
|
continue
|
2020-02-24 19:41:45 +00:00
|
|
|
|
2020-06-02 18:41:29 +00:00
|
|
|
if not self._exchange.market_is_tradable(markets[pair]):
|
2024-05-12 14:37:11 +00:00
|
|
|
self.log_once(
|
2024-05-12 15:51:21 +00:00
|
|
|
f"Pair {pair} is not tradable with Freqtrade. Removing it from whitelist..",
|
2024-05-12 14:37:11 +00:00
|
|
|
logger.warning,
|
|
|
|
)
|
2020-06-02 18:41:29 +00:00
|
|
|
continue
|
|
|
|
|
2024-05-12 14:37:11 +00:00
|
|
|
if self._exchange.get_pair_quote_currency(pair) != self._config["stake_currency"]:
|
|
|
|
self.log_once(
|
|
|
|
f"Pair {pair} is not compatible with your stake currency "
|
|
|
|
f"{self._config['stake_currency']}. Removing it from whitelist..",
|
|
|
|
logger.warning,
|
|
|
|
)
|
2019-11-12 08:27:53 +00:00
|
|
|
continue
|
|
|
|
|
2019-03-05 20:23:55 +00:00
|
|
|
# Check if market is active
|
|
|
|
market = markets[pair]
|
2019-10-17 16:06:58 +00:00
|
|
|
if not market_is_active(market):
|
2021-02-01 18:40:31 +00:00
|
|
|
self.log_once(f"Ignoring {pair} from whitelist. Market is not active.", logger.info)
|
2019-03-05 20:23:55 +00:00
|
|
|
continue
|
2019-10-30 14:59:52 +00:00
|
|
|
if pair not in sanitized_whitelist:
|
|
|
|
sanitized_whitelist.append(pair)
|
2018-12-05 19:44:56 +00:00
|
|
|
|
|
|
|
# We need to remove pairs that are unknown
|
2019-10-30 14:59:52 +00:00
|
|
|
return sanitized_whitelist
|