sell_reason -> exit_reason

This commit is contained in:
Sam Germain 2022-01-04 22:52:52 -06:00
parent c899eabe1d
commit f5805543ed
43 changed files with 324 additions and 324 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

@ -49,7 +49,7 @@ from freqtrade.exchange import timeframe_to_prev_date
class AwesomeStrategy(IStrategy): class AwesomeStrategy(IStrategy):
def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float, def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float,
rate: float, time_in_force: str, sell_reason: str, rate: float, time_in_force: str, exit_reason: str,
current_time: 'datetime', **kwargs) -> bool: current_time: 'datetime', **kwargs) -> bool:
# Obtain pair dataframe. # Obtain pair dataframe.
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
@ -125,7 +125,7 @@ def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame
The provided exit-tag is then used as sell-reason - and shown as such in backtest results. The provided exit-tag is then used as sell-reason - and shown as such in backtest results.
!!! Note !!! Note
`sell_reason` is limited to 100 characters, remaining data will be truncated. `exit_reason` is limited to 100 characters, remaining data will be truncated.
## Strategy version ## Strategy version

View File

@ -539,7 +539,7 @@ class AwesomeStrategy(IStrategy):
# ... populate_* methods # ... populate_* methods
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
rate: float, time_in_force: str, sell_reason: str, rate: float, time_in_force: str, exit_reason: str,
current_time: datetime, **kwargs) -> bool: current_time: datetime, **kwargs) -> bool:
""" """
Called right before placing a regular sell order. Called right before placing a regular sell order.
@ -555,7 +555,7 @@ class AwesomeStrategy(IStrategy):
:param amount: Amount in quote currency. :param amount: Amount in quote currency.
:param rate: Rate that's going to be used when using limit orders :param rate: Rate that's going to be used when using limit orders
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param sell_reason: Sell reason. :param exit_reason: Sell reason.
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss', Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
'sell_signal', 'force_sell', 'emergency_sell'] 'sell_signal', 'force_sell', 'emergency_sell']
:param current_time: datetime object, containing the current datetime :param current_time: datetime object, containing the current datetime
@ -563,7 +563,7 @@ class AwesomeStrategy(IStrategy):
:return bool: When True is returned, then the sell-order is placed on the exchange. :return bool: When True is returned, then the sell-order is placed on the exchange.
False aborts the process False aborts the process
""" """
if sell_reason == 'force_sell' and trade.calc_profit_ratio(rate) < 0: if exit_reason == 'force_sell' and trade.calc_profit_ratio(rate) < 0:
# Reject force-sells with negative profit # Reject force-sells with negative profit
# This is just a sample, please adjust to your needs # This is just a sample, please adjust to your needs
# (this does not necessarily make sense, assuming you know when you're force-selling) # (this does not necessarily make sense, assuming you know when you're force-selling)

View File

@ -822,7 +822,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

@ -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

@ -17,18 +17,18 @@ logger = logging.getLogger(__name__)
# Old format - maybe remove? # Old format - maybe remove?
BT_DATA_COLUMNS_OLD = ["pair", "profit_percent", "open_date", "close_date", "index", BT_DATA_COLUMNS_OLD = ["pair", "profit_percent", "open_date", "close_date", "index",
"trade_duration", "open_rate", "close_rate", "open_at_end", "sell_reason"] "trade_duration", "open_rate", "close_rate", "open_at_end", "exit_reason"]
# Mid-term format, created by BacktestResult Named Tuple # Mid-term format, created by BacktestResult Named Tuple
BT_DATA_COLUMNS_MID = ['pair', 'profit_percent', 'open_date', 'close_date', 'trade_duration', BT_DATA_COLUMNS_MID = ['pair', 'profit_percent', 'open_date', 'close_date', 'trade_duration',
'open_rate', 'close_rate', 'open_at_end', 'sell_reason', 'fee_open', 'open_rate', 'close_rate', 'open_at_end', 'exit_reason', 'fee_open',
'fee_close', 'amount', 'profit_abs', 'profit_ratio'] 'fee_close', 'amount', 'profit_abs', 'profit_ratio']
# Newest format # Newest format
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

@ -914,7 +914,7 @@ class FreqtradeBot(LoggingMixin):
trade.stoploss_order_id = None trade.stoploss_order_id = None
logger.error(f'Unable to place a stoploss order on exchange. {e}') logger.error(f'Unable to place a stoploss order on exchange. {e}')
logger.warning('Exiting the trade forcefully') logger.warning('Exiting the trade forcefully')
self.execute_trade_exit(trade, trade.stop_loss, sell_reason=SellCheckTuple( self.execute_trade_exit(trade, trade.stop_loss, exit_reason=SellCheckTuple(
sell_type=SellType.EMERGENCY_SELL)) sell_type=SellType.EMERGENCY_SELL))
except ExchangeError: except ExchangeError:
@ -947,7 +947,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'):
# TODO-lev: Update to exit reason # TODO-lev: Update to exit reason
trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value trade.exit_reason = SellType.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
@ -1102,7 +1102,7 @@ class FreqtradeBot(LoggingMixin):
try: try:
self.execute_trade_exit( self.execute_trade_exit(
trade, order.get('price'), trade, order.get('price'),
sell_reason=SellCheckTuple(sell_type=SellType.EMERGENCY_SELL)) exit_reason=SellCheckTuple(sell_type=SellType.EMERGENCY_SELL))
except DependencyException as exception: except DependencyException as exception:
logger.warning( logger.warning(
f'Unable to emergency sell trade {trade.pair}: {exception}') f'Unable to emergency sell trade {trade.pair}: {exception}')
@ -1266,7 +1266,7 @@ class FreqtradeBot(LoggingMixin):
self, self,
trade: Trade, trade: Trade,
limit: float, limit: float,
sell_reason: SellCheckTuple, exit_reason: SellCheckTuple,
*, *,
exit_tag: Optional[str] = None, exit_tag: Optional[str] = None,
ordertype: Optional[str] = None, ordertype: Optional[str] = None,
@ -1275,7 +1275,7 @@ class FreqtradeBot(LoggingMixin):
Executes a trade exit for the given trade and limit Executes a trade exit for the given trade and limit
:param trade: Trade instance :param trade: Trade instance
:param limit: limit rate for the sell order :param limit: limit rate for the sell order
:param sell_reason: Reason the sell was triggered :param exit_reason: Reason the sell was triggered
:return: True if it succeeds (supported) False (not supported) :return: True if it succeeds (supported) False (not supported)
""" """
trade.funding_fees = self.exchange.get_funding_fees( trade.funding_fees = self.exchange.get_funding_fees(
@ -1284,7 +1284,7 @@ class FreqtradeBot(LoggingMixin):
trade.open_date trade.open_date
) )
exit_type = 'sell' # TODO-lev: Update to exit exit_type = 'sell' # TODO-lev: Update to exit
if sell_reason.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): if exit_reason.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
exit_type = 'stoploss' exit_type = 'stoploss'
# if stoploss is on exchange and we are on dry_run mode, # if stoploss is on exchange and we are on dry_run mode,
@ -1314,7 +1314,7 @@ class FreqtradeBot(LoggingMixin):
logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}") logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}")
order_type = ordertype or self.strategy.order_types[exit_type] order_type = ordertype or self.strategy.order_types[exit_type]
if sell_reason.sell_type == SellType.EMERGENCY_SELL: if exit_reason.sell_type == SellType.EMERGENCY_SELL:
# Emergency sells (default to market!) # Emergency sells (default to market!)
order_type = self.strategy.order_types.get("emergencysell", "market") order_type = self.strategy.order_types.get("emergencysell", "market")
@ -1323,7 +1323,7 @@ class FreqtradeBot(LoggingMixin):
if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)( if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)(
pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit, pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit,
time_in_force=time_in_force, sell_reason=sell_reason.sell_reason, time_in_force=time_in_force, exit_reason=exit_reason.exit_reason,
current_time=datetime.now(timezone.utc)): # TODO-lev: Update to exit current_time=datetime.now(timezone.utc)): # TODO-lev: Update to exit
logger.info(f"User requested abortion of exiting {trade.pair}") logger.info(f"User requested abortion of exiting {trade.pair}")
return False return False
@ -1350,7 +1350,7 @@ class FreqtradeBot(LoggingMixin):
trade.open_order_id = order['id'] trade.open_order_id = order['id']
trade.exit_order_status = '' trade.exit_order_status = ''
trade.close_rate_requested = limit trade.close_rate_requested = limit
trade.sell_reason = exit_tag or sell_reason.sell_reason trade.exit_reason = exit_tag or exit_reason.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),
@ -1395,7 +1395,7 @@ 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, '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'],
@ -1442,7 +1442,7 @@ 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, '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

