Fixed create order margin call count tests and made _ccxt_config a computed property

This commit is contained in:
Sam Germain 2021-09-17 14:16:52 -06:00
parent 32e52cd460
commit 2c21bbfa0c
6 changed files with 96 additions and 47 deletions

View File

@ -20,4 +20,7 @@ class Bibox(Exchange):
# fetchCurrencies API point requires authentication for Bibox, # fetchCurrencies API point requires authentication for Bibox,
# so switch it off for Freqtrade load_markets() # so switch it off for Freqtrade load_markets()
_ccxt_config: Dict = {"has": {"fetchCurrencies": False}} @property
def _ccxt_config(self) -> Dict:
# Parameters to add directly to ccxt sync/async initialization.
return {"has": {"fetchCurrencies": False}}

View File

@ -33,9 +33,27 @@ class Binance(Exchange):
# TradingMode.SPOT always supported and not required in this list # TradingMode.SPOT always supported and not required in this list
# (TradingMode.MARGIN, Collateral.CROSS), # TODO-lev: Uncomment once supported # (TradingMode.MARGIN, Collateral.CROSS), # TODO-lev: Uncomment once supported
# (TradingMode.FUTURES, Collateral.CROSS), # TODO-lev: Uncomment once supported # (TradingMode.FUTURES, Collateral.CROSS), # TODO-lev: Uncomment once supported
(TradingMode.FUTURES, Collateral.ISOLATED) # (TradingMode.FUTURES, Collateral.ISOLATED) # TODO-lev: Uncomment once supported
] ]
@property
def _ccxt_config(self) -> Dict:
# Parameters to add directly to ccxt sync/async initialization.
if self.trading_mode == TradingMode.MARGIN:
return {
"options": {
"defaultType": "margin"
}
}
elif self.trading_mode == TradingMode.FUTURES:
return {
"options": {
"defaultType": "future"
}
}
else:
return {}
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)

View File

