Merge branch 'feat/short' into futures_pairlist

This commit is contained in:
Matthias
2021-11-15 19:12:36 +01:00
25 changed files with 966 additions and 244 deletions

View File

@@ -3,9 +3,9 @@ from enum import Enum
class Collateral(Enum):
"""
Enum to distinguish between
cross margin/futures collateral and
isolated margin/futures collateral
Enum to distinguish between
cross margin/futures collateral and
isolated margin/futures collateral
"""
CROSS = "cross"
ISOLATED = "isolated"

View File

@@ -3,8 +3,8 @@ from enum import Enum
class TradingMode(Enum):
"""
Enum to distinguish between
spot, margin, futures or any other trading method
Enum to distinguish between
spot, margin, futures or any other trading method
"""
SPOT = "spot"
MARGIN = "margin"

View File

@@ -1,6 +1,6 @@
""" Bibox exchange subclass """
import logging
from typing import Dict, List
from typing import Dict
from freqtrade.exchange import Exchange
@@ -23,6 +23,6 @@ class Bibox(Exchange):
@property
def _ccxt_config(self) -> Dict:
# Parameters to add directly to ccxt sync/async initialization.
return {"has": {"fetchCurrencies": False}}
funding_fee_times: List[int] = [0, 8, 16] # hours of the day
config = {"has": {"fetchCurrencies": False}}
config.update(super()._ccxt_config)
return config

View File

