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

@@ -17,18 +17,18 @@ logger = logging.getLogger(__name__)
# Old format - maybe remove?
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
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']
# Newest format
BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
'open_rate', 'close_rate',
'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',
'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'enter_tag',
'is_short'

View File

@@ -914,7 +914,7 @@ class FreqtradeBot(LoggingMixin):
trade.stoploss_order_id = None
logger.error(f'Unable to place a stoploss order on exchange. {e}')
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))
except ExchangeError:
@@ -947,7 +947,7 @@ class FreqtradeBot(LoggingMixin):
# We check if stoploss order is fulfilled
if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'):
# 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,
stoploss_order=True)
# Lock pair for one candle to prevent immediate rebuys
@@ -1102,7 +1102,7 @@ class FreqtradeBot(LoggingMixin):
try:
self.execute_trade_exit(
trade, order.get('price'),
sell_reason=SellCheckTuple(sell_type=SellType.EMERGENCY_SELL))
exit_reason=SellCheckTuple(sell_type=SellType.EMERGENCY_SELL))
except DependencyException as exception:
logger.warning(
f'Unable to emergency sell trade {trade.pair}: {exception}')
@@ -1266,7 +1266,7 @@ class FreqtradeBot(LoggingMixin):
self,
trade: Trade,
limit: float,
sell_reason: SellCheckTuple,
exit_reason: SellCheckTuple,
*,
exit_tag: Optional[str] = None,
ordertype: Optional[str] = None,
@@ -1275,7 +1275,7 @@ class FreqtradeBot(LoggingMixin):
Executes a trade exit for the given trade and limit
:param trade: Trade instance
: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)
"""
trade.funding_fees = self.exchange.get_funding_fees(
@@ -1284,7 +1284,7 @@ class FreqtradeBot(LoggingMixin):
trade.open_date
)
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'
# 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}")
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!)
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)(
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
logger.info(f"User requested abortion of exiting {trade.pair}")
return False
@@ -1350,7 +1350,7 @@ class FreqtradeBot(LoggingMixin):
trade.open_order_id = order['id']
trade.exit_order_status = ''
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
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
@@ -1395,7 +1395,7 @@ class FreqtradeBot(LoggingMixin):
'profit_ratio': profit_ratio,
'buy_tag': trade.enter_tag,
'enter_tag': trade.enter_tag,
'sell_reason': trade.sell_reason,
'exit_reason': trade.exit_reason,
'open_date': trade.open_date,
'close_date': trade.close_date or datetime.utcnow(),
'stake_currency': self.config['stake_currency'],
@@ -1442,7 +1442,7 @@ class FreqtradeBot(LoggingMixin):
'profit_ratio': profit_ratio,
'buy_tag': trade.enter_tag,
'enter_tag': trade.enter_tag,
'sell_reason': trade.sell_reason,
'exit_reason': trade.exit_reason,
'open_date': trade.open_date,
'close_date': trade.close_date or datetime.now(timezone.utc),
'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,
rate=closerate,
time_in_force=time_in_force,
sell_reason=sell.sell_reason,
exit_reason=sell.exit_reason,
current_time=sell_candle_time):
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
# 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 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)
return trade
@@ -542,7 +542,7 @@ class Backtesting:
sell_row = data[pair][-1]
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)
LocalTrade.close_bt_trade(trade)
# Deepcopy object to have wallets update correctly

View File

@@ -159,7 +159,7 @@ def generate_tag_metrics(tag_type: str,
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
: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 = []
for reason, count in results['sell_reason'].value_counts().iteritems():
result = results.loc[results['sell_reason'] == reason]
for reason, count in results['exit_reason'].value_counts().iteritems():
result = results.loc[results['exit_reason'] == reason]
profit_mean = result['profit_ratio'].mean()
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(
{
'sell_reason': reason,
'exit_reason': reason,
'trades': count,
'wins': 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,
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)
left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
starting_balance=start_balance,
@@ -407,7 +407,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
'worst_pair': worst_pair,
'results_per_pair': pair_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,
# '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")
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
:param sell_reason_stats: Sell reason metrics
:param exit_reason_stats: Sell reason metrics
:param stake_currency: Stakecurrency used
: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 = [[
t['sell_reason'], t['trades'],
t['exit_reason'], t['trades'],
_generate_wins_draws_losses(t['wins'], t['draws'], t['losses']),
t['profit_mean_pct'], t['profit_sum_pct'],
round_coin_value(t['profit_total_abs'], stake_currency, False),
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")
@@ -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(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)
if isinstance(table, str) and len(table) > 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')
max_rate = get_column_def(cols, 'max_rate', '0.0')
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')
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,
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
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,
trading_mode, leverage, isolated_liq, is_short,
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_pct} initial_stop_loss_pct,
{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,
{strategy} strategy, {enter_tag} enter_tag, {timeframe} timeframe,
{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
# Lowest price reached
min_rate: float = 0.0
sell_reason: str = ''
exit_reason: str = ''
exit_order_status: str = ''
strategy: str = ''
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_abs': self.close_profit_abs,
'sell_reason': self.sell_reason,
'exit_reason': self.exit_reason,
'exit_order_status': self.exit_order_status,
'stop_loss_abs': self.stop_loss,
'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'):
self.stoploss_order_id = None
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:
logger.info(f'{order_type.upper()} is hit for {self}.')
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)
# Lowest price reached
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)
strategy = Column(String(100), nullable=True)
enter_tag = Column(String(100), nullable=True)
@@ -1143,7 +1143,7 @@ class Trade(_DECL_BASE, LocalTrade):
]
@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
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)
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_abs).label('profit_sum_abs'),
func.count(Trade.pair).label('count')
).filter(*filters)\
.group_by(Trade.sell_reason) \
.group_by(Trade.exit_reason) \
.order_by(desc('profit_sum_abs')) \
.all()
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_pct': round(profit * 100, 2),
'profit_abs': profit_abs,
'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
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
NOTE: Not supported in Backtesting.
"""
@@ -1190,7 +1190,7 @@ class Trade(_DECL_BASE, LocalTrade):
mix_tag_perf = Trade.query.with_entities(
Trade.id,
Trade.enter_tag,
Trade.sell_reason,
Trade.exit_reason,
func.sum(Trade.close_profit).label('profit_sum'),
func.sum(Trade.close_profit_abs).label('profit_sum_abs'),
func.count(Trade.pair).label('count')
@@ -1200,12 +1200,12 @@ class Trade(_DECL_BASE, LocalTrade):
.all()
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"
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):
mix_tag = enter_tag + " " + sell_reason
if(exit_reason is not None and enter_tag is not None):
mix_tag = enter_tag + " " + exit_reason
i = 0
if not any(item["mix_tag"] == mix_tag for item in return_list):
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:
# Create description for sell summarizing the trade
trades['desc'] = trades.apply(lambda row: f"{row['profit_ratio']:.2%}, "
f"{row['sell_reason']}, "
f"{row['exit_reason']}, "
f"{row['trade_duration']} min",
axis=1)
trade_buys = go.Scatter(
@@ -535,7 +535,7 @@ def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame],
"Profit per pair",
"Parallelism",
"Underwater",
])
])
fig['layout'].update(title="Freqtrade Profit plot")
fig['layout']['yaxis1'].update(title='Price')
fig['layout']['yaxis2'].update(title=f'Profit {stake_currency}')

