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