@ -49,9 +49,6 @@ class Exchange:
_config: Dict = {} _config: Dict = {}
# Parameters to add directly to ccxt sync/async initialization.
_ccxt_config: Dict = {}
# Parameters to add directly to buy/sell calls (like agreeing to trading agreement) # Parameters to add directly to buy/sell calls (like agreeing to trading agreement)
_params: Dict = {} _params: Dict = {}
@ -131,21 +128,6 @@ class Exchange:
self._trades_pagination = self._ft_has['trades_pagination'] self._trades_pagination = self._ft_has['trades_pagination']
self._trades_pagination_arg = self._ft_has['trades_pagination_arg'] self._trades_pagination_arg = self._ft_has['trades_pagination_arg']
# Initialize ccxt objects
ccxt_config = self._ccxt_config.copy()
ccxt_config = deep_merge_dicts(exchange_config.get('ccxt_config', {}), ccxt_config)
ccxt_config = deep_merge_dicts(exchange_config.get('ccxt_sync_config', {}), ccxt_config)
self._api = self._init_ccxt(exchange_config, ccxt_kwargs=ccxt_config)
ccxt_async_config = self._ccxt_config.copy()
ccxt_async_config = deep_merge_dicts(exchange_config.get('ccxt_config', {}),
ccxt_async_config)
ccxt_async_config = deep_merge_dicts(exchange_config.get('ccxt_async_config', {}),
ccxt_async_config)
self._api_async = self._init_ccxt(
exchange_config, ccxt_async, ccxt_kwargs=ccxt_async_config)
self.trading_mode: TradingMode = ( self.trading_mode: TradingMode = (
TradingMode(config.get('trading_mode')) TradingMode(config.get('trading_mode'))
if config.get('trading_mode') if config.get('trading_mode')
@ -157,6 +139,21 @@ class Exchange:
else None else None
) )
# Initialize ccxt objects
ccxt_config = self._ccxt_config
ccxt_config = deep_merge_dicts(exchange_config.get('ccxt_config', {}), ccxt_config)
ccxt_config = deep_merge_dicts(exchange_config.get('ccxt_sync_config', {}), ccxt_config)
self._api = self._init_ccxt(exchange_config, ccxt_kwargs=ccxt_config)
ccxt_async_config = self._ccxt_config
ccxt_async_config = deep_merge_dicts(exchange_config.get('ccxt_config', {}),
ccxt_async_config)
ccxt_async_config = deep_merge_dicts(exchange_config.get('ccxt_async_config', {}),
ccxt_async_config)
self._api_async = self._init_ccxt(
exchange_config, ccxt_async, ccxt_kwargs=ccxt_async_config)
if self.trading_mode != TradingMode.SPOT: if self.trading_mode != TradingMode.SPOT:
self.fill_leverage_brackets() self.fill_leverage_brackets()
@ -210,7 +207,7 @@ class Exchange:
'secret': exchange_config.get('secret'), 'secret': exchange_config.get('secret'),
'password': exchange_config.get('password'), 'password': exchange_config.get('password'),
'uid': exchange_config.get('uid', ''), 'uid': exchange_config.get('uid', ''),
'options': exchange_config.get('options', {}) # 'options': exchange_config.get('options', {})
} }
if ccxt_kwargs: if ccxt_kwargs:
logger.info('Applying additional ccxt config: %s', ccxt_kwargs) logger.info('Applying additional ccxt config: %s', ccxt_kwargs)
@ -231,6 +228,11 @@ class Exchange:
return api return api
@property
def _ccxt_config(self) -> Dict:
# Parameters to add directly to ccxt sync/async initialization.
return {}
@property @property
def name(self) -> str: def name(self) -> str:
"""exchange Name (from ccxt)""" """exchange Name (from ccxt)"""
@ -258,13 +260,6 @@ class Exchange:
"""exchange ccxt precisionMode""" """exchange ccxt precisionMode"""
return self._api.precisionMode return self._api.precisionMode
@property
def running_live_mode(self) -> bool:
return (
self._config['runmode'].value not in ('backtest', 'hyperopt') and
not self._config['dry_run']
)
def _log_exchange_response(self, endpoint, response) -> None: def _log_exchange_response(self, endpoint, response) -> None:
""" Log exchange responses """ """ Log exchange responses """
if self.log_responses: if self.log_responses:
@ -624,12 +619,12 @@ class Exchange:
# The value returned should satisfy both limits: for amount (base currency) and # The value returned should satisfy both limits: for amount (base currency) and
# for cost (quote, stake currency), so max() is used here. # for cost (quote, stake currency), so max() is used here.
# See also #2575 at github. # See also #2575 at github.
return self._divide_stake_amount_by_leverage( return self._get_stake_amount_considering_leverage(
max(min_stake_amounts) * amount_reserve_percent, max(min_stake_amounts) * amount_reserve_percent,
leverage or 1.0 leverage or 1.0
) )
def _divide_stake_amount_by_leverage(self, stake_amount: float, leverage: float): def _get_stake_amount_considering_leverage(self, stake_amount: float, leverage: float):
""" """
Takes the minimum stake amount for a pair with no leverage and returns the minimum Takes the minimum stake amount for a pair with no leverage and returns the minimum
stake amount when leverage is considered stake amount when leverage is considered
@ -1603,7 +1598,7 @@ class Exchange:
def fill_leverage_brackets(self): def fill_leverage_brackets(self):
""" """
#TODO-lev: Should maybe be renamed, leverage_brackets might not be accurate for kraken # TODO-lev: Should maybe be renamed, leverage_brackets might not be accurate for kraken
Assigns property _leverage_brackets to a dictionary of information about the leverage Assigns property _leverage_brackets to a dictionary of information about the leverage
allowed on each pair allowed on each pair
""" """

View File

