leverage updates on exchange classes

This commit is contained in:
Sam Germain 2021-09-16 23:05:13 -06:00
parent cbaf477bec
commit 57c7926515
11 changed files with 1467 additions and 126 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,7 @@
""" Binance exchange subclass """ """ Binance exchange subclass """
import json
import logging import logging
from pathlib import Path
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Optional, Tuple
import arrow import arrow
@ -47,8 +49,8 @@ class Binance(Exchange):
) )
@retrier(retries=0) @retrier(retries=0)
def stoploss(self, pair: str, amount: float, def stoploss(self, pair: str, amount: float, stop_price: float,
stop_price: float, order_types: Dict, side: str) -> Dict: order_types: Dict, side: str, leverage: float) -> Dict:
""" """
creates a stoploss limit order. creates a stoploss limit order.
this stoploss-limit is binance-specific. this stoploss-limit is binance-specific.
@ -76,7 +78,7 @@ class Binance(Exchange):
if self._config['dry_run']: if self._config['dry_run']:
dry_order = self.create_dry_run_order( dry_order = self.create_dry_run_order(
pair, ordertype, side, amount, stop_price) pair, ordertype, side, amount, stop_price, leverage)
return dry_order return dry_order
try: try:
@ -87,8 +89,15 @@ class Binance(Exchange):
rate = self.price_to_precision(pair, rate) rate = self.price_to_precision(pair, rate)
order = self._api.create_order(symbol=pair, type=ordertype, side=side, order = self._api.create_order(
amount=amount, price=rate, params=params) symbol=pair,
type=ordertype,
side=side,
amount=amount,
price=rate,
params=params,
leverage=leverage
)
logger.info('stoploss limit order added for %s. ' logger.info('stoploss limit order added for %s. '
'stop price: %s. limit: %s', pair, stop_price, rate) 'stop price: %s. limit: %s', pair, stop_price, rate)
self._log_exchange_response('create_stoploss_order', order) self._log_exchange_response('create_stoploss_order', order)
@ -119,26 +128,33 @@ class Binance(Exchange):
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
""" """
try: if self.trading_mode == TradingMode.FUTURES:
leverage_brackets = self._api.load_leverage_brackets() try:
for pair, brackets in leverage_brackets.items(): if self._config['dry_run']:
self._leverage_brackets[pair] = [ leverage_brackets_path = Path('data') / 'leverage_brackets.json'
[ with open(leverage_brackets_path) as json_file:
min_amount, leverage_brackets = json.load(json_file)
float(margin_req) else:
] for [ leverage_brackets = self._api.load_leverage_brackets()
min_amount,
margin_req
] in brackets
]
except ccxt.DDoSProtection as e: for pair, brackets in leverage_brackets.items():
raise DDosProtection(e) from e self._leverage_brackets[pair] = [
except (ccxt.NetworkError, ccxt.ExchangeError) as e: [
raise TemporaryError(f'Could not fetch leverage amounts due to' min_amount,
f'{e.__class__.__name__}. Message: {e}') from e float(margin_req)
except ccxt.BaseError as e: ] for [
raise OperationalException(e) from e min_amount,
margin_req
] in brackets
]
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(f'Could not fetch leverage amounts due to'
f'{e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float: def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float:
""" """
@ -153,10 +169,6 @@ class Binance(Exchange):
max_lev = 1/margin_req max_lev = 1/margin_req
return max_lev return max_lev
def lev_prep(self, pair: str, leverage: float):
self.set_margin_mode(pair, self.collateral)
self._set_leverage(leverage, pair, self.trading_mode)
@retrier @retrier
def _set_leverage( def _set_leverage(
self, self,
@ -170,9 +182,11 @@ class Binance(Exchange):
""" """
trading_mode = trading_mode or self.trading_mode trading_mode = trading_mode or self.trading_mode
if self._config['dry_run'] or trading_mode != TradingMode.FUTURES:
return
try: try:
if trading_mode == TradingMode.FUTURES: self._api.set_leverage(symbol=pair, leverage=leverage)
self._api.set_leverage(symbol=pair, leverage=leverage)
except ccxt.DDoSProtection as e: except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e: except (ccxt.NetworkError, ccxt.ExchangeError) as e:

View File

@ -258,6 +258,13 @@ 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:
@ -617,15 +624,13 @@ 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._apply_leverage_to_stake_amount( return self._divide_stake_amount_by_leverage(
max(min_stake_amounts) * amount_reserve_percent, max(min_stake_amounts) * amount_reserve_percent,
leverage or 1.0 leverage or 1.0
) )
def _apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float): def _divide_stake_amount_by_leverage(self, stake_amount: float, leverage: float):
""" """
#TODO-lev: Find out how this works on Kraken and FTX
# * Should be implemented by child classes if leverage affects the stake_amount
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
:param stake_amount: The stake amount for a pair before leverage is considered :param stake_amount: The stake amount for a pair before leverage is considered
@ -636,7 +641,7 @@ class Exchange:
# Dry-run methods # Dry-run methods
def create_dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, def create_dry_run_order(self, pair: str, ordertype: str, side: str, amount: float,
rate: float, params: Dict = {}) -> Dict[str, Any]: rate: float, leverage: float, params: Dict = {}) -> Dict[str, Any]:
order_id = f'dry_run_{side}_{datetime.now().timestamp()}' order_id = f'dry_run_{side}_{datetime.now().timestamp()}'
_amount = self.amount_to_precision(pair, amount) _amount = self.amount_to_precision(pair, amount)
dry_order: Dict[str, Any] = { dry_order: Dict[str, Any] = {
@ -653,7 +658,8 @@ class Exchange:
'timestamp': arrow.utcnow().int_timestamp * 1000, 'timestamp': arrow.utcnow().int_timestamp * 1000,
'status': "closed" if ordertype == "market" else "open", 'status': "closed" if ordertype == "market" else "open",
'fee': None, 'fee': None,
'info': {} 'info': {},
'leverage': leverage
} }
if dry_order["type"] in ["stop_loss_limit", "stop-loss-limit"]: if dry_order["type"] in ["stop_loss_limit", "stop-loss-limit"]:
dry_order["info"] = {"stopPrice": dry_order["price"]} dry_order["info"] = {"stopPrice": dry_order["price"]}
@ -663,7 +669,7 @@ class Exchange:
average = self.get_dry_market_fill_price(pair, side, amount, rate) average = self.get_dry_market_fill_price(pair, side, amount, rate)
dry_order.update({ dry_order.update({
'average': average, 'average': average,
'cost': dry_order['amount'] * average, 'cost': (dry_order['amount'] * average) / leverage
}) })
dry_order = self.add_dry_order_fee(pair, dry_order) dry_order = self.add_dry_order_fee(pair, dry_order)
@ -771,7 +777,7 @@ class Exchange:
# Order handling # Order handling
def lev_prep(self, pair: str, leverage: float): def _lev_prep(self, pair: str, leverage: float):
self.set_margin_mode(pair, self.collateral) self.set_margin_mode(pair, self.collateral)
self._set_leverage(leverage, pair) self._set_leverage(leverage, pair)
@ -783,14 +789,14 @@ class Exchange:
return params return params
def create_order(self, pair: str, ordertype: str, side: str, amount: float, def create_order(self, pair: str, ordertype: str, side: str, amount: float,
rate: float, time_in_force: str = 'gtc', leverage=1.0) -> Dict: rate: float, leverage: float = 1.0, time_in_force: str = 'gtc') -> Dict:
# TODO-lev: remove default for leverage
if self._config['dry_run']: if self._config['dry_run']:
dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate) dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate, leverage)
return dry_order return dry_order
if self.trading_mode != TradingMode.SPOT: if self.trading_mode != TradingMode.SPOT:
self.lev_prep(pair, leverage) self._lev_prep(pair, leverage)
params = self._get_params(time_in_force, ordertype, leverage) params = self._get_params(time_in_force, ordertype, leverage)
@ -831,8 +837,8 @@ class Exchange:
""" """
raise OperationalException(f"stoploss is not implemented for {self.name}.") raise OperationalException(f"stoploss is not implemented for {self.name}.")
def stoploss(self, pair: str, amount: float, def stoploss(self, pair: str, amount: float, stop_price: float,
stop_price: float, order_types: Dict, side: str) -> Dict: order_types: Dict, side: str, leverage: float) -> Dict:
""" """
creates a stoploss order. creates a stoploss order.
The precise ordertype is determined by the order_types dict or exchange default. The precise ordertype is determined by the order_types dict or exchange default.
@ -1595,15 +1601,13 @@ class Exchange:
self._async_get_trade_history(pair=pair, since=since, self._async_get_trade_history(pair=pair, since=since,
until=until, from_id=from_id)) until=until, from_id=from_id))
@retrier
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
""" """
raise OperationalException( return
f"{self.name.capitalize()}.fill_leverage_brackets has not been implemented.")
def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float: def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float:
""" """
@ -1624,7 +1628,9 @@ class Exchange:
Set's the leverage before making a trade, in order to not Set's the leverage before making a trade, in order to not
have the same leverage on every trade have the same leverage on every trade
""" """
if not self.exchange_has("setLeverage"): # TODO-lev: Make a documentation page that says you can't run 2 bots
# TODO-lev: on the same account with leverage
if self._config['dry_run'] or not self.exchange_has("setLeverage"):
# Some exchanges only support one collateral type # Some exchanges only support one collateral type
return return
@ -1644,7 +1650,7 @@ class Exchange:
Set's the margin mode on the exchange to cross or isolated for a specific pair Set's the margin mode on the exchange to cross or isolated for a specific pair
:param symbol: base/quote currency pair (e.g. "ADA/USDT") :param symbol: base/quote currency pair (e.g. "ADA/USDT")
''' '''
if not self.exchange_has("setMarginMode"): if self._config['dry_run'] or not self.exchange_has("setMarginMode"):
# Some exchanges only support one collateral type # Some exchanges only support one collateral type
return return

