Merge pull request #5924 from freqtrade/feat/leverage

call leverage methods
This commit is contained in:
Matthias 2021-11-21 19:30:27 +01:00 committed by GitHub
commit aad37bb8f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 145 additions and 59 deletions

View File

@ -371,9 +371,9 @@ 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. It contains some useful key metrics about performance of your strategy on backtesting data.
``` ```
=============== SUMMARY METRICS =============== ================ SUMMARY METRICS ===============
| Metric | Value | | Metric | Value |
|-----------------------+---------------------| |------------------------+---------------------|
| Backtesting from | 2019-01-01 00:00:00 | | Backtesting from | 2019-01-01 00:00:00 |
| Backtesting to | 2019-05-01 00:00:00 | | Backtesting to | 2019-05-01 00:00:00 |
| Max open trades | 3 | | Max open trades | 3 |
@ -386,6 +386,12 @@ It contains some useful key metrics about performance of your strategy on backte
| Avg. stake amount | 0.001 BTC | | Avg. stake amount | 0.001 BTC |
| Total trade volume | 0.429 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% | | Best Pair | LSK/BTC 26.26% |
| Worst Pair | ZEC/BTC -10.18% | | Worst Pair | ZEC/BTC -10.18% |
| Best Trade | LSK/BTC 4.25% | | Best Trade | LSK/BTC 4.25% |
@ -406,7 +412,7 @@ It contains some useful key metrics about performance of your strategy on backte
| Drawdown Start | 2019-02-15 14:10:00 | | Drawdown Start | 2019-02-15 14:10:00 |
| Drawdown End | 2019-04-11 18:15:00 | | Drawdown End | 2019-04-11 18:15:00 |
| Market change | -5.88% | | 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 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). - `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. - `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 ### Daily / Weekly / Monthly breakdown

View File

@ -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 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. * Determine stake size by calling the `custom_stake_amount()` callback.
* Before a buy order is placed, `confirm_trade_entry()` strategy callback is called. * 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. This loop will be repeated again and again until the bot is stopped.

View File

@ -30,7 +30,9 @@ BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
'fee_open', 'fee_close', 'trade_duration', 'fee_open', 'fee_close', 'trade_duration',
'profit_ratio', 'profit_abs', 'sell_reason', 'profit_ratio', 'profit_abs', 'sell_reason',
'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs', '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?, ...?) # TODO-lev: usage of the above might need compatibility code (buy_tag, is_short?, ...?)

View File

