update sell_reason to exit_reason
This commit is contained in:
@@ -22,7 +22,7 @@ logger = logging.getLogger(__name__)
|
||||
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'
|
||||
|
@@ -1010,7 +1010,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
|
||||
# We check if stoploss order is fulfilled
|
||||
if stoploss_order and stoploss_order['status'] in ('closed', 'triggered'):
|
||||
trade.sell_reason = ExitType.STOPLOSS_ON_EXCHANGE.value
|
||||
trade.exit_reason = ExitType.STOPLOSS_ON_EXCHANGE.value
|
||||
self.update_trade_state(trade, trade.stoploss_order_id, stoploss_order,
|
||||
stoploss_order=True)
|
||||
# Lock pair for one candle to prevent immediate rebuys
|
||||
@@ -1286,7 +1286,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
trade.close_date = None
|
||||
trade.is_open = True
|
||||
trade.open_order_id = None
|
||||
trade.sell_reason = None
|
||||
trade.exit_reason = None
|
||||
cancelled = True
|
||||
else:
|
||||
# TODO: figure out how to handle partially complete sell orders
|
||||
@@ -1416,7 +1416,7 @@ class FreqtradeBot(LoggingMixin):
|
||||
trade.open_order_id = order['id']
|
||||
trade.sell_order_status = ''
|
||||
trade.close_rate_requested = limit
|
||||
trade.sell_reason = exit_tag or exit_check.exit_reason
|
||||
trade.exit_reason = exit_tag or exit_check.sell_reason
|
||||
|
||||
# Lock pair for one candle to prevent immediate re-trading
|
||||
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
|
||||
@@ -1461,7 +1461,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
'profit_ratio': profit_ratio,
|
||||
'buy_tag': trade.enter_tag,
|
||||
'enter_tag': trade.enter_tag,
|
||||
'sell_reason': trade.sell_reason,
|
||||
'sell_reason': trade.exit_reason, # Deprecated
|
||||
'exit_reason': trade.exit_reason,
|
||||
'open_date': trade.open_date,
|
||||
'close_date': trade.close_date or datetime.utcnow(),
|
||||
'stake_currency': self.config['stake_currency'],
|
||||
@@ -1509,7 +1510,8 @@ class FreqtradeBot(LoggingMixin):
|
||||
'profit_ratio': profit_ratio,
|
||||
'buy_tag': trade.enter_tag,
|
||||
'enter_tag': trade.enter_tag,
|
||||
'sell_reason': trade.sell_reason,
|
||||
'sell_reason': trade.exit_reason, # Deprecated
|
||||
'exit_reason': trade.exit_reason,
|
||||
'open_date': trade.open_date,
|
||||
'close_date': trade.close_date or datetime.now(timezone.utc),
|
||||
'stake_currency': self.config['stake_currency'],
|
||||
|
@@ -555,7 +555,7 @@ class Backtesting:
|
||||
current_time=sell_candle_time):
|
||||
return None
|
||||
|
||||
trade.sell_reason = sell.exit_reason
|
||||
trade.exit_reason = sell.exit_reason
|
||||
|
||||
# Checks and adds an exit tag, after checking that the length of the
|
||||
# sell_row has the length for an exit tag column
|
||||
@@ -564,7 +564,7 @@ class Backtesting:
|
||||
and sell_row[EXIT_TAG_IDX] is not None
|
||||
and len(sell_row[EXIT_TAG_IDX]) > 0
|
||||
):
|
||||
trade.sell_reason = sell_row[EXIT_TAG_IDX]
|
||||
trade.exit_reason = sell_row[EXIT_TAG_IDX]
|
||||
|
||||
self.order_id_counter += 1
|
||||
order = Order(
|
||||
@@ -810,7 +810,7 @@ class Backtesting:
|
||||
sell_row = data[pair][-1]
|
||||
|
||||
trade.close_date = sell_row[DATE_IDX].to_pydatetime()
|
||||
trade.sell_reason = ExitType.FORCE_SELL.value
|
||||
trade.exit_reason = ExitType.FORCE_SELL.value
|
||||
trade.close(sell_row[OPEN_IDX], show_msg=False)
|
||||
LocalTrade.close_bt_trade(trade)
|
||||
# Deepcopy object to have wallets update correctly
|
||||
|
@@ -166,7 +166,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
|
||||
@@ -175,8 +175,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()
|
||||
@@ -184,7 +184,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]),
|
||||
@@ -382,7 +382,7 @@ def generate_strategy_stats(pairlist: List[str],
|
||||
enter_tag_results = generate_tag_metrics("enter_tag", starting_balance=start_balance,
|
||||
results=results, skip_nan=False)
|
||||
|
||||
exit_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades,
|
||||
exit_reason_stats = generate_exit_reason_stats(max_open_trades=max_open_trades,
|
||||
results=results)
|
||||
left_open_results = generate_pair_metrics(pairlist, stake_currency=stake_currency,
|
||||
starting_balance=start_balance,
|
||||
@@ -406,7 +406,7 @@ def generate_strategy_stats(pairlist: List[str],
|
||||
'worst_pair': worst_pair,
|
||||
'results_per_pair': pair_results,
|
||||
'results_per_enter_tag': enter_tag_results,
|
||||
'sell_reason_summary': exit_reason_stats,
|
||||
'exit_reason_summary': exit_reason_stats,
|
||||
'left_open_trades': left_open_results,
|
||||
# 'days_breakdown_stats': days_breakdown_stats,
|
||||
|
||||
@@ -572,7 +572,7 @@ def text_table_bt_results(pair_results: List[Dict[str, Any]], stake_currency: st
|
||||
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right")
|
||||
|
||||
|
||||
def text_table_exit_reason(sell_reason_stats: List[Dict[str, Any]], stake_currency: str) -> str:
|
||||
def text_table_exit_reason(exit_reason_stats: List[Dict[str, Any]], stake_currency: str) -> str:
|
||||
"""
|
||||
Generate small table outlining Backtest results
|
||||
:param sell_reason_stats: Exit reason metrics
|
||||
@@ -590,12 +590,12 @@ def text_table_exit_reason(sell_reason_stats: List[Dict[str, Any]], stake_curren
|
||||
]
|
||||
|
||||
output = [[
|
||||
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")
|
||||
|
||||
|
||||
@@ -813,7 +813,7 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
|
||||
print(' ENTER TAG STATS '.center(len(table.splitlines()[0]), '='))
|
||||
print(table)
|
||||
|
||||
table = text_table_exit_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(' EXIT REASON STATS '.center(len(table.splitlines()[0]), '='))
|
||||
|
@@ -74,7 +74,7 @@ def migrate_trades_and_orders_table(
|
||||
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, 'sell_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'))
|
||||
|
||||
@@ -136,7 +136,7 @@ def migrate_trades_and_orders_table(
|
||||
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, sell_order_status, strategy, enter_tag,
|
||||
max_rate, min_rate, exit_reason, sell_order_status, strategy, enter_tag,
|
||||
timeframe, open_trade_value, close_profit_abs,
|
||||
trading_mode, leverage, liquidation_price, is_short,
|
||||
interest_rate, funding_fees
|
||||
@@ -152,7 +152,7 @@ def migrate_trades_and_orders_table(
|
||||
{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,
|
||||
{sell_order_status} sell_order_status,
|
||||
{strategy} strategy, {enter_tag} enter_tag, {timeframe} timeframe,
|
||||
{open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs,
|
||||
|
@@ -316,7 +316,7 @@ class LocalTrade():
|
||||
max_rate: float = 0.0
|
||||
# Lowest price reached
|
||||
min_rate: float = 0.0
|
||||
sell_reason: str = ''
|
||||
exit_reason: str = ''
|
||||
sell_order_status: str = ''
|
||||
strategy: str = ''
|
||||
enter_tag: Optional[str] = None
|
||||
@@ -459,7 +459,8 @@ class LocalTrade():
|
||||
'profit_pct': round(self.close_profit * 100, 2) if self.close_profit else None,
|
||||
'profit_abs': self.close_profit_abs,
|
||||
|
||||
'sell_reason': self.sell_reason,
|
||||
'sell_reason': self.exit_reason, # Deprecated
|
||||
'exit_reason': self.exit_reason,
|
||||
'sell_order_status': self.sell_order_status,
|
||||
'stop_loss_abs': self.stop_loss,
|
||||
'stop_loss_ratio': self.stop_loss_pct if self.stop_loss_pct else None,
|
||||
@@ -618,7 +619,7 @@ class LocalTrade():
|
||||
elif order.ft_order_side == 'stoploss':
|
||||
self.stoploss_order_id = None
|
||||
self.close_rate_requested = self.stop_loss
|
||||
self.sell_reason = ExitType.STOPLOSS_ON_EXCHANGE.value
|
||||
self.exit_reason = ExitType.STOPLOSS_ON_EXCHANGE.value
|
||||
if self.is_open:
|
||||
logger.info(f'{order.order_type.upper()} is hit for {self}.')
|
||||
self.close(order.safe_price)
|
||||
@@ -947,6 +948,12 @@ class LocalTrade():
|
||||
"""
|
||||
return len(self.select_filled_orders('sell'))
|
||||
|
||||
@property
|
||||
def sell_reason(self) -> str:
|
||||
""" DEPRECATED! Please use exit_reason instead."""
|
||||
return self.exit_reason
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_trades_proxy(*, pair: str = None, is_open: bool = None,
|
||||
open_date: datetime = None, close_date: datetime = None,
|
||||
@@ -1076,7 +1083,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)
|
||||
sell_order_status = Column(String(100), nullable=True)
|
||||
strategy = Column(String(100), nullable=True)
|
||||
enter_tag = Column(String(100), nullable=True)
|
||||
@@ -1295,12 +1302,12 @@ 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()
|
||||
|
||||
@@ -1330,7 +1337,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')
|
||||
|
@@ -53,7 +53,7 @@ class StoplossGuard(IProtection):
|
||||
# trades = Trade.get_trades(filters).all()
|
||||
|
||||
trades1 = Trade.get_trades_proxy(pair=pair, is_open=False, close_date=look_back_until)
|
||||
trades = [trade for trade in trades1 if (str(trade.sell_reason) in (
|
||||
trades = [trade for trade in trades1 if (str(trade.exit_reason) in (
|
||||
ExitType.TRAILING_STOP_LOSS.value, ExitType.STOP_LOSS.value,
|
||||
ExitType.STOPLOSS_ON_EXCHANGE.value)
|
||||
and trade.close_profit and trade.close_profit < 0)]
|
||||
|
@@ -113,7 +113,7 @@ class SellReason(BaseModel):
|
||||
|
||||
|
||||
class Stats(BaseModel):
|
||||
sell_reasons: Dict[str, SellReason]
|
||||
exit_reasons: Dict[str, SellReason]
|
||||
durations: Dict[str, Optional[float]]
|
||||
|
||||
|
||||
@@ -236,6 +236,7 @@ class TradeSchema(BaseModel):
|
||||
profit_abs: Optional[float]
|
||||
profit_fiat: Optional[float]
|
||||
sell_reason: Optional[str]
|
||||
exit_reason: Optional[str]
|
||||
sell_order_status: Optional[str]
|
||||
stop_loss_abs: Optional[float]
|
||||
stop_loss_ratio: Optional[float]
|
||||
|
@@ -428,13 +428,13 @@ class RPC:
|
||||
return 'losses'
|
||||
else:
|
||||
return 'draws'
|
||||
trades = trades = Trade.get_trades([Trade.is_open.is_(False)])
|
||||
trades: List[Trade] = Trade.get_trades([Trade.is_open.is_(False)])
|
||||
# Sell reason
|
||||
sell_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': []}
|
||||
@@ -448,7 +448,7 @@ class RPC:
|
||||
losses_dur = sum(dur['losses']) / len(dur['losses']) if len(dur['losses']) > 0 else None
|
||||
|
||||
durations = {'wins': wins_dur, 'draws': draws_dur, 'losses': losses_dur}
|
||||
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,
|
||||
|
@@ -290,7 +290,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']}"
|
||||
@@ -361,7 +361,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)
|
||||
@@ -384,7 +384,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}"
|
||||
@@ -466,7 +466,7 @@ class Telegram(RPCHandler):
|
||||
for r in results:
|
||||
r['open_date_hum'] = arrow.get(r['open_date']).humanize()
|
||||
r['num_entries'] = len([o for o in r['orders'] if o['ft_is_entry']])
|
||||
r['sell_reason'] = r.get('sell_reason', "")
|
||||
r['exit_reason'] = r.get('exit_reason', "")
|
||||
lines = [
|
||||
"*Trade ID:* `{trade_id}`" +
|
||||
("` (since {open_date_hum})`" if r['is_open'] else ""),
|
||||
@@ -475,7 +475,7 @@ class Telegram(RPCHandler):
|
||||
"*Leverage:* `{leverage}`" if r.get('leverage') else "",
|
||||
"*Amount:* `{amount} ({stake_amount} {base_currency})`",
|
||||
"*Enter Tag:* `{enter_tag}`" if r['enter_tag'] else "",
|
||||
"*Exit Reason:* `{sell_reason}`" if r['sell_reason'] else "",
|
||||
"*Exit Reason:* `{exit_reason}`" if r['exit_reason'] else "",
|
||||
]
|
||||
|
||||
if position_adjust:
|
||||
@@ -771,23 +771,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']
|
||||
headers=['Exit Reason', 'Exits', '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(
|
||||
@@ -799,7 +799,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)
|
||||
|
||||
|
Reference in New Issue
Block a user