@ -18,7 +18,7 @@ from freqtrade import constants
from freqtrade.commands import Arguments from freqtrade.commands import Arguments
from freqtrade.data.converter import ohlcv_to_dataframe from freqtrade.data.converter import ohlcv_to_dataframe
from freqtrade.edge import Edge, PairInfo from freqtrade.edge import Edge, PairInfo
from freqtrade.enums import RunMode from freqtrade.enums import Collateral, RunMode, TradingMode
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade.freqtradebot import FreqtradeBot from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import LocalTrade, Trade, init_db from freqtrade.persistence import LocalTrade, Trade, init_db
@ -81,7 +81,13 @@ def patched_configuration_load_config_file(mocker, config) -> None:
) )
def patch_exchange(mocker, api_mock=None, id='binance', mock_markets=True) -> None: def patch_exchange(
mocker,
api_mock=None,
id='binance',
mock_markets=True,
mock_supported_modes=True
) -> None:
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock(return_value={})) mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
@ -90,10 +96,22 @@ def patch_exchange(mocker, api_mock=None, id='binance', mock_markets=True) -> No
mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value=id)) mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value=id))
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value=id.title())) mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value=id.title()))
mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=2)) mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=2))
if mock_markets: if mock_markets:
mocker.patch('freqtrade.exchange.Exchange.markets', mocker.patch('freqtrade.exchange.Exchange.markets',
PropertyMock(return_value=get_markets())) PropertyMock(return_value=get_markets()))
if mock_supported_modes:
mocker.patch(
f'freqtrade.exchange.{id.capitalize()}._supported_trading_mode_collateral_pairs',
PropertyMock(return_value=[
(TradingMode.MARGIN, Collateral.CROSS),
(TradingMode.MARGIN, Collateral.ISOLATED),
(TradingMode.FUTURES, Collateral.CROSS),
(TradingMode.FUTURES, Collateral.ISOLATED)
])
)
if api_mock: if api_mock:
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
else: else:
@ -101,8 +119,8 @@ def patch_exchange(mocker, api_mock=None, id='binance', mock_markets=True) -> No
def get_patched_exchange(mocker, config, api_mock=None, id='binance', def get_patched_exchange(mocker, config, api_mock=None, id='binance',
mock_markets=True) -> Exchange: mock_markets=True, mock_supported_modes=True) -> Exchange:
patch_exchange(mocker, api_mock, id, mock_markets) patch_exchange(mocker, api_mock, id, mock_markets, mock_supported_modes)
config['exchange']['name'] = id config['exchange']['name'] = id
try: try:
exchange = ExchangeResolver.load_exchange(id, config) exchange = ExchangeResolver.load_exchange(id, config)

View File

@ -336,3 +336,15 @@ async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog):
assert exchange._api_async.fetch_ohlcv.call_count == 2 assert exchange._api_async.fetch_ohlcv.call_count == 2
assert res == ohlcv assert res == ohlcv
assert log_has_re(r"Candle-data for ETH/BTC available starting with .*", caplog) assert log_has_re(r"Candle-data for ETH/BTC available starting with .*", caplog)
@pytest.mark.parametrize("trading_mode,collateral,config", [
("", "", {}),
("margin", "cross", {"options": {"defaultType": "margin"}}),
("futures", "isolated", {"options": {"defaultType": "future"}}),
])
def test__ccxt_config(default_conf, mocker, trading_mode, collateral, config):
default_conf['trading_mode'] = trading_mode
default_conf['collateral'] = collateral
exchange = get_patched_exchange(mocker, default_conf, id="binance")
assert exchange._ccxt_config == config

View File

