Merge pull request #4197 from nas-/develop
Added support for regex in whitelist
This commit is contained in:
commit
baef8b4f79
@ -83,7 +83,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
|||||||
| `exchange.key` | API key to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
| `exchange.key` | API key to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
||||||
| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
||||||
| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
||||||
| `exchange.pair_whitelist` | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Not used by VolumePairList (see [below](#pairlists-and-pairlist-handlers)). <br> **Datatype:** List
|
| `exchange.pair_whitelist` | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Supports regex pairs as `.*/BTC`. Not used by VolumePairList (see [below](#pairlists-and-pairlist-handlers)). <br> **Datatype:** List
|
||||||
| `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#pairlists-and-pairlist-handlers)). <br> **Datatype:** List
|
| `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#pairlists-and-pairlist-handlers)). <br> **Datatype:** List
|
||||||
| `exchange.ccxt_config` | Additional CCXT parameters passed to both ccxt instances (sync and async). This is usually the correct place for ccxt configurations. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) <br> **Datatype:** Dict
|
| `exchange.ccxt_config` | Additional CCXT parameters passed to both ccxt instances (sync and async). This is usually the correct place for ccxt configurations. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) <br> **Datatype:** Dict
|
||||||
| `exchange.ccxt_sync_config` | Additional CCXT parameters passed to the regular (sync) ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) <br> **Datatype:** Dict
|
| `exchange.ccxt_sync_config` | Additional CCXT parameters passed to the regular (sync) ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) <br> **Datatype:** Dict
|
||||||
|
@ -35,7 +35,7 @@ You may also use something like `.*DOWN/BTC` or `.*UP/BTC` to exclude leveraged
|
|||||||
|
|
||||||
#### Static Pair List
|
#### Static Pair List
|
||||||
|
|
||||||
By default, the `StaticPairList` method is used, which uses a statically defined pair whitelist from the configuration.
|
By default, the `StaticPairList` method is used, which uses a statically defined pair whitelist from the configuration. The pairlist also supports wildcards (in regex-style) - so `.*/BTC` will include all pairs with BTC as a stake.
|
||||||
|
|
||||||
It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklist`.
|
It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklist`.
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ from freqtrade.data.history import (convert_trades_to_ohlcv, refresh_backtest_oh
|
|||||||
refresh_backtest_trades_data)
|
refresh_backtest_trades_data)
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
|
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
||||||
from freqtrade.resolvers import ExchangeResolver
|
from freqtrade.resolvers import ExchangeResolver
|
||||||
from freqtrade.state import RunMode
|
from freqtrade.state import RunMode
|
||||||
|
|
||||||
@ -42,15 +43,17 @@ def start_download_data(args: Dict[str, Any]) -> None:
|
|||||||
"Downloading data requires a list of pairs. "
|
"Downloading data requires a list of pairs. "
|
||||||
"Please check the documentation on how to configure this.")
|
"Please check the documentation on how to configure this.")
|
||||||
|
|
||||||
logger.info(f"About to download pairs: {config['pairs']}, "
|
|
||||||
f"intervals: {config['timeframes']} to {config['datadir']}")
|
|
||||||
|
|
||||||
pairs_not_available: List[str] = []
|
pairs_not_available: List[str] = []
|
||||||
|
|
||||||
# Init exchange
|
# Init exchange
|
||||||
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False)
|
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False)
|
||||||
# Manual validations of relevant settings
|
# Manual validations of relevant settings
|
||||||
exchange.validate_pairs(config['pairs'])
|
exchange.validate_pairs(config['pairs'])
|
||||||
|
expanded_pairs = expand_pairlist(config['pairs'], list(exchange.markets))
|
||||||
|
|
||||||
|
logger.info(f"About to download pairs: {expanded_pairs}, "
|
||||||
|
f"intervals: {config['timeframes']} to {config['datadir']}")
|
||||||
|
|
||||||
for timeframe in config['timeframes']:
|
for timeframe in config['timeframes']:
|
||||||
exchange.validate_timeframes(timeframe)
|
exchange.validate_timeframes(timeframe)
|
||||||
|
|
||||||
@ -58,20 +61,20 @@ def start_download_data(args: Dict[str, Any]) -> None:
|
|||||||
|
|
||||||
if config.get('download_trades'):
|
if config.get('download_trades'):
|
||||||
pairs_not_available = refresh_backtest_trades_data(
|
pairs_not_available = refresh_backtest_trades_data(
|
||||||
exchange, pairs=config['pairs'], datadir=config['datadir'],
|
exchange, pairs=expanded_pairs, datadir=config['datadir'],
|
||||||
timerange=timerange, erase=bool(config.get('erase')),
|
timerange=timerange, erase=bool(config.get('erase')),
|
||||||
data_format=config['dataformat_trades'])
|
data_format=config['dataformat_trades'])
|
||||||
|
|
||||||
# Convert downloaded trade data to different timeframes
|
# Convert downloaded trade data to different timeframes
|
||||||
convert_trades_to_ohlcv(
|
convert_trades_to_ohlcv(
|
||||||
pairs=config['pairs'], timeframes=config['timeframes'],
|
pairs=expanded_pairs, timeframes=config['timeframes'],
|
||||||
datadir=config['datadir'], timerange=timerange, erase=bool(config.get('erase')),
|
datadir=config['datadir'], timerange=timerange, erase=bool(config.get('erase')),
|
||||||
data_format_ohlcv=config['dataformat_ohlcv'],
|
data_format_ohlcv=config['dataformat_ohlcv'],
|
||||||
data_format_trades=config['dataformat_trades'],
|
data_format_trades=config['dataformat_trades'],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
pairs_not_available = refresh_backtest_ohlcv_data(
|
pairs_not_available = refresh_backtest_ohlcv_data(
|
||||||
exchange, pairs=config['pairs'], timeframes=config['timeframes'],
|
exchange, pairs=expanded_pairs, timeframes=config['timeframes'],
|
||||||
datadir=config['datadir'], timerange=timerange, erase=bool(config.get('erase')),
|
datadir=config['datadir'], timerange=timerange, erase=bool(config.get('erase')),
|
||||||
data_format=config['dataformat_ohlcv'])
|
data_format=config['dataformat_ohlcv'])
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ from freqtrade.configuration import TimeRange
|
|||||||
from freqtrade.constants import DATETIME_PRINT_FORMAT, UNLIMITED_STAKE_AMOUNT
|
from freqtrade.constants import DATETIME_PRINT_FORMAT, UNLIMITED_STAKE_AMOUNT
|
||||||
from freqtrade.data.history import get_timerange, load_data, refresh_data
|
from freqtrade.data.history import get_timerange, load_data, refresh_data
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
|
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
||||||
from freqtrade.strategy.interface import SellType
|
from freqtrade.strategy.interface import SellType
|
||||||
|
|
||||||
|
|
||||||
@ -80,10 +81,12 @@ class Edge:
|
|||||||
if config.get('fee'):
|
if config.get('fee'):
|
||||||
self.fee = config['fee']
|
self.fee = config['fee']
|
||||||
else:
|
else:
|
||||||
self.fee = self.exchange.get_fee(symbol=self.config['exchange']['pair_whitelist'][0])
|
self.fee = self.exchange.get_fee(symbol=expand_pairlist(
|
||||||
|
self.config['exchange']['pair_whitelist'], list(self.exchange.markets))[0])
|
||||||
|
|
||||||
def calculate(self) -> bool:
|
def calculate(self) -> bool:
|
||||||
pairs = self.config['exchange']['pair_whitelist']
|
pairs = expand_pairlist(self.config['exchange']['pair_whitelist'],
|
||||||
|
list(self.exchange.markets))
|
||||||
heartbeat = self.edge_config.get('process_throttle_secs')
|
heartbeat = self.edge_config.get('process_throttle_secs')
|
||||||
|
|
||||||
if (self._last_updated > 0) and (
|
if (self._last_updated > 0) and (
|
||||||
|
@ -25,6 +25,7 @@ from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFun
|
|||||||
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, BAD_EXCHANGES, retrier,
|
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, BAD_EXCHANGES, retrier,
|
||||||
retrier_async)
|
retrier_async)
|
||||||
from freqtrade.misc import deep_merge_dicts, safe_value_fallback2
|
from freqtrade.misc import deep_merge_dicts, safe_value_fallback2
|
||||||
|
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
||||||
|
|
||||||
|
|
||||||
CcxtModuleType = Any
|
CcxtModuleType = Any
|
||||||
@ -335,8 +336,9 @@ class Exchange:
|
|||||||
if not self.markets:
|
if not self.markets:
|
||||||
logger.warning('Unable to validate pairs (assuming they are correct).')
|
logger.warning('Unable to validate pairs (assuming they are correct).')
|
||||||
return
|
return
|
||||||
|
extended_pairs = expand_pairlist(pairs, list(self.markets), keep_invalid=True)
|
||||||
invalid_pairs = []
|
invalid_pairs = []
|
||||||
for pair in pairs:
|
for pair in extended_pairs:
|
||||||
# Note: ccxt has BaseCurrency/QuoteCurrency format for pairs
|
# Note: ccxt has BaseCurrency/QuoteCurrency format for pairs
|
||||||
# TODO: add a support for having coins in BTC/USDT format
|
# TODO: add a support for having coins in BTC/USDT format
|
||||||
if self.markets and pair not in self.markets:
|
if self.markets and pair not in self.markets:
|
||||||
|
@ -13,6 +13,7 @@ from freqtrade.data.history import get_timerange, load_data
|
|||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange import timeframe_to_prev_date, timeframe_to_seconds
|
from freqtrade.exchange import timeframe_to_prev_date, timeframe_to_seconds
|
||||||
from freqtrade.misc import pair_to_filename
|
from freqtrade.misc import pair_to_filename
|
||||||
|
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
||||||
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
||||||
from freqtrade.strategy import IStrategy
|
from freqtrade.strategy import IStrategy
|
||||||
|
|
||||||
@ -29,16 +30,16 @@ except ImportError:
|
|||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
|
||||||
def init_plotscript(config, startup_candles: int = 0):
|
def init_plotscript(config, markets: List, startup_candles: int = 0):
|
||||||
"""
|
"""
|
||||||
Initialize objects needed for plotting
|
Initialize objects needed for plotting
|
||||||
:return: Dict with candle (OHLCV) data, trades and pairs
|
:return: Dict with candle (OHLCV) data, trades and pairs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if "pairs" in config:
|
if "pairs" in config:
|
||||||
pairs = config['pairs']
|
pairs = expand_pairlist(config['pairs'], markets)
|
||||||
else:
|
else:
|
||||||
pairs = config['exchange']['pair_whitelist']
|
pairs = expand_pairlist(config['exchange']['pair_whitelist'], markets)
|
||||||
|
|
||||||
# Set timerange to use
|
# Set timerange to use
|
||||||
timerange = TimeRange.parse_timerange(config.get('timerange'))
|
timerange = TimeRange.parse_timerange(config.get('timerange'))
|
||||||
@ -527,7 +528,7 @@ def load_and_plot_trades(config: Dict[str, Any]):
|
|||||||
|
|
||||||
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config)
|
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config)
|
||||||
IStrategy.dp = DataProvider(config, exchange)
|
IStrategy.dp = DataProvider(config, exchange)
|
||||||
plot_elements = init_plotscript(config, strategy.startup_candle_count)
|
plot_elements = init_plotscript(config, list(exchange.markets), strategy.startup_candle_count)
|
||||||
timerange = plot_elements['timerange']
|
timerange = plot_elements['timerange']
|
||||||
trades = plot_elements['trades']
|
trades = plot_elements['trades']
|
||||||
pair_counter = 0
|
pair_counter = 0
|
||||||
@ -562,7 +563,8 @@ def plot_profit(config: Dict[str, Any]) -> None:
|
|||||||
But should be somewhat proportional, and therefor useful
|
But should be somewhat proportional, and therefor useful
|
||||||
in helping out to find a good algorithm.
|
in helping out to find a good algorithm.
|
||||||
"""
|
"""
|
||||||
plot_elements = init_plotscript(config)
|
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config)
|
||||||
|
plot_elements = init_plotscript(config, list(exchange.markets))
|
||||||
trades = plot_elements['trades']
|
trades = plot_elements['trades']
|
||||||
# Filter trades to relevant pairs
|
# Filter trades to relevant pairs
|
||||||
# Remove open pairs - we don't know the profit yet so can't calculate profit for these.
|
# Remove open pairs - we don't know the profit yet so can't calculate profit for these.
|
||||||
|
@ -124,10 +124,21 @@ class IPairList(LoggingMixin, ABC):
|
|||||||
"""
|
"""
|
||||||
return self._pairlistmanager.verify_blacklist(pairlist, logmethod)
|
return self._pairlistmanager.verify_blacklist(pairlist, logmethod)
|
||||||
|
|
||||||
|
def verify_whitelist(self, pairlist: List[str], logmethod,
|
||||||
|
keep_invalid: bool = False) -> List[str]:
|
||||||
|
"""
|
||||||
|
Proxy method to verify_whitelist for easy access for child classes.
|
||||||
|
:param pairlist: Pairlist to validate
|
||||||
|
: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.
|
||||||
|
:return: pairlist - whitelisted pairs
|
||||||
|
"""
|
||||||
|
return self._pairlistmanager.verify_whitelist(pairlist, logmethod, keep_invalid)
|
||||||
|
|
||||||
def _whitelist_for_active_markets(self, pairlist: List[str]) -> List[str]:
|
def _whitelist_for_active_markets(self, pairlist: List[str]) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Check available markets and remove pair from whitelist if necessary
|
Check available markets and remove pair from whitelist if necessary
|
||||||
:param whitelist: the sorted list of pairs the user might want to trade
|
:param pairlist: the sorted list of pairs the user might want to trade
|
||||||
:return: the list of pairs the user wants to trade without those unavailable or
|
:return: the list of pairs the user wants to trade without those unavailable or
|
||||||
black_listed
|
black_listed
|
||||||
"""
|
"""
|
||||||
|
@ -50,9 +50,12 @@ class StaticPairList(IPairList):
|
|||||||
:return: List of pairs
|
:return: List of pairs
|
||||||
"""
|
"""
|
||||||
if self._allow_inactive:
|
if self._allow_inactive:
|
||||||
return self._config['exchange']['pair_whitelist']
|
return self.verify_whitelist(
|
||||||
|
self._config['exchange']['pair_whitelist'], logger.info, keep_invalid=True
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
return self._whitelist_for_active_markets(self._config['exchange']['pair_whitelist'])
|
return self._whitelist_for_active_markets(
|
||||||
|
self.verify_whitelist(self._config['exchange']['pair_whitelist'], logger.info))
|
||||||
|
|
||||||
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
|
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
|
||||||
"""
|
"""
|
||||||
|
@ -2,16 +2,35 @@ import re
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
def expand_pairlist(wildcardpl: List[str], available_pairs: List[str]) -> List[str]:
|
def expand_pairlist(wildcardpl: List[str], available_pairs: List[str],
|
||||||
|
keep_invalid: bool = False) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Expand pairlist potentially containing wildcards based on available markets.
|
Expand pairlist potentially containing wildcards based on available markets.
|
||||||
This will implicitly filter all pairs in the wildcard-list which are not in available_pairs.
|
This will implicitly filter all pairs in the wildcard-list which are not in available_pairs.
|
||||||
:param wildcardpl: List of Pairlists, which may contain regex
|
:param wildcardpl: List of Pairlists, which may contain regex
|
||||||
:param available_pairs: List of all available pairs (`exchange.get_markets().keys()`)
|
:param available_pairs: List of all available pairs (`exchange.get_markets().keys()`)
|
||||||
|
:param keep_invalid: If sets to True, drops invalid pairs silently while expanding regexes
|
||||||
:return expanded pairlist, with Regexes from wildcardpl applied to match all available pairs.
|
:return expanded pairlist, with Regexes from wildcardpl applied to match all available pairs.
|
||||||
:raises: ValueError if a wildcard is invalid (like '*/BTC' - which should be `.*/BTC`)
|
:raises: ValueError if a wildcard is invalid (like '*/BTC' - which should be `.*/BTC`)
|
||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
|
if keep_invalid:
|
||||||
|
for pair_wc in wildcardpl:
|
||||||
|
try:
|
||||||
|
comp = re.compile(pair_wc)
|
||||||
|
result_partial = [
|
||||||
|
pair for pair in available_pairs if re.match(comp, pair)
|
||||||
|
]
|
||||||
|
# Add all matching pairs.
|
||||||
|
# If there are no matching pairs (Pair not on exchange) keep it.
|
||||||
|
result += result_partial or [pair_wc]
|
||||||
|
except re.error as err:
|
||||||
|
raise ValueError(f"Wildcard error in {pair_wc}, {err}")
|
||||||
|
|
||||||
|
for element in result:
|
||||||
|
if not re.fullmatch(r'^[A-Za-z0-9/-]+$', element):
|
||||||
|
result.remove(element)
|
||||||
|
else:
|
||||||
for pair_wc in wildcardpl:
|
for pair_wc in wildcardpl:
|
||||||
try:
|
try:
|
||||||
comp = re.compile(pair_wc)
|
comp = re.compile(pair_wc)
|
||||||
|
@ -59,6 +59,17 @@ class PairListManager():
|
|||||||
"""The expanded blacklist (including wildcard expansion)"""
|
"""The expanded blacklist (including wildcard expansion)"""
|
||||||
return expand_pairlist(self._blacklist, self._exchange.get_markets().keys())
|
return expand_pairlist(self._blacklist, self._exchange.get_markets().keys())
|
||||||
|
|
||||||
|
@property
|
||||||
|
def expanded_whitelist_keep_invalid(self) -> List[str]:
|
||||||
|
"""The expanded whitelist (including wildcard expansion), maintaining invalid pairs"""
|
||||||
|
return expand_pairlist(self._whitelist, self._exchange.get_markets().keys(),
|
||||||
|
keep_invalid=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def expanded_whitelist(self) -> List[str]:
|
||||||
|
"""The expanded whitelist (including wildcard expansion), filtering invalid pairs"""
|
||||||
|
return expand_pairlist(self._whitelist, self._exchange.get_markets().keys())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name_list(self) -> List[str]:
|
def name_list(self) -> List[str]:
|
||||||
"""Get list of loaded Pairlist Handler names"""
|
"""Get list of loaded Pairlist Handler names"""
|
||||||
@ -129,6 +140,28 @@ class PairListManager():
|
|||||||
pairlist.remove(pair)
|
pairlist.remove(pair)
|
||||||
return pairlist
|
return pairlist
|
||||||
|
|
||||||
|
def verify_whitelist(self, pairlist: List[str], logmethod,
|
||||||
|
keep_invalid: bool = False) -> List[str]:
|
||||||
|
"""
|
||||||
|
Verify and remove items from pairlist - returning a filtered pairlist.
|
||||||
|
Logs a warning or info depending on `aswarning`.
|
||||||
|
Pairlist Handlers explicitly using this method shall use
|
||||||
|
`logmethod=logger.info` to avoid spamming with warning messages
|
||||||
|
:param pairlist: Pairlist to validate
|
||||||
|
: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.
|
||||||
|
:return: pairlist - whitelisted pairs
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if keep_invalid:
|
||||||
|
whitelist = self.expanded_whitelist_keep_invalid
|
||||||
|
else:
|
||||||
|
whitelist = self.expanded_whitelist
|
||||||
|
except ValueError as err:
|
||||||
|
logger.error(f"Pair whitelist contains an invalid Wildcard: {err}")
|
||||||
|
return []
|
||||||
|
return whitelist
|
||||||
|
|
||||||
def create_pair_list(self, pairs: List[str], timeframe: str = None) -> ListPairsWithTimeframes:
|
def create_pair_list(self, pairs: List[str], timeframe: str = None) -> ListPairsWithTimeframes:
|
||||||
"""
|
"""
|
||||||
Create list of pair tuples with (pair, timeframe)
|
Create list of pair tuples with (pair, timeframe)
|
||||||
|
@ -508,7 +508,7 @@ def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs d
|
|||||||
def test_validate_pairs_not_available(default_conf, mocker):
|
def test_validate_pairs_not_available(default_conf, mocker):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
type(api_mock).markets = PropertyMock(return_value={
|
type(api_mock).markets = PropertyMock(return_value={
|
||||||
'XRP/BTC': {'inactive': True}
|
'XRP/BTC': {'inactive': True, 'base': 'XRP', 'quote': 'BTC'}
|
||||||
})
|
})
|
||||||
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||||
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
||||||
|
@ -156,6 +156,31 @@ def test_refresh_static_pairlist(mocker, markets, static_pl_conf):
|
|||||||
assert static_pl_conf['exchange']['pair_blacklist'] == freqtrade.pairlists.blacklist
|
assert static_pl_conf['exchange']['pair_blacklist'] == freqtrade.pairlists.blacklist
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('pairs,expected', [
|
||||||
|
(['NOEXIST/BTC', r'\+WHAT/BTC'],
|
||||||
|
['ETH/BTC', 'TKN/BTC', 'TRST/BTC', 'NOEXIST/BTC', 'SWT/BTC', 'BCC/BTC', 'HOT/BTC']),
|
||||||
|
(['NOEXIST/BTC', r'*/BTC'], # This is an invalid regex
|
||||||
|
[]),
|
||||||
|
])
|
||||||
|
def test_refresh_static_pairlist_noexist(mocker, markets, static_pl_conf, pairs, expected, caplog):
|
||||||
|
|
||||||
|
static_pl_conf['pairlists'][0]['allow_inactive'] = True
|
||||||
|
static_pl_conf['exchange']['pair_whitelist'] += pairs
|
||||||
|
freqtrade = get_patched_freqtradebot(mocker, static_pl_conf)
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
exchange_has=MagicMock(return_value=True),
|
||||||
|
markets=PropertyMock(return_value=markets),
|
||||||
|
)
|
||||||
|
freqtrade.pairlists.refresh_pairlist()
|
||||||
|
|
||||||
|
# Ensure all except those in whitelist are removed
|
||||||
|
assert set(expected) == set(freqtrade.pairlists.whitelist)
|
||||||
|
assert static_pl_conf['exchange']['pair_blacklist'] == freqtrade.pairlists.blacklist
|
||||||
|
if not expected:
|
||||||
|
assert log_has_re(r'Pair whitelist contains an invalid Wildcard: Wildcard error.*', caplog)
|
||||||
|
|
||||||
|
|
||||||
def test_invalid_blacklist(mocker, markets, static_pl_conf, caplog):
|
def test_invalid_blacklist(mocker, markets, static_pl_conf, caplog):
|
||||||
static_pl_conf['exchange']['pair_blacklist'] = ['*/BTC']
|
static_pl_conf['exchange']['pair_blacklist'] = ['*/BTC']
|
||||||
freqtrade = get_patched_freqtradebot(mocker, static_pl_conf)
|
freqtrade = get_patched_freqtradebot(mocker, static_pl_conf)
|
||||||
@ -165,7 +190,6 @@ def test_invalid_blacklist(mocker, markets, static_pl_conf, caplog):
|
|||||||
markets=PropertyMock(return_value=markets),
|
markets=PropertyMock(return_value=markets),
|
||||||
)
|
)
|
||||||
freqtrade.pairlists.refresh_pairlist()
|
freqtrade.pairlists.refresh_pairlist()
|
||||||
# List ordered by BaseVolume
|
|
||||||
whitelist = []
|
whitelist = []
|
||||||
# Ensure all except those in whitelist are removed
|
# Ensure all except those in whitelist are removed
|
||||||
assert set(whitelist) == set(freqtrade.pairlists.whitelist)
|
assert set(whitelist) == set(freqtrade.pairlists.whitelist)
|
||||||
@ -853,3 +877,35 @@ def test_expand_pairlist(wildcardlist, pairs, expected):
|
|||||||
expand_pairlist(wildcardlist, pairs)
|
expand_pairlist(wildcardlist, pairs)
|
||||||
else:
|
else:
|
||||||
assert sorted(expand_pairlist(wildcardlist, pairs)) == sorted(expected)
|
assert sorted(expand_pairlist(wildcardlist, pairs)) == sorted(expected)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('wildcardlist,pairs,expected', [
|
||||||
|
(['BTC/USDT'],
|
||||||
|
['BTC/USDT'],
|
||||||
|
['BTC/USDT']),
|
||||||
|
(['BTC/USDT', 'ETH/USDT'],
|
||||||
|
['BTC/USDT', 'ETH/USDT'],
|
||||||
|
['BTC/USDT', 'ETH/USDT']),
|
||||||
|
(['BTC/USDT', 'ETH/USDT'],
|
||||||
|
['BTC/USDT'], ['BTC/USDT', 'ETH/USDT']), # Test one too many
|
||||||
|
(['.*/USDT'],
|
||||||
|
['BTC/USDT', 'ETH/USDT'], ['BTC/USDT', 'ETH/USDT']), # Wildcard simple
|
||||||
|
(['.*C/USDT'],
|
||||||
|
['BTC/USDT', 'ETC/USDT', 'ETH/USDT'], ['BTC/USDT', 'ETC/USDT']), # Wildcard exclude one
|
||||||
|
(['.*UP/USDT', 'BTC/USDT', 'ETH/USDT'],
|
||||||
|
['BTC/USDT', 'ETC/USDT', 'ETH/USDT', 'BTCUP/USDT', 'XRPUP/USDT', 'XRPDOWN/USDT'],
|
||||||
|
['BTC/USDT', 'ETH/USDT', 'BTCUP/USDT', 'XRPUP/USDT']), # Wildcard exclude one
|
||||||
|
(['BTC/.*', 'ETH/.*'],
|
||||||
|
['BTC/USDT', 'ETC/USDT', 'ETH/USDT', 'BTC/USD', 'ETH/EUR', 'BTC/GBP'],
|
||||||
|
['BTC/USDT', 'ETH/USDT', 'BTC/USD', 'ETH/EUR', 'BTC/GBP']), # Wildcard exclude one
|
||||||
|
(['*UP/USDT', 'BTC/USDT', 'ETH/USDT'],
|
||||||
|
['BTC/USDT', 'ETC/USDT', 'ETH/USDT', 'BTCUP/USDT', 'XRPUP/USDT', 'XRPDOWN/USDT'],
|
||||||
|
None),
|
||||||
|
(['HELLO/WORLD'], [], ['HELLO/WORLD']) # Invalid pair kept
|
||||||
|
])
|
||||||
|
def test_expand_pairlist_keep_invalid(wildcardlist, pairs, expected):
|
||||||
|
if expected is None:
|
||||||
|
with pytest.raises(ValueError, match=r'Wildcard error in \*UP/USDT,'):
|
||||||
|
expand_pairlist(wildcardlist, pairs, keep_invalid=True)
|
||||||
|
else:
|
||||||
|
assert sorted(expand_pairlist(wildcardlist, pairs, keep_invalid=True)) == sorted(expected)
|
||||||
|
@ -47,14 +47,15 @@ def test_init_plotscript(default_conf, mocker, testdatadir):
|
|||||||
default_conf['timeframe'] = "5m"
|
default_conf['timeframe'] = "5m"
|
||||||
default_conf["datadir"] = testdatadir
|
default_conf["datadir"] = testdatadir
|
||||||
default_conf['exportfilename'] = testdatadir / "backtest-result_test.json"
|
default_conf['exportfilename'] = testdatadir / "backtest-result_test.json"
|
||||||
ret = init_plotscript(default_conf)
|
supported_markets = ["TRX/BTC", "ADA/BTC"]
|
||||||
|
ret = init_plotscript(default_conf, supported_markets)
|
||||||
assert "ohlcv" in ret
|
assert "ohlcv" in ret
|
||||||
assert "trades" in ret
|
assert "trades" in ret
|
||||||
assert "pairs" in ret
|
assert "pairs" in ret
|
||||||
assert 'timerange' in ret
|
assert 'timerange' in ret
|
||||||
|
|
||||||
default_conf['pairs'] = ["TRX/BTC", "ADA/BTC"]
|
default_conf['pairs'] = ["TRX/BTC", "ADA/BTC"]
|
||||||
ret = init_plotscript(default_conf, 20)
|
ret = init_plotscript(default_conf, supported_markets, 20)
|
||||||
assert "ohlcv" in ret
|
assert "ohlcv" in ret
|
||||||
assert "TRX/BTC" in ret["ohlcv"]
|
assert "TRX/BTC" in ret["ohlcv"]
|
||||||
assert "ADA/BTC" in ret["ohlcv"]
|
assert "ADA/BTC" in ret["ohlcv"]
|
||||||
|
Loading…
Reference in New Issue
Block a user