@ -411,11 +411,11 @@ class Backtesting:
pair=trade.pair, trade=trade, order_type='limit', amount=trade.amount, pair=trade.pair, trade=trade, order_type='limit', amount=trade.amount,
rate=closerate, rate=closerate,
time_in_force=time_in_force, time_in_force=time_in_force,
sell_reason=sell.sell_reason, exit_reason=sell.exit_reason,
current_time=sell_candle_time): current_time=sell_candle_time):
return None return None
trade.sell_reason = sell.sell_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
@ -424,7 +424,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]
trade.close(closerate, show_msg=False) trade.close(closerate, show_msg=False)
return trade return trade
@ -542,7 +542,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 = SellType.FORCE_SELL.value trade.exit_reason = SellType.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

@ -159,7 +159,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
@ -168,8 +168,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()
@ -177,7 +177,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]),
@ -383,7 +383,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
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)
sell_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(btdata, stake_currency=stake_currency, left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
starting_balance=start_balance, starting_balance=start_balance,
@ -407,7 +407,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
'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': sell_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,
@ -558,10 +558,10 @@ 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_sell_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: Sell reason metrics :param exit_reason_stats: Sell reason metrics
:param stake_currency: Stakecurrency used :param stake_currency: Stakecurrency used
:return: pretty printed table with tabulate as string :return: pretty printed table with tabulate as string
""" """
@ -576,12 +576,12 @@ def text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], stake_curren
] ]
output = [[ output = [[
t['sell_reason'], t['trades'], t['exit_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")
@ -788,7 +788,7 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
print(' BUY TAG STATS '.center(len(table.splitlines()[0]), '=')) print(' BUY TAG STATS '.center(len(table.splitlines()[0]), '='))
print(table) print(table)
table = text_table_sell_reason(sell_reason_stats=results['sell_reason_summary'], table = text_table_exit_reason(exit_reason_stats=results['exit_reason_summary'],
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(' SELL REASON STATS '.center(len(table.splitlines()[0]), '=')) print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '='))

View File

@ -45,7 +45,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
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, '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'))
@ -98,7 +98,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
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, exit_order_status, strategy, enter_tag, max_rate, min_rate, exit_reason, exit_order_status, strategy, enter_tag,
timeframe, open_trade_value, close_profit_abs, timeframe, open_trade_value, close_profit_abs,
trading_mode, leverage, isolated_liq, is_short, trading_mode, leverage, isolated_liq, is_short,
interest_rate, funding_fees interest_rate, funding_fees
@ -114,7 +114,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
{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,
{exit_order_status} exit_order_status, {exit_order_status} exit_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,

View File

@ -261,7 +261,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 = ''
exit_order_status: str = '' exit_order_status: str = ''
strategy: str = '' strategy: str = ''
enter_tag: Optional[str] = None enter_tag: Optional[str] = None
@ -435,7 +435,7 @@ 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, 'exit_reason': self.exit_reason,
'exit_order_status': self.exit_order_status, 'exit_order_status': self.exit_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,
@ -575,7 +575,7 @@ class LocalTrade():
elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'): elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'):
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 = SellType.STOPLOSS_ON_EXCHANGE.value self.exit_reason = SellType.STOPLOSS_ON_EXCHANGE.value
if self.is_open: if self.is_open:
logger.info(f'{order_type.upper()} is hit for {self}.') logger.info(f'{order_type.upper()} is hit for {self}.')
self.close(safe_value_fallback(order, 'average', 'price')) self.close(safe_value_fallback(order, 'average', 'price'))
@ -936,7 +936,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)
exit_order_status = Column(String(100), nullable=True) exit_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)
@ -1143,7 +1143,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
@ -1155,30 +1155,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 buy_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.
""" """
@ -1190,7 +1190,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')
@ -1200,12 +1200,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

@ -236,7 +236,7 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
if trades is not None and len(trades) > 0: if trades is not None and len(trades) > 0:
# Create description for sell summarizing the trade # Create description for sell summarizing the trade
trades['desc'] = trades.apply(lambda row: f"{row['profit_ratio']:.2%}, " trades['desc'] = trades.apply(lambda row: f"{row['profit_ratio']:.2%}, "
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(
@ -535,7 +535,7 @@ def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame],
"Profit per pair", "Profit per pair",
"Parallelism", "Parallelism",
"Underwater", "Underwater",
]) ])
fig['layout'].update(title="Freqtrade Profit plot") fig['layout'].update(title="Freqtrade Profit plot")
fig['layout']['yaxis1'].update(title='Price') fig['layout']['yaxis1'].update(title='Price')
fig['layout']['yaxis2'].update(title=f'Profit {stake_currency}') fig['layout']['yaxis2'].update(title=f'Profit {stake_currency}')

View File

@ -44,8 +44,8 @@ class StoplossGuard(IProtection):
# filters = [ # filters = [
# Trade.is_open.is_(False), # Trade.is_open.is_(False),
# Trade.close_date > look_back_until, # Trade.close_date > look_back_until,
# or_(Trade.sell_reason == SellType.STOP_LOSS.value, # or_(Trade.exit_reason == SellType.STOP_LOSS.value,
# and_(Trade.sell_reason == SellType.TRAILING_STOP_LOSS.value, # and_(Trade.exit_reason == SellType.TRAILING_STOP_LOSS.value,
# Trade.close_profit < 0)) # Trade.close_profit < 0))
# ] # ]
# if pair: # if pair:
@ -54,7 +54,7 @@ class StoplossGuard(IProtection):
# TODO-lev: Liquidation price? # TODO-lev: Liquidation price?
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 (
SellType.TRAILING_STOP_LOSS.value, SellType.STOP_LOSS.value, SellType.TRAILING_STOP_LOSS.value, SellType.STOP_LOSS.value,
SellType.STOPLOSS_ON_EXCHANGE.value) SellType.STOPLOSS_ON_EXCHANGE.value)
and trade.close_profit and trade.close_profit < 0)] and trade.close_profit and trade.close_profit < 0)]

View File

@ -108,7 +108,7 @@ class SellReason(BaseModel):
class Stats(BaseModel): class Stats(BaseModel):
sell_reasons: Dict[str, SellReason] exit_reasons: Dict[str, SellReason]
durations: Dict[str, Union[str, float]] durations: Dict[str, Union[str, float]]
@ -212,7 +212,7 @@ 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] exit_reason: Optional[str]
exit_order_status: Optional[str] exit_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

@ -416,11 +416,11 @@ class RPC:
return 'draws' return 'draws'
trades = trades = Trade.get_trades([Trade.is_open.is_(False)]) trades = trades = 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': []}
@ -434,7 +434,7 @@ class RPC:
losses_dur = sum(dur['losses']) / len(dur['losses']) if len(dur['losses']) > 0 else 'N/A' losses_dur = sum(dur['losses']) / len(dur['losses']) if len(dur['losses']) > 0 else 'N/A'
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,
@ -672,12 +672,12 @@ class RPC:
closing_side = "buy" if trade.is_short else "sell" closing_side = "buy" if trade.is_short else "sell"
current_rate = self._freqtrade.exchange.get_rate( current_rate = self._freqtrade.exchange.get_rate(
trade.pair, refresh=False, side=closing_side) trade.pair, refresh=False, side=closing_side)
sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL) exit_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL)
order_type = ordertype or self._freqtrade.strategy.order_types.get( order_type = ordertype or self._freqtrade.strategy.order_types.get(
"forcesell", self._freqtrade.strategy.order_types["sell"]) "forcesell", self._freqtrade.strategy.order_types["sell"])
self._freqtrade.execute_trade_exit( self._freqtrade.execute_trade_exit(
trade, current_rate, sell_reason, ordertype=order_type) trade, current_rate, exit_reason, ordertype=order_type)
# ---- EOF def _exec_forcesell ---- # ---- EOF def _exec_forcesell ----
if self._freqtrade.state != State.RUNNING: if self._freqtrade.state != State.RUNNING:
@ -799,16 +799,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)
@ -872,7 +872,7 @@ class RPC:
else: else:
errors[pair] = { errors[pair] = {
'error_msg': f"Pair {pair} is not in the current blacklist." 'error_msg': f"Pair {pair} is not in the current blacklist."
} }
resp = self._rpc_blacklist() resp = self._rpc_blacklist()
resp['errors'] = errors resp['errors'] = errors
return resp return resp