View File

@ -49,8 +49,8 @@ class Ftx(Exchange):
) )
@retrier(retries=0) @retrier(retries=0)
def stoploss(self, pair: str, amount: float, def stoploss(self, pair: str, amount: float, stop_price: float,
stop_price: float, order_types: Dict, side: str) -> Dict: order_types: Dict, side: str, leverage: float) -> Dict:
""" """
Creates a stoploss order. Creates a stoploss order.
depending on order_types.stoploss configuration, uses 'market' or limit order. depending on order_types.stoploss configuration, uses 'market' or limit order.
@ -69,7 +69,7 @@ class Ftx(Exchange):
if self._config['dry_run']: if self._config['dry_run']:
dry_order = self.create_dry_run_order( dry_order = self.create_dry_run_order(
pair, ordertype, side, amount, stop_price) pair, ordertype, side, amount, stop_price, leverage)
return dry_order return dry_order
try: try:
@ -81,8 +81,14 @@ class Ftx(Exchange):
params['stopPrice'] = stop_price params['stopPrice'] = stop_price
amount = self.amount_to_precision(pair, amount) amount = self.amount_to_precision(pair, amount)
order = self._api.create_order(symbol=pair, type=ordertype, side=side, order = self._api.create_order(
amount=amount, params=params) symbol=pair,
type=ordertype,
side=side,
amount=amount,
leverage=leverage,
params=params
)
self._log_exchange_response('create_stoploss_order', order) self._log_exchange_response('create_stoploss_order', order)
logger.info('stoploss order added for %s. ' logger.info('stoploss order added for %s. '
'stop price: %s.', pair, stop_price) 'stop price: %s.', pair, stop_price)

