Fixed flake 8, changed sell_tag to exit_tag and fixed telegram functions
This commit is contained in:
		@@ -30,7 +30,7 @@ BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
 | 
			
		||||
                   'fee_open', 'fee_close', 'trade_duration',
 | 
			
		||||
                   'profit_ratio', 'profit_abs', 'sell_reason',
 | 
			
		||||
                   'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs',
 | 
			
		||||
                   'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_tag', 'sell_tag']
 | 
			
		||||
                   'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_tag', 'exit_tag']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str:
 | 
			
		||||
 
 | 
			
		||||
@@ -14,4 +14,4 @@ class SignalTagType(Enum):
 | 
			
		||||
    Enum for signal columns
 | 
			
		||||
    """
 | 
			
		||||
    BUY_TAG = "buy_tag"
 | 
			
		||||
    SELL_TAG = "sell_tag"
 | 
			
		||||
    EXIT_TAG = "exit_tag"
 | 
			
		||||
 
 | 
			
		||||
@@ -201,11 +201,11 @@ class FreqtradeBot(LoggingMixin):
 | 
			
		||||
        if len(open_trades) != 0:
 | 
			
		||||
            msg = {
 | 
			
		||||
                'type': RPCMessageType.WARNING,
 | 
			
		||||
                'status':  f"{len(open_trades)} open trades active.\n\n"
 | 
			
		||||
                           f"Handle these trades manually on {self.exchange.name}, "
 | 
			
		||||
                           f"or '/start' the bot again and use '/stopbuy' "
 | 
			
		||||
                           f"to handle open trades gracefully. \n"
 | 
			
		||||
                           f"{'Trades are simulated.' if self.config['dry_run'] else ''}",
 | 
			
		||||
                'status': f"{len(open_trades)} open trades active.\n\n"
 | 
			
		||||
                f"Handle these trades manually on {self.exchange.name}, "
 | 
			
		||||
                f"or '/start' the bot again and use '/stopbuy' "
 | 
			
		||||
                f"to handle open trades gracefully. \n"
 | 
			
		||||
                f"{'Trades are simulated.' if self.config['dry_run'] else ''}",
 | 
			
		||||
            }
 | 
			
		||||
            self.rpc.send_msg(msg)
 | 
			
		||||
 | 
			
		||||
@@ -420,7 +420,7 @@ class FreqtradeBot(LoggingMixin):
 | 
			
		||||
            return False
 | 
			
		||||
 | 
			
		||||
        # running get_signal on historical data fetched
 | 
			
		||||
        (buy, sell, buy_tag,sell_tag) = self.strategy.get_signal(
 | 
			
		||||
        (buy, sell, buy_tag, exit_tag) = self.strategy.get_signal(
 | 
			
		||||
            pair,
 | 
			
		||||
            self.strategy.timeframe,
 | 
			
		||||
            analyzed_df
 | 
			
		||||
@@ -700,15 +700,14 @@ class FreqtradeBot(LoggingMixin):
 | 
			
		||||
        logger.debug('Handling %s ...', trade)
 | 
			
		||||
 | 
			
		||||
        (buy, sell) = (False, False)
 | 
			
		||||
 | 
			
		||||
        sell_tag=None
 | 
			
		||||
        exit_tag = None
 | 
			
		||||
 | 
			
		||||
        if (self.config.get('use_sell_signal', True) or
 | 
			
		||||
                self.config.get('ignore_roi_if_buy_signal', False)):
 | 
			
		||||
            analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair,
 | 
			
		||||
                                                                      self.strategy.timeframe)
 | 
			
		||||
 | 
			
		||||
            (buy, sell, buy_tag, sell_tag) = self.strategy.get_signal(
 | 
			
		||||
            (buy, sell, buy_tag, exit_tag) = self.strategy.get_signal(
 | 
			
		||||
                trade.pair,
 | 
			
		||||
                self.strategy.timeframe,
 | 
			
		||||
                analyzed_df
 | 
			
		||||
@@ -716,7 +715,7 @@ class FreqtradeBot(LoggingMixin):
 | 
			
		||||
 | 
			
		||||
        logger.debug('checking sell')
 | 
			
		||||
        sell_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell")
 | 
			
		||||
        if self._check_and_execute_exit(trade, sell_rate, buy, sell, sell_tag):
 | 
			
		||||
        if self._check_and_execute_exit(trade, sell_rate, buy, sell, exit_tag):
 | 
			
		||||
            return True
 | 
			
		||||
 | 
			
		||||
        logger.debug('Found no sell signal for %s.', trade)
 | 
			
		||||
@@ -854,7 +853,7 @@ class FreqtradeBot(LoggingMixin):
 | 
			
		||||
                                   f"for pair {trade.pair}.")
 | 
			
		||||
 | 
			
		||||
    def _check_and_execute_exit(self, trade: Trade, exit_rate: float,
 | 
			
		||||
                                buy: bool, sell: bool, sell_tag:  Optional[str]) -> bool:
 | 
			
		||||
                                buy: bool, sell: bool, exit_tag: Optional[str]) -> bool:
 | 
			
		||||
        """
 | 
			
		||||
        Check and execute exit
 | 
			
		||||
        """
 | 
			
		||||
@@ -865,8 +864,9 @@ class FreqtradeBot(LoggingMixin):
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if should_sell.sell_flag:
 | 
			
		||||
            logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}. Tag: {sell_tag if sell_tag is not None else "None"}')
 | 
			
		||||
            self.execute_trade_exit(trade, exit_rate, should_sell,sell_tag)
 | 
			
		||||
            logger.info(
 | 
			
		||||
                f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}. Tag: {exit_tag if exit_tag is not None else "None"}')
 | 
			
		||||
            self.execute_trade_exit(trade, exit_rate, should_sell, exit_tag)
 | 
			
		||||
            return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
@@ -1067,7 +1067,12 @@ class FreqtradeBot(LoggingMixin):
 | 
			
		||||
            raise DependencyException(
 | 
			
		||||
                f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}")
 | 
			
		||||
 | 
			
		||||
    def execute_trade_exit(self, trade: Trade, limit: float, sell_reason: SellCheckTuple, sell_tag: Optional[str] = None) -> bool:
 | 
			
		||||
    def execute_trade_exit(
 | 
			
		||||
            self,
 | 
			
		||||
            trade: Trade,
 | 
			
		||||
            limit: float,
 | 
			
		||||
            sell_reason: SellCheckTuple,
 | 
			
		||||
            exit_tag: Optional[str] = None) -> bool:
 | 
			
		||||
        """
 | 
			
		||||
        Executes a trade exit for the given trade and limit
 | 
			
		||||
        :param trade: Trade instance
 | 
			
		||||
@@ -1144,8 +1149,8 @@ class FreqtradeBot(LoggingMixin):
 | 
			
		||||
        trade.sell_order_status = ''
 | 
			
		||||
        trade.close_rate_requested = limit
 | 
			
		||||
        trade.sell_reason = sell_reason.sell_reason
 | 
			
		||||
        if(sell_tag is not None):
 | 
			
		||||
            trade.sell_tag = sell_tag
 | 
			
		||||
        if(exit_tag is not None):
 | 
			
		||||
            trade.exit_tag = exit_tag
 | 
			
		||||
        # In case of market sell orders the order can be closed immediately
 | 
			
		||||
        if order.get('status', 'unknown') in ('closed', 'expired'):
 | 
			
		||||
            self.update_trade_state(trade, trade.open_order_id, order)
 | 
			
		||||
