Implemented fill_leverage_brackets get_max_leverage and set_leverage for binance, kraken and ftx. Wrote tests test_apply_leverage_to_stake_amount and test_get_max_leverage

This commit is contained in:
Sam Germain 2021-08-20 02:40:22 -06:00
parent 455bcf5389
commit 134a7ec59b
6 changed files with 245 additions and 23 deletions

View File

@ -1,6 +1,6 @@
""" Binance exchange subclass """
import logging
from typing import Dict
from typing import Dict, Optional
import ccxt
@ -96,5 +96,43 @@ class Binance(Exchange):
except ccxt.BaseError as e:
raise OperationalException(e) from e
def apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float):
def _apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float):
return stake_amount / leverage
def fill_leverage_brackets(self):
"""
Assigns property _leverage_brackets to a dictionary of information about the leverage
allowed on each pair
"""
leverage_brackets = self._api.load_leverage_brackets()
for pair, brackets in leverage_brackets.items:
self.leverage_brackets[pair] = [
[
min_amount,
float(margin_req)
] for [
min_amount,
margin_req
] in brackets
]
def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float:
"""
Returns the maximum leverage that a pair can be traded at
:param pair: The base/quote currency pair being traded
:nominal_value: The total value of the trade in quote currency (collateral + debt)
"""
pair_brackets = self._leverage_brackets[pair]
max_lev = 1.0
for [min_amount, margin_req] in pair_brackets:
print(nominal_value, min_amount)
if nominal_value >= min_amount:
max_lev = 1/margin_req
return max_lev
def set_leverage(self, pair, leverage):
"""
Binance Futures must set the leverage before making a futures trade, in order to not
have the same leverage on every trade
"""
self._api.set_leverage(symbol=pair, leverage=leverage)

View File

