Merge branch 'develop' of github.com:lolongcovas/freqtrade into strategies

This commit is contained in:
longyu 2023-01-31 19:47:13 +01:00
commit b42d396a01
26 changed files with 420 additions and 56 deletions

View File

@ -40,6 +40,7 @@ Please read the [exchange specific notes](docs/exchanges.md) to learn about even
- [X] [Binance](https://www.binance.com/) - [X] [Binance](https://www.binance.com/)
- [X] [Gate.io](https://www.gate.io/ref/6266643) - [X] [Gate.io](https://www.gate.io/ref/6266643)
- [X] [OKX](https://okx.com/) - [X] [OKX](https://okx.com/)
- [X] [Bybit](https://bybit.com/)
Please make sure to read the [exchange specific notes](docs/exchanges.md), as well as the [trading with leverage](docs/leverage.md) documentation before diving in. Please make sure to read the [exchange specific notes](docs/exchanges.md), as well as the [trading with leverage](docs/leverage.md) documentation before diving in.

View File

@ -255,6 +255,18 @@ OKX requires a passphrase for each api key, you will therefore need to add this
Gate.io allows the use of `POINT` to pay for fees. As this is not a tradable currency (no regular market available), automatic fee calculations will fail (and default to a fee of 0). Gate.io allows the use of `POINT` to pay for fees. As this is not a tradable currency (no regular market available), automatic fee calculations will fail (and default to a fee of 0).
The configuration parameter `exchange.unknown_fee_rate` can be used to specify the exchange rate between Point and the stake currency. Obviously, changing the stake-currency will also require changes to this value. The configuration parameter `exchange.unknown_fee_rate` can be used to specify the exchange rate between Point and the stake currency. Obviously, changing the stake-currency will also require changes to this value.
## Bybit
Futures trading on bybit is currently supported for USDT markets, and will use isolated futures mode.
Users with unified accounts (there's no way back) can create a Sub-account which will start as "non-unified", and can therefore use isolated futures.
On startup, freqtrade will set the position mode to "One-way Mode" for the whole (sub)account. This avoids making this call over and over again (slowing down bot operations), but means that changes to this setting may result in exceptions and errors.
As bybit doesn't provide funding rate history, the dry-run calculation is used for live trades as well.
!!! Tip "Stoploss on Exchange"
Bybit (futures only) supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange.
On futures, Bybit supports both `stop-limit` as well as `stop-market` orders. You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type to use.
## All exchanges ## All exchanges
Should you experience constant errors with Nonce (like `InvalidNonce`), it is best to regenerate the API keys. Resetting Nonce is difficult and it's usually easier to regenerate the API keys. Should you experience constant errors with Nonce (like `InvalidNonce`), it is best to regenerate the API keys. Resetting Nonce is difficult and it's usually easier to regenerate the API keys.

View File

@ -52,6 +52,7 @@ Please read the [exchange specific notes](exchanges.md) to learn about eventual,
- [X] [Binance](https://www.binance.com/) - [X] [Binance](https://www.binance.com/)
- [X] [Gate.io](https://www.gate.io/ref/6266643) - [X] [Gate.io](https://www.gate.io/ref/6266643)
- [X] [OKX](https://okx.com/) - [X] [OKX](https://okx.com/)
- [X] [Bybit](https://bybit.com/)
Please make sure to read the [exchange specific notes](exchanges.md), as well as the [trading with leverage](leverage.md) documentation before diving in. Please make sure to read the [exchange specific notes](exchanges.md), as well as the [trading with leverage](leverage.md) documentation before diving in.

View File

@ -1,6 +1,6 @@
markdown==3.3.7 markdown==3.3.7
mkdocs==1.4.2 mkdocs==1.4.2
mkdocs-material==9.0.5 mkdocs-material==9.0.8
mdx_truly_sane_lists==1.3 mdx_truly_sane_lists==1.3
pymdown-extensions==9.9.1 pymdown-extensions==9.9.1
jinja2==3.1.2 jinja2==3.1.2

View File

@ -80,7 +80,7 @@ class AwesomeStrategy(IStrategy):
## Enter Tag ## Enter Tag
When your strategy has multiple buy signals, you can name the signal that triggered. When your strategy has multiple buy signals, you can name the signal that triggered.
Then you can access you buy signal on `custom_exit` Then you can access your buy signal on `custom_exit`
```python ```python
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

View File

@ -1,5 +1,5 @@
""" Freqtrade bot """ """ Freqtrade bot """
__version__ = '2023.1.dev' __version__ = '2023.2.dev'
if 'dev' in __version__: if 'dev' in __version__:
from pathlib import Path from pathlib import Path

View File

@ -9,7 +9,7 @@ from collections import deque
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import Any, Dict, List, Optional, Tuple from typing import Any, Dict, List, Optional, Tuple
from pandas import DataFrame, to_timedelta from pandas import DataFrame, Timedelta, Timestamp, to_timedelta
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.constants import (FULL_DATAFRAME_THRESHOLD, Config, ListPairsWithTimeframes, from freqtrade.constants import (FULL_DATAFRAME_THRESHOLD, Config, ListPairsWithTimeframes,
@ -206,9 +206,11 @@ class DataProvider:
existing_df, _ = self.__producer_pairs_df[producer_name][pair_key] existing_df, _ = self.__producer_pairs_df[producer_name][pair_key]
# CHECK FOR MISSING CANDLES # CHECK FOR MISSING CANDLES
timeframe_delta = to_timedelta(timeframe) # Convert the timeframe to a timedelta for pandas # Convert the timeframe to a timedelta for pandas
local_last = existing_df.iloc[-1]['date'] # We want the last date from our copy timeframe_delta: Timedelta = to_timedelta(timeframe)
incoming_first = dataframe.iloc[0]['date'] # We want the first date from the incoming local_last: Timestamp = existing_df.iloc[-1]['date'] # We want the last date from our copy
# We want the first date from the incoming
incoming_first: Timestamp = dataframe.iloc[0]['date']
# Remove existing candles that are newer than the incoming first candle # Remove existing candles that are newer than the incoming first candle
existing_df1 = existing_df[existing_df['date'] < incoming_first] existing_df1 = existing_df[existing_df['date'] < incoming_first]
@ -221,7 +223,7 @@ class DataProvider:
# we missed some candles between our data and the incoming # we missed some candles between our data and the incoming
# so return False and candle_difference. # so return False and candle_difference.
if candle_difference > 1: if candle_difference > 1:
return (False, candle_difference) return (False, int(candle_difference))
if existing_df1.empty: if existing_df1.empty:
appended_df = dataframe appended_df = dataframe
else: else:

View File

@ -308,7 +308,7 @@ class IDataHandler(ABC):
timerange=timerange_startup, timerange=timerange_startup,
candle_type=candle_type candle_type=candle_type
) )
if self._check_empty_df(pairdf, pair, timeframe, candle_type, warn_no_data, True): if self._check_empty_df(pairdf, pair, timeframe, candle_type, warn_no_data):
return pairdf return pairdf
else: else:
enddate = pairdf.iloc[-1]['date'] enddate = pairdf.iloc[-1]['date']
@ -316,7 +316,7 @@ class IDataHandler(ABC):
if timerange_startup: if timerange_startup:
self._validate_pairdata(pair, pairdf, timeframe, candle_type, timerange_startup) self._validate_pairdata(pair, pairdf, timeframe, candle_type, timerange_startup)
pairdf = trim_dataframe(pairdf, timerange_startup) pairdf = trim_dataframe(pairdf, timerange_startup)
if self._check_empty_df(pairdf, pair, timeframe, candle_type, warn_no_data): if self._check_empty_df(pairdf, pair, timeframe, candle_type, warn_no_data, True):
return pairdf return pairdf
# incomplete candles should only be dropped if we didn't trim the end beforehand. # incomplete candles should only be dropped if we didn't trim the end beforehand.

View File

@ -28,7 +28,6 @@ class Binance(Exchange):
"trades_pagination": "id", "trades_pagination": "id",
"trades_pagination_arg": "fromId", "trades_pagination_arg": "fromId",
"l2_limit_range": [5, 10, 20, 50, 100, 500, 1000], "l2_limit_range": [5, 10, 20, 50, 100, 500, 1000],
"ccxt_futures_name": "swap"
} }
_ft_has_futures: Dict = { _ft_has_futures: Dict = {
"stoploss_order_types": {"limit": "stop", "market": "stop_market"}, "stoploss_order_types": {"limit": "stop", "market": "stop_market"},
@ -78,7 +77,9 @@ class Binance(Exchange):
raise DDosProtection(e) from e raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e: except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError( raise TemporaryError(
f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e f'Error in additional_exchange_init due to {e.__class__.__name__}. Message: {e}'
) from e
except ccxt.BaseError as e: except ccxt.BaseError as e:
raise OperationalException(e) from e raise OperationalException(e) from e
@ -87,7 +88,8 @@ class Binance(Exchange):
self, self,
leverage: float, leverage: float,
pair: Optional[str] = None, pair: Optional[str] = None,
trading_mode: Optional[TradingMode] = None trading_mode: Optional[TradingMode] = None,
accept_fail: bool = False,
): ):
""" """
Set's the leverage before making a trade, in order to not Set's the leverage before making a trade, in order to not
@ -150,6 +152,7 @@ class Binance(Exchange):
is_short: bool, is_short: bool,
amount: float, amount: float,
stake_amount: float, stake_amount: float,
leverage: float,
wallet_balance: float, # Or margin balance wallet_balance: float, # Or margin balance
mm_ex_1: float = 0.0, # (Binance) Cross only mm_ex_1: float = 0.0, # (Binance) Cross only
upnl_ex_1: float = 0.0, # (Binance) Cross only upnl_ex_1: float = 0.0, # (Binance) Cross only
@ -159,11 +162,12 @@ class Binance(Exchange):
MARGIN: https://www.binance.com/en/support/faq/f6b010588e55413aa58b7d63ee0125ed MARGIN: https://www.binance.com/en/support/faq/f6b010588e55413aa58b7d63ee0125ed
PERPETUAL: https://www.binance.com/en/support/faq/b3c689c1f50a44cabb3a84e663b81d93 PERPETUAL: https://www.binance.com/en/support/faq/b3c689c1f50a44cabb3a84e663b81d93
:param exchange_name: :param pair: Pair to calculate liquidation price for
:param open_rate: Entry price of position :param open_rate: Entry price of position
:param is_short: True if the trade is a short, false otherwise :param is_short: True if the trade is a short, false otherwise
:param amount: Absolute value of position size incl. leverage (in base currency) :param amount: Absolute value of position size incl. leverage (in base currency)
:param stake_amount: Stake amount - Collateral in settle currency. :param stake_amount: Stake amount - Collateral in settle currency.
:param leverage: Leverage used for this position.
:param trading_mode: SPOT, MARGIN, FUTURES, etc. :param trading_mode: SPOT, MARGIN, FUTURES, etc.
:param margin_mode: Either ISOLATED or CROSS :param margin_mode: Either ISOLATED or CROSS
:param wallet_balance: Amount of margin_mode in the wallet being used to trade :param wallet_balance: Amount of margin_mode in the wallet being used to trade

View File

@ -1,9 +1,16 @@
""" Bybit exchange subclass """ """ Bybit exchange subclass """
import logging import logging
from typing import Dict, List, Tuple from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple
import ccxt
from freqtrade.constants import BuySell
from freqtrade.enums import MarginMode, TradingMode from freqtrade.enums import MarginMode, TradingMode
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade.exchange.common import retrier
from freqtrade.exchange.exchange_utils import timeframe_to_msecs
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -21,17 +28,20 @@ class Bybit(Exchange):
_ft_has: Dict = { _ft_has: Dict = {
"ohlcv_candle_limit": 1000, "ohlcv_candle_limit": 1000,
"ccxt_futures_name": "linear",
"ohlcv_has_history": False, "ohlcv_has_history": False,
} }
_ft_has_futures: Dict = { _ft_has_futures: Dict = {
"ohlcv_candle_limit": 200,
"ohlcv_has_history": True, "ohlcv_has_history": True,
"mark_ohlcv_timeframe": "4h",
"stoploss_on_exchange": True,
"stoploss_order_types": {"limit": "limit", "market": "market"},
} }
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
# TradingMode.SPOT always supported and not required in this list # TradingMode.SPOT always supported and not required in this list
# (TradingMode.FUTURES, MarginMode.CROSS), # (TradingMode.FUTURES, MarginMode.CROSS),
# (TradingMode.FUTURES, MarginMode.ISOLATED) (TradingMode.FUTURES, MarginMode.ISOLATED)
] ]
@property @property
@ -47,3 +57,158 @@ class Bybit(Exchange):
}) })
config.update(super()._ccxt_config) config.update(super()._ccxt_config)
return config return config
def market_is_future(self, market: Dict[str, Any]) -> bool:
main = super().market_is_future(market)
# For ByBit, we'll only support USDT markets for now.
return (
main and market['settle'] == 'USDT'
)
@retrier
def additional_exchange_init(self) -> None:
"""
Additional exchange initialization logic.
.api will be available at this point.
Must be overridden in child methods if required.
"""
try:
if self.trading_mode == TradingMode.FUTURES and not self._config['dry_run']:
position_mode = self._api.set_position_mode(False)
self._log_exchange_response('set_position_mode', position_mode)
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Error in additional_exchange_init due to {e.__class__.__name__}. Message: {e}'
) from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
async def _fetch_funding_rate_history(
self,
pair: str,
timeframe: str,
limit: int,
since_ms: Optional[int] = None,
) -> List[List]:
"""
Fetch funding rate history
Necessary workaround until https://github.com/ccxt/ccxt/issues/15990 is fixed.
"""
params = {}
if since_ms:
until = since_ms + (timeframe_to_msecs(timeframe) * self._ft_has['ohlcv_candle_limit'])
params.update({'until': until})
# Funding rate
data = await self._api_async.fetch_funding_rate_history(
pair, since=since_ms,
params=params)
# Convert funding rate to candle pattern
data = [[x['timestamp'], x['fundingRate'], 0, 0, 0, 0] for x in data]
return data
def _lev_prep(self, pair: str, leverage: float, side: BuySell):
if self.trading_mode != TradingMode.SPOT:
params = {'leverage': leverage}
self.set_margin_mode(pair, self.margin_mode, accept_fail=True, params=params)
self._set_leverage(leverage, pair, accept_fail=True)
def _get_params(
self,
side: BuySell,
ordertype: str,
leverage: float,
reduceOnly: bool,
time_in_force: str = 'GTC',
) -> Dict:
params = super()._get_params(
side=side,
ordertype=ordertype,
leverage=leverage,
reduceOnly=reduceOnly,
time_in_force=time_in_force,
)
if self.trading_mode == TradingMode.FUTURES and self.margin_mode:
params['position_idx'] = 0
return params
def dry_run_liquidation_price(
self,
pair: str,
open_rate: float, # Entry price of position
is_short: bool,
amount: float,
stake_amount: float,
leverage: float,
wallet_balance: float, # Or margin balance
mm_ex_1: float = 0.0, # (Binance) Cross only
upnl_ex_1: float = 0.0, # (Binance) Cross only
) -> Optional[float]:
"""
Important: Must be fetching data from cached values as this is used by backtesting!
PERPETUAL:
bybit:
https://www.bybithelp.com/HelpCenterKnowledge/bybitHC_Article?language=en_US&id=000001067
Long:
Liquidation Price = (
Entry Price * (1 - Initial Margin Rate + Maintenance Margin Rate)
- Extra Margin Added/ Contract)
Short:
Liquidation Price = (
Entry Price * (1 + Initial Margin Rate - Maintenance Margin Rate)
+ Extra Margin Added/ Contract)
Implementation Note: Extra margin is currently not used.
:param pair: Pair to calculate liquidation price for
:param open_rate: Entry price of position
:param is_short: True if the trade is a short, false otherwise
:param amount: Absolute value of position size incl. leverage (in base currency)
:param stake_amount: Stake amount - Collateral in settle currency.
:param leverage: Leverage used for this position.
:param trading_mode: SPOT, MARGIN, FUTURES, etc.
:param margin_mode: Either ISOLATED or CROSS
:param wallet_balance: Amount of margin_mode in the wallet being used to trade
Cross-Margin Mode: crossWalletBalance
Isolated-Margin Mode: isolatedWalletBalance
"""
market = self.markets[pair]
mm_ratio, _ = self.get_maintenance_ratio_and_amt(pair, stake_amount)
if self.trading_mode == TradingMode.FUTURES and self.margin_mode == MarginMode.ISOLATED:
if market['inverse']:
raise OperationalException(
"Freqtrade does not yet support inverse contracts")
initial_margin_rate = 1 / leverage
# See docstring - ignores extra margin!
if is_short:
return open_rate * (1 + initial_margin_rate - mm_ratio)
else:
return open_rate * (1 - initial_margin_rate + mm_ratio)
else:
raise OperationalException(
"Freqtrade only supports isolated futures for leverage trading")
def get_funding_fees(
self, pair: str, amount: float, is_short: bool, 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 is_short: trade direction
:param amount: Trade amount
:param open_date: Open date of the trade
:return: funding fee since open_date
:raises: ExchangeError if something goes wrong.
"""
# Bybit does not provide "applied" funding fees per position.
if self.trading_mode == TradingMode.FUTURES:
return self._fetch_and_calculate_funding_fees(
pair, amount, is_short, open_date)
return 0.0

View File

@ -2484,7 +2484,8 @@ class Exchange:
self, self,
leverage: float, leverage: float,
pair: Optional[str] = None, pair: Optional[str] = None,
trading_mode: Optional[TradingMode] = None trading_mode: Optional[TradingMode] = None,
accept_fail: bool = False,
): ):
""" """
Set's the leverage before making a trade, in order to not Set's the leverage before making a trade, in order to not
@ -2499,6 +2500,10 @@ class Exchange:
self._log_exchange_response('set_leverage', res) self._log_exchange_response('set_leverage', res)
except ccxt.DDoSProtection as e: except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e raise DDosProtection(e) from e
except ccxt.BadRequest as e:
if not accept_fail:
raise TemporaryError(
f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e: except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError( raise TemporaryError(
f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e
@ -2520,7 +2525,8 @@ class Exchange:
return open_date.minute > 0 or open_date.second > 0 return open_date.minute > 0 or open_date.second > 0
@retrier @retrier
def set_margin_mode(self, pair: str, margin_mode: MarginMode, params: dict = {}): def set_margin_mode(self, pair: str, margin_mode: MarginMode, accept_fail: bool = False,
params: dict = {}):
""" """
Set's the margin mode on the exchange to cross or isolated for a specific pair 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") :param pair: base/quote currency pair (e.g. "ADA/USDT")
@ -2534,6 +2540,10 @@ class Exchange:
self._log_exchange_response('set_margin_mode', res) self._log_exchange_response('set_margin_mode', res)
except ccxt.DDoSProtection as e: except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e raise DDosProtection(e) from e
except ccxt.BadRequest as e:
if not accept_fail:
raise TemporaryError(
f'Could not set margin mode due to {e.__class__.__name__}. Message: {e}') from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e: except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError( raise TemporaryError(
f'Could not set margin mode due to {e.__class__.__name__}. Message: {e}') from e f'Could not set margin mode due to {e.__class__.__name__}. Message: {e}') from e
@ -2687,6 +2697,7 @@ class Exchange:
is_short: bool, is_short: bool,
amount: float, # Absolute value of position size amount: float, # Absolute value of position size
stake_amount: float, stake_amount: float,
leverage: float,
wallet_balance: float, wallet_balance: float,
mm_ex_1: float = 0.0, # (Binance) Cross only mm_ex_1: float = 0.0, # (Binance) Cross only
upnl_ex_1: float = 0.0, # (Binance) Cross only upnl_ex_1: float = 0.0, # (Binance) Cross only
@ -2708,6 +2719,7 @@ class Exchange:
open_rate=open_rate, open_rate=open_rate,
is_short=is_short, is_short=is_short,
amount=amount, amount=amount,
leverage=leverage,
stake_amount=stake_amount, stake_amount=stake_amount,
wallet_balance=wallet_balance, wallet_balance=wallet_balance,
mm_ex_1=mm_ex_1, mm_ex_1=mm_ex_1,
@ -2737,6 +2749,7 @@ class Exchange:
is_short: bool, is_short: bool,
amount: float, amount: float,
stake_amount: float, stake_amount: float,
leverage: float,
wallet_balance: float, # Or margin balance wallet_balance: float, # Or margin balance
mm_ex_1: float = 0.0, # (Binance) Cross only mm_ex_1: float = 0.0, # (Binance) Cross only
upnl_ex_1: float = 0.0, # (Binance) Cross only upnl_ex_1: float = 0.0, # (Binance) Cross only
@ -2758,6 +2771,7 @@ class Exchange:
:param is_short: True if the trade is a short, false otherwise :param is_short: True if the trade is a short, false otherwise
:param amount: Absolute value of position size incl. leverage (in base currency) :param amount: Absolute value of position size incl. leverage (in base currency)
:param stake_amount: Stake amount - Collateral in settle currency. :param stake_amount: Stake amount - Collateral in settle currency.
:param leverage: Leverage used for this position.
:param trading_mode: SPOT, MARGIN, FUTURES, etc. :param trading_mode: SPOT, MARGIN, FUTURES, etc.
:param margin_mode: Either ISOLATED or CROSS :param margin_mode: Either ISOLATED or CROSS
:param wallet_balance: Amount of margin_mode in the wallet being used to trade :param wallet_balance: Amount of margin_mode in the wallet being used to trade

View File

@ -158,7 +158,8 @@ class Kraken(Exchange):
self, self,
leverage: float, leverage: float,
pair: Optional[str] = None, pair: Optional[str] = None,
trading_mode: Optional[TradingMode] = None trading_mode: Optional[TradingMode] = None,
accept_fail: bool = False,
): ):
""" """
Kraken set's the leverage as an option in the order object, so we need to Kraken set's the leverage as an option in the order object, so we need to

View File

@ -1789,6 +1789,7 @@ class FreqtradeBot(LoggingMixin):
is_short=trade.is_short, is_short=trade.is_short,
amount=trade.amount, amount=trade.amount,
stake_amount=trade.stake_amount, stake_amount=trade.stake_amount,
leverage=trade.leverage,
wallet_balance=trade.stake_amount, wallet_balance=trade.stake_amount,
)) ))

View File

@ -775,6 +775,11 @@ class Backtesting:
trade: Optional[LocalTrade] = None, trade: Optional[LocalTrade] = None,
requested_rate: Optional[float] = None, requested_rate: Optional[float] = None,
requested_stake: Optional[float] = None) -> Optional[LocalTrade]: requested_stake: Optional[float] = None) -> Optional[LocalTrade]:
"""
:param trade: Trade to adjust - initial entry if None
:param requested_rate: Adjusted entry rate
:param requested_stake: Stake amount for adjusted orders (`adjust_entry_price`).
"""
current_time = row[DATE_IDX].to_pydatetime() current_time = row[DATE_IDX].to_pydatetime()
entry_tag = row[ENTER_TAG_IDX] if len(row) >= ENTER_TAG_IDX + 1 else None entry_tag = row[ENTER_TAG_IDX] if len(row) >= ENTER_TAG_IDX + 1 else None
@ -800,7 +805,7 @@ class Backtesting:
return trade return trade
time_in_force = self.strategy.order_time_in_force['entry'] time_in_force = self.strategy.order_time_in_force['entry']
if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount): if stake_amount and (not min_stake_amount or stake_amount >= min_stake_amount):
self.order_id_counter += 1 self.order_id_counter += 1
base_currency = self.exchange.get_pair_base_currency(pair) base_currency = self.exchange.get_pair_base_currency(pair)
amount_p = (stake_amount / propose_rate) * leverage amount_p = (stake_amount / propose_rate) * leverage
@ -863,6 +868,7 @@ class Backtesting:
open_rate=propose_rate, open_rate=propose_rate,
amount=amount, amount=amount,
stake_amount=trade.stake_amount, stake_amount=trade.stake_amount,
leverage=trade.leverage,
wallet_balance=trade.stake_amount, wallet_balance=trade.stake_amount,
is_short=is_short, is_short=is_short,
)) ))

View File

@ -44,7 +44,7 @@ class SharpeHyperOptLossDaily(IHyperOptLoss):
sum_daily = ( sum_daily = (
results.resample(resample_freq, on='close_date').agg( results.resample(resample_freq, on='close_date').agg(
{"profit_ratio_after_slippage": sum}).reindex(t_index).fillna(0) {"profit_ratio_after_slippage": 'sum'}).reindex(t_index).fillna(0)
) )
total_profit = sum_daily["profit_ratio_after_slippage"] - risk_free_rate total_profit = sum_daily["profit_ratio_after_slippage"] - risk_free_rate

View File

@ -46,7 +46,7 @@ class SortinoHyperOptLossDaily(IHyperOptLoss):
sum_daily = ( sum_daily = (
results.resample(resample_freq, on='close_date').agg( results.resample(resample_freq, on='close_date').agg(
{"profit_ratio_after_slippage": sum}).reindex(t_index).fillna(0) {"profit_ratio_after_slippage": 'sum'}).reindex(t_index).fillna(0)
) )
total_profit = sum_daily["profit_ratio_after_slippage"] - minimum_acceptable_return total_profit = sum_daily["profit_ratio_after_slippage"] - minimum_acceptable_return

View File

@ -90,7 +90,7 @@ async def _process_consumer_request(
elif type == RPCRequestType.ANALYZED_DF: elif type == RPCRequestType.ANALYZED_DF:
# Limit the amount of candles per dataframe to 'limit' or 1500 # Limit the amount of candles per dataframe to 'limit' or 1500
limit = min(data.get('limit', 1500), 1500) if data else None limit = int(min(data.get('limit', 1500), 1500)) if data else None
pair = data.get('pair', None) if data else None pair = data.get('pair', None) if data else None
# For every pair in the generator, send a separate message # For every pair in the generator, send a separate message

View File

@ -3,7 +3,7 @@
# Required for freqai-rl # Required for freqai-rl
torch==1.13.1 torch==1.13.1
stable-baselines3==1.6.2 stable-baselines3==1.7.0
sb3-contrib==1.6.2 sb3-contrib==1.7.0
# Gym is forced to this version by stable-baselines3. # Gym is forced to this version by stable-baselines3.
gym==0.21 gym==0.21

View File

@ -6,6 +6,6 @@
scikit-learn==1.1.3 scikit-learn==1.1.3
joblib==1.2.0 joblib==1.2.0
catboost==1.1.1; platform_machine != 'aarch64' catboost==1.1.1; platform_machine != 'aarch64'
lightgbm==3.3.4 lightgbm==3.3.5
xgboost==1.7.3 xgboost==1.7.3
tensorboard==2.11.2 tensorboard==2.11.2

View File

@ -2,7 +2,7 @@ numpy==1.24.1
pandas==1.5.3 pandas==1.5.3
pandas-ta==0.3.14b pandas-ta==0.3.14b
ccxt==2.6.65 ccxt==2.7.12
# Pin cryptography for now due to rust build errors with piwheels # Pin cryptography for now due to rust build errors with piwheels
cryptography==38.0.1; platform_machine == 'armv7l' cryptography==38.0.1; platform_machine == 'armv7l'
cryptography==39.0.0; platform_machine != 'armv7l' cryptography==39.0.0; platform_machine != 'armv7l'
@ -22,7 +22,7 @@ jinja2==3.1.2
tables==3.8.0 tables==3.8.0
blosc==1.11.1 blosc==1.11.1
joblib==1.2.0 joblib==1.2.0
pyarrow==10.0.1; platform_machine != 'armv7l' pyarrow==11.0.0; platform_machine != 'armv7l'
# find first, C search in arrays # find first, C search in arrays
py_find_1st==1.1.5 py_find_1st==1.1.5

View File

@ -437,6 +437,7 @@ def test_dp__add_external_df(default_conf_usdt):
# Add the same dataframe again - dataframe size shall not change. # Add the same dataframe again - dataframe size shall not change.
res = dp._add_external_df('ETH/USDT', df, last_analyzed, timeframe, CandleType.SPOT) res = dp._add_external_df('ETH/USDT', df, last_analyzed, timeframe, CandleType.SPOT)
assert res[0] is True assert res[0] is True
assert isinstance(res[1], int)
assert res[1] == 0 assert res[1] == 0
df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT) df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT)
assert len(df) == 24 assert len(df) == 24
@ -446,6 +447,7 @@ def test_dp__add_external_df(default_conf_usdt):
res = dp._add_external_df('ETH/USDT', df2, last_analyzed, timeframe, CandleType.SPOT) res = dp._add_external_df('ETH/USDT', df2, last_analyzed, timeframe, CandleType.SPOT)
assert res[0] is True assert res[0] is True
assert isinstance(res[1], int)
assert res[1] == 0 assert res[1] == 0
df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT) df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT)
assert len(df) == 48 assert len(df) == 48
@ -455,6 +457,7 @@ def test_dp__add_external_df(default_conf_usdt):
res = dp._add_external_df('ETH/USDT', df3, last_analyzed, timeframe, CandleType.SPOT) res = dp._add_external_df('ETH/USDT', df3, last_analyzed, timeframe, CandleType.SPOT)
assert res[0] is True assert res[0] is True
assert isinstance(res[1], int)
assert res[1] == 0 assert res[1] == 0
df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT) df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT)
# New length = 48 + 12 (since we have a 12 hour offset). # New length = 48 + 12 (since we have a 12 hour offset).
@ -478,6 +481,7 @@ def test_dp__add_external_df(default_conf_usdt):
res = dp._add_external_df('ETH/USDT', df4, last_analyzed, timeframe, CandleType.SPOT) res = dp._add_external_df('ETH/USDT', df4, last_analyzed, timeframe, CandleType.SPOT)
assert res[0] is False assert res[0] is False
# 36 hours - from 2022-01-03 12:00:00+00:00 to 2022-01-05 00:00:00+00:00 # 36 hours - from 2022-01-03 12:00:00+00:00 to 2022-01-05 00:00:00+00:00
assert isinstance(res[1], int)
assert res[1] == 36 assert res[1] == 36
df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT) df, _ = dp.get_producer_df('ETH/USDT', timeframe, CandleType.SPOT)
# New length = 61 + 1 # New length = 61 + 1
@ -488,4 +492,5 @@ def test_dp__add_external_df(default_conf_usdt):
res = dp._add_external_df('ETH/USDT', df4, last_analyzed, timeframe, CandleType.SPOT) res = dp._add_external_df('ETH/USDT', df4, last_analyzed, timeframe, CandleType.SPOT)
assert res[0] is False assert res[0] is False
# 36 hours - from 2022-01-03 12:00:00+00:00 to 2022-01-05 00:00:00+00:00 # 36 hours - from 2022-01-03 12:00:00+00:00 to 2022-01-05 00:00:00+00:00
assert isinstance(res[1], int)
assert res[1] == 0 assert res[1] == 0

View File

@ -0,0 +1,57 @@
from unittest.mock import MagicMock
from freqtrade.enums.marginmode import MarginMode
from freqtrade.enums.tradingmode import TradingMode
from freqtrade.exchange.exchange_utils import timeframe_to_msecs
from tests.conftest import get_mock_coro, get_patched_exchange
from tests.exchange.test_exchange import ccxt_exceptionhandlers
def test_additional_exchange_init_bybit(default_conf, mocker):
default_conf['dry_run'] = False
default_conf['trading_mode'] = TradingMode.FUTURES
default_conf['margin_mode'] = MarginMode.ISOLATED
api_mock = MagicMock()
api_mock.set_position_mode = MagicMock(return_value={"dualSidePosition": False})
get_patched_exchange(mocker, default_conf, id="bybit", api_mock=api_mock)
assert api_mock.set_position_mode.call_count == 1
ccxt_exceptionhandlers(mocker, default_conf, api_mock, 'bybit',
"additional_exchange_init", "set_position_mode")
async def test_bybit_fetch_funding_rate(default_conf, mocker):
default_conf['trading_mode'] = 'futures'
default_conf['margin_mode'] = 'isolated'
api_mock = MagicMock()
api_mock.fetch_funding_rate_history = get_mock_coro(return_value=[])
exchange = get_patched_exchange(mocker, default_conf, id='bybit', api_mock=api_mock)
limit = 200
# Test fetch_funding_rate_history (current data)
await exchange._fetch_funding_rate_history(
pair='BTC/USDT:USDT',
timeframe='4h',
limit=limit,
)
assert api_mock.fetch_funding_rate_history.call_count == 1
assert api_mock.fetch_funding_rate_history.call_args_list[0][0][0] == 'BTC/USDT:USDT'
kwargs = api_mock.fetch_funding_rate_history.call_args_list[0][1]
assert kwargs['params'] == {}
assert kwargs['since'] is None
api_mock.fetch_funding_rate_history.reset_mock()
since_ms = 1610000000000
since_ms_end = since_ms + (timeframe_to_msecs('4h') * limit)
# Test fetch_funding_rate_history (current data)
await exchange._fetch_funding_rate_history(
pair='BTC/USDT:USDT',
timeframe='4h',
limit=limit,
since_ms=since_ms,
)
assert api_mock.fetch_funding_rate_history.call_count == 1
assert api_mock.fetch_funding_rate_history.call_args_list[0][0][0] == 'BTC/USDT:USDT'
kwargs = api_mock.fetch_funding_rate_history.call_args_list[0][1]
assert kwargs['params'] == {'until': since_ms_end}
assert kwargs['since'] == since_ms

View File

@ -43,7 +43,7 @@ EXCHANGES = {
'hasQuoteVolumeFutures': True, 'hasQuoteVolumeFutures': True,
'leverage_tiers_public': False, 'leverage_tiers_public': False,
'leverage_in_spot_market': False, 'leverage_in_spot_market': False,
'sample_order': { 'sample_order': [{
"symbol": "SOLUSDT", "symbol": "SOLUSDT",
"orderId": 3551312894, "orderId": 3551312894,
"orderListId": -1, "orderListId": -1,
@ -60,7 +60,32 @@ EXCHANGES = {
"workingTime": 1674493798550, "workingTime": 1674493798550,
"fills": [], "fills": [],
"selfTradePreventionMode": "NONE", "selfTradePreventionMode": "NONE",
} }]
},
'binanceus': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'futures': False,
'sample_order': [{
"symbol": "SOLUSDT",
"orderId": 3551312894,
"orderListId": -1,
"clientOrderId": "x-R4DD3S8297c73a11ccb9dc8f2811ba",
"transactTime": 1674493798550,
"price": "15.00000000",
"origQty": "1.00000000",
"executedQty": "0.00000000",
"cummulativeQuoteQty": "0.00000000",
"status": "NEW",
"timeInForce": "GTC",
"type": "LIMIT",
"side": "BUY",
"workingTime": 1674493798550,
"fills": [],
"selfTradePreventionMode": "NONE",
}]
}, },
'kraken': { 'kraken': {
'pair': 'BTC/USDT', 'pair': 'BTC/USDT',
@ -77,6 +102,40 @@ EXCHANGES = {
'timeframe': '5m', 'timeframe': '5m',
'leverage_tiers_public': False, 'leverage_tiers_public': False,
'leverage_in_spot_market': True, 'leverage_in_spot_market': True,
'sample_order': [
{'id': '63d6742d0adc5570001d2bbf7'}, # create order
{
'id': '63d6742d0adc5570001d2bbf7',
'symbol': 'NAKA-USDT',
'opType': 'DEAL',
'type': 'limit',
'side': 'buy',
'price': '30',
'size': '0.1',
'funds': '0',
'dealFunds': '0.032626',
'dealSize': '0.1',
'fee': '0.000065252',
'feeCurrency': 'USDT',
'stp': '',
'stop': '',
'stopTriggered': False,
'stopPrice': '0',
'timeInForce': 'GTC',
'postOnly': False,
'hidden': False,
'iceberg': False,
'visibleSize': '0',
'cancelAfter': 0,
'channel': 'API',
'clientOid': '0a053870-11bf-41e5-be61-b272a4cb62e1',
'remark': None,
'tags': 'partner:ccxt',
'isActive': False,
'cancelExist': False,
'createdAt': 1674493798550,
'tradeType': 'TRADE'
}],
}, },
'gateio': { 'gateio': {
'pair': 'BTC/USDT', 'pair': 'BTC/USDT',
@ -100,6 +159,16 @@ EXCHANGES = {
'leverage_tiers_public': True, 'leverage_tiers_public': True,
'leverage_in_spot_market': True, 'leverage_in_spot_market': True,
}, },
'bybit': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'hasQuoteVolume': True,
'timeframe': '5m',
'futures_pair': 'BTC/USDT:USDT',
'futures': True,
'leverage_tiers_public': True,
'leverage_in_spot_market': True,
},
'huobi': { 'huobi': {
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'stake_currency': 'BTC', 'stake_currency': 'BTC',
@ -176,6 +245,7 @@ def exchange_futures(request, exchange_conf, class_mocker):
class_mocker.patch('freqtrade.exchange.exchange.Exchange.fetch_trading_fees') class_mocker.patch('freqtrade.exchange.exchange.Exchange.fetch_trading_fees')
class_mocker.patch('freqtrade.exchange.okx.Okx.additional_exchange_init') class_mocker.patch('freqtrade.exchange.okx.Okx.additional_exchange_init')
class_mocker.patch('freqtrade.exchange.binance.Binance.additional_exchange_init') class_mocker.patch('freqtrade.exchange.binance.Binance.additional_exchange_init')
class_mocker.patch('freqtrade.exchange.bybit.Bybit.additional_exchange_init')
class_mocker.patch('freqtrade.exchange.exchange.Exchange.load_cached_leverage_tiers', class_mocker.patch('freqtrade.exchange.exchange.Exchange.load_cached_leverage_tiers',
return_value=None) return_value=None)
class_mocker.patch('freqtrade.exchange.exchange.Exchange.cache_leverage_tiers') class_mocker.patch('freqtrade.exchange.exchange.Exchange.cache_leverage_tiers')
@ -231,14 +301,18 @@ class TestCCXTExchange():
def test_ccxt_order_parse(self, exchange: EXCHANGE_FIXTURE_TYPE): def test_ccxt_order_parse(self, exchange: EXCHANGE_FIXTURE_TYPE):
exch, exchange_name = exchange exch, exchange_name = exchange
if stuff := EXCHANGES[exchange_name].get('sample_order'): if orders := EXCHANGES[exchange_name].get('sample_order'):
for order in orders:
po = exch._api.parse_order(stuff) po = exch._api.parse_order(order)
assert po['timestamp'] == 1674493798550 assert isinstance(po['id'], str)
assert isinstance(po['timestamp'], int) assert po['id'] is not None
assert isinstance(po['price'], float) if len(order.keys()) > 1:
assert isinstance(po['amount'], float) assert po['timestamp'] == 1674493798550
assert isinstance(po['status'], str) assert isinstance(po['datetime'], str)
assert isinstance(po['timestamp'], int)
assert isinstance(po['price'], float)
assert isinstance(po['amount'], float)
assert isinstance(po['status'], str)
else: else:
pytest.skip(f"No sample order available for exchange {exchange_name}") pytest.skip(f"No sample order available for exchange {exchange_name}")
@ -553,23 +627,25 @@ class TestCCXTExchange():
) )
liquidation_price = futures.dry_run_liquidation_price( liquidation_price = futures.dry_run_liquidation_price(
futures_pair, pair=futures_pair,
40000, open_rate=40000,
False, is_short=False,
100, amount=100,
100, stake_amount=100,
100, leverage=5,
wallet_balance=100,
) )
assert (isinstance(liquidation_price, float)) assert (isinstance(liquidation_price, float))
assert liquidation_price >= 0.0 assert liquidation_price >= 0.0
liquidation_price = futures.dry_run_liquidation_price( liquidation_price = futures.dry_run_liquidation_price(
futures_pair, pair=futures_pair,
40000, open_rate=40000,
False, is_short=False,
100, amount=100,
100, stake_amount=100,
100, leverage=5,
wallet_balance=100,
) )
assert (isinstance(liquidation_price, float)) assert (isinstance(liquidation_price, float))
assert liquidation_price >= 0.0 assert liquidation_price >= 0.0

View File

@ -3959,7 +3959,7 @@ def test_validate_trading_mode_and_margin_mode(
("binance", "margin", {"options": {"defaultType": "margin"}}), ("binance", "margin", {"options": {"defaultType": "margin"}}),
("binance", "futures", {"options": {"defaultType": "swap"}}), ("binance", "futures", {"options": {"defaultType": "swap"}}),
("bybit", "spot", {"options": {"defaultType": "spot"}}), ("bybit", "spot", {"options": {"defaultType": "spot"}}),
("bybit", "futures", {"options": {"defaultType": "linear"}}), ("bybit", "futures", {"options": {"defaultType": "swap"}}),
("gateio", "futures", {"options": {"defaultType": "swap"}}), ("gateio", "futures", {"options": {"defaultType": "swap"}}),
("hitbtc", "futures", {"options": {"defaultType": "swap"}}), ("hitbtc", "futures", {"options": {"defaultType": "swap"}}),
("kraken", "futures", {"options": {"defaultType": "swap"}}), ("kraken", "futures", {"options": {"defaultType": "swap"}}),
@ -4566,6 +4566,7 @@ def test_liquidation_price_is_none(
is_short=is_short, is_short=is_short,
amount=71200.81144, amount=71200.81144,
stake_amount=open_rate * 71200.81144, stake_amount=open_rate * 71200.81144,
leverage=5,
wallet_balance=-56354.57, wallet_balance=-56354.57,
mm_ex_1=0.10, mm_ex_1=0.10,
upnl_ex_1=0.0 upnl_ex_1=0.0
@ -4586,7 +4587,7 @@ def test_liquidation_price_is_none(
("binance", False, 'futures', 'cross', 1535443.01, 356512.508, ("binance", False, 'futures', 'cross', 1535443.01, 356512.508,
-448192.89, 16300.000, 109.488, 32481.980, 0.025, 26316.89) -448192.89, 16300.000, 109.488, 32481.980, 0.025, 26316.89)
]) ])
def test_liquidation_price( def test_liquidation_price_binance(
mocker, default_conf, exchange_name, open_rate, is_short, trading_mode, mocker, default_conf, exchange_name, open_rate, is_short, trading_mode,
margin_mode, wallet_balance, mm_ex_1, upnl_ex_1, maintenance_amt, amount, mm_ratio, expected margin_mode, wallet_balance, mm_ex_1, upnl_ex_1, maintenance_amt, amount, mm_ratio, expected
): ):
@ -4604,6 +4605,7 @@ def test_liquidation_price(
upnl_ex_1=upnl_ex_1, upnl_ex_1=upnl_ex_1,
amount=amount, amount=amount,
stake_amount=open_rate * amount, stake_amount=open_rate * amount,
leverage=5,
), 2)) == expected ), 2)) == expected
@ -5025,6 +5027,7 @@ def test__get_params(mocker, default_conf, exchange_name):
def test_get_liquidation_price1(mocker, default_conf): def test_get_liquidation_price1(mocker, default_conf):
api_mock = MagicMock() api_mock = MagicMock()
leverage = 9.97
positions = [ positions = [
{ {
'info': {}, 'info': {},
@ -5037,7 +5040,7 @@ def test_get_liquidation_price1(mocker, default_conf):
'maintenanceMarginPercentage': 0.025, 'maintenanceMarginPercentage': 0.025,
'entryPrice': 18.884, 'entryPrice': 18.884,
'notional': 15.1072, 'notional': 15.1072,
'leverage': 9.97, 'leverage': leverage,
'unrealizedPnl': 0.0048, 'unrealizedPnl': 0.0048,
'contracts': 8, 'contracts': 8,
'contractSize': 0.1, 'contractSize': 0.1,
@ -5067,6 +5070,7 @@ def test_get_liquidation_price1(mocker, default_conf):
is_short=False, is_short=False,
amount=0.8, amount=0.8,
stake_amount=18.884 * 0.8, stake_amount=18.884 * 0.8,
leverage=leverage,
wallet_balance=18.884 * 0.8, wallet_balance=18.884 * 0.8,
) )
assert liq_price == 17.47 assert liq_price == 17.47
@ -5079,6 +5083,7 @@ def test_get_liquidation_price1(mocker, default_conf):
is_short=False, is_short=False,
amount=0.8, amount=0.8,
stake_amount=18.884 * 0.8, stake_amount=18.884 * 0.8,
leverage=leverage,
wallet_balance=18.884 * 0.8, wallet_balance=18.884 * 0.8,
) )
assert liq_price == 17.540699999999998 assert liq_price == 17.540699999999998
@ -5091,6 +5096,7 @@ def test_get_liquidation_price1(mocker, default_conf):
is_short=False, is_short=False,
amount=0.8, amount=0.8,
stake_amount=18.884 * 0.8, stake_amount=18.884 * 0.8,
leverage=leverage,
wallet_balance=18.884 * 0.8, wallet_balance=18.884 * 0.8,
) )
assert liq_price is None assert liq_price is None
@ -5104,11 +5110,12 @@ def test_get_liquidation_price1(mocker, default_conf):
is_short=False, is_short=False,
amount=0.8, amount=0.8,
stake_amount=18.884 * 0.8, stake_amount=18.884 * 0.8,
leverage=leverage,
wallet_balance=18.884 * 0.8, wallet_balance=18.884 * 0.8,
) )
@pytest.mark.parametrize('liquidation_buffer', [0.0, 0.05]) @pytest.mark.parametrize('liquidation_buffer', [0.0])
@pytest.mark.parametrize( @pytest.mark.parametrize(
"is_short,trading_mode,exchange_name,margin_mode,leverage,open_rate,amount,expected_liq", [ "is_short,trading_mode,exchange_name,margin_mode,leverage,open_rate,amount,expected_liq", [
(False, 'spot', 'binance', '', 5.0, 10.0, 1.0, None), (False, 'spot', 'binance', '', 5.0, 10.0, 1.0, None),
@ -5137,6 +5144,16 @@ def test_get_liquidation_price1(mocker, default_conf):
(False, 'futures', 'gateio', 'isolated', 5.0, 10.0, 1.0, 8.085708510208207), (False, 'futures', 'gateio', 'isolated', 5.0, 10.0, 1.0, 8.085708510208207),
(False, 'futures', 'gateio', 'isolated', 3.0, 10.0, 1.0, 6.738090425173506), (False, 'futures', 'gateio', 'isolated', 3.0, 10.0, 1.0, 6.738090425173506),
(False, 'futures', 'okx', 'isolated', 3.0, 10.0, 1.0, 6.738090425173506), (False, 'futures', 'okx', 'isolated', 3.0, 10.0, 1.0, 6.738090425173506),
# bybit, long
(False, 'futures', 'bybit', 'isolated', 1.0, 10.0, 1.0, 0.1),
(False, 'futures', 'bybit', 'isolated', 3.0, 10.0, 1.0, 6.7666666),
(False, 'futures', 'bybit', 'isolated', 5.0, 10.0, 1.0, 8.1),
(False, 'futures', 'bybit', 'isolated', 10.0, 10.0, 1.0, 9.1),
# bybit, short
(True, 'futures', 'bybit', 'isolated', 1.0, 10.0, 1.0, 19.9),
(True, 'futures', 'bybit', 'isolated', 3.0, 10.0, 1.0, 13.233333),
(True, 'futures', 'bybit', 'isolated', 5.0, 10.0, 1.0, 11.9),
(True, 'futures', 'bybit', 'isolated', 10.0, 10.0, 1.0, 10.9),
] ]
) )
def test_get_liquidation_price( def test_get_liquidation_price(
@ -5222,7 +5239,7 @@ def test_get_liquidation_price(
amount=amount, amount=amount,
stake_amount=amount * open_rate / leverage, stake_amount=amount * open_rate / leverage,
wallet_balance=amount * open_rate / leverage, wallet_balance=amount * open_rate / leverage,
# leverage=leverage, leverage=leverage,
is_short=is_short, is_short=is_short,
) )
if expected_liq is None: if expected_liq is None:

View File

@ -751,6 +751,8 @@ def test_process_informative_pairs_added(default_conf_usdt, ticker_usdt, mocker)
(False, 'futures', 'gateio', 'isolated', 0.05, 8.181423084697796), (False, 'futures', 'gateio', 'isolated', 0.05, 8.181423084697796),
(True, 'futures', 'okx', 'isolated', 0.0, 11.87413417771621), (True, 'futures', 'okx', 'isolated', 0.0, 11.87413417771621),
(False, 'futures', 'okx', 'isolated', 0.0, 8.085708510208207), (False, 'futures', 'okx', 'isolated', 0.0, 8.085708510208207),
(True, 'futures', 'bybit', 'isolated', 0.0, 11.9),
(False, 'futures', 'bybit', 'isolated', 0.0, 8.1),
]) ])
def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, def test_execute_entry(mocker, default_conf_usdt, fee, limit_order,
limit_order_open, is_short, trading_mode, limit_order_open, is_short, trading_mode,