Merge pull request #5924 from freqtrade/feat/leverage
call leverage methods
This commit is contained in:
commit
aad37bb8f3
@ -371,14 +371,14 @@ 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 |
|
||||||
| | |
|
| | |
|
||||||
| Total/Daily Avg Trades| 429 / 3.575 |
|
| Total/Daily Avg Trades | 429 / 3.575 |
|
||||||
| Starting balance | 0.01000000 BTC |
|
| Starting balance | 0.01000000 BTC |
|
||||||
| Final balance | 0.01762792 BTC |
|
| Final balance | 0.01762792 BTC |
|
||||||
| Absolute profit | 0.00762792 BTC |
|
| Absolute profit | 0.00762792 BTC |
|
||||||
@ -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
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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?, ...?)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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%}"),
|
||||||
|
@ -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],
|
||||||
|
@ -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]
|
||||||
|
@ -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]
|
||||||
|
@ -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))
|
||||||
|
Loading…
Reference in New Issue
Block a user