diff --git a/freqtrade/enums/marginmode.py b/freqtrade/enums/marginmode.py index 999051ca4..1e42809ea 100644 --- a/freqtrade/enums/marginmode.py +++ b/freqtrade/enums/marginmode.py @@ -9,3 +9,4 @@ class MarginMode(Enum): """ CROSS = "cross" ISOLATED = "isolated" + NONE = '' diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index b3098d3d4..7a89ac9a6 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -941,11 +941,10 @@ class Exchange: side: str, amount: float, rate: float, + leverage: float, reduceOnly: bool = False, - 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 diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index e6e1ae5e1..ce76bcdaf 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -605,7 +605,7 @@ class FreqtradeBot(LoggingMixin): self, pair: str, open_rate: float, - amount: float, + amount: float, # quote currency, includes leverage leverage: float, is_short: bool ) -> Tuple[float, Optional[float]]: @@ -1285,7 +1285,8 @@ class FreqtradeBot(LoggingMixin): # to the order dict acquired before cancelling. # we need to fall back to the values from order if corder does not contain these keys. trade.amount = filled_amount - # TODO-lev: Check edge cases, we don't want to make leverage > 1.0 if we don't have to + # * Check edge cases, we don't want to make leverage > 1.0 if we don't have to + # * (for leverage modes which aren't isolated futures) trade.stake_amount = trade.amount * trade.open_rate self.update_trade_state(trade, trade.open_order_id, corder) @@ -1352,13 +1353,14 @@ class FreqtradeBot(LoggingMixin): :return: amount to sell :raise: DependencyException: if available balance is not within 2% of the available amount. """ - # TODO-lev Maybe update? # Update wallets to ensure amounts tied up in a stoploss is now free! self.wallets.update() trade_base_currency = self.exchange.get_pair_base_currency(pair) wallet_amount = self.wallets.get_free(trade_base_currency) logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}") - if wallet_amount >= amount: + # TODO-lev: Get wallet amount + value of positions + if wallet_amount >= amount or self.trading_mode == TradingMode.FUTURES: + # A safe exit amount isn't needed for futures, you can just exit/close the position return amount elif wallet_amount > amount * 0.98: logger.info(f"{pair} - Falling back to wallet-amount {wallet_amount} -> {amount}.") @@ -1436,6 +1438,7 @@ class FreqtradeBot(LoggingMixin): side=trade.exit_side, amount=amount, rate=limit, + leverage=trade.leverage, reduceOnly=self.trading_mode == TradingMode.FUTURES, time_in_force=time_in_force ) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 56894e13b..872f05f26 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -333,8 +333,12 @@ class Backtesting: df_analyzed.loc[:, col] = 0 if not tag_col else None # Update dataprovider cache - self.dataprovider._set_cached_df(pair, self.timeframe, df_analyzed, CandleType.SPOT) - # TODO-lev: Candle-type should be conditional, either "spot" or futures + self.dataprovider._set_cached_df( + pair, + self.timeframe, + df_analyzed, + self.config['candle_type_def'] + ) df_analyzed = df_analyzed.drop(df_analyzed.head(1).index) @@ -499,7 +503,7 @@ class Backtesting: sell_candle_time: datetime = sell_row[DATE_IDX].to_pydatetime() if self.trading_mode == TradingMode.FUTURES: - # TODO-lev: Other fees / liquidation price? + # TODO-lev: liquidation price? trade.funding_fees = self.exchange.calculate_funding_fees( self.futures_data[trade.pair], amount=trade.amount, diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index afee0725f..2fb0f39c9 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -569,7 +569,6 @@ class LocalTrade(): payment = "BUY" if self.is_short else "SELL" # * On margin shorts, you buy a little bit more than the amount (amount + interest) logger.info(f'{order_type.upper()}_{payment} has been fulfilled for {self}.') - # TODO-lev: Double check this self.close(safe_value_fallback(order, 'average', 'price')) elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'): self.stoploss_order_id = None diff --git a/freqtrade/plot/plotting.py b/freqtrade/plot/plotting.py index 3a5ff4311..fee84e5ea 100644 --- a/freqtrade/plot/plotting.py +++ b/freqtrade/plot/plotting.py @@ -431,7 +431,6 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra ) fig.add_trace(candles, 1, 1) - # TODO-lev: Needs short equivalent if 'enter_long' in data.columns: df_buy = data[data['enter_long'] == 1] if len(df_buy) > 0: @@ -537,7 +536,7 @@ def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame], "Profit per pair", "Parallelism", "Underwater", - ]) + ]) fig['layout'].update(title="Freqtrade Profit plot") fig['layout']['yaxis1'].update(title='Price') fig['layout']['yaxis2'].update(title=f'Profit {stake_currency}') diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 7be9a2207..fda66b32e 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1227,7 +1227,8 @@ def test_buy_dry_run(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf) order = exchange.create_order(pair='ETH/BTC', ordertype='limit', side="buy", - amount=1, rate=200, time_in_force='gtc') + amount=1, rate=200, leverage=1.0, + time_in_force='gtc') assert 'id' in order assert 'dry_run_buy_' in order['id'] @@ -1252,7 +1253,8 @@ def test_buy_prod(default_conf, mocker, exchange_name): exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) assert 'id' in order assert 'info' in order @@ -1271,7 +1273,9 @@ def test_buy_prod(default_conf, mocker, exchange_name): side="buy", amount=1, rate=200, - time_in_force=time_in_force) + leverage=1.0, + time_in_force=time_in_force + ) assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' assert api_mock.create_order.call_args[0][1] == order_type assert api_mock.create_order.call_args[0][2] == 'buy' @@ -1283,31 +1287,36 @@ def test_buy_prod(default_conf, mocker, exchange_name): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("Not enough funds")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.create_order(pair='ETH/BTC', ordertype='limit', side="buy", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.create_order(pair='ETH/BTC', ordertype='market', side="buy", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) with pytest.raises(TemporaryError): api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("Network disconnect")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) with pytest.raises(OperationalException): api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("Unknown error")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) @pytest.mark.parametrize("exchange_name", EXCHANGES) @@ -1331,7 +1340,8 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name): time_in_force = 'ioc' order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) assert 'id' in order assert 'info' in order @@ -1348,7 +1358,8 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name): time_in_force = 'ioc' order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) assert 'id' in order assert 'info' in order @@ -1367,7 +1378,7 @@ def test_sell_dry_run(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf) order = exchange.create_order(pair='ETH/BTC', ordertype='limit', - side="sell", amount=1, rate=200) + side="sell", amount=1, rate=200, leverage=1.0) assert 'id' in order assert 'dry_run_sell_' in order['id'] @@ -1392,7 +1403,7 @@ def test_sell_prod(default_conf, mocker, exchange_name): exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, - side="sell", amount=1, rate=200) + side="sell", amount=1, rate=200, leverage=1.0) assert 'id' in order assert 'info' in order @@ -1406,7 +1417,8 @@ def test_sell_prod(default_conf, mocker, exchange_name): api_mock.create_order.reset_mock() order_type = 'limit' order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, - side="sell", amount=1, rate=200) + side="sell", amount=1, rate=200, + leverage=1.0) assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' assert api_mock.create_order.call_args[0][1] == order_type assert api_mock.create_order.call_args[0][2] == 'sell' @@ -1417,28 +1429,33 @@ def test_sell_prod(default_conf, mocker, exchange_name): with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200) + exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200, + leverage=1.0) with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.create_order(pair='ETH/BTC', ordertype='limit', side="sell", amount=1, rate=200) + exchange.create_order(pair='ETH/BTC', ordertype='limit', side="sell", amount=1, rate=200, + leverage=1.0) # Market orders don't require price, so the behaviour is slightly different with pytest.raises(DependencyException): api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.create_order(pair='ETH/BTC', ordertype='market', side="sell", amount=1, rate=200) + exchange.create_order(pair='ETH/BTC', ordertype='market', side="sell", amount=1, rate=200, + leverage=1.0) with pytest.raises(TemporaryError): api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No Connection")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200) + exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200, + leverage=1.0) with pytest.raises(OperationalException): api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef")) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) - exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200) + exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", amount=1, rate=200, + leverage=1.0) @pytest.mark.parametrize("exchange_name", EXCHANGES) @@ -1462,7 +1479,8 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name): time_in_force = 'ioc' order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) assert 'id' in order assert 'info' in order @@ -1478,7 +1496,8 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name): order_type = 'market' time_in_force = 'ioc' order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="sell", - amount=1, rate=200, time_in_force=time_in_force) + amount=1, rate=200, leverage=1.0, + time_in_force=time_in_force) assert 'id' in order assert 'info' in order @@ -3792,9 +3811,10 @@ def test__fetch_and_calculate_funding_fees_datetime_called( d1 = datetime.strptime("2021-09-01 00:00:00 +0000", '%Y-%m-%d %H:%M:%S %z') time_machine.move_to("2021-09-01 08:00:00 +00:00") - # TODO-lev: test this for longs funding_fees = exchange._fetch_and_calculate_funding_fees('ADA/USDT', 30.0, True, d1) assert funding_fees == expected_fees + funding_fees = exchange._fetch_and_calculate_funding_fees('ADA/USDT', 30.0, False, d1) + assert funding_fees == 0 - expected_fees @pytest.mark.parametrize('pair,expected_size,trading_mode', [ diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index ff4200f8d..02df60990 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -32,8 +32,15 @@ def test_buy_kraken_trading_agreement(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") - order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, side="buy", - amount=1, rate=200, time_in_force=time_in_force) + order = exchange.create_order( + pair='ETH/BTC', + ordertype=order_type, + side="buy", + amount=1, + rate=200, + leverage=1.0, + time_in_force=time_in_force + ) assert 'id' in order assert 'info' in order @@ -66,7 +73,7 @@ def test_sell_kraken_trading_agreement(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") order = exchange.create_order(pair='ETH/BTC', ordertype=order_type, - side="sell", amount=1, rate=200) + side="sell", amount=1, rate=200, leverage=1.0) assert 'id' in order assert 'info' in order diff --git a/tests/strategy/strats/strategy_test_v3.py b/tests/strategy/strats/strategy_test_v3.py index d92c6c734..5d689e0a1 100644 --- a/tests/strategy/strats/strategy_test_v3.py +++ b/tests/strategy/strats/strategy_test_v3.py @@ -169,7 +169,6 @@ class StrategyTestV3(IStrategy): ), 'exit_short'] = 1 - # TODO-lev: Add short logic return dataframe def leverage(self, pair: str, current_time: datetime, current_rate: float, diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index d26c1e65a..ef443a27f 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -4799,9 +4799,125 @@ def test_get_valid_price(mocker, default_conf_usdt) -> None: assert valid_price_at_min_alwd < proposed_price -def test_leverage_prep(): - # TODO-lev - return +@pytest.mark.parametrize( + "is_short,trading_mode,exchange_name,margin_mode,leverage,open_rate,amount,expected_liq", [ + (False, 'spot', 'binance', '', 5.0, 10.0, 1.0, None), + (True, 'spot', 'binance', '', 5.0, 10.0, 1.0, None), + (False, 'spot', 'gateio', '', 5.0, 10.0, 1.0, None), + (True, 'spot', 'gateio', '', 5.0, 10.0, 1.0, None), + (False, 'spot', 'okex', '', 5.0, 10.0, 1.0, None), + (True, 'spot', 'okex', '', 5.0, 10.0, 1.0, None), + # Binance, short + (True, 'futures', 'binance', 'isolated', 5.0, 10.0, 1.0, 11.89108910891089), + (True, 'futures', 'binance', 'isolated', 3.0, 10.0, 1.0, 13.211221122079207), + (True, 'futures', 'binance', 'isolated', 5.0, 8.0, 1.0, 9.514851485148514), + (True, 'futures', 'binance', 'isolated', 5.0, 10.0, 0.6, 12.557755775577558), + # Binance, long + (False, 'futures', 'binance', 'isolated', 5, 10, 1.0, 8.070707070707071), + (False, 'futures', 'binance', 'isolated', 5, 8, 1.0, 6.454545454545454), + (False, 'futures', 'binance', 'isolated', 3, 10, 1.0, 6.717171717171718), + (False, 'futures', 'binance', 'isolated', 5, 10, 0.6, 7.39057239057239), + # Gateio/okex, short + (True, 'futures', 'gateio', 'isolated', 5, 10, 1.0, 11.87413417771621), + (True, 'futures', 'gateio', 'isolated', 5, 10, 2.0, 11.87413417771621), + (True, 'futures', 'gateio', 'isolated', 3, 10, 1.0, 13.476180850346978), + (True, 'futures', 'gateio', 'isolated', 5, 8, 1.0, 9.499307342172967), + # Gateio/okex, long + (False, 'futures', 'gateio', 'isolated', 5.0, 10.0, 1.0, 8.085708510208207), + (False, 'futures', 'gateio', 'isolated', 3.0, 10.0, 1.0, 6.738090425173506), + # (True, 'futures', 'okex', 'isolated', 11.87413417771621), + # (False, 'futures', 'okex', 'isolated', 8.085708510208207), + ] +) +def test_leverage_prep( + mocker, + default_conf_usdt, + is_short, + trading_mode, + exchange_name, + margin_mode, + leverage, + open_rate, + amount, + expected_liq, +): + """ + position = 0.2 * 5 + wb: wallet balance (stake_amount if isolated) + cum_b: maintenance amount + side_1: -1 if is_short else 1 + ep1: entry price + mmr_b: maintenance margin ratio + + Binance, Short + leverage = 5, open_rate = 10, amount = 1.0 + ((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position)) + ((2 + 0.01) - ((-1) * 1 * 10)) / ((1 * 0.01) - ((-1) * 1)) = 11.89108910891089 + leverage = 3, open_rate = 10, amount = 1.0 + ((3.3333333333 + 0.01) - ((-1) * 1.0 * 10)) / ((1.0 * 0.01) - ((-1) * 1.0)) = 13.2112211220 + leverage = 5, open_rate = 8, amount = 1.0 + ((1.6 + 0.01) - ((-1) * 1 * 8)) / ((1 * 0.01) - ((-1) * 1)) = 9.514851485148514 + leverage = 5, open_rate = 10, amount = 0.6 + ((1.6 + 0.01) - ((-1) * 0.6 * 10)) / ((0.6 * 0.01) - ((-1) * 0.6)) = 12.557755775577558 + + Binance, Long + leverage = 5, open_rate = 10, amount = 1.0 + ((wb + cum_b) - (side_1 * position * ep1)) / ((position * mmr_b) - (side_1 * position)) + ((2 + 0.01) - (1 * 1 * 10)) / ((1 * 0.01) - (1 * 1)) = 8.070707070707071 + leverage = 5, open_rate = 8, amount = 1.0 + ((1.6 + 0.01) - (1 * 1 * 8)) / ((1 * 0.01) - (1 * 1)) = 6.454545454545454 + leverage = 3, open_rate = 10, amount = 1.0 + ((2 + 0.01) - (1 * 0.6 * 10)) / ((0.6 * 0.01) - (1 * 0.6)) = 6.717171717171718 + leverage = 5, open_rate = 10, amount = 0.6 + ((1.6 + 0.01) - (1 * 0.6 * 10)) / ((0.6 * 0.01) - (1 * 0.6)) = 7.39057239057239 + + Gateio/Okex, Short + leverage = 5, open_rate = 10, amount = 1.0 + (open_rate + (wallet_balance / position)) / (1 + (mm_ratio + taker_fee_rate)) + (10 + (2 / 1.0)) / (1 + (0.01 + 0.0006)) = 11.87413417771621 + leverage = 5, open_rate = 10, amount = 2.0 + (10 + (4 / 2.0)) / (1 + (0.01 + 0.0006)) = 11.87413417771621 + leverage = 3, open_rate = 10, amount = 1.0 + (10 + (3.3333333333333 / 1.0)) / (1 - (0.01 + 0.0006)) = 13.476180850346978 + leverage = 5, open_rate = 8, amount = 1.0 + (8 + (1.6 / 1.0)) / (1 + (0.01 + 0.0006)) = 9.499307342172967 + + Gateio/Okex, Long + leverage = 5, open_rate = 10, amount = 1.0 + (open_rate - (wallet_balance / position)) / (1 - (mm_ratio + taker_fee_rate)) + (10 - (2 / 1)) / (1 - (0.01 + 0.0006)) = 8.085708510208207 + leverage = 5, open_rate = 10, amount = 2.0 + (10 - (4 / 2.0)) / (1 + (0.01 + 0.0006)) = 7.916089451810806 + leverage = 3, open_rate = 10, amount = 1.0 + (10 - (3.333333333333333333 / 1.0)) / (1 - (0.01 + 0.0006)) = 6.738090425173506 + leverage = 5, open_rate = 8, amount = 1.0 + (8 - (1.6 / 1.0)) / (1 + (0.01 + 0.0006)) = 6.332871561448645 + """ + default_conf_usdt['trading_mode'] = trading_mode + default_conf_usdt['exchange']['name'] = exchange_name + default_conf_usdt['margin_mode'] = margin_mode + mocker.patch('freqtrade.exchange.Gateio.validate_ordertypes') + patch_RPCManager(mocker) + patch_exchange(mocker, id=exchange_name) + freqtrade = FreqtradeBot(default_conf_usdt) + + freqtrade.exchange.get_maintenance_ratio_and_amt = MagicMock(return_value=(0.01, 0.01)) + freqtrade.exchange.name = exchange_name + # default_conf_usdt.update({ + # "dry_run": False, + # }) + (interest, liq) = freqtrade.leverage_prep( + pair='ETH/USDT:USDT', + open_rate=open_rate, + amount=amount, + leverage=leverage, + is_short=is_short, + ) + assert interest == 0.0 + if expected_liq is None: + assert liq is None + else: + isclose(expected_liq, liq) @pytest.mark.parametrize('trading_mode,calls,t1,t2', [