View File

@ -156,7 +156,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', 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),
@ -186,8 +186,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._forcebuy_inline), CallbackQueryHandler(self._forcebuy_inline),
@ -284,7 +284,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']}"
@ -355,7 +355,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)
@ -378,7 +378,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}"
@ -697,23 +697,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=['Sell Reason', 'Sells', '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(
@ -725,7 +725,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)
@ -1026,7 +1026,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
@ -1039,11 +1039,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>Sell 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")
@ -1055,7 +1055,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

@ -35,11 +35,11 @@ class SellCheckTuple:
NamedTuple for Sell type + reason NamedTuple for Sell type + reason
""" """
sell_type: SellType sell_type: SellType
sell_reason: str = '' exit_reason: str = ''
def __init__(self, sell_type: SellType, sell_reason: str = ''): def __init__(self, sell_type: SellType, exit_reason: str = ''):
self.sell_type = sell_type self.sell_type = sell_type
self.sell_reason = sell_reason or sell_type.value self.exit_reason = exit_reason or sell_type.value
@property @property
def sell_flag(self): def sell_flag(self):
@ -256,7 +256,7 @@ class IStrategy(ABC, HyperStrategyMixin):
return True return True
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float, def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
rate: float, time_in_force: str, sell_reason: str, rate: float, time_in_force: str, exit_reason: str,
current_time: datetime, **kwargs) -> bool: current_time: datetime, **kwargs) -> bool:
""" """
Called right before placing a regular exit order. Called right before placing a regular exit order.
@ -273,7 +273,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param amount: Amount in quote currency. :param amount: Amount in quote currency.
:param rate: Rate that's going to be used when using limit orders :param rate: Rate that's going to be used when using limit orders
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param sell_reason: Exit reason. :param exit_reason: Exit reason.
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss', Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
'sell_signal', 'force_sell', 'emergency_sell'] 'sell_signal', 'force_sell', 'emergency_sell']
:param current_time: datetime object, containing the current datetime :param current_time: datetime object, containing the current datetime
@ -815,7 +815,7 @@ class IStrategy(ABC, HyperStrategyMixin):
logger.debug(f"{trade.pair} - Sell signal received. " logger.debug(f"{trade.pair} - Sell signal received. "
f"sell_type=SellType.{sell_signal.name}" + f"sell_type=SellType.{sell_signal.name}" +
(f", custom_reason={custom_reason}" if custom_reason else "")) (f", custom_reason={custom_reason}" if custom_reason else ""))
return SellCheckTuple(sell_type=sell_signal, sell_reason=custom_reason) return SellCheckTuple(sell_type=sell_signal, exit_reason=custom_reason)
# Sequence: # Sequence:
# Exit-signal # Exit-signal

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

@ -103,7 +103,7 @@ def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: f
return True return True
def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float, def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float,
rate: float, time_in_force: str, sell_reason: str, rate: float, time_in_force: str, exit_reason: str,
current_time: 'datetime', **kwargs) -> bool: current_time: 'datetime', **kwargs) -> bool:
""" """
Called right before placing a regular sell order. Called right before placing a regular sell order.
@ -120,7 +120,7 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount:
:param amount: Amount in quote currency. :param amount: Amount in quote currency.
:param rate: Rate that's going to be used when using limit orders :param rate: Rate that's going to be used when using limit orders
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled). :param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param sell_reason: Sell reason. :param exit_reason: Sell reason.
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss', Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
'sell_signal', 'force_sell', 'emergency_sell'] 'sell_signal', 'force_sell', 'emergency_sell']
:param current_time: datetime object, containing the current datetime :param current_time: datetime object, containing the current datetime

View File