@@ -1,6 +1,7 @@
""" Binance exchange subclass """
import json
import logging
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional, Tuple
@@ -27,35 +28,17 @@ class Binance(Exchange):
"trades_pagination": "id",
"trades_pagination_arg": "fromId",
"l2_limit_range": [5, 10, 20, 50, 100, 500, 1000],
"ccxt_futures_name": "future"
}
funding_fee_times: List[int] = [0, 8, 16] # hours of the day
# but the schedule won't check within this timeframe
_supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [
# 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
# TODO-lev: Uncomment once supported
# (TradingMode.MARGIN, Collateral.CROSS),
# (TradingMode.FUTURES, Collateral.CROSS),
# (TradingMode.FUTURES, Collateral.ISOLATED)
]
@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)
@@ -139,8 +122,8 @@ class Binance(Exchange):
@retrier
def fill_leverage_brackets(self):
"""
Assigns property _leverage_brackets to a dictionary of information about the leverage
allowed on each pair
Assigns property _leverage_brackets to a dictionary of information about the leverage
allowed on each pair
"""
if self.trading_mode == TradingMode.FUTURES:
try:
@@ -174,9 +157,9 @@ class Binance(Exchange):
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)
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)
"""
if pair not in self._leverage_brackets:
return 1.0
@@ -195,8 +178,8 @@ class Binance(Exchange):
trading_mode: Optional[TradingMode] = None
):
"""
Set's the leverage before making a trade, in order to not
have the same leverage on every trade
Set's the leverage before making a trade, in order to not
have the same leverage on every trade
"""
trading_mode = trading_mode or self.trading_mode
@@ -229,3 +212,11 @@ class Binance(Exchange):
f"{arrow.get(since_ms // 1000).isoformat()}.")
return await super()._async_get_historic_ohlcv(
pair=pair, timeframe=timeframe, since_ms=since_ms, is_new_pair=is_new_pair)
def funding_fee_cutoff(self, open_date: datetime):
"""
# TODO-lev: Double check that gateio, ftx, and kraken don't also have this
:param open_date: The open date for a trade
:return: The cutoff open time for when a funding fee is charged
"""
return open_date.minute > 0 or (open_date.minute == 0 and open_date.second > 15)

View File

@@ -21,12 +21,12 @@ class Bybit(Exchange):
_ft_has: Dict = {
"ohlcv_candle_limit": 200,
"ccxt_futures_name": "linear"
}
funding_fee_times: List[int] = [0, 8, 16] # hours of the day
_supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [
# TradingMode.SPOT always supported and not required in this list
# (TradingMode.FUTURES, Collateral.CROSS), # TODO-lev: Uncomment once supported
# (TradingMode.FUTURES, Collateral.ISOLATED) # TODO-lev: Uncomment once supported
# TODO-lev: Uncomment once supported
# (TradingMode.FUTURES, Collateral.CROSS),
# (TradingMode.FUTURES, Collateral.ISOLATED)
]

View File

@@ -7,7 +7,7 @@ import http
import inspect
import logging
from copy import deepcopy
from datetime import datetime, timezone
from datetime import datetime, timedelta, timezone
from math import ceil
from typing import Any, Dict, List, Optional, Tuple, Union
@@ -69,13 +69,11 @@ class Exchange:
"trades_pagination_arg": "since",
"l2_limit_range": None,
"l2_limit_range_required": True, # Allow Empty L2 limit (kucoin)
"mark_ohlcv_price": "mark",
"ccxt_futures_name": "swap"
}
_ft_has: Dict = {}
# funding_fee_times is currently unused, but should ideally be used to properly
# schedule refresh times
funding_fee_times: List[int] = [] # hours of the day
_supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [
# TradingMode.SPOT always supported and not required in this list
]
@@ -89,6 +87,7 @@ class Exchange:
self._api: ccxt.Exchange = None
self._api_async: ccxt_async.Exchange = None
self._markets: Dict = {}
self._leverage_brackets: Dict = {}
self._config.update(config)
@@ -179,7 +178,6 @@ class Exchange:
self.markets_refresh_interval: int = exchange_config.get(
"markets_refresh_interval", 60) * 60
self._leverage_brackets: Dict = {}
if self.trading_mode != TradingMode.SPOT:
self.fill_leverage_brackets()
@@ -234,7 +232,20 @@ class Exchange:
@property
def _ccxt_config(self) -> Dict:
# Parameters to add directly to ccxt sync/async initialization.
return {}
if self.trading_mode == TradingMode.MARGIN:
return {
"options": {
"defaultType": "margin"
}
}
elif self.trading_mode == TradingMode.FUTURES:
return {
"options": {
"defaultType": self._ft_has["ccxt_futures_name"]
}
}
else:
return {}
@property
def name(self) -> str:
@@ -532,10 +543,10 @@ class Exchange:
collateral: Optional[Collateral] # Only None when trading_mode = TradingMode.SPOT
):
"""
Checks if freqtrade can perform trades using the configured
trading mode(Margin, Futures) and Collateral(Cross, Isolated)
Throws OperationalException:
If the trading_mode/collateral type are not supported by freqtrade on this exchange
Checks if freqtrade can perform trades using the configured
trading mode(Margin, Futures) and Collateral(Cross, Isolated)
Throws OperationalException:
If the trading_mode/collateral type are not supported by freqtrade on this exchange
"""
if trading_mode != TradingMode.SPOT and (
(trading_mode, collateral) not in self._supported_trading_mode_collateral_pairs
@@ -1622,18 +1633,18 @@ class Exchange:
until=until, from_id=from_id))
@retrier
def get_funding_fees_from_exchange(self, pair: str, since: Union[datetime, int]) -> float:
def _get_funding_fees_from_exchange(self, pair: str, since: Union[datetime, int]) -> float:
"""
Returns the sum of all funding fees that were exchanged for a pair within a timeframe
:param pair: (e.g. ADA/USDT)
:param since: The earliest time of consideration for calculating funding fees,
in unix time or as a datetime
Returns the sum of all funding fees that were exchanged for a pair within a timeframe
Dry-run handling happens as part of _calculate_funding_fees.
:param pair: (e.g. ADA/USDT)
:param since: The earliest time of consideration for calculating funding fees,
in unix time or as a datetime
"""
# TODO-lev: Add dry-run handling for this.
if not self.exchange_has("fetchFundingHistory"):
raise OperationalException(
f"fetch_funding_history() has not been implemented on ccxt.{self.name}")
f"fetch_funding_history() is not available using {self.name}"
)
if type(since) is datetime:
since = int(since.timestamp()) * 1000 # * 1000 for ms
@@ -1654,17 +1665,17 @@ class Exchange:
def fill_leverage_brackets(self):
"""
Assigns property _leverage_brackets to a dictionary of information about the leverage
allowed on each pair
Not used if the exchange has a static max leverage value for the account or each pair
Assigns property _leverage_brackets to a dictionary of information about the leverage
allowed on each pair
Not used if the exchange has a static max leverage value for the account or each pair
"""
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
:param pair: The base/quote currency pair being traded
:nominal_value: The total value of the trade in quote currency (collateral + debt)
Returns the maximum leverage that a pair can be traded at
:param pair: The base/quote currency pair being traded
:param nominal_value: The total value of the trade in quote currency (collateral + debt)
"""
market = self.markets[pair]
if (
@@ -1676,6 +1687,25 @@ class Exchange:
else:
return 1.0
def _get_funding_fee(
self,
size: float,
funding_rate: float,
mark_price: float,
time_in_ratio: Optional[float] = None
) -> float:
"""
Calculates a single funding fee
:param size: contract size * number of contracts
:param mark_price: The price of the asset that the contract is based off of
:param funding_rate: the interest rate and the premium
- interest rate:
- premium: varies by price difference between the perpetual contract and mark price
:param time_in_ratio: Not used by most exchange classes
"""
nominal_value = mark_price * size
return nominal_value * funding_rate
@retrier
def _set_leverage(
self,
@@ -1684,8 +1714,8 @@ class Exchange:
trading_mode: Optional[TradingMode] = None
):
"""
Set's the leverage before making a trade, in order to not
have the same leverage on every trade
Set's the leverage before making a trade, in order to not
have the same leverage on every trade
"""
if self._config['dry_run'] or not self.exchange_has("setLeverage"):
# Some exchanges only support one collateral type
@@ -1701,12 +1731,19 @@ class Exchange:
except ccxt.BaseError as e:
raise OperationalException(e) from e
def funding_fee_cutoff(self, open_date: datetime):
"""
:param open_date: The open date for a trade
:return: The cutoff open time for when a funding fee is charged
"""
return open_date.minute > 0 or open_date.second > 0
@retrier
def set_margin_mode(self, pair: str, collateral: Collateral, params: dict = {}):
'''
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")
'''
"""
Set's the margin mode on the exchange to cross or isolated for a specific pair
:param pair: base/quote currency pair (e.g. "ADA/USDT")
"""
if self._config['dry_run'] or not self.exchange_has("setMarginMode"):
# Some exchanges only support one collateral type
return
@@ -1721,6 +1758,150 @@ class Exchange:
except ccxt.BaseError as e:
raise OperationalException(e) from e
@retrier
def _get_mark_price_history(self, pair: str, since: int) -> Dict:
"""
Get's the mark price history for a pair
:param pair: The quote/base pair of the trade
:param since: The earliest time to start downloading candles, in ms.
"""
try:
candles = self._api.fetch_ohlcv(
pair,
timeframe="1h",
since=since,
params={
'price': self._ft_has["mark_ohlcv_price"]
}
)
history = {}
for candle in candles:
d = datetime.fromtimestamp(int(candle[0] / 1000), timezone.utc)
# Round down to the nearest hour, in case of a delayed timestamp
# The millisecond timestamps can be delayed ~20ms
time = timeframe_to_prev_date('1h', d).timestamp() * 1000
opening_mark_price = candle[1]
history[time] = opening_mark_price
return history
except ccxt.NotSupported as e:
raise OperationalException(
f'Exchange {self._api.name} does not support fetching historical '
f'mark price candle (OHLCV) data. Message: {e}') from e
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(f'Could not fetch historical mark price candle (OHLCV) data '
f'for pair {pair} due to {e.__class__.__name__}. '
f'Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(f'Could not fetch historical mark price candle (OHLCV) data '
f'for pair {pair}. Message: {e}') from e
def _calculate_funding_fees(
self,
pair: str,
amount: float,
open_date: datetime,
close_date: Optional[datetime] = None
) -> float:
"""
calculates the sum of all funding fees that occurred for a pair during a futures trade
Only used during dry-run or if the exchange does not provide a funding_rates endpoint.
:param pair: The quote/base pair of the trade
:param amount: The quantity of the trade
:param open_date: The date and time that the trade started
:param close_date: The date and time that the trade ended
"""
if self.funding_fee_cutoff(open_date):
open_date += timedelta(hours=1)
open_date = timeframe_to_prev_date('1h', open_date)
fees: float = 0
if not close_date:
close_date = datetime.now(timezone.utc)
open_timestamp = int(open_date.timestamp()) * 1000
# close_timestamp = int(close_date.timestamp()) * 1000
funding_rate_history = self.get_funding_rate_history(
pair,
open_timestamp
)
mark_price_history = self._get_mark_price_history(
pair,
open_timestamp
)
for timestamp in funding_rate_history.keys():
funding_rate = funding_rate_history[timestamp]
if timestamp in mark_price_history:
mark_price = mark_price_history[timestamp]
fees += self._get_funding_fee(
size=amount,
mark_price=mark_price,
funding_rate=funding_rate
)
else:
logger.warning(
f"Mark price for {pair} at timestamp {timestamp} not found in "
f"funding_rate_history Funding fee calculation may be incorrect"
)
return fees
def get_funding_fees(self, pair: str, amount: float, open_date: datetime) -> float:
"""
Fetch funding fees, either from the exchange (live) or calculates them
based on funding rate/mark price history
:param pair: The quote/base pair of the trade
:param amount: Trade amount
:param open_date: Open date of the trade
"""
if self.trading_mode == TradingMode.FUTURES:
if self._config['dry_run']:
funding_fees = self._calculate_funding_fees(pair, amount, open_date)
else:
funding_fees = self._get_funding_fees_from_exchange(pair, open_date)
return funding_fees
else:
return 0.0
@retrier
def get_funding_rate_history(self, pair: str, since: int) -> Dict:
"""
:param pair: quote/base currency pair
:param since: timestamp in ms of the beginning time
:param end: timestamp in ms of the end time
"""
if not self.exchange_has("fetchFundingRateHistory"):
raise ExchangeError(
f"fetch_funding_rate_history is not available using {self.name}"
)
# TODO-lev: Gateio has a max limit into the past of 333 days, okex has a limit of 3 months
try:
funding_history: Dict = {}
response = self._api.fetch_funding_rate_history(
pair,
limit=1000,
since=since
)
for fund in response:
d = datetime.fromtimestamp(int(fund['timestamp'] / 1000), timezone.utc)
# Round down to the nearest hour, in case of a delayed timestamp
# The millisecond timestamps can be delayed ~20ms
time = int(timeframe_to_prev_date('1h', d).timestamp() * 1000)
funding_history[time] = fund['fundingRate']
return funding_history
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not set margin mode due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool:
return exchange_name in ccxt_exchanges(ccxt_module)

View File

@@ -20,13 +20,14 @@ class Ftx(Exchange):
_ft_has: Dict = {
"stoploss_on_exchange": True,
"ohlcv_candle_limit": 1500,
"mark_ohlcv_price": "index"
}
funding_fee_times: List[int] = list(range(0, 24))
_supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [
# 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
# TODO-lev: Uncomment once supported
# (TradingMode.MARGIN, Collateral.CROSS),
# (TradingMode.FUTURES, Collateral.CROSS)
]
def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool:

View File

@@ -26,33 +26,14 @@ class Gateio(Exchange):
_headers = {'X-Gate-Channel-Id': 'freqtrade'}
funding_fee_times: List[int] = [0, 8, 16] # hours of the day
_supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [
# 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
# TODO-lev: Uncomment once supported
# (TradingMode.MARGIN, Collateral.CROSS),
# (TradingMode.FUTURES, Collateral.CROSS),
# (TradingMode.FUTURES, Collateral.ISOLATED)
]
@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": "swap"
}
}
else:
return {}
def validate_ordertypes(self, order_types: Dict) -> None:
super().validate_ordertypes(order_types)