View File

@ -85,8 +85,8 @@ class Kraken(Exchange):
)) ))
@retrier(retries=0) @retrier(retries=0)
def stoploss(self, pair: str, amount: float, def stoploss(self, pair: str, amount: float, stop_price: float,
stop_price: float, order_types: Dict, side: str) -> Dict: order_types: Dict, side: str, leverage: float) -> Dict:
""" """
Creates a stoploss market order. Creates a stoploss market order.
Stoploss market orders is the only stoploss type supported by kraken. Stoploss market orders is the only stoploss type supported by kraken.
@ -108,14 +108,21 @@ class Kraken(Exchange):
if self._config['dry_run']: if self._config['dry_run']:
dry_order = self.create_dry_run_order( dry_order = self.create_dry_run_order(
pair, ordertype, side, amount, stop_price) pair, ordertype, side, amount, stop_price, leverage)
return dry_order return dry_order
try: try:
amount = self.amount_to_precision(pair, amount) amount = self.amount_to_precision(pair, amount)
order = self._api.create_order(symbol=pair, type=ordertype, side=side, order = self._api.create_order(
amount=amount, price=stop_price, params=params) symbol=pair,
type=ordertype,
side=side,
amount=amount,
price=stop_price,
leverage=leverage,
params=params
)
self._log_exchange_response('create_stoploss_order', order) self._log_exchange_response('create_stoploss_order', order)
logger.info('stoploss order added for %s. ' logger.info('stoploss order added for %s. '
'stop price: %s.', pair, stop_price) 'stop price: %s.', pair, stop_price)