@ -103,7 +103,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
@ -163,7 +163,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
@ -400,7 +400,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', # TODO-lev: Update to exit/close reason exit_reason='sell_signal', # TODO-lev: Update to exit/close reason
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
@ -489,7 +489,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=SellType.SELL_SIGNAL, open_tick=1, close_tick=2), trades=[BTrade(exit_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=2),
BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=4, close_tick=6)] BTrade(exit_reason=SellType.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=SellType.STOP_LOSS, open_tick=1, close_tick=1)] trades=[BTrade(exit_reason=SellType.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=SellType.STOP_LOSS, open_tick=1, close_tick=1)] trades=[BTrade(exit_reason=SellType.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=SellType.STOP_LOSS, open_tick=1, close_tick=1)] trades=[BTrade(exit_reason=SellType.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: SellType exit_reason: SellType
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': [SellType.STOP_LOSS, SellType.ROI, SellType.STOP_LOSS, SellType.ROI], 'exit_reason': [SellType.STOP_LOSS, SellType.ROI, SellType.STOP_LOSS, SellType.ROI],
'open_date': 'open_date':
[ [
datetime(2019, 1, 1, 9, 15, 0), datetime(2019, 1, 1, 9, 15, 0),

View File

@ -22,7 +22,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_exit_signal=True, stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_exit_signal=True,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)] trades=[BTrade(exit_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
) )
# Test 1: Stop-Loss Triggered 1% loss # Test 1: Stop-Loss Triggered 1% loss
@ -36,7 +36,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=SellType.STOP_LOSS, open_tick=1, close_tick=2)] trades=[BTrade(exit_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
) )
@ -51,7 +51,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=SellType.STOP_LOSS, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)]
) )
@ -71,8 +71,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=SellType.STOP_LOSS, open_tick=1, close_tick=2), trades=[BTrade(exit_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2),
BTrade(sell_reason=SellType.STOP_LOSS, open_tick=4, close_tick=5)] BTrade(exit_reason=SellType.STOP_LOSS, open_tick=4, close_tick=5)]
) )
# Test 4: Minus 3% / recovery +15% # Test 4: Minus 3% / recovery +15%
@ -88,7 +88,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=SellType.STOP_LOSS, open_tick=1, close_tick=2)] trades=[BTrade(exit_reason=SellType.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
@ -102,7 +102,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=SellType.ROI, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=SellType.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
@ -116,7 +116,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=SellType.STOP_LOSS, open_tick=1, close_tick=2)] trades=[BTrade(exit_reason=SellType.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
@ -130,7 +130,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=SellType.ROI, open_tick=1, close_tick=2)] trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=2)]
) )
@ -144,7 +144,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=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
) )
@ -158,7 +158,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=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=SellType.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
@ -174,7 +174,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=SellType.STOP_LOSS, open_tick=1, close_tick=4)] trades=[BTrade(exit_reason=SellType.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
@ -190,7 +190,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=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=SellType.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
@ -206,7 +206,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=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)] trades=[BTrade(exit_reason=SellType.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
@ -219,7 +219,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=SellType.ROI, open_tick=1, close_tick=1)] trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=1)]
) )
# Test 14 - Buy and Stoploss on same candle # Test 14 - Buy and Stoploss on same candle
@ -232,7 +232,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=SellType.STOP_LOSS, open_tick=1, close_tick=1)] trades=[BTrade(exit_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
) )
@ -246,8 +246,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=SellType.ROI, open_tick=1, close_tick=1), trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=1),
BTrade(sell_reason=SellType.STOP_LOSS, open_tick=2, close_tick=2)] BTrade(exit_reason=SellType.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
@ -262,7 +262,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=SellType.ROI, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=SellType.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
@ -278,7 +278,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=SellType.ROI, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=3)]
) )
@ -294,7 +294,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=SellType.ROI, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=SellType.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.
@ -309,7 +309,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=SellType.ROI, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=SellType.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.
@ -324,7 +324,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=SellType.ROI, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=3)]
) )
# Test 21: trailing_stop ROI collision. # Test 21: trailing_stop ROI collision.
@ -341,7 +341,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=SellType.ROI, open_tick=1, close_tick=2)] trades=[BTrade(exit_reason=SellType.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.
@ -357,7 +357,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=SellType.ROI, open_tick=1, close_tick=2)] trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=2)]
) )
# Test 23: trailing_stop Raises in candle 2 (does not trigger) # Test 23: trailing_stop Raises in candle 2 (does not trigger)
@ -376,7 +376,7 @@ tc23 = 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=SellType.ROI, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=3)]
) )
# Test 24: Sell with signal sell in candle 3 (stoploss also triggers on this candle) # Test 24: Sell with signal sell in candle 3 (stoploss also triggers on this candle)
@ -391,7 +391,7 @@ tc24 = 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_exit_signal=True, stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, use_exit_signal=True,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=SellType.STOP_LOSS, 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)
@ -406,7 +406,7 @@ tc25 = 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_exit_signal=True, stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_exit_signal=True,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)] trades=[BTrade(exit_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
) )
# Test 26: Sell with signal sell in candle 3 (ROI at signal candle) # Test 26: Sell with signal sell in candle 3 (ROI at signal candle)
@ -421,7 +421,7 @@ tc26 = 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_exit_signal=True, stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_exit_signal=True,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=SellType.ROI, open_tick=1, close_tick=3)]
) )
# Test 27: Sell with signal sell in candle 3 (ROI at signal candle) # Test 27: Sell with signal sell in candle 3 (ROI at signal candle)
@ -435,7 +435,7 @@ tc27 = 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_exit_signal=True, stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.002, use_exit_signal=True,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)] trades=[BTrade(exit_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
) )
# Test 28: trailing_stop should raise so candle 3 causes a stoploss # Test 28: trailing_stop should raise so candle 3 causes a stoploss
@ -452,7 +452,7 @@ tc28 = 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=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)] trades=[BTrade(exit_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
) )
# Test 29: trailing_stop should be triggered by low of next candle, without adjusting stoploss using # Test 29: trailing_stop should be triggered by low of next candle, without adjusting stoploss using
@ -467,7 +467,7 @@ tc29 = 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=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)] trades=[BTrade(exit_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)]
) )
# Test 30: trailing_stop should be triggered immediately on trade open candle. # Test 30: trailing_stop should be triggered immediately on trade open candle.
@ -481,7 +481,7 @@ tc30 = 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=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)] trades=[BTrade(exit_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
) )
# Test 31: trailing_stop should be triggered immediately on trade open candle. # Test 31: trailing_stop should be triggered immediately on trade open candle.
@ -496,7 +496,7 @@ tc31 = 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=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)] trades=[BTrade(exit_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
) )
# Test 32: trailing_stop should be triggered immediately on trade open candle. # Test 32: trailing_stop should be triggered immediately on trade open candle.
@ -511,7 +511,7 @@ tc32 = 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=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)] trades=[BTrade(exit_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=1)]
) )
# Test 33: trailing_stop should be triggered immediately on trade open candle. # Test 33: trailing_stop should be triggered immediately on trade open candle.
@ -527,7 +527,7 @@ tc33 = 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=SellType.TRAILING_STOP_LOSS, exit_reason=SellType.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'
@ -548,7 +548,7 @@ tc34 = BTContainer(data=[
[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 * 5.0, use_exit_signal=True, stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002 * 5.0, use_exit_signal=True,
leverage=5.0, leverage=5.0,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)] trades=[BTrade(exit_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
) )
TESTS = [ TESTS = [
@ -641,7 +641,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 = results.iloc[c] res = 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

@ -628,7 +628,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 == SellType.ROI.value assert res.exit_reason == SellType.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
@ -647,7 +647,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 == SellType.ROI.value assert res.exit_reason == SellType.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)
assert round(res.close_rate, 3) == round(209.0225, 3) assert round(res.close_rate, 3) == round(209.0225, 3)
@ -693,7 +693,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': [SellType.ROI.value, SellType.ROI.value], 'exit_reason': [SellType.ROI.value, SellType.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],
@ -1002,7 +1002,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
PropertyMock(return_value=['UNITTEST/BTC'])) PropertyMock(return_value=['UNITTEST/BTC']))
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock) mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
text_table_mock = MagicMock() text_table_mock = MagicMock()
sell_reason_mock = MagicMock() exit_reason_mock = MagicMock()
strattable_mock = MagicMock() strattable_mock = MagicMock()
strat_summary = MagicMock() strat_summary = MagicMock()
@ -1010,7 +1010,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=exit_reason_mock,
generate_strategy_comparison=strat_summary, generate_strategy_comparison=strat_summary,
generate_daily_stats=MagicMock(), generate_daily_stats=MagicMock(),
) )
@ -1035,7 +1035,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog, testdatadir):
assert backtestmock.call_count == 2 assert backtestmock.call_count == 2
assert text_table_mock.call_count == 4 assert text_table_mock.call_count == 4
assert strattable_mock.call_count == 1 assert strattable_mock.call_count == 1
assert sell_reason_mock.call_count == 2 assert exit_reason_mock.call_count == 2
assert strat_summary.call_count == 1 assert strat_summary.call_count == 1
# check the logs, that will contain the backtest result # check the logs, that will contain the backtest result
@ -1081,7 +1081,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': [SellType.ROI, SellType.ROI] 'exit_reason': [SellType.ROI, SellType.ROI]
}) })
result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'], result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
'profit_ratio': [0.03, 0.01, 0.1], 'profit_ratio': [0.03, 0.01, 0.1],
@ -1099,7 +1099,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': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS] 'exit_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
}) })
backtestmock = MagicMock(side_effect=[ backtestmock = MagicMock(side_effect=[
{ {
@ -1192,7 +1192,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': [SellType.ROI, SellType.ROI] 'exit_reason': [SellType.ROI, SellType.ROI]
}) })
result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'], result2 = pd.DataFrame({'pair': ['XRP/BTC', 'LTC/BTC', 'ETH/BTC'],
'profit_ratio': [0.03, 0.01, 0.1], 'profit_ratio': [0.03, 0.01, 0.1],
@ -1210,7 +1210,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': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS] 'exit_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
}) })
backtestmock = MagicMock(side_effect=[ backtestmock = MagicMock(side_effect=[
{ {

View File

@ -24,25 +24,25 @@ from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, log_has_re
def generate_result_metrics(): def generate_result_metrics():
return { return {
'trade_count': 1, 'trade_count': 1,
'total_trades': 1, 'total_trades': 1,
'avg_profit': 0.1, 'avg_profit': 0.1,
'total_profit': 0.001, 'total_profit': 0.001,
'profit': 0.01, 'profit': 0.01,
'duration': 20.0, 'duration': 20.0,
'wins': 1, 'wins': 1,
'draws': 0, 'draws': 0,
'losses': 0, 'losses': 0,
'profit_mean': 0.01, 'profit_mean': 0.01,
'profit_total_abs': 0.001, 'profit_total_abs': 0.001,
'profit_total': 0.01, 'profit_total': 0.01,
'holding_avg': timedelta(minutes=20), 'holding_avg': timedelta(minutes=20),
'max_drawdown': 0.001, 'max_drawdown': 0.001,
'max_drawdown_abs': 0.001, 'max_drawdown_abs': 0.001,
'loss': 0.001, 'loss': 0.001,
'is_initial_point': 0.001, 'is_initial_point': 0.001,
'is_best': 1, 'is_best': 1,
} }
def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None: def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None:
@ -359,7 +359,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": [SellType.ROI, SellType.STOP_LOSS, "exit_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL] SellType.ROI, SellType.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": [SellType.ROI, SellType.STOP_LOSS, "exit_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL] SellType.ROI, SellType.FORCE_SELL]
}), }),
'config': hyperopt_conf, 'config': hyperopt_conf,