@@ -1187,7 +1192,7 @@ class FreqtradeBot(LoggingMixin):
 | 
			
		||||
            'profit_amount': profit_trade,
 | 
			
		||||
            'profit_ratio': profit_ratio,
 | 
			
		||||
            'sell_reason': trade.sell_reason,
 | 
			
		||||
            'sell_tag': trade.sell_tag,
 | 
			
		||||
            'exit_tag': trade.exit_tag,
 | 
			
		||||
            'open_date': trade.open_date,
 | 
			
		||||
            'close_date': trade.close_date or datetime.utcnow(),
 | 
			
		||||
            'stake_currency': self.config['stake_currency'],
 | 
			
		||||
@@ -1231,7 +1236,7 @@ class FreqtradeBot(LoggingMixin):
 | 
			
		||||
            'profit_amount': profit_trade,
 | 
			
		||||
            'profit_ratio': profit_ratio,
 | 
			
		||||
            'sell_reason': trade.sell_reason,
 | 
			
		||||
            'sell_tag': trade.sell_tag,
 | 
			
		||||
            'exit_tag': trade.exit_tag,
 | 
			
		||||
            'open_date': trade.open_date,
 | 
			
		||||
            'close_date': trade.close_date or datetime.now(timezone.utc),
 | 
			
		||||
            'stake_currency': self.config['stake_currency'],
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,8 @@ SELL_IDX = 4
 | 
			
		||||
LOW_IDX = 5
 | 
			
		||||
HIGH_IDX = 6
 | 
			
		||||
BUY_TAG_IDX = 7
 | 
			
		||||
SELL_TAG_IDX = 8
 | 
			
		||||
EXIT_TAG_IDX = 8
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Backtesting:
 | 
			
		||||
    """
 | 
			
		||||
@@ -247,7 +248,7 @@ class Backtesting:
 | 
			
		||||
        """
 | 
			
		||||
        # Every change to this headers list must evaluate further usages of the resulting tuple
 | 
			
		||||
        # and eventually change the constants for indexes at the top
 | 
			
		||||
        headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high', 'buy_tag', 'sell_tag']
 | 
			
		||||
        headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high', 'buy_tag', 'exit_tag']
 | 
			
		||||
        data: Dict = {}
 | 
			
		||||
        self.progress.init_step(BacktestState.CONVERT, len(processed))
 | 
			
		||||
 | 
			
		||||
@@ -259,7 +260,7 @@ class Backtesting:
 | 
			
		||||
                pair_data.loc[:, 'buy'] = 0  # cleanup if buy_signal is exist
 | 
			
		||||
                pair_data.loc[:, 'sell'] = 0  # cleanup if sell_signal is exist
 | 
			
		||||
                pair_data.loc[:, 'buy_tag'] = None  # cleanup if buy_tag is exist
 | 
			
		||||
                pair_data.loc[:, 'sell_tag'] = None  # cleanup if sell_tag is exist
 | 
			
		||||
                pair_data.loc[:, 'exit_tag'] = None  # cleanup if exit_tag is exist
 | 
			
		||||
 | 
			
		||||
            df_analyzed = self.strategy.advise_sell(
 | 
			
		||||
                self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair}).copy()
 | 
			
		||||
@@ -271,7 +272,7 @@ class Backtesting:
 | 
			
		||||
            df_analyzed.loc[:, 'buy'] = df_analyzed.loc[:, 'buy'].shift(1)
 | 
			
		||||
            df_analyzed.loc[:, 'sell'] = df_analyzed.loc[:, 'sell'].shift(1)
 | 
			
		||||
            df_analyzed.loc[:, 'buy_tag'] = df_analyzed.loc[:, 'buy_tag'].shift(1)
 | 
			
		||||
            df_analyzed.loc[:, 'sell_tag'] = df_analyzed.loc[:, 'sell_tag'].shift(1)
 | 
			
		||||
            df_analyzed.loc[:, 'exit_tag'] = df_analyzed.loc[:, 'exit_tag'].shift(1)
 | 
			
		||||
 | 
			
		||||
            # Update dataprovider cache
 | 
			
		||||
            self.dataprovider._set_cached_df(pair, self.timeframe, df_analyzed)
 | 
			
		||||
@@ -359,8 +360,10 @@ class Backtesting:
 | 
			
		||||
 | 
			
		||||
        if sell.sell_flag:
 | 
			
		||||
            trade.close_date = sell_candle_time
 | 
			
		||||
            if(sell_row[SELL_TAG_IDX] is not None):
 | 
			
		||||
                trade.sell_tag = sell_row[SELL_TAG_IDX]
 | 
			
		||||
            if(sell_row[EXIT_TAG_IDX] is not None):
 | 
			
		||||
                trade.exit_tag = sell_row[EXIT_TAG_IDX]
 | 
			
		||||
            else:
 | 
			
		||||
                trade.exit_tag = None
 | 
			
		||||
            trade.sell_reason = sell.sell_reason
 | 
			
		||||
            trade_dur = int((trade.close_date_utc - trade.open_date_utc).total_seconds() // 60)
 | 
			
		||||
            closerate = self._get_close_rate(sell_row, trade, sell, trade_dur)
 | 
			
		||||
@@ -389,7 +392,7 @@ class Backtesting:
 | 
			
		||||
            detail_data = detail_data.loc[
 | 
			
		||||
                (detail_data['date'] >= sell_candle_time) &
 | 
			
		||||
                (detail_data['date'] < sell_candle_end)
 | 
			
		||||
             ].copy()
 | 
			
		||||
            ].copy()
 | 
			
		||||
            if len(detail_data) == 0:
 | 
			
		||||
                # Fall back to "regular" data if no detail data was found for this candle
 | 
			
		||||
                return self._get_sell_trade_entry_for_candle(trade, sell_row)
 | 
			
		||||
@@ -435,7 +438,7 @@ class Backtesting:
 | 
			
		||||
        if stake_amount and (not min_stake_amount or stake_amount > min_stake_amount):
 | 
			
		||||
            # Enter trade
 | 
			
		||||
            has_buy_tag = len(row) >= BUY_TAG_IDX + 1
 | 
			
		||||
            has_sell_tag = len(row) >= SELL_TAG_IDX + 1
 | 
			
		||||
            has_exit_tag = len(row) >= EXIT_TAG_IDX + 1
 | 
			
		||||
            trade = LocalTrade(
 | 
			
		||||
                pair=pair,
 | 
			
		||||
                open_rate=row[OPEN_IDX],
 | 
			
		||||
@@ -446,7 +449,7 @@ class Backtesting:
 | 
			
		||||
                fee_close=self.fee,
 | 
			
		||||
                is_open=True,
 | 
			
		||||
                buy_tag=row[BUY_TAG_IDX] if has_buy_tag else None,
 | 
			
		||||
                sell_tag=row[SELL_TAG_IDX] if has_sell_tag else None,
 | 
			
		||||
                exit_tag=row[EXIT_TAG_IDX] if has_exit_tag else None,
 | 
			
		||||
                exchange='backtesting',
 | 
			
		||||
            )
 | 
			
		||||
            return trade
 | 
			
		||||
 
 | 
			
		||||
