merged lev-freqtradebot with lev-strat
This commit is contained in:
@@ -20,4 +20,7 @@ class Bibox(Exchange):
|
||||
|
||||
# fetchCurrencies API point requires authentication for Bibox,
|
||||
# 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}}
|
||||
|
@@ -1,5 +1,7 @@
|
||||
""" Binance exchange subclass """
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
import arrow
|
||||
@@ -31,9 +33,27 @@ class Binance(Exchange):
|
||||
# TradingMode.SPOT always supported and not required in this list
|
||||
# (TradingMode.MARGIN, Collateral.CROSS), # TODO-lev: Uncomment once supported
|
||||
# (TradingMode.FUTURES, Collateral.CROSS), # TODO-lev: Uncomment once supported
|
||||
# (TradingMode.FUTURES, Collateral.ISOLATED) # TODO-lev: Uncomment once supported
|
||||
# (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:
|
||||
"""
|
||||
Verify stop_loss against stoploss-order value (limit or price)
|
||||
@@ -47,8 +67,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 +96,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,6 +107,7 @@ class Binance(Exchange):
|
||||
|
||||
rate = self.price_to_precision(pair, rate)
|
||||
|
||||
self._lev_prep(pair, leverage)
|
||||
order = self._api.create_order(symbol=pair, type=ordertype, side=side,
|
||||
amount=amount, price=rate, params=params)
|
||||
logger.info('stoploss limit order added for %s. '
|
||||
@@ -119,26 +140,35 @@ 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(__file__).parent / 'binance_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:
|
||||
"""
|
||||
@@ -166,9 +196,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:
|
||||
|
1214
freqtrade/exchange/binance_leverage_brackets.json
Normal file
1214
freqtrade/exchange/binance_leverage_brackets.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -49,9 +49,6 @@ class Exchange:
|
||||
|
||||
_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)
|
||||
_params: Dict = {}
|
||||
|
||||
@@ -131,14 +128,25 @@ class Exchange:
|
||||
self._trades_pagination = self._ft_has['trades_pagination']
|
||||
self._trades_pagination_arg = self._ft_has['trades_pagination_arg']
|
||||
|
||||
self.trading_mode: TradingMode = (
|
||||
TradingMode(config.get('trading_mode'))
|
||||
if config.get('trading_mode')
|
||||
else TradingMode.SPOT
|
||||
)
|
||||
self.collateral: Optional[Collateral] = (
|
||||
Collateral(config.get('collateral'))
|
||||
if config.get('collateral')
|
||||
else None
|
||||
)
|
||||
|
||||
# Initialize ccxt objects
|
||||
ccxt_config = self._ccxt_config.copy()
|
||||
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.copy()
|
||||
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', {}),
|
||||
@@ -146,17 +154,6 @@ class Exchange:
|
||||
self._api_async = self._init_ccxt(
|
||||
exchange_config, ccxt_async, ccxt_kwargs=ccxt_async_config)
|
||||
|
||||
self.trading_mode: TradingMode = (
|
||||
TradingMode(config.get('trading_mode'))
|
||||
if config.get('trading_mode')
|
||||
else TradingMode.SPOT
|
||||
)
|
||||
collateral: Optional[Collateral] = (
|
||||
Collateral(config.get('collateral'))
|
||||
if config.get('collateral')
|
||||
else None
|
||||
)
|
||||
|
||||
if self.trading_mode != TradingMode.SPOT:
|
||||
self.fill_leverage_brackets()
|
||||
|
||||
@@ -177,7 +174,7 @@ class Exchange:
|
||||
self.validate_order_time_in_force(config.get('order_time_in_force', {}))
|
||||
self.validate_required_startup_candles(config.get('startup_candle_count', 0),
|
||||
config.get('timeframe', ''))
|
||||
self.validate_trading_mode_and_collateral(self.trading_mode, collateral)
|
||||
self.validate_trading_mode_and_collateral(self.trading_mode, self.collateral)
|
||||
# Converts the interval provided in minutes in config to seconds
|
||||
self.markets_refresh_interval: int = exchange_config.get(
|
||||
"markets_refresh_interval", 60) * 60
|
||||
@@ -210,7 +207,6 @@ class Exchange:
|
||||
'secret': exchange_config.get('secret'),
|
||||
'password': exchange_config.get('password'),
|
||||
'uid': exchange_config.get('uid', ''),
|
||||
'options': exchange_config.get('options', {})
|
||||
}
|
||||
if ccxt_kwargs:
|
||||
logger.info('Applying additional ccxt config: %s', ccxt_kwargs)
|
||||
@@ -231,6 +227,11 @@ class Exchange:
|
||||
|
||||
return api
|
||||
|
||||
@property
|
||||
def _ccxt_config(self) -> Dict:
|
||||
# Parameters to add directly to ccxt sync/async initialization.
|
||||
return {}
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""exchange Name (from ccxt)"""
|
||||
@@ -617,15 +618,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._get_stake_amount_considering_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 _get_stake_amount_considering_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 +635,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 +652,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 +663,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,19 +771,26 @@ class Exchange:
|
||||
|
||||
# Order handling
|
||||
|
||||
def create_order(self, pair: str, ordertype: str, side: str, amount: float,
|
||||
rate: float, time_in_force: str = 'gtc', leverage=1.0) -> Dict:
|
||||
|
||||
if self._config['dry_run']:
|
||||
dry_order = self.create_dry_run_order(pair, ordertype, side, amount, rate)
|
||||
return dry_order
|
||||
|
||||
def _lev_prep(self, pair: str, leverage: float):
|
||||
if self.trading_mode != TradingMode.SPOT:
|
||||
self.set_margin_mode(pair, self.collateral)
|
||||
self._set_leverage(leverage, pair)
|
||||
|
||||
def _get_params(self, ordertype: str, leverage: float, time_in_force: str = 'gtc') -> Dict:
|
||||
params = self._params.copy()
|
||||
if time_in_force != 'gtc' and ordertype != 'market':
|
||||
param = self._ft_has.get('time_in_force_parameter', '')
|
||||
params.update({param: time_in_force})
|
||||
return params
|
||||
|
||||
def create_order(self, pair: str, ordertype: str, side: str, amount: float,
|
||||
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, leverage)
|
||||
return dry_order
|
||||
|
||||
params = self._get_params(ordertype, leverage, time_in_force)
|
||||
|
||||
try:
|
||||
# Set the precision for amount and price(rate) as accepted by the exchange
|
||||
@@ -792,6 +799,7 @@ class Exchange:
|
||||
or self._api.options.get("createMarketBuyOrderRequiresPrice", False))
|
||||
rate_for_order = self.price_to_precision(pair, rate) if needs_price else None
|
||||
|
||||
self._lev_prep(pair, leverage)
|
||||
order = self._api.create_order(pair, ordertype, side,
|
||||
amount, rate_for_order, params)
|
||||
self._log_exchange_response('create_order', order)
|
||||
@@ -822,8 +830,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.
|
||||
@@ -1586,15 +1594,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
|
||||
# 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:
|
||||
"""
|
||||
@@ -1615,7 +1621,7 @@ 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"):
|
||||
if self._config['dry_run'] or not self.exchange_has("setLeverage"):
|
||||
# Some exchanges only support one collateral type
|
||||
return
|
||||
|
||||
@@ -1635,7 +1641,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
|
||||
|
||||
|
@@ -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,6 +81,7 @@ class Ftx(Exchange):
|
||||
params['stopPrice'] = stop_price
|
||||
amount = self.amount_to_precision(pair, amount)
|
||||
|
||||
self._lev_prep(pair, leverage)
|
||||
order = self._api.create_order(symbol=pair, type=ordertype, side=side,
|
||||
amount=amount, params=params)
|
||||
self._log_exchange_response('create_stoploss_order', order)
|
||||
|
@@ -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,7 +108,7 @@ 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:
|
||||
@@ -182,8 +182,16 @@ class Kraken(Exchange):
|
||||
Kraken set's the leverage as an option in the order object, so we need to
|
||||
add it to params
|
||||
"""
|
||||
|
||||
if leverage > 1.0:
|
||||
self._params['leverage'] = leverage
|
||||
else:
|
||||
if 'leverage' in self._params:
|
||||
del self._params['leverage']
|
||||
return
|
||||
|
||||
def _get_params(self, ordertype: str, leverage: float, time_in_force: str = 'gtc') -> Dict:
|
||||
params = super()._get_params(ordertype, leverage, time_in_force)
|
||||
if leverage > 1.0:
|
||||
params['leverage'] = leverage
|
||||
return params
|
||||
|
Reference in New Issue
Block a user