View File

@ -18,11 +18,11 @@ from freqtrade.optimize.optimize_reports import (_get_resample_from_period, gene
generate_daily_stats, generate_edge_table, generate_daily_stats, generate_edge_table,
generate_pair_metrics, generate_pair_metrics,
generate_periodic_breakdown_stats, generate_periodic_breakdown_stats,
generate_sell_reason_stats, generate_exit_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,
text_table_sell_reason, text_table_strategy) text_table_exit_reason, text_table_strategy)
from freqtrade.resolvers.strategy_resolver import StrategyResolver from freqtrade.resolvers.strategy_resolver import StrategyResolver
from tests.conftest import CURRENT_TEST_STRATEGY from tests.conftest import CURRENT_TEST_STRATEGY
from tests.data.test_history import _backup_file, _clean_test_file from tests.data.test_history import _backup_file, _clean_test_file
@ -78,7 +78,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": [SellType.ROI, SellType.STOP_LOSS, "exit_reason": [SellType.ROI, SellType.STOP_LOSS,
SellType.ROI, SellType.FORCE_SELL] SellType.ROI, SellType.FORCE_SELL]
}), }),
'config': default_conf, 'config': default_conf,
@ -87,8 +87,8 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
'rejected_signals': 20, 'rejected_signals': 20,
'backtest_start_time': Arrow.utcnow().int_timestamp, 'backtest_start_time': Arrow.utcnow().int_timestamp,
'backtest_end_time': Arrow.utcnow().int_timestamp, 'backtest_end_time': Arrow.utcnow().int_timestamp,
} }
} }
timerange = TimeRange.parse_timerange('1510688220-1510700340') timerange = TimeRange.parse_timerange('1510688220-1510700340')
min_date = Arrow.fromtimestamp(1510688220) min_date = Arrow.fromtimestamp(1510688220)
max_date = Arrow.fromtimestamp(1510700340) max_date = Arrow.fromtimestamp(1510700340)
@ -127,7 +127,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": [SellType.ROI, SellType.ROI, "exit_reason": [SellType.ROI, SellType.ROI,
SellType.STOP_LOSS, SellType.FORCE_SELL] SellType.STOP_LOSS, SellType.FORCE_SELL]
}), }),
'config': default_conf, 'config': default_conf,
@ -136,7 +136,7 @@ def test_generate_backtest_stats(default_conf, testdatadir, tmpdir):
'rejected_signals': 20, 'rejected_signals': 20,
'backtest_start_time': Arrow.utcnow().int_timestamp, 'backtest_start_time': Arrow.utcnow().int_timestamp,
'backtest_end_time': Arrow.utcnow().int_timestamp, 'backtest_end_time': Arrow.utcnow().int_timestamp,
} }
} }
stats = generate_backtest_stats(btdata, results, min_date, max_date) stats = generate_backtest_stats(btdata, results, min_date, max_date)
@ -260,7 +260,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(
{ {
@ -271,7 +271,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': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS] 'exit_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
} }
) )
@ -286,13 +286,13 @@ 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_sell_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
def test_generate_sell_reason_stats(): def test_generate_exit_reason_stats():
results = pd.DataFrame( results = pd.DataFrame(
{ {
@ -303,23 +303,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': [SellType.ROI.value, SellType.ROI.value, SellType.STOP_LOSS.value] 'exit_reason': [SellType.ROI.value, SellType.ROI.value, SellType.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)
@ -343,7 +343,7 @@ def test_text_table_strategy(default_conf):
'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': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS] 'exit_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
} }
), 'config': default_conf} ), 'config': default_conf}
results['TestStrategy2'] = {'results': pd.DataFrame( results['TestStrategy2'] = {'results': pd.DataFrame(
@ -356,7 +356,7 @@ def test_text_table_strategy(default_conf):
'wins': [4, 1, 0], 'wins': [4, 1, 0],
'draws': [0, 0, 0], 'draws': [0, 0, 0],
'losses': [0, 0, 1], 'losses': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS] 'exit_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
} }
), 'config': default_conf} ), 'config': default_conf}

View File

@ -11,7 +11,7 @@ from tests.conftest import get_patched_freqtradebot, log_has_re
def generate_mock_trade(pair: str, fee: float, is_open: bool, def generate_mock_trade(pair: str, fee: float, is_open: bool,
sell_reason: str = SellType.SELL_SIGNAL, exit_reason: str = SellType.SELL_SIGNAL,
min_ago_open: int = None, min_ago_close: int = None, min_ago_open: int = None, min_ago_close: int = None,
profit_rate: float = 0.9 profit_rate: float = 0.9
): ):
@ -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 = exit_reason
return trade return trade
@ -91,7 +91,7 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog):
caplog.clear() caplog.clear()
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, 'XRP/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=30, min_ago_open=200, min_ago_close=30,
)) ))
@ -100,12 +100,12 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog):
caplog.clear() caplog.clear()
# This trade does not count, as it's closed too long ago # This trade does not count, as it's closed too long ago
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
'BCH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, 'BCH/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=250, min_ago_close=100, min_ago_open=250, min_ago_close=100,
)) ))
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
'ETH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, 'ETH/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=240, min_ago_close=30, min_ago_open=240, min_ago_close=30,
)) ))
# 3 Trades closed - but the 2nd has been closed too long ago. # 3 Trades closed - but the 2nd has been closed too long ago.
@ -114,7 +114,7 @@ def test_stoploss_guard(mocker, default_conf, fee, caplog):
caplog.clear() caplog.clear()
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
'LTC/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, 'LTC/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=180, min_ago_close=30, min_ago_open=180, min_ago_close=30,
)) ))
@ -148,7 +148,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
caplog.clear() caplog.clear()
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, pair, fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=30, profit_rate=0.9, min_ago_open=200, min_ago_close=30, profit_rate=0.9,
)) ))
@ -158,12 +158,12 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
caplog.clear() caplog.clear()
# This trade does not count, as it's closed too long ago # This trade does not count, as it's closed too long ago
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, pair, fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=250, min_ago_close=100, profit_rate=0.9, min_ago_open=250, min_ago_close=100, profit_rate=0.9,
)) ))
# Trade does not count for per pair stop as it's the wrong pair. # Trade does not count for per pair stop as it's the wrong pair.
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
'ETH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, 'ETH/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=240, min_ago_close=30, profit_rate=0.9, min_ago_open=240, min_ago_close=30, profit_rate=0.9,
)) ))
# 3 Trades closed - but the 2nd has been closed too long ago. # 3 Trades closed - but the 2nd has been closed too long ago.
@ -178,7 +178,7 @@ def test_stoploss_guard_perpair(mocker, default_conf, fee, caplog, only_per_pair
# 2nd Trade that counts with correct pair # 2nd Trade that counts with correct pair
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
pair, fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, pair, fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=180, min_ago_close=30, profit_rate=0.9, min_ago_open=180, min_ago_close=30, profit_rate=0.9,
)) ))
@ -203,7 +203,7 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog):
caplog.clear() caplog.clear()
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, 'XRP/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=30, min_ago_open=200, min_ago_close=30,
)) ))
@ -213,7 +213,7 @@ def test_CooldownPeriod(mocker, default_conf, fee, caplog):
assert not PairLocks.is_global_lock() assert not PairLocks.is_global_lock()
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
'ETH/BTC', fee.return_value, False, sell_reason=SellType.ROI.value, 'ETH/BTC', fee.return_value, False, exit_reason=SellType.ROI.value,
min_ago_open=205, min_ago_close=35, min_ago_open=205, min_ago_close=35,
)) ))
@ -242,7 +242,7 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
caplog.clear() caplog.clear()
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, 'XRP/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=800, min_ago_close=450, profit_rate=0.9, min_ago_open=800, min_ago_close=450, profit_rate=0.9,
)) ))
@ -253,7 +253,7 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
assert not PairLocks.is_global_lock() assert not PairLocks.is_global_lock()
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, 'XRP/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=200, min_ago_close=120, profit_rate=0.9, min_ago_open=200, min_ago_close=120, profit_rate=0.9,
)) ))
@ -265,14 +265,14 @@ def test_LowProfitPairs(mocker, default_conf, fee, caplog):
# Add positive trade # Add positive trade
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value, 'XRP/BTC', fee.return_value, False, exit_reason=SellType.ROI.value,
min_ago_open=20, min_ago_close=10, profit_rate=1.15, min_ago_open=20, min_ago_close=10, profit_rate=1.15,
)) ))
assert not freqtrade.protections.stop_per_pair('XRP/BTC') assert not freqtrade.protections.stop_per_pair('XRP/BTC')
assert not PairLocks.is_pair_locked('XRP/BTC') assert not PairLocks.is_pair_locked('XRP/BTC')
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, 'XRP/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=110, min_ago_close=20, profit_rate=0.8, min_ago_open=110, min_ago_close=20, profit_rate=0.8,
)) ))
@ -300,15 +300,15 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
caplog.clear() caplog.clear()
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, 'XRP/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=1000, min_ago_close=900, profit_rate=1.1, min_ago_open=1000, min_ago_close=900, profit_rate=1.1,
)) ))
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
'ETH/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, 'ETH/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=1000, min_ago_close=900, profit_rate=1.1, min_ago_open=1000, min_ago_close=900, profit_rate=1.1,
)) ))
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
'NEO/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, 'NEO/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=1000, min_ago_close=900, profit_rate=1.1, min_ago_open=1000, min_ago_close=900, profit_rate=1.1,
)) ))
# No losing trade yet ... so max_drawdown will raise exception # No losing trade yet ... so max_drawdown will raise exception
@ -316,7 +316,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
assert not freqtrade.protections.stop_per_pair('XRP/BTC') assert not freqtrade.protections.stop_per_pair('XRP/BTC')
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, 'XRP/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=500, min_ago_close=400, profit_rate=0.9, min_ago_open=500, min_ago_close=400, profit_rate=0.9,
)) ))
# Not locked with one trade # Not locked with one trade
@ -326,7 +326,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
assert not PairLocks.is_global_lock() assert not PairLocks.is_global_lock()
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.STOP_LOSS.value, 'XRP/BTC', fee.return_value, False, exit_reason=SellType.STOP_LOSS.value,
min_ago_open=1200, min_ago_close=1100, profit_rate=0.5, min_ago_open=1200, min_ago_close=1100, profit_rate=0.5,
)) ))
@ -339,7 +339,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
# Winning trade ... (should not lock, does not change drawdown!) # Winning trade ... (should not lock, does not change drawdown!)
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value, 'XRP/BTC', fee.return_value, False, exit_reason=SellType.ROI.value,
min_ago_open=320, min_ago_close=410, profit_rate=1.5, min_ago_open=320, min_ago_close=410, profit_rate=1.5,
)) ))
assert not freqtrade.protections.global_stop() assert not freqtrade.protections.global_stop()
@ -349,7 +349,7 @@ def test_MaxDrawdown(mocker, default_conf, fee, caplog):
# Add additional negative trade, causing a loss of > 15% # Add additional negative trade, causing a loss of > 15%
Trade.query.session.add(generate_mock_trade( Trade.query.session.add(generate_mock_trade(
'XRP/BTC', fee.return_value, False, sell_reason=SellType.ROI.value, 'XRP/BTC', fee.return_value, False, exit_reason=SellType.ROI.value,
min_ago_open=20, min_ago_close=10, profit_rate=0.8, min_ago_open=20, min_ago_close=10, profit_rate=0.8,
)) ))
assert not freqtrade.protections.stop_per_pair('XRP/BTC') assert not freqtrade.protections.stop_per_pair('XRP/BTC')