@ -576,7 +576,6 @@ class FreqtradeBot(LoggingMixin):
stake_amount: float, stake_amount: float,
price: Optional[float] = None, price: Optional[float] = None,
forcebuy: bool = False, forcebuy: bool = False,
leverage: float = 1.0,
is_short: bool = False, is_short: bool = False,
enter_tag: Optional[str] = None enter_tag: Optional[str] = None
) -> bool: ) -> bool:
@ -590,6 +589,7 @@ class FreqtradeBot(LoggingMixin):
time_in_force = self.strategy.order_time_in_force['buy'] time_in_force = self.strategy.order_time_in_force['buy']
[side, name] = ['sell', 'Short'] if is_short else ['buy', 'Long'] [side, name] = ['sell', 'Short'] if is_short else ['buy', 'Long']
trade_side = 'short' if is_short else 'long'
if price: if price:
enter_limit_requested = price enter_limit_requested = price
@ -606,11 +606,14 @@ class FreqtradeBot(LoggingMixin):
if not enter_limit_requested: if not enter_limit_requested:
raise PricingError(f'Could not determine {side} price.') 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( min_stake_amount = self.exchange.get_min_pair_stake_amount(
pair, pair,
enter_limit_requested, enter_limit_requested,
self.strategy.stoploss, self.strategy.stoploss,
leverage=leverage
) )
if not self.edge: if not self.edge:
@ -620,7 +623,7 @@ class FreqtradeBot(LoggingMixin):
pair=pair, current_time=datetime.now(timezone.utc), pair=pair, current_time=datetime.now(timezone.utc),
current_rate=enter_limit_requested, proposed_stake=stake_amount, current_rate=enter_limit_requested, proposed_stake=stake_amount,
min_stake=min_stake_amount, max_stake=max_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) stake_amount = self.wallets.validate_stake_amount(pair, stake_amount, min_stake_amount)
@ -628,6 +631,18 @@ class FreqtradeBot(LoggingMixin):
if not stake_amount: if not stake_amount:
return False 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( logger.info(
f"{name} signal found: about create a new trade for {pair} with stake_amount: " f"{name} signal found: about create a new trade for {pair} with stake_amount: "
f"{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)( 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, pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
time_in_force=time_in_force, current_time=datetime.now(timezone.utc), 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}") logger.info(f"User requested abortion of buying {pair}")
return False return False

View File

@ -368,6 +368,10 @@ class Backtesting:
def _get_sell_trade_entry_for_candle(self, trade: LocalTrade, def _get_sell_trade_entry_for_candle(self, trade: LocalTrade,
sell_row: Tuple) -> Optional[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() sell_candle_time = sell_row[DATE_IDX].to_pydatetime()
enter = sell_row[SHORT_IDX] if trade.is_short else sell_row[LONG_IDX] 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] 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) stake_amount = self.wallets.get_trade_stake_amount(pair, None)
except DependencyException: except DependencyException:
return None 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 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() max_stake_amount = self.wallets.get_available_stake_amount()
stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount, stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount,
default_retval=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, proposed_stake=stake_amount, min_stake=min_stake_amount, max_stake=max_stake_amount,
side=direction) side=direction)
stake_amount = self.wallets.validate_stake_amount(pair, stake_amount, min_stake_amount) stake_amount = self.wallets.validate_stake_amount(pair, stake_amount, min_stake_amount)
@ -457,12 +461,24 @@ class Backtesting:
if not stake_amount: if not stake_amount:
return None 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'] order_type = self.strategy.order_types['buy']
time_in_force = self.strategy.order_time_in_force['sell'] time_in_force = self.strategy.order_time_in_force['sell']
# Confirm trade entry: # Confirm trade entry:
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)( 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], 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): side=direction):
return None return None
@ -472,7 +488,7 @@ class Backtesting:
trade = LocalTrade( trade = LocalTrade(
pair=pair, pair=pair,
open_rate=row[OPEN_IDX], open_rate=row[OPEN_IDX],
open_date=row[DATE_IDX].to_pydatetime(), open_date=current_time,
stake_amount=stake_amount, stake_amount=stake_amount,
amount=round(stake_amount / row[OPEN_IDX], 8), amount=round(stake_amount / row[OPEN_IDX], 8),
fee_open=self.fee, fee_open=self.fee,
@ -481,6 +497,7 @@ class Backtesting:
buy_tag=row[ENTER_TAG_IDX] if has_enter_tag else None, buy_tag=row[ENTER_TAG_IDX] if has_enter_tag else None,
exchange=self._exchange_name, exchange=self._exchange_name,
is_short=(direction == 'short'), is_short=(direction == 'short'),
leverage=leverage,
) )
return trade return trade
return None return None

View File

