From b898f86364787b3c1ba0686281a96254eb213579 Mon Sep 17 00:00:00 2001 From: theluxaz <theluxaz@gmail.com> Date: Wed, 13 Oct 2021 00:02:28 +0300 Subject: [PATCH 01/26] Added sell_tag and buy/sell telegram performance functions --- freqtrade/data/btanalysis.py | 2 +- freqtrade/enums/signaltype.py | 1 + freqtrade/freqtradebot.py | 16 +- freqtrade/optimize/backtesting.py | 13 +- freqtrade/optimize/optimize_reports.py | 138 +- freqtrade/persistence/migrations.py | 6 +- freqtrade/persistence/models.py | 121 +- freqtrade/rpc/rpc.py | 31 + freqtrade/rpc/telegram.py | 152 +++ freqtrade/strategy/interface.py | 8 +- .../hyperopts/RuleNOTANDoptimizer.py | 1203 +++++++++++++++++ 11 files changed, 1673 insertions(+), 18 deletions(-) create mode 100644 freqtrade/user_data/hyperopts/RuleNOTANDoptimizer.py diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 7d97661c4..82b2bb3a9 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -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'] + 'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_tag', 'sell_tag'] def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str: diff --git a/freqtrade/enums/signaltype.py b/freqtrade/enums/signaltype.py index d2995d57a..32ac19ba4 100644 --- a/freqtrade/enums/signaltype.py +++ b/freqtrade/enums/signaltype.py @@ -14,3 +14,4 @@ class SignalTagType(Enum): Enum for signal columns """ BUY_TAG = "buy_tag" + SELL_TAG = "sell_tag" \ No newline at end of file diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 259270483..55828f763 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -420,7 +420,7 @@ class FreqtradeBot(LoggingMixin): return False # running get_signal on historical data fetched - (buy, sell, buy_tag) = self.strategy.get_signal( + (buy, sell, buy_tag,sell_tag) = self.strategy.get_signal( pair, self.strategy.timeframe, analyzed_df @@ -706,7 +706,7 @@ class FreqtradeBot(LoggingMixin): analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair, self.strategy.timeframe) - (buy, sell, _) = self.strategy.get_signal( + (buy, sell, buy_tag, sell_tag) = self.strategy.get_signal( trade.pair, self.strategy.timeframe, analyzed_df @@ -714,7 +714,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_sell(trade, sell_rate, buy, sell): + if self._check_and_execute_sell(trade, sell_rate, buy, sell, sell_tag): return True logger.debug('Found no sell signal for %s.', trade) @@ -852,18 +852,19 @@ class FreqtradeBot(LoggingMixin): f"for pair {trade.pair}.") def _check_and_execute_sell(self, trade: Trade, sell_rate: float, - buy: bool, sell: bool) -> bool: + buy: bool, sell: bool, sell_tag: Optional[str]) -> bool: """ Check and execute sell """ + print(str(sell_tag)+"1") should_sell = self.strategy.should_sell( trade, sell_rate, datetime.now(timezone.utc), buy, sell, force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0 ) if should_sell.sell_flag: - logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}') - self.execute_trade_exit(trade, sell_rate, should_sell) + logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}. Tag: {sell_tag}') + self.execute_trade_exit(trade, sell_rate, should_sell,sell_tag) return True return False @@ -1064,7 +1065,7 @@ 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) -> bool: + def execute_trade_exit(self, trade: Trade, limit: float, sell_reason: SellCheckTuple, sell_tag: Optional[str] = None) -> bool: """ Executes a trade exit for the given trade and limit :param trade: Trade instance @@ -1141,6 +1142,7 @@ class FreqtradeBot(LoggingMixin): trade.sell_order_status = '' trade.close_rate_requested = limit trade.sell_reason = sell_reason.sell_reason + trade.sell_tag = sell_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) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index eecc7af54..3bed3c540 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -44,7 +44,7 @@ SELL_IDX = 4 LOW_IDX = 5 HIGH_IDX = 6 BUY_TAG_IDX = 7 - +SELL_TAG_IDX = 8 class Backtesting: """ @@ -218,7 +218,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'] + headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high', 'buy_tag', 'sell_tag'] data: Dict = {} self.progress.init_step(BacktestState.CONVERT, len(processed)) @@ -230,6 +230,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 df_analyzed = self.strategy.advise_sell( self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair}).copy() @@ -241,6 +242,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) # Update dataprovider cache self.dataprovider._set_cached_df(pair, self.timeframe, df_analyzed) @@ -319,6 +321,9 @@ class Backtesting: return sell_row[OPEN_IDX] def _get_sell_trade_entry(self, trade: LocalTrade, sell_row: Tuple) -> Optional[LocalTrade]: + + + sell_candle_time = sell_row[DATE_IDX].to_pydatetime() sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], # type: ignore sell_candle_time, sell_row[BUY_IDX], @@ -327,6 +332,8 @@ 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] 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) @@ -375,6 +382,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 trade = LocalTrade( pair=pair, open_rate=row[OPEN_IDX], @@ -385,6 +393,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, exchange='backtesting', ) return trade diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 7bb60228a..fcead07ba 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -82,7 +82,7 @@ def _generate_result_line(result: DataFrame, starting_balance: int, first_column 'profit_sum_pct': round(profit_sum * 100.0, 2), 'profit_total_abs': result['profit_abs'].sum(), 'profit_total': profit_total, - 'profit_total_pct': round(profit_total * 100.0, 2), + 'profit_total_pct': round(profit_sum * 100.0, 2), 'duration_avg': str(timedelta( minutes=round(result['trade_duration'].mean())) ) if not result.empty else '0:00', @@ -126,6 +126,92 @@ 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]: + """ + 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 + :return: List of Dicts containing the metrics per pair + """ + + 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(): + continue + + tabular_data.append(_generate_tag_result_line(result, starting_balance, tag)) + + # Sort by total profit %: + tabular_data = sorted(tabular_data, key=lambda k: k['profit_total_abs'], reverse=True) + + # Append Total + 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. + """ + profit_sum = result['profit_ratio'].sum() + # (end-capital - starting capital) / starting capital + profit_total = result['profit_abs'].sum() / starting_balance + + return { + 'key': first_column, + 'trades': len(result), + 'profit_mean': result['profit_ratio'].mean() if len(result) > 0 else 0.0, + 'profit_mean_pct': result['profit_ratio'].mean() * 100.0 if len(result) > 0 else 0.0, + 'profit_sum': profit_sum, + 'profit_sum_pct': round(profit_sum * 100.0, 2), + 'profit_total_abs': result['profit_abs'].sum(), + 'profit_total': profit_total, + 'profit_total_pct': round(profit_total * 100.0, 2), + 'duration_avg': str(timedelta( + minutes=round(result['trade_duration'].mean())) + ) if not result.empty else '0:00', + # 'duration_max': str(timedelta( + # minutes=round(result['trade_duration'].max())) + # ) if not result.empty else '0:00', + # 'duration_min': str(timedelta( + # minutes=round(result['trade_duration'].min())) + # ) if not result.empty else '0:00', + 'wins': len(result[result['profit_abs'] > 0]), + 'draws': len(result[result['profit_abs'] == 0]), + 'losses': len(result[result['profit_abs'] < 0]), + } + def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List[Dict]: """ @@ -313,6 +399,13 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency, 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) + sell_reason_stats = generate_sell_reason_stats(max_open_trades=max_open_trades, results=results) left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency, @@ -336,6 +429,8 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], 'best_pair': best_pair, 'worst_pair': worst_pair, 'results_per_pair': pair_results, + 'results_per_buy_tag': buy_tag_results, + 'results_per_sell_tag': sell_tag_results, 'sell_reason_summary': sell_reason_stats, 'left_open_trades': left_open_results, 'total_trades': len(results), @@ -504,6 +599,27 @@ 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: + """ + 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 + """ + + headers = _get_line_header("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] + # 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: """ @@ -624,12 +740,24 @@ 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) + + if isinstance(table, str) and len(table) > 0: + print(' BUY TAG STATS '.center(len(table.splitlines()[0]), '=')) + print(table) + + table = text_table_sell_reason(sell_reason_stats=results['sell_reason_summary'], stake_currency=stake_currency) if isinstance(table, str) and len(table) > 0: 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]), '=')) @@ -640,8 +768,16 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency: print(' SUMMARY METRICS '.center(len(table.splitlines()[0]), '=')) print(table) + table = text_table_tags("sell_tag",results['results_per_sell_tag'], stake_currency=stake_currency) + + if isinstance(table, str) and len(table) > 0: + print(' SELL TAG STATS '.center(len(table.splitlines()[0]), '=')) + print(table) + if isinstance(table, str) and len(table) > 0: print('=' * len(table.splitlines()[0])) + + print() diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 1839c4130..db93cf8b0 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -82,7 +82,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, + max_rate, min_rate, sell_reason, sell_order_status, strategy, buy_tag, sell_tag, timeframe, open_trade_value, close_profit_abs ) select id, lower(exchange), pair, @@ -98,7 +98,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, {timeframe} timeframe, + {strategy} strategy, {buy_tag} buy_tag, {sell_tag} sell_tag, {timeframe} timeframe, {open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs from {table_back_name} """)) @@ -157,7 +157,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, 'buy_tag'): + if not has_column(cols, 'sell_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! diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 8c8c1e0a9..b06386810 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -258,6 +258,7 @@ class LocalTrade(): sell_order_status: str = '' strategy: str = '' buy_tag: Optional[str] = None + sell_tag: Optional[str] = None timeframe: Optional[int] = None def __init__(self, **kwargs): @@ -324,7 +325,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': (f' ({self.sell_reason})' if self.sell_reason else ''), #+str(self.sell_reason) ## CHANGE TO BUY TAG IF NEEDED + 'sell_tag': self.sell_tag, '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, @@ -706,6 +708,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) timeframe = Column(Integer, nullable=True) def __init__(self, **kwargs): @@ -856,6 +859,122 @@ class Trade(_DECL_BASE, LocalTrade): for pair, profit, profit_abs, count in pair_rates ] + @staticmethod + def get_buy_tag_performance(pair: str) -> List[Dict[str, Any]]: + """ + Returns List of dicts containing all Trades, based on buy 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, + 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() + 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() + + return [ + { + 'buy_tag': buy_tag, + 'profit': profit, + 'profit_abs': profit_abs, + 'count': count + } + for buy_tag, profit, profit_abs, count in tag_perf + ] + + @staticmethod + def get_sell_tag_performance(pair: str) -> List[Dict[str, Any]]: + """ + Returns List of dicts containing all Trades, based on sell 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() + 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() + + return [ + { + 'sell_tag': sell_tag, + 'profit': profit, + 'profit_abs': profit_abs, + 'count': count + } + for sell_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 + 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() + 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() + + 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 + ] + @staticmethod def get_best_pair(start_date: datetime = datetime.fromtimestamp(0)): """ diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 95a37452b..a53ce2150 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -669,6 +669,37 @@ class RPC: [x.update({'profit': round(x['profit'] * 100, 2)}) for x in pair_rates] return pair_rates + def _rpc_buy_tag_performance(self, pair: str) -> List[Dict[str, Any]]: + """ + Handler for buy tag performance. + Shows a performance statistic from finished trades + """ + buy_tags = Trade.get_buy_tag_performance(pair) + # Round and convert to % + [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]]: + """ + Handler for sell tag performance. + Shows a performance statistic from finished trades + """ + sell_tags = Trade.get_sell_tag_performance(pair) + # Round and convert to % + [x.update({'profit': round(x['profit'] * 100, 2)}) for x in sell_tags] + return sell_tags + + def _rpc_mix_tag_performance(self, pair: str) -> List[Dict[str, Any]]: + """ + Handler for mix tag performance. + Shows a performance statistic from finished trades + """ + mix_tags = Trade.get_mix_tag_performance(pair) + # Round and convert to % + [x.update({'profit': round(x['profit'] * 100, 2)}) for x in mix_tags] + return mix_tags + def _rpc_count(self) -> Dict[str, float]: """ Returns the number of trades running """ if self._freqtrade.state != State.RUNNING: diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index a988d2b60..1834abd64 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -108,6 +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'/stopbuy$', r'/reload_config$', r'/show_config$', r'/logs$', r'/whitelist$', r'/blacklist$', r'/edge$', r'/forcebuy$', r'/help$', r'/version$'] @@ -152,6 +153,9 @@ class Telegram(RPCHandler): CommandHandler('trades', self._trades), CommandHandler('delete', self._delete_trade), CommandHandler('performance', self._performance), + CommandHandler('buys', self._buy_tag_performance), + CommandHandler('sells', self._sell_tag_performance), + CommandHandler('mix_tags', self._mix_tag_performance), CommandHandler('stats', self._stats), CommandHandler('daily', self._daily), CommandHandler('count', self._count), @@ -173,6 +177,9 @@ class Telegram(RPCHandler): CallbackQueryHandler(self._profit, pattern='update_profit'), 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_mix_tag_performance'), CallbackQueryHandler(self._count, pattern='update_count'), CallbackQueryHandler(self._forcebuy_inline), ] @@ -258,6 +265,42 @@ class Telegram(RPCHandler): "*Current Rate:* `{current_rate:.8f}`\n" "*Close Rate:* `{limit:.8f}`").format(**msg) + sell_tag = msg['sell_tag'] + 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 send_msg(self, msg: Dict[str, Any]) -> None: @@ -364,6 +407,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 "", "*Open Rate:* `{open_rate:.8f}`", "*Close Rate:* `{close_rate}`" if r['close_rate'] else "", "*Current Rate:* `{current_rate:.8f}`", @@ -845,6 +889,111 @@ class Telegram(RPCHandler): except RPCException as e: self._send_msg(str(e)) + @authorized_only + def _buy_tag_performance(self, update: Update, context: CallbackContext) -> None: + """ + Handler for /buys PAIR . + Shows a performance statistic from finished trades + :param bot: telegram bot + :param update: message update + :return: None + """ + try: + pair=None + if context.args: + pair = context.args[0] + + trades = self._rpc._rpc_buy_tag_performance(pair) + output = "<b>Performance:</b>\n" + for i, trade in enumerate(trades): + stat_line = ( + f"{i+1}.\t <code>{trade['buy_tag']}\t" + f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " + f"({trade['profit']:.2f}%) " + f"({trade['count']})</code>\n") + + if len(output + stat_line) >= MAX_TELEGRAM_MESSAGE_LENGTH: + self._send_msg(output, parse_mode=ParseMode.HTML) + output = stat_line + else: + output += stat_line + + self._send_msg(output, parse_mode=ParseMode.HTML, + reload_able=True, callback_path="update_buy_tag_performance", + query=update.callback_query) + except RPCException as e: + self._send_msg(str(e)) + + @authorized_only + def _sell_tag_performance(self, update: Update, context: CallbackContext) -> None: + """ + Handler for /sells. + Shows a performance statistic from finished trades + :param bot: telegram bot + :param update: message update + :return: None + """ + try: + pair=None + if context.args: + pair = context.args[0] + + trades = self._rpc._rpc_sell_tag_performance(pair) + output = "<b>Performance:</b>\n" + for i, trade in enumerate(trades): + stat_line = ( + f"{i+1}.\t <code>{trade['sell_tag']}\t" + f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " + f"({trade['profit']:.2f}%) " + f"({trade['count']})</code>\n") + + if len(output + stat_line) >= MAX_TELEGRAM_MESSAGE_LENGTH: + self._send_msg(output, parse_mode=ParseMode.HTML) + output = stat_line + else: + output += stat_line + + self._send_msg(output, parse_mode=ParseMode.HTML, + reload_able=True, callback_path="update_sell_tag_performance", + query=update.callback_query) + except RPCException as e: + self._send_msg(str(e)) + + @authorized_only + def _mix_tag_performance(self, update: Update, context: CallbackContext) -> None: + """ + Handler for /mix_tags. + Shows a performance statistic from finished trades + :param bot: telegram bot + :param update: message update + :return: None + """ + try: + pair=None + if context.args: + pair = context.args[0] + + trades = self._rpc._rpc_mix_tag_performance(pair) + output = "<b>Performance:</b>\n" + for i, trade in enumerate(trades): + stat_line = ( + f"{i+1}.\t <code>{trade['mix_tag']}\t" + f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " + f"({trade['profit']:.2f}%) " + f"({trade['count']})</code>\n") + + if len(output + stat_line) >= MAX_TELEGRAM_MESSAGE_LENGTH: + self._send_msg(output, parse_mode=ParseMode.HTML) + output = stat_line + else: + output += stat_line + + self._send_msg(output, parse_mode=ParseMode.HTML, + reload_able=True, callback_path="update_mix_tag_performance", + query=update.callback_query) + except RPCException as e: + self._send_msg(str(e)) + @authorized_only def _count(self, update: Update, context: CallbackContext) -> None: """ @@ -1020,6 +1169,9 @@ class Telegram(RPCHandler): " *table :* `will display trades in a table`\n" " `pending buy orders are marked with an asterisk (*)`\n" " `pending sell orders are marked with a double asterisk (**)`\n" + "*/buys <pair|none>:* `Shows the buy_tag performance`\n" + "*/sells <pair|none>:* `Shows the sell reason performance`\n" + "*/mix_tag <pair|none>:* `Shows combined buy tag + sell reason performance`\n" "*/trades [limit]:* `Lists last closed trades (limited to 10 by default)`\n" "*/profit [<n>]:* `Lists cumulative profit from all finished trades, " "over the last n days`\n" diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index c51860011..68b65b293 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -460,6 +460,7 @@ class IStrategy(ABC, HyperStrategyMixin): dataframe['buy'] = 0 dataframe['sell'] = 0 dataframe['buy_tag'] = None + dataframe['sell_tag'] = None # Other Defs in strategy that want to be called every loop here # twitter_sell = self.watch_twitter_feed(dataframe, metadata) @@ -537,7 +538,7 @@ class IStrategy(ABC, HyperStrategyMixin): pair: str, timeframe: str, dataframe: DataFrame - ) -> Tuple[bool, bool, Optional[str]]: + ) -> Tuple[bool, bool, Optional[str], Optional[str]]: """ Calculates current signal based based on the buy / sell columns of the dataframe. Used by Bot to get the signal to buy or sell @@ -572,6 +573,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) logger.debug('trigger: %s (pair=%s) buy=%s sell=%s', latest['date'], pair, str(buy), str(sell)) @@ -580,8 +582,8 @@ class IStrategy(ABC, HyperStrategyMixin): current_time=datetime.now(timezone.utc), timeframe_seconds=timeframe_seconds, buy=buy): - return False, sell, buy_tag - return buy, sell, buy_tag + return False, sell, buy_tag, sell_tag + return buy, sell, buy_tag, sell_tag def ignore_expired_candle(self, latest_date: datetime, current_time: datetime, timeframe_seconds: int, buy: bool): diff --git a/freqtrade/user_data/hyperopts/RuleNOTANDoptimizer.py b/freqtrade/user_data/hyperopts/RuleNOTANDoptimizer.py new file mode 100644 index 000000000..f720b59ca --- /dev/null +++ b/freqtrade/user_data/hyperopts/RuleNOTANDoptimizer.py @@ -0,0 +1,1203 @@ +# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement +# isort: skip_file +# --- Do not remove these libs --- +from functools import reduce +from typing import Any, Callable, Dict, List + +import numpy as np # noqa +import pandas as pd # noqa +from pandas import DataFrame +from skopt.space import Categorical, Dimension,Integer , Real # noqa +from freqtrade.optimize.space import SKDecimal +from freqtrade.optimize.hyperopt_interface import IHyperOpt + +# -------------------------------- +# Add your lib to import here +import talib.abstract as ta # noqa +import freqtrade.vendor.qtpylib.indicators as qtpylib + +##PYCHARM +import sys +sys.path.append(r"/freqtrade/user_data/strategies") + + +# ##HYPEROPT +# import sys,os +# file_dir = os.path.dirname(__file__) +# sys.path.append(file_dir) + + +from z_buyer_mid_volatility import mid_volatility_buyer +from z_seller_mid_volatility import mid_volatility_seller +from z_COMMON_FUNCTIONS import MID_VOLATILITY + + + + +class RuleOptimizer15min(IHyperOpt): + """ + This is a sample hyperopt to inspire you. + Feel free to customize it. + + More information in the documentation: https://www.freqtrade.io/en/latest/hyperopt/ + + You should: + - Rename the class name to some unique name. + - Add any methods you want to build your hyperopt. + - Add any lib you need to build your hyperopt. + + You must keep: + - The prototypes for the methods: populate_indicators, indicator_space, buy_strategy_generator. + + The methods roi_space, generate_roi_table and stoploss_space are not required + and are provided by default. + However, you may override them if you need the + 'roi' and the 'stoploss' spaces that differ from the defaults offered by Freqtrade. + + This sample illustrates how to override these methods. + """ + + + @staticmethod + def buy_strategy_generator(params: Dict[str, Any]) -> Callable: + """ + Define the buy strategy parameters to be used by hyperopt + """ + def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Buy strategy Hyperopt will build and use + """ + conditions = [] + + + +#--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +#--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + ##MAIN SELECTORS + +#-------------------- + + ##VOLATILITY + + conditions.append(dataframe['vol_mid'] > 0 ) + + # conditions.append((dataframe['vol_low'] > 0) |(dataframe['vol_mid'] > 0) ) + + # conditions.append((dataframe['vol_high'] > 0) |(dataframe['vol_mid'] > 0) ) + + +#-------------------- + + + ##PICKS TREND COMBO + + conditions.append( + + (dataframe['downtrend'] >= params['main_1_trend_strength']) + |#OR & + (dataframe['downtrendsmall'] >= params['main_2_trend_strength']) + + ) + + ##UPTREND + #conditions.append(dataframe['uptrend'] >= params['main_1_trend_strength']) + ##DOWNTREND + #conditions.append(dataframe['downtrend'] >= params['main_1_trend_strength']) + ##NOTREND + #conditions.append((dataframe['uptrend'] <1)&(dataframe['downtrend'] <1)) + +#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + ##ABOVE / BELOW THRESHOLDS + + #RSI ABOVE + if 'include_sell_ab_9_rsi_above_value' in params and params['include_sell_ab_9_rsi_above_value']: + conditions.append(dataframe['rsi'] > params['sell_ab_9_rsi_above_value']) + #RSI RECENT PIT 5 + if 'include_sell_ab_10_rsi_recent_pit_2_value' in params and params['include_sell_ab_10_rsi_recent_pit_2_value']: + conditions.append(dataframe['rsi'].rolling(2).min() < params['sell_ab_10_rsi_recent_pit_2_value']) + #RSI RECENT PIT 12 + if 'include_sell_ab_11_rsi_recent_pit_4_value' in params and params['include_sell_ab_11_rsi_recent_pit_4_value']: + conditions.append(dataframe['rsi'].rolling(4).min() < params['sell_ab_11_rsi_recent_pit_4_value']) + #RSI5 BELOW + if 'include_sell_ab_12_rsi5_above_value' in params and params['include_sell_ab_12_rsi5_above_value']: + conditions.append(dataframe['rsi5'] > params['sell_ab_12_rsi5_above_value']) + #RSI50 BELOW + if 'include_sell_ab_13_rsi50_above_value' in params and params['include_sell_ab_13_rsi50_above_value']: + conditions.append(dataframe['rsi50'] > params['sell_ab_13_rsi50_above_value']) + +#----------------------- + + #ROC BELOW + if 'include_sell_ab_14_roc_above_value' in params and params['include_sell_ab_14_roc_above_value']: + conditions.append(dataframe['roc'] > (params['sell_ab_14_roc_above_value']/2)) + #ROC50 BELOW + if 'include_sell_ab_15_roc50_above_value' in params and params['include_sell_ab_15_roc50_above_value']: + conditions.append(dataframe['roc50'] > (params['sell_ab_15_roc50_above_value'])) + #ROC2 BELOW + if 'include_sell_ab_16_roc2_above_value' in params and params['include_sell_ab_16_roc2_above_value']: + conditions.append(dataframe['roc2'] > (params['sell_ab_16_roc2_above_value']/2)) + +#----------------------- + + #PPO5 BELOW + if 'include_sell_ab_17_ppo5_above_value' in params and params['include_sell_ab_17_ppo5_above_value']: + conditions.append(dataframe['ppo5'] > (params['sell_ab_17_ppo5_above_value']/2)) + #PPO10 BELOW + if 'include_sell_ab_18_ppo10_above_value' in params and params['include_sell_ab_18_ppo10_above_value']: + conditions.append(dataframe['ppo10'] > (params['sell_ab_18_ppo10_above_value']/2)) + #PPO25 BELOW + if 'include_sell_ab_19_ppo25_above_value' in params and params['include_sell_ab_19_ppo25_above_value']: + conditions.append(dataframe['ppo25'] > (params['sell_ab_19_ppo25_above_value']/2)) + + #PPO50 BELOW + if 'include_sell_ab_20_ppo50_above_value' in params and params['include_sell_ab_20_ppo50_above_value']: + conditions.append(dataframe['ppo50'] > (params['sell_ab_20_ppo50_above_value']/2)) + #PPO100 BELOW + if 'include_sell_ab_21_ppo100_above_value' in params and params['include_sell_ab_21_ppo100_above_value']: + conditions.append(dataframe['ppo100'] > (params['sell_ab_21_ppo100_above_value'])) + #PPO200 BELOW + if 'include_sell_ab_22_ppo200_above_value' in params and params['include_sell_ab_22_ppo200_above_value']: + conditions.append(dataframe['ppo200'] > (params['sell_ab_22_ppo200_above_value'])) + #PPO500 BELOW + if 'include_sell_ab_23_ppo500_above_value' in params and params['include_sell_ab_23_ppo500_above_value']: + conditions.append(dataframe['ppo500'] > (params['sell_ab_23_ppo500_above_value']*2)) + + ##USE AT A LATER STEP + + #convsmall BELOW + if 'include_sell_ab_24_convsmall_above_value' in params and params['include_sell_ab_24_convsmall_above_value']: + conditions.append(dataframe['convsmall'] > (params['sell_ab_24_convsmall_above_value']/2)) + #convmedium BELOW + if 'include_sell_ab_25_convmedium_above_value' in params and params['include_sell_ab_25_convmedium_above_value']: + conditions.append(dataframe['convmedium'] >(params['sell_ab_25_convmedium_above_value'])) + #convlarge BELOW + if 'include_sell_ab_26_convlarge_above_value' in params and params['include_sell_ab_26_convlarge_above_value']: + conditions.append(dataframe['convlarge'] > (params['sell_ab_26_convlarge_above_value'])) + #convultra BELOW + if 'include_sell_ab_27_convultra_above_value' in params and params['include_sell_ab_27_convultra_above_value']: + conditions.append(dataframe['convultra'] > (params['sell_ab_27_convultra_above_value']/2)) + #convdist BELOW + if 'include_sell_ab_28_convdist_above_value' in params and params['include_sell_ab_28_convdist_above_value']: + conditions.append(dataframe['convdist'] > (params['sell_ab_28_convdist_above_value'])) + + +#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + ##SMA'S GOING DOWN + + if 'sell_down_0a_sma3' in params and params['sell_down_0a_sma3']: + conditions.append((dataframe['sma3'].shift(1) >dataframe['sma3'])) + if 'sell_down_0b_sma5' in params and params['sell_down_0b_sma5']: + conditions.append((dataframe['sma5'].shift(1) >dataframe['sma5'])) + if 'sell_down_1_sma10' in params and params['sell_down_1_sma10']: + conditions.append((dataframe['sma10'].shift(1) >dataframe['sma10'])) + if 'sell_down_2_sma25' in params and params['sell_down_2_sma25']: + conditions.append((dataframe['sma25'].shift(1) >dataframe['sma25'])) + if 'sell_down_3_sma50' in params and params['sell_down_3_sma50']: + conditions.append((dataframe['sma50'].shift(2) >dataframe['sma50'])) + if 'sell_down_4_sma100' in params and params['sell_down_4_sma100']: + conditions.append((dataframe['sma100'].shift(3) >dataframe['sma100'])) + if 'sell_down_5_sma200' in params and params['sell_down_5_sma200']: + conditions.append((dataframe['sma200'].shift(4) >dataframe['sma200'])) + + if 'sell_down_6_sma400' in params and params['sell_down_6_sma400']: + conditions.append((dataframe['sma400'].shift(4) >dataframe['sma400'])) + if 'sell_down_7_sma10k' in params and params['sell_down_7_sma10k']: + conditions.append((dataframe['sma10k'].shift(5) >dataframe['sma10k'])) + # if 'sell_down_8_sma20k' in params and params['sell_down_8_sma20k']: + # conditions.append((dataframe['sma20k'].shift(5) >dataframe['sma20k'])) + # if 'sell_down_9_sma30k' in params and params['sell_down_9_sma30k']: + # conditions.append((dataframe['sma30k'].shift(5) >dataframe['sma30k'])) + + if 'sell_down_10_convsmall' in params and params['sell_down_10_convsmall']: + conditions.append((dataframe['convsmall'].shift(2) >dataframe['convsmall'])) + if 'sell_down_11_convmedium' in params and params['sell_down_11_convmedium']: + conditions.append((dataframe['convmedium'].shift(3) >dataframe['convmedium'])) + if 'sell_down_12_convlarge' in params and params['sell_down_12_convlarge']: + conditions.append((dataframe['convlarge'].shift(4) >dataframe['convlarge'])) + if 'sell_down_13_convultra' in params and params['sell_down_13_convultra']: + conditions.append((dataframe['convultra'].shift(4) >dataframe['convultra'])) + if 'sell_down_14_convdist' in params and params['sell_down_14_convdist']: + conditions.append((dataframe['convdist'].shift(4) >dataframe['convdist'])) + + if 'sell_down_15_vol50' in params and params['sell_down_15_vol50']: + conditions.append((dataframe['vol50'].shift(2) >dataframe['vol50'])) + if 'sell_down_16_vol100' in params and params['sell_down_16_vol100']: + conditions.append((dataframe['vol100'].shift(3) >dataframe['vol100'])) + if 'sell_down_17_vol175' in params and params['sell_down_17_vol175']: + conditions.append((dataframe['vol175'].shift(4) >dataframe['vol175'])) + if 'sell_down_18_vol250' in params and params['sell_down_18_vol250']: + conditions.append((dataframe['vol250'].shift(4) >dataframe['vol250'])) + if 'sell_down_19_vol500' in params and params['sell_down_19_vol500']: + conditions.append((dataframe['vol500'].shift(4) >dataframe['vol500'])) + + if 'sell_down_20_vol1000' in params and params['sell_down_20_vol1000']: + conditions.append((dataframe['vol1000'].shift(4) >dataframe['vol1000'])) + if 'sell_down_21_vol100mean' in params and params['sell_down_21_vol100mean']: + conditions.append((dataframe['vol100mean'].shift(4) >dataframe['vol100mean'])) + if 'sell_down_22_vol250mean' in params and params['sell_down_22_vol250mean']: + conditions.append((dataframe['vol250mean'].shift(4) >dataframe['vol250mean'])) + + if 'up_20_conv3' in params and params['up_20_conv3']: + conditions.append(((dataframe['conv3'].shift(25) < dataframe['conv3'])&(dataframe['conv3'].shift(50) < dataframe['conv3']))) + if 'up_21_vol5' in params and params['up_21_vol5']: + conditions.append(((dataframe['vol5'].shift(25) < dataframe['vol5'])&(dataframe['vol5'].shift(50) < dataframe['vol5']))) + if 'up_22_vol5ultra' in params and params['up_22_vol5ultra']: + conditions.append(((dataframe['vol5ultra'].shift(25) < dataframe['vol5ultra'])&(dataframe['vol5ultra'].shift(50) < dataframe['vol5ultra']))) + if 'up_23_vol1ultra' in params and params['up_23_vol1ultra']: + conditions.append(((dataframe['vol1ultra'].shift(25) < dataframe['vol1ultra'])& (dataframe['vol1ultra'].shift(50) < dataframe['vol1ultra']))) + if 'up_24_vol1' in params and params['up_24_vol1']: + conditions.append(((dataframe['vol1'].shift(30) < dataframe['vol1'])&(dataframe['vol1'].shift(10) < dataframe['vol1']))) + if 'up_25_vol5inc24' in params and params['up_25_vol5inc24']: + conditions.append((dataframe['vol5inc24'].shift(50) < dataframe['vol5inc24'])) + + +#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + ##ABOVE / BELOW SMAS 1 above/ 0 None / -1 below + + #SMA10 + conditions.append((dataframe['close'] > dataframe['sma10'])|(0.5 > params['ab_1_sma10'])) + conditions.append((dataframe['close'] < dataframe['sma10'])|(-0.5 < params['ab_1_sma10'])) + #SMA25 + conditions.append((dataframe['close'] > dataframe['sma25'])|(0.5 > params['ab_2_sma25'])) + conditions.append((dataframe['close'] < dataframe['sma25'])|(-0.5 < params['ab_2_sma25'])) + #SMA50 + conditions.append((dataframe['close'] > dataframe['sma50'])|(0.5 > params['ab_3_sma50'])) + conditions.append((dataframe['close'] < dataframe['sma50'])|(-0.5 < params['ab_3_sma50'])) + + + #SMA100 + conditions.append((dataframe['close'] > dataframe['sma100'])|(0.5 > params['ab_4_sma100'])) + conditions.append((dataframe['close'] < dataframe['sma100'])|(-0.5 < params['ab_4_sma100'])) + #SMA100 + conditions.append((dataframe['close'] > dataframe['sma200'])|(0.5 > params['ab_5_sma200'])) + conditions.append((dataframe['close'] < dataframe['sma200'])|(-0.5 < params['ab_5_sma200'])) + #SMA400 + conditions.append((dataframe['close'] > dataframe['sma400'])|(0.5 > params['ab_6_sma400'])) + conditions.append((dataframe['close'] < dataframe['sma400'])|(-0.5 < params['ab_6_sma400'])) + #SMA10k + conditions.append((dataframe['close'] > dataframe['sma10k'])|(0.5 > params['ab_7_sma10k'])) + conditions.append((dataframe['close'] < dataframe['sma10k'])|(-0.5 < params['ab_7_sma10k'])) + +#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + ##DOWNSWINGS / UPSWINGS PPO'S + + #ppo5 UP OR DOWN (1 UP, 0 NOTHING, -1 DOWN) + conditions.append((dataframe['ppo5'].shift(2) <dataframe['ppo5'])|(0.5 > params['sell_swings_1_ppo5_up_or_down_bool'])) + conditions.append((dataframe['ppo5'].shift(2) >dataframe['ppo5'])|(-0.5 < params['sell_swings_1_ppo5_up_or_down_bool'])) + #ppo10 + conditions.append((dataframe['ppo10'].shift(3) <dataframe['ppo10'])|(0.5 > params['sell_swings_2_ppo10_up_or_down_bool'])) + conditions.append((dataframe['ppo10'].shift(3) >dataframe['ppo10'])|(-0.5 < params['sell_swings_2_ppo10_up_or_down_bool'])) + #ppo25 + #conditions.append((dataframe['ppo25'].shift(3) <dataframe['ppo25'])|(0.5 > params['sell_swings_3_ppo25_up_or_down_bool'])) + conditions.append((dataframe['ppo25'].shift(3) >dataframe['ppo25'])|(-0.5 < params['sell_swings_3_ppo25_up_or_down_bool'])) + + #ppo50 + #conditions.append((dataframe['ppo50'].shift(3 <dataframe['ppo50'])|(0.5 > params['sell_swings_4_ppo50_up_or_down_bool'])) + conditions.append((dataframe['ppo50'].shift(3) >dataframe['ppo50'])|(-0.5 < params['sell_swings_4_ppo50_up_or_down_bool'])) + #ppo100 + #conditions.append((dataframe['ppo100'].shift(4) <dataframe['ppo100'])|(0.5 > params['sell_swings_5_ppo100_up_or_down_bool'])) + conditions.append((dataframe['ppo100'].shift(4) >dataframe['ppo100'])|(-0.5 < params['sell_swings_5_ppo100_up_or_down_bool'])) + #ppo200 + #conditions.append((dataframe['ppo200'].shift(4) <dataframe['ppo200'])|(0.5 > params['sell_swings_6_ppo200_up_or_down_bool'])) + conditions.append((dataframe['ppo200'].shift(4) >dataframe['ppo200'])|(-0.5 < params['sell_swings_6_ppo200_up_or_down_bool'])) + + #ppo500 + #conditions.append((dataframe['ppo500'].shift(5) <dataframe['ppo500'])|(0.5 > params['sell_swings_7_ppo500_up_or_down_bool'])) + conditions.append((dataframe['ppo500'].shift(5) >dataframe['ppo500'])|(-0.5 < params['sell_swings_7_ppo500_up_or_down_bool'])) + + #roc50 + #conditions.append((dataframe['roc50'].shift(3) <dataframe['roc50'])|(0.5 > params['sell_swings_8_roc50_up_or_down_bool'])) + conditions.append((dataframe['roc50'].shift(3) >dataframe['roc50'])|(-0.5 < params['sell_swings_8_roc50_up_or_down_bool'])) + #roc10 + #conditions.append((dataframe['roc10'].shift(2) <dataframe['roc10'])|(0.5 > params['sell_swings_9_roc10_up_or_down_bool'])) + conditions.append((dataframe['roc10'].shift(2) >dataframe['roc10'])|(-0.5 < params['sell_swings_9_roc10_up_or_down_bool'])) + + +#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + ##DISTANCES/ROC + + ##FOR MORE TOP SELLERS + #dist50 MORE THAN + if 'include_sell_dist_1_dist50_more_value' in params and params['include_sell_dist_1_dist50_more_value']: + conditions.append(dataframe['dist50'] > (params['sell_dist_1_dist50_more_value'])) + #dist200 MORE THAN + if 'include_sell_dist_2_dist200_more_value' in params and params['include_sell_dist_2_dist200_more_value']: + conditions.append(dataframe['dist200'] > (params['sell_dist_2_dist200_more_value'])) + + #dist400 MORE THAN + if 'include_sell_dist_3_dist400_more_value' in params and params['include_sell_dist_3_dist400_more_value']: + conditions.append(dataframe['dist400'] > (params['sell_dist_3_dist400_more_value'])) + #dist10k MORE THAN + if 'include_sell_dist_4_dist10k_more_value' in params and params['include_sell_dist_4_dist10k_more_value']: + conditions.append(dataframe['dist10k'] > (params['sell_dist_4_dist10k_more_value'])) + + ##FOR MORE TOP SELLERS + #more =further from top bol up + #dist_upbol50 MORE THAN + if 'include_sell_dist_5_dist_upbol50_more_value' in params and params['include_sell_dist_5_dist_upbol50_more_value']: + conditions.append(dataframe['dist_upbol50'] > (params['sell_dist_5_dist_upbol50_more_value']/2)) + #dist_upbol100 MORE THAN + if 'include_sell_dist_6_dist_upbol100_more_value' in params and params['include_sell_dist_6_dist_upbol100_more_value']: + conditions.append(dataframe['dist_upbol100'] > (params['sell_dist_6_dist_upbol100_more_value']/2)) + + + ##for bot bol prevent seller + # #less =closer to bot bol + #dist_upbol50 LESS THAN. + #if 'include_sell_dist_7_dist_lowbol50_more_value' in params and params['include_sell_dist_7_dist_lowbol50_more_value']: + # conditions.append(dataframe['dist_lowbol50'] > (params['sell_dist_7_dist_lowbol50_more_value']/2)) + #dist_upbol100 LESS THAN + # if 'include_sell_dist_8_dist_lowbol100_more_value' in params and params['include_sell_dist_8_dist_lowbol100_more_value']: + # conditions.append(dataframe['dist_lowbol100'] > (params['sell_dist_8_dist_lowbol100_more_value']/2)) + + + + ##others + #roc50sma LESS THAN + if 'include_sell_dist_7_roc50sma_less_value' in params and params['include_sell_dist_7_roc50sma_less_value']: + conditions.append(dataframe['roc50sma'] < (params['sell_dist_7_roc50sma_less_value'])*2) + #roc200sma LESS THAN + if 'include_sell_dist_8_roc200sma_less_value' in params and params['include_sell_dist_8_roc200sma_less_value']: + conditions.append(dataframe['roc200sma'] < (params['sell_dist_8_roc200sma_less_value'])*2) + + ##ENABLE TO BUY AWAY FROM HIGH + # #HIGH500 TO CLOSE MORE THAN + #if 'include_sell_dist_9_high100_more_value' in params and params['include_sell_dist_9_high100_more_value']: + # conditions.append((dataframe['high100']-dataframe['close']) > ((dataframe['high100']/100* (params['sell_dist_9_high100_more_value'])) + +#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + + + + + # Check that volume is not 0 + conditions.append(dataframe['volume'] > 0) + + + + + if conditions: + + + # ##ENABLE PRODUCTION BUYS + # dataframe.loc[ + # (add_production_buys_mid(dataframe)), + # 'buy'] = 1 + # + + + dataframe.loc[ + (~(reduce(lambda x, y: x & y, conditions)))&OPTIMIZED_RULE(dataframe,params), + 'buy'] = 1 + + return dataframe + + return populate_buy_trend + + @staticmethod + def indicator_space() -> List[Dimension]: + """ + Define your Hyperopt space for searching strategy parameters + """ + return [ + + +#------------------------------------------------------------------------------------------------------- + + ## CUSTOM RULE TRESHOLDS + + # SKDecimal(5.0, 7.0,decimals=1, name='sell_trigger_0_roc_ab_value'),# 5 range 5-7 or 4-7 + # SKDecimal(3.2, 4.5,decimals=1, name='sell_trigger_0_roc2_ab_value'),#3.8 range 3.2-4.5 + # Integer(77, 82, name='sell_trigger_0_rsi_ab_value'),#79 range 82-77 + # Integer(90, 95, name='sell_trigger_0_rsi5_ab_value'),#94 range 95-90 + # Integer(63, 67, name='sell_trigger_0_rsi50_ab_value'),#66 range 67-63 + +#------------------------------------------------------------------------------------------------------- + + ##MAIN + + Categorical([1, 2, 3], name='sell_main_1_trend_strength'), #BIG TREND STR + Categorical([1, 2, 3], name='sell_main_2_trend_strength'), #SMALL UPTREND STR + + + #Categorical([-1, 0, 1], name='sell_main_2_small_uptrend_downtrend'), #SMALL UPTREND ON/OFF 1 is on -1 is down + +#------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------- + + ##INCLUDE/EXCLUDE RULES + + Categorical([True, False], name='include_sell_ab_9_rsi_above_value'), + Categorical([True, False], name='include_sell_ab_10_rsi_recent_pit_2_value'), + Categorical([True, False], name='include_sell_ab_11_rsi_recent_pit_4_value'), + Categorical([True, False], name='include_sell_ab_12_rsi5_above_value'), + Categorical([True, False], name='include_sell_ab_13_rsi50_above_value'), + + Categorical([True, False], name='include_sell_ab_14_roc_above_value'), + Categorical([True, False], name='include_sell_ab_15_roc50_above_value'), + Categorical([True, False], name='include_sell_ab_16_roc2_above_value'), + + Categorical([True, False], name='include_sell_ab_17_ppo5_above_value'), + Categorical([True, False], name='include_sell_ab_18_ppo10_above_value'), + Categorical([True, False], name='include_sell_ab_19_ppo25_above_value'), + + Categorical([True, False], name='include_sell_ab_20_ppo50_above_value'), + Categorical([True, False], name='include_sell_ab_21_ppo100_above_value'), + Categorical([True, False], name='include_sell_ab_22_ppo200_above_value'), + Categorical([True, False], name='include_sell_ab_23_ppo500_above_value'), + + ##USE AT A LATER STEP + Categorical([True, False], name='include_sell_ab_24_convsmall_above_value'), + Categorical([True, False], name='include_sell_ab_25_convmedium_above_value'), + Categorical([True, False], name='include_sell_ab_26_convlarge_above_value'), + Categorical([True, False], name='include_sell_ab_27_convultra_above_value'), + Categorical([True, False], name='include_sell_ab_28_convdist_above_value'), + + Categorical([True, False], name='include_sell_dist_1_dist50_more_value'), + Categorical([True, False], name='include_sell_dist_2_dist200_more_value'), + Categorical([True, False], name='include_sell_dist_3_dist400_more_value'), + Categorical([True, False], name='include_sell_dist_4_dist10k_more_value'), + + Categorical([True, False], name='include_sell_dist_5_dist_upbol50_more_value'), + Categorical([True, False], name='include_sell_dist_6_dist_upbol100_more_value'), + + + # FOR MORE DOWNTREND BUYS LIKELY + # Categorical([True, False], name='include_sell_dist_7_dist_lowbol50_more_value'), + # Categorical([True, False], name='include_sell_dist_8_dist_lowbol100_more_value'), + + #MORE LIKE TRIGGERS + Categorical([True, False], name='include_sell_dist_7_roc50sma_less_value'), + Categorical([True, False], name='include_sell_dist_8_roc200sma_less_value'), + + ##below high 100 + #Categorical([True, False], name='include_sell_dist_9_high100_more_value'), + +#------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------- + + ##ABOVE/BELOW VALUES + + Integer(35, 82, name='sell_ab_9_rsi_above_value'), + Integer(18, 35, name='sell_ab_10_rsi_recent_pit_2_value'), + Integer(18, 35, name='sell_ab_11_rsi_recent_pit_4_value'), + Integer(70, 91, name='sell_ab_12_rsi5_above_value'), + Integer(37, 60, name='sell_ab_13_rsi50_above_value'), + + Integer(-4, 10, name='sell_ab_14_roc_above_value'),#/2 + Integer(-2, 8, name='sell_ab_15_roc50_above_value'), + Integer(-4, 8, name='sell_ab_16_roc2_above_value'),#/2 + +#-------------------------------- + + ##CHANGE DEPENDING WHAT TYPE OF SELL --> PEAK OR DOWTRENDS + Integer(-4, 6, name='sell_ab_17_ppo5_above_value'),#/2 + Integer(-4, 6, name='sell_ab_18_ppo10_above_value'),#/2 + Integer(-10, 8, name='sell_ab_19_ppo25_above_value'),#/2 + + Integer(-10, 8, name='sell_ab_20_ppo50_above_value'),#/2 + Integer(-6, 6, name='sell_ab_21_ppo100_above_value'), + Integer(-6, 6, name='sell_ab_22_ppo200_above_value'), + Integer(-4, 5, name='sell_ab_23_ppo500_above_value'),#*2 + + # ##USE AT A LATER STEP + # + # Integer(-1, 6, name='sell_ab_24_convsmall_above_value'),#/2 # extreme 12 + # Integer(-1, 4, name='sell_ab_25_convmedium_above_value'),# extreme 6 + # Integer(-1, 7, name='sell_ab_26_convlarge_above_value'),# extreme 12 + # Integer(-1, 8, name='sell_ab_27_convultra_above_value'),#/2# extreme 12 + # + # Integer(-1, 6, name='sell_ab_28_convdist_above_value'), #very extreme not useful 10+ + +#------------------------------------------------------------------------------------------------------- + + #SMA'S GOING DOWN + + Categorical([True, False], name='sell_down_0a_sma3'), + Categorical([True, False], name='sell_down_0b_sma5'), + Categorical([True, False], name='sell_down_1_sma10'), + Categorical([True, False], name='sell_down_2_sma25'), + Categorical([True, False], name='sell_down_3_sma50'), + Categorical([True, False], name='sell_down_4_sma100'), + Categorical([True, False], name='sell_down_5_sma200'), + + Categorical([True, False], name='sell_down_6_sma400'), + Categorical([True, False], name='sell_down_7_sma10k'), + # Categorical([True, False], name='sell_down_8_sma20k'), + # Categorical([True, False], name='sell_down_9_sma30k'), + + Categorical([True, False], name='sell_down_10_convsmall'), + Categorical([True, False], name='sell_down_11_convmedium'), + Categorical([True, False], name='sell_down_12_convlarge'), + Categorical([True, False], name='sell_down_13_convultra'), + Categorical([True, False], name='sell_down_14_convdist'), + + Categorical([True, False], name='sell_down_15_vol50'), + Categorical([True, False], name='sell_down_16_vol100'), + Categorical([True, False], name='sell_down_17_vol175'), + Categorical([True, False], name='sell_down_18_vol250'), + Categorical([True, False], name='sell_down_19_vol500'), + + Categorical([True, False], name='sell_down_20_vol1000'), + Categorical([True, False], name='sell_down_21_vol100mean'), + Categorical([True, False], name='sell_down_22_vol250mean'), + +#------------------------------------------------------------------------------------------------------- + + ##ABOVE/BELOW SMAS + + Categorical([-1, 0, 1], name='sell_ab_1_sma10'), + Categorical([-1, 0, 1], name='sell_ab_2_sma25'), + Categorical([-1, 0, 1], name='sell_ab_3_sma50'), + + Categorical([-1, 0, 1], name='sell_ab_4_sma100'), + Categorical([-1, 0, 1], name='sell_ab_5_sma200'), + Categorical([-1, 0, 1], name='sell_ab_6_sma400'), + Categorical([-1, 0, 1], name='sell_ab_7_sma10k'), + +#------------------------------------------------------------------------------------------------------- + + ##DOWNSWINGS / UPSWINGS PPO'S + + ##UP OR DOWN (1 UP, 0 NOTHING, -1 DOWN) + + Categorical([-1, 0, 1], name='sell_swings_1_ppo5_up_or_down_bool'), + Categorical([-1, 0, 1], name='sell_swings_2_ppo10_up_or_down_bool'), + Categorical([-1, 0], name='sell_swings_3_ppo25_up_or_down_bool'), + + Categorical([-1, 0], name='sell_swings_4_ppo50_up_or_down_bool'), + Categorical([-1, 0], name='sell_swings_5_ppo100_up_or_down_bool'), + Categorical([-1, 0], name='sell_swings_6_ppo200_up_or_down_bool'), + Categorical([-1, 0], name='sell_swings_7_ppo500_up_or_down_bool'), + + Categorical([-1, 0], name='sell_swings_8_roc50_up_or_down_bool'), + Categorical([-1, 0], name='sell_swings_9_roc10_up_or_down_bool'), + +#------------------------------------------------------------------------------------------------------- + + #DISTANCES + + #FOR MORE TOP SELLERS + Integer(-6, 14, name='sell_dist_1_dist50_more_value'), #extreme, useless -4 ,30 + Integer(-8, 20, name='sell_dist_2_dist200_more_value'), #extreme, useless -12-40 + Integer(-15, 30, name='sell_dist_3_dist400_more_value'), + Integer(-15, 35, name='sell_dist_4_dist10k_more_value'), + + #FOR MORE TOP SELLERS + Integer(-30, 25, name='sell_dist_5_dist_upbol50_more_value'),#/2 + Integer(-30, 25, name='sell_dist_6_dist_upbol100_more_value'),#/2 + + + #FOR MORE DOWNTREND BUYS LIKELY + # Integer(-8, 50, name='sell_dist_7_dist_lowbol50_more_value'),#/2 ##set to more, as in higher from lower boll + # Integer(-8, 50, name='sell_dist_8_dist_lowbol100_more_value'),#/2 ##set to more, as in higher from lower boll + + # Integer(-70, 40, name='sell_dist_7_roc50sma_more_value'),#*2 ##fix less more + # Integer(-40, 12, name='sell_dist_8_roc200sma_more_value'),#*2 + + ##below high 100 + #Integer(0, 0, name='sell_dist_9_high100_more_value'), + +#------------------------------------------------------------------------------------------------------- + + + + + ] + + + + @staticmethod + def sell_strategy_generator(params: Dict[str, Any]) -> Callable: + """ + Define the sell strategy parameters to be used by hyperopt + """ + def populate_sell_trend(dataframe: DataFrame, metadata: dict) -> DataFrame: + """ + Sell strategy Hyperopt will build and use + """ + # print(params) + conditions = [] + # GUARDS AND TRENDS + + +#--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +#--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + ##MAIN SELECTORS + +#-------------------- + + ##VOLATILITY + + conditions.append(dataframe['vol_mid'] > 0 ) + + # conditions.append((dataframe['vol_low'] > 0) |(dataframe['vol_mid'] > 0) ) + + # conditions.append((dataframe['vol_high'] > 0) |(dataframe['vol_mid'] > 0) ) + +#-------------------- + + + ##PICKS TREND COMBO + + conditions.append( + + (dataframe['uptrend'] >= params['main_1_trend_strength']) + |#OR & + (dataframe['uptrendsmall'] >= params['main_2_trend_strength']) + + ) + + ##UPTREND + #conditions.append(dataframe['uptrend'] >= params['main_1_trend_strength']) + ##DOWNTREND + #conditions.append(dataframe['downtrend'] >= params['main_1_trend_strength']) + ##NOTREND + #conditions.append((dataframe['uptrend'] <1)&(dataframe['downtrend'] <1)) + +#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + ##ABOVE/BELOW VALUES + + #RSI BELOW + if 'include_ab_9_rsi_below_value' in params and params['include_ab_9_rsi_below_value']: + conditions.append(dataframe['rsi'] < params['ab_9_rsi_below_value']) + #RSI RECENT PEAK 5 + if 'include_ab_10_rsi_recent_peak_2_value' in params and params['include_ab_10_rsi_recent_peak_2_value']: + conditions.append(dataframe['rsi'].rolling(2).max() < params['ab_10_rsi_recent_peak_2_value']) + + #RSI RECENT PEAK 12 + if 'include_ab_11_rsi_recent_peak_4_value' in params and params['include_ab_11_rsi_recent_peak_4_value']: + conditions.append(dataframe['rsi'].rolling(4).max() < params['ab_11_rsi_recent_peak_4_value']) + #RSI5 BELOW + if 'include_ab_12_rsi5_below_value' in params and params['include_ab_12_rsi5_below_value']: + conditions.append(dataframe['rsi5'] < params['ab_12_rsi5_below_value']) + #RSI50 BELOW + if 'include_ab_13_rsi50_below_value' in params and params['include_ab_13_rsi50_below_value']: + conditions.append(dataframe['rsi50'] < params['ab_13_rsi50_below_value']) + +#----------------------- + + #ROC BELOW + if 'include_ab_14_roc_below_value' in params and params['include_ab_14_roc_below_value']: + conditions.append(dataframe['roc'] < (params['ab_14_roc_below_value']/2)) + #ROC50 BELOW + if 'include_ab_15_roc50_below_value' in params and params['include_ab_15_roc50_below_value']: + conditions.append(dataframe['roc50'] < (params['ab_15_roc50_below_value'])) + #ROC2 BELOW + if 'include_ab_16_roc2_below_value' in params and params['include_ab_16_roc2_below_value']: + conditions.append(dataframe['roc2'] < (params['ab_16_roc2_below_value']/2)) + +#----------------------- + + #PPO5 BELOW + if 'include_ab_17_ppo5_below_value' in params and params['include_ab_17_ppo5_below_value']: + conditions.append(dataframe['ppo5'] < (params['ab_17_ppo5_below_value']/2)) + #PPO10 BELOW + if 'include_ab_18_ppo10_below_value' in params and params['include_ab_18_ppo10_below_value']: + conditions.append(dataframe['ppo10'] < (params['ab_18_ppo10_below_value']/2)) + #PPO25 BELOW + if 'include_ab_19_ppo25_below_value' in params and params['include_ab_19_ppo25_below_value']: + conditions.append(dataframe['ppo25'] < (params['ab_19_ppo25_below_value']/2)) + + #PPO50 BELOW + if 'include_ab_20_ppo50_below_value' in params and params['include_ab_20_ppo50_below_value']: + conditions.append(dataframe['ppo50'] < (params['ab_20_ppo50_below_value']/2)) + #PPO100 BELOW + if 'include_ab_21_ppo100_below_value' in params and params['include_ab_21_ppo100_below_value']: + conditions.append(dataframe['ppo100'] < (params['ab_21_ppo100_below_value'])) + #PPO200 BELOW + if 'include_ab_22_ppo200_below_value' in params and params['include_ab_22_ppo200_below_value']: + conditions.append(dataframe['ppo200'] < (params['ab_22_ppo200_below_value'])) + #PPO500 BELOW + if 'include_ab_23_ppo500_below_value' in params and params['include_ab_23_ppo500_below_value']: + conditions.append(dataframe['ppo500'] < (params['ab_23_ppo500_below_value']*2)) + + ##USE AT A LATER STEP + + #convsmall BELOW + if 'include_ab_24_convsmall_below_value' in params and params['include_ab_24_convsmall_below_value']: + conditions.append(dataframe['convsmall'] < (params['ab_24_convsmall_below_value']/2)) + #convmedium BELOW + if 'include_ab_25_convmedium_below_value' in params and params['include_ab_25_convmedium_below_value']: + conditions.append(dataframe['convmedium'] < (params['ab_25_convmedium_below_value'])) + #convlarge BELOW + if 'include_ab_26_convlarge_below_value' in params and params['include_ab_26_convlarge_below_value']: + conditions.append(dataframe['convlarge'] < (params['ab_26_convlarge_below_value'])) + #convultra BELOW + if 'include_ab_27_convultra_below_value' in params and params['include_ab_27_convultra_below_value']: + conditions.append(dataframe['convultra'] < (params['ab_27_convultra_below_value']/2)) + #convdist BELOW + if 'include_ab_28_convdist_below_value' in params and params['include_ab_28_convdist_below_value']: + conditions.append(dataframe['convdist'] < (params['ab_28_convdist_below_value'])) + +#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + ##SMA'S GOING UP + + if 'up_0a_sma3' in params and params['up_0a_sma3']: + conditions.append((dataframe['sma3'].shift(1) <dataframe['sma3'])) + if 'up_0b_sma5' in params and params['up_0b_sma5']: + conditions.append((dataframe['sma5'].shift(1) <dataframe['sma5'])) + if 'up_1_sma10' in params and params['up_1_sma10']: + conditions.append((dataframe['sma10'].shift(1) <dataframe['sma10'])) + if 'up_2_sma25' in params and params['up_2_sma25']: + conditions.append((dataframe['sma25'].shift(1) <dataframe['sma25'])) + if 'up_3_sma50' in params and params['up_3_sma50']: + conditions.append((dataframe['sma50'].shift(2) <dataframe['sma50'])) + if 'up_4_sma100' in params and params['up_4_sma100']: + conditions.append((dataframe['sma100'].shift(3) <dataframe['sma100'])) + if 'up_5_sma200' in params and params['up_5_sma200']: + conditions.append((dataframe['sma200'].shift(4) <dataframe['sma200'])) + + if 'up_6_sma400' in params and params['up_6_sma400']: + conditions.append((dataframe['sma400'].shift(4) <dataframe['sma400'])) + if 'up_7_sma10k' in params and params['up_7_sma10k']: + conditions.append((dataframe['sma10k'].shift(5) <dataframe['sma10k'])) + # if 'up_8_sma20k' in params and params['up_8_sma20k']: + # conditions.append((dataframe['sma20k'].shift(5) <dataframe['sma20k'])) + # if 'up_9_sma30k' in params and params['up_9_sma30k']: + # conditions.append((dataframe['sma30k'].shift(5) <dataframe['sma30k'])) + + if 'up_10_convsmall' in params and params['up_10_convsmall']: + conditions.append((dataframe['convsmall'].shift(2) <dataframe['convsmall'])) + if 'up_11_convmedium' in params and params['up_11_convmedium']: + conditions.append((dataframe['convmedium'].shift(3) <dataframe['convmedium'])) + if 'up_12_convlarge' in params and params['up_12_convlarge']: + conditions.append((dataframe['convlarge'].shift(4) <dataframe['convlarge'])) + if 'up_13_convultra' in params and params['up_13_convultra']: + conditions.append((dataframe['convultra'].shift(4) <dataframe['convultra'])) + if 'up_14_convdist' in params and params['up_14_convdist']: + conditions.append((dataframe['convdist'].shift(4) <dataframe['convdist'])) + + if 'up_15_vol50' in params and params['up_15_vol50']: + conditions.append((dataframe['vol50'].shift(2) <dataframe['vol50'])) + if 'up_16_vol100' in params and params['up_16_vol100']: + conditions.append((dataframe['vol100'].shift(3) <dataframe['vol100'])) + if 'up_17_vol175' in params and params['up_17_vol175']: + conditions.append((dataframe['vol175'].shift(4) <dataframe['vol175'])) + if 'up_18_vol250' in params and params['up_18_vol250']: + conditions.append((dataframe['vol250'].shift(4) <dataframe['vol250'])) + if 'up_19_vol500' in params and params['up_19_vol500']: + conditions.append((dataframe['vol500'].shift(4) <dataframe['vol500'])) + + if 'up_20_vol1000' in params and params['up_20_vol1000']: + conditions.append((dataframe['vol1000'].shift(4) <dataframe['vol1000'])) + if 'up_21_vol100mean' in params and params['up_21_vol100mean']: + conditions.append((dataframe['vol100mean'].shift(4) <dataframe['vol100mean'])) + if 'up_22_vol250mean' in params and params['up_22_vol250mean']: + conditions.append((dataframe['vol250mean'].shift(4) <dataframe['vol250mean'])) + + + if 'up_20_conv3' in params and params['up_20_conv3']: + conditions.append(((dataframe['conv3'].shift(25) < dataframe['conv3'])&(dataframe['conv3'].shift(50) < dataframe['conv3']))) + if 'up_21_vol5' in params and params['up_21_vol5']: + conditions.append(((dataframe['vol5'].shift(25) < dataframe['vol5'])&(dataframe['vol5'].shift(50) < dataframe['vol5']))) + if 'up_22_vol5ultra' in params and params['up_22_vol5ultra']: + conditions.append(((dataframe['vol5ultra'].shift(25) < dataframe['vol5ultra'])&(dataframe['vol5ultra'].shift(50) < dataframe['vol5ultra']))) + if 'up_23_vol1ultra' in params and params['up_23_vol1ultra']: + conditions.append(((dataframe['vol1ultra'].shift(25) < dataframe['vol1ultra'])& (dataframe['vol1ultra'].shift(50) < dataframe['vol1ultra']))) + if 'up_24_vol1' in params and params['up_24_vol1']: + conditions.append(((dataframe['vol1'].shift(30) < dataframe['vol1'])&(dataframe['vol1'].shift(10) < dataframe['vol1']))) + if 'up_25_vol5inc24' in params and params['up_25_vol5inc24']: + conditions.append((dataframe['vol5inc24'].shift(50) < dataframe['vol5inc24'])) + + +#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + ##ABOVE / BELOW SMAS 1 above/ 0 None / -1 below + + #SMA10 + conditions.append((dataframe['close'] > dataframe['sma10'])|(0.5 > params['ab_1_sma10'])) + conditions.append((dataframe['close'] < dataframe['sma10'])|(-0.5 < params['ab_1_sma10'])) + #SMA25 + conditions.append((dataframe['close'] > dataframe['sma25'])|(0.5 > params['ab_2_sma25'])) + conditions.append((dataframe['close'] < dataframe['sma25'])|(-0.5 < params['ab_2_sma25'])) + #SMA50 + conditions.append((dataframe['close'] > dataframe['sma50'])|(0.5 > params['ab_3_sma50'])) + conditions.append((dataframe['close'] < dataframe['sma50'])|(-0.5 < params['ab_3_sma50'])) + + + #SMA100 + conditions.append((dataframe['close'] > dataframe['sma100'])|(0.5 > params['ab_4_sma100'])) + conditions.append((dataframe['close'] < dataframe['sma100'])|(-0.5 < params['ab_4_sma100'])) + #SMA100 + conditions.append((dataframe['close'] > dataframe['sma200'])|(0.5 > params['ab_5_sma200'])) + conditions.append((dataframe['close'] < dataframe['sma200'])|(-0.5 < params['ab_5_sma200'])) + #SMA400 + conditions.append((dataframe['close'] > dataframe['sma400'])|(0.5 > params['ab_6_sma400'])) + conditions.append((dataframe['close'] < dataframe['sma400'])|(-0.5 < params['ab_6_sma400'])) + #SMA10k + conditions.append((dataframe['close'] > dataframe['sma10k'])|(0.5 > params['ab_7_sma10k'])) + conditions.append((dataframe['close'] < dataframe['sma10k'])|(-0.5 < params['ab_7_sma10k'])) + +#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + ##DOWNSWINGS / UPSWINGS PPO'S + + #ppo5 UP OR DOWN (1 UP, 0 NOTHING, -1 DOWN) + conditions.append((dataframe['ppo5'].shift(1) <dataframe['ppo5'])|(0.5 > params['swings_1_ppo5_up_or_down_bool'])) + conditions.append((dataframe['ppo5'].shift(1) >dataframe['ppo5'])|(-0.5 < params['swings_1_ppo5_up_or_down_bool'])) + #ppo10 + conditions.append((dataframe['ppo10'].shift(1) <dataframe['ppo10'])|(0.5 > params['swings_2_ppo10_up_or_down_bool'])) + conditions.append((dataframe['ppo10'].shift(1) >dataframe['ppo10'])|(-0.5 < params['swings_2_ppo10_up_or_down_bool'])) + #ppo25 + conditions.append((dataframe['ppo25'].shift(1) <dataframe['ppo25'])|(0.5 > params['swings_3_ppo25_up_or_down_bool'])) + #conditions.append((dataframe['ppo25'].shift(1) >dataframe['ppo25'])|(-0.5 < params['swings_3_ppo25_up_or_down_bool'])) + + #ppo50 + conditions.append((dataframe['ppo50'].shift(2) <dataframe['ppo50'])|(0.5 > params['swings_4_ppo50_up_or_down_bool'])) + #conditions.append((dataframe['ppo50'].shift(2) >dataframe['ppo50'])|(-0.5 < params['swings_4_ppo50_up_or_down_bool'])) + #ppo100 + conditions.append((dataframe['ppo100'].shift(3) <dataframe['ppo100'])|(0.5 > params['swings_5_ppo100_up_or_down_bool'])) + #conditions.append((dataframe['ppo100'].shift(3) >dataframe['ppo100'])|(-0.5 < params['swings_5_ppo100_up_or_down_bool'])) + #ppo200 + conditions.append((dataframe['ppo200'].shift(4) <dataframe['ppo200'])|(0.5 > params['swings_6_ppo200_up_or_down_bool'])) + #conditions.append((dataframe['ppo200'].shift(4) >dataframe['ppo200'])|(-0.5 < params['swings_6_ppo200_up_or_down_bool'])) + #ppo500 + conditions.append((dataframe['ppo500'].shift(5) <dataframe['ppo500'])|(0.5 > params['swings_7_ppo500_up_or_down_bool'])) + #conditions.append((dataframe['ppo500'].shift(5) >dataframe['ppo500'])|(-0.5 < params['swings_7_ppo500_up_or_down_bool'])) + + #roc50 + conditions.append((dataframe['roc50'].shift(2) <dataframe['roc50'])|(0.5 > params['swings_8_roc50_up_or_down_bool'])) + #conditions.append((dataframe['roc50'].shift(3) >dataframe['roc50'])|(-0.5 < params['swings_8_roc50_up_or_down_bool'])) + #roc10 + conditions.append((dataframe['roc10'].shift(1) <dataframe['roc10'])|(0.5 > params['swings_9_roc10_up_or_down_bool'])) + #conditions.append((dataframe['roc10'].shift(2) >dataframe['roc10'])|(-0.5 < params['swings_9_roc10_up_or_down_bool'])) + + +#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + ##DISTANCES/ROC + + #dist50 LESS THAN + if 'include_dist_1_dist50_less_value' in params and params['include_dist_1_dist50_less_value']: + conditions.append(dataframe['dist50'] < (params['dist_1_dist50_less_value'])) + #dist200 LESS THAN + if 'include_dist_2_dist200_less_value' in params and params['include_dist_2_dist200_less_value']: + conditions.append(dataframe['dist200'] < (params['dist_2_dist200_less_value'])) + + #dist400 LESS THAN + if 'include_dist_3_dist400_less_value' in params and params['include_dist_3_dist400_less_value']: + conditions.append(dataframe['dist400'] < (params['dist_3_dist400_less_value'])) + #dist10k LESS THAN + if 'include_dist_4_dist10k_less_value' in params and params['include_dist_4_dist10k_less_value']: + conditions.append(dataframe['dist10k'] < (params['dist_4_dist10k_less_value'])) + + #less =further from top bol + #dist_upbol50 LESS THAN + if 'include_dist_5_dist_upbol50_less_value' in params and params['include_dist_5_dist_upbol50_less_value']: + conditions.append(dataframe['dist_upbol50'] < (params['dist_5_dist_upbol50_less_value']/2)) + #dist_upbol100 LESS THAN + if 'include_dist_6_dist_upbol100_less_value' in params and params['include_dist_6_dist_upbol100_less_value']: + conditions.append(dataframe['dist_upbol100'] < (params['dist_6_dist_upbol100_less_value']/2)) + + # #less =closer to bot bol + # #dist_upbol50 LESS THAN + # if 'include_dist_7_dist_lowbol50_less_value' in params and params['include_dist_7_dist_lowbol50_less_value']: + # conditions.append(dataframe['dist_lowbol50'] < (params['dist_7_dist_lowbol50_less_value']/2)) + # #dist_upbol100 LESS THAN + # if 'include_dist_8_dist_lowbol100_less_value' in params and params['include_dist_8_dist_lowbol100_less_value']: + # conditions.append(dataframe['dist_lowbol100'] < (params['dist_8_dist_lowbol100_less_value']/2)) + + + + #others + ##roc50sma MORE THAN + if 'include_dist_7_roc50sma_less_value' in params and params['include_dist_7_roc50sma_less_value']: + conditions.append(dataframe['roc50sma'] < (params['dist_7_roc50sma_less_value']*2)) + #roc200sma MORE THAN + if 'include_dist_8_roc200sma_less_value' in params and params['include_dist_8_roc200sma_less_value']: + conditions.append(dataframe['roc200sma'] < (params['dist_8_roc200sma_less_value']*2)) + + ##ENABLE TO BUY AWAY FROM HIGH + # #HIGH500 TO CLOSE MORE THAN + #if 'include_dist_9_high100_more_value' in params and params['include_dist_9_high100_more_value']: + # conditions.append((dataframe['high100']-dataframe['close']) > ((dataframe['high100']/100* (params['dist_9_high100_more_value'])) + +#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + + + + # Check that volume is not 0 + conditions.append(dataframe['volume'] > 0) + + if conditions: + + + # ##ENABLE SELLS ALWAYS ON OTHER VOLATILITYS + # dataframe.loc[ + # ((dataframe['vol_low'] > 0) |(dataframe['vol_high'] > 0) ), + # 'sell'] = 1 + + + # ##ENABLE PRODUCTION SELLS + # dataframe.loc[ + # (add_production_sells_low(dataframe)), + # 'sell'] = 1 + # + + dataframe.loc[ + (~(reduce(lambda x, y: x & y, conditions)))&OPTIMIZED_RULE(dataframe,params), + 'sell'] = 1 + + return dataframe + + return populate_sell_trend + + @staticmethod + def sell_indicator_space() -> List[Dimension]: + """ + Define your Hyperopt space for searching sell strategy parameters + """ + return [ + + +#------------------------------------------------------------------------------------------------------- + + ## CUSTOM RULE TRESHOLDS + + # SKDecimal(5.0, 7.0,decimals=1, name='sell_trigger_0_roc_ab_value'),# 5 range 5-7 or 4-7 + # SKDecimal(3.2, 4.5,decimals=1, name='sell_trigger_0_roc2_ab_value'),#3.8 range 3.2-4.5 + # Integer(77, 82, name='sell_trigger_0_rsi_ab_value'),#79 range 82-77 + # Integer(90, 95, name='sell_trigger_0_rsi5_ab_value'),#94 range 95-90 + # Integer(63, 67, name='sell_trigger_0_rsi50_ab_value'),#66 range 67-63 + +#------------------------------------------------------------------------------------------------------- + + ##MAIN + + Categorical([1, 2, 3], name='main_1_trend_strength'), #UPTREND STR + Categorical([1, 2, 3], name='main_2_trend_strength'), #SMALL UPTREND STR + + + #Categorical([-1, 0, 1], name='main_2_small_uptrend_downtrend'), #SMALL UPTREND ON/OFF 1 is on -1 is down + +#------------------------------------------------------------------------------------------------------- + + ##INCLUDE/EXCLUDE RULES + + Categorical([True, False], name='include_ab_9_rsi_below_value'), + Categorical([True, False], name='include_ab_10_rsi_recent_peak_2_value'), + Categorical([True, False], name='include_ab_11_rsi_recent_peak_4_value'), + Categorical([True, False], name='include_ab_12_rsi5_below_value'), + Categorical([True, False], name='include_ab_13_rsi50_below_value'), + + Categorical([True, False], name='include_ab_14_roc_below_value'), + Categorical([True, False], name='include_ab_15_roc50_below_value'), + Categorical([True, False], name='include_ab_16_roc2_below_value'), + + Categorical([True, False], name='include_ab_17_ppo5_below_value'), + Categorical([True, False], name='include_ab_18_ppo10_below_value'), + Categorical([True, False], name='include_ab_19_ppo25_below_value'), + + Categorical([True, False], name='include_ab_20_ppo50_below_value'), + Categorical([True, False], name='include_ab_21_ppo100_below_value'), + Categorical([True, False], name='include_ab_22_ppo200_below_value'), + Categorical([True, False], name='include_ab_23_ppo500_below_value'), + + ##USE AT A LATER STEP + Categorical([True, False], name='include_ab_24_convsmall_below_value'), + Categorical([True, False], name='include_ab_25_convmedium_below_value'), + Categorical([True, False], name='include_ab_26_convlarge_below_value'), + Categorical([True, False], name='include_ab_27_convultra_below_value'), + + Categorical([True, False], name='include_ab_28_convdist_below_value'), + + Categorical([True, False], name='include_dist_1_dist50_less_value'), + Categorical([True, False], name='include_dist_2_dist200_less_value'), + Categorical([True, False], name='include_dist_3_dist400_less_value'), + Categorical([True, False], name='include_dist_4_dist10k_less_value'), + + Categorical([True, False], name='include_dist_5_dist_upbol50_less_value'), + Categorical([True, False], name='include_dist_6_dist_upbol100_less_value'), + + + # FOR MORE DOWNTREND BUYS LIKELY + # Categorical([True, False], name='include_dist_7_dist_lowbol50_less_value'), + # Categorical([True, False], name='include_dist_8_dist_lowbol100_less_value'), + + #MORE LIKE TRIGGERS + Categorical([True, False], name='include_dist_7_roc50sma_less_value'), + Categorical([True, False], name='include_dist_8_roc200sma_less_value'), + + ##below high 100 + #Categorical([True, False], name='include_dist_9_high100_more_value'), + + + +#------------------------------------------------------------------------------------------------------- + + ##ABOVE/BELOW VALUES + + Integer(35, 75, name='ab_9_rsi_below_value'), + Integer(60, 82, name='ab_10_rsi_recent_peak_2_value'), + Integer(60, 82, name='ab_11_rsi_recent_peak_4_value'), + Integer(40, 101, name='ab_12_rsi5_below_value'), + Integer(37, 73, name='ab_13_rsi50_below_value'), + + Integer(-6, 10, name='ab_14_roc_below_value'),#/2 + Integer(-8, 8, name='ab_15_roc50_below_value'), + Integer(-4, 6, name='ab_16_roc2_below_value'),#/2 + +#-------------------------------- + + Integer(-4, 4, name='ab_17_ppo5_below_value'),#/2 + Integer(-5, 5, name='ab_18_ppo10_below_value'),#/2 + Integer(-8, 10, name='ab_19_ppo25_below_value'),#/2 + + Integer(-6, 7, name='ab_20_ppo50_below_value'),#/2 + Integer(-6, 7, name='ab_21_ppo100_below_value'), + Integer(-5, 7, name='ab_22_ppo200_below_value'), + Integer(-4, 4, name='ab_23_ppo500_below_value'),#*2 + + ##USE AT A LATER STEP + + Integer(1, 12, name='ab_24_convsmall_below_value'),#/2 #final + Integer(1, 6, name='ab_25_convmedium_below_value'),#final + Integer(1, 15, name='ab_26_convlarge_below_value'), #final + Integer(2, 12, name='ab_27_convultra_below_value'),#/2 #final + + Integer(2, 30, name='ab_28_convdist_below_value'), + +#------------------------------------------------------------------------------------------------------- + + #SMA'S GOING UP + + Categorical([True, False], name='up_0a_sma3'), + Categorical([True, False], name='up_0b_sma5'), + Categorical([True, False], name='up_1_sma10'), + Categorical([True, False], name='up_2_sma25'), + Categorical([True, False], name='up_3_sma50'), + Categorical([True, False], name='up_4_sma100'), + Categorical([True, False], name='up_5_sma200'), + + Categorical([True, False], name='up_6_sma400'), + Categorical([True, False], name='up_7_sma10k'), + # Categorical([True, False], name='up_8_sma20k'), + # Categorical([True, False], name='up_9_sma30k'), + + Categorical([True, False], name='up_10_convsmall'), + Categorical([True, False], name='up_11_convmedium'), + Categorical([True, False], name='up_12_convlarge'), + Categorical([True, False], name='up_13_convultra'), + Categorical([True, False], name='up_14_convdist'), + + Categorical([True, False], name='up_15_vol50'), + Categorical([True, False], name='up_16_vol100'), + Categorical([True, False], name='up_17_vol175'), + Categorical([True, False], name='up_18_vol250'), + Categorical([True, False], name='up_19_vol500'), + + Categorical([True, False], name='up_20_vol1000'), + Categorical([True, False], name='up_21_vol100mean'), + Categorical([True, False], name='up_22_vol250mean'), + +#------------------------------------------------------------------------------------------------------- + + ##ABOVE/BELOW SMAS + + Categorical([-1, 0, 1], name='ab_1_sma10'), + Categorical([-1, 0, 1], name='ab_2_sma25'), + Categorical([-1, 0, 1], name='ab_3_sma50'), + + Categorical([-1, 0, 1], name='ab_4_sma100'), + Categorical([-1, 0, 1], name='ab_5_sma200'), + Categorical([-1, 0, 1], name='ab_6_sma400'), + Categorical([-1, 0, 1], name='ab_7_sma10k'), + +#------------------------------------------------------------------------------------------------------- + + ##DOWNSWINGS / UPSWINGS PPO'S + + ##UP OR DOWN (1 UP, 0 NOTHING, -1 DOWN) + + Categorical([-1, 0, 1], name='swings_1_ppo5_up_or_down_bool'), # -1 down, 1 up , 0 off + Categorical([-1, 0, 1],name='swings_2_ppo10_up_or_down_bool'), + Categorical([-1, 0, 1], name='swings_3_ppo25_up_or_down_bool'), #1 up , 0 off + + Categorical([0, 1], name='swings_4_ppo50_up_or_down_bool'), + Categorical([0, 1], name='swings_5_ppo100_up_or_down_bool'), + Categorical([0, 1], name='swings_6_ppo200_up_or_down_bool'), + Categorical([ 0, 1],name='swings_7_ppo500_up_or_down_bool'), + + Categorical([0, 1], name='swings_8_roc50_up_or_down_bool'), + Categorical([0, 1], name='swings_9_roc10_up_or_down_bool'), + +#------------------------------------------------------------------------------------------------------- + + ##DISTANCES + + Integer(-7, 14, name='dist_1_dist50_less_value'), ##extreme 8-30 + Integer(-8, 25, name='dist_2_dist200_less_value'), ##extreme 12 -40 + Integer(-12, 35, name='dist_3_dist400_less_value'), + Integer(-12, 40, name='dist_4_dist10k_less_value'), + + Integer(-25, 30, name='dist_5_dist_upbol50_less_value'),#/2 + Integer(-25, 30, name='dist_6_dist_upbol100_less_value'),#/2 + + + # FOR MORE DOWNTREND BUYS LIKELY + # Integer(-6, 100, name='dist_7_dist_lowbol50_less_value'),#/2 + # Integer(-6, 100, name='dist_8_dist_lowbol100_less_value'),#/2 + + ##MORE LIKE TRIGGERS + # Integer(-40, 70, name='dist_7_roc50sma_less_value'),#*2 ##pretty extreme + # Integer(-12, 40, name='dist_8_roc200sma_less_value'),#*2 + + ##below high 100 + #Integer(0, 0, name='dist_9_high100_more_value'), + +#------------------------------------------------------------------------------------------------------- + + + + + + ] + + +def OPTIMIZED_RULE(dataframe,params): + return( + + (dataframe['sma100'] < dataframe['close']) + + ) + +def add_production_buys_mid(dataframe): + return( + + MID_VOLATILITY(dataframe) + & + mid_volatility_buyer(dataframe) + ) + +def add_production_sells_mid(dataframe): + return( + + MID_VOLATILITY(dataframe) + & + mid_volatility_seller(dataframe) + ) + + From c9edf3bf4ac0b2ce5749ca994bb9ac97c1599fd3 Mon Sep 17 00:00:00 2001 From: theluxaz <theluxaz@gmail.com> Date: Wed, 13 Oct 2021 00:09:30 +0300 Subject: [PATCH 02/26] Updated the gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 16df71194..9e4ce834b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ config*.json *.sqlite logfile.txt user_data/* +freqtrade/user_data/* !user_data/strategy/sample_strategy.py !user_data/notebooks user_data/notebooks/* From 80b71790bc7612056b81630245384f8a19ef3ad4 Mon Sep 17 00:00:00 2001 From: theluxaz <theluxaz@gmail.com> Date: Wed, 13 Oct 2021 01:22:53 +0300 Subject: [PATCH 03/26] Added some bigfixes for sell_tag --- freqtrade/freqtradebot.py | 7 ++++--- freqtrade/optimize/optimize_reports.py | 10 ++++++---- freqtrade/persistence/migrations.py | 1 + freqtrade/persistence/models.py | 2 +- freqtrade/strategy/interface.py | 4 ++-- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 55828f763..3b973bb8b 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -856,14 +856,14 @@ class FreqtradeBot(LoggingMixin): """ Check and execute sell """ - print(str(sell_tag)+"1") + should_sell = self.strategy.should_sell( trade, sell_rate, datetime.now(timezone.utc), buy, sell, force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0 ) if should_sell.sell_flag: - logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}. Tag: {sell_tag}') + 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, sell_rate, should_sell,sell_tag) return True return False @@ -1142,7 +1142,8 @@ class FreqtradeBot(LoggingMixin): trade.sell_order_status = '' trade.close_rate_requested = limit trade.sell_reason = sell_reason.sell_reason - trade.sell_tag = sell_tag + if(sell_tag is not None): + trade.sell_tag = sell_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) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index fcead07ba..ee7af6844 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -399,6 +399,7 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency, 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) @@ -747,6 +748,11 @@ 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_tags("sell_tag",results['results_per_sell_tag'], stake_currency=stake_currency) + + if isinstance(table, str) and len(table) > 0: + print(' SELL TAG STATS '.center(len(table.splitlines()[0]), '=')) + print(table) table = text_table_sell_reason(sell_reason_stats=results['sell_reason_summary'], stake_currency=stake_currency) @@ -768,11 +774,7 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency: print(' SUMMARY METRICS '.center(len(table.splitlines()[0]), '=')) print(table) - table = text_table_tags("sell_tag",results['results_per_sell_tag'], stake_currency=stake_currency) - if isinstance(table, str) and len(table) > 0: - print(' SELL TAG STATS '.center(len(table.splitlines()[0]), '=')) - print(table) if isinstance(table, str) and len(table) > 0: print('=' * len(table.splitlines()[0])) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index db93cf8b0..0f07c13b5 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -48,6 +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') # If ticker-interval existed use that, else null. if has_column(cols, 'ticker_interval'): timeframe = get_column_def(cols, 'timeframe', 'ticker_interval') diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index b06386810..0fdaba5ac 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -326,7 +326,7 @@ class LocalTrade(): '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': self.sell_tag, + 'sell_tag': (f' ({self.sell_tag})' if self.sell_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, diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 68b65b293..be552282d 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -549,7 +549,7 @@ class IStrategy(ABC, HyperStrategyMixin): """ if not isinstance(dataframe, DataFrame) or dataframe.empty: logger.warning(f'Empty candle (OHLCV) data for pair {pair}') - return False, False, None + return False, False, None, None latest_date = dataframe['date'].max() latest = dataframe.loc[dataframe['date'] == latest_date].iloc[-1] @@ -564,7 +564,7 @@ class IStrategy(ABC, HyperStrategyMixin): 'Outdated history for pair %s. Last tick is %s minutes old', pair, int((arrow.utcnow() - latest_date).total_seconds() // 60) ) - return False, False, None + return False, False, None, None buy = latest[SignalType.BUY.value] == 1 From 02243b1a2ba6bae679f0bc4aff5a832f9a7bbff9 Mon Sep 17 00:00:00 2001 From: theluxaz <theluxaz@gmail.com> Date: Wed, 13 Oct 2021 01:34:29 +0300 Subject: [PATCH 04/26] minifix --- freqtrade/freqtradebot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 3b973bb8b..e1734926c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -701,6 +701,8 @@ class FreqtradeBot(LoggingMixin): (buy, sell) = (False, False) + sell_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, From af74850e794997ad58ffcaf4bb78b20f754a2ed1 Mon Sep 17 00:00:00 2001 From: theluxaz <37055144+theluxaz@users.noreply.github.com> Date: Wed, 13 Oct 2021 02:07:23 +0300 Subject: [PATCH 05/26] Update README.md --- README.md | 205 +----------------------------------------------------- 1 file changed, 1 insertion(+), 204 deletions(-) diff --git a/README.md b/README.md index 0a4d6424e..28f764d75 100644 --- a/README.md +++ b/README.md @@ -1,204 +1 @@ -#  - -[](https://github.com/freqtrade/freqtrade/actions/) -[](https://coveralls.io/github/freqtrade/freqtrade?branch=develop) -[](https://www.freqtrade.io) -[](https://codeclimate.com/github/freqtrade/freqtrade/maintainability) - -Freqtrade is a free and open source crypto trading bot written in Python. It is designed to support all major exchanges and be controlled via Telegram. It contains backtesting, plotting and money management tools as well as strategy optimization by machine learning. - - - -## Disclaimer - -This software is for educational purposes only. Do not risk money which -you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS -AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS. - -Always start by running a trading bot in Dry-run and do not engage money -before you understand how it works and what profit/loss you should -expect. - -We strongly recommend you to have coding and Python knowledge. Do not -hesitate to read the source code and understand the mechanism of this bot. - -## Supported Exchange marketplaces - -Please read the [exchange specific notes](docs/exchanges.md) to learn about eventual, special configurations needed for each exchange. - -- [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#binance-blacklist)) -- [X] [Bittrex](https://bittrex.com/) -- [X] [Kraken](https://kraken.com/) -- [X] [FTX](https://ftx.com) -- [X] [Gate.io](https://www.gate.io/ref/6266643) -- [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_ - -### Community tested - -Exchanges confirmed working by the community: - -- [X] [Bitvavo](https://bitvavo.com/) -- [X] [Kucoin](https://www.kucoin.com/) - -## Documentation - -We invite you to read the bot documentation to ensure you understand how the bot is working. - -Please find the complete documentation on our [website](https://www.freqtrade.io). - -## Features - -- [x] **Based on Python 3.7+**: For botting on any operating system - Windows, macOS and Linux. -- [x] **Persistence**: Persistence is achieved through sqlite. -- [x] **Dry-run**: Run the bot without paying money. -- [x] **Backtesting**: Run a simulation of your buy/sell strategy. -- [x] **Strategy Optimization by machine learning**: Use machine learning to optimize your buy/sell strategy parameters with real exchange data. -- [x] **Edge position sizing** Calculate your win rate, risk reward ratio, the best stoploss and adjust your position size before taking a position for each specific market. [Learn more](https://www.freqtrade.io/en/stable/edge/). -- [x] **Whitelist crypto-currencies**: Select which crypto-currency you want to trade or use dynamic whitelists. -- [x] **Blacklist crypto-currencies**: Select which crypto-currency you want to avoid. -- [x] **Manageable via Telegram**: Manage the bot with Telegram. -- [x] **Display profit/loss in fiat**: Display your profit/loss in 33 fiat. -- [x] **Daily summary of profit/loss**: Provide a daily summary of your profit/loss. -- [x] **Performance status report**: Provide a performance status of your current trades. - -## Quick start - -Freqtrade provides a Linux/macOS script to install all dependencies and help you to configure the bot. - -```bash -git clone -b develop https://github.com/freqtrade/freqtrade.git -cd freqtrade -./setup.sh --install -``` - -For any other type of installation please refer to [Installation doc](https://www.freqtrade.io/en/stable/installation/). - -## Basic Usage - -### Bot commands - -``` -usage: freqtrade [-h] [-V] - {trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver} - ... - -Free, open source crypto trading bot - -positional arguments: - {trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver} - trade Trade module. - create-userdir Create user-data directory. - new-config Create new config - new-strategy Create new strategy - download-data Download backtesting data. - convert-data Convert candle (OHLCV) data from one format to - another. - convert-trade-data Convert trade data from one format to another. - list-data List downloaded data. - backtesting Backtesting module. - edge Edge module. - hyperopt Hyperopt module. - hyperopt-list List Hyperopt results - hyperopt-show Show details of Hyperopt results - list-exchanges Print available exchanges. - list-hyperopts Print available hyperopt classes. - list-markets Print markets on exchange. - list-pairs Print pairs on exchange. - list-strategies Print available strategies. - list-timeframes Print available timeframes for the exchange. - show-trades Show trades. - test-pairlist Test your pairlist configuration. - install-ui Install FreqUI - plot-dataframe Plot candles with indicators. - plot-profit Generate plot showing profits. - webserver Webserver module. - -optional arguments: - -h, --help show this help message and exit - -V, --version show program's version number and exit - -``` - -### Telegram RPC commands - -Telegram is not mandatory. However, this is a great way to control your bot. More details and the full command list on our [documentation](https://www.freqtrade.io/en/latest/telegram-usage/) - -- `/start`: Starts the trader. -- `/stop`: Stops the trader. -- `/stopbuy`: Stop entering new trades. -- `/status <trade_id>|[table]`: Lists all or specific open trades. -- `/profit [<n>]`: Lists cumulative profit from all finished trades, over the last n days. -- `/forcesell <trade_id>|all`: Instantly sells the given trade (Ignoring `minimum_roi`). -- `/performance`: Show performance of each finished trade grouped by pair -- `/balance`: Show account balance per currency. -- `/daily <n>`: Shows profit or loss per day, over the last n days. -- `/help`: Show help message. -- `/version`: Show version. - -## Development branches - -The project is currently setup in two main branches: - -- `develop` - This branch has often new features, but might also contain breaking changes. We try hard to keep this branch as stable as possible. -- `stable` - This branch contains the latest stable release. This branch is generally well tested. -- `feat/*` - These are feature branches, which are being worked on heavily. Please don't use these unless you want to test a specific feature. - -## Support - -### Help / Discord - -For any questions not covered by the documentation or for further information about the bot, or to simply engage with like-minded individuals, we encourage you to join the Freqtrade [discord server](https://discord.gg/p7nuUNVfP7). - -### [Bugs / Issues](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue) - -If you discover a bug in the bot, please -[search our issue tracker](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue) -first. If it hasn't been reported, please -[create a new issue](https://github.com/freqtrade/freqtrade/issues/new/choose) and -ensure you follow the template guide so that our team can assist you as -quickly as possible. - -### [Feature Requests](https://github.com/freqtrade/freqtrade/labels/enhancement) - -Have you a great idea to improve the bot you want to share? Please, -first search if this feature was not [already discussed](https://github.com/freqtrade/freqtrade/labels/enhancement). -If it hasn't been requested, please -[create a new request](https://github.com/freqtrade/freqtrade/issues/new/choose) -and ensure you follow the template guide so that it does not get lost -in the bug reports. - -### [Pull Requests](https://github.com/freqtrade/freqtrade/pulls) - -Feel like our bot is missing a feature? We welcome your pull requests! - -Please read our -[Contributing document](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md) -to understand the requirements before sending your pull-requests. - -Coding is not a necessity to contribute - maybe start with improving our documentation? -Issues labeled [good first issue](https://github.com/freqtrade/freqtrade/labels/good%20first%20issue) can be good first contributions, and will help get you familiar with the codebase. - -**Note** before starting any major new feature work, *please open an issue describing what you are planning to do* or talk to us on [discord](https://discord.gg/p7nuUNVfP7) (please use the #dev channel for this). This will ensure that interested parties can give valuable feedback on the feature, and let others know that you are working on it. - -**Important:** Always create your PR against the `develop` branch, not `stable`. - -## Requirements - -### Up-to-date clock - -The clock must be accurate, synchronized to a NTP server very frequently to avoid problems with communication to the exchanges. - -### Min hardware required - -To run this bot we recommend you a cloud instance with a minimum of: - -- Minimal (advised) system requirements: 2GB RAM, 1GB disk space, 2vCPU - -### Software requirements - -- [Python 3.7.x](http://docs.python-guide.org/en/latest/starting/installation/) -- [pip](https://pip.pypa.io/en/stable/installing/) -- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) -- [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html) -- [virtualenv](https://virtualenv.pypa.io/en/stable/installation.html) (Recommended) -- [Docker](https://www.docker.com/products/docker) (Recommended) +Freqtrade fork From 3ee9674bb7d9fffe7a1e1730a1361f01d5aeef19 Mon Sep 17 00:00:00 2001 From: theluxaz <37055144+theluxaz@users.noreply.github.com> Date: Wed, 13 Oct 2021 02:07:45 +0300 Subject: [PATCH 06/26] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 28f764d75..f468e9a9c 100644 --- a/README.md +++ b/README.md @@ -1 +1 @@ -Freqtrade fork +fork From 0f670189ebfaac0817c8726d607295870d9ba576 Mon Sep 17 00:00:00 2001 From: theluxaz <theluxaz@gmail.com> Date: Wed, 13 Oct 2021 02:14:07 +0300 Subject: [PATCH 07/26] quick typo fix --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f0a54500e..f8b9dbb5e 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -716,7 +716,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_sell(trade, sell_rate, buy, sell, sell_tag): + if self._check_and_execute_exit(trade, sell_rate, buy, sell, sell_tag): return True logger.debug('Found no sell signal for %s.', trade) From 96cab22a8c2999c9f224a783f17052d302cfa955 Mon Sep 17 00:00:00 2001 From: theluxaz <theluxaz@gmail.com> Date: Thu, 14 Oct 2021 01:03:15 +0300 Subject: [PATCH 08/26] Fixed some bugs for live sell_tags. --- freqtrade/freqtradebot.py | 2 ++ freqtrade/rpc/telegram.py | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f8b9dbb5e..d415c9d93 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1187,6 +1187,7 @@ class FreqtradeBot(LoggingMixin): 'profit_amount': profit_trade, 'profit_ratio': profit_ratio, 'sell_reason': trade.sell_reason, + 'sell_tag': trade.sell_tag, 'open_date': trade.open_date, 'close_date': trade.close_date or datetime.utcnow(), 'stake_currency': self.config['stake_currency'], @@ -1230,6 +1231,7 @@ class FreqtradeBot(LoggingMixin): 'profit_amount': profit_trade, 'profit_ratio': profit_ratio, 'sell_reason': trade.sell_reason, + 'sell_tag': trade.sell_tag, 'open_date': trade.open_date, 'close_date': trade.close_date or datetime.now(timezone.utc), 'stake_currency': self.config['stake_currency'], diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index bd8c83315..db745ff37 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -265,8 +265,12 @@ class Telegram(RPCHandler): "*Current Rate:* `{current_rate:.8f}`\n" "*Close Rate:* `{limit:.8f}`").format(**msg) - sell_tag = msg['sell_tag'] - buy_tag = msg['buy_tag'] + 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" From d341d85079af2a77ee2122ee654ac08ae7d1a6d8 Mon Sep 17 00:00:00 2001 From: theluxaz <theluxaz@gmail.com> Date: Thu, 14 Oct 2021 01:13:28 +0300 Subject: [PATCH 09/26] Refixed some files for the pull request --- .gitignore | 1 - README.md | 201 ++- .../hyperopts/RuleNOTANDoptimizer.py | 1203 ----------------- 3 files changed, 200 insertions(+), 1205 deletions(-) delete mode 100644 freqtrade/user_data/hyperopts/RuleNOTANDoptimizer.py diff --git a/.gitignore b/.gitignore index 9e4ce834b..16df71194 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ config*.json *.sqlite logfile.txt user_data/* -freqtrade/user_data/* !user_data/strategy/sample_strategy.py !user_data/notebooks user_data/notebooks/* diff --git a/README.md b/README.md index f468e9a9c..1eb96f200 100644 --- a/README.md +++ b/README.md @@ -1 +1,200 @@ -fork +#  + +[](https://github.com/freqtrade/freqtrade/actions/) +[](https://coveralls.io/github/freqtrade/freqtrade?branch=develop) +[](https://www.freqtrade.io) +[](https://codeclimate.com/github/freqtrade/freqtrade/maintainability) + +Freqtrade is a free and open source crypto trading bot written in Python. It is designed to support all major exchanges and be controlled via Telegram. It contains backtesting, plotting and money management tools as well as strategy optimization by machine learning. + + + +## Disclaimer + +This software is for educational purposes only. Do not risk money which +you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS +AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS. + +Always start by running a trading bot in Dry-run and do not engage money +before you understand how it works and what profit/loss you should +expect. + +We strongly recommend you to have coding and Python knowledge. Do not +hesitate to read the source code and understand the mechanism of this bot. + +## Supported Exchange marketplaces + +Please read the [exchange specific notes](docs/exchanges.md) to learn about eventual, special configurations needed for each exchange. + +- [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#binance-blacklist)) +- [X] [Bittrex](https://bittrex.com/) +- [X] [Kraken](https://kraken.com/) +- [X] [FTX](https://ftx.com) +- [X] [Gate.io](https://www.gate.io/ref/6266643) +- [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_ + +### Community tested + +Exchanges confirmed working by the community: + +- [X] [Bitvavo](https://bitvavo.com/) +- [X] [Kucoin](https://www.kucoin.com/) + +## Documentation + +We invite you to read the bot documentation to ensure you understand how the bot is working. + +Please find the complete documentation on our [website](https://www.freqtrade.io). + +## Features + +- [x] **Based on Python 3.7+**: For botting on any operating system - Windows, macOS and Linux. +- [x] **Persistence**: Persistence is achieved through sqlite. +- [x] **Dry-run**: Run the bot without paying money. +- [x] **Backtesting**: Run a simulation of your buy/sell strategy. +- [x] **Strategy Optimization by machine learning**: Use machine learning to optimize your buy/sell strategy parameters with real exchange data. +- [x] **Edge position sizing** Calculate your win rate, risk reward ratio, the best stoploss and adjust your position size before taking a position for each specific market. [Learn more](https://www.freqtrade.io/en/stable/edge/). +- [x] **Whitelist crypto-currencies**: Select which crypto-currency you want to trade or use dynamic whitelists. +- [x] **Blacklist crypto-currencies**: Select which crypto-currency you want to avoid. +- [x] **Manageable via Telegram**: Manage the bot with Telegram. +- [x] **Display profit/loss in fiat**: Display your profit/loss in 33 fiat. +- [x] **Daily summary of profit/loss**: Provide a daily summary of your profit/loss. +- [x] **Performance status report**: Provide a performance status of your current trades. + +## Quick start + +Freqtrade provides a Linux/macOS script to install all dependencies and help you to configure the bot. + +```bash +git clone -b develop https://github.com/freqtrade/freqtrade.git +cd freqtrade +./setup.sh --install +``` + +For any other type of installation please refer to [Installation doc](https://www.freqtrade.io/en/stable/installation/). + +## Basic Usage + +### Bot commands + +``` +usage: freqtrade [-h] [-V] + {trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver} + ... +Free, open source crypto trading bot +positional arguments: + {trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver} + trade Trade module. + create-userdir Create user-data directory. + new-config Create new config + new-strategy Create new strategy + download-data Download backtesting data. + convert-data Convert candle (OHLCV) data from one format to + another. + convert-trade-data Convert trade data from one format to another. + list-data List downloaded data. + backtesting Backtesting module. + edge Edge module. + hyperopt Hyperopt module. + hyperopt-list List Hyperopt results + hyperopt-show Show details of Hyperopt results + list-exchanges Print available exchanges. + list-hyperopts Print available hyperopt classes. + list-markets Print markets on exchange. + list-pairs Print pairs on exchange. + list-strategies Print available strategies. + list-timeframes Print available timeframes for the exchange. + show-trades Show trades. + test-pairlist Test your pairlist configuration. + install-ui Install FreqUI + plot-dataframe Plot candles with indicators. + plot-profit Generate plot showing profits. + webserver Webserver module. +optional arguments: + -h, --help show this help message and exit + -V, --version show program's version number and exit +``` + +### Telegram RPC commands + +Telegram is not mandatory. However, this is a great way to control your bot. More details and the full command list on our [documentation](https://www.freqtrade.io/en/latest/telegram-usage/) + +- `/start`: Starts the trader. +- `/stop`: Stops the trader. +- `/stopbuy`: Stop entering new trades. +- `/status <trade_id>|[table]`: Lists all or specific open trades. +- `/profit [<n>]`: Lists cumulative profit from all finished trades, over the last n days. +- `/forcesell <trade_id>|all`: Instantly sells the given trade (Ignoring `minimum_roi`). +- `/performance`: Show performance of each finished trade grouped by pair +- `/balance`: Show account balance per currency. +- `/daily <n>`: Shows profit or loss per day, over the last n days. +- `/help`: Show help message. +- `/version`: Show version. + +## Development branches + +The project is currently setup in two main branches: + +- `develop` - This branch has often new features, but might also contain breaking changes. We try hard to keep this branch as stable as possible. +- `stable` - This branch contains the latest stable release. This branch is generally well tested. +- `feat/*` - These are feature branches, which are being worked on heavily. Please don't use these unless you want to test a specific feature. + +## Support + +### Help / Discord + +For any questions not covered by the documentation or for further information about the bot, or to simply engage with like-minded individuals, we encourage you to join the Freqtrade [discord server](https://discord.gg/p7nuUNVfP7). + +### [Bugs / Issues](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue) + +If you discover a bug in the bot, please +[search our issue tracker](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue) +first. If it hasn't been reported, please +[create a new issue](https://github.com/freqtrade/freqtrade/issues/new/choose) and +ensure you follow the template guide so that our team can assist you as +quickly as possible. + +### [Feature Requests](https://github.com/freqtrade/freqtrade/labels/enhancement) + +Have you a great idea to improve the bot you want to share? Please, +first search if this feature was not [already discussed](https://github.com/freqtrade/freqtrade/labels/enhancement). +If it hasn't been requested, please +[create a new request](https://github.com/freqtrade/freqtrade/issues/new/choose) +and ensure you follow the template guide so that it does not get lost +in the bug reports. + +### [Pull Requests](https://github.com/freqtrade/freqtrade/pulls) + +Feel like our bot is missing a feature? We welcome your pull requests! + +Please read our +[Contributing document](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md) +to understand the requirements before sending your pull-requests. + +Coding is not a necessity to contribute - maybe start with improving our documentation? +Issues labeled [good first issue](https://github.com/freqtrade/freqtrade/labels/good%20first%20issue) can be good first contributions, and will help get you familiar with the codebase. + +**Note** before starting any major new feature work, *please open an issue describing what you are planning to do* or talk to us on [discord](https://discord.gg/p7nuUNVfP7) (please use the #dev channel for this). This will ensure that interested parties can give valuable feedback on the feature, and let others know that you are working on it. + +**Important:** Always create your PR against the `develop` branch, not `stable`. + +## Requirements + +### Up-to-date clock + +The clock must be accurate, synchronized to a NTP server very frequently to avoid problems with communication to the exchanges. + +### Min hardware required + +To run this bot we recommend you a cloud instance with a minimum of: + +- Minimal (advised) system requirements: 2GB RAM, 1GB disk space, 2vCPU + +### Software requirements + +- [Python 3.7.x](http://docs.python-guide.org/en/latest/starting/installation/) +- [pip](https://pip.pypa.io/en/stable/installing/) +- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +- [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html) +- [virtualenv](https://virtualenv.pypa.io/en/stable/installation.html) (Recommended) +- [Docker](https://www.docker.com/products/docker) (Recommended) \ No newline at end of file diff --git a/freqtrade/user_data/hyperopts/RuleNOTANDoptimizer.py b/freqtrade/user_data/hyperopts/RuleNOTANDoptimizer.py deleted file mode 100644 index f720b59ca..000000000 --- a/freqtrade/user_data/hyperopts/RuleNOTANDoptimizer.py +++ /dev/null @@ -1,1203 +0,0 @@ -# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement -# isort: skip_file -# --- Do not remove these libs --- -from functools import reduce -from typing import Any, Callable, Dict, List - -import numpy as np # noqa -import pandas as pd # noqa -from pandas import DataFrame -from skopt.space import Categorical, Dimension,Integer , Real # noqa -from freqtrade.optimize.space import SKDecimal -from freqtrade.optimize.hyperopt_interface import IHyperOpt - -# -------------------------------- -# Add your lib to import here -import talib.abstract as ta # noqa -import freqtrade.vendor.qtpylib.indicators as qtpylib - -##PYCHARM -import sys -sys.path.append(r"/freqtrade/user_data/strategies") - - -# ##HYPEROPT -# import sys,os -# file_dir = os.path.dirname(__file__) -# sys.path.append(file_dir) - - -from z_buyer_mid_volatility import mid_volatility_buyer -from z_seller_mid_volatility import mid_volatility_seller -from z_COMMON_FUNCTIONS import MID_VOLATILITY - - - - -class RuleOptimizer15min(IHyperOpt): - """ - This is a sample hyperopt to inspire you. - Feel free to customize it. - - More information in the documentation: https://www.freqtrade.io/en/latest/hyperopt/ - - You should: - - Rename the class name to some unique name. - - Add any methods you want to build your hyperopt. - - Add any lib you need to build your hyperopt. - - You must keep: - - The prototypes for the methods: populate_indicators, indicator_space, buy_strategy_generator. - - The methods roi_space, generate_roi_table and stoploss_space are not required - and are provided by default. - However, you may override them if you need the - 'roi' and the 'stoploss' spaces that differ from the defaults offered by Freqtrade. - - This sample illustrates how to override these methods. - """ - - - @staticmethod - def buy_strategy_generator(params: Dict[str, Any]) -> Callable: - """ - Define the buy strategy parameters to be used by hyperopt - """ - def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Buy strategy Hyperopt will build and use - """ - conditions = [] - - - -#--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -#--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - - ##MAIN SELECTORS - -#-------------------- - - ##VOLATILITY - - conditions.append(dataframe['vol_mid'] > 0 ) - - # conditions.append((dataframe['vol_low'] > 0) |(dataframe['vol_mid'] > 0) ) - - # conditions.append((dataframe['vol_high'] > 0) |(dataframe['vol_mid'] > 0) ) - - -#-------------------- - - - ##PICKS TREND COMBO - - conditions.append( - - (dataframe['downtrend'] >= params['main_1_trend_strength']) - |#OR & - (dataframe['downtrendsmall'] >= params['main_2_trend_strength']) - - ) - - ##UPTREND - #conditions.append(dataframe['uptrend'] >= params['main_1_trend_strength']) - ##DOWNTREND - #conditions.append(dataframe['downtrend'] >= params['main_1_trend_strength']) - ##NOTREND - #conditions.append((dataframe['uptrend'] <1)&(dataframe['downtrend'] <1)) - -#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - ##ABOVE / BELOW THRESHOLDS - - #RSI ABOVE - if 'include_sell_ab_9_rsi_above_value' in params and params['include_sell_ab_9_rsi_above_value']: - conditions.append(dataframe['rsi'] > params['sell_ab_9_rsi_above_value']) - #RSI RECENT PIT 5 - if 'include_sell_ab_10_rsi_recent_pit_2_value' in params and params['include_sell_ab_10_rsi_recent_pit_2_value']: - conditions.append(dataframe['rsi'].rolling(2).min() < params['sell_ab_10_rsi_recent_pit_2_value']) - #RSI RECENT PIT 12 - if 'include_sell_ab_11_rsi_recent_pit_4_value' in params and params['include_sell_ab_11_rsi_recent_pit_4_value']: - conditions.append(dataframe['rsi'].rolling(4).min() < params['sell_ab_11_rsi_recent_pit_4_value']) - #RSI5 BELOW - if 'include_sell_ab_12_rsi5_above_value' in params and params['include_sell_ab_12_rsi5_above_value']: - conditions.append(dataframe['rsi5'] > params['sell_ab_12_rsi5_above_value']) - #RSI50 BELOW - if 'include_sell_ab_13_rsi50_above_value' in params and params['include_sell_ab_13_rsi50_above_value']: - conditions.append(dataframe['rsi50'] > params['sell_ab_13_rsi50_above_value']) - -#----------------------- - - #ROC BELOW - if 'include_sell_ab_14_roc_above_value' in params and params['include_sell_ab_14_roc_above_value']: - conditions.append(dataframe['roc'] > (params['sell_ab_14_roc_above_value']/2)) - #ROC50 BELOW - if 'include_sell_ab_15_roc50_above_value' in params and params['include_sell_ab_15_roc50_above_value']: - conditions.append(dataframe['roc50'] > (params['sell_ab_15_roc50_above_value'])) - #ROC2 BELOW - if 'include_sell_ab_16_roc2_above_value' in params and params['include_sell_ab_16_roc2_above_value']: - conditions.append(dataframe['roc2'] > (params['sell_ab_16_roc2_above_value']/2)) - -#----------------------- - - #PPO5 BELOW - if 'include_sell_ab_17_ppo5_above_value' in params and params['include_sell_ab_17_ppo5_above_value']: - conditions.append(dataframe['ppo5'] > (params['sell_ab_17_ppo5_above_value']/2)) - #PPO10 BELOW - if 'include_sell_ab_18_ppo10_above_value' in params and params['include_sell_ab_18_ppo10_above_value']: - conditions.append(dataframe['ppo10'] > (params['sell_ab_18_ppo10_above_value']/2)) - #PPO25 BELOW - if 'include_sell_ab_19_ppo25_above_value' in params and params['include_sell_ab_19_ppo25_above_value']: - conditions.append(dataframe['ppo25'] > (params['sell_ab_19_ppo25_above_value']/2)) - - #PPO50 BELOW - if 'include_sell_ab_20_ppo50_above_value' in params and params['include_sell_ab_20_ppo50_above_value']: - conditions.append(dataframe['ppo50'] > (params['sell_ab_20_ppo50_above_value']/2)) - #PPO100 BELOW - if 'include_sell_ab_21_ppo100_above_value' in params and params['include_sell_ab_21_ppo100_above_value']: - conditions.append(dataframe['ppo100'] > (params['sell_ab_21_ppo100_above_value'])) - #PPO200 BELOW - if 'include_sell_ab_22_ppo200_above_value' in params and params['include_sell_ab_22_ppo200_above_value']: - conditions.append(dataframe['ppo200'] > (params['sell_ab_22_ppo200_above_value'])) - #PPO500 BELOW - if 'include_sell_ab_23_ppo500_above_value' in params and params['include_sell_ab_23_ppo500_above_value']: - conditions.append(dataframe['ppo500'] > (params['sell_ab_23_ppo500_above_value']*2)) - - ##USE AT A LATER STEP - - #convsmall BELOW - if 'include_sell_ab_24_convsmall_above_value' in params and params['include_sell_ab_24_convsmall_above_value']: - conditions.append(dataframe['convsmall'] > (params['sell_ab_24_convsmall_above_value']/2)) - #convmedium BELOW - if 'include_sell_ab_25_convmedium_above_value' in params and params['include_sell_ab_25_convmedium_above_value']: - conditions.append(dataframe['convmedium'] >(params['sell_ab_25_convmedium_above_value'])) - #convlarge BELOW - if 'include_sell_ab_26_convlarge_above_value' in params and params['include_sell_ab_26_convlarge_above_value']: - conditions.append(dataframe['convlarge'] > (params['sell_ab_26_convlarge_above_value'])) - #convultra BELOW - if 'include_sell_ab_27_convultra_above_value' in params and params['include_sell_ab_27_convultra_above_value']: - conditions.append(dataframe['convultra'] > (params['sell_ab_27_convultra_above_value']/2)) - #convdist BELOW - if 'include_sell_ab_28_convdist_above_value' in params and params['include_sell_ab_28_convdist_above_value']: - conditions.append(dataframe['convdist'] > (params['sell_ab_28_convdist_above_value'])) - - -#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - ##SMA'S GOING DOWN - - if 'sell_down_0a_sma3' in params and params['sell_down_0a_sma3']: - conditions.append((dataframe['sma3'].shift(1) >dataframe['sma3'])) - if 'sell_down_0b_sma5' in params and params['sell_down_0b_sma5']: - conditions.append((dataframe['sma5'].shift(1) >dataframe['sma5'])) - if 'sell_down_1_sma10' in params and params['sell_down_1_sma10']: - conditions.append((dataframe['sma10'].shift(1) >dataframe['sma10'])) - if 'sell_down_2_sma25' in params and params['sell_down_2_sma25']: - conditions.append((dataframe['sma25'].shift(1) >dataframe['sma25'])) - if 'sell_down_3_sma50' in params and params['sell_down_3_sma50']: - conditions.append((dataframe['sma50'].shift(2) >dataframe['sma50'])) - if 'sell_down_4_sma100' in params and params['sell_down_4_sma100']: - conditions.append((dataframe['sma100'].shift(3) >dataframe['sma100'])) - if 'sell_down_5_sma200' in params and params['sell_down_5_sma200']: - conditions.append((dataframe['sma200'].shift(4) >dataframe['sma200'])) - - if 'sell_down_6_sma400' in params and params['sell_down_6_sma400']: - conditions.append((dataframe['sma400'].shift(4) >dataframe['sma400'])) - if 'sell_down_7_sma10k' in params and params['sell_down_7_sma10k']: - conditions.append((dataframe['sma10k'].shift(5) >dataframe['sma10k'])) - # if 'sell_down_8_sma20k' in params and params['sell_down_8_sma20k']: - # conditions.append((dataframe['sma20k'].shift(5) >dataframe['sma20k'])) - # if 'sell_down_9_sma30k' in params and params['sell_down_9_sma30k']: - # conditions.append((dataframe['sma30k'].shift(5) >dataframe['sma30k'])) - - if 'sell_down_10_convsmall' in params and params['sell_down_10_convsmall']: - conditions.append((dataframe['convsmall'].shift(2) >dataframe['convsmall'])) - if 'sell_down_11_convmedium' in params and params['sell_down_11_convmedium']: - conditions.append((dataframe['convmedium'].shift(3) >dataframe['convmedium'])) - if 'sell_down_12_convlarge' in params and params['sell_down_12_convlarge']: - conditions.append((dataframe['convlarge'].shift(4) >dataframe['convlarge'])) - if 'sell_down_13_convultra' in params and params['sell_down_13_convultra']: - conditions.append((dataframe['convultra'].shift(4) >dataframe['convultra'])) - if 'sell_down_14_convdist' in params and params['sell_down_14_convdist']: - conditions.append((dataframe['convdist'].shift(4) >dataframe['convdist'])) - - if 'sell_down_15_vol50' in params and params['sell_down_15_vol50']: - conditions.append((dataframe['vol50'].shift(2) >dataframe['vol50'])) - if 'sell_down_16_vol100' in params and params['sell_down_16_vol100']: - conditions.append((dataframe['vol100'].shift(3) >dataframe['vol100'])) - if 'sell_down_17_vol175' in params and params['sell_down_17_vol175']: - conditions.append((dataframe['vol175'].shift(4) >dataframe['vol175'])) - if 'sell_down_18_vol250' in params and params['sell_down_18_vol250']: - conditions.append((dataframe['vol250'].shift(4) >dataframe['vol250'])) - if 'sell_down_19_vol500' in params and params['sell_down_19_vol500']: - conditions.append((dataframe['vol500'].shift(4) >dataframe['vol500'])) - - if 'sell_down_20_vol1000' in params and params['sell_down_20_vol1000']: - conditions.append((dataframe['vol1000'].shift(4) >dataframe['vol1000'])) - if 'sell_down_21_vol100mean' in params and params['sell_down_21_vol100mean']: - conditions.append((dataframe['vol100mean'].shift(4) >dataframe['vol100mean'])) - if 'sell_down_22_vol250mean' in params and params['sell_down_22_vol250mean']: - conditions.append((dataframe['vol250mean'].shift(4) >dataframe['vol250mean'])) - - if 'up_20_conv3' in params and params['up_20_conv3']: - conditions.append(((dataframe['conv3'].shift(25) < dataframe['conv3'])&(dataframe['conv3'].shift(50) < dataframe['conv3']))) - if 'up_21_vol5' in params and params['up_21_vol5']: - conditions.append(((dataframe['vol5'].shift(25) < dataframe['vol5'])&(dataframe['vol5'].shift(50) < dataframe['vol5']))) - if 'up_22_vol5ultra' in params and params['up_22_vol5ultra']: - conditions.append(((dataframe['vol5ultra'].shift(25) < dataframe['vol5ultra'])&(dataframe['vol5ultra'].shift(50) < dataframe['vol5ultra']))) - if 'up_23_vol1ultra' in params and params['up_23_vol1ultra']: - conditions.append(((dataframe['vol1ultra'].shift(25) < dataframe['vol1ultra'])& (dataframe['vol1ultra'].shift(50) < dataframe['vol1ultra']))) - if 'up_24_vol1' in params and params['up_24_vol1']: - conditions.append(((dataframe['vol1'].shift(30) < dataframe['vol1'])&(dataframe['vol1'].shift(10) < dataframe['vol1']))) - if 'up_25_vol5inc24' in params and params['up_25_vol5inc24']: - conditions.append((dataframe['vol5inc24'].shift(50) < dataframe['vol5inc24'])) - - -#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - ##ABOVE / BELOW SMAS 1 above/ 0 None / -1 below - - #SMA10 - conditions.append((dataframe['close'] > dataframe['sma10'])|(0.5 > params['ab_1_sma10'])) - conditions.append((dataframe['close'] < dataframe['sma10'])|(-0.5 < params['ab_1_sma10'])) - #SMA25 - conditions.append((dataframe['close'] > dataframe['sma25'])|(0.5 > params['ab_2_sma25'])) - conditions.append((dataframe['close'] < dataframe['sma25'])|(-0.5 < params['ab_2_sma25'])) - #SMA50 - conditions.append((dataframe['close'] > dataframe['sma50'])|(0.5 > params['ab_3_sma50'])) - conditions.append((dataframe['close'] < dataframe['sma50'])|(-0.5 < params['ab_3_sma50'])) - - - #SMA100 - conditions.append((dataframe['close'] > dataframe['sma100'])|(0.5 > params['ab_4_sma100'])) - conditions.append((dataframe['close'] < dataframe['sma100'])|(-0.5 < params['ab_4_sma100'])) - #SMA100 - conditions.append((dataframe['close'] > dataframe['sma200'])|(0.5 > params['ab_5_sma200'])) - conditions.append((dataframe['close'] < dataframe['sma200'])|(-0.5 < params['ab_5_sma200'])) - #SMA400 - conditions.append((dataframe['close'] > dataframe['sma400'])|(0.5 > params['ab_6_sma400'])) - conditions.append((dataframe['close'] < dataframe['sma400'])|(-0.5 < params['ab_6_sma400'])) - #SMA10k - conditions.append((dataframe['close'] > dataframe['sma10k'])|(0.5 > params['ab_7_sma10k'])) - conditions.append((dataframe['close'] < dataframe['sma10k'])|(-0.5 < params['ab_7_sma10k'])) - -#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - ##DOWNSWINGS / UPSWINGS PPO'S - - #ppo5 UP OR DOWN (1 UP, 0 NOTHING, -1 DOWN) - conditions.append((dataframe['ppo5'].shift(2) <dataframe['ppo5'])|(0.5 > params['sell_swings_1_ppo5_up_or_down_bool'])) - conditions.append((dataframe['ppo5'].shift(2) >dataframe['ppo5'])|(-0.5 < params['sell_swings_1_ppo5_up_or_down_bool'])) - #ppo10 - conditions.append((dataframe['ppo10'].shift(3) <dataframe['ppo10'])|(0.5 > params['sell_swings_2_ppo10_up_or_down_bool'])) - conditions.append((dataframe['ppo10'].shift(3) >dataframe['ppo10'])|(-0.5 < params['sell_swings_2_ppo10_up_or_down_bool'])) - #ppo25 - #conditions.append((dataframe['ppo25'].shift(3) <dataframe['ppo25'])|(0.5 > params['sell_swings_3_ppo25_up_or_down_bool'])) - conditions.append((dataframe['ppo25'].shift(3) >dataframe['ppo25'])|(-0.5 < params['sell_swings_3_ppo25_up_or_down_bool'])) - - #ppo50 - #conditions.append((dataframe['ppo50'].shift(3 <dataframe['ppo50'])|(0.5 > params['sell_swings_4_ppo50_up_or_down_bool'])) - conditions.append((dataframe['ppo50'].shift(3) >dataframe['ppo50'])|(-0.5 < params['sell_swings_4_ppo50_up_or_down_bool'])) - #ppo100 - #conditions.append((dataframe['ppo100'].shift(4) <dataframe['ppo100'])|(0.5 > params['sell_swings_5_ppo100_up_or_down_bool'])) - conditions.append((dataframe['ppo100'].shift(4) >dataframe['ppo100'])|(-0.5 < params['sell_swings_5_ppo100_up_or_down_bool'])) - #ppo200 - #conditions.append((dataframe['ppo200'].shift(4) <dataframe['ppo200'])|(0.5 > params['sell_swings_6_ppo200_up_or_down_bool'])) - conditions.append((dataframe['ppo200'].shift(4) >dataframe['ppo200'])|(-0.5 < params['sell_swings_6_ppo200_up_or_down_bool'])) - - #ppo500 - #conditions.append((dataframe['ppo500'].shift(5) <dataframe['ppo500'])|(0.5 > params['sell_swings_7_ppo500_up_or_down_bool'])) - conditions.append((dataframe['ppo500'].shift(5) >dataframe['ppo500'])|(-0.5 < params['sell_swings_7_ppo500_up_or_down_bool'])) - - #roc50 - #conditions.append((dataframe['roc50'].shift(3) <dataframe['roc50'])|(0.5 > params['sell_swings_8_roc50_up_or_down_bool'])) - conditions.append((dataframe['roc50'].shift(3) >dataframe['roc50'])|(-0.5 < params['sell_swings_8_roc50_up_or_down_bool'])) - #roc10 - #conditions.append((dataframe['roc10'].shift(2) <dataframe['roc10'])|(0.5 > params['sell_swings_9_roc10_up_or_down_bool'])) - conditions.append((dataframe['roc10'].shift(2) >dataframe['roc10'])|(-0.5 < params['sell_swings_9_roc10_up_or_down_bool'])) - - -#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - - ##DISTANCES/ROC - - ##FOR MORE TOP SELLERS - #dist50 MORE THAN - if 'include_sell_dist_1_dist50_more_value' in params and params['include_sell_dist_1_dist50_more_value']: - conditions.append(dataframe['dist50'] > (params['sell_dist_1_dist50_more_value'])) - #dist200 MORE THAN - if 'include_sell_dist_2_dist200_more_value' in params and params['include_sell_dist_2_dist200_more_value']: - conditions.append(dataframe['dist200'] > (params['sell_dist_2_dist200_more_value'])) - - #dist400 MORE THAN - if 'include_sell_dist_3_dist400_more_value' in params and params['include_sell_dist_3_dist400_more_value']: - conditions.append(dataframe['dist400'] > (params['sell_dist_3_dist400_more_value'])) - #dist10k MORE THAN - if 'include_sell_dist_4_dist10k_more_value' in params and params['include_sell_dist_4_dist10k_more_value']: - conditions.append(dataframe['dist10k'] > (params['sell_dist_4_dist10k_more_value'])) - - ##FOR MORE TOP SELLERS - #more =further from top bol up - #dist_upbol50 MORE THAN - if 'include_sell_dist_5_dist_upbol50_more_value' in params and params['include_sell_dist_5_dist_upbol50_more_value']: - conditions.append(dataframe['dist_upbol50'] > (params['sell_dist_5_dist_upbol50_more_value']/2)) - #dist_upbol100 MORE THAN - if 'include_sell_dist_6_dist_upbol100_more_value' in params and params['include_sell_dist_6_dist_upbol100_more_value']: - conditions.append(dataframe['dist_upbol100'] > (params['sell_dist_6_dist_upbol100_more_value']/2)) - - - ##for bot bol prevent seller - # #less =closer to bot bol - #dist_upbol50 LESS THAN. - #if 'include_sell_dist_7_dist_lowbol50_more_value' in params and params['include_sell_dist_7_dist_lowbol50_more_value']: - # conditions.append(dataframe['dist_lowbol50'] > (params['sell_dist_7_dist_lowbol50_more_value']/2)) - #dist_upbol100 LESS THAN - # if 'include_sell_dist_8_dist_lowbol100_more_value' in params and params['include_sell_dist_8_dist_lowbol100_more_value']: - # conditions.append(dataframe['dist_lowbol100'] > (params['sell_dist_8_dist_lowbol100_more_value']/2)) - - - - ##others - #roc50sma LESS THAN - if 'include_sell_dist_7_roc50sma_less_value' in params and params['include_sell_dist_7_roc50sma_less_value']: - conditions.append(dataframe['roc50sma'] < (params['sell_dist_7_roc50sma_less_value'])*2) - #roc200sma LESS THAN - if 'include_sell_dist_8_roc200sma_less_value' in params and params['include_sell_dist_8_roc200sma_less_value']: - conditions.append(dataframe['roc200sma'] < (params['sell_dist_8_roc200sma_less_value'])*2) - - ##ENABLE TO BUY AWAY FROM HIGH - # #HIGH500 TO CLOSE MORE THAN - #if 'include_sell_dist_9_high100_more_value' in params and params['include_sell_dist_9_high100_more_value']: - # conditions.append((dataframe['high100']-dataframe['close']) > ((dataframe['high100']/100* (params['sell_dist_9_high100_more_value'])) - -#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - - - - - - # Check that volume is not 0 - conditions.append(dataframe['volume'] > 0) - - - - - if conditions: - - - # ##ENABLE PRODUCTION BUYS - # dataframe.loc[ - # (add_production_buys_mid(dataframe)), - # 'buy'] = 1 - # - - - dataframe.loc[ - (~(reduce(lambda x, y: x & y, conditions)))&OPTIMIZED_RULE(dataframe,params), - 'buy'] = 1 - - return dataframe - - return populate_buy_trend - - @staticmethod - def indicator_space() -> List[Dimension]: - """ - Define your Hyperopt space for searching strategy parameters - """ - return [ - - -#------------------------------------------------------------------------------------------------------- - - ## CUSTOM RULE TRESHOLDS - - # SKDecimal(5.0, 7.0,decimals=1, name='sell_trigger_0_roc_ab_value'),# 5 range 5-7 or 4-7 - # SKDecimal(3.2, 4.5,decimals=1, name='sell_trigger_0_roc2_ab_value'),#3.8 range 3.2-4.5 - # Integer(77, 82, name='sell_trigger_0_rsi_ab_value'),#79 range 82-77 - # Integer(90, 95, name='sell_trigger_0_rsi5_ab_value'),#94 range 95-90 - # Integer(63, 67, name='sell_trigger_0_rsi50_ab_value'),#66 range 67-63 - -#------------------------------------------------------------------------------------------------------- - - ##MAIN - - Categorical([1, 2, 3], name='sell_main_1_trend_strength'), #BIG TREND STR - Categorical([1, 2, 3], name='sell_main_2_trend_strength'), #SMALL UPTREND STR - - - #Categorical([-1, 0, 1], name='sell_main_2_small_uptrend_downtrend'), #SMALL UPTREND ON/OFF 1 is on -1 is down - -#------------------------------------------------------------------------------------------------------- -#------------------------------------------------------------------------------------------------------- - - ##INCLUDE/EXCLUDE RULES - - Categorical([True, False], name='include_sell_ab_9_rsi_above_value'), - Categorical([True, False], name='include_sell_ab_10_rsi_recent_pit_2_value'), - Categorical([True, False], name='include_sell_ab_11_rsi_recent_pit_4_value'), - Categorical([True, False], name='include_sell_ab_12_rsi5_above_value'), - Categorical([True, False], name='include_sell_ab_13_rsi50_above_value'), - - Categorical([True, False], name='include_sell_ab_14_roc_above_value'), - Categorical([True, False], name='include_sell_ab_15_roc50_above_value'), - Categorical([True, False], name='include_sell_ab_16_roc2_above_value'), - - Categorical([True, False], name='include_sell_ab_17_ppo5_above_value'), - Categorical([True, False], name='include_sell_ab_18_ppo10_above_value'), - Categorical([True, False], name='include_sell_ab_19_ppo25_above_value'), - - Categorical([True, False], name='include_sell_ab_20_ppo50_above_value'), - Categorical([True, False], name='include_sell_ab_21_ppo100_above_value'), - Categorical([True, False], name='include_sell_ab_22_ppo200_above_value'), - Categorical([True, False], name='include_sell_ab_23_ppo500_above_value'), - - ##USE AT A LATER STEP - Categorical([True, False], name='include_sell_ab_24_convsmall_above_value'), - Categorical([True, False], name='include_sell_ab_25_convmedium_above_value'), - Categorical([True, False], name='include_sell_ab_26_convlarge_above_value'), - Categorical([True, False], name='include_sell_ab_27_convultra_above_value'), - Categorical([True, False], name='include_sell_ab_28_convdist_above_value'), - - Categorical([True, False], name='include_sell_dist_1_dist50_more_value'), - Categorical([True, False], name='include_sell_dist_2_dist200_more_value'), - Categorical([True, False], name='include_sell_dist_3_dist400_more_value'), - Categorical([True, False], name='include_sell_dist_4_dist10k_more_value'), - - Categorical([True, False], name='include_sell_dist_5_dist_upbol50_more_value'), - Categorical([True, False], name='include_sell_dist_6_dist_upbol100_more_value'), - - - # FOR MORE DOWNTREND BUYS LIKELY - # Categorical([True, False], name='include_sell_dist_7_dist_lowbol50_more_value'), - # Categorical([True, False], name='include_sell_dist_8_dist_lowbol100_more_value'), - - #MORE LIKE TRIGGERS - Categorical([True, False], name='include_sell_dist_7_roc50sma_less_value'), - Categorical([True, False], name='include_sell_dist_8_roc200sma_less_value'), - - ##below high 100 - #Categorical([True, False], name='include_sell_dist_9_high100_more_value'), - -#------------------------------------------------------------------------------------------------------- -#------------------------------------------------------------------------------------------------------- - - ##ABOVE/BELOW VALUES - - Integer(35, 82, name='sell_ab_9_rsi_above_value'), - Integer(18, 35, name='sell_ab_10_rsi_recent_pit_2_value'), - Integer(18, 35, name='sell_ab_11_rsi_recent_pit_4_value'), - Integer(70, 91, name='sell_ab_12_rsi5_above_value'), - Integer(37, 60, name='sell_ab_13_rsi50_above_value'), - - Integer(-4, 10, name='sell_ab_14_roc_above_value'),#/2 - Integer(-2, 8, name='sell_ab_15_roc50_above_value'), - Integer(-4, 8, name='sell_ab_16_roc2_above_value'),#/2 - -#-------------------------------- - - ##CHANGE DEPENDING WHAT TYPE OF SELL --> PEAK OR DOWTRENDS - Integer(-4, 6, name='sell_ab_17_ppo5_above_value'),#/2 - Integer(-4, 6, name='sell_ab_18_ppo10_above_value'),#/2 - Integer(-10, 8, name='sell_ab_19_ppo25_above_value'),#/2 - - Integer(-10, 8, name='sell_ab_20_ppo50_above_value'),#/2 - Integer(-6, 6, name='sell_ab_21_ppo100_above_value'), - Integer(-6, 6, name='sell_ab_22_ppo200_above_value'), - Integer(-4, 5, name='sell_ab_23_ppo500_above_value'),#*2 - - # ##USE AT A LATER STEP - # - # Integer(-1, 6, name='sell_ab_24_convsmall_above_value'),#/2 # extreme 12 - # Integer(-1, 4, name='sell_ab_25_convmedium_above_value'),# extreme 6 - # Integer(-1, 7, name='sell_ab_26_convlarge_above_value'),# extreme 12 - # Integer(-1, 8, name='sell_ab_27_convultra_above_value'),#/2# extreme 12 - # - # Integer(-1, 6, name='sell_ab_28_convdist_above_value'), #very extreme not useful 10+ - -#------------------------------------------------------------------------------------------------------- - - #SMA'S GOING DOWN - - Categorical([True, False], name='sell_down_0a_sma3'), - Categorical([True, False], name='sell_down_0b_sma5'), - Categorical([True, False], name='sell_down_1_sma10'), - Categorical([True, False], name='sell_down_2_sma25'), - Categorical([True, False], name='sell_down_3_sma50'), - Categorical([True, False], name='sell_down_4_sma100'), - Categorical([True, False], name='sell_down_5_sma200'), - - Categorical([True, False], name='sell_down_6_sma400'), - Categorical([True, False], name='sell_down_7_sma10k'), - # Categorical([True, False], name='sell_down_8_sma20k'), - # Categorical([True, False], name='sell_down_9_sma30k'), - - Categorical([True, False], name='sell_down_10_convsmall'), - Categorical([True, False], name='sell_down_11_convmedium'), - Categorical([True, False], name='sell_down_12_convlarge'), - Categorical([True, False], name='sell_down_13_convultra'), - Categorical([True, False], name='sell_down_14_convdist'), - - Categorical([True, False], name='sell_down_15_vol50'), - Categorical([True, False], name='sell_down_16_vol100'), - Categorical([True, False], name='sell_down_17_vol175'), - Categorical([True, False], name='sell_down_18_vol250'), - Categorical([True, False], name='sell_down_19_vol500'), - - Categorical([True, False], name='sell_down_20_vol1000'), - Categorical([True, False], name='sell_down_21_vol100mean'), - Categorical([True, False], name='sell_down_22_vol250mean'), - -#------------------------------------------------------------------------------------------------------- - - ##ABOVE/BELOW SMAS - - Categorical([-1, 0, 1], name='sell_ab_1_sma10'), - Categorical([-1, 0, 1], name='sell_ab_2_sma25'), - Categorical([-1, 0, 1], name='sell_ab_3_sma50'), - - Categorical([-1, 0, 1], name='sell_ab_4_sma100'), - Categorical([-1, 0, 1], name='sell_ab_5_sma200'), - Categorical([-1, 0, 1], name='sell_ab_6_sma400'), - Categorical([-1, 0, 1], name='sell_ab_7_sma10k'), - -#------------------------------------------------------------------------------------------------------- - - ##DOWNSWINGS / UPSWINGS PPO'S - - ##UP OR DOWN (1 UP, 0 NOTHING, -1 DOWN) - - Categorical([-1, 0, 1], name='sell_swings_1_ppo5_up_or_down_bool'), - Categorical([-1, 0, 1], name='sell_swings_2_ppo10_up_or_down_bool'), - Categorical([-1, 0], name='sell_swings_3_ppo25_up_or_down_bool'), - - Categorical([-1, 0], name='sell_swings_4_ppo50_up_or_down_bool'), - Categorical([-1, 0], name='sell_swings_5_ppo100_up_or_down_bool'), - Categorical([-1, 0], name='sell_swings_6_ppo200_up_or_down_bool'), - Categorical([-1, 0], name='sell_swings_7_ppo500_up_or_down_bool'), - - Categorical([-1, 0], name='sell_swings_8_roc50_up_or_down_bool'), - Categorical([-1, 0], name='sell_swings_9_roc10_up_or_down_bool'), - -#------------------------------------------------------------------------------------------------------- - - #DISTANCES - - #FOR MORE TOP SELLERS - Integer(-6, 14, name='sell_dist_1_dist50_more_value'), #extreme, useless -4 ,30 - Integer(-8, 20, name='sell_dist_2_dist200_more_value'), #extreme, useless -12-40 - Integer(-15, 30, name='sell_dist_3_dist400_more_value'), - Integer(-15, 35, name='sell_dist_4_dist10k_more_value'), - - #FOR MORE TOP SELLERS - Integer(-30, 25, name='sell_dist_5_dist_upbol50_more_value'),#/2 - Integer(-30, 25, name='sell_dist_6_dist_upbol100_more_value'),#/2 - - - #FOR MORE DOWNTREND BUYS LIKELY - # Integer(-8, 50, name='sell_dist_7_dist_lowbol50_more_value'),#/2 ##set to more, as in higher from lower boll - # Integer(-8, 50, name='sell_dist_8_dist_lowbol100_more_value'),#/2 ##set to more, as in higher from lower boll - - # Integer(-70, 40, name='sell_dist_7_roc50sma_more_value'),#*2 ##fix less more - # Integer(-40, 12, name='sell_dist_8_roc200sma_more_value'),#*2 - - ##below high 100 - #Integer(0, 0, name='sell_dist_9_high100_more_value'), - -#------------------------------------------------------------------------------------------------------- - - - - - ] - - - - @staticmethod - def sell_strategy_generator(params: Dict[str, Any]) -> Callable: - """ - Define the sell strategy parameters to be used by hyperopt - """ - def populate_sell_trend(dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Sell strategy Hyperopt will build and use - """ - # print(params) - conditions = [] - # GUARDS AND TRENDS - - -#--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -#--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - - ##MAIN SELECTORS - -#-------------------- - - ##VOLATILITY - - conditions.append(dataframe['vol_mid'] > 0 ) - - # conditions.append((dataframe['vol_low'] > 0) |(dataframe['vol_mid'] > 0) ) - - # conditions.append((dataframe['vol_high'] > 0) |(dataframe['vol_mid'] > 0) ) - -#-------------------- - - - ##PICKS TREND COMBO - - conditions.append( - - (dataframe['uptrend'] >= params['main_1_trend_strength']) - |#OR & - (dataframe['uptrendsmall'] >= params['main_2_trend_strength']) - - ) - - ##UPTREND - #conditions.append(dataframe['uptrend'] >= params['main_1_trend_strength']) - ##DOWNTREND - #conditions.append(dataframe['downtrend'] >= params['main_1_trend_strength']) - ##NOTREND - #conditions.append((dataframe['uptrend'] <1)&(dataframe['downtrend'] <1)) - -#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - ##ABOVE/BELOW VALUES - - #RSI BELOW - if 'include_ab_9_rsi_below_value' in params and params['include_ab_9_rsi_below_value']: - conditions.append(dataframe['rsi'] < params['ab_9_rsi_below_value']) - #RSI RECENT PEAK 5 - if 'include_ab_10_rsi_recent_peak_2_value' in params and params['include_ab_10_rsi_recent_peak_2_value']: - conditions.append(dataframe['rsi'].rolling(2).max() < params['ab_10_rsi_recent_peak_2_value']) - - #RSI RECENT PEAK 12 - if 'include_ab_11_rsi_recent_peak_4_value' in params and params['include_ab_11_rsi_recent_peak_4_value']: - conditions.append(dataframe['rsi'].rolling(4).max() < params['ab_11_rsi_recent_peak_4_value']) - #RSI5 BELOW - if 'include_ab_12_rsi5_below_value' in params and params['include_ab_12_rsi5_below_value']: - conditions.append(dataframe['rsi5'] < params['ab_12_rsi5_below_value']) - #RSI50 BELOW - if 'include_ab_13_rsi50_below_value' in params and params['include_ab_13_rsi50_below_value']: - conditions.append(dataframe['rsi50'] < params['ab_13_rsi50_below_value']) - -#----------------------- - - #ROC BELOW - if 'include_ab_14_roc_below_value' in params and params['include_ab_14_roc_below_value']: - conditions.append(dataframe['roc'] < (params['ab_14_roc_below_value']/2)) - #ROC50 BELOW - if 'include_ab_15_roc50_below_value' in params and params['include_ab_15_roc50_below_value']: - conditions.append(dataframe['roc50'] < (params['ab_15_roc50_below_value'])) - #ROC2 BELOW - if 'include_ab_16_roc2_below_value' in params and params['include_ab_16_roc2_below_value']: - conditions.append(dataframe['roc2'] < (params['ab_16_roc2_below_value']/2)) - -#----------------------- - - #PPO5 BELOW - if 'include_ab_17_ppo5_below_value' in params and params['include_ab_17_ppo5_below_value']: - conditions.append(dataframe['ppo5'] < (params['ab_17_ppo5_below_value']/2)) - #PPO10 BELOW - if 'include_ab_18_ppo10_below_value' in params and params['include_ab_18_ppo10_below_value']: - conditions.append(dataframe['ppo10'] < (params['ab_18_ppo10_below_value']/2)) - #PPO25 BELOW - if 'include_ab_19_ppo25_below_value' in params and params['include_ab_19_ppo25_below_value']: - conditions.append(dataframe['ppo25'] < (params['ab_19_ppo25_below_value']/2)) - - #PPO50 BELOW - if 'include_ab_20_ppo50_below_value' in params and params['include_ab_20_ppo50_below_value']: - conditions.append(dataframe['ppo50'] < (params['ab_20_ppo50_below_value']/2)) - #PPO100 BELOW - if 'include_ab_21_ppo100_below_value' in params and params['include_ab_21_ppo100_below_value']: - conditions.append(dataframe['ppo100'] < (params['ab_21_ppo100_below_value'])) - #PPO200 BELOW - if 'include_ab_22_ppo200_below_value' in params and params['include_ab_22_ppo200_below_value']: - conditions.append(dataframe['ppo200'] < (params['ab_22_ppo200_below_value'])) - #PPO500 BELOW - if 'include_ab_23_ppo500_below_value' in params and params['include_ab_23_ppo500_below_value']: - conditions.append(dataframe['ppo500'] < (params['ab_23_ppo500_below_value']*2)) - - ##USE AT A LATER STEP - - #convsmall BELOW - if 'include_ab_24_convsmall_below_value' in params and params['include_ab_24_convsmall_below_value']: - conditions.append(dataframe['convsmall'] < (params['ab_24_convsmall_below_value']/2)) - #convmedium BELOW - if 'include_ab_25_convmedium_below_value' in params and params['include_ab_25_convmedium_below_value']: - conditions.append(dataframe['convmedium'] < (params['ab_25_convmedium_below_value'])) - #convlarge BELOW - if 'include_ab_26_convlarge_below_value' in params and params['include_ab_26_convlarge_below_value']: - conditions.append(dataframe['convlarge'] < (params['ab_26_convlarge_below_value'])) - #convultra BELOW - if 'include_ab_27_convultra_below_value' in params and params['include_ab_27_convultra_below_value']: - conditions.append(dataframe['convultra'] < (params['ab_27_convultra_below_value']/2)) - #convdist BELOW - if 'include_ab_28_convdist_below_value' in params and params['include_ab_28_convdist_below_value']: - conditions.append(dataframe['convdist'] < (params['ab_28_convdist_below_value'])) - -#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - - ##SMA'S GOING UP - - if 'up_0a_sma3' in params and params['up_0a_sma3']: - conditions.append((dataframe['sma3'].shift(1) <dataframe['sma3'])) - if 'up_0b_sma5' in params and params['up_0b_sma5']: - conditions.append((dataframe['sma5'].shift(1) <dataframe['sma5'])) - if 'up_1_sma10' in params and params['up_1_sma10']: - conditions.append((dataframe['sma10'].shift(1) <dataframe['sma10'])) - if 'up_2_sma25' in params and params['up_2_sma25']: - conditions.append((dataframe['sma25'].shift(1) <dataframe['sma25'])) - if 'up_3_sma50' in params and params['up_3_sma50']: - conditions.append((dataframe['sma50'].shift(2) <dataframe['sma50'])) - if 'up_4_sma100' in params and params['up_4_sma100']: - conditions.append((dataframe['sma100'].shift(3) <dataframe['sma100'])) - if 'up_5_sma200' in params and params['up_5_sma200']: - conditions.append((dataframe['sma200'].shift(4) <dataframe['sma200'])) - - if 'up_6_sma400' in params and params['up_6_sma400']: - conditions.append((dataframe['sma400'].shift(4) <dataframe['sma400'])) - if 'up_7_sma10k' in params and params['up_7_sma10k']: - conditions.append((dataframe['sma10k'].shift(5) <dataframe['sma10k'])) - # if 'up_8_sma20k' in params and params['up_8_sma20k']: - # conditions.append((dataframe['sma20k'].shift(5) <dataframe['sma20k'])) - # if 'up_9_sma30k' in params and params['up_9_sma30k']: - # conditions.append((dataframe['sma30k'].shift(5) <dataframe['sma30k'])) - - if 'up_10_convsmall' in params and params['up_10_convsmall']: - conditions.append((dataframe['convsmall'].shift(2) <dataframe['convsmall'])) - if 'up_11_convmedium' in params and params['up_11_convmedium']: - conditions.append((dataframe['convmedium'].shift(3) <dataframe['convmedium'])) - if 'up_12_convlarge' in params and params['up_12_convlarge']: - conditions.append((dataframe['convlarge'].shift(4) <dataframe['convlarge'])) - if 'up_13_convultra' in params and params['up_13_convultra']: - conditions.append((dataframe['convultra'].shift(4) <dataframe['convultra'])) - if 'up_14_convdist' in params and params['up_14_convdist']: - conditions.append((dataframe['convdist'].shift(4) <dataframe['convdist'])) - - if 'up_15_vol50' in params and params['up_15_vol50']: - conditions.append((dataframe['vol50'].shift(2) <dataframe['vol50'])) - if 'up_16_vol100' in params and params['up_16_vol100']: - conditions.append((dataframe['vol100'].shift(3) <dataframe['vol100'])) - if 'up_17_vol175' in params and params['up_17_vol175']: - conditions.append((dataframe['vol175'].shift(4) <dataframe['vol175'])) - if 'up_18_vol250' in params and params['up_18_vol250']: - conditions.append((dataframe['vol250'].shift(4) <dataframe['vol250'])) - if 'up_19_vol500' in params and params['up_19_vol500']: - conditions.append((dataframe['vol500'].shift(4) <dataframe['vol500'])) - - if 'up_20_vol1000' in params and params['up_20_vol1000']: - conditions.append((dataframe['vol1000'].shift(4) <dataframe['vol1000'])) - if 'up_21_vol100mean' in params and params['up_21_vol100mean']: - conditions.append((dataframe['vol100mean'].shift(4) <dataframe['vol100mean'])) - if 'up_22_vol250mean' in params and params['up_22_vol250mean']: - conditions.append((dataframe['vol250mean'].shift(4) <dataframe['vol250mean'])) - - - if 'up_20_conv3' in params and params['up_20_conv3']: - conditions.append(((dataframe['conv3'].shift(25) < dataframe['conv3'])&(dataframe['conv3'].shift(50) < dataframe['conv3']))) - if 'up_21_vol5' in params and params['up_21_vol5']: - conditions.append(((dataframe['vol5'].shift(25) < dataframe['vol5'])&(dataframe['vol5'].shift(50) < dataframe['vol5']))) - if 'up_22_vol5ultra' in params and params['up_22_vol5ultra']: - conditions.append(((dataframe['vol5ultra'].shift(25) < dataframe['vol5ultra'])&(dataframe['vol5ultra'].shift(50) < dataframe['vol5ultra']))) - if 'up_23_vol1ultra' in params and params['up_23_vol1ultra']: - conditions.append(((dataframe['vol1ultra'].shift(25) < dataframe['vol1ultra'])& (dataframe['vol1ultra'].shift(50) < dataframe['vol1ultra']))) - if 'up_24_vol1' in params and params['up_24_vol1']: - conditions.append(((dataframe['vol1'].shift(30) < dataframe['vol1'])&(dataframe['vol1'].shift(10) < dataframe['vol1']))) - if 'up_25_vol5inc24' in params and params['up_25_vol5inc24']: - conditions.append((dataframe['vol5inc24'].shift(50) < dataframe['vol5inc24'])) - - -#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - ##ABOVE / BELOW SMAS 1 above/ 0 None / -1 below - - #SMA10 - conditions.append((dataframe['close'] > dataframe['sma10'])|(0.5 > params['ab_1_sma10'])) - conditions.append((dataframe['close'] < dataframe['sma10'])|(-0.5 < params['ab_1_sma10'])) - #SMA25 - conditions.append((dataframe['close'] > dataframe['sma25'])|(0.5 > params['ab_2_sma25'])) - conditions.append((dataframe['close'] < dataframe['sma25'])|(-0.5 < params['ab_2_sma25'])) - #SMA50 - conditions.append((dataframe['close'] > dataframe['sma50'])|(0.5 > params['ab_3_sma50'])) - conditions.append((dataframe['close'] < dataframe['sma50'])|(-0.5 < params['ab_3_sma50'])) - - - #SMA100 - conditions.append((dataframe['close'] > dataframe['sma100'])|(0.5 > params['ab_4_sma100'])) - conditions.append((dataframe['close'] < dataframe['sma100'])|(-0.5 < params['ab_4_sma100'])) - #SMA100 - conditions.append((dataframe['close'] > dataframe['sma200'])|(0.5 > params['ab_5_sma200'])) - conditions.append((dataframe['close'] < dataframe['sma200'])|(-0.5 < params['ab_5_sma200'])) - #SMA400 - conditions.append((dataframe['close'] > dataframe['sma400'])|(0.5 > params['ab_6_sma400'])) - conditions.append((dataframe['close'] < dataframe['sma400'])|(-0.5 < params['ab_6_sma400'])) - #SMA10k - conditions.append((dataframe['close'] > dataframe['sma10k'])|(0.5 > params['ab_7_sma10k'])) - conditions.append((dataframe['close'] < dataframe['sma10k'])|(-0.5 < params['ab_7_sma10k'])) - -#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - ##DOWNSWINGS / UPSWINGS PPO'S - - #ppo5 UP OR DOWN (1 UP, 0 NOTHING, -1 DOWN) - conditions.append((dataframe['ppo5'].shift(1) <dataframe['ppo5'])|(0.5 > params['swings_1_ppo5_up_or_down_bool'])) - conditions.append((dataframe['ppo5'].shift(1) >dataframe['ppo5'])|(-0.5 < params['swings_1_ppo5_up_or_down_bool'])) - #ppo10 - conditions.append((dataframe['ppo10'].shift(1) <dataframe['ppo10'])|(0.5 > params['swings_2_ppo10_up_or_down_bool'])) - conditions.append((dataframe['ppo10'].shift(1) >dataframe['ppo10'])|(-0.5 < params['swings_2_ppo10_up_or_down_bool'])) - #ppo25 - conditions.append((dataframe['ppo25'].shift(1) <dataframe['ppo25'])|(0.5 > params['swings_3_ppo25_up_or_down_bool'])) - #conditions.append((dataframe['ppo25'].shift(1) >dataframe['ppo25'])|(-0.5 < params['swings_3_ppo25_up_or_down_bool'])) - - #ppo50 - conditions.append((dataframe['ppo50'].shift(2) <dataframe['ppo50'])|(0.5 > params['swings_4_ppo50_up_or_down_bool'])) - #conditions.append((dataframe['ppo50'].shift(2) >dataframe['ppo50'])|(-0.5 < params['swings_4_ppo50_up_or_down_bool'])) - #ppo100 - conditions.append((dataframe['ppo100'].shift(3) <dataframe['ppo100'])|(0.5 > params['swings_5_ppo100_up_or_down_bool'])) - #conditions.append((dataframe['ppo100'].shift(3) >dataframe['ppo100'])|(-0.5 < params['swings_5_ppo100_up_or_down_bool'])) - #ppo200 - conditions.append((dataframe['ppo200'].shift(4) <dataframe['ppo200'])|(0.5 > params['swings_6_ppo200_up_or_down_bool'])) - #conditions.append((dataframe['ppo200'].shift(4) >dataframe['ppo200'])|(-0.5 < params['swings_6_ppo200_up_or_down_bool'])) - #ppo500 - conditions.append((dataframe['ppo500'].shift(5) <dataframe['ppo500'])|(0.5 > params['swings_7_ppo500_up_or_down_bool'])) - #conditions.append((dataframe['ppo500'].shift(5) >dataframe['ppo500'])|(-0.5 < params['swings_7_ppo500_up_or_down_bool'])) - - #roc50 - conditions.append((dataframe['roc50'].shift(2) <dataframe['roc50'])|(0.5 > params['swings_8_roc50_up_or_down_bool'])) - #conditions.append((dataframe['roc50'].shift(3) >dataframe['roc50'])|(-0.5 < params['swings_8_roc50_up_or_down_bool'])) - #roc10 - conditions.append((dataframe['roc10'].shift(1) <dataframe['roc10'])|(0.5 > params['swings_9_roc10_up_or_down_bool'])) - #conditions.append((dataframe['roc10'].shift(2) >dataframe['roc10'])|(-0.5 < params['swings_9_roc10_up_or_down_bool'])) - - -#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - - ##DISTANCES/ROC - - #dist50 LESS THAN - if 'include_dist_1_dist50_less_value' in params and params['include_dist_1_dist50_less_value']: - conditions.append(dataframe['dist50'] < (params['dist_1_dist50_less_value'])) - #dist200 LESS THAN - if 'include_dist_2_dist200_less_value' in params and params['include_dist_2_dist200_less_value']: - conditions.append(dataframe['dist200'] < (params['dist_2_dist200_less_value'])) - - #dist400 LESS THAN - if 'include_dist_3_dist400_less_value' in params and params['include_dist_3_dist400_less_value']: - conditions.append(dataframe['dist400'] < (params['dist_3_dist400_less_value'])) - #dist10k LESS THAN - if 'include_dist_4_dist10k_less_value' in params and params['include_dist_4_dist10k_less_value']: - conditions.append(dataframe['dist10k'] < (params['dist_4_dist10k_less_value'])) - - #less =further from top bol - #dist_upbol50 LESS THAN - if 'include_dist_5_dist_upbol50_less_value' in params and params['include_dist_5_dist_upbol50_less_value']: - conditions.append(dataframe['dist_upbol50'] < (params['dist_5_dist_upbol50_less_value']/2)) - #dist_upbol100 LESS THAN - if 'include_dist_6_dist_upbol100_less_value' in params and params['include_dist_6_dist_upbol100_less_value']: - conditions.append(dataframe['dist_upbol100'] < (params['dist_6_dist_upbol100_less_value']/2)) - - # #less =closer to bot bol - # #dist_upbol50 LESS THAN - # if 'include_dist_7_dist_lowbol50_less_value' in params and params['include_dist_7_dist_lowbol50_less_value']: - # conditions.append(dataframe['dist_lowbol50'] < (params['dist_7_dist_lowbol50_less_value']/2)) - # #dist_upbol100 LESS THAN - # if 'include_dist_8_dist_lowbol100_less_value' in params and params['include_dist_8_dist_lowbol100_less_value']: - # conditions.append(dataframe['dist_lowbol100'] < (params['dist_8_dist_lowbol100_less_value']/2)) - - - - #others - ##roc50sma MORE THAN - if 'include_dist_7_roc50sma_less_value' in params and params['include_dist_7_roc50sma_less_value']: - conditions.append(dataframe['roc50sma'] < (params['dist_7_roc50sma_less_value']*2)) - #roc200sma MORE THAN - if 'include_dist_8_roc200sma_less_value' in params and params['include_dist_8_roc200sma_less_value']: - conditions.append(dataframe['roc200sma'] < (params['dist_8_roc200sma_less_value']*2)) - - ##ENABLE TO BUY AWAY FROM HIGH - # #HIGH500 TO CLOSE MORE THAN - #if 'include_dist_9_high100_more_value' in params and params['include_dist_9_high100_more_value']: - # conditions.append((dataframe['high100']-dataframe['close']) > ((dataframe['high100']/100* (params['dist_9_high100_more_value'])) - -#------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - - - - - # Check that volume is not 0 - conditions.append(dataframe['volume'] > 0) - - if conditions: - - - # ##ENABLE SELLS ALWAYS ON OTHER VOLATILITYS - # dataframe.loc[ - # ((dataframe['vol_low'] > 0) |(dataframe['vol_high'] > 0) ), - # 'sell'] = 1 - - - # ##ENABLE PRODUCTION SELLS - # dataframe.loc[ - # (add_production_sells_low(dataframe)), - # 'sell'] = 1 - # - - dataframe.loc[ - (~(reduce(lambda x, y: x & y, conditions)))&OPTIMIZED_RULE(dataframe,params), - 'sell'] = 1 - - return dataframe - - return populate_sell_trend - - @staticmethod - def sell_indicator_space() -> List[Dimension]: - """ - Define your Hyperopt space for searching sell strategy parameters - """ - return [ - - -#------------------------------------------------------------------------------------------------------- - - ## CUSTOM RULE TRESHOLDS - - # SKDecimal(5.0, 7.0,decimals=1, name='sell_trigger_0_roc_ab_value'),# 5 range 5-7 or 4-7 - # SKDecimal(3.2, 4.5,decimals=1, name='sell_trigger_0_roc2_ab_value'),#3.8 range 3.2-4.5 - # Integer(77, 82, name='sell_trigger_0_rsi_ab_value'),#79 range 82-77 - # Integer(90, 95, name='sell_trigger_0_rsi5_ab_value'),#94 range 95-90 - # Integer(63, 67, name='sell_trigger_0_rsi50_ab_value'),#66 range 67-63 - -#------------------------------------------------------------------------------------------------------- - - ##MAIN - - Categorical([1, 2, 3], name='main_1_trend_strength'), #UPTREND STR - Categorical([1, 2, 3], name='main_2_trend_strength'), #SMALL UPTREND STR - - - #Categorical([-1, 0, 1], name='main_2_small_uptrend_downtrend'), #SMALL UPTREND ON/OFF 1 is on -1 is down - -#------------------------------------------------------------------------------------------------------- - - ##INCLUDE/EXCLUDE RULES - - Categorical([True, False], name='include_ab_9_rsi_below_value'), - Categorical([True, False], name='include_ab_10_rsi_recent_peak_2_value'), - Categorical([True, False], name='include_ab_11_rsi_recent_peak_4_value'), - Categorical([True, False], name='include_ab_12_rsi5_below_value'), - Categorical([True, False], name='include_ab_13_rsi50_below_value'), - - Categorical([True, False], name='include_ab_14_roc_below_value'), - Categorical([True, False], name='include_ab_15_roc50_below_value'), - Categorical([True, False], name='include_ab_16_roc2_below_value'), - - Categorical([True, False], name='include_ab_17_ppo5_below_value'), - Categorical([True, False], name='include_ab_18_ppo10_below_value'), - Categorical([True, False], name='include_ab_19_ppo25_below_value'), - - Categorical([True, False], name='include_ab_20_ppo50_below_value'), - Categorical([True, False], name='include_ab_21_ppo100_below_value'), - Categorical([True, False], name='include_ab_22_ppo200_below_value'), - Categorical([True, False], name='include_ab_23_ppo500_below_value'), - - ##USE AT A LATER STEP - Categorical([True, False], name='include_ab_24_convsmall_below_value'), - Categorical([True, False], name='include_ab_25_convmedium_below_value'), - Categorical([True, False], name='include_ab_26_convlarge_below_value'), - Categorical([True, False], name='include_ab_27_convultra_below_value'), - - Categorical([True, False], name='include_ab_28_convdist_below_value'), - - Categorical([True, False], name='include_dist_1_dist50_less_value'), - Categorical([True, False], name='include_dist_2_dist200_less_value'), - Categorical([True, False], name='include_dist_3_dist400_less_value'), - Categorical([True, False], name='include_dist_4_dist10k_less_value'), - - Categorical([True, False], name='include_dist_5_dist_upbol50_less_value'), - Categorical([True, False], name='include_dist_6_dist_upbol100_less_value'), - - - # FOR MORE DOWNTREND BUYS LIKELY - # Categorical([True, False], name='include_dist_7_dist_lowbol50_less_value'), - # Categorical([True, False], name='include_dist_8_dist_lowbol100_less_value'), - - #MORE LIKE TRIGGERS - Categorical([True, False], name='include_dist_7_roc50sma_less_value'), - Categorical([True, False], name='include_dist_8_roc200sma_less_value'), - - ##below high 100 - #Categorical([True, False], name='include_dist_9_high100_more_value'), - - - -#------------------------------------------------------------------------------------------------------- - - ##ABOVE/BELOW VALUES - - Integer(35, 75, name='ab_9_rsi_below_value'), - Integer(60, 82, name='ab_10_rsi_recent_peak_2_value'), - Integer(60, 82, name='ab_11_rsi_recent_peak_4_value'), - Integer(40, 101, name='ab_12_rsi5_below_value'), - Integer(37, 73, name='ab_13_rsi50_below_value'), - - Integer(-6, 10, name='ab_14_roc_below_value'),#/2 - Integer(-8, 8, name='ab_15_roc50_below_value'), - Integer(-4, 6, name='ab_16_roc2_below_value'),#/2 - -#-------------------------------- - - Integer(-4, 4, name='ab_17_ppo5_below_value'),#/2 - Integer(-5, 5, name='ab_18_ppo10_below_value'),#/2 - Integer(-8, 10, name='ab_19_ppo25_below_value'),#/2 - - Integer(-6, 7, name='ab_20_ppo50_below_value'),#/2 - Integer(-6, 7, name='ab_21_ppo100_below_value'), - Integer(-5, 7, name='ab_22_ppo200_below_value'), - Integer(-4, 4, name='ab_23_ppo500_below_value'),#*2 - - ##USE AT A LATER STEP - - Integer(1, 12, name='ab_24_convsmall_below_value'),#/2 #final - Integer(1, 6, name='ab_25_convmedium_below_value'),#final - Integer(1, 15, name='ab_26_convlarge_below_value'), #final - Integer(2, 12, name='ab_27_convultra_below_value'),#/2 #final - - Integer(2, 30, name='ab_28_convdist_below_value'), - -#------------------------------------------------------------------------------------------------------- - - #SMA'S GOING UP - - Categorical([True, False], name='up_0a_sma3'), - Categorical([True, False], name='up_0b_sma5'), - Categorical([True, False], name='up_1_sma10'), - Categorical([True, False], name='up_2_sma25'), - Categorical([True, False], name='up_3_sma50'), - Categorical([True, False], name='up_4_sma100'), - Categorical([True, False], name='up_5_sma200'), - - Categorical([True, False], name='up_6_sma400'), - Categorical([True, False], name='up_7_sma10k'), - # Categorical([True, False], name='up_8_sma20k'), - # Categorical([True, False], name='up_9_sma30k'), - - Categorical([True, False], name='up_10_convsmall'), - Categorical([True, False], name='up_11_convmedium'), - Categorical([True, False], name='up_12_convlarge'), - Categorical([True, False], name='up_13_convultra'), - Categorical([True, False], name='up_14_convdist'), - - Categorical([True, False], name='up_15_vol50'), - Categorical([True, False], name='up_16_vol100'), - Categorical([True, False], name='up_17_vol175'), - Categorical([True, False], name='up_18_vol250'), - Categorical([True, False], name='up_19_vol500'), - - Categorical([True, False], name='up_20_vol1000'), - Categorical([True, False], name='up_21_vol100mean'), - Categorical([True, False], name='up_22_vol250mean'), - -#------------------------------------------------------------------------------------------------------- - - ##ABOVE/BELOW SMAS - - Categorical([-1, 0, 1], name='ab_1_sma10'), - Categorical([-1, 0, 1], name='ab_2_sma25'), - Categorical([-1, 0, 1], name='ab_3_sma50'), - - Categorical([-1, 0, 1], name='ab_4_sma100'), - Categorical([-1, 0, 1], name='ab_5_sma200'), - Categorical([-1, 0, 1], name='ab_6_sma400'), - Categorical([-1, 0, 1], name='ab_7_sma10k'), - -#------------------------------------------------------------------------------------------------------- - - ##DOWNSWINGS / UPSWINGS PPO'S - - ##UP OR DOWN (1 UP, 0 NOTHING, -1 DOWN) - - Categorical([-1, 0, 1], name='swings_1_ppo5_up_or_down_bool'), # -1 down, 1 up , 0 off - Categorical([-1, 0, 1],name='swings_2_ppo10_up_or_down_bool'), - Categorical([-1, 0, 1], name='swings_3_ppo25_up_or_down_bool'), #1 up , 0 off - - Categorical([0, 1], name='swings_4_ppo50_up_or_down_bool'), - Categorical([0, 1], name='swings_5_ppo100_up_or_down_bool'), - Categorical([0, 1], name='swings_6_ppo200_up_or_down_bool'), - Categorical([ 0, 1],name='swings_7_ppo500_up_or_down_bool'), - - Categorical([0, 1], name='swings_8_roc50_up_or_down_bool'), - Categorical([0, 1], name='swings_9_roc10_up_or_down_bool'), - -#------------------------------------------------------------------------------------------------------- - - ##DISTANCES - - Integer(-7, 14, name='dist_1_dist50_less_value'), ##extreme 8-30 - Integer(-8, 25, name='dist_2_dist200_less_value'), ##extreme 12 -40 - Integer(-12, 35, name='dist_3_dist400_less_value'), - Integer(-12, 40, name='dist_4_dist10k_less_value'), - - Integer(-25, 30, name='dist_5_dist_upbol50_less_value'),#/2 - Integer(-25, 30, name='dist_6_dist_upbol100_less_value'),#/2 - - - # FOR MORE DOWNTREND BUYS LIKELY - # Integer(-6, 100, name='dist_7_dist_lowbol50_less_value'),#/2 - # Integer(-6, 100, name='dist_8_dist_lowbol100_less_value'),#/2 - - ##MORE LIKE TRIGGERS - # Integer(-40, 70, name='dist_7_roc50sma_less_value'),#*2 ##pretty extreme - # Integer(-12, 40, name='dist_8_roc200sma_less_value'),#*2 - - ##below high 100 - #Integer(0, 0, name='dist_9_high100_more_value'), - -#------------------------------------------------------------------------------------------------------- - - - - - - ] - - -def OPTIMIZED_RULE(dataframe,params): - return( - - (dataframe['sma100'] < dataframe['close']) - - ) - -def add_production_buys_mid(dataframe): - return( - - MID_VOLATILITY(dataframe) - & - mid_volatility_buyer(dataframe) - ) - -def add_production_sells_mid(dataframe): - return( - - MID_VOLATILITY(dataframe) - & - mid_volatility_seller(dataframe) - ) - - From 8b2c14a6fa65d31d8496c5991a64380ef81b8127 Mon Sep 17 00:00:00 2001 From: theluxaz <theluxaz@gmail.com> Date: Thu, 14 Oct 2021 01:15:43 +0300 Subject: [PATCH 10/26] Readme fix --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 1eb96f200..906e19ef7 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,9 @@ For any other type of installation please refer to [Installation doc](https://ww usage: freqtrade [-h] [-V] {trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver} ... + Free, open source crypto trading bot + positional arguments: {trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver} trade Trade module. @@ -110,9 +112,11 @@ positional arguments: plot-dataframe Plot candles with indicators. plot-profit Generate plot showing profits. webserver Webserver module. + optional arguments: -h, --help show this help message and exit -V, --version show program's version number and exit + ``` ### Telegram RPC commands From ed39b8dab06e4b1676710b402de4117ddfc4659f Mon Sep 17 00:00:00 2001 From: theluxaz <theluxaz@gmail.com> Date: Thu, 14 Oct 2021 01:18:16 +0300 Subject: [PATCH 11/26] fixed profit total calculation --- freqtrade/optimize/optimize_reports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 5187cb0ba..0d001b230 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -82,7 +82,7 @@ def _generate_result_line(result: DataFrame, starting_balance: int, first_column 'profit_sum_pct': round(profit_sum * 100.0, 2), 'profit_total_abs': result['profit_abs'].sum(), 'profit_total': profit_total, - 'profit_total_pct': round(profit_sum * 100.0, 2), + 'profit_total_pct': round(profit_total * 100.0, 2), 'duration_avg': str(timedelta( minutes=round(result['trade_duration'].mean())) ) if not result.empty else '0:00', From 0bb7ea10ab03034c05e33443889c10e8fa8fd5dc Mon Sep 17 00:00:00 2001 From: theluxaz <theluxaz@gmail.com> Date: Thu, 14 Oct 2021 01:34:30 +0300 Subject: [PATCH 12/26] Fixed minor header for backtesting --- freqtrade/optimize/optimize_reports.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 0d001b230..0e8467788 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -54,6 +54,14 @@ 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()) + """ + return [first_column, 'Sells', 'Avg Profit %', 'Cum Profit %', + f'Tot Profit {stake_currency}', 'Tot Profit %', 'Avg Duration', + 'Win Draw Loss Win%'] + def _generate_wins_draws_losses(wins, draws, losses): if wins > 0 and losses == 0: @@ -608,8 +616,10 @@ def text_table_tags(tag_type:str, tag_results: List[Dict[str, Any]], stake_curre :param stake_currency: stake-currency - used to correctly name headers :return: pretty printed table with tabulate as string """ - - headers = _get_line_header("TAG", stake_currency) + 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'], From 69a59cdf37e79de1e476dc32d5c9a482bcd9ea51 Mon Sep 17 00:00:00 2001 From: theluxaz <theluxaz@gmail.com> Date: Mon, 18 Oct 2021 23:56:41 +0300 Subject: [PATCH 13/26] Fixed flake 8, changed sell_tag to exit_tag and fixed telegram functions --- freqtrade/data/btanalysis.py | 2 +- freqtrade/enums/signaltype.py | 2 +- freqtrade/freqtradebot.py | 41 ++++--- freqtrade/optimize/backtesting.py | 21 ++-- freqtrade/optimize/optimize_reports.py | 93 ++++++--------- freqtrade/persistence/migrations.py | 8 +- freqtrade/persistence/models.py | 158 ++++++++++++++----------- freqtrade/rpc/rpc.py | 11 +- freqtrade/rpc/telegram.py | 83 +++++-------- freqtrade/strategy/interface.py | 8 +- 10 files changed, 206 insertions(+), 221 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 82b2bb3a9..3dba635e6 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -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: diff --git a/freqtrade/enums/signaltype.py b/freqtrade/enums/signaltype.py index 32ac19ba4..4437f49e3 100644 --- a/freqtrade/enums/signaltype.py +++ b/freqtrade/enums/signaltype.py @@ -14,4 +14,4 @@ class SignalTagType(Enum): Enum for signal columns """ BUY_TAG = "buy_tag" - SELL_TAG = "sell_tag" \ No newline at end of file + EXIT_TAG = "exit_tag" diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index d415c9d93..73d9bb382 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -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'], diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 69f2d2580..6c2a20cb1 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -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 diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 0e8467788..30005f524 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -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() diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 0f07c13b5..d0b3add3c 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -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! diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 33a4429c0..945201982 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -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)): diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 85973add6..508ce6894 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -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]]: """ diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index db745ff37..85a91a10e 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -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'])} " diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 3c82d4d25..e4bf6ca69 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -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): From 1fdc4425dd8ac0fce2ec32198bb125e5f8a6f1e6 Mon Sep 17 00:00:00 2001 From: theluxaz <theluxaz@gmail.com> Date: Wed, 20 Oct 2021 01:26:15 +0300 Subject: [PATCH 14/26] Changed exit_tag to be represented as sell_reason --- freqtrade/freqtradebot.py | 5 +++-- freqtrade/optimize/backtesting.py | 7 +++--- freqtrade/optimize/optimize_reports.py | 12 ---------- freqtrade/persistence/models.py | 31 ++++++++++++-------------- freqtrade/rpc/rpc.py | 15 ++++++++----- freqtrade/rpc/telegram.py | 29 +++++++----------------- 6 files changed, 37 insertions(+), 62 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 73d9bb382..5ecf5b2a3 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1150,6 +1150,7 @@ class FreqtradeBot(LoggingMixin): trade.close_rate_requested = limit trade.sell_reason = sell_reason.sell_reason if(exit_tag is not None): + trade.sell_reason = exit_tag 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'): @@ -1191,8 +1192,8 @@ class FreqtradeBot(LoggingMixin): 'current_rate': current_rate, 'profit_amount': profit_trade, 'profit_ratio': profit_ratio, + 'buy_tag': trade.buy_tag, 'sell_reason': trade.sell_reason, - 'exit_tag': trade.exit_tag, 'open_date': trade.open_date, 'close_date': trade.close_date or datetime.utcnow(), 'stake_currency': self.config['stake_currency'], @@ -1235,8 +1236,8 @@ class FreqtradeBot(LoggingMixin): 'current_rate': current_rate, 'profit_amount': profit_trade, 'profit_ratio': profit_ratio, + 'buy_tag': trade.buy_tag, 'sell_reason': trade.sell_reason, - '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'], diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 6c2a20cb1..827be4d76 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -360,11 +360,10 @@ class Backtesting: if sell.sell_flag: trade.close_date = sell_candle_time - 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 + if(sell_row[EXIT_TAG_IDX] is not None): + trade.sell_reason = sell_row[EXIT_TAG_IDX] + trade.exit_tag = sell_row[EXIT_TAG_IDX] 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) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 30005f524..67dacd7c6 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -387,8 +387,6 @@ def generate_strategy_stats(btdata: Dict[str, DataFrame], 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) @@ -414,7 +412,6 @@ 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_exit_tag': exit_tag_results, 'sell_reason_summary': sell_reason_stats, 'left_open_trades': left_open_results, 'total_trades': len(results), @@ -744,15 +741,6 @@ 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_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]), '=')) - print(table) - table = text_table_sell_reason(sell_reason_stats=results['sell_reason_summary'], stake_currency=stake_currency) if isinstance(table, str) and len(table) > 0: diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 945201982..e03830d7f 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -325,7 +325,6 @@ class LocalTrade(): 'profit_pct': round(self.close_profit * 100, 2) if self.close_profit else None, 'profit_abs': self.close_profit_abs, - # +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, @@ -904,15 +903,15 @@ class Trade(_DECL_BASE, LocalTrade): ] @staticmethod - def get_exit_tag_performance(pair: str) -> List[Dict[str, Any]]: + def get_sell_reason_performance(pair: str) -> List[Dict[str, Any]]: """ - Returns List of dicts containing all Trades, based on exit tag performance + Returns List of dicts containing all Trades, based on sell reason performance Can either be average for all pairs or a specific pair provided NOTE: Not supported in Backtesting. """ if(pair is not None): tag_perf = Trade.query.with_entities( - Trade.exit_tag, + Trade.sell_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') @@ -922,29 +921,29 @@ class Trade(_DECL_BASE, LocalTrade): .all() else: tag_perf = Trade.query.with_entities( - Trade.exit_tag, + Trade.sell_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(Trade.is_open.is_(False))\ - .group_by(Trade.exit_tag) \ + .group_by(Trade.sell_reason) \ .order_by(desc('profit_sum_abs')) \ .all() return [ { - 'exit_tag': exit_tag if exit_tag is not None else "Other", + 'sell_reason': sell_reason if sell_reason is not None else "Other", 'profit': profit, 'profit_abs': profit_abs, 'count': count } - for exit_tag, profit, profit_abs, count in tag_perf + for sell_reason, 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 + exit_tag performance + Returns List of dicts containing all Trades, based on buy_tag + sell_reason performance Can either be average for all pairs or a specific pair provided NOTE: Not supported in Backtesting. """ @@ -952,7 +951,7 @@ class Trade(_DECL_BASE, LocalTrade): tag_perf = Trade.query.with_entities( Trade.id, Trade.buy_tag, - Trade.exit_tag, + Trade.sell_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') @@ -965,7 +964,7 @@ class Trade(_DECL_BASE, LocalTrade): tag_perf = Trade.query.with_entities( Trade.id, Trade.buy_tag, - Trade.exit_tag, + Trade.sell_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') @@ -975,12 +974,12 @@ class Trade(_DECL_BASE, LocalTrade): .all() return_list = [] - for id, buy_tag, exit_tag, profit, profit_abs, count in tag_perf: + for id, buy_tag, sell_reason, 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" + sell_reason = sell_reason if sell_reason is not None else "Other" - if(exit_tag is not None and buy_tag is not None): - mix_tag = buy_tag + " " + exit_tag + if(sell_reason is not None and buy_tag is not None): + mix_tag = buy_tag + " " + sell_reason i = 0 if not any(item["mix_tag"] == mix_tag for item in return_list): return_list.append({'mix_tag': mix_tag, @@ -990,8 +989,6 @@ class Trade(_DECL_BASE, LocalTrade): 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"], diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 508ce6894..2a664e7bc 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -161,6 +161,8 @@ class RPC: current_rate = NAN else: current_rate = trade.close_rate + + buy_tag = trade.buy_tag current_profit = trade.calc_profit_ratio(current_rate) current_profit_abs = trade.calc_profit(current_rate) current_profit_fiat: Optional[float] = None @@ -191,6 +193,7 @@ class RPC: profit_pct=round(current_profit * 100, 2), profit_abs=current_profit_abs, profit_fiat=current_profit_fiat, + buy_tag=buy_tag, stoploss_current_dist=stoploss_current_dist, stoploss_current_dist_ratio=round(stoploss_current_dist_ratio, 8), @@ -696,19 +699,19 @@ class RPC: [x.update({'profit': round(x['profit'] * 100, 2)}) for x in buy_tags] return buy_tags - def _rpc_exit_tag_performance(self, pair: str) -> List[Dict[str, Any]]: + def _rpc_sell_reason_performance(self, pair: str) -> List[Dict[str, Any]]: """ - Handler for sell tag performance. + Handler for sell reason performance. Shows a performance statistic from finished trades """ - exit_tags = Trade.get_exit_tag_performance(pair) + sell_reasons = Trade.get_sell_reason_performance(pair) # Round and convert to % - [x.update({'profit': round(x['profit'] * 100, 2)}) for x in exit_tags] - return exit_tags + [x.update({'profit': round(x['profit'] * 100, 2)}) for x in sell_reasons] + return sell_reasons def _rpc_mix_tag_performance(self, pair: str) -> List[Dict[str, Any]]: """ - Handler for mix tag performance. + Handler for mix tag (buy_tag + exit_tag) performance. Shows a performance statistic from finished trades """ mix_tags = Trade.get_mix_tag_performance(pair) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 0a84b588a..2352d366a 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -156,7 +156,7 @@ class Telegram(RPCHandler): CommandHandler('delete', self._delete_trade), CommandHandler('performance', self._performance), CommandHandler('buys', self._buy_tag_performance), - CommandHandler('sells', self._exit_tag_performance), + CommandHandler('sells', self._sell_reason_performance), CommandHandler('mix_tags', self._mix_tag_performance), CommandHandler('stats', self._stats), CommandHandler('daily', self._daily), @@ -244,8 +244,8 @@ 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['buy_tag'] = msg['buy_tag'] if "buy_tag" in msg.keys() else None msg['emoji'] = self._get_sell_emoji(msg) # Check if all sell properties are available. @@ -261,7 +261,7 @@ class Telegram(RPCHandler): message = ("{emoji} *{exchange}:* Selling {pair} (#{trade_id})\n" "*Profit:* `{profit_percent:.2f}%{profit_extra}`\n" - "{tags}" + "*Buy Tag:* `{buy_tag}`\n" "*Sell Reason:* `{sell_reason}`\n" "*Duration:* `{duration} ({duration_min:.1f} min)`\n" "*Amount:* `{amount:.8f}`\n" @@ -357,18 +357,6 @@ 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: """ @@ -401,7 +389,6 @@ class Telegram(RPCHandler): "*Current Pair:* {pair}", "*Amount:* `{amount} ({stake_amount} {base_currency})`", "*Buy Tag:* `{buy_tag}`" if r['buy_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}`", @@ -925,7 +912,7 @@ class Telegram(RPCHandler): self._send_msg(str(e)) @authorized_only - def _exit_tag_performance(self, update: Update, context: CallbackContext) -> None: + def _sell_reason_performance(self, update: Update, context: CallbackContext) -> None: """ Handler for /sells. Shows a performance statistic from finished trades @@ -938,11 +925,11 @@ class Telegram(RPCHandler): if context.args: pair = context.args[0] - trades = self._rpc._rpc_exit_tag_performance(pair) - output = "<b>Sell Tag Performance:</b>\n" + trades = self._rpc._rpc_sell_reason_performance(pair) + output = "<b>Sell Reason Performance:</b>\n" for i, trade in enumerate(trades): stat_line = ( - f"{i+1}.\t <code>{trade['exit_tag']}\t" + f"{i+1}.\t <code>{trade['sell_reason']}\t" f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " f"({trade['profit']:.2f}%) " f"({trade['count']})</code>\n") @@ -954,7 +941,7 @@ class Telegram(RPCHandler): output += stat_line self._send_msg(output, parse_mode=ParseMode.HTML, - reload_able=True, callback_path="update_exit_tag_performance", + reload_able=True, callback_path="update_sell_reason_performance", query=update.callback_query) except RPCException as e: self._send_msg(str(e)) From 905f3a1a5083d0feb357429870486f8b4fda50d1 Mon Sep 17 00:00:00 2001 From: theluxaz <theluxaz@gmail.com> Date: Wed, 20 Oct 2021 17:58:50 +0300 Subject: [PATCH 15/26] Removed exit_tag from Trade objects. --- freqtrade/data/btanalysis.py | 2 +- freqtrade/freqtradebot.py | 1 - freqtrade/optimize/backtesting.py | 3 --- freqtrade/persistence/migrations.py | 7 +++---- freqtrade/persistence/models.py | 3 --- freqtrade/rpc/rpc.py | 2 +- freqtrade/rpc/telegram.py | 3 +-- 7 files changed, 6 insertions(+), 15 deletions(-) diff --git a/freqtrade/data/btanalysis.py b/freqtrade/data/btanalysis.py index 3dba635e6..7d97661c4 100644 --- a/freqtrade/data/btanalysis.py +++ b/freqtrade/data/btanalysis.py @@ -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', 'exit_tag'] + 'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', 'buy_tag'] def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str: diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5ecf5b2a3..b7449d884 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1151,7 +1151,6 @@ class FreqtradeBot(LoggingMixin): trade.sell_reason = sell_reason.sell_reason if(exit_tag is not None): trade.sell_reason = exit_tag - 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) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 827be4d76..5566127c3 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -363,7 +363,6 @@ class Backtesting: trade.sell_reason = sell.sell_reason if(sell_row[EXIT_TAG_IDX] is not None): trade.sell_reason = sell_row[EXIT_TAG_IDX] - trade.exit_tag = sell_row[EXIT_TAG_IDX] 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) @@ -437,7 +436,6 @@ 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_exit_tag = len(row) >= EXIT_TAG_IDX + 1 trade = LocalTrade( pair=pair, open_rate=row[OPEN_IDX], @@ -448,7 +446,6 @@ class Backtesting: fee_close=self.fee, is_open=True, buy_tag=row[BUY_TAG_IDX] if has_buy_tag else None, - exit_tag=row[EXIT_TAG_IDX] if has_exit_tag else None, exchange='backtesting', ) return trade diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index d0b3add3c..1839c4130 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -48,7 +48,6 @@ 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') - 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 +82,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, exit_tag, + max_rate, min_rate, sell_reason, sell_order_status, strategy, buy_tag, timeframe, open_trade_value, close_profit_abs ) select id, lower(exchange), pair, @@ -99,7 +98,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, {exit_tag} exit_tag, {timeframe} timeframe, + {strategy} strategy, {buy_tag} buy_tag, {timeframe} timeframe, {open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs from {table_back_name} """)) @@ -158,7 +157,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, 'exit_tag'): + if not has_column(cols, 'buy_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! diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index e03830d7f..ed0c2bf9d 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -258,7 +258,6 @@ class LocalTrade(): sell_order_status: str = '' strategy: str = '' buy_tag: Optional[str] = None - exit_tag: Optional[str] = None timeframe: Optional[int] = None def __init__(self, **kwargs): @@ -326,7 +325,6 @@ class LocalTrade(): 'profit_abs': self.close_profit_abs, '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 +706,6 @@ 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) - exit_tag = Column(String(100), nullable=True) timeframe = Column(Integer, nullable=True) def __init__(self, **kwargs): diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 2a664e7bc..310b0ad07 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -711,7 +711,7 @@ class RPC: def _rpc_mix_tag_performance(self, pair: str) -> List[Dict[str, Any]]: """ - Handler for mix tag (buy_tag + exit_tag) performance. + Handler for mix tag (buy_tag + sell_reason) performance. Shows a performance statistic from finished trades """ mix_tags = Trade.get_mix_tag_performance(pair) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 2352d366a..341eec5dd 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -180,7 +180,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_exit_tag_performance'), + CallbackQueryHandler(self._performance, pattern='update_sell_reason_performance'), CallbackQueryHandler(self._performance, pattern='update_mix_tag_performance'), CallbackQueryHandler(self._count, pattern='update_count'), CallbackQueryHandler(self._forcebuy_inline), @@ -963,7 +963,6 @@ class Telegram(RPCHandler): trades = self._rpc._rpc_mix_tag_performance(pair) 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'])} " From 1267374c8a2665dd74179f95eb52edabd66634a6 Mon Sep 17 00:00:00 2001 From: Matthias <xmatthias@outlook.com> Date: Wed, 20 Oct 2021 19:13:34 +0200 Subject: [PATCH 16/26] Small fixes to tests --- freqtrade/freqtradebot.py | 3 ++- freqtrade/persistence/models.py | 4 ++-- tests/conftest.py | 2 +- tests/optimize/__init__.py | 2 ++ tests/test_freqtradebot.py | 41 ++++++++++++++++++--------------- tests/test_persistence.py | 4 ++++ 6 files changed, 34 insertions(+), 22 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index b7449d884..99373ae74 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -865,7 +865,8 @@ class FreqtradeBot(LoggingMixin): if should_sell.sell_flag: logger.info( - f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}. Tag: {exit_tag if exit_tag is not None else "None"}') + f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}. ' + f'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 diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index ed0c2bf9d..9a1f04429 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -324,7 +324,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': (f' ({self.sell_reason})' if self.sell_reason else ''), + 'sell_reason': self.sell_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, @@ -970,7 +970,7 @@ class Trade(_DECL_BASE, LocalTrade): .order_by(desc('profit_sum_abs')) \ .all() - return_list = [] + return_list: List[Dict] = [] for id, buy_tag, sell_reason, profit, profit_abs, count in tag_perf: buy_tag = buy_tag if buy_tag is not None else "Other" sell_reason = sell_reason if sell_reason is not None else "Other" diff --git a/tests/conftest.py b/tests/conftest.py index b35a220df..698c464ed 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -186,7 +186,7 @@ def get_patched_worker(mocker, config) -> Worker: return Worker(args=None, config=config) -def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False, None)) -> None: +def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False, None, None)) -> None: """ :param mocker: mocker to patch IStrategy class :param value: which value IStrategy.get_signal() must return diff --git a/tests/optimize/__init__.py b/tests/optimize/__init__.py index 6ad2d300b..50e7162f4 100644 --- a/tests/optimize/__init__.py +++ b/tests/optimize/__init__.py @@ -54,4 +54,6 @@ def _build_backtest_dataframe(data): frame[column] = frame[column].astype('float64') if 'buy_tag' not in columns: frame['buy_tag'] = None + if 'exit_tag' not in columns: + frame['exit_tag'] = None return frame diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 838a158e0..e590f4f74 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -236,7 +236,7 @@ def test_edge_overrides_stoploss(limit_buy_order_usdt, fee, caplog, mocker, # stoploss shoud be hit assert freqtrade.handle_trade(trade) is not ignore_strat_sl if not ignore_strat_sl: - assert log_has('Executing Sell for NEO/BTC. Reason: stop_loss', caplog) + assert log_has_re(r'Executing Sell for NEO/BTC. Reason: stop_loss.*', caplog) assert trade.sell_reason == SellType.STOP_LOSS.value @@ -450,7 +450,7 @@ def test_create_trade_no_signal(default_conf_usdt, fee, mocker) -> None: ) default_conf_usdt['stake_amount'] = 10 freqtrade = FreqtradeBot(default_conf_usdt) - patch_get_signal(freqtrade, value=(False, False, None)) + patch_get_signal(freqtrade, value=(False, False, None, None)) Trade.query = MagicMock() Trade.query.filter = MagicMock() @@ -677,7 +677,7 @@ def test_process_informative_pairs_added(default_conf_usdt, ticker_usdt, mocker) inf_pairs = MagicMock(return_value=[("BTC/ETH", '1m'), ("ETH/USDT", "1h")]) mocker.patch( 'freqtrade.strategy.interface.IStrategy.get_signal', - return_value=(False, False, '') + return_value=(False, False, '', '') ) mocker.patch('time.sleep', return_value=None) @@ -1808,7 +1808,7 @@ def test_handle_trade(default_conf_usdt, limit_buy_order_usdt, limit_sell_order_ assert trade.is_open is True freqtrade.wallets.update() - patch_get_signal(freqtrade, value=(False, True, None)) + patch_get_signal(freqtrade, value=(False, True, None, None)) assert freqtrade.handle_trade(trade) is True assert trade.open_order_id == limit_sell_order_usdt['id'] @@ -1836,7 +1836,7 @@ def test_handle_overlapping_signals(default_conf_usdt, ticker_usdt, limit_buy_or ) freqtrade = FreqtradeBot(default_conf_usdt) - patch_get_signal(freqtrade, value=(True, True, None)) + patch_get_signal(freqtrade, value=(True, True, None, None)) freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) freqtrade.enter_positions() @@ -1855,7 +1855,7 @@ def test_handle_overlapping_signals(default_conf_usdt, ticker_usdt, limit_buy_or assert trades[0].is_open is True # Buy and Sell are not triggering, so doing nothing ... - patch_get_signal(freqtrade, value=(False, False, None)) + patch_get_signal(freqtrade, value=(False, False, None, None)) assert freqtrade.handle_trade(trades[0]) is False trades = Trade.query.all() nb_trades = len(trades) @@ -1863,7 +1863,7 @@ def test_handle_overlapping_signals(default_conf_usdt, ticker_usdt, limit_buy_or assert trades[0].is_open is True # Buy and Sell are triggering, so doing nothing ... - patch_get_signal(freqtrade, value=(True, True, None)) + patch_get_signal(freqtrade, value=(True, True, None, None)) assert freqtrade.handle_trade(trades[0]) is False trades = Trade.query.all() nb_trades = len(trades) @@ -1871,7 +1871,7 @@ def test_handle_overlapping_signals(default_conf_usdt, ticker_usdt, limit_buy_or assert trades[0].is_open is True # Sell is triggering, guess what : we are Selling! - patch_get_signal(freqtrade, value=(False, True, None)) + patch_get_signal(freqtrade, value=(False, True, None, None)) trades = Trade.query.all() assert freqtrade.handle_trade(trades[0]) is True @@ -1905,7 +1905,7 @@ def test_handle_trade_roi(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_o # we might just want to check if we are in a sell condition without # executing # if ROI is reached we must sell - patch_get_signal(freqtrade, value=(False, True, None)) + patch_get_signal(freqtrade, value=(False, True, None, None)) assert freqtrade.handle_trade(trade) assert log_has("ETH/USDT - Required profit reached. sell_type=SellType.ROI", caplog) @@ -1934,10 +1934,10 @@ def test_handle_trade_use_sell_signal(default_conf_usdt, ticker_usdt, limit_buy_ trade = Trade.query.first() trade.is_open = True - patch_get_signal(freqtrade, value=(False, False, None)) + patch_get_signal(freqtrade, value=(False, False, None, None)) assert not freqtrade.handle_trade(trade) - patch_get_signal(freqtrade, value=(False, True, None)) + patch_get_signal(freqtrade, value=(False, True, None, None)) assert freqtrade.handle_trade(trade) assert log_has("ETH/USDT - Sell signal received. sell_type=SellType.SELL_SIGNAL", caplog) @@ -2579,6 +2579,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_ 'limit': 2.2, 'amount': 30.0, 'order_type': 'limit', + 'buy_tag': None, 'open_rate': 2.0, 'current_rate': 2.3, 'profit_amount': 5.685, @@ -2632,6 +2633,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd 'limit': 2.01, 'amount': 30.0, 'order_type': 'limit', + 'buy_tag': None, 'open_rate': 2.0, 'current_rate': 2.0, 'profit_amount': -0.00075, @@ -2699,6 +2701,7 @@ def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fe 'limit': 2.25, 'amount': 30.0, 'order_type': 'limit', + 'buy_tag': None, 'open_rate': 2.0, 'current_rate': 2.3, 'profit_amount': 7.18125, @@ -2758,6 +2761,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run( 'limit': 1.98, 'amount': 30.0, 'order_type': 'limit', + 'buy_tag': None, 'open_rate': 2.0, 'current_rate': 2.0, 'profit_amount': -0.8985, @@ -2975,6 +2979,7 @@ def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee, 'limit': 2.2, 'amount': 30.0, 'order_type': 'market', + 'buy_tag': None, 'open_rate': 2.0, 'current_rate': 2.3, 'profit_amount': 5.685, @@ -3068,7 +3073,7 @@ def test_sell_profit_only( trade = Trade.query.first() trade.update(limit_buy_order_usdt) freqtrade.wallets.update() - patch_get_signal(freqtrade, value=(False, True, None)) + patch_get_signal(freqtrade, value=(False, True, None, None)) assert freqtrade.handle_trade(trade) is handle_first if handle_second: @@ -3103,7 +3108,7 @@ def test_sell_not_enough_balance(default_conf_usdt, limit_buy_order_usdt, limit_ trade = Trade.query.first() amnt = trade.amount trade.update(limit_buy_order_usdt) - patch_get_signal(freqtrade, value=(False, True, None)) + patch_get_signal(freqtrade, value=(False, True, None, None)) mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(return_value=trade.amount * 0.985)) assert freqtrade.handle_trade(trade) is True @@ -3212,11 +3217,11 @@ def test_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usdt, trade = Trade.query.first() trade.update(limit_buy_order_usdt) freqtrade.wallets.update() - patch_get_signal(freqtrade, value=(True, True, None)) + patch_get_signal(freqtrade, value=(True, True, None, None)) assert freqtrade.handle_trade(trade) is False # Test if buy-signal is absent (should sell due to roi = true) - patch_get_signal(freqtrade, value=(False, True, None)) + patch_get_signal(freqtrade, value=(False, True, None, None)) assert freqtrade.handle_trade(trade) is True assert trade.sell_reason == SellType.ROI.value @@ -3402,11 +3407,11 @@ def test_disable_ignore_roi_if_buy_signal(default_conf_usdt, limit_buy_order_usd trade = Trade.query.first() trade.update(limit_buy_order_usdt) # Sell due to min_roi_reached - patch_get_signal(freqtrade, value=(True, True, None)) + patch_get_signal(freqtrade, value=(True, True, None, None)) assert freqtrade.handle_trade(trade) is True # Test if buy-signal is absent - patch_get_signal(freqtrade, value=(False, True, None)) + patch_get_signal(freqtrade, value=(False, True, None, None)) assert freqtrade.handle_trade(trade) is True assert trade.sell_reason == SellType.ROI.value @@ -3848,7 +3853,7 @@ def test_order_book_ask_strategy( freqtrade.wallets.update() assert trade.is_open is True - patch_get_signal(freqtrade, value=(False, True, None)) + patch_get_signal(freqtrade, value=(False, True, None, None)) assert freqtrade.handle_trade(trade) is True assert trade.close_rate_requested == order_book_l2.return_value['asks'][0][0] diff --git a/tests/test_persistence.py b/tests/test_persistence.py index d036b045e..719dc8263 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -1317,6 +1317,10 @@ def test_Trade_object_idem(): 'get_open_trades_without_assigned_fees', 'get_open_order_trades', 'get_trades', + 'get_sell_reason_performance', + 'get_buy_tag_performance', + 'get_mix_tag_performance', + ) # Parent (LocalTrade) should have the same attributes From 0e085298e9576fa2d88c47c2a70a9124dcc4dcfb Mon Sep 17 00:00:00 2001 From: theluxaz <theluxaz@gmail.com> Date: Thu, 21 Oct 2021 17:25:38 +0300 Subject: [PATCH 17/26] Fixed test failures. --- freqtrade/optimize/backtesting.py | 6 +++- freqtrade/optimize/optimize_reports.py | 38 ++++++++++++++------------ freqtrade/rpc/telegram.py | 11 ++++---- tests/optimize/test_backtesting.py | 18 ++++++------ tests/rpc/test_rpc_telegram.py | 20 +++++++++++--- tests/strategy/test_interface.py | 34 ++++++++++++++++------- 6 files changed, 81 insertions(+), 46 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 5566127c3..d23b6bdc3 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -361,8 +361,12 @@ class Backtesting: if sell.sell_flag: trade.close_date = sell_candle_time trade.sell_reason = sell.sell_reason - if(sell_row[EXIT_TAG_IDX] is not None): + + # Checks and adds an exit tag, after checking that the length of the + # sell_row has the length for an exit tag column + if(len(sell_row) > EXIT_TAG_IDX 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_dur = int((trade.close_date_utc - trade.open_date_utc).total_seconds() // 60) closerate = self._get_close_rate(sell_row, trade, sell, trade_dur) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 67dacd7c6..6e0926660 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -150,19 +150,22 @@ def generate_tag_metrics(tag_type: str, 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(): - continue + if tag_type in results.columns: + 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(): + continue - tabular_data.append(_generate_tag_result_line(result, starting_balance, tag)) + tabular_data.append(_generate_tag_result_line(result, starting_balance, tag)) - # Sort by total profit %: - tabular_data = sorted(tabular_data, key=lambda k: k['profit_total_abs'], reverse=True) + # Sort by total profit %: + tabular_data = sorted(tabular_data, key=lambda k: k['profit_total_abs'], reverse=True) - # Append Total - tabular_data.append(_generate_result_line(results, starting_balance, 'TOTAL')) - return tabular_data + # Append Total + tabular_data.append(_generate_result_line(results, starting_balance, 'TOTAL')) + return tabular_data + else: + return None def _generate_tag_result_line(result: DataFrame, starting_balance: int, first_column: str) -> Dict: @@ -732,14 +735,15 @@ 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) + if(results['results_per_buy_tag'] is not None): + 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) + if isinstance(table, str) and len(table) > 0: + print(' BUY TAG STATS '.center(len(table.splitlines()[0]), '=')) + print(table) table = text_table_sell_reason(sell_reason_stats=results['sell_reason_summary'], stake_currency=stake_currency) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 341eec5dd..96124ff45 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -107,10 +107,9 @@ class Telegram(RPCHandler): # this needs refactoring of the whole telegram module (same # problem in _help()). valid_keys: List[str] = [r'/start$', r'/stop$', r'/status$', r'/status table$', - r'/trades$', r'/performance$', r'/daily$', r'/daily \d+$', - r'/profit$', r'/profit \d+', + r'/trades$', r'/performance$', r'/buys', r'/sells', r'/mix_tags', + 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'/stopbuy$', r'/reload_config$', r'/show_config$', r'/logs$', r'/whitelist$', r'/blacklist$', r'/edge$', r'/forcebuy$', r'/help$', r'/version$'] @@ -179,9 +178,9 @@ class Telegram(RPCHandler): CallbackQueryHandler(self._profit, pattern='update_profit'), 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_reason_performance'), - CallbackQueryHandler(self._performance, pattern='update_mix_tag_performance'), + CallbackQueryHandler(self._buy_tag_performance, pattern='update_buy_tag_performance'), + CallbackQueryHandler(self._sell_reason_performance, pattern='update_sell_reason_performance'), + CallbackQueryHandler(self._mix_tag_performance, pattern='update_mix_tag_performance'), CallbackQueryHandler(self._count, pattern='update_count'), CallbackQueryHandler(self._forcebuy_inline), ] diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 2248cd4c1..9d3ca01a9 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -567,6 +567,7 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None: 195, # Low 201.5, # High '', # Buy Signal Name + '', # Exit Signal Name ] trade = backtesting._enter_trade(pair, row=row) @@ -581,26 +582,27 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None: 195, # Low 210.5, # High '', # Buy Signal Name + '', # Exit Signal Name ] row_detail = pd.DataFrame( [ [ pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=0, tzinfo=timezone.utc), - 1, 200, 199, 0, 197, 200.1, '', + 1, 200, 199, 0, 197, 200.1, '', '', ], [ pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=1, tzinfo=timezone.utc), - 0, 199, 199.5, 0, 199, 199.7, '', + 0, 199, 199.5, 0, 199, 199.7, '', '', ], [ pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=2, tzinfo=timezone.utc), - 0, 199.5, 200.5, 0, 199, 200.8, '', + 0, 199.5, 200.5, 0, 199, 200.8, '', '', ], [ pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=3, tzinfo=timezone.utc), - 0, 200.5, 210.5, 0, 193, 210.5, '', # ROI sell (?) + 0, 200.5, 210.5, 0, 193, 210.5, '', '', # ROI sell (?) ], [ pd.Timestamp(year=2020, month=1, day=1, hour=5, minute=4, tzinfo=timezone.utc), - 0, 200, 199, 0, 193, 200.1, '', + 0, 200, 199, 0, 193, 200.1, '', '', ], - ], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag"] + ], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag", "exit_tag"] ) # No data available. @@ -614,7 +616,7 @@ def test_backtest__get_sell_trade_entry(default_conf, fee, mocker) -> None: assert isinstance(trade, LocalTrade) # Assign empty ... no result. backtesting.detail_data[pair] = pd.DataFrame( - [], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag"]) + [], columns=["date", "buy", "open", "close", "sell", "low", "high", "buy_tag", "exit_tag"]) res = backtesting._get_sell_trade_entry(trade, row) assert res is None @@ -678,7 +680,7 @@ def test_backtest_one(default_conf, fee, mocker, testdatadir) -> None: 'min_rate': [0.10370188, 0.10300000000000001], 'max_rate': [0.10501, 0.1038888], 'is_open': [False, False], - 'buy_tag': [None, None], + 'buy_tag': [None, None] }) pd.testing.assert_frame_equal(results, expected) data_pair = processed[pair] diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 7dde7b803..01d6d92cf 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -33,6 +33,7 @@ class DummyCls(Telegram): """ Dummy class for testing the Telegram @authorized_only decorator """ + def __init__(self, rpc: RPC, config) -> None: super().__init__(rpc, config) self.state = {'called': False} @@ -92,7 +93,8 @@ def test_telegram_init(default_conf, mocker, caplog) -> None: message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], " "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], " - "['delete'], ['performance'], ['stats'], ['daily'], ['count'], ['locks'], " + "['delete'], ['performance'], ['buys'], ['sells'], ['mix_tags'], " + "['stats'], ['daily'], ['count'], ['locks'], " "['unlock', 'delete_locks'], ['reload_config', 'reload_conf'], " "['show_config', 'show_conf'], ['stopbuy'], " "['whitelist'], ['blacklist'], ['logs'], ['edge'], ['help'], ['version']" @@ -713,6 +715,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee, 'profit_ratio': 0.0629778, 'stake_currency': 'BTC', 'fiat_currency': 'USD', + 'buy_tag': ANY, 'sell_reason': SellType.FORCE_SELL.value, 'open_date': ANY, 'close_date': ANY, @@ -776,6 +779,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee, 'profit_ratio': -0.05482878, 'stake_currency': 'BTC', 'fiat_currency': 'USD', + 'buy_tag': ANY, 'sell_reason': SellType.FORCE_SELL.value, 'open_date': ANY, 'close_date': ANY, @@ -829,6 +833,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None 'profit_ratio': -0.00408133, 'stake_currency': 'BTC', 'fiat_currency': 'USD', + 'buy_tag': ANY, 'sell_reason': SellType.FORCE_SELL.value, 'open_date': ANY, 'close_date': ANY, @@ -997,9 +1002,9 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: msg = ('<pre> current max total stake\n--------- ----- -------------\n' ' 1 {} {}</pre>').format( - default_conf['max_open_trades'], - default_conf['stake_amount'] - ) + default_conf['max_open_trades'], + default_conf['stake_amount'] + ) assert msg in msg_mock.call_args_list[0][0][0] @@ -1382,6 +1387,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: 'profit_ratio': -0.57405275, 'stake_currency': 'ETH', 'fiat_currency': 'USD', + 'buy_tag': 'buy_signal1', 'sell_reason': SellType.STOP_LOSS.value, 'open_date': arrow.utcnow().shift(hours=-1), 'close_date': arrow.utcnow(), @@ -1389,6 +1395,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: assert msg_mock.call_args[0][0] \ == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n' '*Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n' + '*Buy Tag:* `buy_signal1`\n' '*Sell Reason:* `stop_loss`\n' '*Duration:* `1:00:00 (60.0 min)`\n' '*Amount:* `1333.33333333`\n' @@ -1412,6 +1419,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: 'profit_amount': -0.05746268, 'profit_ratio': -0.57405275, 'stake_currency': 'ETH', + 'buy_tag': 'buy_signal1', 'sell_reason': SellType.STOP_LOSS.value, 'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30), 'close_date': arrow.utcnow(), @@ -1419,6 +1427,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None: assert msg_mock.call_args[0][0] \ == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n' '*Profit:* `-57.41%`\n' + '*Buy Tag:* `buy_signal1`\n' '*Sell Reason:* `stop_loss`\n' '*Duration:* `1 day, 2:30:00 (1590.0 min)`\n' '*Amount:* `1333.33333333`\n' @@ -1483,6 +1492,7 @@ def test_send_msg_sell_fill_notification(default_conf, mocker) -> None: 'profit_ratio': -0.57405275, 'stake_currency': 'ETH', 'fiat_currency': 'USD', + 'buy_tag': 'buy_signal1', 'sell_reason': SellType.STOP_LOSS.value, 'open_date': arrow.utcnow().shift(hours=-1), 'close_date': arrow.utcnow(), @@ -1574,12 +1584,14 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: 'profit_ratio': -0.57405275, 'stake_currency': 'ETH', 'fiat_currency': 'USD', + 'buy_tag': 'buy_signal1', 'sell_reason': SellType.STOP_LOSS.value, 'open_date': arrow.utcnow().shift(hours=-2, minutes=-35, seconds=-3), 'close_date': arrow.utcnow(), }) assert msg_mock.call_args[0][0] == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n' '*Profit:* `-57.41%`\n' + '*Buy Tag:* `buy_signal1`\n' '*Sell Reason:* `stop_loss`\n' '*Duration:* `2:35:03 (155.1 min)`\n' '*Amount:* `1333.33333333`\n' diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index dcb9e3e64..62510b370 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -38,20 +38,27 @@ def test_returns_latest_signal(mocker, default_conf, ohlcv_history): mocked_history['buy'] = 0 mocked_history.loc[1, 'sell'] = 1 - assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True, None) + assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, True, None, None) mocked_history.loc[1, 'sell'] = 0 mocked_history.loc[1, 'buy'] = 1 - assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, None) + assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, None, None) mocked_history.loc[1, 'sell'] = 0 mocked_history.loc[1, 'buy'] = 0 - assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False, None) + assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (False, False, None, None) mocked_history.loc[1, 'sell'] = 0 mocked_history.loc[1, 'buy'] = 1 mocked_history.loc[1, 'buy_tag'] = 'buy_signal_01' - assert _STRATEGY.get_signal('ETH/BTC', '5m', mocked_history) == (True, False, 'buy_signal_01') + assert _STRATEGY.get_signal( + 'ETH/BTC', + '5m', + mocked_history) == ( + True, + False, + 'buy_signal_01', + None) def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history): @@ -68,17 +75,24 @@ def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history): def test_get_signal_empty(default_conf, mocker, caplog): - assert (False, False, None) == _STRATEGY.get_signal( + assert (False, False, None, None) == _STRATEGY.get_signal( 'foo', default_conf['timeframe'], DataFrame() ) assert log_has('Empty candle (OHLCV) data for pair foo', caplog) caplog.clear() - assert (False, False, None) == _STRATEGY.get_signal('bar', default_conf['timeframe'], None) + assert ( + False, + False, + None, + None) == _STRATEGY.get_signal( + 'bar', + default_conf['timeframe'], + None) assert log_has('Empty candle (OHLCV) data for pair bar', caplog) caplog.clear() - assert (False, False, None) == _STRATEGY.get_signal( + assert (False, False, None, None) == _STRATEGY.get_signal( 'baz', default_conf['timeframe'], DataFrame([]) @@ -118,7 +132,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog, ohlcv_history): caplog.set_level(logging.INFO) mocker.patch.object(_STRATEGY, 'assert_df') - assert (False, False, None) == _STRATEGY.get_signal( + assert (False, False, None, None) == _STRATEGY.get_signal( 'xyz', default_conf['timeframe'], mocked_history @@ -140,7 +154,7 @@ def test_get_signal_no_sell_column(default_conf, mocker, caplog, ohlcv_history): caplog.set_level(logging.INFO) mocker.patch.object(_STRATEGY, 'assert_df') - assert (True, False, None) == _STRATEGY.get_signal( + assert (True, False, None, None) == _STRATEGY.get_signal( 'xyz', default_conf['timeframe'], mocked_history @@ -646,7 +660,7 @@ def test_strategy_safe_wrapper(value): ret = strategy_safe_wrapper(working_method, message='DeadBeef')(value) - assert type(ret) == type(value) + assert isinstance(ret, type(value)) assert ret == value From 17432b2823fdb6fd54b9fd7fde2e88a146c41ce5 Mon Sep 17 00:00:00 2001 From: Matthias <xmatthias@outlook.com> Date: Sun, 24 Oct 2021 09:15:05 +0200 Subject: [PATCH 18/26] Improve some stylings --- README.md | 2 +- freqtrade/freqtradebot.py | 4 +--- freqtrade/optimize/backtesting.py | 6 +++++- freqtrade/rpc/telegram.py | 3 ++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 906e19ef7..0a4d6424e 100644 --- a/README.md +++ b/README.md @@ -201,4 +201,4 @@ To run this bot we recommend you a cloud instance with a minimum of: - [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) - [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html) - [virtualenv](https://virtualenv.pypa.io/en/stable/installation.html) (Recommended) -- [Docker](https://www.docker.com/products/docker) (Recommended) \ No newline at end of file +- [Docker](https://www.docker.com/products/docker) (Recommended) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 99373ae74..fb42a8924 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1149,9 +1149,7 @@ class FreqtradeBot(LoggingMixin): trade.open_order_id = order['id'] trade.sell_order_status = '' trade.close_rate_requested = limit - trade.sell_reason = sell_reason.sell_reason - if(exit_tag is not None): - trade.sell_reason = exit_tag + trade.sell_reason = exit_tag or sell_reason.sell_reason # 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) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index d23b6bdc3..6fea716a0 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -364,7 +364,11 @@ class Backtesting: # Checks and adds an exit tag, after checking that the length of the # sell_row has the length for an exit tag column - if(len(sell_row) > EXIT_TAG_IDX and sell_row[EXIT_TAG_IDX] is not None and len(sell_row[EXIT_TAG_IDX]) > 0): + if( + len(sell_row) > EXIT_TAG_IDX + 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_dur = int((trade.close_date_utc - trade.open_date_utc).total_seconds() // 60) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 96124ff45..f79f8d457 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -179,7 +179,8 @@ class Telegram(RPCHandler): CallbackQueryHandler(self._balance, pattern='update_balance'), CallbackQueryHandler(self._performance, pattern='update_performance'), CallbackQueryHandler(self._buy_tag_performance, pattern='update_buy_tag_performance'), - CallbackQueryHandler(self._sell_reason_performance, pattern='update_sell_reason_performance'), + CallbackQueryHandler(self._sell_reason_performance, + pattern='update_sell_reason_performance'), CallbackQueryHandler(self._mix_tag_performance, pattern='update_mix_tag_performance'), CallbackQueryHandler(self._count, pattern='update_count'), CallbackQueryHandler(self._forcebuy_inline), From 22dd2ca003726868ba45158a9e9bee33dee5aeb8 Mon Sep 17 00:00:00 2001 From: Matthias <xmatthias@outlook.com> Date: Sun, 24 Oct 2021 15:18:29 +0200 Subject: [PATCH 19/26] Fix mypy type errors --- freqtrade/optimize/optimize_reports.py | 2 +- freqtrade/persistence/models.py | 6 +++--- freqtrade/rpc/rpc.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 7fb6a14a0..4e51e80c2 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -165,7 +165,7 @@ def generate_tag_metrics(tag_type: str, tabular_data.append(_generate_result_line(results, starting_balance, 'TOTAL')) return tabular_data else: - return None + return [] def _generate_tag_result_line(result: DataFrame, starting_balance: int, first_column: str) -> Dict: diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 9a1f04429..a3c6656af 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -861,7 +861,7 @@ class Trade(_DECL_BASE, LocalTrade): ] @staticmethod - def get_buy_tag_performance(pair: str) -> List[Dict[str, Any]]: + def get_buy_tag_performance(pair: Optional[str]) -> List[Dict[str, Any]]: """ Returns List of dicts containing all Trades, based on buy tag performance Can either be average for all pairs or a specific pair provided @@ -900,7 +900,7 @@ class Trade(_DECL_BASE, LocalTrade): ] @staticmethod - def get_sell_reason_performance(pair: str) -> List[Dict[str, Any]]: + def get_sell_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 @@ -938,7 +938,7 @@ class Trade(_DECL_BASE, LocalTrade): ] @staticmethod - def get_mix_tag_performance(pair: str) -> List[Dict[str, Any]]: + def get_mix_tag_performance(pair: Optional[str]) -> List[Dict[str, Any]]: """ Returns List of dicts containing all Trades, based on buy_tag + sell_reason performance Can either be average for all pairs or a specific pair provided diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 310b0ad07..4ef9213eb 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -689,7 +689,7 @@ class RPC: [x.update({'profit': round(x['profit'] * 100, 2)}) for x in pair_rates] return pair_rates - def _rpc_buy_tag_performance(self, pair: str) -> List[Dict[str, Any]]: + def _rpc_buy_tag_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]: """ Handler for buy tag performance. Shows a performance statistic from finished trades @@ -699,7 +699,7 @@ class RPC: [x.update({'profit': round(x['profit'] * 100, 2)}) for x in buy_tags] return buy_tags - def _rpc_sell_reason_performance(self, pair: str) -> List[Dict[str, Any]]: + def _rpc_sell_reason_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]: """ Handler for sell reason performance. Shows a performance statistic from finished trades @@ -709,7 +709,7 @@ class RPC: [x.update({'profit': round(x['profit'] * 100, 2)}) for x in sell_reasons] return sell_reasons - def _rpc_mix_tag_performance(self, pair: str) -> List[Dict[str, Any]]: + def _rpc_mix_tag_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]: """ Handler for mix tag (buy_tag + sell_reason) performance. Shows a performance statistic from finished trades From b51f946ee07d579a09f0b0368412454fc6e92ef7 Mon Sep 17 00:00:00 2001 From: theluxaz <theluxaz@gmail.com> Date: Mon, 25 Oct 2021 23:43:22 +0300 Subject: [PATCH 20/26] Fixed models and rpc performance functions, added skeletons for tests. --- freqtrade/persistence/models.py | 124 ++++++++++++++------------------ freqtrade/rpc/rpc.py | 12 ++-- tests/rpc/test_rpc.py | 114 +++++++++++++++++++++++++++++ tests/rpc/test_rpc_telegram.py | 99 +++++++++++++++++++++++++ 4 files changed, 272 insertions(+), 77 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index a3c6656af..8ccf8bbef 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -850,7 +850,8 @@ class Trade(_DECL_BASE, LocalTrade): .group_by(Trade.pair) \ .order_by(desc('profit_sum_abs')) \ .all() - return [ + + response = [ { 'pair': pair, 'profit': profit, @@ -859,6 +860,8 @@ class Trade(_DECL_BASE, LocalTrade): } for pair, profit, profit_abs, count in pair_rates ] + [x.update({'profit': round(x['profit'] * 100, 2)}) for x in response] + return response @staticmethod def get_buy_tag_performance(pair: Optional[str]) -> List[Dict[str, Any]]: @@ -868,36 +871,31 @@ class Trade(_DECL_BASE, LocalTrade): NOTE: Not supported in Backtesting. """ + filters = [Trade.is_open.is_(False)] 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 == 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.buy_tag) \ - .order_by(desc('profit_sum_abs')) \ - .all() + filters.append(Trade.pair == pair) - return [ + buy_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(*filters)\ + .group_by(Trade.buy_tag) \ + .order_by(desc('profit_sum_abs')) \ + .all() + + response = [ { 'buy_tag': buy_tag if buy_tag is not None else "Other", 'profit': profit, 'profit_abs': profit_abs, 'count': count } - for buy_tag, profit, profit_abs, count in tag_perf + for buy_tag, profit, profit_abs, count in buy_tag_perf ] + [x.update({'profit': round(x['profit'] * 100, 2)}) for x in response] + return response @staticmethod def get_sell_reason_performance(pair: Optional[str]) -> List[Dict[str, Any]]: @@ -906,36 +904,32 @@ class Trade(_DECL_BASE, LocalTrade): 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_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(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_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(Trade.is_open.is_(False))\ - .group_by(Trade.sell_reason) \ - .order_by(desc('profit_sum_abs')) \ - .all() - return [ + filters = [Trade.is_open.is_(False)] + if(pair is not None): + filters.append(Trade.pair == pair) + + sell_tag_perf = Trade.query.with_entities( + Trade.sell_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) \ + .order_by(desc('profit_sum_abs')) \ + .all() + + response = [ { 'sell_reason': sell_reason if sell_reason is not None else "Other", 'profit': profit, 'profit_abs': profit_abs, 'count': count } - for sell_reason, profit, profit_abs, count in tag_perf + for sell_reason, profit, profit_abs, count in sell_tag_perf ] + [x.update({'profit': round(x['profit'] * 100, 2)}) for x in response] + return response @staticmethod def get_mix_tag_performance(pair: Optional[str]) -> List[Dict[str, Any]]: @@ -944,34 +938,25 @@ class Trade(_DECL_BASE, LocalTrade): 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.id, - Trade.buy_tag, - Trade.sell_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(Trade.is_open.is_(False))\ - .filter(Trade.pair == pair) \ - .order_by(desc('profit_sum_abs')) \ - .all() - else: - tag_perf = Trade.query.with_entities( - Trade.id, - Trade.buy_tag, - Trade.sell_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(Trade.is_open.is_(False))\ - .group_by(Trade.id) \ - .order_by(desc('profit_sum_abs')) \ - .all() + filters = [Trade.is_open.is_(False)] + if(pair is not None): + filters.append(Trade.pair == pair) + + mix_tag_perf = Trade.query.with_entities( + Trade.id, + Trade.buy_tag, + Trade.sell_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.id) \ + .order_by(desc('profit_sum_abs')) \ + .all() return_list: List[Dict] = [] - for id, buy_tag, sell_reason, profit, profit_abs, count in tag_perf: + for id, buy_tag, sell_reason, profit, profit_abs, count in mix_tag_perf: buy_tag = buy_tag if buy_tag is not None else "Other" sell_reason = sell_reason if sell_reason is not None else "Other" @@ -993,6 +978,7 @@ class Trade(_DECL_BASE, LocalTrade): 'count': 1 + return_list[i]["count"]} i += 1 + [x.update({'profit': round(x['profit'] * 100, 2)}) for x in return_list] return return_list @staticmethod diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 4ef9213eb..42d502cd8 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -685,8 +685,7 @@ class RPC: Shows a performance statistic from finished trades """ pair_rates = Trade.get_overall_performance() - # Round and convert to % - [x.update({'profit': round(x['profit'] * 100, 2)}) for x in pair_rates] + return pair_rates def _rpc_buy_tag_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]: @@ -695,8 +694,7 @@ class RPC: Shows a performance statistic from finished trades """ buy_tags = Trade.get_buy_tag_performance(pair) - # Round and convert to % - [x.update({'profit': round(x['profit'] * 100, 2)}) for x in buy_tags] + return buy_tags def _rpc_sell_reason_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]: @@ -705,8 +703,7 @@ class RPC: Shows a performance statistic from finished trades """ sell_reasons = Trade.get_sell_reason_performance(pair) - # Round and convert to % - [x.update({'profit': round(x['profit'] * 100, 2)}) for x in sell_reasons] + return sell_reasons def _rpc_mix_tag_performance(self, pair: Optional[str]) -> List[Dict[str, Any]]: @@ -715,8 +712,7 @@ class RPC: Shows a performance statistic from finished trades """ mix_tags = Trade.get_mix_tag_performance(pair) - # Round and convert to % - [x.update({'profit': round(x['profit'] * 100, 2)}) for x in mix_tags] + return mix_tags def _rpc_count(self) -> Dict[str, float]: diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index f8c923958..78805a456 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -827,6 +827,120 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, assert res[0]['count'] == 1 assert prec_satoshi(res[0]['profit'], 6.2) + # TEST FOR TRADES WITH NO BUY TAG + # TEST TRADE WITH ONE BUY_TAG AND OTHER TWO TRADES WITH THE SAME TAG + # TEST THE SAME FOR A PAIR + + +def test_buy_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, + limit_sell_order, mocker) -> None: + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_balances=MagicMock(return_value=ticker), + fetch_ticker=ticker, + get_fee=fee, + ) + + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + patch_get_signal(freqtradebot) + rpc = RPC(freqtradebot) + + # Create some test data + freqtradebot.enter_positions() + trade = Trade.query.first() + assert trade + + # Simulate fulfilled LIMIT_BUY order for trade + trade.update(limit_buy_order) + + # Simulate fulfilled LIMIT_SELL order for trade + trade.update(limit_sell_order) + + trade.close_date = datetime.utcnow() + trade.is_open = False + res = rpc._rpc_buy_tag_performance(None) + assert len(res) == 1 + assert res[0]['pair'] == 'ETH/BTC' + assert res[0]['count'] == 1 + assert prec_satoshi(res[0]['profit'], 6.2) + + # TEST FOR TRADES WITH NO SELL REASON + # TEST TRADE WITH ONE SELL REASON AND OTHER TWO TRADES WITH THE SAME reason + # TEST THE SAME FOR A PAIR + + +def test_sell_reason_performance_handle(default_conf, ticker, limit_buy_order, fee, + limit_sell_order, mocker) -> None: + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_balances=MagicMock(return_value=ticker), + fetch_ticker=ticker, + get_fee=fee, + ) + + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + patch_get_signal(freqtradebot) + rpc = RPC(freqtradebot) + + # Create some test data + freqtradebot.enter_positions() + trade = Trade.query.first() + assert trade + + # Simulate fulfilled LIMIT_BUY order for trade + trade.update(limit_buy_order) + + # Simulate fulfilled LIMIT_SELL order for trade + trade.update(limit_sell_order) + + trade.close_date = datetime.utcnow() + trade.is_open = False + res = rpc._rpc_sell_reason_performance(None) + assert len(res) == 1 + assert res[0]['pair'] == 'ETH/BTC' + assert res[0]['count'] == 1 + assert prec_satoshi(res[0]['profit'], 6.2) + + # TEST FOR TRADES WITH NO TAGS + # TEST TRADE WITH ONE TAG MIX AND OTHER TWO TRADES WITH THE SAME TAG MIX + # TEST THE SAME FOR A PAIR + + +def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, + limit_sell_order, mocker) -> None: + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_balances=MagicMock(return_value=ticker), + fetch_ticker=ticker, + get_fee=fee, + ) + + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + patch_get_signal(freqtradebot) + rpc = RPC(freqtradebot) + + # Create some test data + freqtradebot.enter_positions() + trade = Trade.query.first() + assert trade + + # Simulate fulfilled LIMIT_BUY order for trade + trade.update(limit_buy_order) + + # Simulate fulfilled LIMIT_SELL order for trade + trade.update(limit_sell_order) + + trade.close_date = datetime.utcnow() + trade.is_open = False + res = rpc._rpc_mix_tag_performance(None) + assert len(res) == 1 + assert res[0]['pair'] == 'ETH/BTC' + assert res[0]['count'] == 1 + assert prec_satoshi(res[0]['profit'], 6.2) + def test_rpc_count(mocker, default_conf, ticker, fee) -> None: mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 01d6d92cf..306181eae 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -978,6 +978,105 @@ def test_performance_handle(default_conf, update, ticker, fee, assert 'Performance' in msg_mock.call_args_list[0][0][0] assert '<code>ETH/BTC\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0] + # TEST FOR TRADES WITH NO BUY TAG + # TEST TRADE WITH ONE BUY_TAG AND OTHER TWO TRADES WITH THE SAME TAG + # TEST THE SAME FOR A PAIR + + +def test_buy_tag_performance_handle(default_conf, update, ticker, fee, + limit_buy_order, limit_sell_order, mocker) -> None: + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + fetch_ticker=ticker, + get_fee=fee, + ) + telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) + patch_get_signal(freqtradebot) + + # Create some test data + freqtradebot.enter_positions() + trade = Trade.query.first() + assert trade + + # Simulate fulfilled LIMIT_BUY order for trade + trade.update(limit_buy_order) + + # Simulate fulfilled LIMIT_SELL order for trade + trade.update(limit_sell_order) + + trade.close_date = datetime.utcnow() + trade.is_open = False + telegram._buy_tag_performance(update=update, context=MagicMock()) + assert msg_mock.call_count == 1 + assert 'Performance' in msg_mock.call_args_list[0][0][0] + assert '<code>ETH/BTC\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0] + + # TEST FOR TRADES WITH NO SELL REASON + # TEST TRADE WITH ONE SELL REASON AND OTHER TWO TRADES WITH THE SAME reason + # TEST THE SAME FOR A PAIR + + +def test_sell_reason_performance_handle(default_conf, update, ticker, fee, + limit_buy_order, limit_sell_order, mocker) -> None: + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + fetch_ticker=ticker, + get_fee=fee, + ) + telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) + patch_get_signal(freqtradebot) + + # Create some test data + freqtradebot.enter_positions() + trade = Trade.query.first() + assert trade + + # Simulate fulfilled LIMIT_BUY order for trade + trade.update(limit_buy_order) + + # Simulate fulfilled LIMIT_SELL order for trade + trade.update(limit_sell_order) + + trade.close_date = datetime.utcnow() + trade.is_open = False + telegram._sell_reason_performance(update=update, context=MagicMock()) + assert msg_mock.call_count == 1 + assert 'Performance' in msg_mock.call_args_list[0][0][0] + assert '<code>ETH/BTC\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0] + + # TEST FOR TRADES WITH NO TAGS + # TEST TRADE WITH ONE TAG MIX AND OTHER TWO TRADES WITH THE SAME TAG MIX + # TEST THE SAME FOR A PAIR + + +def test_mix_tag_performance_handle(default_conf, update, ticker, fee, + limit_buy_order, limit_sell_order, mocker) -> None: + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + fetch_ticker=ticker, + get_fee=fee, + ) + telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) + patch_get_signal(freqtradebot) + + # Create some test data + freqtradebot.enter_positions() + trade = Trade.query.first() + assert trade + + # Simulate fulfilled LIMIT_BUY order for trade + trade.update(limit_buy_order) + + # Simulate fulfilled LIMIT_SELL order for trade + trade.update(limit_sell_order) + + trade.close_date = datetime.utcnow() + trade.is_open = False + telegram._mix_tag_performance(update=update, context=MagicMock()) + assert msg_mock.call_count == 1 + assert 'Performance' in msg_mock.call_args_list[0][0][0] + assert '<code>ETH/BTC\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0] + def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: mocker.patch.multiple( From e4e75d4861247c726781373de54a1e37ba95352d Mon Sep 17 00:00:00 2001 From: theluxaz <theluxaz@gmail.com> Date: Wed, 27 Oct 2021 01:29:19 +0300 Subject: [PATCH 21/26] Added test data for buy_tag/sell_reason testing --- tests/conftest.py | 38 +++++++- tests/conftest_trades_tags.py | 165 ++++++++++++++++++++++++++++++++++ tests/rpc/test_rpc.py | 51 +++++++++-- 3 files changed, 242 insertions(+), 12 deletions(-) create mode 100644 tests/conftest_trades_tags.py diff --git a/tests/conftest.py b/tests/conftest.py index 698c464ed..6afda47d5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -23,11 +23,19 @@ from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import LocalTrade, Trade, init_db from freqtrade.resolvers import ExchangeResolver from freqtrade.worker import Worker -from tests.conftest_trades import (mock_trade_1, mock_trade_2, mock_trade_3, mock_trade_4, - mock_trade_5, mock_trade_6) +from tests.conftest_trades import ( + mock_trade_1, + mock_trade_2, + mock_trade_3, + mock_trade_4, + mock_trade_5, + mock_trade_6, + mock_trade_7, + mock_trade_8, + mock_trade_9) from tests.conftest_trades_usdt import (mock_trade_usdt_1, mock_trade_usdt_2, mock_trade_usdt_3, mock_trade_usdt_4, mock_trade_usdt_5, mock_trade_usdt_6) - +from tests.conftest_trades_tags import (mock_trade_tags_1, mock_trade_tags_2, mock_trade_tags_3) logging.getLogger('').setLevel(logging.INFO) @@ -229,6 +237,30 @@ def create_mock_trades(fee, use_db: bool = True): Trade.commit() +def create_mock_trades_tags(fee, use_db: bool = True): + """ + Create some fake trades to simulate buy tags and sell reasons + """ + def add_trade(trade): + if use_db: + Trade.query.session.add(trade) + else: + LocalTrade.add_bt_trade(trade) + + # Simulate dry_run entries + trade = mock_trade_tags_1(fee) + add_trade(trade) + + trade = mock_trade_tags_2(fee) + add_trade(trade) + + trade = mock_trade_tags_3(fee) + add_trade(trade) + + if use_db: + Trade.commit() + + def create_mock_trades_usdt(fee, use_db: bool = True): """ Create some fake trades ... diff --git a/tests/conftest_trades_tags.py b/tests/conftest_trades_tags.py new file mode 100644 index 000000000..db0d3d3bd --- /dev/null +++ b/tests/conftest_trades_tags.py @@ -0,0 +1,165 @@ +from datetime import datetime, timedelta, timezone + +from freqtrade.persistence.models import Order, Trade + + +MOCK_TRADE_COUNT = 3 + + +def mock_order_1(): + return { + 'id': 'prod_buy_1', + 'symbol': 'LTC/BTC', + 'status': 'closed', + 'side': 'buy', + 'type': 'limit', + 'price': 0.15, + 'amount': 2.0, + 'filled': 2.0, + 'remaining': 0.0, + } + + +def mock_order_1_sell(): + return { + 'id': 'prod_sell_1', + 'symbol': 'LTC/BTC', + 'status': 'open', + 'side': 'sell', + 'type': 'limit', + 'price': 0.20, + 'amount': 2.0, + 'filled': 0.0, + 'remaining': 2.0, + } + + +def mock_trade_tags_1(fee): + trade = Trade( + pair='LTC/BTC', + stake_amount=0.001, + amount=2.0, + amount_requested=2.0, + fee_open=fee.return_value, + fee_close=fee.return_value, + is_open=True, + open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=15), + open_rate=0.15, + exchange='binance', + open_order_id='dry_run_buy_123455', + strategy='StrategyTestV2', + timeframe=5, + buy_tag="BUY_TAG1", + sell_reason="SELL_REASON2" + ) + o = Order.parse_from_ccxt_object(mock_order_1(), 'LTC/BTC', 'buy') + trade.orders.append(o) + o = Order.parse_from_ccxt_object(mock_order_1_sell(), 'LTC/BTC', 'sell') + trade.orders.append(o) + return trade + + +def mock_order_2(): + return { + 'id': '1239', + 'symbol': 'LTC/BTC', + 'status': 'closed', + 'side': 'buy', + 'type': 'limit', + 'price': 0.120, + 'amount': 100.0, + 'filled': 100.0, + 'remaining': 0.0, + } + + +def mock_order_2_sell(): + return { + 'id': '12392', + 'symbol': 'LTC/BTC', + 'status': 'closed', + 'side': 'sell', + 'type': 'limit', + 'price': 0.138, + 'amount': 100.0, + 'filled': 100.0, + 'remaining': 0.0, + } + + +def mock_trade_tags_2(fee): + trade = Trade( + pair='LTC/BTC', + stake_amount=0.001, + amount=100.0, + amount_requested=100.0, + fee_open=fee.return_value, + fee_close=fee.return_value, + is_open=True, + open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=13), + open_rate=0.120, + exchange='binance', + open_order_id='dry_run_buy_123456', + strategy='StrategyTestV2', + timeframe=5, + buy_tag="BUY_TAG2", + sell_reason="SELL_REASON1" + ) + o = Order.parse_from_ccxt_object(mock_order_2(), 'LTC/BTC', 'buy') + trade.orders.append(o) + o = Order.parse_from_ccxt_object(mock_order_2_sell(), 'LTC/BTC', 'sell') + trade.orders.append(o) + return trade + + +def mock_order_3(): + return { + 'id': '1235', + 'symbol': 'ETC/BTC', + 'status': 'closed', + 'side': 'buy', + 'type': 'limit', + 'price': 0.123, + 'amount': 123.0, + 'filled': 123.0, + 'remaining': 0.0, + } + + +def mock_order_3_sell(): + return { + 'id': '12352', + 'symbol': 'ETC/BTC', + 'status': 'closed', + 'side': 'sell', + 'type': 'limit', + 'price': 0.128, + 'amount': 123.0, + 'filled': 123.0, + 'remaining': 0.0, + } + + +def mock_trade_tags_3(fee): + trade = Trade( + pair='ETC/BTC', + stake_amount=0.001, + amount=123.0, + amount_requested=123.0, + fee_open=fee.return_value, + fee_close=fee.return_value, + is_open=True, + open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=12), + open_rate=0.123, + exchange='binance', + open_order_id='dry_run_buy_123457', + strategy='StrategyTestV2', + timeframe=5, + buy_tag="BUY_TAG1", + sell_reason="SELL_REASON2" + ) + o = Order.parse_from_ccxt_object(mock_order_3(), 'ETC/BTC', 'buy') + trade.orders.append(o) + o = Order.parse_from_ccxt_object(mock_order_3_sell(), 'ETC/BTC', 'sell') + trade.orders.append(o) + return trade diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 78805a456..294b5eac8 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -14,7 +14,7 @@ from freqtrade.persistence import Trade from freqtrade.persistence.pairlock_middleware import PairLocks from freqtrade.rpc import RPC, RPCException from freqtrade.rpc.fiat_convert import CryptoToFiatConverter -from tests.conftest import create_mock_trades, get_patched_freqtradebot, patch_get_signal +from tests.conftest import create_mock_trades, get_patched_freqtradebot, patch_get_signal, create_mock_trades_tags # Functions for recurrent object patching @@ -822,10 +822,11 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, trade.close_date = datetime.utcnow() trade.is_open = False res = rpc._rpc_performance() + print(str(res)) assert len(res) == 1 assert res[0]['pair'] == 'ETH/BTC' assert res[0]['count'] == 1 - assert prec_satoshi(res[0]['profit'], 6.2) + assert prec_satoshi(res[0]['profit'], 6.3) # TEST FOR TRADES WITH NO BUY TAG # TEST TRADE WITH ONE BUY_TAG AND OTHER TWO TRADES WITH THE SAME TAG @@ -860,11 +861,43 @@ def test_buy_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, trade.close_date = datetime.utcnow() trade.is_open = False res = rpc._rpc_buy_tag_performance(None) + print(str(res)) assert len(res) == 1 - assert res[0]['pair'] == 'ETH/BTC' + assert res[0]['buy_tag'] == 'Other' assert res[0]['count'] == 1 assert prec_satoshi(res[0]['profit'], 6.2) + print(Trade.pair) + trade.buy_tag = "TEST_TAG" + res = rpc._rpc_buy_tag_performance(None) + print(str(res)) + assert len(res) == 1 + assert res[0]['buy_tag'] == 'TEST_TAG' + assert res[0]['count'] == 1 + assert prec_satoshi(res[0]['profit'], 6.3) + + +def test_buy_tag_performance_handle2(mocker, default_conf, markets, fee): + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + markets=PropertyMock(return_value=markets) + ) + + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + create_mock_trades_tags(fee) + rpc = RPC(freqtradebot) + + trades = Trade.query.all() + print(str(trades[0].buy_tag)) + + res = rpc._rpc_performance() + print(res) + assert len(trades) == 1 + assert trades[0]['buy_tag'] == 'TEST_TAG' + assert trades[0]['count'] == 1 + assert prec_satoshi(trades[0]['profit'], 6.3) + # TEST FOR TRADES WITH NO SELL REASON # TEST TRADE WITH ONE SELL REASON AND OTHER TWO TRADES WITH THE SAME reason # TEST THE SAME FOR A PAIR @@ -899,9 +932,9 @@ def test_sell_reason_performance_handle(default_conf, ticker, limit_buy_order, f trade.is_open = False res = rpc._rpc_sell_reason_performance(None) assert len(res) == 1 - assert res[0]['pair'] == 'ETH/BTC' - assert res[0]['count'] == 1 - assert prec_satoshi(res[0]['profit'], 6.2) + # assert res[0]['pair'] == 'ETH/BTC' + # assert res[0]['count'] == 1 + # assert prec_satoshi(res[0]['profit'], 6.2) # TEST FOR TRADES WITH NO TAGS # TEST TRADE WITH ONE TAG MIX AND OTHER TWO TRADES WITH THE SAME TAG MIX @@ -937,9 +970,9 @@ def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, trade.is_open = False res = rpc._rpc_mix_tag_performance(None) assert len(res) == 1 - assert res[0]['pair'] == 'ETH/BTC' - assert res[0]['count'] == 1 - assert prec_satoshi(res[0]['profit'], 6.2) + # assert res[0]['pair'] == 'ETH/BTC' + # assert res[0]['count'] == 1 + # assert prec_satoshi(res[0]['profit'], 6.2) def test_rpc_count(mocker, default_conf, ticker, fee) -> None: From 21ab83163d3a6fd7d7bc16ca7534e187ecbf8c8a Mon Sep 17 00:00:00 2001 From: theluxaz <theluxaz@gmail.com> Date: Wed, 27 Oct 2021 01:35:47 +0300 Subject: [PATCH 22/26] Quick import/clarity fix --- tests/conftest.py | 5 +---- tests/rpc/test_rpc.py | 4 ++-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 6afda47d5..a2a9f77c6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -29,10 +29,7 @@ from tests.conftest_trades import ( mock_trade_3, mock_trade_4, mock_trade_5, - mock_trade_6, - mock_trade_7, - mock_trade_8, - mock_trade_9) + mock_trade_6) from tests.conftest_trades_usdt import (mock_trade_usdt_1, mock_trade_usdt_2, mock_trade_usdt_3, mock_trade_usdt_4, mock_trade_usdt_5, mock_trade_usdt_6) from tests.conftest_trades_tags import (mock_trade_tags_1, mock_trade_tags_2, mock_trade_tags_3) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 294b5eac8..bce618f30 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -885,11 +885,11 @@ def test_buy_tag_performance_handle2(mocker, default_conf, markets, fee): ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - create_mock_trades_tags(fee) + #create_mock_trades(fee) #this works + create_mock_trades_tags(fee) #this doesn't rpc = RPC(freqtradebot) trades = Trade.query.all() - print(str(trades[0].buy_tag)) res = rpc._rpc_performance() print(res) From 560802c326e9e8baed1e4873a7794531e40988ec Mon Sep 17 00:00:00 2001 From: theluxaz <theluxaz@gmail.com> Date: Thu, 28 Oct 2021 21:39:42 +0300 Subject: [PATCH 23/26] Added tests for the new rpc/telegram functions --- freqtrade/rpc/telegram.py | 6 +- tests/conftest.py | 25 ----- tests/conftest_trades.py | 3 + tests/conftest_trades_tags.py | 165 --------------------------------- tests/rpc/test_rpc.py | 138 ++++++++++++++++++++------- tests/rpc/test_rpc_telegram.py | 32 +++---- 6 files changed, 125 insertions(+), 244 deletions(-) delete mode 100644 tests/conftest_trades_tags.py diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index f79f8d457..23938c686 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -887,7 +887,7 @@ class Telegram(RPCHandler): """ try: pair = None - if context.args: + if context.args and isinstance(context.args[0], str): pair = context.args[0] trades = self._rpc._rpc_buy_tag_performance(pair) @@ -922,7 +922,7 @@ class Telegram(RPCHandler): """ try: pair = None - if context.args: + if context.args and isinstance(context.args[0], str): pair = context.args[0] trades = self._rpc._rpc_sell_reason_performance(pair) @@ -957,7 +957,7 @@ class Telegram(RPCHandler): """ try: pair = None - if context.args: + if context.args and isinstance(context.args[0], str): pair = context.args[0] trades = self._rpc._rpc_mix_tag_performance(pair) diff --git a/tests/conftest.py b/tests/conftest.py index a2a9f77c6..501cfc9b9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -32,7 +32,6 @@ from tests.conftest_trades import ( mock_trade_6) from tests.conftest_trades_usdt import (mock_trade_usdt_1, mock_trade_usdt_2, mock_trade_usdt_3, mock_trade_usdt_4, mock_trade_usdt_5, mock_trade_usdt_6) -from tests.conftest_trades_tags import (mock_trade_tags_1, mock_trade_tags_2, mock_trade_tags_3) logging.getLogger('').setLevel(logging.INFO) @@ -234,30 +233,6 @@ def create_mock_trades(fee, use_db: bool = True): Trade.commit() -def create_mock_trades_tags(fee, use_db: bool = True): - """ - Create some fake trades to simulate buy tags and sell reasons - """ - def add_trade(trade): - if use_db: - Trade.query.session.add(trade) - else: - LocalTrade.add_bt_trade(trade) - - # Simulate dry_run entries - trade = mock_trade_tags_1(fee) - add_trade(trade) - - trade = mock_trade_tags_2(fee) - add_trade(trade) - - trade = mock_trade_tags_3(fee) - add_trade(trade) - - if use_db: - Trade.commit() - - def create_mock_trades_usdt(fee, use_db: bool = True): """ Create some fake trades ... diff --git a/tests/conftest_trades.py b/tests/conftest_trades.py index 024803be0..4496df37d 100644 --- a/tests/conftest_trades.py +++ b/tests/conftest_trades.py @@ -89,6 +89,7 @@ def mock_trade_2(fee): open_order_id='dry_run_sell_12345', strategy='StrategyTestV2', timeframe=5, + buy_tag='TEST1', sell_reason='sell_signal', open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=20), close_date=datetime.now(tz=timezone.utc) - timedelta(minutes=2), @@ -241,6 +242,7 @@ def mock_trade_5(fee): open_rate=0.123, exchange='binance', strategy='SampleStrategy', + buy_tag='TEST1', stoploss_order_id='prod_stoploss_3455', timeframe=5, ) @@ -295,6 +297,7 @@ def mock_trade_6(fee): open_rate=0.15, exchange='binance', strategy='SampleStrategy', + buy_tag='TEST2', open_order_id="prod_sell_6", timeframe=5, ) diff --git a/tests/conftest_trades_tags.py b/tests/conftest_trades_tags.py deleted file mode 100644 index db0d3d3bd..000000000 --- a/tests/conftest_trades_tags.py +++ /dev/null @@ -1,165 +0,0 @@ -from datetime import datetime, timedelta, timezone - -from freqtrade.persistence.models import Order, Trade - - -MOCK_TRADE_COUNT = 3 - - -def mock_order_1(): - return { - 'id': 'prod_buy_1', - 'symbol': 'LTC/BTC', - 'status': 'closed', - 'side': 'buy', - 'type': 'limit', - 'price': 0.15, - 'amount': 2.0, - 'filled': 2.0, - 'remaining': 0.0, - } - - -def mock_order_1_sell(): - return { - 'id': 'prod_sell_1', - 'symbol': 'LTC/BTC', - 'status': 'open', - 'side': 'sell', - 'type': 'limit', - 'price': 0.20, - 'amount': 2.0, - 'filled': 0.0, - 'remaining': 2.0, - } - - -def mock_trade_tags_1(fee): - trade = Trade( - pair='LTC/BTC', - stake_amount=0.001, - amount=2.0, - amount_requested=2.0, - fee_open=fee.return_value, - fee_close=fee.return_value, - is_open=True, - open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=15), - open_rate=0.15, - exchange='binance', - open_order_id='dry_run_buy_123455', - strategy='StrategyTestV2', - timeframe=5, - buy_tag="BUY_TAG1", - sell_reason="SELL_REASON2" - ) - o = Order.parse_from_ccxt_object(mock_order_1(), 'LTC/BTC', 'buy') - trade.orders.append(o) - o = Order.parse_from_ccxt_object(mock_order_1_sell(), 'LTC/BTC', 'sell') - trade.orders.append(o) - return trade - - -def mock_order_2(): - return { - 'id': '1239', - 'symbol': 'LTC/BTC', - 'status': 'closed', - 'side': 'buy', - 'type': 'limit', - 'price': 0.120, - 'amount': 100.0, - 'filled': 100.0, - 'remaining': 0.0, - } - - -def mock_order_2_sell(): - return { - 'id': '12392', - 'symbol': 'LTC/BTC', - 'status': 'closed', - 'side': 'sell', - 'type': 'limit', - 'price': 0.138, - 'amount': 100.0, - 'filled': 100.0, - 'remaining': 0.0, - } - - -def mock_trade_tags_2(fee): - trade = Trade( - pair='LTC/BTC', - stake_amount=0.001, - amount=100.0, - amount_requested=100.0, - fee_open=fee.return_value, - fee_close=fee.return_value, - is_open=True, - open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=13), - open_rate=0.120, - exchange='binance', - open_order_id='dry_run_buy_123456', - strategy='StrategyTestV2', - timeframe=5, - buy_tag="BUY_TAG2", - sell_reason="SELL_REASON1" - ) - o = Order.parse_from_ccxt_object(mock_order_2(), 'LTC/BTC', 'buy') - trade.orders.append(o) - o = Order.parse_from_ccxt_object(mock_order_2_sell(), 'LTC/BTC', 'sell') - trade.orders.append(o) - return trade - - -def mock_order_3(): - return { - 'id': '1235', - 'symbol': 'ETC/BTC', - 'status': 'closed', - 'side': 'buy', - 'type': 'limit', - 'price': 0.123, - 'amount': 123.0, - 'filled': 123.0, - 'remaining': 0.0, - } - - -def mock_order_3_sell(): - return { - 'id': '12352', - 'symbol': 'ETC/BTC', - 'status': 'closed', - 'side': 'sell', - 'type': 'limit', - 'price': 0.128, - 'amount': 123.0, - 'filled': 123.0, - 'remaining': 0.0, - } - - -def mock_trade_tags_3(fee): - trade = Trade( - pair='ETC/BTC', - stake_amount=0.001, - amount=123.0, - amount_requested=123.0, - fee_open=fee.return_value, - fee_close=fee.return_value, - is_open=True, - open_date=datetime.now(tz=timezone.utc) - timedelta(minutes=12), - open_rate=0.123, - exchange='binance', - open_order_id='dry_run_buy_123457', - strategy='StrategyTestV2', - timeframe=5, - buy_tag="BUY_TAG1", - sell_reason="SELL_REASON2" - ) - o = Order.parse_from_ccxt_object(mock_order_3(), 'ETC/BTC', 'buy') - trade.orders.append(o) - o = Order.parse_from_ccxt_object(mock_order_3_sell(), 'ETC/BTC', 'sell') - trade.orders.append(o) - return trade diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index bce618f30..aeb0483de 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -14,7 +14,7 @@ from freqtrade.persistence import Trade from freqtrade.persistence.pairlock_middleware import PairLocks from freqtrade.rpc import RPC, RPCException from freqtrade.rpc.fiat_convert import CryptoToFiatConverter -from tests.conftest import create_mock_trades, get_patched_freqtradebot, patch_get_signal, create_mock_trades_tags +from tests.conftest import create_mock_trades, get_patched_freqtradebot, patch_get_signal # Functions for recurrent object patching @@ -826,11 +826,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, assert len(res) == 1 assert res[0]['pair'] == 'ETH/BTC' assert res[0]['count'] == 1 - assert prec_satoshi(res[0]['profit'], 6.3) - - # TEST FOR TRADES WITH NO BUY TAG - # TEST TRADE WITH ONE BUY_TAG AND OTHER TWO TRADES WITH THE SAME TAG - # TEST THE SAME FOR A PAIR + assert prec_satoshi(res[0]['profit'], 6.2) def test_buy_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, @@ -861,23 +857,22 @@ def test_buy_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, trade.close_date = datetime.utcnow() trade.is_open = False res = rpc._rpc_buy_tag_performance(None) - print(str(res)) + assert len(res) == 1 assert res[0]['buy_tag'] == 'Other' assert res[0]['count'] == 1 assert prec_satoshi(res[0]['profit'], 6.2) - print(Trade.pair) trade.buy_tag = "TEST_TAG" res = rpc._rpc_buy_tag_performance(None) - print(str(res)) + assert len(res) == 1 assert res[0]['buy_tag'] == 'TEST_TAG' assert res[0]['count'] == 1 - assert prec_satoshi(res[0]['profit'], 6.3) + assert prec_satoshi(res[0]['profit'], 6.2) -def test_buy_tag_performance_handle2(mocker, default_conf, markets, fee): +def test_buy_tag_performance_handle_2(mocker, default_conf, markets, fee): mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch.multiple( 'freqtrade.exchange.Exchange', @@ -885,22 +880,25 @@ def test_buy_tag_performance_handle2(mocker, default_conf, markets, fee): ) freqtradebot = get_patched_freqtradebot(mocker, default_conf) - #create_mock_trades(fee) #this works - create_mock_trades_tags(fee) #this doesn't + create_mock_trades(fee) rpc = RPC(freqtradebot) - trades = Trade.query.all() + res = rpc._rpc_buy_tag_performance(None) - res = rpc._rpc_performance() - print(res) - assert len(trades) == 1 - assert trades[0]['buy_tag'] == 'TEST_TAG' - assert trades[0]['count'] == 1 - assert prec_satoshi(trades[0]['profit'], 6.3) + assert len(res) == 2 + assert res[0]['buy_tag'] == 'TEST1' + assert res[0]['count'] == 1 + assert prec_satoshi(res[0]['profit'], 0.5) + assert res[1]['buy_tag'] == 'Other' + assert res[1]['count'] == 1 + assert prec_satoshi(res[1]['profit'], 1.0) - # TEST FOR TRADES WITH NO SELL REASON - # TEST TRADE WITH ONE SELL REASON AND OTHER TWO TRADES WITH THE SAME reason - # TEST THE SAME FOR A PAIR + # Test for a specific pair + res = rpc._rpc_buy_tag_performance('ETC/BTC') + assert len(res) == 1 + assert res[0]['count'] == 1 + assert res[0]['buy_tag'] == 'TEST1' + assert prec_satoshi(res[0]['profit'], 0.5) def test_sell_reason_performance_handle(default_conf, ticker, limit_buy_order, fee, @@ -931,14 +929,48 @@ def test_sell_reason_performance_handle(default_conf, ticker, limit_buy_order, f trade.close_date = datetime.utcnow() trade.is_open = False res = rpc._rpc_sell_reason_performance(None) - assert len(res) == 1 - # assert res[0]['pair'] == 'ETH/BTC' - # assert res[0]['count'] == 1 - # assert prec_satoshi(res[0]['profit'], 6.2) - # TEST FOR TRADES WITH NO TAGS - # TEST TRADE WITH ONE TAG MIX AND OTHER TWO TRADES WITH THE SAME TAG MIX - # TEST THE SAME FOR A PAIR + assert len(res) == 1 + assert res[0]['sell_reason'] == 'Other' + assert res[0]['count'] == 1 + assert prec_satoshi(res[0]['profit'], 6.2) + + trade.sell_reason = "TEST1" + res = rpc._rpc_sell_reason_performance(None) + + assert len(res) == 1 + assert res[0]['sell_reason'] == 'TEST1' + assert res[0]['count'] == 1 + assert prec_satoshi(res[0]['profit'], 6.2) + + +def test_sell_reason_performance_handle_2(mocker, default_conf, markets, fee): + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + markets=PropertyMock(return_value=markets) + ) + + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + create_mock_trades(fee) + rpc = RPC(freqtradebot) + + res = rpc._rpc_sell_reason_performance(None) + + assert len(res) == 2 + assert res[0]['sell_reason'] == 'sell_signal' + assert res[0]['count'] == 1 + assert prec_satoshi(res[0]['profit'], 0.5) + assert res[1]['sell_reason'] == 'roi' + assert res[1]['count'] == 1 + assert prec_satoshi(res[1]['profit'], 1.0) + + # Test for a specific pair + res = rpc._rpc_sell_reason_performance('ETC/BTC') + assert len(res) == 1 + assert res[0]['count'] == 1 + assert res[0]['sell_reason'] == 'sell_signal' + assert prec_satoshi(res[0]['profit'], 0.5) def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, @@ -969,10 +1001,50 @@ def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, trade.close_date = datetime.utcnow() trade.is_open = False res = rpc._rpc_mix_tag_performance(None) + assert len(res) == 1 - # assert res[0]['pair'] == 'ETH/BTC' - # assert res[0]['count'] == 1 - # assert prec_satoshi(res[0]['profit'], 6.2) + assert res[0]['mix_tag'] == 'Other Other' + assert res[0]['count'] == 1 + assert prec_satoshi(res[0]['profit'], 6.2) + + trade.buy_tag = "TESTBUY" + trade.sell_reason = "TESTSELL" + res = rpc._rpc_mix_tag_performance(None) + + assert len(res) == 1 + assert res[0]['mix_tag'] == 'TESTBUY TESTSELL' + assert res[0]['count'] == 1 + assert prec_satoshi(res[0]['profit'], 6.2) + + +def test_mix_tag_performance_handle_2(mocker, default_conf, markets, fee): + mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + markets=PropertyMock(return_value=markets) + ) + + freqtradebot = get_patched_freqtradebot(mocker, default_conf) + create_mock_trades(fee) + rpc = RPC(freqtradebot) + + res = rpc._rpc_mix_tag_performance(None) + + assert len(res) == 2 + assert res[0]['mix_tag'] == 'TEST1 sell_signal' + assert res[0]['count'] == 1 + assert prec_satoshi(res[0]['profit'], 0.5) + assert res[1]['mix_tag'] == 'Other roi' + assert res[1]['count'] == 1 + assert prec_satoshi(res[1]['profit'], 1.0) + + # Test for a specific pair + res = rpc._rpc_mix_tag_performance('ETC/BTC') + + assert len(res) == 1 + assert res[0]['count'] == 1 + assert res[0]['mix_tag'] == 'TEST1 sell_signal' + assert prec_satoshi(res[0]['profit'], 0.5) def test_rpc_count(mocker, default_conf, ticker, fee) -> None: diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 306181eae..f669e9411 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -978,10 +978,6 @@ def test_performance_handle(default_conf, update, ticker, fee, assert 'Performance' in msg_mock.call_args_list[0][0][0] assert '<code>ETH/BTC\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0] - # TEST FOR TRADES WITH NO BUY TAG - # TEST TRADE WITH ONE BUY_TAG AND OTHER TWO TRADES WITH THE SAME TAG - # TEST THE SAME FOR A PAIR - def test_buy_tag_performance_handle(default_conf, update, ticker, fee, limit_buy_order, limit_sell_order, mocker) -> None: @@ -1001,19 +997,17 @@ def test_buy_tag_performance_handle(default_conf, update, ticker, fee, # Simulate fulfilled LIMIT_BUY order for trade trade.update(limit_buy_order) + trade.buy_tag = "TESTBUY" # Simulate fulfilled LIMIT_SELL order for trade trade.update(limit_sell_order) trade.close_date = datetime.utcnow() trade.is_open = False + telegram._buy_tag_performance(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert 'Performance' in msg_mock.call_args_list[0][0][0] - assert '<code>ETH/BTC\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0] - - # TEST FOR TRADES WITH NO SELL REASON - # TEST TRADE WITH ONE SELL REASON AND OTHER TWO TRADES WITH THE SAME reason - # TEST THE SAME FOR A PAIR + assert 'Buy Tag Performance' in msg_mock.call_args_list[0][0][0] + assert '<code>TESTBUY\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0] def test_sell_reason_performance_handle(default_conf, update, ticker, fee, @@ -1034,19 +1028,17 @@ def test_sell_reason_performance_handle(default_conf, update, ticker, fee, # Simulate fulfilled LIMIT_BUY order for trade trade.update(limit_buy_order) + trade.sell_reason = 'TESTSELL' # Simulate fulfilled LIMIT_SELL order for trade trade.update(limit_sell_order) trade.close_date = datetime.utcnow() trade.is_open = False + telegram._sell_reason_performance(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert 'Performance' in msg_mock.call_args_list[0][0][0] - assert '<code>ETH/BTC\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0] - - # TEST FOR TRADES WITH NO TAGS - # TEST TRADE WITH ONE TAG MIX AND OTHER TWO TRADES WITH THE SAME TAG MIX - # TEST THE SAME FOR A PAIR + assert 'Sell Reason Performance' in msg_mock.call_args_list[0][0][0] + assert '<code>TESTSELL\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0] def test_mix_tag_performance_handle(default_conf, update, ticker, fee, @@ -1067,15 +1059,19 @@ def test_mix_tag_performance_handle(default_conf, update, ticker, fee, # Simulate fulfilled LIMIT_BUY order for trade trade.update(limit_buy_order) + trade.buy_tag = "TESTBUY" + trade.sell_reason = "TESTSELL" + # Simulate fulfilled LIMIT_SELL order for trade trade.update(limit_sell_order) trade.close_date = datetime.utcnow() trade.is_open = False + telegram._mix_tag_performance(update=update, context=MagicMock()) assert msg_mock.call_count == 1 - assert 'Performance' in msg_mock.call_args_list[0][0][0] - assert '<code>ETH/BTC\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0] + assert 'Mix Tag Performance' in msg_mock.call_args_list[0][0][0] + assert '<code>TESTBUY TESTSELL\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0] def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: From 240923341b580f0e719c614ddb3ffc2faf93154e Mon Sep 17 00:00:00 2001 From: Matthias <xmatthias@outlook.com> Date: Fri, 29 Oct 2021 07:04:20 +0200 Subject: [PATCH 24/26] Reformat telegram test --- tests/conftest.py | 10 +++------- tests/rpc/test_rpc_telegram.py | 3 ++- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 501cfc9b9..698c464ed 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -23,16 +23,12 @@ from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import LocalTrade, Trade, init_db from freqtrade.resolvers import ExchangeResolver from freqtrade.worker import Worker -from tests.conftest_trades import ( - mock_trade_1, - mock_trade_2, - mock_trade_3, - mock_trade_4, - mock_trade_5, - mock_trade_6) +from tests.conftest_trades import (mock_trade_1, mock_trade_2, mock_trade_3, mock_trade_4, + mock_trade_5, mock_trade_6) from tests.conftest_trades_usdt import (mock_trade_usdt_1, mock_trade_usdt_2, mock_trade_usdt_3, mock_trade_usdt_4, mock_trade_usdt_5, mock_trade_usdt_6) + logging.getLogger('').setLevel(logging.INFO) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index f669e9411..5f49c8bf7 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1071,7 +1071,8 @@ def test_mix_tag_performance_handle(default_conf, update, ticker, fee, telegram._mix_tag_performance(update=update, context=MagicMock()) assert msg_mock.call_count == 1 assert 'Mix Tag Performance' in msg_mock.call_args_list[0][0][0] - assert '<code>TESTBUY TESTSELL\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0] + assert ('<code>TESTBUY TESTSELL\t0.00006217 BTC (6.20%) (1)</code>' + in msg_mock.call_args_list[0][0][0]) def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: From dffe76f10935e1c88141d85e00c3ad1204863738 Mon Sep 17 00:00:00 2001 From: Matthias <xmatthias@outlook.com> Date: Sun, 31 Oct 2021 10:42:42 +0100 Subject: [PATCH 25/26] Don't double-loop to generate profits --- freqtrade/freqtradebot.py | 8 ++++---- freqtrade/persistence/models.py | 23 ++++++++++------------- freqtrade/rpc/api_server/api_schemas.py | 2 ++ freqtrade/rpc/rpc.py | 3 --- freqtrade/rpc/telegram.py | 8 ++++---- tests/rpc/test_rpc.py | 23 +++++++++++------------ tests/rpc/test_rpc_apiserver.py | 6 ++++-- 7 files changed, 35 insertions(+), 38 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index fb42a8924..a0b773a16 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -202,10 +202,10 @@ class FreqtradeBot(LoggingMixin): 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 ''}", + 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) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 8ccf8bbef..3da415e9b 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -850,18 +850,17 @@ class Trade(_DECL_BASE, LocalTrade): .group_by(Trade.pair) \ .order_by(desc('profit_sum_abs')) \ .all() - - response = [ + return [ { 'pair': pair, - 'profit': profit, + 'profit_ratio': profit, + 'profit': round(profit * 100, 2), # Compatibility mode + 'profit_pct': round(profit * 100, 2), 'profit_abs': profit_abs, 'count': count } for pair, profit, profit_abs, count in pair_rates ] - [x.update({'profit': round(x['profit'] * 100, 2)}) for x in response] - return response @staticmethod def get_buy_tag_performance(pair: Optional[str]) -> List[Dict[str, Any]]: @@ -885,17 +884,16 @@ class Trade(_DECL_BASE, LocalTrade): .order_by(desc('profit_sum_abs')) \ .all() - response = [ + return [ { 'buy_tag': buy_tag if buy_tag is not None else "Other", - 'profit': profit, + 'profit_ratio': profit, + 'profit_pct': round(profit * 100, 2), 'profit_abs': profit_abs, 'count': count } for buy_tag, profit, profit_abs, count in buy_tag_perf ] - [x.update({'profit': round(x['profit'] * 100, 2)}) for x in response] - return response @staticmethod def get_sell_reason_performance(pair: Optional[str]) -> List[Dict[str, Any]]: @@ -919,17 +917,16 @@ class Trade(_DECL_BASE, LocalTrade): .order_by(desc('profit_sum_abs')) \ .all() - response = [ + return [ { 'sell_reason': sell_reason if sell_reason is not None else "Other", - 'profit': profit, + '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 ] - [x.update({'profit': round(x['profit'] * 100, 2)}) for x in response] - return response @staticmethod def get_mix_tag_performance(pair: Optional[str]) -> List[Dict[str, Any]]: diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index e9985c3c6..ff1915fca 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -63,6 +63,8 @@ class Count(BaseModel): class PerformanceEntry(BaseModel): pair: str profit: float + profit_ratio: float + profit_pct: float profit_abs: float count: int diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 42d502cd8..da8d23b7a 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -161,8 +161,6 @@ class RPC: current_rate = NAN else: current_rate = trade.close_rate - - buy_tag = trade.buy_tag current_profit = trade.calc_profit_ratio(current_rate) current_profit_abs = trade.calc_profit(current_rate) current_profit_fiat: Optional[float] = None @@ -193,7 +191,6 @@ class RPC: profit_pct=round(current_profit * 100, 2), profit_abs=current_profit_abs, profit_fiat=current_profit_fiat, - buy_tag=buy_tag, stoploss_current_dist=stoploss_current_dist, stoploss_current_dist_ratio=round(stoploss_current_dist_ratio, 8), diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 23938c686..e07734fda 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -861,7 +861,7 @@ class Telegram(RPCHandler): stat_line = ( f"{i+1}.\t <code>{trade['pair']}\t" f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " - f"({trade['profit']:.2f}%) " + f"({trade['profit_pct']:.2f}%) " f"({trade['count']})</code>\n") if len(output + stat_line) >= MAX_TELEGRAM_MESSAGE_LENGTH: @@ -896,7 +896,7 @@ class Telegram(RPCHandler): stat_line = ( f"{i+1}.\t <code>{trade['buy_tag']}\t" f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " - f"({trade['profit']:.2f}%) " + f"({trade['profit_pct']:.2f}%) " f"({trade['count']})</code>\n") if len(output + stat_line) >= MAX_TELEGRAM_MESSAGE_LENGTH: @@ -931,7 +931,7 @@ class Telegram(RPCHandler): stat_line = ( f"{i+1}.\t <code>{trade['sell_reason']}\t" f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " - f"({trade['profit']:.2f}%) " + f"({trade['profit_pct']:.2f}%) " f"({trade['count']})</code>\n") if len(output + stat_line) >= MAX_TELEGRAM_MESSAGE_LENGTH: @@ -1158,7 +1158,7 @@ class Telegram(RPCHandler): " `pending sell orders are marked with a double asterisk (**)`\n" "*/buys <pair|none>:* `Shows the buy_tag performance`\n" "*/sells <pair|none>:* `Shows the sell reason performance`\n" - "*/mix_tag <pair|none>:* `Shows combined buy tag + sell reason performance`\n" + "*/mix_tags <pair|none>:* `Shows combined buy tag + sell reason performance`\n" "*/trades [limit]:* `Lists last closed trades (limited to 10 by default)`\n" "*/profit [<n>]:* `Lists cumulative profit from all finished trades, " "over the last n days`\n" diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index aeb0483de..945217b8a 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -822,11 +822,10 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee, trade.close_date = datetime.utcnow() trade.is_open = False res = rpc._rpc_performance() - print(str(res)) assert len(res) == 1 assert res[0]['pair'] == 'ETH/BTC' assert res[0]['count'] == 1 - assert prec_satoshi(res[0]['profit'], 6.2) + assert prec_satoshi(res[0]['profit_pct'], 6.2) def test_buy_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, @@ -861,7 +860,7 @@ def test_buy_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, assert len(res) == 1 assert res[0]['buy_tag'] == 'Other' assert res[0]['count'] == 1 - assert prec_satoshi(res[0]['profit'], 6.2) + assert prec_satoshi(res[0]['profit_pct'], 6.2) trade.buy_tag = "TEST_TAG" res = rpc._rpc_buy_tag_performance(None) @@ -869,7 +868,7 @@ def test_buy_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, assert len(res) == 1 assert res[0]['buy_tag'] == 'TEST_TAG' assert res[0]['count'] == 1 - assert prec_satoshi(res[0]['profit'], 6.2) + assert prec_satoshi(res[0]['profit_pct'], 6.2) def test_buy_tag_performance_handle_2(mocker, default_conf, markets, fee): @@ -888,17 +887,17 @@ def test_buy_tag_performance_handle_2(mocker, default_conf, markets, fee): assert len(res) == 2 assert res[0]['buy_tag'] == 'TEST1' assert res[0]['count'] == 1 - assert prec_satoshi(res[0]['profit'], 0.5) + assert prec_satoshi(res[0]['profit_pct'], 0.5) assert res[1]['buy_tag'] == 'Other' assert res[1]['count'] == 1 - assert prec_satoshi(res[1]['profit'], 1.0) + assert prec_satoshi(res[1]['profit_pct'], 1.0) # Test for a specific pair res = rpc._rpc_buy_tag_performance('ETC/BTC') assert len(res) == 1 assert res[0]['count'] == 1 assert res[0]['buy_tag'] == 'TEST1' - assert prec_satoshi(res[0]['profit'], 0.5) + assert prec_satoshi(res[0]['profit_pct'], 0.5) def test_sell_reason_performance_handle(default_conf, ticker, limit_buy_order, fee, @@ -933,7 +932,7 @@ def test_sell_reason_performance_handle(default_conf, ticker, limit_buy_order, f assert len(res) == 1 assert res[0]['sell_reason'] == 'Other' assert res[0]['count'] == 1 - assert prec_satoshi(res[0]['profit'], 6.2) + assert prec_satoshi(res[0]['profit_pct'], 6.2) trade.sell_reason = "TEST1" res = rpc._rpc_sell_reason_performance(None) @@ -941,7 +940,7 @@ def test_sell_reason_performance_handle(default_conf, ticker, limit_buy_order, f assert len(res) == 1 assert res[0]['sell_reason'] == 'TEST1' assert res[0]['count'] == 1 - assert prec_satoshi(res[0]['profit'], 6.2) + assert prec_satoshi(res[0]['profit_pct'], 6.2) def test_sell_reason_performance_handle_2(mocker, default_conf, markets, fee): @@ -960,17 +959,17 @@ def test_sell_reason_performance_handle_2(mocker, default_conf, markets, fee): assert len(res) == 2 assert res[0]['sell_reason'] == 'sell_signal' assert res[0]['count'] == 1 - assert prec_satoshi(res[0]['profit'], 0.5) + assert prec_satoshi(res[0]['profit_pct'], 0.5) assert res[1]['sell_reason'] == 'roi' assert res[1]['count'] == 1 - assert prec_satoshi(res[1]['profit'], 1.0) + assert prec_satoshi(res[1]['profit_pct'], 1.0) # Test for a specific pair res = rpc._rpc_sell_reason_performance('ETC/BTC') assert len(res) == 1 assert res[0]['count'] == 1 assert res[0]['sell_reason'] == 'sell_signal' - assert prec_satoshi(res[0]['profit'], 0.5) + assert prec_satoshi(res[0]['profit_pct'], 0.5) def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee, diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 02ed26459..e0bbee861 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -812,8 +812,10 @@ def test_api_performance(botclient, fee): rc = client_get(client, f"{BASE_URI}/performance") assert_response(rc) assert len(rc.json()) == 2 - assert rc.json() == [{'count': 1, 'pair': 'LTC/ETH', 'profit': 7.61, 'profit_abs': 0.01872279}, - {'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57, 'profit_abs': -0.1150375}] + assert rc.json() == [{'count': 1, 'pair': 'LTC/ETH', 'profit': 7.61, 'profit_pct': 7.61, + 'profit_ratio': 0.07609203, 'profit_abs': 0.01872279}, + {'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57, 'profit_pct': -5.57, + 'profit_ratio': -0.05570419, 'profit_abs': -0.1150375}] def test_api_status(botclient, mocker, ticker, fee, markets): From 6b90b4a144e559ffc81318788116be6cfd2ac0a2 Mon Sep 17 00:00:00 2001 From: Matthias <xmatthias@outlook.com> Date: Sun, 31 Oct 2021 10:51:56 +0100 Subject: [PATCH 26/26] Test "get-signal" --- freqtrade/freqtradebot.py | 4 ++-- tests/strategy/test_interface.py | 14 +++++++++++++- tests/test_freqtradebot.py | 3 ++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a0b773a16..d23ba270d 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -420,7 +420,7 @@ class FreqtradeBot(LoggingMixin): return False # running get_signal on historical data fetched - (buy, sell, buy_tag, exit_tag) = self.strategy.get_signal( + (buy, sell, buy_tag, _) = self.strategy.get_signal( pair, self.strategy.timeframe, analyzed_df @@ -707,7 +707,7 @@ class FreqtradeBot(LoggingMixin): analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair, self.strategy.timeframe) - (buy, sell, buy_tag, exit_tag) = self.strategy.get_signal( + (buy, sell, _, exit_tag) = self.strategy.get_signal( trade.pair, self.strategy.timeframe, analyzed_df diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py index 62510b370..e8ee0bfed 100644 --- a/tests/strategy/test_interface.py +++ b/tests/strategy/test_interface.py @@ -30,7 +30,7 @@ _STRATEGY = StrategyTestV2(config={}) _STRATEGY.dp = DataProvider({}, None, None) -def test_returns_latest_signal(mocker, default_conf, ohlcv_history): +def test_returns_latest_signal(ohlcv_history): ohlcv_history.loc[1, 'date'] = arrow.utcnow() # Take a copy to correctly modify the call mocked_history = ohlcv_history.copy() @@ -60,6 +60,18 @@ def test_returns_latest_signal(mocker, default_conf, ohlcv_history): 'buy_signal_01', None) + mocked_history.loc[1, 'buy_tag'] = None + mocked_history.loc[1, 'exit_tag'] = 'sell_signal_01' + + assert _STRATEGY.get_signal( + 'ETH/BTC', + '5m', + mocked_history) == ( + True, + False, + None, + 'sell_signal_01') + def test_analyze_pair_empty(default_conf, mocker, caplog, ohlcv_history): mocker.patch.object(_STRATEGY.dp, 'ohlcv', return_value=ohlcv_history) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index e590f4f74..0435dc3a2 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1808,7 +1808,7 @@ def test_handle_trade(default_conf_usdt, limit_buy_order_usdt, limit_sell_order_ assert trade.is_open is True freqtrade.wallets.update() - patch_get_signal(freqtrade, value=(False, True, None, None)) + patch_get_signal(freqtrade, value=(False, True, None, 'sell_signal1')) assert freqtrade.handle_trade(trade) is True assert trade.open_order_id == limit_sell_order_usdt['id'] @@ -1819,6 +1819,7 @@ def test_handle_trade(default_conf_usdt, limit_buy_order_usdt, limit_sell_order_ assert trade.close_profit == 0.09451372 assert trade.calc_profit() == 5.685 assert trade.close_date is not None + assert trade.sell_reason == 'sell_signal1' def test_handle_overlapping_signals(default_conf_usdt, ticker_usdt, limit_buy_order_usdt_open,