View File

@ -64,7 +64,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'open_rate_requested': ANY, 'open_rate_requested': ANY,
'open_trade_value': 0.0010025, 'open_trade_value': 0.0010025,
'close_rate_requested': ANY, 'close_rate_requested': ANY,
'sell_reason': ANY, 'exit_reason': ANY,
'exit_order_status': ANY, 'exit_order_status': ANY,
'min_rate': ANY, 'min_rate': ANY,
'max_rate': ANY, 'max_rate': ANY,
@ -138,7 +138,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'open_rate_requested': ANY, 'open_rate_requested': ANY,
'open_trade_value': ANY, 'open_trade_value': ANY,
'close_rate_requested': ANY, 'close_rate_requested': ANY,
'sell_reason': ANY, 'exit_reason': ANY,
'exit_order_status': ANY, 'exit_order_status': ANY,
'min_rate': ANY, 'min_rate': ANY,
'max_rate': ANY, 'max_rate': ANY,
@ -916,7 +916,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(
@ -943,23 +943,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',
@ -970,21 +970,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)
@ -1023,7 +1023,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

@ -816,14 +816,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']
@ -955,7 +955,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
'open_order_id': open_order_id, 'open_order_id': open_order_id,
'open_rate_requested': ANY, 'open_rate_requested': ANY,
'open_trade_value': open_trade_value, 'open_trade_value': open_trade_value,
'sell_reason': None, 'exit_reason': None,
'exit_order_status': None, 'exit_order_status': None,
'strategy': CURRENT_TEST_STRATEGY, 'strategy': CURRENT_TEST_STRATEGY,
'buy_tag': None, 'buy_tag': None,
@ -1146,7 +1146,7 @@ def test_api_forcebuy(botclient, mocker, fee):
'open_order_id': '123456', 'open_order_id': '123456',
'open_rate_requested': None, 'open_rate_requested': None,
'open_trade_value': 0.24605460, 'open_trade_value': 0.24605460,
'sell_reason': None, 'exit_reason': None,
'exit_order_status': None, 'exit_order_status': None,
'strategy': CURRENT_TEST_STRATEGY, 'strategy': CURRENT_TEST_STRATEGY,
'buy_tag': None, 'buy_tag': None,

View File

@ -962,7 +962,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'buy_tag': ANY, 'buy_tag': ANY,
'enter_tag': ANY, 'enter_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value, 'exit_reason': SellType.FORCE_SELL.value,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
@ -1030,7 +1030,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'buy_tag': ANY, 'buy_tag': ANY,
'enter_tag': ANY, 'enter_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value, 'exit_reason': SellType.FORCE_SELL.value,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
@ -1088,7 +1088,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'buy_tag': ANY, 'buy_tag': ANY,
'enter_tag': ANY, 'enter_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value, 'exit_reason': SellType.FORCE_SELL.value,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
@ -1276,7 +1276,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',
@ -1294,26 +1294,26 @@ def test_telegram_sell_reason_performance_handle(default_conf, update, ticker, f
# Simulate fulfilled LIMIT_BUY order for trade # Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_buy_order) trade.update(limit_buy_order)
trade.sell_reason = 'TESTSELL' trade.exit_reason = 'TESTSELL'
# Simulate fulfilled LIMIT_SELL order for trade # Simulate fulfilled LIMIT_SELL order for trade
trade.update(limit_sell_order) trade.update(limit_sell_order)
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 'Sell 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]
@ -1338,7 +1338,7 @@ def test_telegram_mix_tag_performance_handle(default_conf, update, ticker, fee,
trade.update(limit_buy_order) trade.update(limit_buy_order)
trade.enter_tag = "TESTBUY" trade.enter_tag = "TESTBUY"
trade.sell_reason = "TESTSELL" trade.exit_reason = "TESTSELL"
# Simulate fulfilled LIMIT_SELL order for trade # Simulate fulfilled LIMIT_SELL order for trade
trade.update(limit_sell_order) trade.update(limit_sell_order)
@ -1826,7 +1826,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': SellType.STOP_LOSS.value, 'exit_reason': SellType.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(),
}) })
@ -1860,7 +1860,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': SellType.STOP_LOSS.value, 'exit_reason': SellType.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(),
}) })
@ -1939,7 +1939,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': SellType.STOP_LOSS.value, 'exit_reason': SellType.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(),
}) })
@ -2063,7 +2063,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': SellType.STOP_LOSS.value, 'exit_reason': SellType.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(),
}) })
@ -2085,13 +2085,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

