Merge pull request #6632 from freqtrade/short_terminology

Short terminology
This commit is contained in:
Matthias 2022-04-03 11:10:16 +02:00 committed by GitHub
commit d054916439
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 269 additions and 246 deletions

View File

@ -56,7 +56,7 @@ Currently, the arguments are:
* `results`: DataFrame containing the resulting trades. * `results`: DataFrame containing the resulting trades.
The following columns are available in results (corresponds to the output-file of backtesting when used with `--export trades`): The following columns are available in results (corresponds to the output-file of backtesting when used with `--export trades`):
`pair, profit_ratio, profit_abs, open_date, open_rate, fee_open, close_date, close_rate, fee_close, amount, trade_duration, is_open, sell_reason, stake_amount, min_rate, max_rate, stop_loss_ratio, stop_loss_abs` `pair, profit_ratio, profit_abs, open_date, open_rate, fee_open, close_date, close_rate, fee_close, amount, trade_duration, is_open, exit_reason, stake_amount, min_rate, max_rate, stop_loss_ratio, stop_loss_abs`
* `trade_count`: Amount of trades (identical to `len(results)`) * `trade_count`: Amount of trades (identical to `len(results)`)
* `min_date`: Start date of the timerange used * `min_date`: Start date of the timerange used
* `min_date`: End date of the timerange used * `min_date`: End date of the timerange used

View File

@ -65,7 +65,7 @@ SET is_open=0,
close_rate=<close_rate>, close_rate=<close_rate>,
close_profit = close_rate / open_rate - 1, close_profit = close_rate / open_rate - 1,
close_profit_abs = (amount * <close_rate> * (1 - fee_close) - (amount * (open_rate * (1 - fee_open)))), close_profit_abs = (amount * <close_rate> * (1 - fee_close) - (amount * (open_rate * (1 - fee_open)))),
sell_reason=<sell_reason> exit_reason=<exit_reason>
WHERE id=<trade_ID_to_update>; WHERE id=<trade_ID_to_update>;
``` ```
@ -78,7 +78,7 @@ SET is_open=0,
close_rate=0.19638016, close_rate=0.19638016,
close_profit=0.0496, close_profit=0.0496,
close_profit_abs = (amount * 0.19638016 * (1 - fee_close) - (amount * (open_rate * (1 - fee_open)))), close_profit_abs = (amount * 0.19638016 * (1 - fee_close) - (amount * (open_rate * (1 - fee_open)))),
sell_reason='force_sell' exit_reason='force_sell'
WHERE id=31; WHERE id=31;
``` ```

View File

@ -898,7 +898,7 @@ Stoploss values returned from `custom_stoploss` must specify a percentage relati
!!! Note !!! Note
Providing invalid input to `stoploss_from_open()` may produce "CustomStoploss function did not return valid stoploss" warnings. Providing invalid input to `stoploss_from_open()` may produce "CustomStoploss function did not return valid stoploss" warnings.
This may happen if `current_profit` parameter is below specified `open_relative_stop`. Such situations may arise when closing trade This may happen if `current_profit` parameter is below specified `open_relative_stop`. Such situations may arise when closing trade
is blocked by `confirm_trade_exit()` method. Warnings can be solved by never blocking stop loss sells by checking `sell_reason` in is blocked by `confirm_trade_exit()` method. Warnings can be solved by never blocking stop loss sells by checking `exit_reason` in
`confirm_trade_exit()`, or by using `return stoploss_from_open(...) or 1` idiom, which will request to not change stop loss when `confirm_trade_exit()`, or by using `return stoploss_from_open(...) or 1` idiom, which will request to not change stop loss when
`current_profit < open_relative_stop`. `current_profit < open_relative_stop`.

View File

@ -129,7 +129,7 @@ print(stats['strategy_comparison'])
trades = load_backtest_data(backtest_dir) trades = load_backtest_data(backtest_dir)
# Show value-counts per pair # Show value-counts per pair
trades.groupby("pair")["sell_reason"].value_counts() trades.groupby("pair")["exit_reason"].value_counts()
``` ```
## Plotting daily profit / equity line ## Plotting daily profit / equity line
@ -182,7 +182,7 @@ from freqtrade.data.btanalysis import load_trades_from_db
trades = load_trades_from_db("sqlite:///tradesv3.sqlite") trades = load_trades_from_db("sqlite:///tradesv3.sqlite")
# Display results # Display results
trades.groupby("pair")["sell_reason"].value_counts() trades.groupby("pair")["exit_reason"].value_counts()
``` ```
## Analyze the loaded trades for trade parallelism ## Analyze the loaded trades for trade parallelism

View File

