load pairlists via resolver

This commit is contained in:
Matthias 2018-12-05 20:44:56 +01:00
parent 43031aa3bb
commit 3e2fa58029
7 changed files with 179 additions and 86 deletions

View File

@ -20,6 +20,16 @@ This is a simple provider, which however serves as a good example on how to star
Next, modify the classname of the provider (ideally align this with the Filename). Next, modify the classname of the provider (ideally align this with the Filename).
The base-class provides the an instance of the bot (`self._freqtrade`), as well as the configuration (`self._config`), and initiates both `_blacklist` and `_whitelist`.
```python
self._freqtrade = freqtrade
self._config = config
self._whitelist = self._config['exchange']['pair_whitelist']
self._blacklist = self._config['exchange'].get('pair_blacklist', [])
```
Now, let's step through the methods which require actions: Now, let's step through the methods which require actions:
#### configuration #### configuration
@ -35,7 +45,7 @@ Additional elements can be configured as needed. `VolumePairList` uses `"sort_ke
Returns a description used for Telegram messages. Returns a description used for Telegram messages.
This should coutain the name of the Provider, as well as a short description containing the number of assets. Please follow the format `"PairlistName - top/bottom X pairs"`. This should coutain the name of the Provider, as well as a short description containing the number of assets. Please follow the format `"PairlistName - top/bottom X pairs"`.
#### refresh_whitelist #### refresh_pairlist
Override this method and run all calculations needed in this method. Override this method and run all calculations needed in this method.
This is called with each iteration of the bot - so consider implementing caching for compute/network heavy calculations. This is called with each iteration of the bot - so consider implementing caching for compute/network heavy calculations.
@ -47,7 +57,7 @@ Please also run `self._validate_whitelist(pairs)` and to check and remove pairs
##### sample ##### sample
``` python ``` python
def refresh_whitelist(self) -> None: def refresh_pairlist(self) -> None:
# Generate dynamic whitelist # Generate dynamic whitelist
pairs = self._gen_pair_whitelist(self._config['stake_currency'], self._sort_key) pairs = self._gen_pair_whitelist(self._config['stake_currency'], self._sort_key)
# Validate whitelist to only have active market pairs # Validate whitelist to only have active market pairs

View File

@ -19,12 +19,10 @@ from freqtrade.wallets import Wallets
from freqtrade.edge import Edge from freqtrade.edge import Edge
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.rpc import RPCManager, RPCMessageType
from freqtrade.resolvers import StrategyResolver from freqtrade.resolvers import StrategyResolver, PairListResolver
from freqtrade.state import State from freqtrade.state import State
from freqtrade.strategy.interface import SellType, IStrategy from freqtrade.strategy.interface import SellType, IStrategy
from freqtrade.exchange.exchange_helpers import order_book_to_dataframe from freqtrade.exchange.exchange_helpers import order_book_to_dataframe
from freqtrade.pairlist.StaticPairList import StaticPairList
from freqtrade.pairlist.VolumePairList import VolumePairList
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -59,10 +57,8 @@ class FreqtradeBot(object):
self.persistence = None self.persistence = None
self.exchange = Exchange(self.config) self.exchange = Exchange(self.config)
self.wallets = Wallets(self.exchange) self.wallets = Wallets(self.exchange)
if self.config.get('pairlist', {}).get('method') == 'VolumePairList': pairlistname = self.config.get('pairlist', {}).get('method', 'StaticPairList')
self.pairlists: StaticPairList = VolumePairList(self, self.config) self.pairlists = PairListResolver(pairlistname, self, self.config).pairlist
else:
self.pairlists: StaticPairList = StaticPairList(self, self.config)
# Initializing Edge only if enabled # Initializing Edge only if enabled
self.edge = Edge(self.config, self.exchange, self.strategy) if \ self.edge = Edge(self.config, self.exchange, self.strategy) if \
@ -151,7 +147,7 @@ class FreqtradeBot(object):
state_changed = False state_changed = False
try: try:
# Refresh whitelist # Refresh whitelist
self.pairlists.refresh_whitelist() self.pairlists.refresh_pairlist()
self.active_pair_whitelist = self.pairlists.whitelist self.active_pair_whitelist = self.pairlists.whitelist
# Calculating Edge positiong # Calculating Edge positiong

View File

@ -0,0 +1,91 @@
"""
Static List provider
Provides lists as configured in config.json
"""
import logging
from abc import ABC, abstractmethod
from typing import List
logger = logging.getLogger(__name__)
class IPairList(ABC):
def __init__(self, freqtrade, config: dict) -> None:
self._freqtrade = freqtrade
self._config = config
self._whitelist = self._config['exchange']['pair_whitelist']
self._blacklist = self._config['exchange'].get('pair_blacklist', [])
@property
def name(self) -> str:
"""
Gets name of the class
-> no need to overwrite in subclasses
"""
return self.__class__.__name__
@property
def whitelist(self) -> List[str]:
"""
Has the current whitelist
-> no need to overwrite in subclasses
"""
return self._whitelist
@property
def blacklist(self) -> List[str]:
"""
Has the current blacklist
-> no need to overwrite in subclasses
"""
return self._blacklist
@abstractmethod
def short_desc(self) -> str:
"""
Short whitelist method description - used for startup-messages
-> Please overwrite in subclasses
"""
@abstractmethod
def refresh_pairlist(self) -> None:
"""
Refreshes pairlists and assigns them to self._whitelist and self._blacklist respectively
-> Please overwrite in subclasses
"""
def _validate_whitelist(self, whitelist: List[str]) -> List[str]:
"""
Check available markets and remove pair from whitelist if necessary
:param whitelist: the sorted list (based on BaseVolume) of pairs the user might want to
trade
:return: the list of pairs the user wants to trade without the one unavailable or
black_listed
"""
sanitized_whitelist = whitelist
markets = self._freqtrade.exchange.get_markets()
# Filter to markets in stake currency
markets = [m for m in markets if m['quote'] == self._config['stake_currency']]
known_pairs = set()
for market in markets:
pair = market['symbol']
# pair is not int the generated dynamic market, or in the blacklist ... ignore it
if pair not in whitelist or pair in self.blacklist:
continue
# else the pair is valid
known_pairs.add(pair)
# Market is not active
if not market['active']:
sanitized_whitelist.remove(pair)
logger.info(
'Ignoring %s from whitelist. Market is not active.',
pair
)
# We need to remove pairs that are unknown
return [x for x in sanitized_whitelist if x in known_pairs]

View File

@ -5,43 +5,16 @@ Provides lists as configured in config.json
""" """
import logging import logging
from typing import List
from freqtrade.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class StaticPairList(object): class StaticPairList(IPairList):
def __init__(self, freqtrade, config: dict) -> None: def __init__(self, freqtrade, config: dict) -> None:
self._freqtrade = freqtrade super().__init__(freqtrade, config)
self._config = config
self._whitelist = self._config['exchange']['pair_whitelist']
self._blacklist = self._config['exchange'].get('pair_blacklist', [])
# self.refresh_whitelist()
@property
def name(self) -> str:
"""
Gets name of the class
-> no need to overwrite in subclasses
"""
return self.__class__.__name__
@property
def whitelist(self) -> List[str]:
"""
Has the current whitelist
-> no need to overwrite in subclasses
"""
return self._whitelist
@property
def blacklist(self) -> List[str]:
"""
Has the current blacklist
-> no need to overwrite in subclasses
"""
return self._blacklist
def short_desc(self) -> str: def short_desc(self) -> str:
""" """
@ -50,41 +23,8 @@ class StaticPairList(object):
""" """
return f"{self.name}: {self.whitelist}" return f"{self.name}: {self.whitelist}"
def refresh_whitelist(self) -> None: def refresh_pairlist(self) -> None:
""" """
Refreshes whitelist and assigns it to self._whitelist Refreshes pairlists and assigns them to self._whitelist and self._blacklist respectively
""" """
self._whitelist = self._validate_whitelist(self._config['exchange']['pair_whitelist']) self._whitelist = self._validate_whitelist(self._config['exchange']['pair_whitelist'])
def _validate_whitelist(self, whitelist: List[str]) -> List[str]:
"""
Check available markets and remove pair from whitelist if necessary
:param whitelist: the sorted list (based on BaseVolume) of pairs the user might want to
trade
:return: the list of pairs the user wants to trade without the one unavailable or
black_listed
"""
sanitized_whitelist = whitelist
markets = self._freqtrade.exchange.get_markets()
# Filter to markets in stake currency
markets = [m for m in markets if m['quote'] == self._config['stake_currency']]
known_pairs = set()
for market in markets:
pair = market['symbol']
# pair is not int the generated dynamic market, or in the blacklist ... ignore it
if pair not in whitelist or pair in self.blacklist:
continue
# else the pair is valid
known_pairs.add(pair)
# Market is not active
if not market['active']:
sanitized_whitelist.remove(pair)
logger.info(
'Ignoring %s from whitelist. Market is not active.',
pair
)
# We need to remove pairs that are unknown
return [x for x in sanitized_whitelist if x in known_pairs]

View File

@ -8,21 +8,18 @@ import logging
from typing import List from typing import List
from cachetools import TTLCache, cached from cachetools import TTLCache, cached
from freqtrade.pairlist.StaticPairList import StaticPairList from freqtrade.pairlist.IPairList import IPairList
from freqtrade import OperationalException from freqtrade import OperationalException
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume'] SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume']
class VolumePairList(StaticPairList): class VolumePairList(IPairList):
def __init__(self, freqtrade, config: dict) -> None: def __init__(self, freqtrade, config: dict) -> None:
self._freqtrade = freqtrade super().__init__(freqtrade, config)
self._config = config
self._whitelistconf = self._config.get('pairlist', {}).get('config') self._whitelistconf = self._config.get('pairlist', {}).get('config')
self._whitelist = self._config['exchange']['pair_whitelist']
self._blacklist = self._config['exchange'].get('pair_blacklist', [])
self._number_pairs = self._whitelistconf['number_assets'] self._number_pairs = self._whitelistconf['number_assets']
self._sort_key = self._whitelistconf.get('sort_key', 'quoteVolume') self._sort_key = self._whitelistconf.get('sort_key', 'quoteVolume')
@ -34,7 +31,6 @@ class VolumePairList(StaticPairList):
if not self._validate_keys(self._sort_key): if not self._validate_keys(self._sort_key):
raise OperationalException( raise OperationalException(
f'key {self._sort_key} not in {SORT_VALUES}') f'key {self._sort_key} not in {SORT_VALUES}')
# self.refresh_whitelist()
def _validate_keys(self, key): def _validate_keys(self, key):
return key in SORT_VALUES return key in SORT_VALUES
@ -46,9 +42,10 @@ class VolumePairList(StaticPairList):
""" """
return f"{self.name} - top {self._whitelistconf['number_assets']} volume pairs." return f"{self.name} - top {self._whitelistconf['number_assets']} volume pairs."
def refresh_whitelist(self) -> None: def refresh_pairlist(self) -> None:
""" """
Refreshes whitelist and assigns it to self._whitelist Refreshes pairlists and assigns them to self._whitelist and self._blacklist respectively
-> Please overwrite in subclasses
""" """
# Generate dynamic whitelist # Generate dynamic whitelist
pairs = self._gen_pair_whitelist(self._config['stake_currency'], self._sort_key) pairs = self._gen_pair_whitelist(self._config['stake_currency'], self._sort_key)
@ -72,4 +69,3 @@ class VolumePairList(StaticPairList):
sorted_tickers = sorted(tickers, reverse=True, key=lambda t: t[key]) sorted_tickers = sorted(tickers, reverse=True, key=lambda t: t[key])
pairs = [s['symbol'] for s in sorted_tickers] pairs = [s['symbol'] for s in sorted_tickers]
return pairs return pairs

View File

@ -1,3 +1,4 @@
from freqtrade.resolvers.iresolver import IResolver # noqa: F401 from freqtrade.resolvers.iresolver import IResolver # noqa: F401
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver # noqa: F401 from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver # noqa: F401
from freqtrade.resolvers.pairlist_resolver import PairListResolver # noqa: F401
from freqtrade.resolvers.strategy_resolver import StrategyResolver # noqa: F401 from freqtrade.resolvers.strategy_resolver import StrategyResolver # noqa: F401

View File

@ -0,0 +1,59 @@
# pragma pylint: disable=attribute-defined-outside-init
"""
This module load custom hyperopts
"""
import logging
from pathlib import Path
from freqtrade.pairlist.IPairList import IPairList
from freqtrade.resolvers import IResolver
logger = logging.getLogger(__name__)
class PairListResolver(IResolver):
"""
This class contains all the logic to load custom hyperopt class
"""
__slots__ = ['pairlist']
def __init__(self, pairlist_name: str, freqtrade, config: dict) -> None:
"""
Load the custom class from config parameter
:param config: configuration dictionary or None
"""
self.pairlist = self._load_pairlist(pairlist_name, kwargs={'freqtrade': freqtrade,
'config': config})
def _load_pairlist(
self, pairlist_name: str, kwargs: dict) -> IPairList:
"""
Search and loads the specified pairlist.
:param pairlist_name: name of the module to import
:param extra_dir: additional directory to search for the given pairlist
:return: PairList instance or None
"""
current_path = Path(__file__).parent.parent.joinpath('pairlist').resolve()
abs_paths = [
current_path.parent.parent.joinpath('user_data/pairlist'),
current_path,
]
for _path in abs_paths:
try:
pairlist = self._search_object(directory=_path, object_type=IPairList,
object_name=pairlist_name,
kwargs=kwargs)
if pairlist:
logger.info('Using resolved pairlist %s from \'%s\'', pairlist_name, _path)
return pairlist
except FileNotFoundError:
logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd()))
raise ImportError(
"Impossible to load Pairlist '{}'. This class does not exist"
" or contains Python code errors".format(pairlist_name)
)