@ -244,7 +244,7 @@ def test_send_msg_webhook(default_conf, mocker):
'profit_amount': 0.001, 'profit_amount': 0.001,
'profit_ratio': 0.20, 'profit_ratio': 0.20,
'stake_currency': 'BTC', 'stake_currency': 'BTC',
'sell_reason': SellType.STOP_LOSS.value 'exit_reason': SellType.STOP_LOSS.value
} }
webhook.send_msg(msg=msg) webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
@ -269,7 +269,7 @@ def test_send_msg_webhook(default_conf, mocker):
'profit_amount': 0.001, 'profit_amount': 0.001,
'profit_ratio': 0.20, 'profit_ratio': 0.20,
'stake_currency': 'BTC', 'stake_currency': 'BTC',
'sell_reason': SellType.STOP_LOSS.value 'exit_reason': SellType.STOP_LOSS.value
} }
webhook.send_msg(msg=msg) webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
@ -294,7 +294,7 @@ def test_send_msg_webhook(default_conf, mocker):
'profit_amount': 0.001, 'profit_amount': 0.001,
'profit_ratio': 0.20, 'profit_ratio': 0.20,
'stake_currency': 'BTC', 'stake_currency': 'BTC',
'sell_reason': SellType.STOP_LOSS.value 'exit_reason': SellType.STOP_LOSS.value
} }
webhook.send_msg(msg=msg) webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1

View File

@ -46,7 +46,7 @@ def test_strategy_test_v3(result, fee, is_short, side):
current_time=datetime.utcnow(), current_time=datetime.utcnow(),
side=side) is True side=side) is True
assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=trade, order_type='limit', amount=0.1, assert strategy.confirm_trade_exit(pair='ETH/BTC', trade=trade, order_type='limit', amount=0.1,
rate=20000, time_in_force='gtc', sell_reason='roi', rate=20000, time_in_force='gtc', exit_reason='roi',
current_time=datetime.utcnow(), current_time=datetime.utcnow(),
side=side) is True side=side) is True

View File

@ -488,7 +488,7 @@ def test_custom_sell(default_conf, fee, caplog) -> None:
low=None, high=None) low=None, high=None)
assert res.sell_flag is True assert res.sell_flag is True
assert res.sell_type == SellType.CUSTOM_SELL assert res.sell_type == SellType.CUSTOM_SELL
assert res.sell_reason == 'custom_sell' assert res.exit_reason == 'custom_sell'
strategy.custom_sell = MagicMock(return_value='hello world') strategy.custom_sell = MagicMock(return_value='hello world')
@ -497,7 +497,7 @@ def test_custom_sell(default_conf, fee, caplog) -> None:
low=None, high=None) low=None, high=None)
assert res.sell_type == SellType.CUSTOM_SELL assert res.sell_type == SellType.CUSTOM_SELL
assert res.sell_flag is True assert res.sell_flag is True
assert res.sell_reason == 'hello world' assert res.exit_reason == 'hello world'
caplog.clear() caplog.clear()
strategy.custom_sell = MagicMock(return_value='h' * 100) strategy.custom_sell = MagicMock(return_value='h' * 100)
@ -506,7 +506,7 @@ def test_custom_sell(default_conf, fee, caplog) -> None:
low=None, high=None) low=None, high=None)
assert res.sell_type == SellType.CUSTOM_SELL assert res.sell_type == SellType.CUSTOM_SELL
assert res.sell_flag is True assert res.sell_flag is True
assert res.sell_reason == 'h' * 64 assert res.exit_reason == 'h' * 64
assert log_has_re('Custom sell reason returned from custom_sell is too long.*', caplog) assert log_has_re('Custom sell reason returned from custom_sell is too long.*', caplog)
@ -522,7 +522,7 @@ def test_leverage_callback(default_conf, side) -> None:
proposed_leverage=1.0, proposed_leverage=1.0,
max_leverage=5.0, max_leverage=5.0,
side=side, side=side,
) == 1 ) == 1
default_conf['strategy'] = CURRENT_TEST_STRATEGY default_conf['strategy'] = CURRENT_TEST_STRATEGY
strategy = StrategyResolver.load_strategy(default_conf) strategy = StrategyResolver.load_strategy(default_conf)
@ -533,7 +533,7 @@ def test_leverage_callback(default_conf, side) -> None:
proposed_leverage=1.0, proposed_leverage=1.0,
max_leverage=5.0, max_leverage=5.0,
side=side, side=side,
) == 3 ) == 3
def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None: def test_analyze_ticker_default(ohlcv_history, mocker, caplog) -> None:

View File