@ -24,7 +24,12 @@ You can use the quick summary as checklist. Please refer to the detailed section
* [`sell` -> `exit_long`](#populate_sell_trend) * [`sell` -> `exit_long`](#populate_sell_trend)
* [`buy_tag` -> `enter_tag` (used for both long and short trades)](#populate_buy_trend) * [`buy_tag` -> `enter_tag` (used for both long and short trades)](#populate_buy_trend)
* [New column `enter_short` and corresponding new column `exit_short`](#populate_sell_trend) * [New column `enter_short` and corresponding new column `exit_short`](#populate_sell_trend)
* trade-object now has the following new properties: `is_short`, `enter_side`, `exit_side` and `trade_direction`. * trade-object now has the following new properties:
* `is_short`
* `enter_side`
* `exit_side`
* `trade_direction`
* renamed: `sell_reason` -> `exit_reason`
* [Renamed `trade.nr_of_successful_buys` to `trade.nr_of_successful_entries` (mostly relevant for `adjust_trade_position()`)](#adjust-trade-position-changes) * [Renamed `trade.nr_of_successful_buys` to `trade.nr_of_successful_entries` (mostly relevant for `adjust_trade_position()`)](#adjust-trade-position-changes)
* Introduced new [`leverage` callback](strategy-callbacks.md#leverage-callback). * Introduced new [`leverage` callback](strategy-callbacks.md#leverage-callback).
* Informative pairs can now pass a 3rd element in the Tuple, defining the candle type. * Informative pairs can now pass a 3rd element in the Tuple, defining the candle type.

View File

@ -180,7 +180,9 @@ official commands. You can ask at any moment for help with `/help`.
| `/daily <n>` | Shows profit or loss per day, over the last n days (n defaults to 7) | `/daily <n>` | Shows profit or loss per day, over the last n days (n defaults to 7)
| `/weekly <n>` | Shows profit or loss per week, over the last n weeks (n defaults to 8) | `/weekly <n>` | Shows profit or loss per week, over the last n weeks (n defaults to 8)
| `/monthly <n>` | Shows profit or loss per month, over the last n months (n defaults to 6) | `/monthly <n>` | Shows profit or loss per month, over the last n months (n defaults to 6)
| `/stats` | Shows Wins / losses by Sell reason as well as Avg. holding durations for buys and sells | `/stats` | Shows Wins / losses by Exit reason as well as Avg. holding durations for buys and sells
| `/exits` | Shows Wins / losses by Exit reason as well as Avg. holding durations for buys and sells
| `/entries` | Shows Wins / losses by Exit reason as well as Avg. holding durations for buys and sells
| `/whitelist` | Show the current whitelist | `/whitelist` | Show the current whitelist
| `/blacklist [pair]` | Show the current blacklist, or adds a pair to the blacklist. | `/blacklist [pair]` | Show the current blacklist, or adds a pair to the blacklist.
| `/edge` | Show validated pairs by Edge if it is enabled. | `/edge` | Show validated pairs by Edge if it is enabled.

View File

@ -178,7 +178,7 @@ Possible parameters are:
* `stake_currency` * `stake_currency`
* `base_currency` * `base_currency`
* `fiat_currency` * `fiat_currency`
* `sell_reason` * `exit_reason`
* `order_type` * `order_type`
* `open_date` * `open_date`
* `close_date` * `close_date`
@ -203,7 +203,7 @@ Possible parameters are:
* `stake_currency` * `stake_currency`
* `base_currency` * `base_currency`
* `fiat_currency` * `fiat_currency`
* `sell_reason` * `exit_reason`
* `order_type` * `order_type`
* `open_date` * `open_date`
* `close_date` * `close_date`
@ -228,7 +228,7 @@ Possible parameters are:
* `stake_currency` * `stake_currency`
* `base_currency` * `base_currency`
* `fiat_currency` * `fiat_currency`
* `sell_reason` * `exit_reason`
* `order_type` * `order_type`
* `open_date` * `open_date`
* `close_date` * `close_date`

View File

@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date', BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
'open_rate', 'close_rate', 'open_rate', 'close_rate',
'fee_open', 'fee_close', 'trade_duration', 'fee_open', 'fee_close', 'trade_duration',
'profit_ratio', 'profit_abs', 'sell_reason', 'profit_ratio', 'profit_abs', 'exit_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', 'enter_tag', 'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'enter_tag',
'is_short' 'is_short'

View File

@ -1010,7 +1010,7 @@ class FreqtradeBot(LoggingMixin):
# We check if stoploss order is fulfilled # We check if stoploss order is fulfilled
if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'): if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'):
trade.sell_reason = ExitType.STOPLOSS_ON_EXCHANGE.value trade.exit_reason = ExitType.STOPLOSS_ON_EXCHANGE.value
self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order, self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order,
stoploss_order=True) stoploss_order=True)
# Lock pair for one candle to prevent immediate rebuys # Lock pair for one candle to prevent immediate rebuys
@ -1286,7 +1286,7 @@ class FreqtradeBot(LoggingMixin):
trade.close_date = None trade.close_date = None
trade.is_open = True trade.is_open = True
trade.open_order_id = None trade.open_order_id = None
trade.sell_reason = None trade.exit_reason = None
cancelled = True cancelled = True
else: else:
# TODO: figure out how to handle partially complete sell orders # TODO: figure out how to handle partially complete sell orders
@ -1416,7 +1416,7 @@ class FreqtradeBot(LoggingMixin):
trade.open_order_id = order['id'] trade.open_order_id = order['id']
trade.sell_order_status = '' trade.sell_order_status = ''
trade.close_rate_requested = limit trade.close_rate_requested = limit
trade.sell_reason = exit_tag or exit_check.exit_reason trade.exit_reason = exit_tag or exit_check.exit_reason
# Lock pair for one candle to prevent immediate re-trading # Lock pair for one candle to prevent immediate re-trading
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc), self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
@ -1461,7 +1461,8 @@ class FreqtradeBot(LoggingMixin):
'profit_ratio': profit_ratio, 'profit_ratio': profit_ratio,
'buy_tag': trade.enter_tag, 'buy_tag': trade.enter_tag,
'enter_tag': trade.enter_tag, 'enter_tag': trade.enter_tag,
'sell_reason': trade.sell_reason, 'sell_reason': trade.exit_reason, # Deprecated
'exit_reason': trade.exit_reason,
'open_date': trade.open_date, 'open_date': trade.open_date,
'close_date': trade.close_date or datetime.utcnow(), 'close_date': trade.close_date or datetime.utcnow(),
'stake_currency': self.config['stake_currency'], 'stake_currency': self.config['stake_currency'],
@ -1509,7 +1510,8 @@ class FreqtradeBot(LoggingMixin):
'profit_ratio': profit_ratio, 'profit_ratio': profit_ratio,
'buy_tag': trade.enter_tag, 'buy_tag': trade.enter_tag,
'enter_tag': trade.enter_tag, 'enter_tag': trade.enter_tag,
'sell_reason': trade.sell_reason, 'sell_reason': trade.exit_reason, # Deprecated
'exit_reason': trade.exit_reason,
'open_date': trade.open_date, 'open_date': trade.open_date,
'close_date': trade.close_date or datetime.now(timezone.utc), 'close_date': trade.close_date or datetime.now(timezone.utc),
'stake_currency': self.config['stake_currency'], 'stake_currency': self.config['stake_currency'],

View File

@ -555,7 +555,7 @@ class Backtesting:
current_time=sell_candle_time): current_time=sell_candle_time):
return None return None
trade.sell_reason = sell.exit_reason trade.exit_reason = sell.exit_reason
# Checks and adds an exit tag, after checking that the length of the # Checks and adds an exit tag, after checking that the length of the
# sell_row has the length for an exit tag column # sell_row has the length for an exit tag column
@ -564,7 +564,7 @@ class Backtesting:
and sell_row[EXIT_TAG_IDX] is not None and sell_row[EXIT_TAG_IDX] is not None
and len(sell_row[EXIT_TAG_IDX]) > 0 and len(sell_row[EXIT_TAG_IDX]) > 0
): ):
trade.sell_reason = sell_row[EXIT_TAG_IDX] trade.exit_reason = sell_row[EXIT_TAG_IDX]
self.order_id_counter += 1 self.order_id_counter += 1
order = Order( order = Order(
@ -810,7 +810,7 @@ class Backtesting:
sell_row = data[pair][-1] sell_row = data[pair][-1]
trade.close_date = sell_row[DATE_IDX].to_pydatetime() trade.close_date = sell_row[DATE_IDX].to_pydatetime()
trade.sell_reason = ExitType.FORCE_SELL.value trade.exit_reason = ExitType.FORCE_SELL.value
trade.close(sell_row[OPEN_IDX], show_msg=False) trade.close(sell_row[OPEN_IDX], show_msg=False)
LocalTrade.close_bt_trade(trade) LocalTrade.close_bt_trade(trade)
# Deepcopy object to have wallets update correctly # Deepcopy object to have wallets update correctly

View File

@ -166,7 +166,7 @@ def generate_tag_metrics(tag_type: str,
return [] return []
def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List[Dict]: def generate_exit_reason_stats(max_open_trades: int, results: DataFrame) -> List[Dict]:
""" """
Generate small table outlining Backtest results Generate small table outlining Backtest results
:param max_open_trades: Max_open_trades parameter :param max_open_trades: Max_open_trades parameter
@ -175,8 +175,8 @@ def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List
""" """
tabular_data = [] tabular_data = []
for reason, count in results['sell_reason'].value_counts().iteritems(): for reason, count in results['exit_reason'].value_counts().iteritems():
result = results.loc[results['sell_reason'] == reason] result = results.loc[results['exit_reason'] == reason]
profit_mean = result['profit_ratio'].mean() profit_mean = result['profit_ratio'].mean()
profit_sum = result['profit_ratio'].sum() profit_sum = result['profit_ratio'].sum()
@ -184,7 +184,7 @@ def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List
tabular_data.append( tabular_data.append(
{ {
'sell_reason': reason, 'exit_reason': reason,
'trades': count, 'trades': count,
'wins': len(result[result['profit_abs'] > 0]), 'wins': len(result[result['profit_abs'] > 0]),
'draws': len(result[result['profit_abs'] == 0]), 'draws': len(result[result['profit_abs'] == 0]),
@ -382,7 +382,7 @@ def generate_strategy_stats(pairlist: List[str],
enter_tag_results = generate_tag_metrics("enter_tag", starting_balance=start_balance, enter_tag_results = generate_tag_metrics("enter_tag", starting_balance=start_balance,
results=results, skip_nan=False) results=results, skip_nan=False)
exit_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades, exit_reason_stats = generate_exit_reason_stats(max_open_trades=max_open_trades,
results=results) results=results)
left_open_results = generate_pair_metrics(pairlist, stake_currency=stake_currency, left_open_results = generate_pair_metrics(pairlist, stake_currency=stake_currency,
starting_balance=start_balance, starting_balance=start_balance,
@ -406,7 +406,7 @@ def generate_strategy_stats(pairlist: List[str],
'worst_pair': worst_pair, 'worst_pair': worst_pair,
'results_per_pair': pair_results, 'results_per_pair': pair_results,
'results_per_enter_tag': enter_tag_results, 'results_per_enter_tag': enter_tag_results,
'sell_reason_summary': exit_reason_stats, 'exit_reason_summary': exit_reason_stats,
'left_open_trades': left_open_results, 'left_open_trades': left_open_results,
# 'days_breakdown_stats': days_breakdown_stats, # 'days_breakdown_stats': days_breakdown_stats,
@ -572,7 +572,7 @@ def text_table_bt_results(pair_results: List[Dict[str, Any]], stake_currency: st
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") floatfmt=floatfmt, tablefmt="orgtbl", stralign="right")
def text_table_exit_reason(sell_reason_stats: List[Dict[str, Any]], stake_currency: str) -> str: def text_table_exit_reason(exit_reason_stats: List[Dict[str, Any]], stake_currency: str) -> str:
""" """
Generate small table outlining Backtest results Generate small table outlining Backtest results
:param sell_reason_stats: Exit reason metrics :param sell_reason_stats: Exit reason metrics
@ -590,12 +590,12 @@ def text_table_exit_reason(sell_reason_stats: List[Dict[str, Any]], stake_curren
] ]
output = [[ output = [[
t['sell_reason'], t['trades'], t.get('exit_reason', t.get('sell_reason')), t['trades'],
_generate_wins_draws_losses(t['wins'], t['draws'], t['losses']), _generate_wins_draws_losses(t['wins'], t['draws'], t['losses']),
t['profit_mean_pct'], t['profit_sum_pct'], t['profit_mean_pct'], t['profit_sum_pct'],
round_coin_value(t['profit_total_abs'], stake_currency, False), round_coin_value(t['profit_total_abs'], stake_currency, False),
t['profit_total_pct'], t['profit_total_pct'],
] for t in sell_reason_stats] ] for t in exit_reason_stats]
return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right") return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right")
@ -813,7 +813,8 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
print(' ENTER TAG STATS '.center(len(table.splitlines()[0]), '=')) print(' ENTER TAG STATS '.center(len(table.splitlines()[0]), '='))
print(table) print(table)
table = text_table_exit_reason(sell_reason_stats=results['sell_reason_summary'], exit_reasons = results.get('exit_reason_summary', results.get('sell_reason_summary'))
table = text_table_exit_reason(exit_reason_stats=exit_reasons,
stake_currency=stake_currency) stake_currency=stake_currency)
if isinstance(table, str) and len(table) > 0: if isinstance(table, str) and len(table) > 0:
print(' EXIT REASON STATS '.center(len(table.splitlines()[0]), '=')) print(' EXIT REASON STATS '.center(len(table.splitlines()[0]), '='))

View File

@ -74,7 +74,7 @@ def migrate_trades_and_orders_table(
stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null') stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null')
max_rate = get_column_def(cols, 'max_rate', '0.0') max_rate = get_column_def(cols, 'max_rate', '0.0')
min_rate = get_column_def(cols, 'min_rate', 'null') min_rate = get_column_def(cols, 'min_rate', 'null')
sell_reason = get_column_def(cols, 'sell_reason', 'null') exit_reason = get_column_def(cols, 'sell_reason', get_column_def(cols, 'exit_reason', 'null'))
strategy = get_column_def(cols, 'strategy', 'null') strategy = get_column_def(cols, 'strategy', 'null')
enter_tag = get_column_def(cols, 'buy_tag', get_column_def(cols, 'enter_tag', 'null')) enter_tag = get_column_def(cols, 'buy_tag', get_column_def(cols, 'enter_tag', 'null'))
@ -136,7 +136,7 @@ def migrate_trades_and_orders_table(
stake_amount, amount, amount_requested, open_date, close_date, open_order_id, stake_amount, amount, amount_requested, open_date, close_date, open_order_id,
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct, stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
stoploss_order_id, stoploss_last_update, stoploss_order_id, stoploss_last_update,
max_rate, min_rate, sell_reason, sell_order_status, strategy, enter_tag, max_rate, min_rate, exit_reason, sell_order_status, strategy, enter_tag,
timeframe, open_trade_value, close_profit_abs, timeframe, open_trade_value, close_profit_abs,
trading_mode, leverage, liquidation_price, is_short, trading_mode, leverage, liquidation_price, is_short,
interest_rate, funding_fees interest_rate, funding_fees
@ -152,7 +152,7 @@ def migrate_trades_and_orders_table(
{initial_stop_loss} initial_stop_loss, {initial_stop_loss} initial_stop_loss,
{initial_stop_loss_pct} initial_stop_loss_pct, {initial_stop_loss_pct} initial_stop_loss_pct,
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update, {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
{max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason, {max_rate} max_rate, {min_rate} min_rate, {exit_reason} exit_reason,
{sell_order_status} sell_order_status, {sell_order_status} sell_order_status,
{strategy} strategy, {enter_tag} enter_tag, {timeframe} timeframe, {strategy} strategy, {enter_tag} enter_tag, {timeframe} timeframe,
{open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs, {open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs,
@ -234,7 +234,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
# Migrates both trades and orders table! # Migrates both trades and orders table!
# if ('orders' not in previous_tables # if ('orders' not in previous_tables
# or not has_column(cols_orders, 'leverage')): # or not has_column(cols_orders, 'leverage')):
if not has_column(cols, 'liquidation_price'): if not has_column(cols, 'exit_reason'):
logger.info(f"Running database migration for trades - " logger.info(f"Running database migration for trades - "
f"backup: {table_back_name}, {order_table_bak_name}") f"backup: {table_back_name}, {order_table_bak_name}")
migrate_trades_and_orders_table( migrate_trades_and_orders_table(

View File

@ -316,7 +316,7 @@ class LocalTrade():
max_rate: float = 0.0 max_rate: float = 0.0
# Lowest price reached # Lowest price reached
min_rate: float = 0.0 min_rate: float = 0.0
sell_reason: str = '' exit_reason: str = ''
sell_order_status: str = '' sell_order_status: str = ''
strategy: str = '' strategy: str = ''
enter_tag: Optional[str] = None enter_tag: Optional[str] = None
@ -459,7 +459,8 @@ class LocalTrade():
'profit_pct': round(self.close_profit * 100, 2) if self.close_profit else None, 'profit_pct': round(self.close_profit * 100, 2) if self.close_profit else None,
'profit_abs': self.close_profit_abs, 'profit_abs': self.close_profit_abs,
'sell_reason': self.sell_reason, 'sell_reason': self.exit_reason, # Deprecated
'exit_reason': self.exit_reason,
'sell_order_status': self.sell_order_status, 'sell_order_status': self.sell_order_status,
'stop_loss_abs': self.stop_loss, 'stop_loss_abs': self.stop_loss,
'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None, 'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None,
@ -618,7 +619,7 @@ class LocalTrade():
elif order.ft_order_side == 'stoploss': elif order.ft_order_side == 'stoploss':
self.stoploss_order_id = None self.stoploss_order_id = None
self.close_rate_requested = self.stop_loss self.close_rate_requested = self.stop_loss
self.sell_reason = ExitType.STOPLOSS_ON_EXCHANGE.value self.exit_reason = ExitType.STOPLOSS_ON_EXCHANGE.value
if self.is_open: if self.is_open:
logger.info(f'{order.order_type.upper()} is hit for {self}.') logger.info(f'{order.order_type.upper()} is hit for {self}.')
self.close(order.safe_price) self.close(order.safe_price)
@ -947,6 +948,11 @@ class LocalTrade():
""" """
return len(self.select_filled_orders('sell')) return len(self.select_filled_orders('sell'))
@property
def sell_reason(self) -> str:
""" DEPRECATED! Please use exit_reason instead."""
return self.exit_reason
@staticmethod @staticmethod
def get_trades_proxy(*, pair: str = None, is_open: bool = None, def get_trades_proxy(*, pair: str = None, is_open: bool = None,
open_date: datetime = None, close_date: datetime = None, open_date: datetime = None, close_date: datetime = None,
@ -1076,7 +1082,7 @@ class Trade(_DECL_BASE, LocalTrade):
max_rate = Column(Float, nullable=True, default=0.0) max_rate = Column(Float, nullable=True, default=0.0)
# Lowest price reached # Lowest price reached
min_rate = Column(Float, nullable=True) min_rate = Column(Float, nullable=True)
sell_reason = Column(String(100), nullable=True) exit_reason = Column(String(100), nullable=True)
sell_order_status = Column(String(100), nullable=True) sell_order_status = Column(String(100), nullable=True)
strategy = Column(String(100), nullable=True) strategy = Column(String(100), nullable=True)
enter_tag = Column(String(100), nullable=True) enter_tag = Column(String(100), nullable=True)
@ -1283,7 +1289,7 @@ class Trade(_DECL_BASE, LocalTrade):
] ]
@staticmethod @staticmethod
def get_sell_reason_performance(pair: Optional[str]) -> List[Dict[str, Any]]: def get_exit_reason_performance(pair: Optional[str]) -> List[Dict[str, Any]]:
""" """
Returns List of dicts containing all Trades, based on sell reason performance Returns List of dicts containing all Trades, based on sell reason performance
Can either be average for all pairs or a specific pair provided Can either be average for all pairs or a specific pair provided
@ -1295,30 +1301,30 @@ class Trade(_DECL_BASE, LocalTrade):
filters.append(Trade.pair == pair) filters.append(Trade.pair == pair)
sell_tag_perf = Trade.query.with_entities( sell_tag_perf = Trade.query.with_entities(
Trade.sell_reason, Trade.exit_reason,
func.sum(Trade.close_profit).label('profit_sum'), func.sum(Trade.close_profit).label('profit_sum'),
func.sum(Trade.close_profit_abs).label('profit_sum_abs'), func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
func.count(Trade.pair).label('count') func.count(Trade.pair).label('count')
).filter(*filters)\ ).filter(*filters)\
.group_by(Trade.sell_reason) \ .group_by(Trade.exit_reason) \
.order_by(desc('profit_sum_abs')) \ .order_by(desc('profit_sum_abs')) \
.all() .all()
return [ return [
{ {
'sell_reason': sell_reason if sell_reason is not None else "Other", 'exit_reason': exit_reason if exit_reason is not None else "Other",
'profit_ratio': profit, 'profit_ratio': profit,
'profit_pct': round(profit * 100, 2), 'profit_pct': round(profit * 100, 2),
'profit_abs': profit_abs, 'profit_abs': profit_abs,
'count': count 'count': count
} }
for sell_reason, profit, profit_abs, count in sell_tag_perf for exit_reason, profit, profit_abs, count in sell_tag_perf
] ]
@staticmethod @staticmethod
def get_mix_tag_performance(pair: Optional[str]) -> List[Dict[str, Any]]: def get_mix_tag_performance(pair: Optional[str]) -> List[Dict[str, Any]]:
""" """
Returns List of dicts containing all Trades, based on buy_tag + sell_reason performance Returns List of dicts containing all Trades, based on entry_tag + exit_reason performance
Can either be average for all pairs or a specific pair provided Can either be average for all pairs or a specific pair provided
NOTE: Not supported in Backtesting. NOTE: Not supported in Backtesting.
""" """
@ -1330,7 +1336,7 @@ class Trade(_DECL_BASE, LocalTrade):
mix_tag_perf = Trade.query.with_entities( mix_tag_perf = Trade.query.with_entities(
Trade.id, Trade.id,
Trade.enter_tag, Trade.enter_tag,
Trade.sell_reason, Trade.exit_reason,
func.sum(Trade.close_profit).label('profit_sum'), func.sum(Trade.close_profit).label('profit_sum'),
func.sum(Trade.close_profit_abs).label('profit_sum_abs'), func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
func.count(Trade.pair).label('count') func.count(Trade.pair).label('count')
@ -1340,12 +1346,12 @@ class Trade(_DECL_BASE, LocalTrade):
.all() .all()
return_list: List[Dict] = [] return_list: List[Dict] = []
for id, enter_tag, sell_reason, profit, profit_abs, count in mix_tag_perf: for id, enter_tag, exit_reason, profit, profit_abs, count in mix_tag_perf:
enter_tag = enter_tag if enter_tag is not None else "Other" enter_tag = enter_tag if enter_tag is not None else "Other"
sell_reason = sell_reason if sell_reason is not None else "Other" exit_reason = exit_reason if exit_reason is not None else "Other"
if(sell_reason is not None and enter_tag is not None): if(exit_reason is not None and enter_tag is not None):
mix_tag = enter_tag + " " + sell_reason mix_tag = enter_tag + " " + exit_reason
i = 0 i = 0
if not any(item["mix_tag"] == mix_tag for item in return_list): if not any(item["mix_tag"] == mix_tag for item in return_list):
return_list.append({'mix_tag': mix_tag, return_list.append({'mix_tag': mix_tag,

View File

@ -240,7 +240,7 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
trades['desc'] = trades.apply( trades['desc'] = trades.apply(
lambda row: f"{row['profit_ratio']:.2%}, " + lambda row: f"{row['profit_ratio']:.2%}, " +
(f"{row['enter_tag']}, " if row['enter_tag'] is not None else "") + (f"{row['enter_tag']}, " if row['enter_tag'] is not None else "") +
f"{row['sell_reason']}, " + f"{row['exit_reason']}, " +
f"{row['trade_duration']} min", f"{row['trade_duration']} min",
axis=1) axis=1)
trade_buys = go.Scatter( trade_buys = go.Scatter(

View File

@ -41,19 +41,9 @@ class StoplossGuard(IProtection):
Evaluate recent trades Evaluate recent trades
""" """
look_back_until = date_now - timedelta(minutes=self._lookback_period) look_back_until = date_now - timedelta(minutes=self._lookback_period)
# filters = [
# Trade.is_open.is_(False),
# Trade.close_date > look_back_until,
# or_(Trade.sell_reason == ExitType.STOP_LOSS.value,
# and_(Trade.sell_reason == ExitType.TRAILING_STOP_LOSS.value,
# Trade.close_profit < 0))
# ]
# if pair:
# filters.append(Trade.pair == pair)
# trades = Trade.get_trades(filters).all()
trades1 = Trade.get_trades_proxy(pair=pair, is_open=False, close_date=look_back_until) trades1 = Trade.get_trades_proxy(pair=pair, is_open=False, close_date=look_back_until)
trades = [trade for trade in trades1 if (str(trade.sell_reason) in ( trades = [trade for trade in trades1 if (str(trade.exit_reason) in (
ExitType.TRAILING_STOP_LOSS.value, ExitType.STOP_LOSS.value, ExitType.TRAILING_STOP_LOSS.value, ExitType.STOP_LOSS.value,
ExitType.STOPLOSS_ON_EXCHANGE.value) ExitType.STOPLOSS_ON_EXCHANGE.value)
and trade.close_profit and trade.close_profit < 0)] and trade.close_profit and trade.close_profit < 0)]

View File

@ -113,7 +113,7 @@ class SellReason(BaseModel):
class Stats(BaseModel): class Stats(BaseModel):
sell_reasons: Dict[str, SellReason] exit_reasons: Dict[str, SellReason]
durations: Dict[str, Optional[float]] durations: Dict[str, Optional[float]]
@ -235,7 +235,8 @@ class TradeSchema(BaseModel):
profit_pct: Optional[float] profit_pct: Optional[float]
profit_abs: Optional[float] profit_abs: Optional[float]
profit_fiat: Optional[float] profit_fiat: Optional[float]
sell_reason: Optional[str] sell_reason: Optional[str] # Deprecated
exit_reason: Optional[str]
sell_order_status: Optional[str] sell_order_status: Optional[str]
stop_loss_abs: Optional[float] stop_loss_abs: Optional[float]
stop_loss_ratio: Optional[float] stop_loss_ratio: Optional[float]

View File

@ -428,13 +428,13 @@ class RPC:
return 'losses' return 'losses'
else: else:
return 'draws' return 'draws'
trades = trades = Trade.get_trades([Trade.is_open.is_(False)]) trades: List[Trade] = Trade.get_trades([Trade.is_open.is_(False)])
# Sell reason # Sell reason
sell_reasons = {} exit_reasons = {}
for trade in trades: for trade in trades:
if trade.sell_reason not in sell_reasons: if trade.exit_reason not in exit_reasons:
sell_reasons[trade.sell_reason] = {'wins': 0, 'losses': 0, 'draws': 0} exit_reasons[trade.exit_reason] = {'wins': 0, 'losses': 0, 'draws': 0}
sell_reasons[trade.sell_reason][trade_win_loss(trade)] += 1 exit_reasons[trade.exit_reason][trade_win_loss(trade)] += 1
# Duration # Duration
dur: Dict[str, List[int]] = {'wins': [], 'draws': [], 'losses': []} dur: Dict[str, List[int]] = {'wins': [], 'draws': [], 'losses': []}
@ -448,7 +448,7 @@ class RPC:
losses_dur = sum(dur['losses']) / len(dur['losses']) if len(dur['losses']) > 0 else None losses_dur = sum(dur['losses']) / len(dur['losses']) if len(dur['losses']) > 0 else None
durations = {'wins': wins_dur, 'draws': draws_dur, 'losses': losses_dur} durations = {'wins': wins_dur, 'draws': draws_dur, 'losses': losses_dur}
return {'sell_reasons': sell_reasons, 'durations': durations} return {'exit_reasons': exit_reasons, 'durations': durations}
def _rpc_trade_statistics( def _rpc_trade_statistics(
self, stake_currency: str, fiat_display_currency: str, self, stake_currency: str, fiat_display_currency: str,
@ -848,16 +848,16 @@ class RPC:
""" """
return Trade.get_enter_tag_performance(pair) return Trade.get_enter_tag_performance(pair)
def _rpc_sell_reason_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]: def _rpc_exit_reason_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]:
""" """
Handler for sell reason performance. Handler for sell reason performance.
Shows a performance statistic from finished trades Shows a performance statistic from finished trades
""" """
return Trade.get_sell_reason_performance(pair) return Trade.get_exit_reason_performance(pair)
def _rpc_mix_tag_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]: def _rpc_mix_tag_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]:
""" """
Handler for mix tag (enter_tag + sell_reason) performance. Handler for mix tag (enter_tag + exit_reason) performance.
Shows a performance statistic from finished trades Shows a performance statistic from finished trades
""" """
mix_tags = Trade.get_mix_tag_performance(pair) mix_tags = Trade.get_mix_tag_performance(pair)

View File

@ -108,7 +108,8 @@ class Telegram(RPCHandler):
# this needs refactoring of the whole telegram module (same # this needs refactoring of the whole telegram module (same
# problem in _help()). # problem in _help()).
valid_keys: List[str] = [r'/start$', r'/stop$', r'/status$', r'/status table$', valid_keys: List[str] = [r'/start$', r'/stop$', r'/status$', r'/status table$',
r'/trades$', r'/performance$', r'/buys', r'/sells', r'/mix_tags', r'/trades$', r'/performance$', r'/buys', r'/entries',
r'/sells', r'/exits', r'/mix_tags',
r'/daily$', r'/daily \d+$', r'/profit$', r'/profit \d+', r'/daily$', r'/daily \d+$', r'/profit$', r'/profit \d+',
r'/stats$', r'/count$', r'/locks$', r'/balance$', r'/stats$', r'/count$', r'/locks$', r'/balance$',
r'/stopbuy$', r'/reload_config$', r'/show_config$', r'/stopbuy$', r'/reload_config$', r'/show_config$',
@ -161,7 +162,7 @@ class Telegram(RPCHandler):
CommandHandler('delete', self._delete_trade), CommandHandler('delete', self._delete_trade),
CommandHandler('performance', self._performance), CommandHandler('performance', self._performance),
CommandHandler(['buys', 'entries'], self._enter_tag_performance), CommandHandler(['buys', 'entries'], self._enter_tag_performance),
CommandHandler('sells', self._sell_reason_performance), CommandHandler(['sells', 'exits'], self._exit_reason_performance),
CommandHandler('mix_tags', self._mix_tag_performance), CommandHandler('mix_tags', self._mix_tag_performance),
CommandHandler('stats', self._stats), CommandHandler('stats', self._stats),
CommandHandler('daily', self._daily), CommandHandler('daily', self._daily),
@ -192,8 +193,8 @@ class Telegram(RPCHandler):
CallbackQueryHandler(self._performance, pattern='update_performance'), CallbackQueryHandler(self._performance, pattern='update_performance'),
CallbackQueryHandler(self._enter_tag_performance, CallbackQueryHandler(self._enter_tag_performance,
pattern='update_enter_tag_performance'), pattern='update_enter_tag_performance'),
CallbackQueryHandler(self._sell_reason_performance, CallbackQueryHandler(self._exit_reason_performance,
pattern='update_sell_reason_performance'), pattern='update_exit_reason_performance'),
CallbackQueryHandler(self._mix_tag_performance, pattern='update_mix_tag_performance'), CallbackQueryHandler(self._mix_tag_performance, pattern='update_mix_tag_performance'),
CallbackQueryHandler(self._count, pattern='update_count'), CallbackQueryHandler(self._count, pattern='update_count'),
CallbackQueryHandler(self._forceenter_inline), CallbackQueryHandler(self._forceenter_inline),
@ -290,7 +291,7 @@ class Telegram(RPCHandler):
f"*{'Profit' if is_fill else 'Unrealized Profit'}:* " f"*{'Profit' if is_fill else 'Unrealized Profit'}:* "
f"`{msg['profit_ratio']:.2%}{msg['profit_extra']}`\n" f"`{msg['profit_ratio']:.2%}{msg['profit_extra']}`\n"
f"*Enter Tag:* `{msg['enter_tag']}`\n" f"*Enter Tag:* `{msg['enter_tag']}`\n"
f"*Exit Reason:* `{msg['sell_reason']}`\n" f"*Exit Reason:* `{msg['exit_reason']}`\n"
f"*Duration:* `{msg['duration']} ({msg['duration_min']:.1f} min)`\n" f"*Duration:* `{msg['duration']} ({msg['duration_min']:.1f} min)`\n"
f"*Direction:* `{msg['direction']}`\n" f"*Direction:* `{msg['direction']}`\n"
f"{msg['leverage_text']}" f"{msg['leverage_text']}"
@ -361,7 +362,7 @@ class Telegram(RPCHandler):
if isinstance(sell_noti, str): if isinstance(sell_noti, str):
noti = sell_noti noti = sell_noti
else: else:
noti = sell_noti.get(str(msg['sell_reason']), default_noti) noti = sell_noti.get(str(msg['exit_reason']), default_noti)
else: else:
noti = self._config['telegram'] \ noti = self._config['telegram'] \
.get('notification_settings', {}).get(str(msg_type), default_noti) .get('notification_settings', {}).get(str(msg_type), default_noti)
@ -384,7 +385,7 @@ class Telegram(RPCHandler):
return "\N{ROCKET}" return "\N{ROCKET}"
elif float(msg['profit_percent']) >= 0.0: elif float(msg['profit_percent']) >= 0.0:
return "\N{EIGHT SPOKED ASTERISK}" return "\N{EIGHT SPOKED ASTERISK}"
elif msg['sell_reason'] == "stop_loss": elif msg['exit_reason'] == "stop_loss":
return "\N{WARNING SIGN}" return "\N{WARNING SIGN}"
else: else:
return "\N{CROSS MARK}" return "\N{CROSS MARK}"
@ -466,7 +467,7 @@ class Telegram(RPCHandler):
for r in results: for r in results:
r['open_date_hum'] = arrow.get(r['open_date']).humanize() r['open_date_hum'] = arrow.get(r['open_date']).humanize()
r['num_entries'] = len([o for o in r['orders'] if o['ft_is_entry']]) r['num_entries'] = len([o for o in r['orders'] if o['ft_is_entry']])
r['sell_reason'] = r.get('sell_reason', "") r['exit_reason'] = r.get('exit_reason', "")
lines = [ lines = [
"*Trade ID:* `{trade_id}`" + "*Trade ID:* `{trade_id}`" +
("` (since {open_date_hum})`" if r['is_open'] else ""), ("` (since {open_date_hum})`" if r['is_open'] else ""),
@ -475,7 +476,7 @@ class Telegram(RPCHandler):
"*Leverage:* `{leverage}`" if r.get('leverage') else "", "*Leverage:* `{leverage}`" if r.get('leverage') else "",
"*Amount:* `{amount} ({stake_amount} {base_currency})`", "*Amount:* `{amount} ({stake_amount} {base_currency})`",
"*Enter Tag:* `{enter_tag}`" if r['enter_tag'] else "", "*Enter Tag:* `{enter_tag}`" if r['enter_tag'] else "",
"*Exit Reason:* `{sell_reason}`" if r['sell_reason'] else "", "*Exit Reason:* `{exit_reason}`" if r['exit_reason'] else "",
] ]
if position_adjust: if position_adjust:
@ -771,23 +772,23 @@ class Telegram(RPCHandler):
'force_sell': 'Forcesell', 'force_sell': 'Forcesell',
'emergency_sell': 'Emergency Sell', 'emergency_sell': 'Emergency Sell',
} }
sell_reasons_tabulate = [ exit_reasons_tabulate = [
[ [
reason_map.get(reason, reason), reason_map.get(reason, reason),
sum(count.values()), sum(count.values()),
count['wins'], count['wins'],
count['losses'] count['losses']
] for reason, count in stats['sell_reasons'].items() ] for reason, count in stats['exit_reasons'].items()
] ]
sell_reasons_msg = 'No trades yet.' exit_reasons_msg = 'No trades yet.'
for reason in chunks(sell_reasons_tabulate, 25): for reason in chunks(exit_reasons_tabulate, 25):
sell_reasons_msg = tabulate( exit_reasons_msg = tabulate(
reason, reason,
headers=['Sell Reason', 'Sells', 'Wins', 'Losses'] headers=['Exit Reason', 'Exits', 'Wins', 'Losses']
) )
if len(sell_reasons_tabulate) > 25: if len(exit_reasons_tabulate) > 25:
self._send_msg(sell_reasons_msg, ParseMode.MARKDOWN) self._send_msg(exit_reasons_msg, ParseMode.MARKDOWN)
sell_reasons_msg = '' exit_reasons_msg = ''
durations = stats['durations'] durations = stats['durations']
duration_msg = tabulate( duration_msg = tabulate(
@ -799,7 +800,7 @@ class Telegram(RPCHandler):
], ],
headers=['', 'Avg. Duration'] headers=['', 'Avg. Duration']
) )
msg = (f"""```\n{sell_reasons_msg}```\n```\n{duration_msg}```""") msg = (f"""```\n{exit_reasons_msg}```\n```\n{duration_msg}```""")
self._send_msg(msg, ParseMode.MARKDOWN) self._send_msg(msg, ParseMode.MARKDOWN)
@ -1101,7 +1102,7 @@ class Telegram(RPCHandler):
pair = context.args[0] pair = context.args[0]
trades = self._rpc._rpc_enter_tag_performance(pair) trades = self._rpc._rpc_enter_tag_performance(pair)
output = "<b>Buy Tag Performance:</b>\n" output = "<b>Entry Tag Performance:</b>\n"
for i, trade in enumerate(trades): for i, trade in enumerate(trades):
stat_line = ( stat_line = (
f"{i+1}.\t <code>{trade['enter_tag']}\t" f"{i+1}.\t <code>{trade['enter_tag']}\t"
@ -1122,7 +1123,7 @@ class Telegram(RPCHandler):
self._send_msg(str(e)) self._send_msg(str(e))
@authorized_only @authorized_only
def _sell_reason_performance(self, update: Update, context: CallbackContext) -> None: def _exit_reason_performance(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /sells. Handler for /sells.
Shows a performance statistic from finished trades Shows a performance statistic from finished trades
@ -1135,11 +1136,11 @@ class Telegram(RPCHandler):
if context.args and isinstance(context.args[0], str): if context.args and isinstance(context.args[0], str):
pair = context.args[0] pair = context.args[0]
trades = self._rpc._rpc_sell_reason_performance(pair) trades = self._rpc._rpc_exit_reason_performance(pair)
output = "<b>Sell Reason Performance:</b>\n" output = "<b>Exit Reason Performance:</b>\n"
for i, trade in enumerate(trades): for i, trade in enumerate(trades):
stat_line = ( stat_line = (
f"{i+1}.\t <code>{trade['sell_reason']}\t" f"{i+1}.\t <code>{trade['exit_reason']}\t"
f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} "
f"({trade['profit_ratio']:.2%}) " f"({trade['profit_ratio']:.2%}) "
f"({trade['count']})</code>\n") f"({trade['count']})</code>\n")
@ -1151,7 +1152,7 @@ class Telegram(RPCHandler):
output += stat_line output += stat_line
self._send_msg(output, parse_mode=ParseMode.HTML, self._send_msg(output, parse_mode=ParseMode.HTML,
reload_able=True, callback_path="update_sell_reason_performance", reload_able=True, callback_path="update_exit_reason_performance",
query=update.callback_query) query=update.callback_query)
except RPCException as e: except RPCException as e:
self._send_msg(str(e)) self._send_msg(str(e))

View File

@ -187,7 +187,7 @@
"trades = load_backtest_data(backtest_dir)\n", "trades = load_backtest_data(backtest_dir)\n",
"\n", "\n",
"# Show value-counts per pair\n", "# Show value-counts per pair\n",
"trades.groupby(\"pair\")[\"sell_reason\"].value_counts()" "trades.groupby(\"pair\")[\"exit_reason\"].value_counts()"
] ]
}, },
{ {
@ -257,7 +257,7 @@
"trades = load_trades_from_db(\"sqlite:///tradesv3.sqlite\")\n", "trades = load_trades_from_db(\"sqlite:///tradesv3.sqlite\")\n",
"\n", "\n",
"# Display results\n", "# Display results\n",
"trades.groupby(\"pair\")[\"sell_reason\"].value_counts()" "trades.groupby(\"pair\")[\"exit_reason\"].value_counts()"
] ]
}, },
{ {

View File

@ -104,7 +104,7 @@ def mock_trade_2(fee, is_short: bool):
strategy='StrategyTestV3', strategy='StrategyTestV3',
timeframe=5, timeframe=5,
enter_tag='TEST1', enter_tag='TEST1',
sell_reason='sell_signal', exit_reason='sell_signal',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2), close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
is_short=is_short is_short=is_short
@ -164,7 +164,7 @@ def mock_trade_3(fee, is_short: bool):
is_open=False, is_open=False,
strategy='StrategyTestV3', strategy='StrategyTestV3',
timeframe=5, timeframe=5,
sell_reason='roi', exit_reason='roi',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
close_date=datetime.now(tz=timezone.utc), close_date=datetime.now(tz=timezone.utc),
is_short=is_short is_short=is_short
@ -401,7 +401,7 @@ def short_trade(fee):
open_order_id='dry_run_exit_short_12345', open_order_id='dry_run_exit_short_12345',
strategy='DefaultStrategy', strategy='DefaultStrategy',
timeframe=5, timeframe=5,
sell_reason='sell_signal', exit_reason='sell_signal',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
# close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2), # close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
is_short=True is_short=True
@ -490,7 +490,7 @@ def leverage_trade(fee):
open_order_id='dry_run_leverage_buy_12368', open_order_id='dry_run_leverage_buy_12368',
strategy='DefaultStrategy', strategy='DefaultStrategy',
timeframe=5, timeframe=5,
sell_reason='sell_signal', exit_reason='sell_signal',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=300), open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=300),
close_date=datetime.now(tz=timezone.utc), close_date=datetime.now(tz=timezone.utc),
interest_rate=0.0005 interest_rate=0.0005

View File

@ -89,7 +89,7 @@ def mock_trade_usdt_2(fee):
open_order_id='dry_run_sell_12345', open_order_id='dry_run_sell_12345',
strategy='StrategyTestV2', strategy='StrategyTestV2',
timeframe=5, timeframe=5,
sell_reason='sell_signal', exit_reason='sell_signal',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2), close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2),
) )
@ -148,7 +148,7 @@ def mock_trade_usdt_3(fee):
is_open=False, is_open=False,
strategy='StrategyTestV2', strategy='StrategyTestV2',
timeframe=5, timeframe=5,
sell_reason='roi', exit_reason='roi',
open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20),
close_date=datetime.now(tz=timezone.utc), close_date=datetime.now(tz=timezone.utc),
) )

View File

@ -95,8 +95,8 @@ tc1 = BTContainer(data=[
[6, 5000, 5025, 4975, 4987, 6172, 0, 0], # should sell [6, 5000, 5025, 4975, 4987, 6172, 0, 0], # should sell
], ],
stop_loss=-0.99, roi={"0": float('inf')}, profit_perc=0.00, stop_loss=-0.99, roi={"0": float('inf')}, profit_perc=0.00,
trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=2), trades=[BTrade(exit_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=2),
BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=4, close_tick=6)] BTrade(exit_reason=ExitType.SELL_SIGNAL, open_tick=4, close_tick=6)]
) )
# 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss # 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss
@ -107,7 +107,7 @@ tc2 = BTContainer(data=[
[2, 5000, 5025, 4975, 4987, 6172, 0, 0], [2, 5000, 5025, 4975, 4987, 6172, 0, 0],
], ],
stop_loss=-0.01, roi={"0": float('inf')}, profit_perc=-0.01, stop_loss=-0.01, roi={"0": float('inf')}, profit_perc=-0.01,
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)] trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)]
) )
# 4) Entered, sl 3 %, candle drops 4%, recovers to 1 % = > Trade closed, 3 % loss # 4) Entered, sl 3 %, candle drops 4%, recovers to 1 % = > Trade closed, 3 % loss
@ -118,7 +118,7 @@ tc3 = BTContainer(data=[
[2, 5000, 5025, 4975, 4987, 6172, 0, 0], [2, 5000, 5025, 4975, 4987, 6172, 0, 0],
], ],
stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03, stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03,
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)] trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)]
) )
# 5) Stoploss and sell are hit. should sell on stoploss # 5) Stoploss and sell are hit. should sell on stoploss
@ -129,7 +129,7 @@ tc4 = BTContainer(data=[
[2, 5000, 5025, 4975, 4987, 6172, 0, 0], [2, 5000, 5025, 4975, 4987, 6172, 0, 0],
], ],
stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03, stop_loss=-0.03, roi={"0": float('inf')}, profit_perc=-0.03,
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)] trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)]
) )
TESTS = [ TESTS = [
@ -162,7 +162,7 @@ def test_edge_results(edge_conf, mocker, caplog, data) -> None:
for c, trade in enumerate(data.trades): for c, trade in enumerate(data.trades):
res = results.iloc[c] res = results.iloc[c]
assert res.exit_type == trade.sell_reason assert res.exit_type == trade.exit_reason
assert res.open_date == _get_frame_time_from_offset(trade.open_tick).replace(tzinfo=None) assert res.open_date == _get_frame_time_from_offset(trade.open_tick).replace(tzinfo=None)
assert res.close_date == _get_frame_time_from_offset(trade.close_tick).replace(tzinfo=None) assert res.close_date == _get_frame_time_from_offset(trade.close_tick).replace(tzinfo=None)

View File

@ -15,7 +15,7 @@ class BTrade(NamedTuple):
""" """
Minimalistic Trade result used for functional backtesting Minimalistic Trade result used for functional backtesting
""" """
sell_reason: ExitType exit_reason: ExitType
open_tick: int open_tick: int
close_tick: int close_tick: int
enter_tag: Optional[str] = None enter_tag: Optional[str] = None

View File

@ -44,7 +44,7 @@ def hyperopt_results():
'profit_abs': [-0.2, 0.4, -0.2, 0.6], 'profit_abs': [-0.2, 0.4, -0.2, 0.6],
'trade_duration': [10, 30, 10, 10], 'trade_duration': [10, 30, 10, 10],
'amount': [0.1, 0.1, 0.1, 0.1], 'amount': [0.1, 0.1, 0.1, 0.1],
'sell_reason': [ExitType.STOP_LOSS, ExitType.ROI, ExitType.STOP_LOSS, ExitType.ROI], 'exit_reason': [ExitType.STOP_LOSS, ExitType.ROI, ExitType.STOP_LOSS, ExitType.ROI],
'open_date': 'open_date':
[ [
datetime(2019, 1, 1, 9, 15, 0), datetime(2019, 1, 1, 9, 15, 0),

View File

@ -23,7 +23,7 @@ tc0 = BTContainer(data=[
[4, 5010, 5011, 4977, 4995, 6172, 0, 0], [4, 5010, 5011, 4977, 4995, 6172, 0, 0],
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]], [5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True, stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True,
trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)] trades=[BTrade(exit_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)]
) )
# Test 1: Stop-Loss Triggered 1% loss # Test 1: Stop-Loss Triggered 1% loss
@ -37,7 +37,7 @@ tc1 = BTContainer(data=[
[4, 4977, 4995, 4977, 4995, 6172, 0, 0], [4, 4977, 4995, 4977, 4995, 6172, 0, 0],
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]], [5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01,
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2)] trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2)]
) )
@ -52,7 +52,7 @@ tc2 = BTContainer(data=[
[4, 4962, 4987, 4937, 4950, 6172, 0, 0], [4, 4962, 4987, 4937, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.03, roi={"0": 1}, profit_perc=-0.03, stop_loss=-0.03, roi={"0": 1}, profit_perc=-0.03,
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=3)]
) )
@ -72,8 +72,8 @@ tc3 = BTContainer(data=[
[5, 4962, 4987, 4000, 4000, 6172, 0, 0], # exit with stoploss hit [5, 4962, 4987, 4000, 4000, 6172, 0, 0], # exit with stoploss hit
[6, 4950, 4975, 4950, 4950, 6172, 0, 0]], [6, 4950, 4975, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi={"0": 1}, profit_perc=-0.04, stop_loss=-0.02, roi={"0": 1}, profit_perc=-0.04,
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2), trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2),
BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=4, close_tick=5)] BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=4, close_tick=5)]
) )
# Test 4: Minus 3% / recovery +15% # Test 4: Minus 3% / recovery +15%
@ -89,7 +89,7 @@ tc4 = BTContainer(data=[
[4, 4962, 4987, 4937, 4950, 6172, 0, 0], [4, 4962, 4987, 4937, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi={"0": 0.06}, profit_perc=-0.02, stop_loss=-0.02, roi={"0": 0.06}, profit_perc=-0.02,
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2)] trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2)]
) )
# Test 5: Drops 0.5% Closes +20%, ROI triggers 3% Gain # Test 5: Drops 0.5% Closes +20%, ROI triggers 3% Gain
@ -103,7 +103,7 @@ tc5 = BTContainer(data=[
[4, 4962, 4987, 4962, 4972, 6172, 0, 0], [4, 4962, 4987, 4962, 4972, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 0.03}, profit_perc=0.03, stop_loss=-0.01, roi={"0": 0.03}, profit_perc=0.03,
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=3)]
) )
# Test 6: Drops 3% / Recovers 6% Positive / Closes 1% positve, Stop-Loss triggers 2% Loss # Test 6: Drops 3% / Recovers 6% Positive / Closes 1% positve, Stop-Loss triggers 2% Loss
@ -117,7 +117,7 @@ tc6 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0], [4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi={"0": 0.05}, profit_perc=-0.02, stop_loss=-0.02, roi={"0": 0.05}, profit_perc=-0.02,
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2)] trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=2)]
) )
# Test 7: 6% Positive / 1% Negative / Close 1% Positve, ROI Triggers 3% Gain # Test 7: 6% Positive / 1% Negative / Close 1% Positve, ROI Triggers 3% Gain
@ -131,7 +131,7 @@ tc7 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0], [4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi={"0": 0.03}, profit_perc=0.03, stop_loss=-0.02, roi={"0": 0.03}, profit_perc=0.03,
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=2)] trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=2)]
) )
@ -145,7 +145,7 @@ tc8 = BTContainer(data=[
[3, 4850, 5050, 4650, 4750, 6172, 0, 0], [3, 4850, 5050, 4650, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.055, trailing_stop=True, stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.055, trailing_stop=True,
trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
) )
@ -159,7 +159,7 @@ tc9 = BTContainer(data=[
[3, 5000, 5200, 4550, 4850, 6172, 0, 0], [3, 5000, 5200, 4550, 4850, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.064, trailing_stop=True, stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.064, trailing_stop=True,
trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
) )
# Test 10: trailing_stop should raise so candle 3 causes a stoploss # Test 10: trailing_stop should raise so candle 3 causes a stoploss
@ -175,7 +175,7 @@ tc10 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.1, trailing_stop=True, stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.1, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.10, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.10,
trailing_stop_positive=0.03, trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=4)] trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=4)]
) )
# Test 11: trailing_stop should raise so candle 3 causes a stoploss # Test 11: trailing_stop should raise so candle 3 causes a stoploss
@ -191,7 +191,7 @@ tc11 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True, stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03, trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
) )
# Test 12: trailing_stop should raise in candle 2 and cause a stoploss in the same candle # Test 12: trailing_stop should raise in candle 2 and cause a stoploss in the same candle
@ -207,7 +207,7 @@ tc12 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True, stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.019, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03, trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)] trades=[BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)]
) )
# Test 13: Buy and sell ROI on same candle # Test 13: Buy and sell ROI on same candle
@ -220,7 +220,7 @@ tc13 = BTContainer(data=[
[3, 4850, 5050, 4750, 4750, 6172, 0, 0], [3, 4850, 5050, 4750, 4750, 6172, 0, 0],
[4, 4750, 4950, 4750, 4750, 6172, 0, 0]], [4, 4750, 4950, 4750, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01, stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01,
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=1)] trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=1)]
) )
# Test 14 - Buy and Stoploss on same candle # Test 14 - Buy and Stoploss on same candle
@ -233,7 +233,7 @@ tc14 = BTContainer(data=[
[3, 4850, 5050, 4750, 4750, 6172, 0, 0], [3, 4850, 5050, 4750, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.05, roi={"0": 0.10}, profit_perc=-0.05, stop_loss=-0.05, roi={"0": 0.10}, profit_perc=-0.05,
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)] trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)]
) )
@ -247,8 +247,8 @@ tc15 = BTContainer(data=[
[3, 4850, 5050, 4750, 4750, 6172, 0, 0], [3, 4850, 5050, 4750, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.05, roi={"0": 0.01}, profit_perc=-0.04, stop_loss=-0.05, roi={"0": 0.01}, profit_perc=-0.04,
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=1), trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=1),
BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=2, close_tick=2)] BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=2, close_tick=2)]
) )
# Test 16: Buy, hold for 65 min, then forcesell using roi=-1 # Test 16: Buy, hold for 65 min, then forcesell using roi=-1
@ -263,7 +263,7 @@ tc16 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0], [4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "65": -1}, profit_perc=-0.012, stop_loss=-0.10, roi={"0": 0.10, "65": -1}, profit_perc=-0.012,
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=3)]
) )
# Test 17: Buy, hold for 120 mins, then forcesell using roi=-1 # Test 17: Buy, hold for 120 mins, then forcesell using roi=-1
@ -279,7 +279,7 @@ tc17 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0], [4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "120": -1}, profit_perc=-0.004, stop_loss=-0.10, roi={"0": 0.10, "120": -1}, profit_perc=-0.004,
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=3)]
) )
@ -295,7 +295,7 @@ tc18 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0], [4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.04, stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.04,
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=3)]
) )
# Test 19: Buy, hold for 119 mins, then drop ROI to 1%, causing a sell in candle 3. # Test 19: Buy, hold for 119 mins, then drop ROI to 1%, causing a sell in candle 3.
@ -310,7 +310,7 @@ tc19 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0], [4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4550, 4975, 4550, 4950, 6172, 0, 0]], [5, 4550, 4975, 4550, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.01, stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.01,
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=3)]
) )
# Test 20: Buy, hold for 119 mins, then drop ROI to 1%, causing a sell in candle 3. # Test 20: Buy, hold for 119 mins, then drop ROI to 1%, causing a sell in candle 3.
@ -325,7 +325,7 @@ tc20 = BTContainer(data=[
[4, 4962, 4987, 4950, 4950, 6172, 0, 0], [4, 4962, 4987, 4950, 4950, 6172, 0, 0],
[5, 4925, 4975, 4925, 4950, 6172, 0, 0]], [5, 4925, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10, "119": 0.01}, profit_perc=0.01, stop_loss=-0.10, roi={"0": 0.10, "119": 0.01}, profit_perc=0.01,
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=3)]
) )
# Test 21: trailing_stop ROI collision. # Test 21: trailing_stop ROI collision.
@ -342,7 +342,7 @@ tc21 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True, stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03, trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=2)] trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=2)]
) )
# Test 22: trailing_stop Raises in candle 2 - but ROI applies at the same time. # Test 22: trailing_stop Raises in candle 2 - but ROI applies at the same time.
@ -358,7 +358,7 @@ tc22 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True, stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03, trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=2)] trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=2)]
) )
@ -375,7 +375,7 @@ tc23 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True, stop_loss=-0.10, roi={"0": 0.04}, profit_perc=0.04, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03, trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=2, is_short=True)] trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=2, is_short=True)]
) )
# Test 24: trailing_stop Raises in candle 2 (does not trigger) # Test 24: trailing_stop Raises in candle 2 (does not trigger)
@ -394,7 +394,7 @@ tc24 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.1, "119": 0.03}, profit_perc=0.03, trailing_stop=True, stop_loss=-0.10, roi={"0": 0.1, "119": 0.03}, profit_perc=0.03, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03, trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=3)]
) )
# Test 25: Sell with signal sell in candle 3 (stoploss also triggers on this candle) # Test 25: Sell with signal sell in candle 3 (stoploss also triggers on this candle)
@ -409,7 +409,7 @@ tc25 = BTContainer(data=[
[4, 5010, 5010, 4977, 4995, 6172, 0, 0], [4, 5010, 5010, 4977, 4995, 6172, 0, 0],
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]], [5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, use_sell_signal=True, stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, use_sell_signal=True,
trades=[BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=3)]
) )
# Test 26: Sell with signal sell in candle 3 (stoploss also triggers on this candle) # Test 26: Sell with signal sell in candle 3 (stoploss also triggers on this candle)
@ -424,7 +424,7 @@ tc26 = BTContainer(data=[
[4, 5010, 5010, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on [4, 5010, 5010, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]], [5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True, stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True,
trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)] trades=[BTrade(exit_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)]
) )
# Test 27: (copy of test26 with leverage) # Test 27: (copy of test26 with leverage)
@ -441,7 +441,7 @@ tc27 = BTContainer(data=[
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]], [5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.05, roi={"0": 1}, profit_perc=0.002 * 5.0, use_sell_signal=True, stop_loss=-0.05, roi={"0": 1}, profit_perc=0.002 * 5.0, use_sell_signal=True,
leverage=5.0, leverage=5.0,
trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)] trades=[BTrade(exit_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)]
) )
# Test 28: (copy of test26 with leverage and as short) # Test 28: (copy of test26 with leverage and as short)
@ -458,7 +458,7 @@ tc28 = BTContainer(data=[
[5, 4995, 4995, 4950, 4950, 6172, 0, 0, 0, 0]], [5, 4995, 4995, 4950, 4950, 6172, 0, 0, 0, 0]],
stop_loss=-0.05, roi={"0": 1}, profit_perc=0.002 * 5.0, use_sell_signal=True, stop_loss=-0.05, roi={"0": 1}, profit_perc=0.002 * 5.0, use_sell_signal=True,
leverage=5.0, leverage=5.0,
trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4, is_short=True)] trades=[BTrade(exit_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4, is_short=True)]
) )
# Test 29: Sell with signal sell in candle 3 (ROI at signal candle) # Test 29: Sell with signal sell in candle 3 (ROI at signal candle)
# Stoploss at 10% (irrelevant), ROI at 5% (will trigger) # Stoploss at 10% (irrelevant), ROI at 5% (will trigger)
@ -472,7 +472,7 @@ tc29 = BTContainer(data=[
[4, 5010, 5010, 4855, 4995, 6172, 0, 0], [4, 5010, 5010, 4855, 4995, 6172, 0, 0],
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]], [5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_sell_signal=True, stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_sell_signal=True,
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=3)]
) )
# Test 30: Sell with signal sell in candle 3 (ROI at signal candle) # Test 30: Sell with signal sell in candle 3 (ROI at signal candle)
@ -486,7 +486,7 @@ tc30 = BTContainer(data=[
[4, 5010, 5251, 4855, 4995, 6172, 0, 0], # Triggers ROI, sell-signal acted on [4, 5010, 5251, 4855, 4995, 6172, 0, 0], # Triggers ROI, sell-signal acted on
[5, 4995, 4995, 4950, 4950, 6172, 0, 0]], [5, 4995, 4995, 4950, 4950, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.002, use_sell_signal=True, stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.002, use_sell_signal=True,
trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)] trades=[BTrade(exit_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=4)]
) )
# Test 31: trailing_stop should raise so candle 3 causes a stoploss # Test 31: trailing_stop should raise so candle 3 causes a stoploss
@ -503,7 +503,7 @@ tc31 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.03, trailing_stop=True, stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.03, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03, trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
) )
# Test 32: (Short of test 31) trailing_stop should raise so candle 3 causes a stoploss # Test 32: (Short of test 31) trailing_stop should raise so candle 3 causes a stoploss
@ -521,7 +521,7 @@ tc32 = BTContainer(data=[
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03, trailing_stop_positive=0.03,
trades=[ trades=[
BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3, is_short=True) BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3, is_short=True)
] ]
) )
@ -537,7 +537,7 @@ tc33 = BTContainer(data=[
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.02, trailing_stop=True, stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.02, trailing_stop=True,
trailing_stop_positive=0.03, trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)] trades=[BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)]
) )
# Test 34: trailing_stop should be triggered immediately on trade open candle. # Test 34: trailing_stop should be triggered immediately on trade open candle.
@ -551,7 +551,7 @@ tc34 = BTContainer(data=[
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True, stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True,
trailing_stop_positive=0.01, trailing_stop_positive=0.01,
trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)] trades=[BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
) )
# Test 35: trailing_stop should be triggered immediately on trade open candle. # Test 35: trailing_stop should be triggered immediately on trade open candle.
@ -566,7 +566,7 @@ tc35 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.01, trailing_stop=True, stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.01, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
trailing_stop_positive=0.01, trailing_stop_positive=0.01,
trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)] trades=[BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
) )
# Test 36: trailing_stop should be triggered immediately on trade open candle. # Test 36: trailing_stop should be triggered immediately on trade open candle.
@ -581,7 +581,7 @@ tc36 = BTContainer(data=[
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True, stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
trailing_stop_positive=0.01, use_custom_stoploss=True, trailing_stop_positive=0.01, use_custom_stoploss=True,
trades=[BTrade(sell_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)] trades=[BTrade(exit_reason=ExitType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
) )
# Test 37: trailing_stop should be triggered immediately on trade open candle. # Test 37: trailing_stop should be triggered immediately on trade open candle.
@ -597,7 +597,7 @@ tc37 = BTContainer(data=[
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
trailing_stop_positive=0.01, use_custom_stoploss=True, trailing_stop_positive=0.01, use_custom_stoploss=True,
trades=[BTrade( trades=[BTrade(
sell_reason=ExitType.TRAILING_STOP_LOSS, exit_reason=ExitType.TRAILING_STOP_LOSS,
open_tick=1, open_tick=1,
close_tick=1, close_tick=1,
enter_tag='buy_signal_01' enter_tag='buy_signal_01'
@ -617,7 +617,7 @@ tc38 = BTContainer(data=[
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02, trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.02,
trailing_stop_positive=0.01, use_custom_stoploss=True, trailing_stop_positive=0.01, use_custom_stoploss=True,
trades=[BTrade( trades=[BTrade(
sell_reason=ExitType.TRAILING_STOP_LOSS, exit_reason=ExitType.TRAILING_STOP_LOSS,
open_tick=1, open_tick=1,
close_tick=1, close_tick=1,
enter_tag='short_signal_01', enter_tag='short_signal_01',
@ -647,7 +647,7 @@ tc40 = BTContainer(data=[
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01,
custom_entry_price=7200, trades=[ custom_entry_price=7200, trades=[
BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1) BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)
]) ])
# Test 41: Custom-entry-price above all candles should have rate adjusted to "entry candle high" # Test 41: Custom-entry-price above all candles should have rate adjusted to "entry candle high"
@ -661,7 +661,7 @@ tc41 = BTContainer(data=[
stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01, stop_loss=-0.01, roi={"0": 0.10}, profit_perc=-0.01,
custom_entry_price=4000, custom_entry_price=4000,
trades=[ trades=[
BTrade(sell_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1, is_short=True) BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1, is_short=True)
] ]
) )
@ -678,7 +678,7 @@ tc42 = BTContainer(data=[
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01, stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01,
custom_entry_price=4952, custom_entry_price=4952,
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=2)] trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=2)]
) )
# Test 43: Custom-entry-price around candle low # Test 43: Custom-entry-price around candle low
@ -693,7 +693,7 @@ tc43 = BTContainer(data=[
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01, stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01,
custom_entry_price=4952, custom_entry_price=4952,
trades=[BTrade(sell_reason=ExitType.ROI, open_tick=1, close_tick=1)] trades=[BTrade(exit_reason=ExitType.ROI, open_tick=1, close_tick=1)]
) )
# Test 44: Custom exit price below all candles # Test 44: Custom exit price below all candles
@ -708,7 +708,7 @@ tc44 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01, stop_loss=-0.10, roi={"0": 0.10}, profit_perc=-0.01,
use_sell_signal=True, use_sell_signal=True,
custom_exit_price=4552, custom_exit_price=4552,
trades=[BTrade(sell_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=ExitType.SELL_SIGNAL, open_tick=1, close_tick=3)]
) )
# Test 45: Custom exit price above all candles # Test 45: Custom exit price above all candles
@ -723,7 +723,7 @@ tc45 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0, stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0,
use_sell_signal=True, use_sell_signal=True,
custom_exit_price=6052, custom_exit_price=6052,
trades=[BTrade(sell_reason=ExitType.FORCE_SELL, open_tick=1, close_tick=4)] trades=[BTrade(exit_reason=ExitType.FORCE_SELL, open_tick=1, close_tick=4)]
) )
# Test 46: (Short of tc45) Custom short exit price above below candles # Test 46: (Short of tc45) Custom short exit price above below candles
@ -738,7 +738,7 @@ tc46 = BTContainer(data=[
stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0, stop_loss=-0.10, roi={"0": 0.10}, profit_perc=0.0,
use_sell_signal=True, use_sell_signal=True,
custom_exit_price=4700, custom_exit_price=4700,
trades=[BTrade(sell_reason=ExitType.FORCE_SELL, open_tick=1, close_tick=4, is_short=True)] trades=[BTrade(exit_reason=ExitType.FORCE_SELL, open_tick=1, close_tick=4, is_short=True)]
) )
# Test 47: Colliding long and short signal # Test 47: Colliding long and short signal
@ -861,7 +861,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
for c, trade in enumerate(data.trades): for c, trade in enumerate(data.trades):
res: BTrade = results.iloc[c] res: BTrade = results.iloc[c]
assert res.sell_reason == trade.sell_reason.value assert res.exit_reason == trade.exit_reason.value
assert res.enter_tag == trade.enter_tag assert res.enter_tag == trade.enter_tag
assert res.open_date == _get_frame_time_from_offset(trade.open_tick) assert res.open_date == _get_frame_time_from_offset(trade.open_tick)
assert res.close_date == _get_frame_time_from_offset(trade.close_tick) assert res.close_date == _get_frame_time_from_offset(trade.close_tick)

View File

@ -713,7 +713,7 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
# No data available. # No data available.
res = backtesting._get_sell_trade_entry(trade, row_sell) res = backtesting._get_sell_trade_entry(trade, row_sell)
assert res is not None assert res is not None
assert res.sell_reason == ExitType.ROI.value assert res.exit_reason == ExitType.ROI.value
assert res.close_date_utc == datetime(2020, 1, 1, 5, 0, tzinfo=timezone.utc) assert res.close_date_utc == datetime(2020, 1, 1, 5, 0, tzinfo=timezone.utc)
# Enter new trade # Enter new trade
@ -732,7 +732,7 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None:
res = backtesting._get_sell_trade_entry(trade, row_sell) res = backtesting._get_sell_trade_entry(trade, row_sell)
assert res is not None assert res is not None
assert res.sell_reason == ExitType.ROI.value assert res.exit_reason == ExitType.ROI.value
# Sell at minute 3 (not available above!) # Sell at minute 3 (not available above!)
assert res.close_date_utc == datetime(2020, 1, 1, 5, 3, tzinfo=timezone.utc) assert res.close_date_utc == datetime(2020, 1, 1, 5, 3, tzinfo=timezone.utc)
sell_order = res.select_order('sell', True) sell_order = res.select_order('sell', True)
@ -781,7 +781,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None:
'trade_duration': [235, 40], 'trade_duration': [235, 40],
'profit_ratio': [0.0, 0.0], 'profit_ratio': [0.0, 0.0],
'profit_abs': [0.0, 0.0], 'profit_abs': [0.0, 0.0],
'sell_reason': [ExitType.ROI.value, ExitType.ROI.value], 'exit_reason': [ExitType.ROI.value, ExitType.ROI.value],
'initial_stop_loss_abs': [0.0940005, 0.09272236], 'initial_stop_loss_abs': [0.0940005, 0.09272236],
'initial_stop_loss_ratio': [-0.1, -0.1], 'initial_stop_loss_ratio': [-0.1, -0.1],
'stop_loss_abs': [0.0940005, 0.09272236], 'stop_loss_abs': [0.0940005, 0.09272236],
@ -1178,7 +1178,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
text_table_bt_results=text_table_mock, text_table_bt_results=text_table_mock,
text_table_strategy=strattable_mock, text_table_strategy=strattable_mock,
generate_pair_metrics=MagicMock(), generate_pair_metrics=MagicMock(),
generate_sell_reason_stats=sell_reason_mock, generate_exit_reason_stats=sell_reason_mock,
generate_strategy_comparison=strat_summary, generate_strategy_comparison=strat_summary,
generate_daily_stats=MagicMock(), generate_daily_stats=MagicMock(),
) )
@ -1249,7 +1249,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'close_rate': [0.104969, 0.103541], 'close_rate': [0.104969, 0.103541],
"is_short": [False, False], "is_short": [False, False],
'sell_reason': [ExitType.ROI, ExitType.ROI] 'exit_reason': [ExitType.ROI, ExitType.ROI]
}) })
result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'], result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
'profit_ratio': [0.03, 0.01, 0.1], 'profit_ratio': [0.03, 0.01, 0.1],
@ -1267,7 +1267,7 @@ def test_backtest_start_multi_strat_nomock(default_conf, mocker, caplog, testdat
'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], "is_short": [False, False, False],
'sell_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS] 'exit_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS]
}) })
backtestmock = MagicMock(side_effect=[ backtestmock = MagicMock(side_effect=[
{ {
@ -1367,7 +1367,7 @@ def test_backtest_start_nomock_futures(default_conf_usdt, mocker,
'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],
'sell_reason': [ExitType.ROI, ExitType.ROI] 'exit_reason': [ExitType.ROI, ExitType.ROI]
}) })
result2 = pd.DataFrame({'pair': ['XRP/USDT', 'XRP/USDT', 'XRP/USDT'], result2 = pd.DataFrame({'pair': ['XRP/USDT', 'XRP/USDT', 'XRP/USDT'],
'profit_ratio': [0.03, 0.01, 0.1], 'profit_ratio': [0.03, 0.01, 0.1],
@ -1385,7 +1385,7 @@ def test_backtest_start_nomock_futures(default_conf_usdt, mocker,
'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],
'sell_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS] 'exit_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS]
}) })
backtestmock = MagicMock(side_effect=[ backtestmock = MagicMock(side_effect=[
{ {
@ -1470,7 +1470,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
'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],
'sell_reason': [ExitType.ROI, ExitType.ROI] 'exit_reason': [ExitType.ROI, ExitType.ROI]
}) })
result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'], result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
'profit_ratio': [0.03, 0.01, 0.1], 'profit_ratio': [0.03, 0.01, 0.1],
@ -1488,7 +1488,7 @@ def test_backtest_start_multi_strat_nomock_detail(default_conf, mocker,
'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],
'sell_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS] 'exit_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS]
}) })
backtestmock = MagicMock(side_effect=[ backtestmock = MagicMock(side_effect=[
{ {

View File

@ -60,7 +60,7 @@ def test_backtest_position_adjustment(default_conf, fee, mocker, testdatadir) ->
'trade_duration': [200, 40], 'trade_duration': [200, 40],
'profit_ratio': [0.0, 0.0], 'profit_ratio': [0.0, 0.0],
'profit_abs': [0.0, 0.0], 'profit_abs': [0.0, 0.0],
'sell_reason': [ExitType.ROI.value, ExitType.ROI.value], 'exit_reason': [ExitType.ROI.value, ExitType.ROI.value],
'initial_stop_loss_abs': [0.0940005, 0.09272236], 'initial_stop_loss_abs': [0.0940005, 0.09272236],
'initial_stop_loss_ratio': [-0.1, -0.1], 'initial_stop_loss_ratio': [-0.1, -0.1],
'stop_loss_abs': [0.0940005, 0.09272236], 'stop_loss_abs': [0.0940005, 0.09272236],

View File

@ -357,7 +357,7 @@ def test_hyperopt_format_results(hyperopt):
"is_open": [False, False, False, True], "is_open": [False, False, False, True],
"is_short": [False, False, False, False], "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": [ExitType.ROI, ExitType.STOP_LOSS, "exit_reason": [ExitType.ROI, ExitType.STOP_LOSS,
ExitType.ROI, ExitType.FORCE_SELL] ExitType.ROI, ExitType.FORCE_SELL]
}), }),
'config': hyperopt.config, 'config': hyperopt.config,
@ -428,7 +428,7 @@ def test_generate_optimizer(mocker, hyperopt_conf) -> None:
"is_open": [False, False, False, True], "is_open": [False, False, False, True],
"is_short": [False, False, False, False], "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": [ExitType.ROI, ExitType.STOP_LOSS, "exit_reason": [ExitType.ROI, ExitType.STOP_LOSS,
ExitType.ROI, ExitType.FORCE_SELL] ExitType.ROI, ExitType.FORCE_SELL]
}), }),
'config': hyperopt_conf, 'config': hyperopt_conf,