@@ -54,6 +54,7 @@ def _get_line_header(first_column: str, stake_currency: str) -> List[str]:
 | 
			
		||||
            f'Tot Profit {stake_currency}', 'Tot Profit %', 'Avg Duration',
 | 
			
		||||
            'Win  Draw  Loss  Win%']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _get_line_header_sell(first_column: str, stake_currency: str) -> List[str]:
 | 
			
		||||
    """
 | 
			
		||||
    Generate header lines (goes in line with _generate_result_line())
 | 
			
		||||
@@ -134,12 +135,13 @@ def generate_pair_metrics(data: Dict[str, Dict], stake_currency: str, starting_b
 | 
			
		||||
    tabular_data.append(_generate_result_line(results, starting_balance, 'TOTAL'))
 | 
			
		||||
    return tabular_data
 | 
			
		||||
 | 
			
		||||
def generate_tag_metrics(tag_type:str, data: Dict[str, Dict], stake_currency: str, starting_balance: int,
 | 
			
		||||
                          results: DataFrame, skip_nan: bool = False) -> List[Dict]:
 | 
			
		||||
 | 
			
		||||
def generate_tag_metrics(tag_type: str,
 | 
			
		||||
                         starting_balance: int,
 | 
			
		||||
                         results: DataFrame,
 | 
			
		||||
                         skip_nan: bool = False) -> List[Dict]:
 | 
			
		||||
    """
 | 
			
		||||
    Generates and returns a list of metrics for the given tag trades and the results dataframe
 | 
			
		||||
    :param data: Dict of <pair: dataframe> containing data that was used during backtesting.
 | 
			
		||||
    :param stake_currency: stake-currency - used to correctly name headers
 | 
			
		||||
    :param starting_balance: Starting balance
 | 
			
		||||
    :param results: Dataframe containing the backtest results
 | 
			
		||||
    :param skip_nan: Print "left open" open trades
 | 
			
		||||
@@ -148,32 +150,6 @@ def generate_tag_metrics(tag_type:str, data: Dict[str, Dict], stake_currency: st
 | 
			
		||||
 | 
			
		||||
    tabular_data = []
 | 
			
		||||
 | 
			
		||||
    # for tag, count in results[tag_type].value_counts().iteritems():
 | 
			
		||||
    #     result = results.loc[results[tag_type] == tag]
 | 
			
		||||
    #
 | 
			
		||||
    #     profit_mean = result['profit_ratio'].mean()
 | 
			
		||||
    #     profit_sum = result['profit_ratio'].sum()
 | 
			
		||||
    #     profit_total = profit_sum / max_open_trades
 | 
			
		||||
    #
 | 
			
		||||
    #     tabular_data.append(
 | 
			
		||||
    #         {
 | 
			
		||||
    #             'sell_reason': tag,
 | 
			
		||||
    #             'trades': count,
 | 
			
		||||
    #             'wins': len(result[result['profit_abs'] > 0]),
 | 
			
		||||
    #             'draws': len(result[result['profit_abs'] == 0]),
 | 
			
		||||
    #             'losses': len(result[result['profit_abs'] < 0]),
 | 
			
		||||
    #             'profit_mean': profit_mean,
 | 
			
		||||
    #             'profit_mean_pct': round(profit_mean * 100, 2),
 | 
			
		||||
    #             'profit_sum': profit_sum,
 | 
			
		||||
    #             'profit_sum_pct': round(profit_sum * 100, 2),
 | 
			
		||||
    #             'profit_total_abs': result['profit_abs'].sum(),
 | 
			
		||||
    #             'profit_total': profit_total,
 | 
			
		||||
    #             'profit_total_pct': round(profit_total * 100, 2),
 | 
			
		||||
    #         }
 | 
			
		||||
    #     )
 | 
			
		||||
    #
 | 
			
		||||
    # tabular_data = []
 | 
			
		||||
 | 
			
		||||
    for tag, count in results[tag_type].value_counts().iteritems():
 | 
			
		||||
        result = results[results[tag_type] == tag]
 | 
			
		||||
        if skip_nan and result['profit_abs'].isnull().all():
 | 
			
		||||
@@ -188,6 +164,7 @@ def generate_tag_metrics(tag_type:str, data: Dict[str, Dict], stake_currency: st
 | 
			
		||||
    tabular_data.append(_generate_result_line(results, starting_balance, 'TOTAL'))
 | 
			
		||||
    return tabular_data
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _generate_tag_result_line(result: DataFrame, starting_balance: int, first_column: str) -> Dict:
 | 
			
		||||
    """
 | 
			
		||||
    Generate one result dict, with "first_column" as key.
 | 
			
		||||
@@ -408,12 +385,10 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
 | 
			
		||||
                                         starting_balance=starting_balance,
 | 
			
		||||
                                         results=results, skip_nan=False)
 | 
			
		||||
 | 
			
		||||
    buy_tag_results = generate_tag_metrics("buy_tag",btdata, stake_currency=stake_currency,
 | 
			
		||||
                                         starting_balance=starting_balance,
 | 
			
		||||
                                         results=results, skip_nan=False)
 | 
			
		||||
    sell_tag_results = generate_tag_metrics("sell_tag",btdata, stake_currency=stake_currency,
 | 
			
		||||
                                         starting_balance=starting_balance,
 | 
			
		||||
                                         results=results, skip_nan=False)
 | 
			
		||||
    buy_tag_results = generate_tag_metrics("buy_tag", starting_balance=starting_balance,
 | 
			
		||||
                                           results=results, skip_nan=False)
 | 
			
		||||
    exit_tag_results = generate_tag_metrics("exit_tag", starting_balance=starting_balance,
 | 
			
		||||
                                            results=results, skip_nan=False)
 | 
			
		||||
 | 
			
		||||
    sell_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades,
 | 
			
		||||
                                                   results=results)
 | 
			
		||||
@@ -439,7 +414,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame],
 | 
			
		||||
        'worst_pair': worst_pair,
 | 
			
		||||
        'results_per_pair': pair_results,
 | 
			
		||||
        'results_per_buy_tag': buy_tag_results,
 | 
			
		||||
        'results_per_sell_tag': sell_tag_results,
 | 
			
		||||
        'results_per_exit_tag': exit_tag_results,
 | 
			
		||||
        'sell_reason_summary': sell_reason_stats,
 | 
			
		||||
        'left_open_trades': left_open_results,
 | 
			
		||||
        'total_trades': len(results),
 | 
			
		||||
@@ -609,30 +584,38 @@ def text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], stake_curren
 | 
			
		||||
    ] for t in sell_reason_stats]
 | 
			
		||||
    return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right")
 | 
			
		||||
 | 
			
		||||
def text_table_tags(tag_type:str, tag_results: List[Dict[str, Any]], stake_currency: str) -> str:
 | 
			
		||||
 | 
			
		||||
def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_currency: str) -> str:
 | 
			
		||||
    """
 | 
			
		||||
    Generates and returns a text table for the given backtest data and the results dataframe
 | 
			
		||||
    :param pair_results: List of Dictionaries - one entry per pair + final TOTAL row
 | 
			
		||||
    :param stake_currency: stake-currency - used to correctly name headers
 | 
			
		||||
    :return: pretty printed table with tabulate as string
 | 
			
		||||
    """
 | 
			
		||||
    if(tag_type=="buy_tag"):
 | 
			
		||||
    if(tag_type == "buy_tag"):
 | 
			
		||||
        headers = _get_line_header("TAG", stake_currency)
 | 
			
		||||
    else:
 | 
			
		||||
        headers = _get_line_header_sell("TAG", stake_currency)
 | 
			
		||||
    floatfmt = _get_line_floatfmt(stake_currency)
 | 
			
		||||
    output = [[
 | 
			
		||||
        t['key'], t['trades'], t['profit_mean_pct'], t['profit_sum_pct'], t['profit_total_abs'],
 | 
			
		||||
        t['profit_total_pct'], t['duration_avg'],
 | 
			
		||||
        _generate_wins_draws_losses(t['wins'], t['draws'], t['losses'])
 | 
			
		||||
    ] for t in tag_results]
 | 
			
		||||
    output = [
 | 
			
		||||
        [
 | 
			
		||||
            t['key'] if t['key'] is not None and len(
 | 
			
		||||
                t['key']) > 0 else "OTHER",
 | 
			
		||||
            t['trades'],
 | 
			
		||||
            t['profit_mean_pct'],
 | 
			
		||||
            t['profit_sum_pct'],
 | 
			
		||||
            t['profit_total_abs'],
 | 
			
		||||
            t['profit_total_pct'],
 | 
			
		||||
            t['duration_avg'],
 | 
			
		||||
            _generate_wins_draws_losses(
 | 
			
		||||
                t['wins'],
 | 
			
		||||
                t['draws'],
 | 
			
		||||
                t['losses'])] for t in tag_results]
 | 
			
		||||
    # Ignore type as floatfmt does allow tuples but mypy does not know that
 | 
			
		||||
    return tabulate(output, headers=headers,
 | 
			
		||||
                    floatfmt=floatfmt, tablefmt="orgtbl", stralign="right")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def text_table_strategy(strategy_results, stake_currency: str) -> str:
 | 
			
		||||
    """
 | 
			
		||||
    Generate summary table per strategy
 | 
			
		||||
@@ -752,14 +735,19 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
 | 
			
		||||
        print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '='))
 | 
			
		||||
    print(table)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    table = text_table_tags("buy_tag", results['results_per_buy_tag'], stake_currency=stake_currency)
 | 
			
		||||
    table = text_table_tags(
 | 
			
		||||
        "buy_tag",
 | 
			
		||||
        results['results_per_buy_tag'],
 | 
			
		||||
        stake_currency=stake_currency)
 | 
			
		||||
 | 
			
		||||
    if isinstance(table, str) and len(table) > 0:
 | 
			
		||||
        print(' BUY TAG STATS '.center(len(table.splitlines()[0]), '='))
 | 
			
		||||
    print(table)
 | 
			
		||||
 | 
			
		||||
    table = text_table_tags("sell_tag",results['results_per_sell_tag'], stake_currency=stake_currency)
 | 
			
		||||
    table = text_table_tags(
 | 
			
		||||
        "exit_tag",
 | 
			
		||||
        results['results_per_exit_tag'],
 | 
			
		||||
        stake_currency=stake_currency)
 | 
			
		||||
 | 
			
		||||
    if isinstance(table, str) and len(table) > 0:
 | 
			
		||||
        print(' SELL TAG STATS '.center(len(table.splitlines()[0]), '='))
 | 
			
		||||