View File

@ -737,7 +737,8 @@ class FreqtradeBot(LoggingMixin):
amount=trade.amount, amount=trade.amount,
stop_price=stop_price, stop_price=stop_price,
order_types=self.strategy.order_types, order_types=self.strategy.order_types,
side=trade.exit_side side=trade.exit_side,
leverage=trade.leverage
) )
order_obj = Order.parse_from_ccxt_object(stoploss_order, trade.pair, 'stoploss') order_obj = Order.parse_from_ccxt_object(stoploss_order, trade.pair, 'stoploss')

View File

@ -48,13 +48,20 @@ def test_stoploss_order_binance(
amount=1, amount=1,
stop_price=190, stop_price=190,
side=side, side=side,
order_types={'stoploss_on_exchange_limit_ratio': 1.05} order_types={'stoploss_on_exchange_limit_ratio': 1.05},
leverage=1.0
) )
api_mock.create_order.reset_mock() api_mock.create_order.reset_mock()
order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio} order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio}
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order = exchange.stoploss(
order_types=order_types, side=side) pair='ETH/BTC',
amount=1,
stop_price=220,
order_types=order_types,
side=side,
leverage=1.0
)
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order
@ -71,17 +78,31 @@ def test_stoploss_order_binance(
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0)
with pytest.raises(InvalidOrderException): with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock( api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("binance Order would trigger immediately.")) side_effect=ccxt.InvalidOrder("binance Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance", ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance",
"stoploss", "create_order", retries=1, "stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) pair='ETH/BTC', amount=1, stop_price=220, order_types={},
side=side, leverage=1.0)
def test_stoploss_order_dry_run_binance(default_conf, mocker): def test_stoploss_order_dry_run_binance(default_conf, mocker):
@ -94,12 +115,25 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side="sell", order = exchange.stoploss(
order_types={'stoploss_on_exchange_limit_ratio': 1.05}) pair='ETH/BTC',
amount=1,
stop_price=190,
side="sell",
order_types={'stoploss_on_exchange_limit_ratio': 1.05},
leverage=1.0
)
api_mock.create_order.reset_mock() api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side="sell",
leverage=1.0
)
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order

View File