View File

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

View File

@@ -108,7 +108,7 @@ class SellReason(BaseModel):
class Stats(BaseModel):
sell_reasons: Dict[str, SellReason]
exit_reasons: Dict[str, SellReason]
durations: Dict[str, Union[str, float]]
@@ -212,7 +212,7 @@ class TradeSchema(BaseModel):
profit_pct: Optional[float]
profit_abs: Optional[float]
profit_fiat: Optional[float]
sell_reason: Optional[str]
exit_reason: Optional[str]
exit_order_status: Optional[str]
stop_loss_abs: Optional[float]
stop_loss_ratio: Optional[float]

View File

@@ -416,11 +416,11 @@ class RPC:
return 'draws'
trades = trades = Trade.get_trades([Trade.is_open.is_(False)])
# Sell reason
sell_reasons = {}
exit_reasons = {}
for trade in trades:
if trade.sell_reason not in sell_reasons:
sell_reasons[trade.sell_reason] = {'wins': 0, 'losses': 0, 'draws': 0}
sell_reasons[trade.sell_reason][trade_win_loss(trade)] += 1
if trade.exit_reason not in exit_reasons:
exit_reasons[trade.exit_reason] = {'wins': 0, 'losses': 0, 'draws': 0}
exit_reasons[trade.exit_reason][trade_win_loss(trade)] += 1
# Duration
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'
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(
self, stake_currency: str, fiat_display_currency: str,
@@ -672,12 +672,12 @@ class RPC:
closing_side = "buy" if trade.is_short else "sell"
current_rate = self._freqtrade.exchange.get_rate(
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(
"forcesell", self._freqtrade.strategy.order_types["sell"])
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 ----
if self._freqtrade.state != State.RUNNING:
@@ -799,16 +799,16 @@ class RPC:
"""
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.
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]]:
"""
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
"""
mix_tags = Trade.get_mix_tag_performance(pair)
@@ -872,7 +872,7 @@ class RPC:
else:
errors[pair] = {
'error_msg': f"Pair {pair} is not in the current blacklist."
}
}
resp = self._rpc_blacklist()
resp['errors'] = errors
return resp

View File

@@ -156,7 +156,7 @@ class Telegram(RPCHandler):
CommandHandler('delete', self._delete_trade),
CommandHandler('performance', self._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('stats', self._stats),
CommandHandler('daily', self._daily),
@@ -186,8 +186,8 @@ class Telegram(RPCHandler):
CallbackQueryHandler(self._performance, pattern='update_performance'),
CallbackQueryHandler(self._enter_tag_performance,
pattern='update_enter_tag_performance'),
CallbackQueryHandler(self._sell_reason_performance,
pattern='update_sell_reason_performance'),
CallbackQueryHandler(self._exit_reason_performance,
pattern='update_exit_reason_performance'),
CallbackQueryHandler(self._mix_tag_performance, pattern='update_mix_tag_performance'),
CallbackQueryHandler(self._count, pattern='update_count'),
CallbackQueryHandler(self._forcebuy_inline),
@@ -284,7 +284,7 @@ class Telegram(RPCHandler):
f"*{'Profit' if is_fill else 'Unrealized Profit'}:* "
f"`{msg['profit_ratio']:.2%}{msg['profit_extra']}`\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"*Direction:* `{msg['direction']}`\n"
f"{msg['leverage_text']}"
@@ -355,7 +355,7 @@ class Telegram(RPCHandler):
if isinstance(sell_noti, str):
noti = sell_noti
else:
noti = sell_noti.get(str(msg['sell_reason']), default_noti)
noti = sell_noti.get(str(msg['exit_reason']), default_noti)
else:
noti = self._config['telegram'] \
.get('notification_settings', {}).get(str(msg_type), default_noti)
@@ -378,7 +378,7 @@ class Telegram(RPCHandler):
return "\N{ROCKET}"
elif float(msg['profit_percent']) >= 0.0:
return "\N{EIGHT SPOKED ASTERISK}"
elif msg['sell_reason'] == "stop_loss":
elif msg['exit_reason'] == "stop_loss":
return "\N{WARNING SIGN}"
else:
return "\N{CROSS MARK}"
@@ -697,23 +697,23 @@ class Telegram(RPCHandler):
'force_sell': 'Forcesell',
'emergency_sell': 'Emergency Sell',
}
sell_reasons_tabulate = [
exit_reasons_tabulate = [
[
reason_map.get(reason, reason),
sum(count.values()),
count['wins'],
count['losses']
] for reason, count in stats['sell_reasons'].items()
] for reason, count in stats['exit_reasons'].items()
]
sell_reasons_msg = 'No trades yet.'
for reason in chunks(sell_reasons_tabulate, 25):
sell_reasons_msg = tabulate(
exit_reasons_msg = 'No trades yet.'
for reason in chunks(exit_reasons_tabulate, 25):
exit_reasons_msg = tabulate(
reason,
headers=['Sell Reason', 'Sells', 'Wins', 'Losses']
)
if len(sell_reasons_tabulate) > 25:
self._send_msg(sell_reasons_msg, ParseMode.MARKDOWN)
sell_reasons_msg = ''
if len(exit_reasons_tabulate) > 25:
self._send_msg(exit_reasons_msg, ParseMode.MARKDOWN)
exit_reasons_msg = ''
durations = stats['durations']
duration_msg = tabulate(
@@ -725,7 +725,7 @@ class Telegram(RPCHandler):
],
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)
@@ -1026,7 +1026,7 @@ class Telegram(RPCHandler):
self._send_msg(str(e))
@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.
Shows a performance statistic from finished trades
@@ -1039,11 +1039,11 @@ class Telegram(RPCHandler):
if context.args and isinstance(context.args[0], str):
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"
for i, trade in enumerate(trades):
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"({trade['profit_ratio']:.2%}) "
f"({trade['count']})</code>\n")
@@ -1055,7 +1055,7 @@ class Telegram(RPCHandler):
output += stat_line
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)
except RPCException as e:
self._send_msg(str(e))

View File

@@ -35,11 +35,11 @@ class SellCheckTuple:
NamedTuple for Sell type + reason
"""
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_reason = sell_reason or sell_type.value
self.exit_reason = exit_reason or sell_type.value
@property
def sell_flag(self):
@@ -256,7 +256,7 @@ class IStrategy(ABC, HyperStrategyMixin):
return True
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:
"""
Called right before placing a regular exit order.
@@ -273,7 +273,7 @@ class IStrategy(ABC, HyperStrategyMixin):
:param amount: Amount in quote currency.
: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 sell_reason: Exit reason.
:param exit_reason: Exit reason.
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
'sell_signal', 'force_sell', 'emergency_sell']
: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. "
f"sell_type=SellType.{sell_signal.name}" +
(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:
# Exit-signal

View File

@@ -187,7 +187,7 @@
"trades = load_backtest_data(backtest_dir)\n",
"\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",
"\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
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:
"""
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 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 sell_reason: Sell reason.
:param exit_reason: Sell reason.
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
'sell_signal', 'force_sell', 'emergency_sell']
:param current_time: datetime object, containing the current datetime