View File

@ -15,9 +15,8 @@ from freqtrade.edge import PairInfo
from freqtrade.enums import ExitType from freqtrade.enums import ExitType
from freqtrade.optimize.optimize_reports import (_get_resample_from_period, generate_backtest_stats, from freqtrade.optimize.optimize_reports import (_get_resample_from_period, generate_backtest_stats,
generate_daily_stats, generate_edge_table, generate_daily_stats, generate_edge_table,
generate_pair_metrics, generate_exit_reason_stats, generate_pair_metrics,
generate_periodic_breakdown_stats, generate_periodic_breakdown_stats,
generate_sell_reason_stats,
generate_strategy_comparison, generate_strategy_comparison,
generate_trading_stats, show_sorted_pairlist, generate_trading_stats, show_sorted_pairlist,
store_backtest_stats, text_table_bt_results, store_backtest_stats, text_table_bt_results,
@ -77,7 +76,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
"is_open": [False, False, False, True], "is_open": [False, False, False, True],
"is_short": [False, False, False, False], "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": [ExitType.ROI, ExitType.STOP_LOSS, "exit_reason": [ExitType.ROI, ExitType.STOP_LOSS,
ExitType.ROI, ExitType.FORCE_SELL] ExitType.ROI, ExitType.FORCE_SELL]
}), }),
'config': default_conf, 'config': default_conf,
@ -129,7 +128,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
"is_open": [False, False, False, True], "is_open": [False, False, False, True],
"is_short": [False, False, False, False], "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": [ExitType.ROI, ExitType.ROI, "exit_reason": [ExitType.ROI, ExitType.ROI,
ExitType.STOP_LOSS, ExitType.FORCE_SELL] ExitType.STOP_LOSS, ExitType.FORCE_SELL]
}), }),
'config': default_conf, 'config': default_conf,
@ -265,7 +264,7 @@ def test_generate_trading_stats(testdatadir):
assert res['losses'] == 0 assert res['losses'] == 0
def test_text_table_sell_reason(): def test_text_table_exit_reason():
results = pd.DataFrame( results = pd.DataFrame(
{ {
@ -276,7 +275,7 @@ def test_text_table_sell_reason():
'wins': [2, 0, 0], 'wins': [2, 0, 0],
'draws': [0, 0, 0], 'draws': [0, 0, 0],
'losses': [0, 0, 1], 'losses': [0, 0, 1],
'sell_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS] 'exit_reason': [ExitType.ROI, ExitType.ROI, ExitType.STOP_LOSS]
} }
) )
@ -291,9 +290,9 @@ def test_text_table_sell_reason():
' -0.2 | -5 |' ' -0.2 | -5 |'
) )
sell_reason_stats = generate_sell_reason_stats(max_open_trades=2, exit_reason_stats = generate_exit_reason_stats(max_open_trades=2,
results=results) results=results)
assert text_table_exit_reason(sell_reason_stats=sell_reason_stats, assert text_table_exit_reason(exit_reason_stats=exit_reason_stats,
stake_currency='BTC') == result_str stake_currency='BTC') == result_str
@ -308,23 +307,23 @@ def test_generate_sell_reason_stats():
'wins': [2, 0, 0], 'wins': [2, 0, 0],
'draws': [0, 0, 0], 'draws': [0, 0, 0],
'losses': [0, 0, 1], 'losses': [0, 0, 1],
'sell_reason': [ExitType.ROI.value, ExitType.ROI.value, ExitType.STOP_LOSS.value] 'exit_reason': [ExitType.ROI.value, ExitType.ROI.value, ExitType.STOP_LOSS.value]
} }
) )
sell_reason_stats = generate_sell_reason_stats(max_open_trades=2, exit_reason_stats = generate_exit_reason_stats(max_open_trades=2,
results=results) results=results)
roi_result = sell_reason_stats[0] roi_result = exit_reason_stats[0]
assert roi_result['sell_reason'] == 'roi' assert roi_result['exit_reason'] == 'roi'
assert roi_result['trades'] == 2 assert roi_result['trades'] == 2
assert pytest.approx(roi_result['profit_mean']) == 0.15 assert pytest.approx(roi_result['profit_mean']) == 0.15
assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2) assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2)
assert pytest.approx(roi_result['profit_mean']) == 0.15 assert pytest.approx(roi_result['profit_mean']) == 0.15
assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2) assert roi_result['profit_mean_pct'] == round(roi_result['profit_mean'] * 100, 2)
stop_result = sell_reason_stats[1] stop_result = exit_reason_stats[1]
assert stop_result['sell_reason'] == 'stop_loss' assert stop_result['exit_reason'] == 'stop_loss'
assert stop_result['trades'] == 1 assert stop_result['trades'] == 1
assert pytest.approx(stop_result['profit_mean']) == -0.1 assert pytest.approx(stop_result['profit_mean']) == -0.1
assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2) assert stop_result['profit_mean_pct'] == round(stop_result['profit_mean'] * 100, 2)