View File

@@ -1,5 +1,5 @@
import logging
from typing import Dict, List
from typing import Dict
from freqtrade.exchange import Exchange
@@ -21,5 +21,3 @@ class Hitbtc(Exchange):
"ohlcv_candle_limit": 1000,
"ohlcv_params": {"sort": "DESC"}
}
funding_fee_times: List[int] = [0, 8, 16] # hours of the day

View File

@@ -23,12 +23,12 @@ class Kraken(Exchange):
"trades_pagination": "id",
"trades_pagination_arg": "since",
}
funding_fee_times: List[int] = [0, 4, 8, 12, 16, 20] # hours of the day
_supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [
# 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: No CCXT support
# TODO-lev: Uncomment once supported
# (TradingMode.MARGIN, Collateral.CROSS),
# (TradingMode.FUTURES, Collateral.CROSS)
]
def market_is_tradable(self, market: Dict[str, Any]) -> bool:
@@ -146,8 +146,8 @@ class Kraken(Exchange):
trading_mode: Optional[TradingMode] = None
):
"""
Kraken set's the leverage as an option in the order object, so we need to
add it to params
Kraken set's the leverage as an option in the order object, so we need to
add it to params
"""
return
@@ -156,3 +156,29 @@ class Kraken(Exchange):
if leverage > 1.0:
params['leverage'] = leverage
return params
def _get_funding_fee(
self,
size: float,
funding_rate: float,
mark_price: float,
time_in_ratio: Optional[float] = None
) -> float:
"""
# ! This method will always error when run by Freqtrade because time_in_ratio is never
# ! passed to _get_funding_fee. For kraken futures to work in dry run and backtesting
# ! functionality must be added that passes the parameter time_in_ratio to
# ! _get_funding_fee when using Kraken
Calculates a single funding fee
:param size: contract size * number of contracts
:param mark_price: The price of the asset that the contract is based off of
:param funding_rate: the interest rate and the premium
- interest rate:
- premium: varies by price difference between the perpetual contract and mark price
:param time_in_ratio: time elapsed within funding period without position alteration
"""
if not time_in_ratio:
raise OperationalException(
f"time_in_ratio is required for {self.name}._get_funding_fee")
nominal_value = mark_price * size
return nominal_value * funding_rate * time_in_ratio