@@ -771,10 +759,6 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
 | 
			
		||||
        print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '='))
 | 
			
		||||
    print(table)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    table = text_table_bt_results(results['left_open_trades'], stake_currency=stake_currency)
 | 
			
		||||
    if isinstance(table, str) and len(table) > 0:
 | 
			
		||||
        print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '='))
 | 
			
		||||
@@ -785,12 +769,9 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
 | 
			
		||||
        print(' SUMMARY METRICS '.center(len(table.splitlines()[0]), '='))
 | 
			
		||||
    print(table)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    if isinstance(table, str) and len(table) > 0:
 | 
			
		||||
        print('=' * len(table.splitlines()[0]))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    print()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
 | 
			
		||||
    sell_reason = get_column_def(cols, 'sell_reason', 'null')
 | 
			
		||||
    strategy = get_column_def(cols, 'strategy', 'null')
 | 
			
		||||
    buy_tag = get_column_def(cols, 'buy_tag', 'null')
 | 
			
		||||
    sell_tag = get_column_def(cols, 'sell_tag', 'null')
 | 
			
		||||
    exit_tag = get_column_def(cols, 'exit_tag', 'null')
 | 
			
		||||
    # If ticker-interval existed use that, else null.
 | 
			
		||||
    if has_column(cols, 'ticker_interval'):
 | 
			
		||||
        timeframe = get_column_def(cols, 'timeframe', 'ticker_interval')
 | 
			
		||||
@@ -83,7 +83,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, sell_order_status, strategy, buy_tag, sell_tag,
 | 
			
		||||
            max_rate, min_rate, sell_reason, sell_order_status, strategy, buy_tag, exit_tag,
 | 
			
		||||
            timeframe, open_trade_value, close_profit_abs
 | 
			
		||||
            )
 | 
			
		||||
        select id, lower(exchange), pair,
 | 
			
		||||
