Merge pull request #5825 from freqtrade/futures_pairlist
Futures pairlist
This commit is contained in:
commit
cbb5025711
@ -48,7 +48,8 @@ ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all"]
|
||||
ARGS_LIST_TIMEFRAMES = ["exchange", "print_one_column"]
|
||||
|
||||
ARGS_LIST_PAIRS = ["exchange", "print_list", "list_pairs_print_json", "print_one_column",
|
||||
"print_csv", "base_currencies", "quote_currencies", "list_pairs_all"]
|
||||
"print_csv", "base_currencies", "quote_currencies", "list_pairs_all",
|
||||
"trading_mode"]
|
||||
|
||||
ARGS_TEST_PAIRLIST = ["verbosity", "config", "quote_currencies", "print_one_column",
|
||||
"list_pairs_print_json"]
|
||||
|
@ -179,7 +179,6 @@ AVAILABLE_CLI_OPTIONS = {
|
||||
'--export',
|
||||
help='Export backtest results (default: trades).',
|
||||
choices=constants.EXPORT_OPTIONS,
|
||||
|
||||
),
|
||||
"exportfilename": Arg(
|
||||
'--export-filename',
|
||||
@ -349,6 +348,11 @@ AVAILABLE_CLI_OPTIONS = {
|
||||
nargs='+',
|
||||
metavar='BASE_CURRENCY',
|
||||
),
|
||||
"trading_mode": Arg(
|
||||
'--trading-mode',
|
||||
help='Select Trading mode',
|
||||
choices=constants.TRADING_MODES,
|
||||
),
|
||||
# Script options
|
||||
"pairs": Arg(
|
||||
'-p', '--pairs',
|
||||
|
@ -129,10 +129,9 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
|
||||
quote_currencies = args.get('quote_currencies', [])
|
||||
|
||||
try:
|
||||
# TODO-lev: Add leverage amount to get markets that support a certain leverage
|
||||
pairs = exchange.get_markets(base_currencies=base_currencies,
|
||||
quote_currencies=quote_currencies,
|
||||
pairs_only=pairs_only,
|
||||
tradable_only=pairs_only,
|
||||
active_only=active_only)
|
||||
# Sort the pairs/markets by symbol
|
||||
pairs = dict(sorted(pairs.items()))
|
||||
@ -152,15 +151,19 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
|
||||
if quote_currencies else ""))
|
||||
|
||||
headers = ["Id", "Symbol", "Base", "Quote", "Active",
|
||||
*(['Is pair'] if not pairs_only else [])]
|
||||
"Spot", "Margin", "Future", "Leverage"]
|
||||
|
||||
tabular_data = []
|
||||
for _, v in pairs.items():
|
||||
tabular_data.append({'Id': v['id'], 'Symbol': v['symbol'],
|
||||
'Base': v['base'], 'Quote': v['quote'],
|
||||
'Active': market_is_active(v),
|
||||
**({'Is pair': exchange.market_is_tradable(v)}
|
||||
if not pairs_only else {})})
|
||||
tabular_data = [{
|
||||
'Id': v['id'],
|
||||
'Symbol': v['symbol'],
|
||||
'Base': v['base'],
|
||||
'Quote': v['quote'],
|
||||
'Active': market_is_active(v),
|
||||
'Spot': 'Spot' if exchange.market_is_spot(v) else '',
|
||||
'Margin': 'Margin' if exchange.market_is_margin(v) else '',
|
||||
'Future': 'Future' if exchange.market_is_future(v) else '',
|
||||
'Leverage': exchange.get_max_leverage(v['symbol'], 20)
|
||||
} for _, v in pairs.items()]
|
||||
|
||||
if (args.get('print_one_column', False) or
|
||||
args.get('list_pairs_print_json', False) or
|
||||
|
@ -431,6 +431,8 @@ class Configuration:
|
||||
|
||||
self._args_to_config(config, argname='new_pairs_days',
|
||||
logstring='Detected --new-pairs-days: {}')
|
||||
self._args_to_config(config, argname='trading_mode',
|
||||
logstring='Detected --trading-mode: {}')
|
||||
|
||||
def _process_runmode(self, config: Dict[str, Any]) -> None:
|
||||
|
||||
|
@ -3,7 +3,7 @@ import json
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
import arrow
|
||||
import ccxt
|
||||
@ -119,6 +119,10 @@ class Binance(Exchange):
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e) from e
|
||||
|
||||
def market_is_future(self, market: Dict[str, Any]) -> bool:
|
||||
# TODO-lev: This should be unified in ccxt to "swap"...
|
||||
return market.get('future', False) is True
|
||||
|
||||
@retrier
|
||||
def fill_leverage_brackets(self):
|
||||
"""
|
||||
@ -161,6 +165,8 @@ class Binance(Exchange):
|
||||
:param pair: The base/quote currency pair being traded
|
||||
:nominal_value: The total value of the trade in quote currency (collateral + debt)
|
||||
"""
|
||||
if pair not in self._leverage_brackets:
|
||||
return 1.0
|
||||
pair_brackets = self._leverage_brackets[pair]
|
||||
max_lev = 1.0
|
||||
for [min_amount, margin_req] in pair_brackets:
|
||||
|
@ -291,7 +291,9 @@ class Exchange:
|
||||
timeframe, self._ft_has.get('ohlcv_candle_limit')))
|
||||
|
||||
def get_markets(self, base_currencies: List[str] = None, quote_currencies: List[str] = None,
|
||||
pairs_only: bool = False, active_only: bool = False) -> Dict[str, Any]:
|
||||
spot_only: bool = False, margin_only: bool = False, futures_only: bool = False,
|
||||
tradable_only: bool = True,
|
||||
active_only: bool = False) -> Dict[str, Any]:
|
||||
"""
|
||||
Return exchange ccxt markets, filtered out by base currency and quote currency
|
||||
if this was requested in parameters.
|
||||
@ -306,8 +308,14 @@ class Exchange:
|
||||
markets = {k: v for k, v in markets.items() if v['base'] in base_currencies}
|
||||
if quote_currencies:
|
||||
markets = {k: v for k, v in markets.items() if v['quote'] in quote_currencies}
|
||||
if pairs_only:
|
||||
if tradable_only:
|
||||
markets = {k: v for k, v in markets.items() if self.market_is_tradable(v)}
|
||||
if spot_only:
|
||||
markets = {k: v for k, v in markets.items() if self.market_is_spot(v)}
|
||||
if margin_only:
|
||||
markets = {k: v for k, v in markets.items() if self.market_is_margin(v)}
|
||||
if futures_only:
|
||||
markets = {k: v for k, v in markets.items() if self.market_is_future(v)}
|
||||
if active_only:
|
||||
markets = {k: v for k, v in markets.items() if market_is_active(v)}
|
||||
return markets
|
||||
@ -331,18 +339,27 @@ class Exchange:
|
||||
"""
|
||||
return self.markets.get(pair, {}).get('base', '')
|
||||
|
||||
def market_is_future(self, market: Dict[str, Any]) -> bool:
|
||||
return market.get('swap', False) is True
|
||||
|
||||
def market_is_spot(self, market: Dict[str, Any]) -> bool:
|
||||
return market.get('spot', False) is True
|
||||
|
||||
def market_is_margin(self, market: Dict[str, Any]) -> bool:
|
||||
return market.get('margin', False) is True
|
||||
|
||||
def market_is_tradable(self, market: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
Check if the market symbol is tradable by Freqtrade.
|
||||
By default, checks if it's splittable by `/` and both sides correspond to base / quote
|
||||
Ensures that Configured mode aligns to
|
||||
"""
|
||||
symbol_parts = market['symbol'].split('/')
|
||||
return (len(symbol_parts) == 2 and
|
||||
len(symbol_parts[0]) > 0 and
|
||||
len(symbol_parts[1]) > 0 and
|
||||
symbol_parts[0] == market.get('base') and
|
||||
symbol_parts[1] == market.get('quote')
|
||||
)
|
||||
return (
|
||||
market.get('quote', None) is not None
|
||||
and market.get('base', None) is not None
|
||||
and (self.trading_mode == TradingMode.SPOT and self.market_is_spot(market))
|
||||
or (self.trading_mode == TradingMode.MARGIN and self.market_is_margin(market))
|
||||
or (self.trading_mode == TradingMode.FUTURES and self.market_is_future(market))
|
||||
)
|
||||
|
||||
def klines(self, pair_interval: Tuple[str, str], copy: bool = True) -> DataFrame:
|
||||
if pair_interval in self._klines:
|
||||
|
@ -30,16 +30,6 @@ class Ftx(Exchange):
|
||||
# (TradingMode.FUTURES, Collateral.CROSS)
|
||||
]
|
||||
|
||||
def market_is_tradable(self, market: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
Check if the market symbol is tradable by Freqtrade.
|
||||
Default checks + check if pair is spot pair (no futures trading yet).
|
||||
"""
|
||||
parent_check = super().market_is_tradable(market)
|
||||
|
||||
return (parent_check and
|
||||
market.get('spot', False) is True)
|
||||
|
||||
def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool:
|
||||
"""
|
||||
Verify stop_loss against stoploss-order value (limit or price)
|
||||
@ -169,3 +159,7 @@ class Ftx(Exchange):
|
||||
if order['type'] == 'stop':
|
||||
return safe_value_fallback2(order, order, 'id_stop', 'id')
|
||||
return order['id']
|
||||
|
||||
def market_is_future(self, market: Dict[str, Any]) -> bool:
|
||||
# TODO-lev: This should be unified in ccxt to "swap"...
|
||||
return market.get('future', False) is True
|
||||
|
@ -2,7 +2,7 @@ numpy==1.21.3
|
||||
pandas==1.3.4
|
||||
pandas-ta==0.3.14b
|
||||
|
||||
ccxt==1.60.11
|
||||
ccxt==1.61.24
|
||||
# Pin cryptography for now due to rust build errors with piwheels
|
||||
cryptography==35.0.0
|
||||
aiohttp==3.7.4.post0
|
||||
|
2
setup.py
2
setup.py
@ -43,7 +43,7 @@ setup(
|
||||
],
|
||||
install_requires=[
|
||||
# from requirements.txt
|
||||
'ccxt>=1.60.11',
|
||||
'ccxt>=1.61.24',
|
||||
'SQLAlchemy',
|
||||
'python-telegram-bot>=13.4',
|
||||
'arrow>=0.17.0',
|
||||
|
@ -434,9 +434,9 @@ def test_list_markets(mocker, markets_static, capsys):
|
||||
]
|
||||
start_list_markets(get_args(args), False)
|
||||
captured = capsys.readouterr()
|
||||
assert ("Id,Symbol,Base,Quote,Active,Is pair" in captured.out)
|
||||
assert ("blkbtc,BLK/BTC,BLK,BTC,True,True" in captured.out)
|
||||
assert ("USD-LTC,LTC/USD,LTC,USD,True,True" in captured.out)
|
||||
assert ("Id,Symbol,Base,Quote,Active,Spot,Margin,Future,Leverage" in captured.out)
|
||||
assert ("blkbtc,BLK/BTC,BLK,BTC,True,Spot" in captured.out)
|
||||
assert ("USD-LTC,LTC/USD,LTC,USD,True,Spot" in captured.out)
|
||||
|
||||
# Test --one-column
|
||||
args = [
|
||||
|
@ -575,6 +575,8 @@ def get_markets():
|
||||
'base': 'ETH',
|
||||
'quote': 'BTC',
|
||||
'active': True,
|
||||
'spot': True,
|
||||
'type': 'spot',
|
||||
'precision': {
|
||||
'price': 8,
|
||||
'amount': 8,
|
||||
@ -604,6 +606,8 @@ def get_markets():
|
||||
'quote': 'BTC',
|
||||
# According to ccxt, markets without active item set are also active
|
||||
# 'active': True,
|
||||
'spot': True,
|
||||
'type': 'spot',
|
||||
'precision': {
|
||||
'price': 8,
|
||||
'amount': 8,
|
||||
@ -632,6 +636,8 @@ def get_markets():
|
||||
'base': 'BLK',
|
||||
'quote': 'BTC',
|
||||
'active': True,
|
||||
'spot': True,
|
||||
'type': 'spot',
|
||||
'precision': {
|
||||
'price': 8,
|
||||
'amount': 8,
|
||||
@ -660,6 +666,8 @@ def get_markets():
|
||||
'base': 'LTC',
|
||||
'quote': 'BTC',
|
||||
'active': True,
|
||||
'spot': True,
|
||||
'type': 'spot',
|
||||
'precision': {
|
||||
'price': 8,
|
||||
'amount': 8,
|
||||
@ -685,6 +693,8 @@ def get_markets():
|
||||
'base': 'XRP',
|
||||
'quote': 'BTC',
|
||||
'active': True,
|
||||
'spot': True,
|
||||
'type': 'spot',
|
||||
'precision': {
|
||||
'price': 8,
|
||||
'amount': 8,
|
||||
@ -710,6 +720,8 @@ def get_markets():
|
||||
'base': 'NEO',
|
||||
'quote': 'BTC',
|
||||
'active': True,
|
||||
'spot': True,
|
||||
'type': 'spot',
|
||||
'precision': {
|
||||
'price': 8,
|
||||
'amount': 8,
|
||||
@ -735,6 +747,8 @@ def get_markets():
|
||||
'base': 'BTT',
|
||||
'quote': 'BTC',
|
||||
'active': False,
|
||||
'spot': True,
|
||||
'type': 'spot',
|
||||
'precision': {
|
||||
'base': 8,
|
||||
'quote': 8,
|
||||
@ -762,6 +776,11 @@ def get_markets():
|
||||
'symbol': 'ETH/USDT',
|
||||
'base': 'ETH',
|
||||
'quote': 'USDT',
|
||||
'spot': True,
|
||||
'future': True,
|
||||
'swap': True,
|
||||
'margin': True,
|
||||
'type': 'spot',
|
||||
'precision': {
|
||||
'amount': 8,
|
||||
'price': 8
|
||||
@ -785,6 +804,11 @@ def get_markets():
|
||||
'base': 'LTC',
|
||||
'quote': 'USDT',
|
||||
'active': False,
|
||||
'spot': True,
|
||||
'future': True,
|
||||
'swap': True,
|
||||
'margin': True,
|
||||
'type': 'spot',
|
||||
'precision': {
|
||||
'amount': 8,
|
||||
'price': 8
|
||||
@ -807,6 +831,8 @@ def get_markets():
|
||||
'base': 'XRP',
|
||||
'quote': 'USDT',
|
||||
'active': True,
|
||||
'spot': True,
|
||||
'type': 'spot',
|
||||
'precision': {
|
||||
'price': 8,
|
||||
'amount': 8,
|
||||
@ -832,6 +858,8 @@ def get_markets():
|
||||
'base': 'NEO',
|
||||
'quote': 'USDT',
|
||||
'active': True,
|
||||
'spot': True,
|
||||
'type': 'spot',
|
||||
'precision': {
|
||||
'price': 8,
|
||||
'amount': 8,
|
||||
@ -857,6 +885,8 @@ def get_markets():
|
||||
'base': 'TKN',
|
||||
'quote': 'USDT',
|
||||
'active': True,
|
||||
'spot': True,
|
||||
'type': 'spot',
|
||||
'precision': {
|
||||
'price': 8,
|
||||
'amount': 8,
|
||||
@ -882,6 +912,8 @@ def get_markets():
|
||||
'base': 'LTC',
|
||||
'quote': 'USD',
|
||||
'active': True,
|
||||
'spot': True,
|
||||
'type': 'spot',
|
||||
'precision': {
|
||||
'amount': 8,
|
||||
'price': 8
|
||||
@ -904,6 +936,8 @@ def get_markets():
|
||||
'base': 'LTC',
|
||||
'quote': 'USDT',
|
||||
'active': True,
|
||||
'spot': False,
|
||||
'type': 'SomethingElse',
|
||||
'precision': {
|
||||
'amount': 8,
|
||||
'price': 8
|
||||
@ -926,6 +960,8 @@ def get_markets():
|
||||
'base': 'LTC',
|
||||
'quote': 'ETH',
|
||||
'active': True,
|
||||
'spot': True,
|
||||
'type': 'spot',
|
||||
'precision': {
|
||||
'base': 8,
|
||||
'quote': 8,
|
||||
@ -976,6 +1012,8 @@ def shitcoinmarkets(markets_static):
|
||||
'base': 'HOT',
|
||||
'quote': 'BTC',
|
||||
'active': True,
|
||||
'spot': True,
|
||||
'type': 'spot',
|
||||
'precision': {
|
||||
'base': 8,
|
||||
'quote': 8,
|
||||
@ -1004,6 +1042,8 @@ def shitcoinmarkets(markets_static):
|
||||
'base': 'FUEL',
|
||||
'quote': 'BTC',
|
||||
'active': True,
|
||||
'spot': True,
|
||||
'type': 'spot',
|
||||
'precision': {
|
||||
'base': 8,
|
||||
'quote': 8,
|
||||
|
@ -5,6 +5,7 @@ However, these tests should give a good idea to determine if a new exchange is
|
||||
suitable to run with freqtrade.
|
||||
"""
|
||||
|
||||
from copy import deepcopy
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
@ -26,6 +27,7 @@ EXCHANGES = {
|
||||
'pair': 'BTC/USDT',
|
||||
'hasQuoteVolume': True,
|
||||
'timeframe': '5m',
|
||||
'futures': True,
|
||||
},
|
||||
'kraken': {
|
||||
'pair': 'BTC/USDT',
|
||||
@ -82,13 +84,20 @@ def exchange(request, exchange_conf):
|
||||
|
||||
|
||||
@pytest.fixture(params=EXCHANGES, scope="class")
|
||||
def exchange_futures(request, exchange_conf):
|
||||
def exchange_futures(request, exchange_conf, class_mocker):
|
||||
if not EXCHANGES[request.param].get('futures') is True:
|
||||
yield None, request.param
|
||||
else:
|
||||
exchange_conf = deepcopy(exchange_conf)
|
||||
exchange_conf['exchange']['name'] = request.param
|
||||
exchange_conf['trading_mode'] = 'futures'
|
||||
exchange_conf['collateral'] = 'cross'
|
||||
# TODO-lev This mock should no longer be necessary once futures are enabled.
|
||||
class_mocker.patch(
|
||||
'freqtrade.exchange.exchange.Exchange.validate_trading_mode_and_collateral')
|
||||
class_mocker.patch(
|
||||
'freqtrade.exchange.binance.Binance.fill_leverage_brackets')
|
||||
|
||||
exchange = ExchangeResolver.load_exchange(request.param, exchange_conf, validate=True)
|
||||
|
||||
yield exchange, request.param
|
||||
@ -103,6 +112,20 @@ class TestCCXTExchange():
|
||||
markets = exchange.markets
|
||||
assert pair in markets
|
||||
assert isinstance(markets[pair], dict)
|
||||
assert exchange.market_is_spot(markets[pair])
|
||||
|
||||
def test_load_markets_futures(self, exchange_futures):
|
||||
exchange, exchangename = exchange_futures
|
||||
if not exchange:
|
||||
# exchange_futures only returns values for supported exchanges
|
||||
return
|
||||
pair = EXCHANGES[exchangename]['pair']
|
||||
pair = EXCHANGES[exchangename].get('futures_pair', pair)
|
||||
markets = exchange.markets
|
||||
assert pair in markets
|
||||
assert isinstance(markets[pair], dict)
|
||||
|
||||
assert exchange.market_is_future(markets[pair])
|
||||
|
||||
def test_ccxt_fetch_tickers(self, exchange):
|
||||
exchange, exchangename = exchange
|
||||
|
@ -2760,7 +2760,8 @@ def test_get_valid_pair_combination(default_conf, mocker, markets):
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"base_currencies, quote_currencies, pairs_only, active_only, expected_keys", [
|
||||
"base_currencies,quote_currencies,tradable_only,active_only,spot_only,"
|
||||
"futures_only,expected_keys", [
|
||||
# Testing markets (in conftest.py):
|
||||
# 'BLK/BTC': 'active': True
|
||||
# 'BTT/BTC': 'active': True
|
||||
@ -2775,48 +2776,62 @@ def test_get_valid_pair_combination(default_conf, mocker, markets):
|
||||
# 'XLTCUSDT': 'active': True, not a pair
|
||||
# 'XRP/BTC': 'active': False
|
||||
# all markets
|
||||
([], [], False, False,
|
||||
([], [], False, False, False, False,
|
||||
['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD',
|
||||
'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XLTCUSDT', 'XRP/BTC']),
|
||||
# all markets, only spot pairs
|
||||
([], [], False, False, True, False,
|
||||
['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD',
|
||||
'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC']),
|
||||
# active markets
|
||||
([], [], False, True,
|
||||
([], [], False, True, False, False,
|
||||
['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'NEO/BTC',
|
||||
'TKN/BTC', 'XLTCUSDT', 'XRP/BTC']),
|
||||
# all pairs
|
||||
([], [], True, False,
|
||||
([], [], True, False, False, False,
|
||||
['BLK/BTC', 'BTT/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD',
|
||||
'LTC/USDT', 'NEO/BTC', 'TKN/BTC', 'XRP/BTC']),
|
||||
# active pairs
|
||||
([], [], True, True,
|
||||
([], [], True, True, False, False,
|
||||
['BLK/BTC', 'ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'NEO/BTC',
|
||||
'TKN/BTC', 'XRP/BTC']),
|
||||
# all markets, base=ETH, LTC
|
||||
(['ETH', 'LTC'], [], False, False,
|
||||
(['ETH', 'LTC'], [], False, False, False, False,
|
||||
['ETH/BTC', 'ETH/USDT', 'LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']),
|
||||
# all markets, base=LTC
|
||||
(['LTC'], [], False, False,
|
||||
(['LTC'], [], False, False, False, False,
|
||||
['LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']),
|
||||
# spot markets, base=LTC
|
||||
(['LTC'], [], False, False, True, False,
|
||||
['LTC/BTC', 'LTC/ETH', 'LTC/USD', 'LTC/USDT']),
|
||||
# all markets, quote=USDT
|
||||
([], ['USDT'], False, False,
|
||||
([], ['USDT'], False, False, False, False,
|
||||
['ETH/USDT', 'LTC/USDT', 'XLTCUSDT']),
|
||||
# Futures markets, quote=USDT
|
||||
([], ['USDT'], False, False, False, True,
|
||||
['ETH/USDT', 'LTC/USDT']),
|
||||
# all markets, quote=USDT, USD
|
||||
([], ['USDT', 'USD'], False, False,
|
||||
([], ['USDT', 'USD'], False, False, False, False,
|
||||
['ETH/USDT', 'LTC/USD', 'LTC/USDT', 'XLTCUSDT']),
|
||||
# spot markets, quote=USDT, USD
|
||||
([], ['USDT', 'USD'], False, False, True, False,
|
||||
['ETH/USDT', 'LTC/USD', 'LTC/USDT']),
|
||||
# all markets, base=LTC, quote=USDT
|
||||
(['LTC'], ['USDT'], False, False,
|
||||
(['LTC'], ['USDT'], False, False, False, False,
|
||||
['LTC/USDT', 'XLTCUSDT']),
|
||||
# all pairs, base=LTC, quote=USDT
|
||||
(['LTC'], ['USDT'], True, False,
|
||||
(['LTC'], ['USDT'], True, False, False, False,
|
||||
['LTC/USDT']),
|
||||
# all markets, base=LTC, quote=USDT, NONEXISTENT
|
||||
(['LTC'], ['USDT', 'NONEXISTENT'], False, False,
|
||||
(['LTC'], ['USDT', 'NONEXISTENT'], False, False, False, False,
|
||||
['LTC/USDT', 'XLTCUSDT']),
|
||||
# all markets, base=LTC, quote=NONEXISTENT
|
||||
(['LTC'], ['NONEXISTENT'], False, False,
|
||||
(['LTC'], ['NONEXISTENT'], False, False, False, False,
|
||||
[]),
|
||||
])
|
||||
def test_get_markets(default_conf, mocker, markets_static,
|
||||
base_currencies, quote_currencies, pairs_only, active_only,
|
||||
base_currencies, quote_currencies, tradable_only, active_only,
|
||||
spot_only, futures_only,
|
||||
expected_keys):
|
||||
mocker.patch.multiple('freqtrade.exchange.Exchange',
|
||||
_init_ccxt=MagicMock(return_value=MagicMock()),
|
||||
@ -2825,7 +2840,12 @@ def test_get_markets(default_conf, mocker, markets_static,
|
||||
validate_timeframes=MagicMock(),
|
||||
markets=PropertyMock(return_value=markets_static))
|
||||
ex = Exchange(default_conf)
|
||||
pairs = ex.get_markets(base_currencies, quote_currencies, pairs_only, active_only)
|
||||
pairs = ex.get_markets(base_currencies,
|
||||
quote_currencies,
|
||||
tradable_only=tradable_only,
|
||||
spot_only=spot_only,
|
||||
futures_only=futures_only,
|
||||
active_only=active_only)
|
||||
assert sorted(pairs.keys()) == sorted(expected_keys)
|
||||
|
||||
|
||||
@ -2928,39 +2948,63 @@ def test_timeframe_to_next_date():
|
||||
assert timeframe_to_next_date("5m", date) == date + timedelta(minutes=5)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("market_symbol,base,quote,exchange,add_dict,expected_result", [
|
||||
("BTC/USDT", 'BTC', 'USDT', "binance", {}, True),
|
||||
("USDT/BTC", 'USDT', 'BTC', "binance", {}, True),
|
||||
("USDT/BTC", 'BTC', 'USDT', "binance", {}, False), # Reversed currencies
|
||||
("BTCUSDT", 'BTC', 'USDT', "binance", {}, False), # No seperating /
|
||||
("BTCUSDT", None, "USDT", "binance", {}, False), #
|
||||
("USDT/BTC", "BTC", None, "binance", {}, False),
|
||||
("BTCUSDT", "BTC", None, "binance", {}, False),
|
||||
("BTC/USDT", "BTC", "USDT", "binance", {}, True),
|
||||
("BTC/USDT", "USDT", "BTC", "binance", {}, False), # reversed currencies
|
||||
("BTC/USDT", "BTC", "USD", "binance", {}, False), # Wrong quote currency
|
||||
("BTC/", "BTC", 'UNK', "binance", {}, False),
|
||||
("/USDT", 'UNK', 'USDT', "binance", {}, False),
|
||||
("BTC/EUR", 'BTC', 'EUR', "kraken", {"darkpool": False}, True),
|
||||
("EUR/BTC", 'EUR', 'BTC', "kraken", {"darkpool": False}, True),
|
||||
("EUR/BTC", 'BTC', 'EUR', "kraken", {"darkpool": False}, False), # Reversed currencies
|
||||
("BTC/EUR", 'BTC', 'USD', "kraken", {"darkpool": False}, False), # wrong quote currency
|
||||
("BTC/EUR", 'BTC', 'EUR', "kraken", {"darkpool": True}, False), # no darkpools
|
||||
("BTC/EUR.d", 'BTC', 'EUR', "kraken", {"darkpool": True}, False), # no darkpools
|
||||
("BTC/USD", 'BTC', 'USD', "ftx", {'spot': True}, True),
|
||||
("USD/BTC", 'USD', 'BTC', "ftx", {'spot': True}, True),
|
||||
("BTC/USD", 'BTC', 'USDT', "ftx", {'spot': True}, False), # Wrong quote currency
|
||||
("BTC/USD", 'USD', 'BTC', "ftx", {'spot': True}, False), # Reversed currencies
|
||||
("BTC/USD", 'BTC', 'USD', "ftx", {'spot': False}, False), # Can only trade spot markets
|
||||
("BTC-PERP", 'BTC', 'USD', "ftx", {'spot': False}, False), # Can only trade spot markets
|
||||
])
|
||||
def test_market_is_tradable(mocker, default_conf, market_symbol, base,
|
||||
quote, add_dict, exchange, expected_result) -> None:
|
||||
@pytest.mark.parametrize(
|
||||
"market_symbol,base,quote,exchange,spot,margin,futures,trademode,add_dict,expected_result",
|
||||
[
|
||||
("BTC/USDT", 'BTC', 'USDT', "binance", True, False, False, 'spot', {}, True),
|
||||
("USDT/BTC", 'USDT', 'BTC', "binance", True, False, False, 'spot', {}, True),
|
||||
# No seperating /
|
||||
("BTCUSDT", 'BTC', 'USDT', "binance", True, False, False, 'spot', {}, True),
|
||||
("BTCUSDT", None, "USDT", "binance", True, False, False, 'spot', {}, False),
|
||||
("USDT/BTC", "BTC", None, "binance", True, False, False, 'spot', {}, False),
|
||||
("BTCUSDT", "BTC", None, "binance", True, False, False, 'spot', {}, False),
|
||||
("BTC/USDT", "BTC", "USDT", "binance", True, False, False, 'spot', {}, True),
|
||||
# Futures mode, spot pair
|
||||
("BTC/USDT", "BTC", "USDT", "binance", True, False, False, 'futures', {}, False),
|
||||
("BTC/USDT", "BTC", "USDT", "binance", True, False, False, 'margin', {}, False),
|
||||
("BTC/USDT", "BTC", "USDT", "binance", True, True, True, 'margin', {}, True),
|
||||
("BTC/USDT", "BTC", "USDT", "binance", False, True, False, 'margin', {}, True),
|
||||
# Futures mode, futures pair
|
||||
("BTC/USDT", "BTC", "USDT", "binance", False, False, True, 'futures', {}, True),
|
||||
# Futures market
|
||||
("BTC/UNK", "BTC", 'UNK', "binance", False, False, True, 'spot', {}, False),
|
||||
("BTC/EUR", 'BTC', 'EUR', "kraken", True, False, False, 'spot', {"darkpool": False}, True),
|
||||
("EUR/BTC", 'EUR', 'BTC', "kraken", True, False, False, 'spot', {"darkpool": False}, True),
|
||||
# no darkpools
|
||||
("BTC/EUR", 'BTC', 'EUR', "kraken", True, False, False, 'spot',
|
||||
{"darkpool": True}, False),
|
||||
# no darkpools
|
||||
("BTC/EUR.d", 'BTC', 'EUR', "kraken", True, False, False, 'spot',
|
||||
{"darkpool": True}, False),
|
||||
("BTC/USD", 'BTC', 'USD', "ftx", True, False, False, 'spot', {}, True),
|
||||
("USD/BTC", 'USD', 'BTC', "ftx", True, False, False, 'spot', {}, True),
|
||||
# Can only trade spot markets
|
||||
("BTC/USD", 'BTC', 'USD', "ftx", False, False, True, 'spot', {}, False),
|
||||
("BTC/USD", 'BTC', 'USD', "ftx", False, False, True, 'futures', {}, True),
|
||||
# Can only trade spot markets
|
||||
("BTC-PERP", 'BTC', 'USD', "ftx", False, False, True, 'spot', {}, False),
|
||||
("BTC-PERP", 'BTC', 'USD', "ftx", False, False, True, 'margin', {}, False),
|
||||
("BTC-PERP", 'BTC', 'USD', "ftx", False, False, True, 'futures', {}, True),
|
||||
|
||||
("BTC/USDT:USDT", 'BTC', 'USD', "okex", False, False, True, 'spot', {}, False),
|
||||
("BTC/USDT:USDT", 'BTC', 'USD', "okex", False, False, True, 'margin', {}, False),
|
||||
("BTC/USDT:USDT", 'BTC', 'USD', "okex", False, False, True, 'futures', {}, True),
|
||||
])
|
||||
def test_market_is_tradable(
|
||||
mocker, default_conf, market_symbol, base,
|
||||
quote, spot, margin, futures, trademode, add_dict, exchange, expected_result
|
||||
) -> None:
|
||||
default_conf['trading_mode'] = trademode
|
||||
mocker.patch('freqtrade.exchange.exchange.Exchange.validate_trading_mode_and_collateral')
|
||||
ex = get_patched_exchange(mocker, default_conf, id=exchange)
|
||||
market = {
|
||||
'symbol': market_symbol,
|
||||
'base': base,
|
||||
'quote': quote,
|
||||
'spot': spot,
|
||||
'future': futures,
|
||||
'swap': futures,
|
||||
'margin': margin,
|
||||
**(add_dict),
|
||||
}
|
||||
assert ex.market_is_tradable(market) == expected_result
|
||||
|
Loading…
Reference in New Issue
Block a user