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:
parent
180d92f879
commit
97bb555d41
@ -1,6 +1,6 @@
|
||||
""" Binance exchange subclass """
|
||||
import logging
|
||||
from typing import Dict
|
||||
from typing import Dict, Optional
|
||||
|
||||
import ccxt
|
||||
|
||||
@ -95,5 +95,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)
|
||||
|
@ -69,6 +69,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,
|
||||
@ -156,6 +158,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
|
||||
@ -346,6 +358,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.")
|
||||
|
||||
@ -561,12 +574,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
|
||||
@ -701,14 +714,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,
|
||||
@ -1520,13 +1525,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)
|
||||
|
||||
|
||||
|
@ -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})
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -442,11 +442,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
|
||||
@ -2893,7 +2888,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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user