@@ -99,7 +99,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
 | 
			
		||||
            {stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
 | 
			
		||||
            {max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason,
 | 
			
		||||
            {sell_order_status} sell_order_status,
 | 
			
		||||
            {strategy} strategy, {buy_tag} buy_tag, {sell_tag} sell_tag,  {timeframe} timeframe,
 | 
			
		||||
            {strategy} strategy, {buy_tag} buy_tag, {exit_tag} exit_tag,  {timeframe} timeframe,
 | 
			
		||||
            {open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs
 | 
			
		||||
            from {table_back_name}
 | 
			
		||||
            """))
 | 
			
		||||
@@ -158,7 +158,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
 | 
			
		||||
    table_back_name = get_backup_name(tabs, 'trades_bak')
 | 
			
		||||
 | 
			
		||||
    # Check for latest column
 | 
			
		||||
    if not has_column(cols, 'sell_tag'):
 | 
			
		||||
    if not has_column(cols, 'exit_tag'):
 | 
			
		||||
        logger.info(f'Running database migration for trades - backup: {table_back_name}')
 | 
			
		||||
        migrate_trades_table(decl_base, inspector, engine, table_back_name, cols)
 | 
			
		||||
        # Reread columns - the above recreated the table!
 | 
			
		||||
 
 | 
			
		||||
@@ -258,7 +258,7 @@ class LocalTrade():
 | 
			
		||||
    sell_order_status: str = ''
 | 
			
		||||
    strategy: str = ''
 | 
			
		||||
    buy_tag: Optional[str] = None
 | 
			
		||||
    sell_tag: Optional[str] = None
 | 
			
		||||
    exit_tag: Optional[str] = None
 | 
			
		||||
    timeframe: Optional[int] = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, **kwargs):
 | 
			
		||||
@@ -325,8 +325,9 @@ class LocalTrade():
 | 
			
		||||
            'profit_pct': round(self.close_profit * 100, 2) if self.close_profit else None,
 | 
			
		||||
            'profit_abs': self.close_profit_abs,
 | 
			
		||||
 | 
			
		||||
            'sell_reason':  (f' ({self.sell_reason})' if self.sell_reason else ''), #+str(self.sell_reason) ## CHANGE TO BUY TAG IF NEEDED
 | 
			
		||||
            'sell_tag': (f' ({self.sell_tag})' if self.sell_tag else '') ,
 | 
			
		||||
            # +str(self.sell_reason) ## CHANGE TO BUY TAG IF NEEDED
 | 
			
		||||
            'sell_reason': (f' ({self.sell_reason})' if self.sell_reason else ''),
 | 
			
		||||
            'exit_tag': (f' ({self.exit_tag})' if self.exit_tag else ''),
 | 
			
		||||
            '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,
 | 
			
		||||
@@ -708,7 +709,7 @@ class Trade(_DECL_BASE, LocalTrade):
 | 
			
		||||
    sell_order_status = Column(String(100), nullable=True)
 | 
			
		||||
    strategy = Column(String(100), nullable=True)
 | 
			
		||||
    buy_tag = Column(String(100), nullable=True)
 | 
			
		||||
    sell_tag = Column(String(100), nullable=True)
 | 
			
		||||
    exit_tag = Column(String(100), nullable=True)
 | 
			
		||||
    timeframe = Column(Integer, nullable=True)
 | 
			
		||||
 | 
			
		||||
    def __init__(self, **kwargs):
 | 
			
		||||
@@ -873,28 +874,28 @@ class Trade(_DECL_BASE, LocalTrade):
 | 
			
		||||
 | 
			
		||||
        if(pair is not None):
 | 
			
		||||
            tag_perf = Trade.query.with_entities(
 | 
			
		||||
            Trade.buy_tag,
 | 
			
		||||
            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(Trade.is_open.is_(False))\
 | 
			
		||||
            .filter(Trade.pair.lower() == pair.lower()) \
 | 
			
		||||
            .order_by(desc('profit_sum_abs')) \
 | 
			
		||||
            .all()
 | 
			
		||||
                Trade.buy_tag,
 | 
			
		||||
                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(Trade.is_open.is_(False))\
 | 
			
		||||
                .filter(Trade.pair == pair) \
 | 
			
		||||
                .order_by(desc('profit_sum_abs')) \
 | 
			
		||||
                .all()
 | 
			
		||||
        else:
 | 
			
		||||
            tag_perf = Trade.query.with_entities(
 | 
			
		||||
            Trade.buy_tag,
 | 
			
		||||
            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(Trade.is_open.is_(False))\
 | 
			
		||||
            .group_by(Trade.pair) \
 | 
			
		||||
            .order_by(desc('profit_sum_abs')) \
 | 
			
		||||
            .all()
 | 
			
		||||
                Trade.buy_tag,
 | 
			
		||||
                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(Trade.is_open.is_(False))\
 | 
			
		||||
                .group_by(Trade.buy_tag) \
 | 
			
		||||
                .order_by(desc('profit_sum_abs')) \
 | 
			
		||||
                .all()
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            {
 | 
			
		||||
                'buy_tag': buy_tag,
 | 
			
		||||
                'buy_tag': buy_tag if buy_tag is not None else "Other",
 | 
			
		||||
                'profit': profit,
 | 
			
		||||
                'profit_abs': profit_abs,
 | 
			
		||||
                'count': count
 | 
			
		||||
@@ -903,81 +904,102 @@ class Trade(_DECL_BASE, LocalTrade):
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_sell_tag_performance(pair: str) -> List[Dict[str, Any]]:
 | 
			
		||||
    def get_exit_tag_performance(pair: str) -> List[Dict[str, Any]]:
 | 
			
		||||
        """
 | 
			
		||||
        Returns List of dicts containing all Trades, based on sell tag performance
 | 
			
		||||
        Returns List of dicts containing all Trades, based on exit tag performance
 | 
			
		||||
        Can either be average for all pairs or a specific pair provided
 | 
			
		||||
        NOTE: Not supported in Backtesting.
 | 
			
		||||
        """
 | 
			
		||||
        if(pair is not None):
 | 
			
		||||
            tag_perf = Trade.query.with_entities(
 | 
			
		||||
            Trade.sell_tag,
 | 
			
		||||
            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(Trade.is_open.is_(False))\
 | 
			
		||||
            .filter(Trade.pair.lower() == pair.lower()) \
 | 
			
		||||
            .order_by(desc('profit_sum_abs')) \
 | 
			
		||||
            .all()
 | 
			
		||||
                Trade.exit_tag,
 | 
			
		||||
                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(Trade.is_open.is_(False))\
 | 
			
		||||
                .filter(Trade.pair == pair) \
 | 
			
		||||
                .order_by(desc('profit_sum_abs')) \
 | 
			
		||||
                .all()
 | 
			
		||||
        else:
 | 
			
		||||
            tag_perf = Trade.query.with_entities(
 | 
			
		||||
            Trade.sell_tag,
 | 
			
		||||
            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(Trade.is_open.is_(False))\
 | 
			
		||||
            .group_by(Trade.pair) \
 | 
			
		||||
            .order_by(desc('profit_sum_abs')) \
 | 
			
		||||
            .all()
 | 
			
		||||
                Trade.exit_tag,
 | 
			
		||||
                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(Trade.is_open.is_(False))\
 | 
			
		||||
                .group_by(Trade.exit_tag) \
 | 
			
		||||
                .order_by(desc('profit_sum_abs')) \
 | 
			
		||||
                .all()
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            {
 | 
			
		||||
                'sell_tag': sell_tag,
 | 
			
		||||
                'exit_tag': exit_tag if exit_tag is not None else "Other",
 | 
			
		||||
                'profit': profit,
 | 
			
		||||
                'profit_abs': profit_abs,
 | 
			
		||||
                'count': count
 | 
			
		||||
            }
 | 
			
		||||
            for sell_tag, profit, profit_abs, count in tag_perf
 | 
			
		||||
            for exit_tag, profit, profit_abs, count in tag_perf
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_mix_tag_performance(pair: str) -> List[Dict[str, Any]]:
 | 
			
		||||
        """
 | 
			
		||||
        Returns List of dicts containing all Trades, based on buy_tag + sell_tag performance
 | 
			
		||||
        Returns List of dicts containing all Trades, based on buy_tag + exit_tag performance
 | 
			
		||||
        Can either be average for all pairs or a specific pair provided
 | 
			
		||||
        NOTE: Not supported in Backtesting.
 | 
			
		||||
        """
 | 
			
		||||
        if(pair is not None):
 | 
			
		||||
            tag_perf = Trade.query.with_entities(
 | 
			
		||||
            Trade.buy_tag,
 | 
			
		||||
            Trade.sell_tag,
 | 
			
		||||
            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(Trade.is_open.is_(False))\
 | 
			
		||||
            .filter(Trade.pair.lower() == pair.lower()) \
 | 
			
		||||
            .order_by(desc('profit_sum_abs')) \
 | 
			
		||||
            .all()
 | 
			
		||||
                Trade.id,
 | 
			
		||||
                Trade.buy_tag,
 | 
			
		||||
                Trade.exit_tag,
 | 
			
		||||
                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(Trade.is_open.is_(False))\
 | 
			
		||||
                .filter(Trade.pair == pair) \
 | 
			
		||||
                .order_by(desc('profit_sum_abs')) \
 | 
			
		||||
                .all()
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            tag_perf = Trade.query.with_entities(
 | 
			
		||||
            Trade.buy_tag,
 | 
			
		||||
            Trade.sell_tag,
 | 
			
		||||
            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(Trade.is_open.is_(False))\
 | 
			
		||||
            .group_by(Trade.pair) \
 | 
			
		||||
            .order_by(desc('profit_sum_abs')) \
 | 
			
		||||
            .all()
 | 
			
		||||
                Trade.id,
 | 
			
		||||
                Trade.buy_tag,
 | 
			
		||||
                Trade.exit_tag,
 | 
			
		||||
                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(Trade.is_open.is_(False))\
 | 
			
		||||
                .group_by(Trade.id) \
 | 
			
		||||
                .order_by(desc('profit_sum_abs')) \
 | 
			
		||||
                .all()
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
            {   'mix_tag': str(buy_tag) + " " +str(sell_tag),
 | 
			
		||||
                'profit': profit,
 | 
			
		||||
                'profit_abs': profit_abs,
 | 
			
		||||
                'count': count
 | 
			
		||||
            }
 | 
			
		||||
            for buy_tag, sell_tag, profit, profit_abs, count in tag_perf
 | 
			
		||||
        ]
 | 
			
		||||
        return_list = []
 | 
			
		||||
        for id, buy_tag, exit_tag, profit, profit_abs, count in tag_perf:
 | 
			
		||||
            buy_tag = buy_tag if buy_tag is not None else "Other"
 | 
			
		||||
            exit_tag = exit_tag if exit_tag is not None else "Other"
 | 
			
		||||
 | 
			
		||||
            if(exit_tag is not None and buy_tag is not None):
 | 
			
		||||
                mix_tag = buy_tag + " " + exit_tag
 | 
			
		||||
                i = 0
 | 
			
		||||
                if not any(item["mix_tag"] == mix_tag for item in return_list):
 | 
			
		||||
                    return_list.append({'mix_tag': mix_tag,
 | 
			
		||||
                                        'profit': profit,
 | 
			
		||||
                                        'profit_abs': profit_abs,
 | 
			
		||||
                                        'count': count})
 | 
			
		||||
                else:
 | 
			
		||||
                    while i < len(return_list):
 | 
			
		||||
                        if return_list[i]["mix_tag"] == mix_tag:
 | 
			
		||||
                            print("item below")
 | 
			
		||||
                            print(return_list[i])
 | 
			
		||||
                            return_list[i] = {
 | 
			
		||||
                                'mix_tag': mix_tag,
 | 
			
		||||
                                'profit': profit + return_list[i]["profit"],
 | 
			
		||||
                                'profit_abs': profit_abs + return_list[i]["profit_abs"],
 | 
			
		||||
                                'count': 1 + return_list[i]["count"]}
 | 
			
		||||
                        i += 1
 | 
			
		||||
 | 
			
		||||
        return return_list
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_best_pair(start_date: datetime = datetime.fromtimestamp(0)):
 | 
			
		||||
 
 | 
			
		||||
@@ -105,7 +105,7 @@ class RPC:
 | 
			
		||||
        val = {
 | 
			
		||||
            'dry_run': config['dry_run'],
 | 
			
		||||
            'stake_currency': config['stake_currency'],
 | 
			
		||||
            'stake_currency_decimals':  decimals_per_coin(config['stake_currency']),
 | 
			
		||||
            'stake_currency_decimals': decimals_per_coin(config['stake_currency']),
 | 
			
		||||
            'stake_amount': config['stake_amount'],
 | 
			
		||||
            'available_capital': config.get('available_capital'),
 | 
			
		||||
            'max_open_trades': (config['max_open_trades']
 | 
			
		||||
@@ -696,16 +696,15 @@ class RPC:
 | 
			
		||||
        [x.update({'profit': round(x['profit'] * 100, 2)}) for x in buy_tags]
 | 
			
		||||
        return buy_tags
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def _rpc_sell_tag_performance(self, pair: str) -> List[Dict[str, Any]]:
 | 
			
		||||
    def _rpc_exit_tag_performance(self, pair: str) -> List[Dict[str, Any]]:
 | 
			
		||||
        """
 | 
			
		||||
        Handler for sell tag performance.
 | 
			
		||||
        Shows a performance statistic from finished trades
 | 
			
		||||
        """
 | 
			
		||||
        sell_tags = Trade.get_sell_tag_performance(pair)
 | 
			
		||||
        exit_tags = Trade.get_exit_tag_performance(pair)
 | 
			
		||||
        # Round and convert to %
 | 
			
		||||
        [x.update({'profit': round(x['profit'] * 100, 2)}) for x in sell_tags]
 | 
			
		||||
        return sell_tags
 | 
			
		||||
        [x.update({'profit': round(x['profit'] * 100, 2)}) for x in exit_tags]
 | 
			
		||||
        return exit_tags
 | 
			
		||||
 | 
			
		||||
    def _rpc_mix_tag_performance(self, pair: str) -> List[Dict[str, Any]]:
 | 
			
		||||
        """
 | 
			
		||||
 
 | 
			
		||||
@@ -108,7 +108,7 @@ class Telegram(RPCHandler):
 | 
			
		||||
                                 r'/trades$', r'/performance$', r'/daily$', r'/daily \d+$',
 | 
			
		||||
                                 r'/profit$', r'/profit \d+',
 | 
			
		||||
                                 r'/stats$', r'/count$', r'/locks$', r'/balance$',
 | 
			
		||||
                                 r'/buys',r'/sells',r'/mix_tags',
 | 
			
		||||
                                 r'/buys', r'/sells', r'/mix_tags',
 | 
			
		||||
                                 r'/stopbuy$', r'/reload_config$', r'/show_config$',
 | 
			
		||||
                                 r'/logs$', r'/whitelist$', r'/blacklist$', r'/edge$',
 | 
			
		||||
                                 r'/forcebuy$', r'/help$', r'/version$']
 | 
			
		||||
@@ -154,7 +154,7 @@ class Telegram(RPCHandler):
 | 
			
		||||
            CommandHandler('delete', self._delete_trade),
 | 
			
		||||
            CommandHandler('performance', self._performance),
 | 
			
		||||
            CommandHandler('buys', self._buy_tag_performance),
 | 
			
		||||
            CommandHandler('sells', self._sell_tag_performance),
 | 
			
		||||
            CommandHandler('sells', self._exit_tag_performance),
 | 
			
		||||
            CommandHandler('mix_tags', self._mix_tag_performance),
 | 
			
		||||
            CommandHandler('stats', self._stats),
 | 
			
		||||
            CommandHandler('daily', self._daily),
 | 
			
		||||
@@ -178,7 +178,7 @@ class Telegram(RPCHandler):
 | 
			
		||||
            CallbackQueryHandler(self._balance, pattern='update_balance'),
 | 
			
		||||
            CallbackQueryHandler(self._performance, pattern='update_performance'),
 | 
			
		||||
            CallbackQueryHandler(self._performance, pattern='update_buy_tag_performance'),
 | 
			
		||||
            CallbackQueryHandler(self._performance, pattern='update_sell_tag_performance'),
 | 
			
		||||
            CallbackQueryHandler(self._performance, pattern='update_exit_tag_performance'),
 | 
			
		||||
            CallbackQueryHandler(self._performance, pattern='update_mix_tag_performance'),
 | 
			
		||||
            CallbackQueryHandler(self._count, pattern='update_count'),
 | 
			
		||||
            CallbackQueryHandler(self._forcebuy_inline),
 | 
			
		||||
@@ -242,6 +242,7 @@ class Telegram(RPCHandler):
 | 
			
		||||
        msg['duration'] = msg['close_date'].replace(
 | 
			
		||||
            microsecond=0) - msg['open_date'].replace(microsecond=0)
 | 
			
		||||
        msg['duration_min'] = msg['duration'].total_seconds() / 60
 | 
			
		||||
        msg['tags'] = self._get_tags_string(msg)
 | 
			
		||||
 | 
			
		||||
        msg['emoji'] = self._get_sell_emoji(msg)
 | 
			
		||||
 | 
			
		||||
@@ -258,6 +259,7 @@ class Telegram(RPCHandler):
 | 
			
		||||
 | 
			
		||||
        message = ("{emoji} *{exchange}:* Selling {pair} (#{trade_id})\n"
 | 
			
		||||
                   "*Profit:* `{profit_percent:.2f}%{profit_extra}`\n"
 | 
			
		||||
                   "{tags}"
 | 
			
		||||
                   "*Sell Reason:* `{sell_reason}`\n"
 | 
			
		||||
                   "*Duration:* `{duration} ({duration_min:.1f} min)`\n"
 | 
			
		||||
                   "*Amount:* `{amount:.8f}`\n"
 | 
			
		||||
@@ -265,46 +267,6 @@ class Telegram(RPCHandler):
 | 
			
		||||
                   "*Current Rate:* `{current_rate:.8f}`\n"
 | 
			
		||||
                   "*Close Rate:* `{limit:.8f}`").format(**msg)
 | 
			
		||||
 | 
			
		||||
        sell_tag =None
 | 
			
		||||
        if("sell_tag" in msg.keys()):
 | 
			
		||||
            sell_tag = msg['sell_tag']
 | 
			
		||||
        buy_tag =None
 | 
			
		||||
        if("buy_tag" in msg.keys()):
 | 
			
		||||
            buy_tag = msg['buy_tag']
 | 
			
		||||
 | 
			
		||||
        if sell_tag is not None and buy_tag is not None:
 | 
			
		||||
            message = ("{emoji} *{exchange}:* Selling {pair} (#{trade_id})\n"
 | 
			
		||||
                   "*Profit:* `{profit_percent:.2f}%{profit_extra}`\n"
 | 
			
		||||
                   "*Buy Tag:* `{buy_tag}`\n"
 | 
			
		||||
                   "*Sell Tag:* `{sell_tag}`\n"
 | 
			
		||||
                   "*Sell Reason:* `{sell_reason}`\n"
 | 
			
		||||
                   "*Duration:* `{duration} ({duration_min:.1f} min)`\n"
 | 
			
		||||
                   "*Amount:* `{amount:.8f}`\n"
 | 
			
		||||
                   "*Open Rate:* `{open_rate:.8f}`\n"
 | 
			
		||||
                   "*Current Rate:* `{current_rate:.8f}`\n"
 | 
			
		||||
                   "*Close Rate:* `{limit:.8f}`").format(**msg)
 | 
			
		||||
        elif sell_tag is None and buy_tag is not None:
 | 
			
		||||
            message = ("{emoji} *{exchange}:* Selling {pair} (#{trade_id})\n"
 | 
			
		||||
                   "*Profit:* `{profit_percent:.2f}%{profit_extra}`\n"
 | 
			
		||||
                   "*Buy Tag:* `{buy_tag}`\n"
 | 
			
		||||
                   "*Sell Reason:* `{sell_reason}`\n"
 | 
			
		||||
                   "*Duration:* `{duration} ({duration_min:.1f} min)`\n"
 | 
			
		||||
                   "*Amount:* `{amount:.8f}`\n"
 | 
			
		||||
                   "*Open Rate:* `{open_rate:.8f}`\n"
 | 
			
		||||
                   "*Current Rate:* `{current_rate:.8f}`\n"
 | 
			
		||||
                   "*Close Rate:* `{limit:.8f}`").format(**msg)
 | 
			
		||||
        elif sell_tag is not None and buy_tag is None:
 | 
			
		||||
            message = ("{emoji} *{exchange}:* Selling {pair} (#{trade_id})\n"
 | 
			
		||||
                   "*Profit:* `{profit_percent:.2f}%{profit_extra}`\n"
 | 
			
		||||
                   "*Sell Tag:* `{sell_tag}`\n"
 | 
			
		||||
                   "*Sell Reason:* `{sell_reason}`\n"
 | 
			
		||||
                   "*Duration:* `{duration} ({duration_min:.1f} min)`\n"
 | 
			
		||||
                   "*Amount:* `{amount:.8f}`\n"
 | 
			
		||||
                   "*Open Rate:* `{open_rate:.8f}`\n"
 | 
			
		||||
                   "*Current Rate:* `{current_rate:.8f}`\n"
 | 
			
		||||
                   "*Close Rate:* `{limit:.8f}`").format(**msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        return message
 | 
			
		||||
 | 
			
		||||
    def compose_message(self, msg: Dict[str, Any], msg_type: RPCMessageType) -> str:
 | 
			
		||||
@@ -393,6 +355,18 @@ class Telegram(RPCHandler):
 | 
			
		||||
        else:
 | 
			
		||||
            return "\N{CROSS MARK}"
 | 
			
		||||
 | 
			
		||||
    def _get_tags_string(self, msg):
 | 
			
		||||
        """
 | 
			
		||||
        Get string lines for buy/sell tags to display when a sell is made
 | 
			
		||||
        """
 | 
			
		||||
        tag_lines = ""
 | 
			
		||||
 | 
			
		||||
        if ("buy_tag" in msg.keys() and msg['buy_tag'] is not None):
 | 
			
		||||
            tag_lines += ("*Buy Tag:* `{buy_tag}`\n").format(msg['buy_tag'])
 | 
			
		||||
        if ("exit_tag" in msg.keys() and msg['exit_tag'] is not None):
 | 
			
		||||
            tag_lines += ("*Sell Tag:* `{exit_tag}`\n").format(msg['exit_tag'])
 | 
			
		||||
        return tag_lines
 | 
			
		||||
 | 
			
		||||
    @authorized_only
 | 
			
		||||
    def _status(self, update: Update, context: CallbackContext) -> None:
 | 
			
		||||
        """
 | 
			
		||||
@@ -425,7 +399,7 @@ class Telegram(RPCHandler):
 | 
			
		||||
                    "*Current Pair:* {pair}",
 | 
			
		||||
                    "*Amount:* `{amount} ({stake_amount} {base_currency})`",
 | 
			
		||||
                    "*Buy Tag:* `{buy_tag}`" if r['buy_tag'] else "",
 | 
			
		||||
                    "*Sell Tag:* `{sell_tag}`" if r['sell_tag'] else "",
 | 
			
		||||
                    "*Sell Tag:* `{exit_tag}`" if r['exit_tag'] else "",
 | 
			
		||||
                    "*Open Rate:* `{open_rate:.8f}`",
 | 
			
		||||
                    "*Close Rate:* `{close_rate}`" if r['close_rate'] else "",
 | 
			
		||||
                    "*Current Rate:* `{current_rate:.8f}`",
 | 
			
		||||
@@ -923,12 +897,12 @@ class Telegram(RPCHandler):
 | 
			
		||||
        :return: None
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            pair=None
 | 
			
		||||
            pair = None
 | 
			
		||||
            if context.args:
 | 
			
		||||
                pair = context.args[0]
 | 
			
		||||
 | 
			
		||||
            trades = self._rpc._rpc_buy_tag_performance(pair)
 | 
			
		||||
            output = "<b>Performance:</b>\n"
 | 
			
		||||
            output = "<b>Buy Tag Performance:</b>\n"
 | 
			
		||||
            for i, trade in enumerate(trades):
 | 
			
		||||
                stat_line = (
 | 
			
		||||
                    f"{i+1}.\t <code>{trade['buy_tag']}\t"
 | 
			
		||||
@@ -949,7 +923,7 @@ class Telegram(RPCHandler):
 | 
			
		||||
            self._send_msg(str(e))
 | 
			
		||||
 | 
			
		||||
    @authorized_only
 | 
			
		||||
    def _sell_tag_performance(self, update: Update, context: CallbackContext) -> None:
 | 
			
		||||
    def _exit_tag_performance(self, update: Update, context: CallbackContext) -> None:
 | 
			
		||||
        """
 | 
			
		||||
        Handler for /sells.
 | 
			
		||||
        Shows a performance statistic from finished trades
 | 
			
		||||
@@ -958,15 +932,15 @@ class Telegram(RPCHandler):
 | 
			
		||||
        :return: None
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            pair=None
 | 
			
		||||
            pair = None
 | 
			
		||||
            if context.args:
 | 
			
		||||
                pair = context.args[0]
 | 
			
		||||
 | 
			
		||||
            trades = self._rpc._rpc_sell_tag_performance(pair)
 | 
			
		||||
            output = "<b>Performance:</b>\n"
 | 
			
		||||
            trades = self._rpc._rpc_exit_tag_performance(pair)
 | 
			
		||||
            output = "<b>Sell Tag Performance:</b>\n"
 | 
			
		||||
            for i, trade in enumerate(trades):
 | 
			
		||||
                stat_line = (
 | 
			
		||||
                    f"{i+1}.\t <code>{trade['sell_tag']}\t"
 | 
			
		||||
                    f"{i+1}.\t <code>{trade['exit_tag']}\t"
 | 
			
		||||
                    f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} "
 | 
			
		||||
                    f"({trade['profit']:.2f}%) "
 | 
			
		||||
                    f"({trade['count']})</code>\n")
 | 
			
		||||
@@ -978,7 +952,7 @@ class Telegram(RPCHandler):
 | 
			
		||||
                    output += stat_line
 | 
			
		||||
 | 
			
		||||
            self._send_msg(output, parse_mode=ParseMode.HTML,
 | 
			
		||||
                           reload_able=True, callback_path="update_sell_tag_performance",
 | 
			
		||||
                           reload_able=True, callback_path="update_exit_tag_performance",
 | 
			
		||||
                           query=update.callback_query)
 | 
			
		||||
        except RPCException as e:
 | 
			
		||||
            self._send_msg(str(e))
 | 
			
		||||
@@ -993,13 +967,14 @@ class Telegram(RPCHandler):
 | 
			
		||||
        :return: None
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            pair=None
 | 
			
		||||
            pair = None
 | 
			
		||||
            if context.args:
 | 
			
		||||
                pair = context.args[0]
 | 
			
		||||
 | 
			
		||||
            trades = self._rpc._rpc_mix_tag_performance(pair)
 | 
			
		||||
            output = "<b>Performance:</b>\n"
 | 
			
		||||
            output = "<b>Mix Tag Performance:</b>\n"
 | 
			
		||||
            for i, trade in enumerate(trades):
 | 
			
		||||
                print(str(trade))
 | 
			
		||||
                stat_line = (
 | 
			
		||||
                    f"{i+1}.\t <code>{trade['mix_tag']}\t"
 | 
			
		||||
                    f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} "
 | 
			
		||||
 
 | 
			
		||||
@@ -500,7 +500,7 @@ class IStrategy(ABC, HyperStrategyMixin):
 | 
			
		||||
            dataframe['buy'] = 0
 | 
			
		||||
            dataframe['sell'] = 0
 | 
			
		||||
            dataframe['buy_tag'] = None
 | 
			
		||||
            dataframe['sell_tag'] = None
 | 
			
		||||
            dataframe['exit_tag'] = None
 | 
			
		||||
 | 
			
		||||
        # Other Defs in strategy that want to be called every loop here
 | 
			
		||||
        # twitter_sell = self.watch_twitter_feed(dataframe, metadata)
 | 
			
		||||
@@ -613,7 +613,7 @@ class IStrategy(ABC, HyperStrategyMixin):
 | 
			
		||||
            sell = latest[SignalType.SELL.value] == 1
 | 
			
		||||
 | 
			
		||||
        buy_tag = latest.get(SignalTagType.BUY_TAG.value, None)
 | 
			
		||||
        sell_tag = latest.get(SignalTagType.SELL_TAG.value, None)
 | 
			
		||||
        exit_tag = latest.get(SignalTagType.EXIT_TAG.value, None)
 | 
			
		||||
 | 
			
		||||
        logger.debug('trigger: %s (pair=%s) buy=%s sell=%s',
 | 
			
		||||
                     latest['date'], pair, str(buy), str(sell))
 | 
			
		||||
@@ -622,8 +622,8 @@ class IStrategy(ABC, HyperStrategyMixin):
 | 
			
		||||
                                      current_time=datetime.now(timezone.utc),
 | 
			
		||||
                                      timeframe_seconds=timeframe_seconds,
 | 
			
		||||
                                      buy=buy):
 | 
			
		||||
            return False, sell, buy_tag, sell_tag
 | 
			
		||||
        return buy, sell, buy_tag, sell_tag
 | 
			
		||||
            return False, sell, buy_tag, exit_tag
 | 
			
		||||
        return buy, sell, buy_tag, exit_tag
 | 
			
		||||
 | 
			
		||||
    def ignore_expired_candle(self, latest_date: datetime, current_time: datetime,
 | 
			
		||||
                              timeframe_seconds: int, buy: bool):
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user