View File

@ -32,7 +32,7 @@ def generate_mock_trade(pair: str, fee: float, is_open: bool,
trade.recalc_open_trade_value() trade.recalc_open_trade_value()
if not is_open: if not is_open:
trade.close(open_rate * profit_rate) trade.close(open_rate * profit_rate)
trade.sell_reason = sell_reason trade.exit_reason = sell_reason
return trade return trade

View File

@ -66,6 +66,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'open_trade_value': 0.0010025, 'open_trade_value': 0.0010025,
'close_rate_requested': ANY, 'close_rate_requested': ANY,
'sell_reason': ANY, 'sell_reason': ANY,
'exit_reason': ANY,
'sell_order_status': ANY, 'sell_order_status': ANY,
'min_rate': ANY, 'min_rate': ANY,
'max_rate': ANY, 'max_rate': ANY,
@ -148,6 +149,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'open_trade_value': ANY, 'open_trade_value': ANY,
'close_rate_requested': ANY, 'close_rate_requested': ANY,
'sell_reason': ANY, 'sell_reason': ANY,
'exit_reason': ANY,
'sell_order_status': ANY, 'sell_order_status': ANY,
'min_rate': ANY, 'min_rate': ANY,
'max_rate': ANY, 'max_rate': ANY,
@ -1008,7 +1010,7 @@ def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee):
assert prec_satoshi(res[0]['profit_pct'], 0.5) assert prec_satoshi(res[0]['profit_pct'], 0.5)
def test_sell_reason_performance_handle(default_conf, ticker, limit_buy_order, fee, def test_exit_reason_performance_handle(default_conf, ticker, limit_buy_order, fee,
limit_sell_order, mocker) -> None: limit_sell_order, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
@ -1037,23 +1039,23 @@ def test_sell_reason_performance_handle(default_conf, ticker, limit_buy_order, f
trade.close_date = datetime.utcnow() trade.close_date = datetime.utcnow()
trade.is_open = False trade.is_open = False
res = rpc._rpc_sell_reason_performance(None) res = rpc._rpc_exit_reason_performance(None)
assert len(res) == 1 assert len(res) == 1
assert res[0]['sell_reason'] == 'Other' assert res[0]['exit_reason'] == 'Other'
assert res[0]['count'] == 1 assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2) assert prec_satoshi(res[0]['profit_pct'], 6.2)
trade.sell_reason = "TEST1" trade.exit_reason = "TEST1"
res = rpc._rpc_sell_reason_performance(None) res = rpc._rpc_exit_reason_performance(None)
assert len(res) == 1 assert len(res) == 1
assert res[0]['sell_reason'] == 'TEST1' assert res[0]['exit_reason'] == 'TEST1'
assert res[0]['count'] == 1 assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2) assert prec_satoshi(res[0]['profit_pct'], 6.2)
def test_sell_reason_performance_handle_2(mocker, default_conf, markets, fee): def test_exit_reason_performance_handle_2(mocker, default_conf, markets, fee):
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -1064,21 +1066,21 @@ def test_sell_reason_performance_handle_2(mocker, default_conf, markets, fee):
create_mock_trades(fee) create_mock_trades(fee)
rpc = RPC(freqtradebot) rpc = RPC(freqtradebot)
res = rpc._rpc_sell_reason_performance(None) res = rpc._rpc_exit_reason_performance(None)
assert len(res) == 2 assert len(res) == 2
assert res[0]['sell_reason'] == 'sell_signal' assert res[0]['exit_reason'] == 'sell_signal'
assert res[0]['count'] == 1 assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 0.5) assert prec_satoshi(res[0]['profit_pct'], 0.5)
assert res[1]['sell_reason'] == 'roi' assert res[1]['exit_reason'] == 'roi'
assert res[1]['count'] == 1 assert res[1]['count'] == 1
assert prec_satoshi(res[1]['profit_pct'], 1.0) assert prec_satoshi(res[1]['profit_pct'], 1.0)
# Test for a specific pair # Test for a specific pair
res = rpc._rpc_sell_reason_performance('ETC/BTC') res = rpc._rpc_exit_reason_performance('ETC/BTC')
assert len(res) == 1 assert len(res) == 1
assert res[0]['count'] == 1 assert res[0]['count'] == 1
assert res[0]['sell_reason'] == 'sell_signal' assert res[0]['exit_reason'] == 'sell_signal'
assert prec_satoshi(res[0]['profit_pct'], 0.5) assert prec_satoshi(res[0]['profit_pct'], 0.5)
@ -1119,7 +1121,7 @@ def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee,
assert prec_satoshi(res[0]['profit_pct'], 6.2) assert prec_satoshi(res[0]['profit_pct'], 6.2)
trade.enter_tag = "TESTBUY" trade.enter_tag = "TESTBUY"
trade.sell_reason = "TESTSELL" trade.exit_reason = "TESTSELL"
res = rpc._rpc_mix_tag_performance(None) res = rpc._rpc_mix_tag_performance(None)
assert len(res) == 1 assert len(res) == 1

