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

View File

@@ -1,5 +1,7 @@
""" Binance exchange subclass """
import json
import logging
from pathlib import Path
from typing import Dict, List, Optional, Tuple
import arrow
@@ -47,8 +49,8 @@ class Binance(Exchange):
)
@retrier(retries=0)
def stoploss(self, pair: str, amount: float,
stop_price: float, order_types: Dict, side: str) -> Dict:
def stoploss(self, pair: str, amount: float, stop_price: float,
order_types: Dict, side: str, leverage: float) -> Dict:
"""
creates a stoploss limit order.
this stoploss-limit is binance-specific.
@@ -76,7 +78,7 @@ class Binance(Exchange):
if self._config['dry_run']:
dry_order = self.create_dry_run_order(
pair, ordertype, side, amount, stop_price)
pair, ordertype, side, amount, stop_price, leverage)
return dry_order
try:
@@ -87,8 +89,15 @@ class Binance(Exchange):
rate = self.price_to_precision(pair, rate)
order = self._api.create_order(symbol=pair, type=ordertype, side=side,
amount=amount, price=rate, params=params)
order = self._api.create_order(
symbol=pair,
type=ordertype,
side=side,
amount=amount,
price=rate,
params=params,
leverage=leverage
)
logger.info('stoploss limit order added for %s. '
'stop price: %s. limit: %s', pair, stop_price, rate)
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
allowed on each pair
"""
try:
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
]
if self.trading_mode == TradingMode.FUTURES:
try:
if self._config['dry_run']:
leverage_brackets_path = Path('data') / 'leverage_brackets.json'
with open(leverage_brackets_path) as json_file:
leverage_brackets = json.load(json_file)
else:
leverage_brackets = self._api.load_leverage_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
for pair, brackets in leverage_brackets.items():
self._leverage_brackets[pair] = [
[
min_amount,
float(margin_req)
] for [
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:
"""
@@ -153,10 +169,6 @@ class Binance(Exchange):
max_lev = 1/margin_req
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
def _set_leverage(
self,
@@ -170,9 +182,11 @@ class Binance(Exchange):
"""
trading_mode = trading_mode or self.trading_mode
if self._config['dry_run'] or trading_mode != TradingMode.FUTURES:
return
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:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:

View File

@@ -258,6 +258,13 @@ class Exchange:
"""exchange ccxt 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:
""" Log exchange responses """
if self.log_responses:
@@ -617,15 +624,13 @@ 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._divide_stake_amount_by_leverage(
max(min_stake_amounts) * amount_reserve_percent,
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
stake amount when 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
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()}'
_amount = self.amount_to_precision(pair, amount)
dry_order: Dict[str, Any] = {
@@ -653,7 +658,8 @@ class Exchange:
'timestamp': arrow.utcnow().int_timestamp * 1000,
'status': "closed" if ordertype == "market" else "open",
'fee': None,
'info': {}
'info': {},
'leverage': leverage
}
if dry_order["type"] in ["stop_loss_limit", "stop-loss-limit"]:
dry_order["info"] = {"stopPrice": dry_order["price"]}
@@ -663,7 +669,7 @@ class Exchange:
average = self.get_dry_market_fill_price(pair, side, amount, rate)
dry_order.update({
'average': average,
'cost': dry_order['amount'] * average,
'cost': (dry_order['amount'] * average) / leverage
})
dry_order = self.add_dry_order_fee(pair, dry_order)
@@ -771,7 +777,7 @@ class Exchange:
# 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_leverage(leverage, pair)
@@ -783,14 +789,14 @@ class Exchange:
return params
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']:
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
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)
@@ -831,8 +837,8 @@ class Exchange:
"""
raise OperationalException(f"stoploss is not implemented for {self.name}.")
def stoploss(self, pair: str, amount: float,
stop_price: float, order_types: Dict, side: str) -> Dict:
def stoploss(self, pair: str, amount: float, stop_price: float,
order_types: Dict, side: str, leverage: float) -> Dict:
"""
creates a stoploss order.
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,
until=until, from_id=from_id))
@retrier
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.")
return
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
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
return
@@ -1644,7 +1650,7 @@ class Exchange:
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")
'''
if not self.exchange_has("setMarginMode"):
if self._config['dry_run'] or not self.exchange_has("setMarginMode"):
# Some exchanges only support one collateral type
return

View File

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

View File

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