@ -132,10 +132,9 @@ def test_init_ccxt_kwargs(default_conf, mocker, caplog):
assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog) assert log_has("Applying additional ccxt config: {'TestKWARG': 11, 'TestKWARG44': 11}", caplog)
assert ex._api.headers == {'hello': 'world'} assert ex._api.headers == {'hello': 'world'}
assert ex._ccxt_config == {}
Exchange._headers = {} Exchange._headers = {}
# TODO-lev: Test with options
def test_destroy(default_conf, mocker, caplog): def test_destroy(default_conf, mocker, caplog):
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
@ -1116,6 +1115,8 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange._set_leverage = MagicMock()
exchange.set_margin_mode = MagicMock()
order = exchange.create_order( order = exchange.create_order(
pair='ETH/BTC', pair='ETH/BTC',
@ -1134,10 +1135,10 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
assert api_mock.create_order.call_args[0][2] == side assert api_mock.create_order.call_args[0][2] == side
assert api_mock.create_order.call_args[0][3] == 1 assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] is rate assert api_mock.create_order.call_args[0][4] is rate
assert exchange._set_leverage.call_count == 0
assert exchange.set_margin_mode.call_count == 0
assert api_mock._set_leverage.call_count == 0 if side == "buy" else 1 exchange.trading_mode = TradingMode.FUTURES
assert api_mock.set_margin_mode.call_count == 0 if side == "buy" else 1
order = exchange.create_order( order = exchange.create_order(
pair='ETH/BTC', pair='ETH/BTC',
ordertype=ordertype, ordertype=ordertype,
@ -1147,8 +1148,8 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice,
leverage=3.0 leverage=3.0
) )
assert api_mock._set_leverage.call_count == 1 assert exchange._set_leverage.call_count == 1
assert api_mock.set_margin_mode.call_count == 1 assert exchange.set_margin_mode.call_count == 1
def test_buy_dry_run(default_conf, mocker): def test_buy_dry_run(default_conf, mocker):
@ -3042,7 +3043,6 @@ def test_calculate_fee_rate(mocker, default_conf, order, expected) -> None:
(3, 5, 5), (3, 5, 5),
(4, 5, 2), (4, 5, 2),
(5, 5, 1), (5, 5, 1),
]) ])
def test_calculate_backoff(retrycount, max_retries, expected): def test_calculate_backoff(retrycount, max_retries, expected):
assert calculate_backoff(retrycount, max_retries) == expected assert calculate_backoff(retrycount, max_retries) == expected
@ -3054,7 +3054,7 @@ def test_calculate_backoff(retrycount, max_retries, expected):
(20.0, 5.0, 4.0), (20.0, 5.0, 4.0),
(100.0, 100.0, 1.0) (100.0, 100.0, 1.0)
]) ])
def test_divide_stake_amount_by_leverage( def test_get_stake_amount_considering_leverage(
exchange, exchange,
stake_amount, stake_amount,
leverage, leverage,
@ -3063,7 +3063,8 @@ def test_divide_stake_amount_by_leverage(
default_conf default_conf
): ):
exchange = get_patched_exchange(mocker, default_conf, id=exchange) exchange = get_patched_exchange(mocker, default_conf, id=exchange)
assert exchange._divide_stake_amount_by_leverage(stake_amount, leverage) == min_stake_with_lev assert exchange._get_stake_amount_considering_leverage(
stake_amount, leverage) == min_stake_with_lev
@pytest.mark.parametrize("exchange_name,trading_mode", [ @pytest.mark.parametrize("exchange_name,trading_mode", [
@ -3132,6 +3133,7 @@ def test_set_margin_mode(mocker, default_conf, collateral):
# TODO-lev: Remove once implemented # TODO-lev: Remove once implemented
("binance", TradingMode.MARGIN, Collateral.CROSS, True), ("binance", TradingMode.MARGIN, Collateral.CROSS, True),
("binance", TradingMode.FUTURES, Collateral.CROSS, True), ("binance", TradingMode.FUTURES, Collateral.CROSS, True),
("binance", TradingMode.FUTURES, Collateral.ISOLATED, True),
("kraken", TradingMode.MARGIN, Collateral.CROSS, True), ("kraken", TradingMode.MARGIN, Collateral.CROSS, True),
("kraken", TradingMode.FUTURES, Collateral.CROSS, True), ("kraken", TradingMode.FUTURES, Collateral.CROSS, True),
("ftx", TradingMode.MARGIN, Collateral.CROSS, True), ("ftx", TradingMode.MARGIN, Collateral.CROSS, True),
@ -3140,7 +3142,7 @@ def test_set_margin_mode(mocker, default_conf, collateral):
# TODO-lev: Uncomment once implemented # TODO-lev: Uncomment once implemented
# ("binance", TradingMode.MARGIN, Collateral.CROSS, False), # ("binance", TradingMode.MARGIN, Collateral.CROSS, False),
# ("binance", TradingMode.FUTURES, Collateral.CROSS, False), # ("binance", TradingMode.FUTURES, Collateral.CROSS, False),
("binance", TradingMode.FUTURES, Collateral.ISOLATED, False), # ("binance", TradingMode.FUTURES, Collateral.ISOLATED, False),
# ("kraken", TradingMode.MARGIN, Collateral.CROSS, False), # ("kraken", TradingMode.MARGIN, Collateral.CROSS, False),
# ("kraken", TradingMode.FUTURES, Collateral.CROSS, False), # ("kraken", TradingMode.FUTURES, Collateral.CROSS, False),
# ("ftx", TradingMode.MARGIN, Collateral.CROSS, False), # ("ftx", TradingMode.MARGIN, Collateral.CROSS, False),
@ -3154,7 +3156,8 @@ def test_validate_trading_mode_and_collateral(
collateral, collateral,
exception_thrown exception_thrown
): ):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(
mocker, default_conf, id=exchange_name, mock_supported_modes=False)
if (exception_thrown): if (exception_thrown):
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
exchange.validate_trading_mode_and_collateral(trading_mode, collateral) exchange.validate_trading_mode_and_collateral(trading_mode, collateral)