View File

@ -822,14 +822,14 @@ def test_api_stats(botclient, mocker, ticker, fee, markets, is_short):
rc = client_get(client, f"{BASE_URI}/stats") rc = client_get(client, f"{BASE_URI}/stats")
assert_response(rc, 200) assert_response(rc, 200)
assert 'durations' in rc.json() assert 'durations' in rc.json()
assert 'sell_reasons' in rc.json() assert 'exit_reasons' in rc.json()
create_mock_trades(fee, is_short=is_short) create_mock_trades(fee, is_short=is_short)
rc = client_get(client, f"{BASE_URI}/stats") rc = client_get(client, f"{BASE_URI}/stats")
assert_response(rc, 200) assert_response(rc, 200)
assert 'durations' in rc.json() assert 'durations' in rc.json()
assert 'sell_reasons' in rc.json() assert 'exit_reasons' in rc.json()
assert 'wins' in rc.json()['durations'] assert 'wins' in rc.json()['durations']
assert 'losses' in rc.json()['durations'] assert 'losses' in rc.json()['durations']
@ -962,6 +962,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
'open_rate_requested': ANY, 'open_rate_requested': ANY,
'open_trade_value': open_trade_value, 'open_trade_value': open_trade_value,
'sell_reason': None, 'sell_reason': None,
'exit_reason': None,
'sell_order_status': None, 'sell_order_status': None,
'strategy': CURRENT_TEST_STRATEGY, 'strategy': CURRENT_TEST_STRATEGY,
'buy_tag': None, 'buy_tag': None,
@ -1162,6 +1163,7 @@ def test_api_forceentry(botclient, mocker, fee, endpoint):
'open_rate_requested': None, 'open_rate_requested': None,
'open_trade_value': 0.24605460, 'open_trade_value': 0.24605460,
'sell_reason': None, 'sell_reason': None,
'exit_reason': None,
'sell_order_status': None, 'sell_order_status': None,
'strategy': CURRENT_TEST_STRATEGY, 'strategy': CURRENT_TEST_STRATEGY,
'buy_tag': None, 'buy_tag': None,