@ -415,20 +415,20 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
return {} return {}
config = content['config'] config = content['config']
max_open_trades = min(config['max_open_trades'], len(btdata.keys())) 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'] stake_currency = config['stake_currency']
pair_results = generate_pair_metrics(btdata, stake_currency=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) 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) results=results, skip_nan=False)
sell_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades, sell_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades,
results=results) results=results)
left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency, 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']], results=results.loc[results['is_open']],
skip_nan=True) skip_nan=True)
daily_stats = generate_daily_stats(results) daily_stats = generate_daily_stats(results)
@ -454,12 +454,18 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
# 'days_breakdown_stats': days_breakdown_stats, # 'days_breakdown_stats': days_breakdown_stats,
'total_trades': len(results), '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()), 'total_volume': float(results['stake_amount'].sum()),
'avg_stake_amount': results['stake_amount'].mean() if len(results) > 0 else 0, '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_mean': results['profit_ratio'].mean() if len(results) > 0 else 0,
'profit_median': results['profit_ratio'].median() 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_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': min_date.strftime(DATETIME_PRINT_FORMAT),
'backtest_start_ts': int(min_date.timestamp() * 1000), 'backtest_start_ts': int(min_date.timestamp() * 1000),
'backtest_end': max_date.strftime(DATETIME_PRINT_FORMAT), '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_amount': config['stake_amount'],
'stake_currency': config['stake_currency'], 'stake_currency': config['stake_currency'],
'stake_currency_decimals': decimals_per_coin(config['stake_currency']), 'stake_currency_decimals': decimals_per_coin(config['stake_currency']),
'starting_balance': starting_balance, 'starting_balance': start_balance,
'dry_run_wallet': starting_balance, 'dry_run_wallet': start_balance,
'final_balance': content['final_balance'], 'final_balance': content['final_balance'],
'rejected_signals': content['rejected_signals'], 'rejected_signals': content['rejected_signals'],
'max_open_trades': max_open_trades, 'max_open_trades': max_open_trades,
@ -520,7 +526,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
'max_drawdown_high': high_val, '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({ strat_stats.update({
'csum_min': csum_min, 'csum_min': csum_min,
'csum_max': csum_max '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']) best_trade = max(strat_results['trades'], key=lambda x: x['profit_ratio'])
worst_trade = min(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 # 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 # command stores these results and newer version of freqtrade must be able to handle old
# results with missing new fields. # results with missing new fields.
@ -719,6 +738,7 @@ def text_table_add_metrics(strat_results: Dict) -> str:
('', ''), # Empty line to improve readability ('', ''), # Empty line to improve readability
('Total/Daily Avg Trades', ('Total/Daily Avg Trades',
f"{strat_results['total_trades']} / {strat_results['trades_per_day']}"), f"{strat_results['total_trades']} / {strat_results['trades_per_day']}"),
('Starting balance', round_coin_value(strat_results['starting_balance'], ('Starting balance', round_coin_value(strat_results['starting_balance'],
strat_results['stake_currency'])), strat_results['stake_currency'])),
('Final balance', round_coin_value(strat_results['final_balance'], ('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'])), strat_results['stake_currency'])),
('Total trade volume', round_coin_value(strat_results['total_volume'], ('Total trade volume', round_coin_value(strat_results['total_volume'],
strat_results['stake_currency'])), strat_results['stake_currency'])),
*short_metrics,
('', ''), # Empty line to improve readability ('', ''), # Empty line to improve readability
('Best Pair', f"{strat_results['best_pair']['key']} " ('Best Pair', f"{strat_results['best_pair']['key']} "
f"{strat_results['best_pair']['profit_sum']:.2%}"), f"{strat_results['best_pair']['profit_sum']:.2%}"),

View File

@ -698,7 +698,8 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
'min_rate': [0.10370188, 0.10300000000000001], 'min_rate': [0.10370188, 0.10300000000000001],
'max_rate': [0.10501, 0.1038888], 'max_rate': [0.10501, 0.1038888],
'is_open': [False, False], 'is_open': [False, False],
'buy_tag': [None, None] 'buy_tag': [None, None],
"is_short": [False, False],
}) })
pd.testing.assert_frame_equal(results, expected) pd.testing.assert_frame_equal(results, expected)
data_pair = processed[pair] 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], 'stake_amount': [0.01, 0.01],
'open_rate': [0.104445, 0.10302485], 'open_rate': [0.104445, 0.10302485],
'close_rate': [0.104969, 0.103541], 'close_rate': [0.104969, 0.103541],
"is_short": [False, False],
'sell_reason': [SellType.ROI, SellType.ROI] 'sell_reason': [SellType.ROI, SellType.ROI]
}) })
result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'], 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], 'stake_amount': [0.01, 0.01, 0.01],
'open_rate': [0.104445, 0.10302485, 0.122541], 'open_rate': [0.104445, 0.10302485, 0.122541],
'close_rate': [0.104969, 0.103541, 0.123541], 'close_rate': [0.104969, 0.103541, 0.123541],
"is_short": [False, False, False],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS] 'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
}) })
backtestmock = MagicMock(side_effect=[ 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), '2018-01-30 05:35:00', ], utc=True),
'trade_duration': [235, 40], 'trade_duration': [235, 40],
'is_open': [False, False], 'is_open': [False, False],
'is_short': [False, False],
'stake_amount': [0.01, 0.01], 'stake_amount': [0.01, 0.01],
'open_rate': [0.104445, 0.10302485], 'open_rate': [0.104445, 0.10302485],
'close_rate': [0.104969, 0.103541], '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), '2018-01-30 08:30:00'], utc=True),
'trade_duration': [47, 40, 20], 'trade_duration': [47, 40, 20],
'is_open': [False, False, False], 'is_open': [False, False, False],
'is_short': [False, False, False],
'stake_amount': [0.01, 0.01, 0.01], 'stake_amount': [0.01, 0.01, 0.01],
'open_rate': [0.104445, 0.10302485, 0.122541], 'open_rate': [0.104445, 0.10302485, 0.122541],
'close_rate': [0.104969, 0.103541, 0.123541], 'close_rate': [0.104969, 0.103541, 0.123541],

