Merge pull request #2973 from freqtrade/support_non_pairs
Support non pairs
This commit is contained in:
commit
e6d003f8f2
@ -150,15 +150,3 @@ def _validate_whitelist(conf: Dict[str, Any]) -> None:
|
|||||||
if (pl.get('method') == 'StaticPairList'
|
if (pl.get('method') == 'StaticPairList'
|
||||||
and not conf.get('exchange', {}).get('pair_whitelist')):
|
and not conf.get('exchange', {}).get('pair_whitelist')):
|
||||||
raise OperationalException("StaticPairList requires pair_whitelist to be set.")
|
raise OperationalException("StaticPairList requires pair_whitelist to be set.")
|
||||||
|
|
||||||
if pl.get('method') == 'StaticPairList':
|
|
||||||
stake = conf['stake_currency']
|
|
||||||
invalid_pairs = []
|
|
||||||
for pair in conf['exchange'].get('pair_whitelist'):
|
|
||||||
if not pair.endswith(f'/{stake}'):
|
|
||||||
invalid_pairs.append(pair)
|
|
||||||
|
|
||||||
if invalid_pairs:
|
|
||||||
raise OperationalException(
|
|
||||||
f"Stake-currency '{stake}' not compatible with pair-whitelist. "
|
|
||||||
f"Please remove the following pairs: {invalid_pairs}")
|
|
||||||
|
@ -251,7 +251,6 @@ CONF_SCHEMA = {
|
|||||||
'type': 'array',
|
'type': 'array',
|
||||||
'items': {
|
'items': {
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'pattern': '^[0-9A-Z]+/[0-9A-Z]+$'
|
|
||||||
},
|
},
|
||||||
'uniqueItems': True
|
'uniqueItems': True
|
||||||
},
|
},
|
||||||
@ -259,7 +258,6 @@ CONF_SCHEMA = {
|
|||||||
'type': 'array',
|
'type': 'array',
|
||||||
'items': {
|
'items': {
|
||||||
'type': 'string',
|
'type': 'string',
|
||||||
'pattern': '^[0-9A-Z]+/[0-9A-Z]+$'
|
|
||||||
},
|
},
|
||||||
'uniqueItems': True
|
'uniqueItems': True
|
||||||
},
|
},
|
||||||
|
@ -226,6 +226,18 @@ class Exchange:
|
|||||||
markets = self.markets
|
markets = self.markets
|
||||||
return sorted(set([x['quote'] for _, x in markets.items()]))
|
return sorted(set([x['quote'] for _, x in markets.items()]))
|
||||||
|
|
||||||
|
def get_pair_quote_currency(self, pair: str) -> str:
|
||||||
|
"""
|
||||||
|
Return a pair's quote currency
|
||||||
|
"""
|
||||||
|
return self.markets.get(pair, {}).get('quote', '')
|
||||||
|
|
||||||
|
def get_pair_base_currency(self, pair: str) -> str:
|
||||||
|
"""
|
||||||
|
Return a pair's quote currency
|
||||||
|
"""
|
||||||
|
return self.markets.get(pair, {}).get('base', '')
|
||||||
|
|
||||||
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]
|
||||||
@ -298,7 +310,7 @@ 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
|
||||||
|
invalid_pairs = []
|
||||||
for pair in pairs:
|
for pair in 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
|
||||||
@ -320,6 +332,12 @@ class Exchange:
|
|||||||
logger.warning(f"Pair {pair} is restricted for some users on this exchange."
|
logger.warning(f"Pair {pair} is restricted for some users on this exchange."
|
||||||
f"Please check if you are impacted by this restriction "
|
f"Please check if you are impacted by this restriction "
|
||||||
f"on the exchange and eventually remove {pair} from your whitelist.")
|
f"on the exchange and eventually remove {pair} from your whitelist.")
|
||||||
|
if not self.get_pair_quote_currency(pair) == self._config['stake_currency']:
|
||||||
|
invalid_pairs.append(pair)
|
||||||
|
if invalid_pairs:
|
||||||
|
raise OperationalException(
|
||||||
|
f"Stake-currency '{self._config['stake_currency']}' not compatible with "
|
||||||
|
f"pair-whitelist. Please remove the following pairs: {invalid_pairs}")
|
||||||
|
|
||||||
def get_valid_pair_combination(self, curr_1: str, curr_2: str) -> str:
|
def get_valid_pair_combination(self, curr_1: str, curr_2: str) -> str:
|
||||||
"""
|
"""
|
||||||
|
@ -960,8 +960,8 @@ class FreqtradeBot:
|
|||||||
"""
|
"""
|
||||||
# Update wallets to ensure amounts tied up in a stoploss is now free!
|
# Update wallets to ensure amounts tied up in a stoploss is now free!
|
||||||
self.wallets.update()
|
self.wallets.update()
|
||||||
|
trade_base_currency = self.exchange.get_pair_base_currency(pair)
|
||||||
wallet_amount = self.wallets.get_free(pair.split('/')[0])
|
wallet_amount = self.wallets.get_free(trade_base_currency)
|
||||||
logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}")
|
logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}")
|
||||||
if wallet_amount >= amount:
|
if wallet_amount >= amount:
|
||||||
return amount
|
return amount
|
||||||
@ -1147,12 +1147,13 @@ class FreqtradeBot:
|
|||||||
if trade.fee_open == 0 or order['status'] == 'open':
|
if trade.fee_open == 0 or order['status'] == 'open':
|
||||||
return order_amount
|
return order_amount
|
||||||
|
|
||||||
|
trade_base_currency = self.exchange.get_pair_base_currency(trade.pair)
|
||||||
# use fee from order-dict if possible
|
# use fee from order-dict if possible
|
||||||
if ('fee' in order and order['fee'] is not None and
|
if ('fee' in order and order['fee'] is not None and
|
||||||
(order['fee'].keys() >= {'currency', 'cost'})):
|
(order['fee'].keys() >= {'currency', 'cost'})):
|
||||||
if (order['fee']['currency'] is not None and
|
if (order['fee']['currency'] is not None and
|
||||||
order['fee']['cost'] is not None and
|
order['fee']['cost'] is not None and
|
||||||
trade.pair.startswith(order['fee']['currency'])):
|
trade_base_currency == order['fee']['currency']):
|
||||||
new_amount = order_amount - order['fee']['cost']
|
new_amount = order_amount - order['fee']['cost']
|
||||||
logger.info("Applying fee on amount for %s (from %s to %s) from Order",
|
logger.info("Applying fee on amount for %s (from %s to %s) from Order",
|
||||||
trade, order['amount'], new_amount)
|
trade, order['amount'], new_amount)
|
||||||
@ -1174,7 +1175,7 @@ class FreqtradeBot:
|
|||||||
# only applies if fee is in quote currency!
|
# only applies if fee is in quote currency!
|
||||||
if (exectrade['fee']['currency'] is not None and
|
if (exectrade['fee']['currency'] is not None and
|
||||||
exectrade['fee']['cost'] is not None and
|
exectrade['fee']['cost'] is not None and
|
||||||
trade.pair.startswith(exectrade['fee']['currency'])):
|
trade_base_currency == exectrade['fee']['currency']):
|
||||||
fee_abs += exectrade['fee']['cost']
|
fee_abs += exectrade['fee']['cost']
|
||||||
|
|
||||||
if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC):
|
if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC):
|
||||||
|
@ -99,7 +99,8 @@ class IPairList(ABC):
|
|||||||
logger.warning(f"Pair {pair} is not compatible with exchange "
|
logger.warning(f"Pair {pair} is not compatible with exchange "
|
||||||
f"{self._exchange.name}. Removing it from whitelist..")
|
f"{self._exchange.name}. Removing it from whitelist..")
|
||||||
continue
|
continue
|
||||||
if not pair.endswith(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..")
|
||||||
continue
|
continue
|
||||||
|
@ -91,9 +91,9 @@ class VolumePairList(IPairList):
|
|||||||
|
|
||||||
if self._pairlist_pos == 0:
|
if self._pairlist_pos == 0:
|
||||||
# If VolumePairList is the first in the list, use fresh pairlist
|
# If VolumePairList is the first in the list, use fresh pairlist
|
||||||
# check length so that we make sure that '/' is actually in the string
|
# Check if pair quote currency equals to the stake currency.
|
||||||
filtered_tickers = [v for k, v in tickers.items()
|
filtered_tickers = [v for k, v in tickers.items()
|
||||||
if (len(k.split('/')) == 2 and k.split('/')[1] == base_currency
|
if (self._exchange.get_pair_quote_currency(k) == base_currency
|
||||||
and v[key] is not None)]
|
and v[key] is not None)]
|
||||||
else:
|
else:
|
||||||
# If other pairlist is in front, use the incomming pairlist.
|
# If other pairlist is in front, use the incomming pairlist.
|
||||||
|
@ -460,9 +460,9 @@ class RPC:
|
|||||||
if self._freqtrade.state != State.RUNNING:
|
if self._freqtrade.state != State.RUNNING:
|
||||||
raise RPCException('trader is not running')
|
raise RPCException('trader is not running')
|
||||||
|
|
||||||
# Check pair is in stake currency
|
# Check if pair quote currency equals to the stake currency.
|
||||||
stake_currency = self._freqtrade.config.get('stake_currency')
|
stake_currency = self._freqtrade.config.get('stake_currency')
|
||||||
if not pair.endswith(stake_currency):
|
if not self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency:
|
||||||
raise RPCException(
|
raise RPCException(
|
||||||
f'Wrong pair selected. Please pairs with stake {stake_currency} pairs only')
|
f'Wrong pair selected. Please pairs with stake {stake_currency} pairs only')
|
||||||
# check if valid pair
|
# check if valid pair
|
||||||
@ -517,7 +517,7 @@ class RPC:
|
|||||||
if add:
|
if add:
|
||||||
stake_currency = self._freqtrade.config.get('stake_currency')
|
stake_currency = self._freqtrade.config.get('stake_currency')
|
||||||
for pair in add:
|
for pair in add:
|
||||||
if (pair.endswith(stake_currency)
|
if (self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency
|
||||||
and pair not in self._freqtrade.pairlists.blacklist):
|
and pair not in self._freqtrade.pairlists.blacklist):
|
||||||
self._freqtrade.pairlists.blacklist.append(pair)
|
self._freqtrade.pairlists.blacklist.append(pair)
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ class Wallets:
|
|||||||
)
|
)
|
||||||
|
|
||||||
for trade in open_trades:
|
for trade in open_trades:
|
||||||
curr = trade.pair.split('/')[0]
|
curr = self._exchange.get_pair_base_currency(trade.pair)
|
||||||
_wallets[curr] = Wallet(
|
_wallets[curr] = Wallet(
|
||||||
curr,
|
curr,
|
||||||
trade.amount,
|
trade.amount,
|
||||||
|
@ -217,8 +217,9 @@ def test_list_markets(mocker, markets, capsys):
|
|||||||
]
|
]
|
||||||
start_list_markets(get_args(args), False)
|
start_list_markets(get_args(args), False)
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert ("Exchange Bittrex has 9 active markets: "
|
assert ("Exchange Bittrex has 10 active markets: "
|
||||||
"BLK/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/USD, NEO/BTC, TKN/BTC, XLTCUSDT, XRP/BTC.\n"
|
"BLK/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, NEO/BTC, "
|
||||||
|
"TKN/BTC, XLTCUSDT, XRP/BTC.\n"
|
||||||
in captured.out)
|
in captured.out)
|
||||||
|
|
||||||
patch_exchange(mocker, api_mock=api_mock, id="binance")
|
patch_exchange(mocker, api_mock=api_mock, id="binance")
|
||||||
@ -231,7 +232,7 @@ def test_list_markets(mocker, markets, capsys):
|
|||||||
pargs['config'] = None
|
pargs['config'] = None
|
||||||
start_list_markets(pargs, False)
|
start_list_markets(pargs, False)
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert re.match("\nExchange Binance has 9 active markets:\n",
|
assert re.match("\nExchange Binance has 10 active markets:\n",
|
||||||
captured.out)
|
captured.out)
|
||||||
|
|
||||||
patch_exchange(mocker, api_mock=api_mock, id="bittrex")
|
patch_exchange(mocker, api_mock=api_mock, id="bittrex")
|
||||||
@ -243,8 +244,8 @@ def test_list_markets(mocker, markets, capsys):
|
|||||||
]
|
]
|
||||||
start_list_markets(get_args(args), False)
|
start_list_markets(get_args(args), False)
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert ("Exchange Bittrex has 11 markets: "
|
assert ("Exchange Bittrex has 12 markets: "
|
||||||
"BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/USD, LTC/USDT, NEO/BTC, "
|
"BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, LTC/USDT, NEO/BTC, "
|
||||||
"TKN/BTC, XLTCUSDT, XRP/BTC.\n"
|
"TKN/BTC, XLTCUSDT, XRP/BTC.\n"
|
||||||
in captured.out)
|
in captured.out)
|
||||||
|
|
||||||
@ -256,8 +257,8 @@ def test_list_markets(mocker, markets, capsys):
|
|||||||
]
|
]
|
||||||
start_list_markets(get_args(args), True)
|
start_list_markets(get_args(args), True)
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert ("Exchange Bittrex has 8 active pairs: "
|
assert ("Exchange Bittrex has 9 active pairs: "
|
||||||
"BLK/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/USD, NEO/BTC, TKN/BTC, XRP/BTC.\n"
|
"BLK/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, NEO/BTC, TKN/BTC, XRP/BTC.\n"
|
||||||
in captured.out)
|
in captured.out)
|
||||||
|
|
||||||
# Test list-pairs subcommand with --all: all pairs
|
# Test list-pairs subcommand with --all: all pairs
|
||||||
@ -268,8 +269,8 @@ def test_list_markets(mocker, markets, capsys):
|
|||||||
]
|
]
|
||||||
start_list_markets(get_args(args), True)
|
start_list_markets(get_args(args), True)
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert ("Exchange Bittrex has 10 pairs: "
|
assert ("Exchange Bittrex has 11 pairs: "
|
||||||
"BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/USD, LTC/USDT, NEO/BTC, "
|
"BLK/BTC, BTT/BTC, ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, LTC/USDT, NEO/BTC, "
|
||||||
"TKN/BTC, XRP/BTC.\n"
|
"TKN/BTC, XRP/BTC.\n"
|
||||||
in captured.out)
|
in captured.out)
|
||||||
|
|
||||||
@ -282,8 +283,8 @@ def test_list_markets(mocker, markets, capsys):
|
|||||||
]
|
]
|
||||||
start_list_markets(get_args(args), False)
|
start_list_markets(get_args(args), False)
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert ("Exchange Bittrex has 5 active markets with ETH, LTC as base currencies: "
|
assert ("Exchange Bittrex has 6 active markets with ETH, LTC as base currencies: "
|
||||||
"ETH/BTC, ETH/USDT, LTC/BTC, LTC/USD, XLTCUSDT.\n"
|
"ETH/BTC, ETH/USDT, LTC/BTC, LTC/ETH, LTC/USD, XLTCUSDT.\n"
|
||||||
in captured.out)
|
in captured.out)
|
||||||
|
|
||||||
# active markets, base=LTC
|
# active markets, base=LTC
|
||||||
@ -295,8 +296,8 @@ def test_list_markets(mocker, markets, capsys):
|
|||||||
]
|
]
|
||||||
start_list_markets(get_args(args), False)
|
start_list_markets(get_args(args), False)
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert ("Exchange Bittrex has 3 active markets with LTC as base currency: "
|
assert ("Exchange Bittrex has 4 active markets with LTC as base currency: "
|
||||||
"LTC/BTC, LTC/USD, XLTCUSDT.\n"
|
"LTC/BTC, LTC/ETH, LTC/USD, XLTCUSDT.\n"
|
||||||
in captured.out)
|
in captured.out)
|
||||||
|
|
||||||
# active markets, quote=USDT, USD
|
# active markets, quote=USDT, USD
|
||||||
@ -384,7 +385,7 @@ def test_list_markets(mocker, markets, capsys):
|
|||||||
]
|
]
|
||||||
start_list_markets(get_args(args), False)
|
start_list_markets(get_args(args), False)
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert ("Exchange Bittrex has 9 active markets:\n"
|
assert ("Exchange Bittrex has 10 active markets:\n"
|
||||||
in captured.out)
|
in captured.out)
|
||||||
|
|
||||||
# Test tabular output, no markets found
|
# Test tabular output, no markets found
|
||||||
@ -407,7 +408,7 @@ def test_list_markets(mocker, markets, capsys):
|
|||||||
]
|
]
|
||||||
start_list_markets(get_args(args), False)
|
start_list_markets(get_args(args), False)
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert ('["BLK/BTC","ETH/BTC","ETH/USDT","LTC/BTC","LTC/USD","NEO/BTC",'
|
assert ('["BLK/BTC","ETH/BTC","ETH/USDT","LTC/BTC","LTC/ETH","LTC/USD","NEO/BTC",'
|
||||||
'"TKN/BTC","XLTCUSDT","XRP/BTC"]'
|
'"TKN/BTC","XLTCUSDT","XRP/BTC"]'
|
||||||
in captured.out)
|
in captured.out)
|
||||||
|
|
||||||
|
@ -575,7 +575,34 @@ def get_markets():
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
'info': {},
|
'info': {},
|
||||||
}
|
},
|
||||||
|
'LTC/ETH': {
|
||||||
|
'id': 'LTCETH',
|
||||||
|
'symbol': 'LTC/ETH',
|
||||||
|
'base': 'LTC',
|
||||||
|
'quote': 'ETH',
|
||||||
|
'active': True,
|
||||||
|
'precision': {
|
||||||
|
'base': 8,
|
||||||
|
'quote': 8,
|
||||||
|
'amount': 3,
|
||||||
|
'price': 5
|
||||||
|
},
|
||||||
|
'limits': {
|
||||||
|
'amount': {
|
||||||
|
'min': 0.001,
|
||||||
|
'max': 10000000.0
|
||||||
|
},
|
||||||
|
'price': {
|
||||||
|
'min': 1e-05,
|
||||||
|
'max': 1000.0
|
||||||
|
},
|
||||||
|
'cost': {
|
||||||
|
'min': 0.01,
|
||||||
|
'max': None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -400,13 +400,40 @@ def test_validate_stake_currency_error(default_conf, mocker, caplog):
|
|||||||
def test_get_quote_currencies(default_conf, mocker):
|
def test_get_quote_currencies(default_conf, mocker):
|
||||||
ex = get_patched_exchange(mocker, default_conf)
|
ex = get_patched_exchange(mocker, default_conf)
|
||||||
|
|
||||||
assert set(ex.get_quote_currencies()) == set(['USD', 'BTC', 'USDT'])
|
assert set(ex.get_quote_currencies()) == set(['USD', 'ETH', 'BTC', 'USDT'])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('pair,expected', [
|
||||||
|
('XRP/BTC', 'BTC'),
|
||||||
|
('LTC/USD', 'USD'),
|
||||||
|
('ETH/USDT', 'USDT'),
|
||||||
|
('XLTCUSDT', 'USDT'),
|
||||||
|
('XRP/NOCURRENCY', ''),
|
||||||
|
])
|
||||||
|
def test_get_pair_quote_currency(default_conf, mocker, pair, expected):
|
||||||
|
ex = get_patched_exchange(mocker, default_conf)
|
||||||
|
assert ex.get_pair_quote_currency(pair) == expected
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('pair,expected', [
|
||||||
|
('XRP/BTC', 'XRP'),
|
||||||
|
('LTC/USD', 'LTC'),
|
||||||
|
('ETH/USDT', 'ETH'),
|
||||||
|
('XLTCUSDT', 'LTC'),
|
||||||
|
('XRP/NOCURRENCY', ''),
|
||||||
|
])
|
||||||
|
def test_get_pair_base_currency(default_conf, mocker, pair, expected):
|
||||||
|
ex = get_patched_exchange(mocker, default_conf)
|
||||||
|
assert ex.get_pair_base_currency(pair) == expected
|
||||||
|
|
||||||
|
|
||||||
def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly
|
def test_validate_pairs(default_conf, mocker): # test exchange.validate_pairs directly
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
type(api_mock).markets = PropertyMock(return_value={
|
type(api_mock).markets = PropertyMock(return_value={
|
||||||
'ETH/BTC': {}, 'LTC/BTC': {}, 'XRP/BTC': {}, 'NEO/BTC': {}
|
'ETH/BTC': {'quote': 'BTC'},
|
||||||
|
'LTC/BTC': {'quote': 'BTC'},
|
||||||
|
'XRP/BTC': {'quote': 'BTC'},
|
||||||
|
'NEO/BTC': {'quote': 'BTC'},
|
||||||
})
|
})
|
||||||
id_mock = PropertyMock(return_value='test_exchange')
|
id_mock = PropertyMock(return_value='test_exchange')
|
||||||
type(api_mock).id = id_mock
|
type(api_mock).id = id_mock
|
||||||
@ -454,9 +481,9 @@ def test_validate_pairs_exception(default_conf, mocker, caplog):
|
|||||||
def test_validate_pairs_restricted(default_conf, mocker, caplog):
|
def test_validate_pairs_restricted(default_conf, mocker, caplog):
|
||||||
api_mock = MagicMock()
|
api_mock = MagicMock()
|
||||||
type(api_mock).markets = PropertyMock(return_value={
|
type(api_mock).markets = PropertyMock(return_value={
|
||||||
'ETH/BTC': {}, 'LTC/BTC': {},
|
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
|
||||||
'XRP/BTC': {'info': {'IsRestricted': True}},
|
'XRP/BTC': {'quote': 'BTC', 'info': {'IsRestricted': True}},
|
||||||
'NEO/BTC': {'info': 'TestString'}, # info can also be a string ...
|
'NEO/BTC': {'quote': 'BTC', 'info': 'TestString'}, # info can also be a string ...
|
||||||
})
|
})
|
||||||
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')
|
||||||
@ -469,6 +496,38 @@ def test_validate_pairs_restricted(default_conf, mocker, caplog):
|
|||||||
f"on the exchange and eventually remove XRP/BTC from your whitelist.", caplog)
|
f"on the exchange and eventually remove XRP/BTC from your whitelist.", caplog)
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_pairs_stakecompatibility(default_conf, mocker, caplog):
|
||||||
|
api_mock = MagicMock()
|
||||||
|
type(api_mock).markets = PropertyMock(return_value={
|
||||||
|
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
|
||||||
|
'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'},
|
||||||
|
'HELLO-WORLD': {'quote': 'BTC'},
|
||||||
|
})
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||||
|
|
||||||
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_pairs_stakecompatibility_fail(default_conf, mocker, caplog):
|
||||||
|
default_conf['exchange']['pair_whitelist'].append('HELLO-WORLD')
|
||||||
|
api_mock = MagicMock()
|
||||||
|
type(api_mock).markets = PropertyMock(return_value={
|
||||||
|
'ETH/BTC': {'quote': 'BTC'}, 'LTC/BTC': {'quote': 'BTC'},
|
||||||
|
'XRP/BTC': {'quote': 'BTC'}, 'NEO/BTC': {'quote': 'BTC'},
|
||||||
|
'HELLO-WORLD': {'quote': 'USDT'},
|
||||||
|
})
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange._load_async_markets')
|
||||||
|
mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency')
|
||||||
|
|
||||||
|
with pytest.raises(OperationalException, match=r"Stake-currency 'BTC' not compatible with.*"):
|
||||||
|
Exchange(default_conf)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("timeframe", [
|
@pytest.mark.parametrize("timeframe", [
|
||||||
('5m'), ("1m"), ("15m"), ("1h")
|
('5m'), ("1m"), ("15m"), ("1h")
|
||||||
])
|
])
|
||||||
@ -1819,6 +1878,7 @@ def test_get_valid_pair_combination(default_conf, mocker, markets):
|
|||||||
# 'ETH/BTC': 'active': True
|
# 'ETH/BTC': 'active': True
|
||||||
# 'ETH/USDT': 'active': True
|
# 'ETH/USDT': 'active': True
|
||||||
# 'LTC/BTC': 'active': False
|
# 'LTC/BTC': 'active': False
|
||||||
|
# 'LTC/ETH': 'active': True
|
||||||
# 'LTC/USD': 'active': True
|
# 'LTC/USD': 'active': True
|
||||||
# 'LTC/USDT': 'active': True
|
# 'LTC/USDT': 'active': True
|
||||||
# 'NEO/BTC': 'active': False
|
# 'NEO/BTC': 'active': False
|
||||||
@ -1827,26 +1887,26 @@ def test_get_valid_pair_combination(default_conf, mocker, markets):
|
|||||||
# 'XRP/BTC': 'active': False
|
# 'XRP/BTC': 'active': False
|
||||||
# all markets
|
# all markets
|
||||||
([], [], False, False,
|
([], [], False, False,
|
||||||
['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/USD',
|
['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD',
|
||||||
'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC']),
|
'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC']),
|
||||||
# active markets
|
# active markets
|
||||||
([], [], False, True,
|
([], [], False, True,
|
||||||
['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/USD', 'NEO/BTC',
|
['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'NEO/BTC',
|
||||||
'TKN/BTC', 'XLTCUSDT', 'XRP/BTC']),
|
'TKN/BTC', 'XLTCUSDT', 'XRP/BTC']),
|
||||||
# all pairs
|
# all pairs
|
||||||
([], [], True, False,
|
([], [], True, False,
|
||||||
['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/USD',
|
['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD',
|
||||||
'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC']),
|
'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC']),
|
||||||
# active pairs
|
# active pairs
|
||||||
([], [], True, True,
|
([], [], True, True,
|
||||||
['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/USD', 'NEO/BTC',
|
['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'NEO/BTC',
|
||||||
'TKN/BTC', 'XRP/BTC']),
|
'TKN/BTC', 'XRP/BTC']),
|
||||||
# all markets, base=ETH, LTC
|
# all markets, base=ETH, LTC
|
||||||
(['ETH', 'LTC'], [], False, False,
|
(['ETH', 'LTC'], [], False, False,
|
||||||
['ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']),
|
['ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']),
|
||||||
# all markets, base=LTC
|
# all markets, base=LTC
|
||||||
(['LTC'], [], False, False,
|
(['LTC'], [], False, False,
|
||||||
['LTC/BTC', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']),
|
['LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']),
|
||||||
# all markets, quote=USDT
|
# all markets, quote=USDT
|
||||||
([], ['USDT'], False, False,
|
([], ['USDT'], False, False,
|
||||||
['ETH/USDT', 'LTC/USDT', 'XLTCUSDT']),
|
['ETH/USDT', 'LTC/USDT', 'XLTCUSDT']),
|
||||||
|
@ -681,7 +681,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order) -> None
|
|||||||
|
|
||||||
# Test buy pair not with stakes
|
# Test buy pair not with stakes
|
||||||
with pytest.raises(RPCException, match=r'Wrong pair selected. Please pairs with stake.*'):
|
with pytest.raises(RPCException, match=r'Wrong pair selected. Please pairs with stake.*'):
|
||||||
rpc._rpc_forcebuy('XRP/ETH', 0.0001)
|
rpc._rpc_forcebuy('LTC/ETH', 0.0001)
|
||||||
pair = 'XRP/BTC'
|
pair = 'XRP/BTC'
|
||||||
|
|
||||||
# Test not buying
|
# Test not buying
|
||||||
|
@ -34,13 +34,6 @@ def all_conf():
|
|||||||
return conf
|
return conf
|
||||||
|
|
||||||
|
|
||||||
def test_load_config_invalid_pair(default_conf) -> None:
|
|
||||||
default_conf['exchange']['pair_whitelist'].append('ETH-BTC')
|
|
||||||
|
|
||||||
with pytest.raises(ValidationError, match=r'.*does not match.*'):
|
|
||||||
validate_config_schema(default_conf)
|
|
||||||
|
|
||||||
|
|
||||||
def test_load_config_missing_attributes(default_conf) -> None:
|
def test_load_config_missing_attributes(default_conf) -> None:
|
||||||
conf = deepcopy(default_conf)
|
conf = deepcopy(default_conf)
|
||||||
conf.pop('exchange')
|
conf.pop('exchange')
|
||||||
@ -810,12 +803,6 @@ def test_validate_whitelist(default_conf):
|
|||||||
|
|
||||||
validate_config_consistency(conf)
|
validate_config_consistency(conf)
|
||||||
|
|
||||||
conf = deepcopy(default_conf)
|
|
||||||
conf['stake_currency'] = 'USDT'
|
|
||||||
with pytest.raises(OperationalException,
|
|
||||||
match=r"Stake-currency 'USDT' not compatible with pair-whitelist.*"):
|
|
||||||
validate_config_consistency(conf)
|
|
||||||
|
|
||||||
|
|
||||||
def test_load_config_test_comments() -> None:
|
def test_load_config_test_comments() -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -2200,6 +2200,7 @@ def test_handle_timedout_limit_buy(mocker, default_conf, limit_buy_order) -> Non
|
|||||||
|
|
||||||
Trade.session = MagicMock()
|
Trade.session = MagicMock()
|
||||||
trade = MagicMock()
|
trade = MagicMock()
|
||||||
|
trade.pair = 'LTC/ETH'
|
||||||
limit_buy_order['remaining'] = limit_buy_order['amount']
|
limit_buy_order['remaining'] = limit_buy_order['amount']
|
||||||
assert freqtrade.handle_timedout_limit_buy(trade, limit_buy_order)
|
assert freqtrade.handle_timedout_limit_buy(trade, limit_buy_order)
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
@ -2223,6 +2224,7 @@ def test_handle_timedout_limit_buy_corder_empty(mocker, default_conf, limit_buy_
|
|||||||
|
|
||||||
Trade.session = MagicMock()
|
Trade.session = MagicMock()
|
||||||
trade = MagicMock()
|
trade = MagicMock()
|
||||||
|
trade.pair = 'LTC/ETH'
|
||||||
limit_buy_order['remaining'] = limit_buy_order['amount']
|
limit_buy_order['remaining'] = limit_buy_order['amount']
|
||||||
assert freqtrade.handle_timedout_limit_buy(trade, limit_buy_order)
|
assert freqtrade.handle_timedout_limit_buy(trade, limit_buy_order)
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
|
Loading…
Reference in New Issue
Block a user