@ -403,7 +403,6 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
# With Leverage # With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss, 3.0) result = exchange.get_min_pair_stake_amount('ETH/BTC', 1, stoploss, 3.0)
assert isclose(result, expected_result/3) assert isclose(result, expected_result/3)
# TODO-lev: Min stake for base, kraken and ftx
# min amount is set # min amount is set
markets["ETH/BTC"]["limits"] = { markets["ETH/BTC"]["limits"] = {
@ -420,7 +419,6 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
# With Leverage # With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 5.0) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 5.0)
assert isclose(result, expected_result/5) assert isclose(result, expected_result/5)
# TODO-lev: Min stake for base, kraken and ftx
# min amount and cost are set (cost is minimal) # min amount and cost are set (cost is minimal)
markets["ETH/BTC"]["limits"] = { markets["ETH/BTC"]["limits"] = {
@ -437,7 +435,6 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
# With Leverage # With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 10) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 10)
assert isclose(result, expected_result/10) assert isclose(result, expected_result/10)
# TODO-lev: Min stake for base, kraken and ftx
# min amount and cost are set (amount is minial) # min amount and cost are set (amount is minial)
markets["ETH/BTC"]["limits"] = { markets["ETH/BTC"]["limits"] = {
@ -454,7 +451,6 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
# With Leverage # With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 7.0) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, stoploss, 7.0)
assert isclose(result, expected_result/7.0) assert isclose(result, expected_result/7.0)
# TODO-lev: Min stake for base, kraken and ftx
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4)
expected_result = max(8, 2 * 2) * 1.5 expected_result = max(8, 2 * 2) * 1.5
@ -462,7 +458,6 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
# With Leverage # With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4, 8.0) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -0.4, 8.0)
assert isclose(result, expected_result/8.0) assert isclose(result, expected_result/8.0)
# TODO-lev: Min stake for base, kraken and ftx
# Really big stoploss # Really big stoploss
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1)
@ -471,7 +466,6 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
# With Leverage # With Leverage
result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0) result = exchange.get_min_pair_stake_amount('ETH/BTC', 2, -1, 12.0)
assert isclose(result, expected_result/12) assert isclose(result, expected_result/12)
# TODO-lev: Min stake for base, kraken and ftx
def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None: def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None:
@ -493,7 +487,6 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None:
assert round(result, 8) == round(expected_result, 8) assert round(result, 8) == round(expected_result, 8)
result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0) result = exchange.get_min_pair_stake_amount('ETH/BTC', 0.020405, stoploss, 3.0)
assert round(result, 8) == round(expected_result/3, 8) assert round(result, 8) == round(expected_result/3, 8)
# TODO-lev: Min stake for base, kraken and ftx
def test_set_sandbox(default_conf, mocker): def test_set_sandbox(default_conf, mocker):
@ -1004,7 +997,13 @@ def test_create_dry_run_order(default_conf, mocker, side, exchange_name):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
order = exchange.create_dry_run_order( order = exchange.create_dry_run_order(
pair='ETH/BTC', ordertype='limit', side=side, amount=1, rate=200) pair='ETH/BTC',
ordertype='limit',
side=side,
amount=1,
rate=200,
leverage=1.0
)
assert 'id' in order assert 'id' in order
assert f'dry_run_{side}_' in order["id"] assert f'dry_run_{side}_' in order["id"]
assert order["side"] == side assert order["side"] == side
@ -1027,7 +1026,13 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, startprice,
) )
order = exchange.create_dry_run_order( order = exchange.create_dry_run_order(
pair='LTC/USDT', ordertype='limit', side=side, amount=1, rate=startprice) pair='LTC/USDT',
ordertype='limit',
side=side,
amount=1,
rate=startprice,
leverage=1.0
)
assert order_book_l2_usd.call_count == 1 assert order_book_l2_usd.call_count == 1
assert 'id' in order assert 'id' in order
assert f'dry_run_{side}_' in order["id"] assert f'dry_run_{side}_' in order["id"]
@ -1073,7 +1078,13 @@ def test_create_dry_run_order_market_fill(default_conf, mocker, side, rate, amou
) )
order = exchange.create_dry_run_order( order = exchange.create_dry_run_order(
pair='LTC/USDT', ordertype='market', side=side, amount=amount, rate=rate) pair='LTC/USDT',
ordertype='market',
side=side,
amount=amount,
rate=rate,
leverage=1.0
)
assert 'id' in order assert 'id' in order
assert f'dry_run_{side}_' in order["id"] assert f'dry_run_{side}_' in order["id"]
assert order["side"] == side assert order["side"] == side
@ -2664,7 +2675,14 @@ def test_get_fee(default_conf, mocker, exchange_name):
def test_stoploss_order_unsupported_exchange(default_conf, mocker): def test_stoploss_order_unsupported_exchange(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, id='bittrex') exchange = get_patched_exchange(mocker, default_conf, id='bittrex')
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell") exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side="sell",
leverage=1.0
)
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
exchange.stoploss_adjust(1, {}, side="sell") exchange.stoploss_adjust(1, {}, side="sell")
@ -3024,7 +3042,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_apply_leverage_to_stake_amount( def test_divide_stake_amount_by_leverage(
exchange, exchange,
stake_amount, stake_amount,
leverage, leverage,
@ -3033,7 +3051,7 @@ def test_apply_leverage_to_stake_amount(
default_conf default_conf
): ):
exchange = get_patched_exchange(mocker, default_conf, id=exchange) exchange = get_patched_exchange(mocker, default_conf, id=exchange)
assert exchange._apply_leverage_to_stake_amount(stake_amount, leverage) == min_stake_with_lev assert exchange._divide_stake_amount_by_leverage(stake_amount, leverage) == min_stake_with_lev
@pytest.mark.parametrize("exchange_name,trading_mode", [ @pytest.mark.parametrize("exchange_name,trading_mode", [

View File

@ -1,10 +1,9 @@
from random import randint from random import randint
from unittest.mock import MagicMock, PropertyMock from unittest.mock import MagicMock
import ccxt import ccxt
import pytest import pytest
from freqtrade.enums import TradingMode
from freqtrade.exceptions import DependencyException, InvalidOrderException from freqtrade.exceptions import DependencyException, InvalidOrderException
from freqtrade.exchange.common import API_FETCH_ORDER_RETRY_COUNT from freqtrade.exchange.common import API_FETCH_ORDER_RETRY_COUNT
from tests.conftest import get_patched_exchange from tests.conftest import get_patched_exchange
@ -14,8 +13,6 @@ from .test_exchange import ccxt_exceptionhandlers
STOPLOSS_ORDERTYPE = 'stop' STOPLOSS_ORDERTYPE = 'stop'
# TODO-lev: All these stoploss tests with shorts
@pytest.mark.parametrize('order_price,exchangelimitratio,side', [ @pytest.mark.parametrize('order_price,exchangelimitratio,side', [
(217.8, 1.05, "sell"), (217.8, 1.05, "sell"),
@ -39,8 +36,14 @@ def test_stoploss_order_ftx(default_conf, mocker, order_price, exchangelimitrati
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
# stoploss_on_exchange_limit_ratio is irrelevant for ftx market orders # stoploss_on_exchange_limit_ratio is irrelevant for ftx market orders
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side=side, order = exchange.stoploss(
order_types={'stoploss_on_exchange_limit_ratio': exchangelimitratio}) pair='ETH/BTC',
amount=1,
stop_price=190,
side=side,
order_types={'stoploss_on_exchange_limit_ratio': exchangelimitratio},
leverage=1.0
)
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE assert api_mock.create_order.call_args_list[0][1]['type'] == STOPLOSS_ORDERTYPE
@ -54,7 +57,14 @@ def test_stoploss_order_ftx(default_conf, mocker, order_price, exchangelimitrati
api_mock.create_order.reset_mock() api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order
@ -67,8 +77,13 @@ def test_stoploss_order_ftx(default_conf, mocker, order_price, exchangelimitrati
assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220 assert api_mock.create_order.call_args_list[0][1]['params']['stopPrice'] == 220
api_mock.create_order.reset_mock() api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order = exchange.stoploss(
order_types={'stoploss': 'limit'}, side=side) pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={'stoploss': 'limit'}, side=side,
leverage=1.0
)
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order
@ -85,17 +100,32 @@ def test_stoploss_order_ftx(default_conf, mocker, order_price, exchangelimitrati
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
with pytest.raises(InvalidOrderException): with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock( api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("ftx Order would trigger immediately.")) side_effect=ccxt.InvalidOrder("ftx Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "ftx", ccxt_exceptionhandlers(mocker, default_conf, api_mock, "ftx",
"stoploss", "create_order", retries=1, "stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) pair='ETH/BTC', amount=1, stop_price=220, order_types={},
side=side, leverage=1.0)
@pytest.mark.parametrize('side', [("sell"), ("buy")]) @pytest.mark.parametrize('side', [("sell"), ("buy")])
@ -109,7 +139,14 @@ def test_stoploss_order_dry_run_ftx(default_conf, mocker, side):
api_mock.create_order.reset_mock() api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order
@ -230,26 +267,3 @@ def test_fill_leverage_brackets_ftx(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, id="ftx") exchange = get_patched_exchange(mocker, default_conf, id="ftx")
exchange.fill_leverage_brackets() exchange.fill_leverage_brackets()
assert exchange._leverage_brackets == {} assert exchange._leverage_brackets == {}
@pytest.mark.parametrize("trading_mode", [
(TradingMode.MARGIN),
(TradingMode.FUTURES)
])
def test__set_leverage(mocker, default_conf, trading_mode):
api_mock = MagicMock()
api_mock.set_leverage = MagicMock()
type(api_mock).has = PropertyMock(return_value={'setLeverage': True})
ccxt_exceptionhandlers(
mocker,
default_conf,
api_mock,
"ftx",
"_set_leverage",
"set_leverage",
pair="XRP/USDT",
leverage=5.0,
trading_mode=trading_mode
)

View File

@ -195,7 +195,9 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedpr
order_types={ order_types={
'stoploss': ordertype, 'stoploss': ordertype,
'stoploss_on_exchange_limit_ratio': 0.99 'stoploss_on_exchange_limit_ratio': 0.99
}) },
leverage=1.0
)
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order
@ -219,17 +221,32 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype, side, adjustedpr
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
with pytest.raises(InvalidOrderException): with pytest.raises(InvalidOrderException):
api_mock.create_order = MagicMock( api_mock.create_order = MagicMock(
side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately.")) side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately."))
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken') exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken", ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken",
"stoploss", "create_order", retries=1, "stoploss", "create_order", retries=1,
pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) pair='ETH/BTC', amount=1, stop_price=220, order_types={},
side=side, leverage=1.0)
@pytest.mark.parametrize('side', ['buy', 'sell']) @pytest.mark.parametrize('side', ['buy', 'sell'])
@ -243,7 +260,14 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker, side):
api_mock.create_order.reset_mock() api_mock.create_order.reset_mock()
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side=side) order = exchange.stoploss(
pair='ETH/BTC',
amount=1,
stop_price=220,
order_types={},
side=side,
leverage=1.0
)
assert 'id' in order assert 'id' in order
assert 'info' in order assert 'info' in order

View File

@ -1349,7 +1349,8 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee,
pair='ETH/BTC', pair='ETH/BTC',
order_types=freqtrade.strategy.order_types, order_types=freqtrade.strategy.order_types,
stop_price=0.00002346 * 0.95, stop_price=0.00002346 * 0.95,
side="sell" side="sell",
leverage=1.0
) )
# price fell below stoploss, so dry-run sells trade. # price fell below stoploss, so dry-run sells trade.
@ -1537,7 +1538,8 @@ def test_handle_stoploss_on_exchange_custom_stop(mocker, default_conf, fee,
pair='ETH/BTC', pair='ETH/BTC',
order_types=freqtrade.strategy.order_types, order_types=freqtrade.strategy.order_types,
stop_price=0.00002346 * 0.96, stop_price=0.00002346 * 0.96,
side="sell" side="sell",
leverage=1.0
) )
# price fell below stoploss, so dry-run sells trade. # price fell below stoploss, so dry-run sells trade.
@ -1661,7 +1663,8 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
pair='NEO/BTC', pair='NEO/BTC',
order_types=freqtrade.strategy.order_types, order_types=freqtrade.strategy.order_types,
stop_price=0.00002346 * 0.99, stop_price=0.00002346 * 0.99,
side="sell" side="sell",
leverage=1.0
) )