load pairlists via resolver
This commit is contained in:
parent
43031aa3bb
commit
3e2fa58029
@ -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
|
||||||
|
@ -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
|
||||||
|
91
freqtrade/pairlist/IPairList.py
Normal file
91
freqtrade/pairlist/IPairList.py
Normal 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]
|
@ -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]
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
59
freqtrade/resolvers/pairlist_resolver.py
Normal file
59
freqtrade/resolvers/pairlist_resolver.py
Normal 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)
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user