@ -74,6 +74,8 @@ class Exchange:
}
_ft_has: Dict = {}
_leverage_brackets: Dict
def __init__(self, config: Dict[str, Any], validate: bool = True) -> None:
"""
Initializes this module with the given config,
@ -161,6 +163,16 @@ class Exchange:
self.markets_refresh_interval: int = exchange_config.get(
"markets_refresh_interval", 60) * 60
leverage = config.get('leverage_mode')
if leverage is not False:
try:
# TODO-lev: This shouldn't need to happen, but for some reason I get that the
# TODO-lev: method isn't implemented
self.fill_leverage_brackets()
except Exception as error:
logger.debug(error)
logger.debug("Could not load leverage_brackets")
def __del__(self):
"""
Destructor - clean up async stuff
@ -355,6 +367,7 @@ class Exchange:
# Also reload async markets to avoid issues with newly listed pairs
self._load_async_markets(reload=True)
self._last_markets_refresh = arrow.utcnow().int_timestamp
self.fill_leverage_brackets()
except ccxt.BaseError:
logger.exception("Could not reload markets.")
@ -577,12 +590,12 @@ class Exchange:
# The value returned should satisfy both limits: for amount (base currency) and
# for cost (quote, stake currency), so max() is used here.
# See also #2575 at github.
return self.apply_leverage_to_stake_amount(
return self._apply_leverage_to_stake_amount(
max(min_stake_amounts) * amount_reserve_percent,
leverage or 1.0
)
def apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float):
def _apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float):
"""
# * 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
@ -728,14 +741,6 @@ class Exchange:
raise InvalidOrderException(
f'Tried to get an invalid dry-run-order (id: {order_id}). Message: {e}') from e
def get_max_leverage(self, pair: str, stake_amount: float, price: float) -> float:
"""
Gets the maximum leverage available on this pair
"""
raise OperationalException(f"Leverage is not available on {self.name} using freqtrade")
return 1.0
# Order handling
def create_order(self, pair: str, ordertype: str, side: str, amount: float,
@ -1553,13 +1558,32 @@ class Exchange:
# TODO-lev: implement
return 0.0005
def fill_leverage_brackets(self):
"""
#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
allowed on each pair
"""
raise OperationalException(
f"{self.name.capitalize()}.fill_leverage_brackets has not been implemented.")
def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float:
"""
Returns the maximum leverage that a pair can be traded at
:param pair: The base/quote currency pair being traded
:nominal_value: The total value of the trade in quote currency (collateral + debt)
"""
raise OperationalException(
f"{self.name.capitalize()}.get_max_leverage has not been implemented.")
def set_leverage(self, pair, leverage):
"""
Binance Futures must set the leverage before making a futures trade, in order to not
Set's the leverage before making a trade, in order to not
have the same leverage on every trade
# TODO-lev: This may be the case for any futures exchange, or even margin trading on
# TODO-lev: some exchanges, so check this
"""
raise OperationalException(
f"{self.name.capitalize()}.set_leverage has not been implemented.")
self._api.set_leverage(symbol=pair, leverage=leverage)

View File

@ -1,6 +1,6 @@
""" FTX exchange subclass """
import logging
from typing import Any, Dict
from typing import Any, Dict, Optional
import ccxt
@ -156,3 +156,30 @@ class Ftx(Exchange):
if order['type'] == 'stop':
return safe_value_fallback2(order, order, 'id_stop', 'id')
return order['id']
def _apply_leverage_to_stake_amount(self, stake_amount: float, leverage: float):
# TODO-lev: implement
return stake_amount
def fill_leverage_brackets(self):
"""
FTX leverage is static across the account, and doesn't change from pair to pair,
so _leverage_brackets doesn't need to be set
"""
return
def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float:
"""
Returns the maximum leverage that a pair can be traded at, which is always 20 on ftx
:param pair: Here for super method, not used on FTX
:nominal_value: Here for super method, not used on FTX
"""
return 20.0
def set_leverage(self, pair, leverage):
"""
Sets the leverage used for the user's account
:param pair: Here for super method, not used on FTX
:param leverage:
"""
self._api.private_post_account_leverage({'leverage': leverage})

View File

@ -1,6 +1,6 @@
""" Kraken exchange subclass """
import logging
from typing import Any, Dict
from typing import Any, Dict, Optional
import ccxt
@ -127,3 +127,42 @@ class Kraken(Exchange):
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
def fill_leverage_brackets(self):
"""
Assigns property _leverage_brackets to a dictionary of information about the leverage
allowed on each pair
"""
# TODO-lev: Not sure if this works correctly for futures
leverages = {}
for pair, market in self._api.load_markets().items():
info = market['info']
leverage_buy = info['leverage_buy']
leverage_sell = info['leverage_sell']
if len(info['leverage_buy']) > 0 or len(info['leverage_sell']) > 0:
if leverage_buy != leverage_sell:
print(f"\033[91m The buy leverage != the sell leverage for {pair}."
"please let freqtrade know because this has never happened before"
)
if max(leverage_buy) < max(leverage_sell):
leverages[pair] = leverage_buy
else:
leverages[pair] = leverage_sell
else:
leverages[pair] = leverage_buy
self._leverage_brackets = leverages
def get_max_leverage(self, pair: Optional[str], nominal_value: Optional[float]) -> float:
"""
Returns the maximum leverage that a pair can be traded at
:param pair: The base/quote currency pair being traded
:nominal_value: Here for super class, not needed on Kraken
"""
return float(max(self._leverage_brackets[pair]))
def set_leverage(self, pair, leverage):
"""
Kraken set's the leverage as an option it the order object, so it doesn't do
anything in this function
"""
return

View File

@ -106,3 +106,48 @@ def test_stoploss_adjust_binance(mocker, default_conf):
# Test with invalid order case
order['type'] = 'stop_loss'
assert not exchange.stoploss_adjust(1501, order, side="sell")
@pytest.mark.parametrize('pair,nominal_value,max_lev', [
("BNB/BUSD", 0.0, 40.0),
("BNB/USDT", 100.0, 153.84615384615384),
("BTC/USDT", 170.30, 250.0),
("BNB/BUSD", 999999.9, 10.0),
("BNB/USDT", 5000000.0, 6.666666666666667),
("BTC/USDT", 300000000.1, 2.0),
])
def test_get_max_leverage_binance(
default_conf,
mocker,
pair,
nominal_value,
max_lev
):
exchange = get_patched_exchange(mocker, default_conf, id="binance")
exchange._leverage_brackets = {
'BNB/BUSD': [[0.0, 0.025],
[100000.0, 0.05],
[500000.0, 0.1],
[1000000.0, 0.15],
[2000000.0, 0.25],
[5000000.0, 0.5]],
'BNB/USDT': [[0.0, 0.0065],
[10000.0, 0.01],
[50000.0, 0.02],
[250000.0, 0.05],
[1000000.0, 0.1],
[2000000.0, 0.125],
[5000000.0, 0.15],
[10000000.0, 0.25]],
'BTC/USDT': [[0.0, 0.004],
[50000.0, 0.005],
[250000.0, 0.01],
[1000000.0, 0.025],
[5000000.0, 0.05],
[20000000.0, 0.1],
[50000000.0, 0.125],
[100000000.0, 0.15],
[200000000.0, 0.25],
[300000000.0, 0.5]],
}
assert exchange.get_max_leverage(pair, nominal_value) == max_lev

View File

@ -449,11 +449,6 @@ def test_get_min_pair_stake_amount_real_data(mocker, default_conf) -> None:
)
def apply_leverage_to_stake_amount():
# TODO-lev
return
def test_set_sandbox(default_conf, mocker):
"""
Test working scenario
@ -2944,7 +2939,61 @@ def test_calculate_backoff(retrycount, max_retries, expected):
assert calculate_backoff(retrycount, max_retries) == expected
def test_get_max_leverage():
@pytest.mark.parametrize('exchange,stake_amount,leverage,min_stake_with_lev', [
('binance', 9.0, 3.0, 3.0),
('binance', 20.0, 5.0, 4.0),
('binance', 100.0, 100.0, 1.0),
# Kraken
('kraken', 9.0, 3.0, 9.0),
('kraken', 20.0, 5.0, 20.0),
('kraken', 100.0, 100.0, 100.0),
# FTX
# TODO-lev: - implement FTX tests
# ('ftx', 9.0, 3.0, 10.0),
# ('ftx', 20.0, 5.0, 20.0),
# ('ftx', 100.0, 100.0, 100.0),
])
def test_apply_leverage_to_stake_amount(
exchange,
stake_amount,
leverage,
min_stake_with_lev,
mocker,
default_conf
):
exchange = get_patched_exchange(mocker, default_conf, id=exchange)
assert exchange._apply_leverage_to_stake_amount(stake_amount, leverage) == min_stake_with_lev
@pytest.mark.parametrize('exchange_name,pair,nominal_value,max_lev', [
# Kraken
("kraken", "ADA/BTC", 0.0, 3.0),
("kraken", "BTC/EUR", 100.0, 5.0),
("kraken", "ZEC/USD", 173.31, 2.0),
# FTX
("ftx", "ADA/BTC", 0.0, 20.0),
("ftx", "BTC/EUR", 100.0, 20.0),
("ftx", "ZEC/USD", 173.31, 20.0),
# Binance tests this method inside it's own test file
])
def test_get_max_leverage(
default_conf,
mocker,
exchange_name,
pair,
nominal_value,
max_lev
):
exchange = get_patched_exchange(mocker, default_conf, id=exchange_name)
exchange._leverage_brackets = {
'ADA/BTC': ['2', '3'],
'BTC/EUR': ['2', '3', '4', '5'],
'ZEC/USD': ['2']
}
assert exchange.get_max_leverage(pair, nominal_value) == max_lev
def test_fill_leverage_brackets():
# TODO-lev
return