View File

@@ -1,6 +1,6 @@
""" Kucoin exchange subclass """
import logging
from typing import Dict, List
from typing import Dict
from freqtrade.exchange import Exchange
@@ -24,5 +24,3 @@ class Kucoin(Exchange):
"order_time_in_force": ['gtc', 'fok', 'ioc'],
"time_in_force_parameter": "timeInForce",
}
funding_fee_times: List[int] = [4, 12, 20] # hours of the day

View File

@@ -17,29 +17,11 @@ class Okex(Exchange):
_ft_has: Dict = {
"ohlcv_candle_limit": 100,
}
funding_fee_times: List[int] = [0, 8, 16] # hours of the day
_supported_trading_mode_collateral_pairs: List[Tuple[TradingMode, Collateral]] = [
# 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
# TODO-lev: Uncomment once supported
# (TradingMode.MARGIN, Collateral.CROSS),
# (TradingMode.FUTURES, Collateral.CROSS),
# (TradingMode.FUTURES, Collateral.ISOLATED)
]
@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": "swap"
}
}
else:
return {}

View File

@@ -268,12 +268,16 @@ class FreqtradeBot(LoggingMixin):
def update_funding_fees(self):
if self.trading_mode == TradingMode.FUTURES:
for trade in Trade.get_open_trades():
funding_fees = self.exchange.get_funding_fees_from_exchange(
trades = Trade.get_open_trades()
for trade in trades:
funding_fees = self.exchange.get_funding_fees(
trade.pair,
trade.amount,
trade.open_date
)
trade.funding_fees = funding_fees
else:
return 0.0
def startup_update_open_orders(self):
"""
@@ -617,8 +621,9 @@ class FreqtradeBot(LoggingMixin):
default_retval=stake_amount)(
pair=pair, current_time=datetime.now(timezone.utc),
current_rate=enter_limit_requested, proposed_stake=stake_amount,
min_stake=min_stake_amount, max_stake=max_stake_amount, side='long')
# TODO-lev: Add non-hardcoded "side" parameter
min_stake=min_stake_amount, max_stake=max_stake_amount,
side='short' if is_short else 'long'
)
stake_amount = self.wallets._validate_stake_amount(pair, stake_amount, min_stake_amount)
@@ -638,7 +643,6 @@ class FreqtradeBot(LoggingMixin):
order_type = self.strategy.order_types.get('forcebuy', order_type)
# TODO-lev: Will this work for shorting?
# TODO-lev: Add non-hardcoded "side" parameter
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
time_in_force=time_in_force, current_time=datetime.now(timezone.utc),
@@ -703,10 +707,7 @@ class FreqtradeBot(LoggingMixin):
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
open_date = datetime.now(timezone.utc)
if self.trading_mode == TradingMode.FUTURES:
funding_fees = self.exchange.get_funding_fees_from_exchange(pair, open_date)
else:
funding_fees = 0.0
funding_fees = self.exchange.get_funding_fees(pair, amount, open_date)
trade = Trade(
pair=pair,
@@ -922,8 +923,7 @@ class FreqtradeBot(LoggingMixin):
Check if trade is fulfilled in which case the stoploss
on exchange should be added immediately if stoploss on exchange
is enabled.
# TODO-lev: liquidation price will always be on exchange, even though
# TODO-lev: stoploss_on_exchange might not be enabled
# TODO-lev: liquidation price always on exchange, even without stoploss_on_exchange
"""
logger.debug('Handling stoploss on exchange %s ...', trade)
@@ -1261,6 +1261,11 @@ class FreqtradeBot(LoggingMixin):
:param sell_reason: Reason the sell was triggered
:return: True if it succeeds (supported) False (not supported)
"""
trade.funding_fees = self.exchange.get_funding_fees(
trade.pair,
trade.amount,
trade.open_date
)
exit_type = 'sell' # TODO-lev: Update to exit
if sell_reason.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
exit_type = 'stoploss'
@@ -1517,7 +1522,7 @@ class FreqtradeBot(LoggingMixin):
self.wallets.update()
if fee_abs != 0 and self.wallets.get_free(trade_base_currency) >= amount:
# Eat into dust if we own more than base currency
# TODO-lev: won't be in base currency for shorts
# TODO-lev: settle currency for futures
logger.info(f"Fee amount for {trade} was in base currency - "
f"Eating Fee {fee_abs} into dust.")
elif fee_abs != 0:

View File

@@ -16,18 +16,18 @@ def interest(
hours: Decimal
) -> Decimal:
"""
Equation to calculate interest on margin trades
Equation to calculate interest on margin trades
:param exchange_name: The exchanged being trading on
:param borrowed: The amount of currency being borrowed
:param rate: The rate of interest (i.e daily interest rate)
:param hours: The time in hours that the currency has been borrowed for
:param exchange_name: The exchanged being trading on
:param borrowed: The amount of currency being borrowed
:param rate: The rate of interest (i.e daily interest rate)
:param hours: The time in hours that the currency has been borrowed for
Raises:
OperationalException: Raised if freqtrade does
not support margin trading for this exchange
Raises:
OperationalException: Raised if freqtrade does
not support margin trading for this exchange
Returns: The amount of interest owed (currency matches borrowed)
Returns: The amount of interest owed (currency matches borrowed)
"""
exchange_name = exchange_name.lower()
if exchange_name == "binance":

View File

@@ -30,13 +30,13 @@ _SQL_DOCS_URL = 'http://docs.sqlalchemy.org/en/latest/core/engines.html#database
def init_db(db_url: str, clean_open_orders: bool = False) -> None:
"""
Initializes this module with the given config,
registers all known command handlers
and starts polling for message updates
:param db_url: Database to use
:param clean_open_orders: Remove open orders from the database.
Useful for dry-run or if all orders have been reset on the exchange.
:return: None
Initializes this module with the given config,
registers all known command handlers
and starts polling for message updates
:param db_url: Database to use
:param clean_open_orders: Remove open orders from the database.
Useful for dry-run or if all orders have been reset on the exchange.
:return: None
"""
kwargs = {}
@@ -329,8 +329,8 @@ class LocalTrade():
def _set_stop_loss(self, stop_loss: float, percent: float):
"""
Method you should use to set self.stop_loss.
Assures stop_loss is not passed the liquidation price
Method you should use to set self.stop_loss.
Assures stop_loss is not passed the liquidation price
"""
if self.isolated_liq is not None:
if self.is_short:
@@ -352,8 +352,8 @@ class LocalTrade():
def set_isolated_liq(self, isolated_liq: float):
"""
Method you should use to set self.liquidation price.
Assures stop_loss is not passed the liquidation price
Method you should use to set self.liquidation price.
Assures stop_loss is not passed the liquidation price
"""
if self.stop_loss is not None:
if self.is_short:
@@ -916,8 +916,8 @@ class Trade(_DECL_BASE, LocalTrade):
max_rate = Column(Float, nullable=True, default=0.0)
# Lowest price reached
min_rate = Column(Float, nullable=True)
sell_reason = Column(String(100), nullable=True) # TODO-lev: Change to close_reason
sell_order_status = Column(String(100), nullable=True) # TODO-lev: Change to close_order_status
sell_reason = Column(String(100), nullable=True)
sell_order_status = Column(String(100), nullable=True)
strategy = Column(String(100), nullable=True)
buy_tag = Column(String(100), nullable=True)
timeframe = Column(Integer, nullable=True)

View File

@@ -32,7 +32,7 @@ class StoplossGuard(IProtection):
def _reason(self) -> str:
"""
LockReason to use
#TODO-lev: check if min is the right word for shorts
# TODO-lev: check if min is the right word for shorts
"""
return (f'{self._trade_limit} stoplosses in {self._lookback_period} min, '
f'locking for {self._stop_duration} min.')