diff --git a/docs/backtesting.md b/docs/backtesting.md index 49a94b05e..981d4cf5e 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -371,42 +371,48 @@ The last element of the backtest report is the summary metrics table. It contains some useful key metrics about performance of your strategy on backtesting data. ``` -=============== SUMMARY METRICS =============== -| Metric | Value | -|-----------------------+---------------------| -| Backtesting from | 2019-01-01 00:00:00 | -| Backtesting to | 2019-05-01 00:00:00 | -| Max open trades | 3 | -| | | -| Total/Daily Avg Trades| 429 / 3.575 | -| Starting balance | 0.01000000 BTC | -| Final balance | 0.01762792 BTC | -| Absolute profit | 0.00762792 BTC | -| Total profit % | 76.2% | -| Avg. stake amount | 0.001 BTC | -| Total trade volume | 0.429 BTC | -| | | -| Best Pair | LSK/BTC 26.26% | -| Worst Pair | ZEC/BTC -10.18% | -| Best Trade | LSK/BTC 4.25% | -| Worst Trade | ZEC/BTC -10.25% | -| Best day | 0.00076 BTC | -| Worst day | -0.00036 BTC | -| Days win/draw/lose | 12 / 82 / 25 | -| Avg. Duration Winners | 4:23:00 | -| Avg. Duration Loser | 6:55:00 | -| Rejected Buy signals | 3089 | -| | | -| Min balance | 0.00945123 BTC | -| Max balance | 0.01846651 BTC | -| Drawdown | 50.63% | -| Drawdown | 0.0015 BTC | -| Drawdown high | 0.0013 BTC | -| Drawdown low | -0.0002 BTC | -| Drawdown Start | 2019-02-15 14:10:00 | -| Drawdown End | 2019-04-11 18:15:00 | -| Market change | -5.88% | -=============================================== +================ SUMMARY METRICS =============== +| Metric | Value | +|------------------------+---------------------| +| Backtesting from | 2019-01-01 00:00:00 | +| Backtesting to | 2019-05-01 00:00:00 | +| Max open trades | 3 | +| | | +| Total/Daily Avg Trades | 429 / 3.575 | +| Starting balance | 0.01000000 BTC | +| Final balance | 0.01762792 BTC | +| Absolute profit | 0.00762792 BTC | +| Total profit % | 76.2% | +| Avg. stake amount | 0.001 BTC | +| Total trade volume | 0.429 BTC | +| | | +| Long / Short | 352 / 77 | +| Total profit Long % | 1250.58% | +| Total profit Short % | -15.02% | +| Absolute profit Long | 0.00838792 BTC | +| Absolute profit Short | -0.00076 BTC | +| | | +| Best Pair | LSK/BTC 26.26% | +| Worst Pair | ZEC/BTC -10.18% | +| Best Trade | LSK/BTC 4.25% | +| Worst Trade | ZEC/BTC -10.25% | +| Best day | 0.00076 BTC | +| Worst day | -0.00036 BTC | +| Days win/draw/lose | 12 / 82 / 25 | +| Avg. Duration Winners | 4:23:00 | +| Avg. Duration Loser | 6:55:00 | +| Rejected Buy signals | 3089 | +| | | +| Min balance | 0.00945123 BTC | +| Max balance | 0.01846651 BTC | +| Drawdown | 50.63% | +| Drawdown | 0.0015 BTC | +| Drawdown high | 0.0013 BTC | +| Drawdown low | -0.0002 BTC | +| Drawdown Start | 2019-02-15 14:10:00 | +| Drawdown End | 2019-04-11 18:15:00 | +| Market change | -5.88% | +================================================ ``` @@ -430,6 +436,9 @@ It contains some useful key metrics about performance of your strategy on backte - `Drawdown high` / `Drawdown low`: Profit at the beginning and end of the largest drawdown period. A negative low value means initial capital lost. - `Drawdown Start` / `Drawdown End`: Start and end datetime for this largest drawdown (can also be visualized via the `plot-dataframe` sub-command). - `Market change`: Change of the market during the backtest period. Calculated as average of all pairs changes from the first to the last candle using the "close" column. +- `Long / Short`: Split long/short values (Only shown when short trades were made). +- `Total profit Long %` / `Absolute profit Long`: Profit long trades only (Only shown when short trades were made). +- `Total profit Short %` / `Absolute profit Short`: Profit short trades only (Only shown when short trades were made). ### Daily / Weekly / Monthly breakdown diff --git a/docs/bot-basics.md b/docs/bot-basics.md index 80443a0bf..4a83293d4 100644 --- a/docs/bot-basics.md +++ b/docs/bot-basics.md @@ -43,6 +43,7 @@ By default, loop runs every few seconds (`internals.process_throttle_secs`) and * Determine buy-price based on `bid_strategy` configuration setting, or by using the `custom_entry_price()` callback. * Determine stake size by calling the `custom_stake_amount()` callback. * Before a buy order is placed, `confirm_trade_entry()` strategy callback is called. + * In Margin and Futures mode, `leverage()` strategy callback is called to determine the desired leverage. This loop will be repeated again and again until the bot is stopped. diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index e8d878838..2631e4a46 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -30,7 +30,9 @@ BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date', 'fee_open', 'fee_close', 'trade_duration', 'profit_ratio', 'profit_abs', 'sell_reason', 'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs', - 'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_tag'] + 'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_tag', + 'is_short' + ] # TODO-lev: usage of the above might need compatibility code (buy_tag, is_short?, ...?) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d234ebb07..4a1f5085f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -576,7 +576,6 @@ class FreqtradeBot(LoggingMixin): stake_amount: float, price: Optional[float] = None, forcebuy: bool = False, - leverage: float = 1.0, is_short: bool = False, enter_tag: Optional[str] = None ) -> bool: @@ -590,6 +589,7 @@ class FreqtradeBot(LoggingMixin): time_in_force = self.strategy.order_time_in_force['buy'] [side, name] = ['sell', 'Short'] if is_short else ['buy', 'Long'] + trade_side = 'short' if is_short else 'long' if price: enter_limit_requested = price @@ -606,11 +606,14 @@ class FreqtradeBot(LoggingMixin): if not enter_limit_requested: raise PricingError(f'Could not determine {side} price.') + # Min-stake-amount should actually include Leverage - this way our "minimal" + # stake- amount might be higher than necessary. + # We do however also need min-stake to determine leverage, therefore this is ignored as + # edge-case for now. min_stake_amount = self.exchange.get_min_pair_stake_amount( pair, enter_limit_requested, self.strategy.stoploss, - leverage=leverage ) if not self.edge: @@ -620,7 +623,7 @@ class FreqtradeBot(LoggingMixin): 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='short' if is_short else 'long' + side=trade_side ) stake_amount = self.wallets.validate_stake_amount(pair, stake_amount, min_stake_amount) @@ -628,6 +631,18 @@ class FreqtradeBot(LoggingMixin): if not stake_amount: return False + max_leverage = self.exchange.get_max_leverage(pair, stake_amount) + leverage = strategy_safe_wrapper(self.strategy.leverage, default_retval=1.0)( + pair=pair, + current_time=datetime.now(timezone.utc), + current_rate=enter_limit_requested, + proposed_leverage=1.0, + max_leverage=max_leverage, + side=trade_side, + ) if self.trading_mode != TradingMode.SPOT else 1.0 + # Cap leverage between 1.0 and max_leverage. + leverage = min(max(leverage, 1.0), max_leverage) + logger.info( f"{name} signal found: about create a new trade for {pair} with stake_amount: " f"{stake_amount} ..." @@ -644,7 +659,7 @@ class FreqtradeBot(LoggingMixin): 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), - side='short' if is_short else 'long' + side=trade_side ): logger.info(f"User requested abortion of buying {pair}") return False diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 1710c9805..9bf9b5e0e 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -368,6 +368,10 @@ class Backtesting: def _get_sell_trade_entry_for_candle(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]: + # TODO-lev: add interest / funding fees to trade object -> + # Must be done either here, or one level higher -> + # (if we don't want to do it at "detail" level) + sell_candle_time = sell_row[DATE_IDX].to_pydatetime() enter = sell_row[SHORT_IDX] if trade.is_short else sell_row[LONG_IDX] exit_ = sell_row[ESHORT_IDX] if trade.is_short else sell_row[ELONG_IDX] @@ -443,13 +447,13 @@ class Backtesting: stake_amount = self.wallets.get_trade_stake_amount(pair, None) except DependencyException: return None - + current_time = row[DATE_IDX].to_pydatetime() min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, row[OPEN_IDX], -0.05) or 0 max_stake_amount = self.wallets.get_available_stake_amount() stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount, default_retval=stake_amount)( - pair=pair, current_time=row[DATE_IDX].to_pydatetime(), current_rate=row[OPEN_IDX], + pair=pair, current_time=current_time, current_rate=row[OPEN_IDX], proposed_stake=stake_amount, min_stake=min_stake_amount, max_stake=max_stake_amount, side=direction) stake_amount = self.wallets.validate_stake_amount(pair, stake_amount, min_stake_amount) @@ -457,12 +461,24 @@ class Backtesting: if not stake_amount: return None + max_leverage = self.exchange.get_max_leverage(pair, stake_amount) + leverage = strategy_safe_wrapper(self.strategy.leverage, default_retval=1.0)( + pair=pair, + current_time=current_time, + current_rate=row[OPEN_IDX], + proposed_leverage=1.0, + max_leverage=max_leverage, + side=direction, + ) if self._can_short else 1.0 + # Cap leverage between 1.0 and max_leverage. + leverage = min(max(leverage, 1.0), max_leverage) + order_type = self.strategy.order_types['buy'] time_in_force = self.strategy.order_time_in_force['sell'] # Confirm trade entry: if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)( pair=pair, order_type=order_type, amount=stake_amount, rate=row[OPEN_IDX], - time_in_force=time_in_force, current_time=row[DATE_IDX].to_pydatetime(), + time_in_force=time_in_force, current_time=current_time, side=direction): return None @@ -472,7 +488,7 @@ class Backtesting: trade = LocalTrade( pair=pair, open_rate=row[OPEN_IDX], - open_date=row[DATE_IDX].to_pydatetime(), + open_date=current_time, stake_amount=stake_amount, amount=round(stake_amount / row[OPEN_IDX], 8), fee_open=self.fee, @@ -481,6 +497,7 @@ class Backtesting: buy_tag=row[ENTER_TAG_IDX] if has_enter_tag else None, exchange=self._exchange_name, is_short=(direction == 'short'), + leverage=leverage, ) return trade return None diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index c4002fcbe..30feeb5ac 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -415,20 +415,20 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], return {} config = content['config'] max_open_trades = min(config['max_open_trades'], len(btdata.keys())) - starting_balance = config['dry_run_wallet'] + start_balance = config['dry_run_wallet'] stake_currency = config['stake_currency'] pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency, - starting_balance=starting_balance, + starting_balance=start_balance, results=results, skip_nan=False) - buy_tag_results = generate_tag_metrics("buy_tag", starting_balance=starting_balance, + buy_tag_results = generate_tag_metrics("buy_tag", starting_balance=start_balance, results=results, skip_nan=False) sell_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades, results=results) left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency, - starting_balance=starting_balance, + starting_balance=start_balance, results=results.loc[results['is_open']], skip_nan=True) daily_stats = generate_daily_stats(results) @@ -454,12 +454,18 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], # 'days_breakdown_stats': days_breakdown_stats, 'total_trades': len(results), + 'trade_count_long': len(results.loc[~results['is_short']]), + 'trade_count_short': len(results.loc[results['is_short']]), 'total_volume': float(results['stake_amount'].sum()), 'avg_stake_amount': results['stake_amount'].mean() if len(results) > 0 else 0, 'profit_mean': results['profit_ratio'].mean() if len(results) > 0 else 0, 'profit_median': results['profit_ratio'].median() if len(results) > 0 else 0, - 'profit_total': results['profit_abs'].sum() / starting_balance, + 'profit_total': results['profit_abs'].sum() / start_balance, + 'profit_total_long': results.loc[~results['is_short'], 'profit_abs'].sum() / start_balance, + 'profit_total_short': results.loc[results['is_short'], 'profit_abs'].sum() / start_balance, 'profit_total_abs': results['profit_abs'].sum(), + 'profit_total_long_abs': results.loc[~results['is_short'], 'profit_abs'].sum(), + 'profit_total_short_abs': results.loc[results['is_short'], 'profit_abs'].sum(), 'backtest_start': min_date.strftime(DATETIME_PRINT_FORMAT), 'backtest_start_ts': int(min_date.timestamp() * 1000), 'backtest_end': max_date.strftime(DATETIME_PRINT_FORMAT), @@ -475,8 +481,8 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], 'stake_amount': config['stake_amount'], 'stake_currency': config['stake_currency'], 'stake_currency_decimals': decimals_per_coin(config['stake_currency']), - 'starting_balance': starting_balance, - 'dry_run_wallet': starting_balance, + 'starting_balance': start_balance, + 'dry_run_wallet': start_balance, 'final_balance': content['final_balance'], 'rejected_signals': content['rejected_signals'], 'max_open_trades': max_open_trades, @@ -520,7 +526,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], 'max_drawdown_high': high_val, }) - csum_min, csum_max = calculate_csum(results, starting_balance) + csum_min, csum_max = calculate_csum(results, start_balance) strat_stats.update({ 'csum_min': csum_min, 'csum_max': csum_max @@ -709,6 +715,19 @@ def text_table_add_metrics(strat_results: Dict) -> str: best_trade = max(strat_results['trades'], key=lambda x: x['profit_ratio']) worst_trade = min(strat_results['trades'], key=lambda x: x['profit_ratio']) + short_metrics = [ + ('', ''), # Empty line to improve readability + ('Long / Short', + f"{strat_results.get('trade_count_long', 'total_trades')} / " + f"{strat_results.get('trade_count_short', 0)}"), + ('Total profit Long %', f"{strat_results['profit_total_long']:.2%}"), + ('Total profit Short %', f"{strat_results['profit_total_short']:.2%}"), + ('Absolute profit Long', round_coin_value(strat_results['profit_total_long_abs'], + strat_results['stake_currency'])), + ('Absolute profit Short', round_coin_value(strat_results['profit_total_short_abs'], + strat_results['stake_currency'])), + ] if strat_results.get('trade_count_short', 0) > 0 else [] + # Newly added fields should be ignored if they are missing in strat_results. hyperopt-show # command stores these results and newer version of freqtrade must be able to handle old # results with missing new fields. @@ -719,6 +738,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: ('', ''), # Empty line to improve readability ('Total/Daily Avg Trades', f"{strat_results['total_trades']} / {strat_results['trades_per_day']}"), + ('Starting balance', round_coin_value(strat_results['starting_balance'], strat_results['stake_currency'])), ('Final balance', round_coin_value(strat_results['final_balance'], @@ -733,6 +753,7 @@ def text_table_add_metrics(strat_results: Dict) -> str: strat_results['stake_currency'])), ('Total trade volume', round_coin_value(strat_results['total_volume'], strat_results['stake_currency'])), + *short_metrics, ('', ''), # Empty line to improve readability ('Best Pair', f"{strat_results['best_pair']['key']} " f"{strat_results['best_pair']['profit_sum']:.2%}"), diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 19aa56ef4..21d11d7f7 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -698,7 +698,8 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: 'min_rate': [0.10370188, 0.10300000000000001], 'max_rate': [0.10501, 0.1038888], 'is_open': [False, False], - 'buy_tag': [None, None] + 'buy_tag': [None, None], + "is_short": [False, False], }) pd.testing.assert_frame_equal(results, expected) data_pair = processed[pair] @@ -1074,6 +1075,8 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat 'stake_amount': [0.01, 0.01], 'open_rate': [0.104445, 0.10302485], 'close_rate': [0.104969, 0.103541], + "is_short": [False, False], + 'sell_reason': [SellType.ROI, SellType.ROI] }) result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'], @@ -1091,6 +1094,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat 'stake_amount': [0.01, 0.01, 0.01], 'open_rate': [0.104445, 0.10302485, 0.122541], 'close_rate': [0.104969, 0.103541, 0.123541], + "is_short": [False, False, False], 'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS] }) backtestmock = MagicMock(side_effect=[ @@ -1180,6 +1184,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker, '2018-01-30 05:35:00', ], utc=True), 'trade_duration': [235, 40], 'is_open': [False, False], + 'is_short': [False, False], 'stake_amount': [0.01, 0.01], 'open_rate': [0.104445, 0.10302485], 'close_rate': [0.104969, 0.103541], @@ -1197,6 +1202,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker, '2018-01-30 08:30:00'], utc=True), 'trade_duration': [47, 40, 20], 'is_open': [False, False, False], + 'is_short': [False, False, False], 'stake_amount': [0.01, 0.01, 0.01], 'open_rate': [0.104445, 0.10302485, 0.122541], 'close_rate': [0.104969, 0.103541, 0.123541], diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 57c648b05..7dac751cf 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -344,6 +344,7 @@ def test_hyperopt_format_results(hyperopt): "close_rate": [0.002546, 0.003014, 0.003103, 0.003217], "trade_duration": [123, 34, 31, 14], "is_open": [False, False, False, True], + "is_short": [False, False, False, False], "stake_amount": [0.01, 0.01, 0.01, 0.01], "sell_reason": [SellType.ROI, SellType.STOP_LOSS, SellType.ROI, SellType.FORCE_SELL] @@ -412,6 +413,7 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None: "close_rate": [0.002546, 0.003014, 0.003103, 0.003217], "trade_duration": [123, 34, 31, 14], "is_open": [False, False, False, True], + "is_short": [False, False, False, False], "stake_amount": [0.01, 0.01, 0.01, 0.01], "sell_reason": [SellType.ROI, SellType.STOP_LOSS, SellType.ROI, SellType.FORCE_SELL] diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index 47d4e6ec8..2db7ef070 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -76,6 +76,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir): "close_rate": [0.002546, 0.003014, 0.003103, 0.003217], "trade_duration": [123, 34, 31, 14], "is_open": [False, False, False, True], + "is_short": [False, False, False, False], "stake_amount": [0.01, 0.01, 0.01, 0.01], "sell_reason": [SellType.ROI, SellType.STOP_LOSS, SellType.ROI, SellType.FORCE_SELL] @@ -124,6 +125,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir): "close_rate": [0.002546, 0.003014, 0.0032903, 0.003217], "trade_duration": [123, 34, 31, 14], "is_open": [False, False, False, True], + "is_short": [False, False, False, False], "stake_amount": [0.01, 0.01, 0.01, 0.01], "sell_reason": [SellType.ROI, SellType.ROI, SellType.STOP_LOSS, SellType.FORCE_SELL] diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 2dcb573e1..bd5fcf313 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -701,17 +701,26 @@ def test_process_informative_pairs_added(default_conf_usdt, ticker_usdt, mocker) assert ("ETH/USDT", default_conf_usdt["timeframe"]) in refresh_mock.call_args[0][0] +@pytest.mark.parametrize("trading_mode", [ + 'spot', + # TODO-lev: Enable other modes + # 'margin', 'futures' + ] + ) @pytest.mark.parametrize("is_short", [False, True]) def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, - limit_order_open, is_short) -> None: + limit_order_open, is_short, trading_mode) -> None: open_order = limit_order_open[enter_side(is_short)] order = limit_order[enter_side(is_short)] - + default_conf_usdt['trading_mode'] = trading_mode + leverage = 1.0 if trading_mode == 'spot' else 3.0 + default_conf_usdt['collateral'] = 'cross' patch_RPCManager(mocker) patch_exchange(mocker) freqtrade = FreqtradeBot(default_conf_usdt) freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False) + freqtrade.strategy.leverage = MagicMock(return_value=leverage) stake_amount = 2 bid = 0.11 enter_rate_mock = MagicMock(return_value=bid) @@ -727,6 +736,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, create_order=enter_mm, get_min_pair_stake_amount=MagicMock(return_value=1), get_fee=fee, + get_funding_fees=MagicMock(return_value=0), ) pair = 'ETH/USDT' @@ -744,7 +754,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, call_args = enter_mm.call_args_list[0][1] assert call_args['pair'] == pair assert call_args['rate'] == bid - assert call_args['amount'] == round(stake_amount / bid, 8) + assert pytest.approx(call_args['amount'], round(stake_amount / bid * leverage, 8)) enter_rate_mock.reset_mock() # Should create an open trade with an open order id @@ -766,7 +776,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, call_args = enter_mm.call_args_list[1][1] assert call_args['pair'] == pair assert call_args['rate'] == fix_price - assert call_args['amount'] == round(stake_amount / fix_price, 8) + assert pytest.approx(call_args['amount'], round(stake_amount / fix_price * leverage, 8)) # In case of closed order order['status'] = 'closed' @@ -824,7 +834,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, # In case of the order is rejected and not filled at all order['status'] = 'rejected' - order['amount'] = 30.0 + order['amount'] = 30.0 * leverage order['filled'] = 0.0 order['remaining'] = 30.0 order['price'] = 0.5 @@ -833,6 +843,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_order, mocker.patch('freqtrade.exchange.Exchange.create_order', MagicMock(return_value=order)) assert not freqtrade.execute_entry(pair, stake_amount) + assert freqtrade.strategy.leverage.call_count == 0 if trading_mode == 'spot' else 2 # Fail to get price... mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0))