View File

@ -97,7 +97,7 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
"['balance'], ['start'], ['stop'], " "['balance'], ['start'], ['stop'], "
"['forcesell', 'forceexit'], ['forcebuy', 'forcelong'], ['forceshort'], " "['forcesell', 'forceexit'], ['forcebuy', 'forcelong'], ['forceshort'], "
"['trades'], ['delete'], ['performance'], " "['trades'], ['delete'], ['performance'], "
"['buys', 'entries'], ['sells'], ['mix_tags'], " "['buys', 'entries'], ['sells', 'exits'], ['mix_tags'], "
"['stats'], ['daily'], ['weekly'], ['monthly'], " "['stats'], ['daily'], ['weekly'], ['monthly'], "
"['count'], ['locks'], ['unlock', 'delete_locks'], " "['count'], ['locks'], ['unlock', 'delete_locks'], "
"['reload_config', 'reload_conf'], ['show_config', 'show_conf'], " "['reload_config', 'reload_conf'], ['show_config', 'show_conf'], "
@ -837,7 +837,7 @@ def test_telegram_stats(default_conf, update, ticker, ticker_sell_up, fee,
telegram._stats(update=update, context=MagicMock()) telegram._stats(update=update, context=MagicMock())
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert 'Sell Reason' in msg_mock.call_args_list[-1][0][0] assert 'Exit Reason' in msg_mock.call_args_list[-1][0][0]
assert 'ROI' in msg_mock.call_args_list[-1][0][0] assert 'ROI' in msg_mock.call_args_list[-1][0][0]
assert 'Avg. Duration' in msg_mock.call_args_list[-1][0][0] assert 'Avg. Duration' in msg_mock.call_args_list[-1][0][0]
msg_mock.reset_mock() msg_mock.reset_mock()
@ -1060,6 +1060,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
'buy_tag': ANY, 'buy_tag': ANY,
'enter_tag': ANY, 'enter_tag': ANY,
'sell_reason': ExitType.FORCE_SELL.value, 'sell_reason': ExitType.FORCE_SELL.value,
'exit_reason': ExitType.FORCE_SELL.value,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
@ -1128,6 +1129,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
'buy_tag': ANY, 'buy_tag': ANY,
'enter_tag': ANY, 'enter_tag': ANY,
'sell_reason': ExitType.FORCE_SELL.value, 'sell_reason': ExitType.FORCE_SELL.value,
'exit_reason': ExitType.FORCE_SELL.value,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
@ -1186,6 +1188,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
'buy_tag': ANY, 'buy_tag': ANY,
'enter_tag': ANY, 'enter_tag': ANY,
'sell_reason': ExitType.FORCE_SELL.value, 'sell_reason': ExitType.FORCE_SELL.value,
'exit_reason': ExitType.FORCE_SELL.value,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
@ -1334,8 +1337,8 @@ def test_telegram_performance_handle(default_conf, update, ticker, fee,
assert '<code>ETH/BTC\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0] assert '<code>ETH/BTC\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0]
def test_telegram_buy_tag_performance_handle(default_conf, update, ticker, fee, def test_telegram_entry_tag_performance_handle(
limit_buy_order, limit_sell_order, mocker) -> None: default_conf, update, ticker, fee, limit_buy_order, limit_sell_order, mocker) -> None:
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
fetch_ticker=ticker, fetch_ticker=ticker,
@ -1363,7 +1366,7 @@ def test_telegram_buy_tag_performance_handle(default_conf, update, ticker, fee,
context = MagicMock() context = MagicMock()
telegram._enter_tag_performance(update=update, context=context) telegram._enter_tag_performance(update=update, context=context)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert 'Buy Tag Performance' in msg_mock.call_args_list[0][0][0] assert 'Entry Tag Performance' in msg_mock.call_args_list[0][0][0]
assert '<code>TESTBUY\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0] assert '<code>TESTBUY\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0]
context.args = [trade.pair] context.args = [trade.pair]
@ -1379,7 +1382,7 @@ def test_telegram_buy_tag_performance_handle(default_conf, update, ticker, fee,
assert "Error" in msg_mock.call_args_list[0][0][0] assert "Error" in msg_mock.call_args_list[0][0][0]
def test_telegram_sell_reason_performance_handle(default_conf, update, ticker, fee, def test_telegram_exit_reason_performance_handle(default_conf, update, ticker, fee,
limit_buy_order, limit_sell_order, mocker) -> None: limit_buy_order, limit_sell_order, mocker) -> None:
mocker.patch.multiple( mocker.patch.multiple(
'freqtrade.exchange.Exchange', 'freqtrade.exchange.Exchange',
@ -1393,7 +1396,7 @@ def test_telegram_sell_reason_performance_handle(default_conf, update, ticker, f
freqtradebot.enter_positions() freqtradebot.enter_positions()
trade = Trade.query.first() trade = Trade.query.first()
assert trade assert trade
trade.sell_reason = 'TESTSELL' trade.exit_reason = 'TESTSELL'
# Simulate fulfilled LIMIT_BUY order for trade # Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
trade.update_trade(oobj) trade.update_trade(oobj)
@ -1405,19 +1408,19 @@ def test_telegram_sell_reason_performance_handle(default_conf, update, ticker, f
trade.close_date = datetime.utcnow() trade.close_date = datetime.utcnow()
trade.is_open = False trade.is_open = False
context = MagicMock() context = MagicMock()
telegram._sell_reason_performance(update=update, context=context) telegram._exit_reason_performance(update=update, context=context)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert 'Sell Reason Performance' in msg_mock.call_args_list[0][0][0] assert 'Exit Reason Performance' in msg_mock.call_args_list[0][0][0]
assert '<code>TESTSELL\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0] assert '<code>TESTSELL\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0]
context.args = [trade.pair] context.args = [trade.pair]
telegram._sell_reason_performance(update=update, context=context) telegram._exit_reason_performance(update=update, context=context)
assert msg_mock.call_count == 2 assert msg_mock.call_count == 2
msg_mock.reset_mock() msg_mock.reset_mock()
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_sell_reason_performance', mocker.patch('freqtrade.rpc.rpc.RPC._rpc_exit_reason_performance',
side_effect=RPCException('Error')) side_effect=RPCException('Error'))
telegram._sell_reason_performance(update=update, context=MagicMock()) telegram._exit_reason_performance(update=update, context=MagicMock())
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert "Error" in msg_mock.call_args_list[0][0][0] assert "Error" in msg_mock.call_args_list[0][0][0]
@ -1439,7 +1442,7 @@ def test_telegram_mix_tag_performance_handle(default_conf, update, ticker, fee,
assert trade assert trade
trade.enter_tag = "TESTBUY" trade.enter_tag = "TESTBUY"
trade.sell_reason = "TESTSELL" trade.exit_reason = "TESTSELL"
# Simulate fulfilled LIMIT_BUY order for trade # Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy') oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
@ -1932,7 +1935,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'stake_currency': 'ETH', 'stake_currency': 'ETH',
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'enter_tag': 'buy_signal1', 'enter_tag': 'buy_signal1',
'sell_reason': ExitType.STOP_LOSS.value, 'exit_reason': ExitType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(hours=-1), 'open_date': arrow.utcnow().shift(hours=-1),
'close_date': arrow.utcnow(), 'close_date': arrow.utcnow(),
}) })
@ -1966,7 +1969,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'profit_ratio': -0.57405275, 'profit_ratio': -0.57405275,
'stake_currency': 'ETH', 'stake_currency': 'ETH',
'enter_tag': 'buy_signal1', 'enter_tag': 'buy_signal1',
'sell_reason': ExitType.STOP_LOSS.value, 'exit_reason': ExitType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30), 'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
'close_date': arrow.utcnow(), 'close_date': arrow.utcnow(),
}) })
@ -2045,7 +2048,7 @@ def test_send_msg_sell_fill_notification(default_conf, mocker, direction,
'profit_ratio': -0.57405275, 'profit_ratio': -0.57405275,
'stake_currency': 'ETH', 'stake_currency': 'ETH',
'enter_tag': enter_signal, 'enter_tag': enter_signal,
'sell_reason': ExitType.STOP_LOSS.value, 'exit_reason': ExitType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30), 'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
'close_date': arrow.utcnow(), 'close_date': arrow.utcnow(),
}) })
@ -2169,7 +2172,7 @@ def test_send_msg_sell_notification_no_fiat(
'stake_currency': 'ETH', 'stake_currency': 'ETH',
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'enter_tag': enter_signal, 'enter_tag': enter_signal,
'sell_reason': ExitType.STOP_LOSS.value, 'exit_reason': ExitType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(hours=-2, minutes=-35, seconds=-3), 'open_date': arrow.utcnow().shift(hours=-2, minutes=-35, seconds=-3),
'close_date': arrow.utcnow(), 'close_date': arrow.utcnow(),
}) })
@ -2191,13 +2194,13 @@ def test_send_msg_sell_notification_no_fiat(
@pytest.mark.parametrize('msg,expected', [ @pytest.mark.parametrize('msg,expected', [
({'profit_percent': 20.1, 'sell_reason': 'roi'}, "\N{ROCKET}"), ({'profit_percent': 20.1, 'exit_reason': 'roi'}, "\N{ROCKET}"),
({'profit_percent': 5.1, 'sell_reason': 'roi'}, "\N{ROCKET}"), ({'profit_percent': 5.1, 'exit_reason': 'roi'}, "\N{ROCKET}"),
({'profit_percent': 2.56, 'sell_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"), ({'profit_percent': 2.56, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
({'profit_percent': 1.0, 'sell_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"), ({'profit_percent': 1.0, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
({'profit_percent': 0.0, 'sell_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"), ({'profit_percent': 0.0, 'exit_reason': 'roi'}, "\N{EIGHT SPOKED ASTERISK}"),
({'profit_percent': -5.0, 'sell_reason': 'stop_loss'}, "\N{WARNING SIGN}"), ({'profit_percent': -5.0, 'exit_reason': 'stop_loss'}, "\N{WARNING SIGN}"),
({'profit_percent': -2.0, 'sell_reason': 'sell_signal'}, "\N{CROSS MARK}"), ({'profit_percent': -2.0, 'exit_reason': 'sell_signal'}, "\N{CROSS MARK}"),
]) ])
def test__sell_emoji(default_conf, mocker, msg, expected): def test__sell_emoji(default_conf, mocker, msg, expected):
del default_conf['fiat_display_currency'] del default_conf['fiat_display_currency']

View File

@ -236,6 +236,8 @@ def test_edge_overrides_stoploss(limit_order, fee, caplog, mocker,
assert freqtrade.handle_trade(trade) is not ignore_strat_sl assert freqtrade.handle_trade(trade) is not ignore_strat_sl
if not ignore_strat_sl: if not ignore_strat_sl:
assert log_has_re('Exit for NEO/BTC detected. Reason: stop_loss.*', caplog) assert log_has_re('Exit for NEO/BTC detected. Reason: stop_loss.*', caplog)
assert trade.exit_reason == ExitType.STOP_LOSS.value
# Test compatibility ...
assert trade.sell_reason == ExitType.STOP_LOSS.value assert trade.sell_reason == ExitType.STOP_LOSS.value
@ -1208,7 +1210,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
assert freqtrade.handle_stoploss_on_exchange(trade) is False assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert trade.stoploss_order_id is None assert trade.stoploss_order_id is None
assert trade.is_open is False assert trade.is_open is False
assert trade.sell_reason == str(ExitType.EMERGENCY_SELL) assert trade.exit_reason == str(ExitType.EMERGENCY_SELL)
@pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
@ -1291,7 +1293,7 @@ def test_create_stoploss_order_invalid_order(
caplog.clear() caplog.clear()
freqtrade.create_stoploss_order(trade, 200) freqtrade.create_stoploss_order(trade, 200)
assert trade.stoploss_order_id is None assert trade.stoploss_order_id is None
assert trade.sell_reason == ExitType.EMERGENCY_SELL.value assert trade.exit_reason == ExitType.EMERGENCY_SELL.value
assert log_has("Unable to place a stoploss order on exchange. ", caplog) assert log_has("Unable to place a stoploss order on exchange. ", caplog)
assert log_has("Exiting the trade forcefully", caplog) assert log_has("Exiting the trade forcefully", caplog)
@ -2150,7 +2152,7 @@ def test_handle_trade(
assert trade.close_profit == close_profit assert trade.close_profit == close_profit
assert trade.calc_profit() == 5.685 assert trade.calc_profit() == 5.685
assert trade.close_date is not None assert trade.close_date is not None
assert trade.sell_reason == 'sell_signal1' assert trade.exit_reason == 'sell_signal1'
@pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
@ -2985,7 +2987,7 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee) -> None:
fee_close=fee.return_value, fee_close=fee.return_value,
close_rate=0.555, close_rate=0.555,
close_date=arrow.utcnow().datetime, close_date=arrow.utcnow().datetime,
sell_reason="sell_reason_whatever", exit_reason="sell_reason_whatever",
) )
order = {'remaining': 1, order = {'remaining': 1,
'amount': 1, 'amount': 1,
@ -2995,7 +2997,7 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee) -> None:
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
assert send_msg_mock.call_count == 1 assert send_msg_mock.call_count == 1
assert trade.close_rate is None assert trade.close_rate is None
assert trade.sell_reason is None assert trade.exit_reason is None
send_msg_mock.reset_mock() send_msg_mock.reset_mock()
@ -3107,6 +3109,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
'stake_currency': 'USDT', 'stake_currency': 'USDT',
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'sell_reason': ExitType.ROI.value, 'sell_reason': ExitType.ROI.value,
'exit_reason': ExitType.ROI.value,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
@ -3166,6 +3169,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
'stake_currency': 'USDT', 'stake_currency': 'USDT',
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'sell_reason': ExitType.STOP_LOSS.value, 'sell_reason': ExitType.STOP_LOSS.value,
'exit_reason': ExitType.STOP_LOSS.value,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
@ -3246,6 +3250,7 @@ def test_execute_trade_exit_custom_exit_price(
'stake_currency': 'USDT', 'stake_currency': 'USDT',
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'sell_reason': ExitType.SELL_SIGNAL.value, 'sell_reason': ExitType.SELL_SIGNAL.value,
'exit_reason': ExitType.SELL_SIGNAL.value,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
@ -3313,6 +3318,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
'stake_currency': 'USDT', 'stake_currency': 'USDT',
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'sell_reason': ExitType.STOP_LOSS.value, 'sell_reason': ExitType.STOP_LOSS.value,
'exit_reason': ExitType.STOP_LOSS.value,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
@ -3479,7 +3485,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(
freqtrade.exit_positions(trades) freqtrade.exit_positions(trades)
assert trade.stoploss_order_id is None assert trade.stoploss_order_id is None
assert trade.is_open is False assert trade.is_open is False
assert trade.sell_reason == ExitType.STOPLOSS_ON_EXCHANGE.value assert trade.exit_reason == ExitType.STOPLOSS_ON_EXCHANGE.value
assert rpc_mock.call_count == 3 assert rpc_mock.call_count == 3
if is_short: if is_short:
assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.SHORT assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.SHORT
@ -3576,6 +3582,7 @@ def test_execute_trade_exit_market_order(
'stake_currency': 'USDT', 'stake_currency': 'USDT',
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'sell_reason': ExitType.ROI.value, 'sell_reason': ExitType.ROI.value,
'exit_reason': ExitType.ROI.value,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
@ -3843,7 +3850,7 @@ def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_order_op
else: else:
patch_get_signal(freqtrade, enter_long=False, exit_long=False) patch_get_signal(freqtrade, enter_long=False, exit_long=False)
assert freqtrade.handle_trade(trade) is True assert freqtrade.handle_trade(trade) is True
assert trade.sell_reason == ExitType.ROI.value assert trade.exit_reason == ExitType.ROI.value
@pytest.mark.parametrize("is_short,val1,val2", [ @pytest.mark.parametrize("is_short,val1,val2", [
@ -3905,7 +3912,7 @@ def test_trailing_stop_loss(default_conf_usdt, limit_order_open,
f"stoploss is {(2.0 * val1 * stop_multi):6f}, " f"stoploss is {(2.0 * val1 * stop_multi):6f}, "
f"initial stoploss was at {(2.0 * stop_multi):6f}, trade opened at 2.000000", f"initial stoploss was at {(2.0 * stop_multi):6f}, trade opened at 2.000000",
caplog) caplog)
assert trade.sell_reason == ExitType.TRAILING_STOP_LOSS.value assert trade.exit_reason == ExitType.TRAILING_STOP_LOSS.value
@pytest.mark.parametrize('offset,trail_if_reached,second_sl,is_short', [ @pytest.mark.parametrize('offset,trail_if_reached,second_sl,is_short', [
@ -4011,7 +4018,7 @@ def test_trailing_stop_loss_positive(
f"initial stoploss was at {'2.42' if is_short else '1.80'}0000, " f"initial stoploss was at {'2.42' if is_short else '1.80'}0000, "
f"trade opened at {2.2 if is_short else 2.0}00000", f"trade opened at {2.2 if is_short else 2.0}00000",
caplog) caplog)
assert trade.sell_reason == ExitType.TRAILING_STOP_LOSS.value assert trade.exit_reason == ExitType.TRAILING_STOP_LOSS.value
@pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
@ -4057,7 +4064,7 @@ def test_disable_ignore_roi_if_buy_signal(default_conf_usdt, limit_order, limit_
# Test if entry-signal is absent # Test if entry-signal is absent
patch_get_signal(freqtrade) patch_get_signal(freqtrade)
assert freqtrade.handle_trade(trade) is True assert freqtrade.handle_trade(trade) is True
assert trade.sell_reason == ExitType.ROI.value assert trade.exit_reason == ExitType.ROI.value
def test_get_real_amount_quote(default_conf_usdt, trades_for_order, buy_order_fee, fee, caplog, def test_get_real_amount_quote(default_conf_usdt, trades_for_order, buy_order_fee, fee, caplog,

View File

@ -115,15 +115,15 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
assert wallets_mock.call_count == 4 assert wallets_mock.call_count == 4
trade = trades[0] trade = trades[0]
assert trade.sell_reason == ExitType.STOPLOSS_ON_EXCHANGE.value assert trade.exit_reason == ExitType.STOPLOSS_ON_EXCHANGE.value
assert not trade.is_open assert not trade.is_open
trade = trades[1] trade = trades[1]
assert not trade.sell_reason assert not trade.exit_reason
assert trade.is_open assert trade.is_open
trade = trades[2] trade = trades[2]
assert trade.sell_reason == ExitType.SELL_SIGNAL.value assert trade.exit_reason == ExitType.SELL_SIGNAL.value
assert not trade.is_open assert not trade.is_open

View File

@ -1255,7 +1255,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
assert trade.min_rate is None assert trade.min_rate is None
assert trade.stop_loss == 0.0 assert trade.stop_loss == 0.0
assert trade.initial_stop_loss == 0.0 assert trade.initial_stop_loss == 0.0
assert trade.sell_reason is None assert trade.exit_reason is None
assert trade.strategy is None assert trade.strategy is None
assert trade.timeframe == '5m' assert trade.timeframe == '5m'
assert trade.stoploss_order_id == 'stop_order_id222' assert trade.stoploss_order_id == 'stop_order_id222'
@ -1590,6 +1590,7 @@ def test_to_json(fee):
'profit_pct': None, 'profit_pct': None,
'profit_abs': None, 'profit_abs': None,
'sell_reason': None, 'sell_reason': None,
'exit_reason': None,
'sell_order_status': None, 'sell_order_status': None,
'stop_loss_abs': None, 'stop_loss_abs': None,
'stop_loss_ratio': None, 'stop_loss_ratio': None,
@ -1676,6 +1677,7 @@ def test_to_json(fee):
'open_rate_requested': None, 'open_rate_requested': None,
'open_trade_value': 12.33075, 'open_trade_value': 12.33075,
'sell_reason': None, 'sell_reason': None,
'exit_reason': None,
'sell_order_status': None, 'sell_order_status': None,
'strategy': None, 'strategy': None,
'buy_tag': 'buys_signal_001', 'buy_tag': 'buys_signal_001',
@ -2195,7 +2197,7 @@ def test_Trade_object_idem():
'get_open_trades_without_assigned_fees', 'get_open_trades_without_assigned_fees',
'get_open_order_trades', 'get_open_order_trades',
'get_trades', 'get_trades',
'get_sell_reason_performance', 'get_exit_reason_performance',
'get_enter_tag_performance', 'get_enter_tag_performance',
'get_mix_tag_performance', 'get_mix_tag_performance',

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long