View File

@ -344,6 +344,7 @@ def test_hyperopt_format_results(hyperopt):
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217], "close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"trade_duration": [123, 34, 31, 14], "trade_duration": [123, 34, 31, 14],
"is_open": [False, False, False, True], "is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01], "stake_amount": [0.01, 0.01, 0.01, 0.01],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS, "sell_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL] 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], "close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"trade_duration": [123, 34, 31, 14], "trade_duration": [123, 34, 31, 14],
"is_open": [False, False, False, True], "is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01], "stake_amount": [0.01, 0.01, 0.01, 0.01],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS, "sell_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL] SellType.ROI, SellType.FORCE_SELL]

View File

@ -76,6 +76,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
"close_rate": [0.002546, 0.003014, 0.003103, 0.003217], "close_rate": [0.002546, 0.003014, 0.003103, 0.003217],
"trade_duration": [123, 34, 31, 14], "trade_duration": [123, 34, 31, 14],
"is_open": [False, False, False, True], "is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01], "stake_amount": [0.01, 0.01, 0.01, 0.01],
"sell_reason": [SellType.ROI, SellType.STOP_LOSS, "sell_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL] 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], "close_rate": [0.002546, 0.003014, 0.0032903, 0.003217],
"trade_duration": [123, 34, 31, 14], "trade_duration": [123, 34, 31, 14],
"is_open": [False, False, False, True], "is_open": [False, False, False, True],
"is_short": [False, False, False, False],
"stake_amount": [0.01, 0.01, 0.01, 0.01], "stake_amount": [0.01, 0.01, 0.01, 0.01],
"sell_reason": [SellType.ROI, SellType.ROI, "sell_reason": [SellType.ROI, SellType.ROI,
SellType.STOP_LOSS, SellType.FORCE_SELL] SellType.STOP_LOSS, SellType.FORCE_SELL]

View File

@ -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] 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]) @pytest.mark.parametrize("is_short", [False, True])
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) -> None: limit_order_open, is_short, trading_mode) -> None:
open_order = limit_order_open[enter_side(is_short)] open_order = limit_order_open[enter_side(is_short)]
order = limit_order[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_RPCManager(mocker)
patch_exchange(mocker) patch_exchange(mocker)
freqtrade = FreqtradeBot(default_conf_usdt) freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False) freqtrade.strategy.confirm_trade_entry = MagicMock(return_value=False)
freqtrade.strategy.leverage = MagicMock(return_value=leverage)
stake_amount = 2 stake_amount = 2
bid = 0.11 bid = 0.11
enter_rate_mock = MagicMock(return_value=bid) 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, create_order=enter_mm,
get_min_pair_stake_amount=MagicMock(return_value=1), get_min_pair_stake_amount=MagicMock(return_value=1),
get_fee=fee, get_fee=fee,
get_funding_fees=MagicMock(return_value=0),
) )
pair = 'ETH/USDT' 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] call_args = enter_mm.call_args_list[0][1]
assert call_args['pair'] == pair assert call_args['pair'] == pair
assert call_args['rate'] == bid 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() enter_rate_mock.reset_mock()
# Should create an open trade with an open order id # 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] call_args = enter_mm.call_args_list[1][1]
assert call_args['pair'] == pair assert call_args['pair'] == pair
assert call_args['rate'] == fix_price 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 # In case of closed order
order['status'] = 'closed' 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 # In case of the order is rejected and not filled at all
order['status'] = 'rejected' order['status'] = 'rejected'
order['amount'] = 30.0 order['amount'] = 30.0 * leverage
order['filled'] = 0.0 order['filled'] = 0.0
order['remaining'] = 30.0 order['remaining'] = 30.0
order['price'] = 0.5 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', mocker.patch('freqtrade.exchange.Exchange.create_order',
MagicMock(return_value=order)) MagicMock(return_value=order))
assert not freqtrade.execute_entry(pair, stake_amount) 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... # Fail to get price...
mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0)) mocker.patch('freqtrade.exchange.Exchange.get_rate', MagicMock(return_value=0.0))