@ -234,7 +234,7 @@ 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.sell_reason == SellType.STOP_LOSS.value assert trade.exit_reason == SellType.STOP_LOSS.value
def test_total_open_trades_stakes(mocker, default_conf_usdt, ticker_usdt, fee) -> None: def test_total_open_trades_stakes(mocker, default_conf_usdt, ticker_usdt, fee) -> None:
@ -1164,7 +1164,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 == SellType.EMERGENCY_SELL.value assert trade.exit_reason == SellType.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)
@ -1176,7 +1176,7 @@ def test_create_stoploss_order_invalid_order(
# Rpc is sending first buy, then sell # Rpc is sending first buy, then sell
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
assert rpc_mock.call_args_list[1][0][0]['sell_reason'] == SellType.EMERGENCY_SELL.value assert rpc_mock.call_args_list[1][0][0]['exit_reason'] == SellType.EMERGENCY_SELL.value
assert rpc_mock.call_args_list[1][0][0]['order_type'] == 'market' assert rpc_mock.call_args_list[1][0][0]['order_type'] == 'market'
@ -1977,7 +1977,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])
@ -2855,7 +2855,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
freqtrade.execute_trade_exit( freqtrade.execute_trade_exit(
trade=trade, trade=trade,
limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']), limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']),
sell_reason=SellCheckTuple(sell_type=SellType.ROI) exit_reason=SellCheckTuple(sell_type=SellType.ROI)
) )
assert rpc_mock.call_count == 0 assert rpc_mock.call_count == 0
assert freqtrade.strategy.confirm_trade_exit.call_count == 1 assert freqtrade.strategy.confirm_trade_exit.call_count == 1
@ -2867,7 +2867,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
freqtrade.execute_trade_exit( freqtrade.execute_trade_exit(
trade=trade, trade=trade,
limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']), limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']),
sell_reason=SellCheckTuple(sell_type=SellType.ROI) exit_reason=SellCheckTuple(sell_type=SellType.ROI)
) )
assert freqtrade.strategy.confirm_trade_exit.call_count == 1 assert freqtrade.strategy.confirm_trade_exit.call_count == 1
@ -2892,7 +2892,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
'profit_ratio': 0.00493809 if is_short else 0.09451372, 'profit_ratio': 0.00493809 if is_short else 0.09451372,
'stake_currency': 'USDT', 'stake_currency': 'USDT',
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'sell_reason': SellType.ROI.value, 'exit_reason': SellType.ROI.value,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
@ -2928,7 +2928,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
) )
freqtrade.execute_trade_exit( freqtrade.execute_trade_exit(
trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down)()['bid'], trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down)()['bid'],
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) exit_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
last_msg = rpc_mock.call_args_list[-1][0][0] last_msg = rpc_mock.call_args_list[-1][0][0]
@ -2951,7 +2951,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
'profit_ratio': -0.0945681 if is_short else -1.247e-05, 'profit_ratio': -0.0945681 if is_short else -1.247e-05,
'stake_currency': 'USDT', 'stake_currency': 'USDT',
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'sell_reason': SellType.STOP_LOSS.value, 'exit_reason': SellType.STOP_LOSS.value,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
@ -3003,7 +3003,7 @@ def test_execute_trade_exit_custom_exit_price(
freqtrade.execute_trade_exit( freqtrade.execute_trade_exit(
trade=trade, trade=trade,
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'], limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
sell_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL) exit_reason=SellCheckTuple(sell_type=SellType.SELL_SIGNAL)
) )
# Sell price must be different to default bid price # Sell price must be different to default bid price
@ -3031,7 +3031,7 @@ def test_execute_trade_exit_custom_exit_price(
'profit_ratio': profit_ratio, 'profit_ratio': profit_ratio,
'stake_currency': 'USDT', 'stake_currency': 'USDT',
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'sell_reason': SellType.SELL_SIGNAL.value, 'exit_reason': SellType.SELL_SIGNAL.value,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
@ -3074,7 +3074,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
trade.stop_loss = 2.0 * 1.01 if is_short else 2.0 * 0.99 trade.stop_loss = 2.0 * 1.01 if is_short else 2.0 * 0.99
freqtrade.execute_trade_exit( freqtrade.execute_trade_exit(
trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down())['bid'], trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down())['bid'],
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) exit_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
last_msg = rpc_mock.call_args_list[-1][0][0] last_msg = rpc_mock.call_args_list[-1][0][0]
@ -3098,7 +3098,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
'profit_ratio': -0.00501253 if is_short else -0.01493766, 'profit_ratio': -0.00501253 if is_short else -0.01493766,
'stake_currency': 'USDT', 'stake_currency': 'USDT',
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'sell_reason': SellType.STOP_LOSS.value, 'exit_reason': SellType.STOP_LOSS.value,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
@ -3134,7 +3134,7 @@ def test_execute_trade_exit_sloe_cancel_exception(
trade.stoploss_order_id = "abcd" trade.stoploss_order_id = "abcd"
freqtrade.execute_trade_exit(trade=trade, limit=1234, freqtrade.execute_trade_exit(trade=trade, limit=1234,
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)) exit_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
assert create_order_mock.call_count == 2 assert create_order_mock.call_count == 2
assert log_has('Could not cancel stoploss order abcd', caplog) assert log_has('Could not cancel stoploss order abcd', caplog)
@ -3189,7 +3189,7 @@ def test_execute_trade_exit_with_stoploss_on_exchange(
freqtrade.execute_trade_exit( freqtrade.execute_trade_exit(
trade=trade, trade=trade,
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'], limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS) exit_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)
) )
trade = Trade.query.first() trade = Trade.query.first()
@ -3265,7 +3265,7 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf_usdt
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 == SellType.STOPLOSS_ON_EXCHANGE.value assert trade.exit_reason == SellType.STOPLOSS_ON_EXCHANGE.value
assert rpc_mock.call_count == 3 assert rpc_mock.call_count == 3
assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.BUY assert rpc_mock.call_args_list[0][0][0]['type'] == RPCMessageType.BUY
assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.BUY_FILL assert rpc_mock.call_args_list[1][0][0]['type'] == RPCMessageType.BUY_FILL
@ -3328,7 +3328,7 @@ def test_execute_trade_exit_market_order(
freqtrade.execute_trade_exit( freqtrade.execute_trade_exit(
trade=trade, trade=trade,
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'], limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
sell_reason=SellCheckTuple(sell_type=SellType.ROI) exit_reason=SellCheckTuple(sell_type=SellType.ROI)
) )
assert not trade.is_open assert not trade.is_open
@ -3355,7 +3355,7 @@ def test_execute_trade_exit_market_order(
'profit_ratio': profit_ratio, 'profit_ratio': profit_ratio,
'stake_currency': 'USDT', 'stake_currency': 'USDT',
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'sell_reason': SellType.ROI.value, 'exit_reason': SellType.ROI.value,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
@ -3392,11 +3392,11 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u
fetch_ticker=ticker_usdt_sell_up fetch_ticker=ticker_usdt_sell_up
) )
sell_reason = SellCheckTuple(sell_type=SellType.ROI) exit_reason = SellCheckTuple(sell_type=SellType.ROI)
assert not freqtrade.execute_trade_exit( assert not freqtrade.execute_trade_exit(
trade=trade, trade=trade,
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'], limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
sell_reason=sell_reason exit_reason=exit_reason
) )
assert mock_insuf.call_count == 1 assert mock_insuf.call_count == 1
@ -3561,7 +3561,7 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee,
freqtrade.execute_trade_exit( freqtrade.execute_trade_exit(
trade=trade, trade=trade,
limit=ticker_usdt_sell_down()['ask' if is_short else 'bid'], limit=ticker_usdt_sell_down()['ask' if is_short else 'bid'],
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS) exit_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS)
) )
trade.close(ticker_usdt_sell_down()['bid']) trade.close(ticker_usdt_sell_down()['bid'])
assert freqtrade.strategy.is_pair_locked(trade.pair) assert freqtrade.strategy.is_pair_locked(trade.pair)
@ -3616,7 +3616,7 @@ def test_ignore_roi_if_enter_signal(default_conf_usdt, limit_order, limit_order_
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 == SellType.ROI.value assert trade.exit_reason == SellType.ROI.value
@pytest.mark.parametrize("is_short,val1,val2", [ @pytest.mark.parametrize("is_short,val1,val2", [
@ -3678,7 +3678,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 == SellType.TRAILING_STOP_LOSS.value assert trade.exit_reason == SellType.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', [
@ -3782,7 +3782,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 == SellType.TRAILING_STOP_LOSS.value assert trade.exit_reason == SellType.TRAILING_STOP_LOSS.value
@pytest.mark.parametrize("is_short", [False, True]) @pytest.mark.parametrize("is_short", [False, True])
@ -3824,7 +3824,7 @@ def test_disable_ignore_roi_if_enter_signal(default_conf_usdt, limit_order, limi
# Test if buy-signal is absent # Test if buy-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 == SellType.ROI.value assert trade.exit_reason == SellType.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,
@ -4920,7 +4920,7 @@ def test_update_funding_fees(
trade=trade, trade=trade,
# The values of the next 2 params are irrelevant for this test # The values of the next 2 params are irrelevant for this test
limit=ticker_usdt_sell_up()['bid'], limit=ticker_usdt_sell_up()['bid'],
sell_reason=SellCheckTuple(sell_type=SellType.ROI) exit_reason=SellCheckTuple(sell_type=SellType.ROI)
) )
assert trade.funding_fees == pytest.approx(sum( assert trade.funding_fees == pytest.approx(sum(
trade.amount * trade.amount *

View File

@ -111,15 +111,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 == SellType.STOPLOSS_ON_EXCHANGE.value assert trade.exit_reason == SellType.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 == SellType.SELL_SIGNAL.value assert trade.exit_reason == SellType.SELL_SIGNAL.value
assert not trade.is_open assert not trade.is_open

View File

@ -1175,7 +1175,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
stop_loss FLOAT, stop_loss FLOAT,
initial_stop_loss FLOAT, initial_stop_loss FLOAT,
max_rate FLOAT, max_rate FLOAT,
sell_reason VARCHAR, exit_reason VARCHAR,
strategy VARCHAR, strategy VARCHAR,
ticker_interval INTEGER, ticker_interval INTEGER,
stoploss_order_id VARCHAR, stoploss_order_id VARCHAR,
@ -1228,7 +1228,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'
@ -1587,7 +1587,7 @@ def test_to_json(default_conf, fee):
'profit_ratio': None, 'profit_ratio': None,
'profit_pct': None, 'profit_pct': None,
'profit_abs': None, 'profit_abs': None,
'sell_reason': None, 'exit_reason': None,
'exit_order_status': None, 'exit_order_status': None,
'stop_loss_abs': None, 'stop_loss_abs': None,
'stop_loss_ratio': None, 'stop_loss_ratio': None,
@ -1672,7 +1672,7 @@ def test_to_json(default_conf, fee):
'open_order_id': None, 'open_order_id': None,
'open_rate_requested': None, 'open_rate_requested': None,
'open_trade_value': 12.33075, 'open_trade_value': 12.33075,
'sell_reason': None, 'exit_reason': None,
'exit_order_status': None, 'exit_order_status': None,
'strategy': None, 'strategy': None,
'buy_tag': 'buys_signal_001', 'buy_tag': 'buys_signal_001',
@ -2126,7 +2126,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

File diff suppressed because one or more lines are too long