From 85979c317679a58172fd15c2432d3c94cb1d9c4f Mon Sep 17 00:00:00 2001 From: Cryptomeister Nox Date: Thu, 17 Jun 2021 20:35:02 +0200 Subject: [PATCH 01/64] * Adding command for Filtering * Read latest Backtest file and print trades --- freqtrade/commands/__init__.py | 2 +- freqtrade/commands/arguments.py | 16 ++++++++++++-- freqtrade/commands/cli_options.py | 5 +++++ freqtrade/commands/optimize_commands.py | 28 +++++++++++++++++++++++++ freqtrade/optimize/optimize_reports.py | 15 +++++++++++++ 5 files changed, 63 insertions(+), 3 deletions(-) diff --git a/freqtrade/commands/__init__.py b/freqtrade/commands/__init__.py index 784b99bed..ecce709e5 100644 --- a/freqtrade/commands/__init__.py +++ b/freqtrade/commands/__init__.py @@ -16,7 +16,7 @@ from freqtrade.commands.hyperopt_commands import start_hyperopt_list, start_hype from freqtrade.commands.list_commands import (start_list_exchanges, start_list_hyperopts, start_list_markets, start_list_strategies, start_list_timeframes, start_show_trades) -from freqtrade.commands.optimize_commands import start_backtesting, start_edge, start_hyperopt +from freqtrade.commands.optimize_commands import start_backtest_filter, start_backtesting, start_edge, start_hyperopt from freqtrade.commands.pairlist_commands import start_test_pairlist from freqtrade.commands.plot_commands import start_plot_dataframe, start_plot_profit from freqtrade.commands.trade_commands import start_trading diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 7f4f7edd6..f5b9f9cc2 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -2,6 +2,7 @@ This module contains the argument manager class """ import argparse +from freqtrade.commands.optimize_commands import start_backtest_filter from functools import partial from pathlib import Path from typing import Any, Dict, List, Optional @@ -37,6 +38,8 @@ ARGS_LIST_STRATEGIES = ["strategy_path", "print_one_column", "print_colorized"] ARGS_LIST_HYPEROPTS = ["hyperopt_path", "print_one_column", "print_colorized"] +ARGS_BACKTEST_FILTER = ["backtest_path"] + ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all"] ARGS_LIST_TIMEFRAMES = ["exchange", "print_one_column"] @@ -89,7 +92,7 @@ ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperop NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list-timeframes", "list-markets", "list-pairs", "list-strategies", "list-data", - "list-hyperopts", "hyperopt-list", "hyperopt-show", + "list-hyperopts", "hyperopt-list", "backtest-filter", "hyperopt-show", "plot-dataframe", "plot-profit", "show-trades"] NO_CONF_ALLOWED = ["create-userdir", "list-exchanges", "new-hyperopt", "new-strategy"] @@ -168,7 +171,7 @@ class Arguments: self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot') self._build_args(optionlist=['version'], parser=self.parser) - from freqtrade.commands import (start_backtesting, start_convert_data, start_create_userdir, + from freqtrade.commands import (start_backtesting, start_backtest_filter, start_convert_data, start_create_userdir, start_download_data, start_edge, start_hyperopt, start_hyperopt_list, start_hyperopt_show, start_install_ui, start_list_data, start_list_exchanges, start_list_hyperopts, @@ -256,6 +259,15 @@ class Arguments: backtesting_cmd.set_defaults(func=start_backtesting) self._build_args(optionlist=ARGS_BACKTEST, parser=backtesting_cmd) + # Add backtest-filter subcommand + backtest_filter_cmd = subparsers.add_parser( + 'backtest-filter', + help='Filter Backtest results', + parents=[_common_parser], + ) + backtest_filter_cmd.set_defaults(func=start_backtest_filter) + self._build_args(optionlist=ARGS_BACKTEST_FILTER, parser=backtest_filter_cmd) + # Add edge subcommand edge_cmd = subparsers.add_parser('edge', help='Edge module.', parents=[_common_parser, _strategy_parser]) diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index d832693ee..8ba66b32d 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -202,6 +202,11 @@ AVAILABLE_CLI_OPTIONS = { help='Specify additional lookup path for Hyperopt and Hyperopt Loss functions.', metavar='PATH', ), + "backtest_path": Arg( + '--backtest-path', + help='Specify lookup file path for backtest filter.', + metavar='PATH', + ), "epochs": Arg( '-e', '--epochs', help='Specify number of epochs (default: %(default)d).', diff --git a/freqtrade/commands/optimize_commands.py b/freqtrade/commands/optimize_commands.py index a84b3b3bd..3165852fa 100644 --- a/freqtrade/commands/optimize_commands.py +++ b/freqtrade/commands/optimize_commands.py @@ -1,3 +1,7 @@ +from freqtrade.data.btanalysis import get_latest_backtest_filename +import pandas +from pandas.io import json +from freqtrade.optimize import backtesting import logging from typing import Any, Dict @@ -52,6 +56,30 @@ def start_backtesting(args: Dict[str, Any]) -> None: backtesting = Backtesting(config) backtesting.start() +def start_backtest_filter(args: Dict[str, Any]) -> None: + """ + List backtest pairs previously filtered + """ + + config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) + + no_header = config.get('backtest_show_pair_list', False) + results_file = get_latest_backtest_filename( + config['user_data_dir'] / 'backtest_results/') + + logger.info("Using Backtesting result {results_file}") + + # load data using Python JSON module + with open(config['user_data_dir'] / 'backtest_results/' / results_file,'r') as f: + data = json.loads(f.read()) + strategy = list(data["strategy"])[0] + trades = data["strategy"][strategy] + + print(trades) + + + logger.info("Backtest filtering complete. ") + def start_hyperopt(args: Dict[str, Any]) -> None: """ diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 84e052ac4..f401614c5 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -668,3 +668,18 @@ def show_backtest_results(config: Dict, backtest_stats: Dict): print(table) print('=' * len(table.splitlines()[0])) print('\nFor more details, please look at the detail tables above') + +def show_backtest_results_filtered(config: Dict, backtest_stats: Dict): + stake_currency = config['stake_currency'] + + for strategy, results in backtest_stats['strategy'].items(): + show_backtest_result(strategy, results, stake_currency) + + if len(backtest_stats['strategy']) > 1: + # Print Strategy summary table + + table = text_table_strategy(backtest_stats['strategy_comparison'], stake_currency) + print(' STRATEGY SUMMARY '.center(len(table.splitlines()[0]), '=')) + print(table) + print('=' * len(table.splitlines()[0])) + print('\nFor more details, please look at the detail tables above') From b898f86364787b3c1ba0686281a96254eb213579 Mon Sep 17 00:00:00 2001 From: theluxaz Date: Wed, 13 Oct 2021 00:02:28 +0300 Subject: [PATCH 02/64] 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 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 = "Performance:\n" + for i, trade in enumerate(trades): + stat_line = ( + f"{i+1}.\t {trade['buy_tag']}\t" + f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " + f"({trade['profit']:.2f}%) " + f"({trade['count']})\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 = "Performance:\n" + for i, trade in enumerate(trades): + stat_line = ( + f"{i+1}.\t {trade['sell_tag']}\t" + f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " + f"({trade['profit']:.2f}%) " + f"({trade['count']})\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 = "Performance:\n" + for i, trade in enumerate(trades): + stat_line = ( + f"{i+1}.\t {trade['mix_tag']}\t" + f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " + f"({trade['profit']:.2f}%) " + f"({trade['count']})\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 :* `Shows the buy_tag performance`\n" + "*/sells :* `Shows the sell reason performance`\n" + "*/mix_tag :* `Shows combined buy tag + sell reason performance`\n" "*/trades [limit]:* `Lists last closed trades (limited to 10 by default)`\n" "*/profit []:* `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) 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) 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) 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 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) 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) 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) 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) 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) 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['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) 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) 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) 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) 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) 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) 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) 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) 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) 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 Date: Wed, 13 Oct 2021 00:09:30 +0300 Subject: [PATCH 03/64] 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 Date: Wed, 13 Oct 2021 01:22:53 +0300 Subject: [PATCH 04/64] 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 Date: Wed, 13 Oct 2021 01:34:29 +0300 Subject: [PATCH 05/64] 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 06/64] 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 @@ -# ![freqtrade](https://raw.githubusercontent.com/freqtrade/freqtrade/develop/docs/assets/freqtrade_poweredby.svg) - -[![Freqtrade CI](https://github.com/freqtrade/freqtrade/workflows/Freqtrade%20CI/badge.svg)](https://github.com/freqtrade/freqtrade/actions/) -[![Coverage Status](https://coveralls.io/repos/github/freqtrade/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/freqtrade/freqtrade?branch=develop) -[![Documentation](https://readthedocs.org/projects/freqtrade/badge/)](https://www.freqtrade.io) -[![Maintainability](https://api.codeclimate.com/v1/badges/5737e6d668200b7518ff/maintainability)](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. - -![freqtrade](https://raw.githubusercontent.com/freqtrade/freqtrade/develop/docs/assets/freqtrade-screenshot.png) - -## 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 |[table]`: Lists all or specific open trades. -- `/profit []`: Lists cumulative profit from all finished trades, over the last n days. -- `/forcesell |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 `: 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 07/64] 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 Date: Wed, 13 Oct 2021 02:14:07 +0300 Subject: [PATCH 08/64] 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 Date: Thu, 14 Oct 2021 01:03:15 +0300 Subject: [PATCH 09/64] 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 Date: Thu, 14 Oct 2021 01:13:28 +0300 Subject: [PATCH 10/64] 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 +# ![freqtrade](https://raw.githubusercontent.com/freqtrade/freqtrade/develop/docs/assets/freqtrade_poweredby.svg) + +[![Freqtrade CI](https://github.com/freqtrade/freqtrade/workflows/Freqtrade%20CI/badge.svg)](https://github.com/freqtrade/freqtrade/actions/) +[![Coverage Status](https://coveralls.io/repos/github/freqtrade/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/freqtrade/freqtrade?branch=develop) +[![Documentation](https://readthedocs.org/projects/freqtrade/badge/)](https://www.freqtrade.io) +[![Maintainability](https://api.codeclimate.com/v1/badges/5737e6d668200b7518ff/maintainability)](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. + +![freqtrade](https://raw.githubusercontent.com/freqtrade/freqtrade/develop/docs/assets/freqtrade-screenshot.png) + +## 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 |[table]`: Lists all or specific open trades. +- `/profit []`: Lists cumulative profit from all finished trades, over the last n days. +- `/forcesell |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 `: 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) 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) 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) 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 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) 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) 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) 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) 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) 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['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) 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) 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) 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) 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) 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) 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) 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) 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) 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 Date: Thu, 14 Oct 2021 01:15:43 +0300 Subject: [PATCH 11/64] 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 Date: Thu, 14 Oct 2021 01:18:16 +0300 Subject: [PATCH 12/64] 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 Date: Thu, 14 Oct 2021 01:34:30 +0300 Subject: [PATCH 13/64] 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 Date: Mon, 18 Oct 2021 23:56:41 +0300 Subject: [PATCH 14/64] 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 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 = "Performance:\n" + output = "Buy Tag Performance:\n" for i, trade in enumerate(trades): stat_line = ( f"{i+1}.\t {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 = "Performance:\n" + trades = self._rpc._rpc_exit_tag_performance(pair) + output = "Sell Tag Performance:\n" for i, trade in enumerate(trades): stat_line = ( - f"{i+1}.\t {trade['sell_tag']}\t" + f"{i+1}.\t {trade['exit_tag']}\t" f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " f"({trade['profit']:.2f}%) " f"({trade['count']})\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 = "Performance:\n" + output = "Mix Tag Performance:\n" for i, trade in enumerate(trades): + print(str(trade)) stat_line = ( f"{i+1}.\t {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 Date: Wed, 20 Oct 2021 01:26:15 +0300 Subject: [PATCH 15/64] 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 = "Sell Tag Performance:\n" + trades = self._rpc._rpc_sell_reason_performance(pair) + output = "Sell Reason Performance:\n" for i, trade in enumerate(trades): stat_line = ( - f"{i+1}.\t {trade['exit_tag']}\t" + f"{i+1}.\t {trade['sell_reason']}\t" f"{round_coin_value(trade['profit_abs'], self._config['stake_currency'])} " f"({trade['profit']:.2f}%) " f"({trade['count']})\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 Date: Wed, 20 Oct 2021 17:58:50 +0300 Subject: [PATCH 16/64] 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 = "Mix Tag Performance:\n" for i, trade in enumerate(trades): - print(str(trade)) stat_line = ( f"{i+1}.\t {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 Date: Wed, 20 Oct 2021 19:13:34 +0200 Subject: [PATCH 17/64] 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 Date: Thu, 21 Oct 2021 17:25:38 +0300 Subject: [PATCH 18/64] 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 = ('
  current    max    total stake\n---------  -----  -------------\n'
            '        1      {}          {}
').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 Date: Sun, 24 Oct 2021 09:15:05 +0200 Subject: [PATCH 19/64] 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 Date: Sun, 24 Oct 2021 15:18:29 +0200 Subject: [PATCH 20/64] 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 7ff16997e9b440d68d3e1c57a60cc31c7591d5ac Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 24 Oct 2021 23:27:08 -0600 Subject: [PATCH 21/64] Wrote echo block method for setup script --- setup.sh | 50 +++++++++++++++++--------------------------------- 1 file changed, 17 insertions(+), 33 deletions(-) diff --git a/setup.sh b/setup.sh index 1173b59b9..3af358468 100755 --- a/setup.sh +++ b/setup.sh @@ -1,12 +1,16 @@ #!/usr/bin/env bash #encoding=utf8 +function echo_block() { + echo "----------------------------" + echo $1 + echo "----------------------------" +} + function check_installed_pip() { ${PYTHON} -m pip > /dev/null if [ $? -ne 0 ]; then - echo "-----------------------------" - echo "Installing Pip for ${PYTHON}" - echo "-----------------------------" + echo_block "Installing Pip for ${PYTHON}" curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py ${PYTHON} get-pip.py rm get-pip.py @@ -37,9 +41,7 @@ function check_installed_python() { } function updateenv() { - echo "-------------------------" - echo "Updating your virtual env" - echo "-------------------------" + echo_block "Updating your virtual env" if [ ! -f .env/bin/activate ]; then echo "Something went wrong, no virtual environment found." exit 1 @@ -110,18 +112,14 @@ function install_mac_newer_python_dependencies() { if [ ! $(brew --prefix --installed hdf5 2>/dev/null) ] then - echo "-------------------------" - echo "Installing hdf5" - echo "-------------------------" + echo_block "Installing hdf5" brew install hdf5 fi export HDF5_DIR=$(brew --prefix) if [ ! $(brew --prefix --installed c-blosc 2>/dev/null) ] then - echo "-------------------------" - echo "Installing c-blosc" - echo "-------------------------" + echo_block "Installing c-blosc" brew install c-blosc fi export CBLOSC_DIR=$(brew --prefix) @@ -131,9 +129,7 @@ function install_mac_newer_python_dependencies() { function install_macos() { if [ ! -x "$(command -v brew)" ] then - echo "-------------------------" - echo "Installing Brew" - echo "-------------------------" + echo_block "Installing Brew" /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" fi #Gets number after decimal in python version @@ -160,9 +156,7 @@ function update() { # Reset Develop or Stable branch function reset() { - echo "----------------------------" - echo "Resetting branch and virtual env" - echo "----------------------------" + echo_block "Resetting branch and virtual env" if [ "1" == $(git branch -vv |grep -cE "\* develop|\* stable") ] then @@ -200,16 +194,12 @@ function reset() { } function config() { - - echo "-------------------------" - echo "Please use 'freqtrade new-config -c config.json' to generate a new configuration file." - echo "-------------------------" + echo_block "Please use 'freqtrade new-config -c config.json' to generate a new configuration file." } function install() { - echo "-------------------------" - echo "Installing mandatory dependencies" - echo "-------------------------" + + echo_block "Installing mandatory dependencies" if [ "$(uname -s)" == "Darwin" ] then @@ -228,20 +218,14 @@ function install() { echo reset config - echo "-------------------------" - echo "Run the bot !" - echo "-------------------------" + echo_block "Run the bot !" echo "You can now use the bot by executing 'source .env/bin/activate; freqtrade '." echo "You can see the list of available bot sub-commands by executing 'source .env/bin/activate; freqtrade --help'." echo "You verify that freqtrade is installed successfully by running 'source .env/bin/activate; freqtrade --version'." } function plot() { - echo " - ----------------------------------------- - Installing dependencies for Plotting scripts - ----------------------------------------- - " + echo_block "Installing dependencies for Plotting scripts" ${PYTHON} -m pip install plotly --upgrade } From d1e2a53267bdd0a6b2d1a2d7bae72734ed80ce01 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sun, 24 Oct 2021 23:33:02 -0600 Subject: [PATCH 22/64] Added centOS support to setup.sh script --- setup.sh | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/setup.sh b/setup.sh index 3af358468..1d76c6d2a 100755 --- a/setup.sh +++ b/setup.sh @@ -144,7 +144,14 @@ function install_macos() { # Install bot Debian_ubuntu function install_debian() { sudo apt-get update - sudo apt-get install -y build-essential autoconf libtool pkg-config make wget git $(echo lib${PYTHON}-dev ${PYTHON}-venv) + sudo apt-get install -y gcc build-essential autoconf libtool pkg-config make wget git $(echo lib${PYTHON}-dev ${PYTHON}-venv) + install_talib +} + +# Install bot RedHat_CentOS +function install_redhat() { + sudo yum update + sudo yum install -y gcc gcc-c++ build-essential autoconf libtool pkg-config make wget git $(echo ${PYTHON}-devel | sed 's/\.//g') install_talib } @@ -201,17 +208,18 @@ function install() { echo_block "Installing mandatory dependencies" - if [ "$(uname -s)" == "Darwin" ] - then + if [ "$(uname -s)" == "Darwin" ]; then echo "macOS detected. Setup for this system in-progress" install_macos - elif [ -x "$(command -v apt-get)" ] - then + elif [ -x "$(command -v apt-get)" ]; then echo "Debian/Ubuntu detected. Setup for this system in-progress" install_debian + elif [ -x "$(command -v yum)" ]; then + echo "Red Hat/CentOS detected. Setup for this system in-progress" + install_redhat else echo "This script does not support your OS." - echo "If you have Python3.6 or Python3.7, pip, virtualenv, ta-lib you can continue." + echo "If you have Python version 3.6 - 3.9, pip, virtualenv, ta-lib you can continue." echo "Wait 10 seconds to continue the next install steps or use ctrl+c to interrupt this shell." sleep 10 fi From b51f946ee07d579a09f0b0368412454fc6e92ef7 Mon Sep 17 00:00:00 2001 From: theluxaz Date: Mon, 25 Oct 2021 23:43:22 +0300 Subject: [PATCH 23/64] 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 'ETH/BTC\t0.00006217 BTC (6.20%) (1)' 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 'ETH/BTC\t0.00006217 BTC (6.20%) (1)' 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 'ETH/BTC\t0.00006217 BTC (6.20%) (1)' 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 'ETH/BTC\t0.00006217 BTC (6.20%) (1)' 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 Date: Wed, 27 Oct 2021 01:29:19 +0300 Subject: [PATCH 24/64] 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 Date: Wed, 27 Oct 2021 01:35:47 +0300 Subject: [PATCH 25/64] 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 Date: Thu, 28 Oct 2021 21:39:42 +0300 Subject: [PATCH 26/64] 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 'ETH/BTC\t0.00006217 BTC (6.20%) (1)' 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 'ETH/BTC\t0.00006217 BTC (6.20%) (1)' 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 'TESTBUY\t0.00006217 BTC (6.20%) (1)' 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 'ETH/BTC\t0.00006217 BTC (6.20%) (1)' 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 'TESTSELL\t0.00006217 BTC (6.20%) (1)' 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 'ETH/BTC\t0.00006217 BTC (6.20%) (1)' in msg_mock.call_args_list[0][0][0] + assert 'Mix Tag Performance' in msg_mock.call_args_list[0][0][0] + assert 'TESTBUY TESTSELL\t0.00006217 BTC (6.20%) (1)' 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 Date: Fri, 29 Oct 2021 07:04:20 +0200 Subject: [PATCH 27/64] 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 'TESTBUY TESTSELL\t0.00006217 BTC (6.20%) (1)' in msg_mock.call_args_list[0][0][0] + assert ('TESTBUY TESTSELL\t0.00006217 BTC (6.20%) (1)' + in msg_mock.call_args_list[0][0][0]) def test_count_handle(default_conf, update, ticker, fee, mocker) -> None: From 0f3809345a737ab3e8279cd02ba78fd244472395 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Oct 2021 10:28:12 +0200 Subject: [PATCH 28/64] Remove backtest-path parameter --- freqtrade/commands/__init__.py | 6 +++--- freqtrade/commands/arguments.py | 3 +-- freqtrade/commands/cli_options.py | 5 ----- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/freqtrade/commands/__init__.py b/freqtrade/commands/__init__.py index 323d85825..89b6fdc3a 100644 --- a/freqtrade/commands/__init__.py +++ b/freqtrade/commands/__init__.py @@ -13,9 +13,9 @@ from freqtrade.commands.data_commands import (start_convert_data, start_convert_ from freqtrade.commands.deploy_commands import (start_create_userdir, start_install_ui, start_new_strategy) from freqtrade.commands.hyperopt_commands import start_hyperopt_list, start_hyperopt_show -from freqtrade.commands.list_commands import (start_list_exchanges, start_list_hyperopts, - start_list_markets, start_list_strategies, - start_list_timeframes, start_show_trades) +from freqtrade.commands.list_commands import (start_list_exchanges, start_list_markets, + start_list_strategies, start_list_timeframes, + start_show_trades) from freqtrade.commands.optimize_commands import (start_backtest_filter, start_backtesting, start_edge, start_hyperopt) from freqtrade.commands.pairlist_commands import start_test_pairlist diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 080c5a9f6..df4c35cc0 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -2,7 +2,6 @@ This module contains the argument manager class """ import argparse -from freqtrade.commands.optimize_commands import start_backtest_filter from functools import partial from pathlib import Path from typing import Any, Dict, List, Optional @@ -42,7 +41,7 @@ ARGS_LIST_STRATEGIES = ["strategy_path", "print_one_column", "print_colorized"] ARGS_LIST_HYPEROPTS = ["hyperopt_path", "print_one_column", "print_colorized"] -ARGS_BACKTEST_FILTER = ["backtest_path"] +ARGS_BACKTEST_FILTER = [] ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all"] diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 60c888756..8d9b28c40 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -218,11 +218,6 @@ AVAILABLE_CLI_OPTIONS = { help='Specify additional lookup path for Hyperopt Loss functions.', metavar='PATH', ), - "backtest_path": Arg( - '--backtest-path', - help='Specify lookup file path for backtest filter.', - metavar='PATH', - ), "epochs": Arg( '-e', '--epochs', help='Specify number of epochs (default: %(default)d).', From f47270943817d5ad3a863256d953289c59485eb7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Oct 2021 10:50:40 +0200 Subject: [PATCH 29/64] Add option to show sorted pairlist Allows easy copy/pasting of the pairlist to a configuration --- freqtrade/commands/arguments.py | 21 +++++++++---------- freqtrade/commands/cli_options.py | 6 ++++++ freqtrade/commands/optimize_commands.py | 26 ++++++++---------------- freqtrade/configuration/configuration.py | 4 ++++ freqtrade/optimize/optimize_reports.py | 21 ++++++++----------- 5 files changed, 36 insertions(+), 42 deletions(-) diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index df4c35cc0..8f68e6521 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -41,7 +41,7 @@ ARGS_LIST_STRATEGIES = ["strategy_path", "print_one_column", "print_colorized"] ARGS_LIST_HYPEROPTS = ["hyperopt_path", "print_one_column", "print_colorized"] -ARGS_BACKTEST_FILTER = [] +ARGS_BACKTEST_FILTER = ["exportfilename", "backtest_show_pair_list"] ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all"] @@ -175,16 +175,15 @@ class Arguments: self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot') self._build_args(optionlist=['version'], parser=self.parser) - from freqtrade.commands import ( - start_backtesting, start_backtest_filter, start_convert_data, start_convert_trades, - start_create_userdir, start_download_data, start_edge, - start_hyperopt, start_hyperopt_list, start_hyperopt_show, - start_install_ui, start_list_data, start_list_exchanges, - start_list_markets, start_list_strategies, - start_list_timeframes, start_new_config, start_new_strategy, - start_plot_dataframe, start_plot_profit, start_show_trades, - start_test_pairlist, start_trading, start_webserver - ) + from freqtrade.commands import (start_backtest_filter, start_backtesting, + start_convert_data, start_convert_trades, + start_create_userdir, start_download_data, start_edge, + start_hyperopt, start_hyperopt_list, start_hyperopt_show, + start_install_ui, start_list_data, start_list_exchanges, + start_list_markets, start_list_strategies, + start_list_timeframes, start_new_config, start_new_strategy, + start_plot_dataframe, start_plot_profit, start_show_trades, + start_test_pairlist, start_trading, start_webserver) subparsers = self.parser.add_subparsers(dest='command', # Use custom message when no subhandler is added diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 8d9b28c40..6aa4ed363 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -152,6 +152,12 @@ AVAILABLE_CLI_OPTIONS = { action='store_false', default=True, ), + "backtest_show_pair_list": Arg( + '--show-pair-list', + help='Show backtesting pairlist sorted by profit.', + action='store_true', + default=False, + ), "enable_protections": Arg( '--enable-protections', '--enableprotections', help='Enable protections for backtesting.' diff --git a/freqtrade/commands/optimize_commands.py b/freqtrade/commands/optimize_commands.py index 75392d897..0d1f304db 100644 --- a/freqtrade/commands/optimize_commands.py +++ b/freqtrade/commands/optimize_commands.py @@ -1,15 +1,13 @@ -from freqtrade.data.btanalysis import get_latest_backtest_filename -import pandas -from pandas.io import json -from freqtrade.optimize import backtesting import logging from typing import Any, Dict from freqtrade import constants from freqtrade.configuration import setup_utils_configuration +from freqtrade.data.btanalysis import load_backtest_stats from freqtrade.enums import RunMode from freqtrade.exceptions import OperationalException from freqtrade.misc import round_coin_value +from freqtrade.optimize.optimize_reports import show_backtest_results, show_filtered_pairlist logger = logging.getLogger(__name__) @@ -57,27 +55,19 @@ def start_backtesting(args: Dict[str, Any]) -> None: backtesting = Backtesting(config) backtesting.start() + def start_backtest_filter(args: Dict[str, Any]) -> None: """ - List backtest pairs previously filtered + Show previous backtest result """ config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) - no_header = config.get('backtest_show_pair_list', False) - results_file = get_latest_backtest_filename( - config['user_data_dir'] / 'backtest_results/') - - logger.info("Using Backtesting result {results_file}") - - # load data using Python JSON module - with open(config['user_data_dir'] / 'backtest_results/' / results_file,'r') as f: - data = json.loads(f.read()) - strategy = list(data["strategy"])[0] - trades = data["strategy"][strategy] - - print(trades) + results = load_backtest_stats(config['exportfilename']) + # print(results) + show_backtest_results(config, results) + show_filtered_pairlist(config, results) logger.info("Backtest filtering complete. ") diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 822577916..f5a674878 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -245,6 +245,10 @@ class Configuration: self._args_to_config(config, argname='timeframe_detail', logstring='Parameter --timeframe-detail detected, ' 'using {} for intra-candle backtesting ...') + + self._args_to_config(config, argname='backtest_show_pair_list', + logstring='Parameter --show-pair-list detected.') + self._args_to_config(config, argname='stake_amount', logstring='Parameter --stake-amount detected, ' 'overriding stake_amount to: {} ...') diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 712cce028..5adad39c1 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -736,17 +736,12 @@ def show_backtest_results(config: Dict, backtest_stats: Dict): print('=' * len(table.splitlines()[0])) print('\nFor more details, please look at the detail tables above') -def show_backtest_results_filtered(config: Dict, backtest_stats: Dict): - stake_currency = config['stake_currency'] - for strategy, results in backtest_stats['strategy'].items(): - show_backtest_result(strategy, results, stake_currency) - - if len(backtest_stats['strategy']) > 1: - # Print Strategy summary table - - table = text_table_strategy(backtest_stats['strategy_comparison'], stake_currency) - print(' STRATEGY SUMMARY '.center(len(table.splitlines()[0]), '=')) - print(table) - print('=' * len(table.splitlines()[0])) - print('\nFor more details, please look at the detail tables above') +def show_filtered_pairlist(config: Dict, backtest_stats: Dict): + if config.get('backtest_show_pair_list', False): + for strategy, results in backtest_stats['strategy'].items(): + print("Pairs for Strategy: \n[") + for result in results['results_per_pair']: + if result["key"] != 'TOTAL': + print(f'"{result["key"]}", // {round(result["profit_mean_pct"], 2)}%') + print("]") From 851062ca46463139276544dbb9b8289ec705af86 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Oct 2021 10:53:18 +0200 Subject: [PATCH 30/64] Rename backtest-filter to backtest_show --- freqtrade/commands/__init__.py | 2 +- freqtrade/commands/arguments.py | 16 ++++++++-------- freqtrade/commands/optimize_commands.py | 4 +--- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/freqtrade/commands/__init__.py b/freqtrade/commands/__init__.py index 89b6fdc3a..ba977b6bd 100644 --- a/freqtrade/commands/__init__.py +++ b/freqtrade/commands/__init__.py @@ -16,7 +16,7 @@ from freqtrade.commands.hyperopt_commands import start_hyperopt_list, start_hype from freqtrade.commands.list_commands import (start_list_exchanges, start_list_markets, start_list_strategies, start_list_timeframes, start_show_trades) -from freqtrade.commands.optimize_commands import (start_backtest_filter, start_backtesting, +from freqtrade.commands.optimize_commands import (start_backtest_show, start_backtesting, start_edge, start_hyperopt) from freqtrade.commands.pairlist_commands import start_test_pairlist from freqtrade.commands.plot_commands import start_plot_dataframe, start_plot_profit diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 8f68e6521..be4f9188f 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -41,7 +41,7 @@ ARGS_LIST_STRATEGIES = ["strategy_path", "print_one_column", "print_colorized"] ARGS_LIST_HYPEROPTS = ["hyperopt_path", "print_one_column", "print_colorized"] -ARGS_BACKTEST_FILTER = ["exportfilename", "backtest_show_pair_list"] +ARGS_BACKTEST_SHOW = ["exportfilename", "backtest_show_pair_list"] ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all"] @@ -175,7 +175,7 @@ class Arguments: self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot') self._build_args(optionlist=['version'], parser=self.parser) - from freqtrade.commands import (start_backtest_filter, start_backtesting, + from freqtrade.commands import (start_backtest_show, start_backtesting, start_convert_data, start_convert_trades, start_create_userdir, start_download_data, start_edge, start_hyperopt, start_hyperopt_list, start_hyperopt_show, @@ -267,14 +267,14 @@ class Arguments: backtesting_cmd.set_defaults(func=start_backtesting) self._build_args(optionlist=ARGS_BACKTEST, parser=backtesting_cmd) - # Add backtest-filter subcommand - backtest_filter_cmd = subparsers.add_parser( - 'backtest-filter', - help='Filter Backtest results', + # Add backtest-show subcommand + backtest_show_cmd = subparsers.add_parser( + 'backtest-show', + help='Show past Backtest results', parents=[_common_parser], ) - backtest_filter_cmd.set_defaults(func=start_backtest_filter) - self._build_args(optionlist=ARGS_BACKTEST_FILTER, parser=backtest_filter_cmd) + backtest_show_cmd.set_defaults(func=start_backtest_show) + self._build_args(optionlist=ARGS_BACKTEST_SHOW, parser=backtest_show_cmd) # Add edge subcommand edge_cmd = subparsers.add_parser('edge', help='Edge module.', diff --git a/freqtrade/commands/optimize_commands.py b/freqtrade/commands/optimize_commands.py index 0d1f304db..c35e78153 100644 --- a/freqtrade/commands/optimize_commands.py +++ b/freqtrade/commands/optimize_commands.py @@ -56,7 +56,7 @@ def start_backtesting(args: Dict[str, Any]) -> None: backtesting.start() -def start_backtest_filter(args: Dict[str, Any]) -> None: +def start_backtest_show(args: Dict[str, Any]) -> None: """ Show previous backtest result """ @@ -69,8 +69,6 @@ def start_backtest_filter(args: Dict[str, Any]) -> None: show_backtest_results(config, results) show_filtered_pairlist(config, results) - logger.info("Backtest filtering complete. ") - def start_hyperopt(args: Dict[str, Any]) -> None: """ From 6cf140f8fb95c70a35cb2dfc56fe2fdd55ac7d8e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Oct 2021 15:58:14 +0200 Subject: [PATCH 31/64] FIx testcases --- tests/optimize/test_backtest_detail.py | 60 +++++++++++++------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index e5c037f3e..f64855baf 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -17,10 +17,10 @@ tc0 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) - [2, 4987, 5012, 4986, 4600, 6172, 0, 0], # exit with stoploss hit + [2, 4987, 5012, 4986, 4986, 6172, 0, 0], # exit with stoploss hit [3, 5010, 5000, 4980, 5010, 6172, 0, 1], [4, 5010, 4987, 4977, 4995, 6172, 0, 0], - [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], + [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True, trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)] ) @@ -32,9 +32,9 @@ tc1 = BTContainer(data=[ [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5012, 4600, 4600, 6172, 0, 0], # exit with stoploss hit - [3, 4975, 5000, 4980, 4977, 6172, 0, 0], + [3, 4975, 5000, 4977, 4977, 6172, 0, 0], [4, 4977, 4987, 4977, 4995, 6172, 0, 0], - [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], + [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)] ) @@ -69,7 +69,7 @@ tc3 = BTContainer(data=[ [3, 4975, 5000, 4950, 4962, 6172, 1, 0], [4, 4975, 5000, 4950, 4962, 6172, 0, 0], # enter trade 2 (signal on last candle) [5, 4962, 4987, 4000, 4000, 6172, 0, 0], # exit with stoploss hit - [6, 4950, 4975, 4975, 4950, 6172, 0, 0]], + [6, 4950, 4975, 4950, 4950, 6172, 0, 0]], stop_loss=-0.02, roi={"0": 1}, profit_perc=-0.04, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2), BTrade(sell_reason=SellType.STOP_LOSS, open_tick=4, close_tick=5)] @@ -99,7 +99,7 @@ tc5 = BTContainer(data=[ [1, 5000, 5025, 4980, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5025, 4975, 4987, 6172, 0, 0], [3, 4975, 6000, 4975, 6000, 6172, 0, 0], # ROI - [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [4, 4962, 4987, 4972, 4972, 6172, 0, 0], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 0.03}, profit_perc=0.03, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] @@ -113,7 +113,7 @@ tc6 = BTContainer(data=[ [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5300, 4850, 5050, 6172, 0, 0], # Exit with stoploss [3, 4975, 5000, 4950, 4962, 6172, 0, 0], - [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [4, 4962, 4987, 4950, 4950, 6172, 0, 0], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.02, roi={"0": 0.05}, profit_perc=-0.02, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)] @@ -127,7 +127,7 @@ tc7 = BTContainer(data=[ [1, 5000, 5025, 4975, 4987, 6172, 0, 0], [2, 4987, 5300, 4950, 5050, 6172, 0, 0], [3, 4975, 5000, 4950, 4962, 6172, 0, 0], - [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [4, 4962, 4987, 4950, 4950, 6172, 0, 0], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.02, roi={"0": 0.03}, profit_perc=0.03, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)] @@ -216,8 +216,8 @@ tc13 = BTContainer(data=[ [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5100, 4950, 5100, 6172, 0, 0], [2, 5100, 5251, 4850, 5100, 6172, 0, 0], - [3, 4850, 5050, 4850, 4750, 6172, 0, 0], - [4, 4750, 4950, 4850, 4750, 6172, 0, 0]], + [3, 4850, 5050, 4750, 4750, 6172, 0, 0], + [4, 4750, 4950, 4750, 4750, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.01}, profit_perc=0.01, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1)] ) @@ -229,7 +229,7 @@ tc14 = BTContainer(data=[ [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5100, 4600, 5100, 6172, 0, 0], [2, 5100, 5251, 4850, 5100, 6172, 0, 0], - [3, 4850, 5050, 4850, 4750, 6172, 0, 0], + [3, 4850, 5050, 4750, 4750, 6172, 0, 0], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.05, roi={"0": 0.10}, profit_perc=-0.05, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)] @@ -243,7 +243,7 @@ tc15 = BTContainer(data=[ [0, 5000, 5050, 4950, 5000, 6172, 1, 0], [1, 5000, 5100, 4900, 5100, 6172, 1, 0], [2, 5100, 5251, 4650, 5100, 6172, 0, 0], - [3, 4850, 5050, 4850, 4750, 6172, 0, 0], + [3, 4850, 5050, 4750, 4750, 6172, 0, 0], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], stop_loss=-0.05, roi={"0": 0.01}, profit_perc=-0.04, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=1), @@ -259,7 +259,7 @@ tc16 = BTContainer(data=[ [1, 5000, 5025, 4975, 4987, 6172, 0, 0], [2, 4987, 5300, 4950, 5050, 6172, 0, 0], [3, 4975, 5000, 4940, 4962, 6172, 0, 0], # ForceSell on ROI (roi=-1) - [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [4, 4962, 4987, 4950, 4950, 6172, 0, 0], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10, "65": -1}, profit_perc=-0.012, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] @@ -275,7 +275,7 @@ tc17 = BTContainer(data=[ [1, 5000, 5025, 4975, 4987, 6172, 0, 0], [2, 4987, 5300, 4950, 5050, 6172, 0, 0], [3, 4980, 5000, 4940, 4962, 6172, 0, 0], # ForceSell on ROI (roi=-1) - [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [4, 4962, 4987, 4950, 4950, 6172, 0, 0], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10, "120": -1}, profit_perc=-0.004, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] @@ -291,7 +291,7 @@ tc18 = BTContainer(data=[ [1, 5000, 5025, 4975, 4987, 6172, 0, 0], [2, 4987, 5300, 4950, 5200, 6172, 0, 0], [3, 5200, 5220, 4940, 4962, 6172, 0, 0], # Sell on ROI (sells on open) - [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [4, 4962, 4987, 4950, 4950, 6172, 0, 0], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.04, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] @@ -306,7 +306,7 @@ tc19 = BTContainer(data=[ [1, 5000, 5025, 4975, 4987, 6172, 0, 0], [2, 4987, 5300, 4950, 5200, 6172, 0, 0], [3, 5000, 5300, 4940, 4962, 6172, 0, 0], # Sell on ROI - [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [4, 4962, 4987, 4950, 4950, 6172, 0, 0], [5, 4550, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.01, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] @@ -321,7 +321,7 @@ tc20 = BTContainer(data=[ [1, 5000, 5025, 4975, 4987, 6172, 0, 0], [2, 4987, 5300, 4950, 5200, 6172, 0, 0], [3, 5200, 5300, 4940, 4962, 6172, 0, 0], # Sell on ROI - [4, 4962, 4987, 4972, 4950, 6172, 0, 0], + [4, 4962, 4987, 4950, 4950, 6172, 0, 0], [5, 4550, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10, "119": 0.01}, profit_perc=0.01, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] @@ -386,10 +386,10 @@ tc24 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) - [2, 4987, 5012, 4986, 4600, 6172, 0, 0], + [2, 4987, 5012, 4986, 4986, 6172, 0, 0], [3, 5010, 5000, 4855, 5010, 6172, 0, 1], # Triggers stoploss + sellsignal [4, 5010, 4987, 4977, 4995, 6172, 0, 0], - [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], + [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, use_sell_signal=True, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)] ) @@ -401,10 +401,10 @@ tc25 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) - [2, 4987, 5012, 4986, 4600, 6172, 0, 0], + [2, 4987, 5012, 4986, 4986, 6172, 0, 0], [3, 5010, 5000, 4986, 5010, 6172, 0, 1], [4, 5010, 4987, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on - [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], + [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True, trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)] ) @@ -416,10 +416,10 @@ tc26 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) - [2, 4987, 5012, 4986, 4600, 6172, 0, 0], + [2, 4987, 5012, 4986, 4986, 6172, 0, 0], [3, 5010, 5251, 4986, 5010, 6172, 0, 1], # Triggers ROI, sell-signal [4, 5010, 4987, 4855, 4995, 6172, 0, 0], - [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], + [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_sell_signal=True, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) @@ -432,10 +432,10 @@ tc27 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) - [2, 4987, 5012, 4986, 4600, 6172, 0, 0], + [2, 4987, 5012, 4986, 4986, 6172, 0, 0], [3, 5010, 5012, 4986, 5010, 6172, 0, 1], # sell-signal [4, 5010, 5251, 4855, 4995, 6172, 0, 0], # Triggers ROI, sell-signal acted on - [5, 4995, 4995, 4995, 4950, 6172, 0, 0]], + [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_sell_signal=True, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=4)] ) @@ -463,7 +463,7 @@ tc28 = BTContainer(data=[ tc29 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5050, 5000, 4900, 6172, 0, 0], # enter trade (signal on last candle) + [1, 5000, 5050, 5000, 5000, 6172, 0, 0], # enter trade (signal on last candle) [2, 4900, 5250, 4500, 5100, 6172, 0, 0], # Triggers trailing-stoploss [3, 5100, 5100, 4650, 4750, 6172, 0, 0], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], @@ -477,7 +477,7 @@ tc29 = BTContainer(data=[ tc30 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5500, 5000, 4900, 6172, 0, 0], # enter trade (signal on last candle) and stop + [1, 5000, 5500, 4900, 4900, 6172, 0, 0], # enter trade (signal on last candle) and stop [2, 4900, 5250, 4500, 5100, 6172, 0, 0], [3, 5100, 5100, 4650, 4750, 6172, 0, 0], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], @@ -491,7 +491,7 @@ tc30 = BTContainer(data=[ tc31 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5500, 5000, 4900, 6172, 0, 0], # enter trade (signal on last candle) and stop + [1, 5000, 5500, 4900, 4900, 6172, 0, 0], # enter trade (signal on last candle) and stop [2, 4900, 5250, 4500, 5100, 6172, 0, 0], [3, 5100, 5100, 4650, 4750, 6172, 0, 0], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], @@ -506,7 +506,7 @@ tc31 = BTContainer(data=[ tc32 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5500, 5000, 4900, 6172, 0, 0], # enter trade (signal on last candle) and stop + [1, 5000, 5500, 4951, 5000, 6172, 0, 0], # enter trade (signal on last candle) and stop [2, 4900, 5250, 4500, 5100, 6172, 0, 0], [3, 5100, 5100, 4650, 4750, 6172, 0, 0], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], @@ -521,7 +521,7 @@ tc32 = BTContainer(data=[ tc33 = BTContainer(data=[ # D O H L C V B S BT [0, 5000, 5050, 4950, 5000, 6172, 1, 0, 'buy_signal_01'], - [1, 5000, 5500, 5000, 4900, 6172, 0, 0, None], # enter trade (signal on last candle) and stop + [1, 5000, 5500, 4951, 5000, 6172, 0, 0, None], # enter trade (signal on last candle) and stop [2, 4900, 5250, 4500, 5100, 6172, 0, 0, None], [3, 5100, 5100, 4650, 4750, 6172, 0, 0, None], [4, 4750, 4950, 4350, 4750, 6172, 0, 0, None]], From 459a2239ce4837fedab427bbc492cb0352381613 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Oct 2021 16:10:28 +0200 Subject: [PATCH 32/64] Fix candle ranges in backtesting test --- tests/optimize/__init__.py | 6 ++++ tests/optimize/test_backtest_detail.py | 38 +++++++++++++------------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/tests/optimize/__init__.py b/tests/optimize/__init__.py index 6ad2d300b..8a2be39a1 100644 --- a/tests/optimize/__init__.py +++ b/tests/optimize/__init__.py @@ -54,4 +54,10 @@ def _build_backtest_dataframe(data): frame[column] = frame[column].astype('float64') if 'buy_tag' not in columns: frame['buy_tag'] = None + + # Ensure all candles make kindof sense + assert all(frame['low'] <= frame['close']) + assert all(frame['low'] <= frame['open']) + assert all(frame['high'] >= frame['close']) + assert all(frame['high'] >= frame['open']) return frame diff --git a/tests/optimize/test_backtest_detail.py b/tests/optimize/test_backtest_detail.py index f64855baf..775f15b87 100644 --- a/tests/optimize/test_backtest_detail.py +++ b/tests/optimize/test_backtest_detail.py @@ -18,8 +18,8 @@ tc0 = BTContainer(data=[ [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5012, 4986, 4986, 6172, 0, 0], # exit with stoploss hit - [3, 5010, 5000, 4980, 5010, 6172, 0, 1], - [4, 5010, 4987, 4977, 4995, 6172, 0, 0], + [3, 5010, 5010, 4980, 5010, 6172, 0, 1], + [4, 5010, 5011, 4977, 4995, 6172, 0, 0], [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True, trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)] @@ -32,8 +32,8 @@ tc1 = BTContainer(data=[ [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5012, 4600, 4600, 6172, 0, 0], # exit with stoploss hit - [3, 4975, 5000, 4977, 4977, 6172, 0, 0], - [4, 4977, 4987, 4977, 4995, 6172, 0, 0], + [3, 4975, 5000, 4975, 4977, 6172, 0, 0], + [4, 4977, 4995, 4977, 4995, 6172, 0, 0], [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)] @@ -99,7 +99,7 @@ tc5 = BTContainer(data=[ [1, 5000, 5025, 4980, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5025, 4975, 4987, 6172, 0, 0], [3, 4975, 6000, 4975, 6000, 6172, 0, 0], # ROI - [4, 4962, 4987, 4972, 4972, 6172, 0, 0], + [4, 4962, 4987, 4962, 4972, 6172, 0, 0], [5, 4950, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 0.03}, profit_perc=0.03, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] @@ -167,7 +167,7 @@ tc9 = BTContainer(data=[ tc10 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0], + [1, 5000, 5100, 4950, 5100, 6172, 0, 0], [2, 5100, 5251, 5100, 5100, 6172, 0, 0], [3, 4850, 5050, 4650, 4750, 6172, 0, 0], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], @@ -183,7 +183,7 @@ tc10 = BTContainer(data=[ tc11 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0], + [1, 5000, 5100, 4950, 5100, 6172, 0, 0], [2, 5100, 5251, 5100, 5100, 6172, 0, 0], [3, 5000, 5150, 4650, 4750, 6172, 0, 0], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], @@ -199,7 +199,7 @@ tc11 = BTContainer(data=[ tc12 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0], + [1, 5000, 5100, 4950, 5100, 6172, 0, 0], [2, 5100, 5251, 4650, 5100, 6172, 0, 0], [3, 4850, 5050, 4650, 4750, 6172, 0, 0], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], @@ -307,7 +307,7 @@ tc19 = BTContainer(data=[ [2, 4987, 5300, 4950, 5200, 6172, 0, 0], [3, 5000, 5300, 4940, 4962, 6172, 0, 0], # Sell on ROI [4, 4962, 4987, 4950, 4950, 6172, 0, 0], - [5, 4550, 4975, 4925, 4950, 6172, 0, 0]], + [5, 4550, 4975, 4550, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10, "120": 0.01}, profit_perc=0.01, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) @@ -322,7 +322,7 @@ tc20 = BTContainer(data=[ [2, 4987, 5300, 4950, 5200, 6172, 0, 0], [3, 5200, 5300, 4940, 4962, 6172, 0, 0], # Sell on ROI [4, 4962, 4987, 4950, 4950, 6172, 0, 0], - [5, 4550, 4975, 4925, 4950, 6172, 0, 0]], + [5, 4925, 4975, 4925, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.10, "119": 0.01}, profit_perc=0.01, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] ) @@ -334,7 +334,7 @@ tc20 = BTContainer(data=[ tc21 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0], + [1, 5000, 5100, 4950, 5100, 6172, 0, 0], [2, 5100, 5251, 4650, 5100, 6172, 0, 0], [3, 4850, 5050, 4650, 4750, 6172, 0, 0], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], @@ -350,7 +350,7 @@ tc21 = BTContainer(data=[ tc22 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0], + [1, 5000, 5100, 4950, 5100, 6172, 0, 0], [2, 5100, 5251, 5100, 5100, 6172, 0, 0], [3, 4850, 5050, 4650, 4750, 6172, 0, 0], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], @@ -369,7 +369,7 @@ tc22 = BTContainer(data=[ tc23 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0], + [1, 5000, 5100, 4950, 5100, 6172, 0, 0], [2, 5100, 5251, 5100, 5100, 6172, 0, 0], [3, 4850, 5251, 4650, 4750, 6172, 0, 0], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], @@ -387,8 +387,8 @@ tc24 = BTContainer(data=[ [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5012, 4986, 4986, 6172, 0, 0], - [3, 5010, 5000, 4855, 5010, 6172, 0, 1], # Triggers stoploss + sellsignal - [4, 5010, 4987, 4977, 4995, 6172, 0, 0], + [3, 5010, 5010, 4855, 5010, 6172, 0, 1], # Triggers stoploss + sellsignal + [4, 5010, 5010, 4977, 4995, 6172, 0, 0], [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 1}, profit_perc=-0.01, use_sell_signal=True, trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)] @@ -402,8 +402,8 @@ tc25 = BTContainer(data=[ [0, 5000, 5025, 4975, 4987, 6172, 1, 0], [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5012, 4986, 4986, 6172, 0, 0], - [3, 5010, 5000, 4986, 5010, 6172, 0, 1], - [4, 5010, 4987, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on + [3, 5010, 5010, 4986, 5010, 6172, 0, 1], + [4, 5010, 5010, 4855, 4995, 6172, 0, 0], # Triggers stoploss + sellsignal acted on [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], stop_loss=-0.01, roi={"0": 1}, profit_perc=0.002, use_sell_signal=True, trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)] @@ -418,7 +418,7 @@ tc26 = BTContainer(data=[ [1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle) [2, 4987, 5012, 4986, 4986, 6172, 0, 0], [3, 5010, 5251, 4986, 5010, 6172, 0, 1], # Triggers ROI, sell-signal - [4, 5010, 4987, 4855, 4995, 6172, 0, 0], + [4, 5010, 5010, 4855, 4995, 6172, 0, 0], [5, 4995, 4995, 4950, 4950, 6172, 0, 0]], stop_loss=-0.10, roi={"0": 0.05}, profit_perc=0.05, use_sell_signal=True, trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)] @@ -447,7 +447,7 @@ tc27 = BTContainer(data=[ tc28 = BTContainer(data=[ # D O H L C V B S [0, 5000, 5050, 4950, 5000, 6172, 1, 0], - [1, 5000, 5050, 4950, 5100, 6172, 0, 0], + [1, 5000, 5100, 4950, 5100, 6172, 0, 0], [2, 5100, 5251, 5100, 5100, 6172, 0, 0], [3, 4850, 5050, 4650, 4750, 6172, 0, 0], [4, 4750, 4950, 4350, 4750, 6172, 0, 0]], From d60001e886caf91344697af41554857186774cb1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Oct 2021 16:14:13 +0200 Subject: [PATCH 33/64] Stoploss cannot be below candle low fix #5816 --- freqtrade/optimize/backtesting.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 8328d61d3..82c072867 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -312,7 +312,9 @@ class Backtesting: # Worst case: price ticks tiny bit above open and dives down. stop_rate = sell_row[OPEN_IDX] * (1 - abs(trade.stop_loss_pct)) assert stop_rate < sell_row[HIGH_IDX] - return stop_rate + # Limit lower-end to candle low to avoid sells below the low. + # This still remains "worst case" - but "worst realistic case". + return max(sell_row[LOW_IDX], stop_rate) # Set close_rate to stoploss return trade.stop_loss From 650d6c276a3bd54686217fbcc48b7f34f58f13b8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Oct 2021 10:58:03 +0200 Subject: [PATCH 34/64] Add documentation --- docs/utils.md | 40 +++++++++++++++++++++++++++++++++ freqtrade/commands/arguments.py | 18 +++++++-------- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index a65ba5db4..6934e0a5c 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -577,6 +577,46 @@ Common arguments: ``` +## Show previous Backtest results + +Allows you to show previous backtest results. +Adding `--show-pair-list` outputs a sorted pair list you can easily copy/paste into your configuration (omitting bad pairs). + +??? Warning "Strategy overfitting" + Only using winning pairs can lead to an overfitted strategy, which will not work well on future data. Make sure to extensively test your strategy in dry-run before risking real money. + +``` +usage: freqtrade backtest-show [-h] [-v] [--logfile FILE] [-V] [-c PATH] + [-d PATH] [--userdir PATH] + [--export-filename PATH] [--show-pair-list] + +optional arguments: + -h, --help show this help message and exit + --export-filename PATH + Save backtest results to the file with this filename. + Requires `--export` to be set as well. Example: + `--export-filename=user_data/backtest_results/backtest + _today.json` + --show-pair-list Show backtesting pairlist sorted by profit. + +Common arguments: + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). + --logfile FILE Log to the file specified. Special values are: + 'syslog', 'journald'. See the documentation for more + details. + -V, --version show program's version number and exit + -c PATH, --config PATH + Specify configuration file (default: + `userdir/config.json` or `config.json` whichever + exists). Multiple --config options may be used. Can be + set to `-` to read config from stdin. + -d PATH, --datadir PATH + Path to directory with historical backtesting data. + --userdir PATH, --user-data-dir PATH + Path to userdata directory. + +``` + ## List Hyperopt results You can list the hyperoptimization epochs the Hyperopt module evaluated previously with the `hyperopt-list` sub-command. diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index be4f9188f..9d14bb38d 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -175,15 +175,15 @@ class Arguments: self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot') self._build_args(optionlist=['version'], parser=self.parser) - from freqtrade.commands import (start_backtest_show, start_backtesting, - start_convert_data, start_convert_trades, - start_create_userdir, start_download_data, start_edge, - start_hyperopt, start_hyperopt_list, start_hyperopt_show, - start_install_ui, start_list_data, start_list_exchanges, - start_list_markets, start_list_strategies, - start_list_timeframes, start_new_config, start_new_strategy, - start_plot_dataframe, start_plot_profit, start_show_trades, - start_test_pairlist, start_trading, start_webserver) + from freqtrade.commands import (start_backtest_show, start_backtesting, start_convert_data, + start_convert_trades, start_create_userdir, + start_download_data, start_edge, start_hyperopt, + start_hyperopt_list, start_hyperopt_show, start_install_ui, + start_list_data, start_list_exchanges, start_list_markets, + start_list_strategies, start_list_timeframes, + start_new_config, start_new_strategy, start_plot_dataframe, + start_plot_profit, start_show_trades, start_test_pairlist, + start_trading, start_webserver) subparsers = self.parser.add_subparsers(dest='command', # Use custom message when no subhandler is added From 72ecb45d868dbf2dbcb10a2a2597a795ae451436 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Oct 2021 16:53:48 +0200 Subject: [PATCH 35/64] Add test for backtest_show logic --- freqtrade/optimize/optimize_reports.py | 2 +- tests/optimize/test_optimize_reports.py | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index 5adad39c1..d6fcf8a04 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -740,7 +740,7 @@ def show_backtest_results(config: Dict, backtest_stats: Dict): def show_filtered_pairlist(config: Dict, backtest_stats: Dict): if config.get('backtest_show_pair_list', False): for strategy, results in backtest_stats['strategy'].items(): - print("Pairs for Strategy: \n[") + print(f"Pairs for Strategy {strategy}: \n[") for result in results['results_per_pair']: if result["key"] != 'TOTAL': print(f'"{result["key"]}", // {round(result["profit_mean_pct"], 2)}%') diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index b5eb09923..7f5f5e8a9 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -10,7 +10,7 @@ from arrow import Arrow from freqtrade.configuration import TimeRange from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN from freqtrade.data import history -from freqtrade.data.btanalysis import get_latest_backtest_filename, load_backtest_data +from freqtrade.data.btanalysis import get_latest_backtest_filename, load_backtest_data, load_backtest_stats from freqtrade.edge import PairInfo from freqtrade.enums import SellType from freqtrade.optimize.optimize_reports import (_get_resample_from_period, generate_backtest_stats, @@ -19,7 +19,7 @@ from freqtrade.optimize.optimize_reports import (_get_resample_from_period, gene generate_periodic_breakdown_stats, generate_sell_reason_stats, generate_strategy_comparison, - generate_trading_stats, store_backtest_stats, + generate_trading_stats, show_filtered_pairlist, store_backtest_stats, text_table_bt_results, text_table_sell_reason, text_table_strategy) from freqtrade.resolvers.strategy_resolver import StrategyResolver @@ -407,3 +407,16 @@ def test__get_resample_from_period(): assert _get_resample_from_period('month') == '1M' with pytest.raises(ValueError, match=r"Period noooo is not supported."): _get_resample_from_period('noooo') + + +def test_show_filtered_pairlist(testdatadir, default_conf, capsys): + filename = testdatadir / "backtest-result_new.json" + bt_data = load_backtest_stats(filename) + default_conf['backtest_show_pair_list'] = True + + show_filtered_pairlist(default_conf, bt_data) + + out, err = capsys.readouterr() + assert 'Pairs for Strategy StrategyTestV2: \n[' in out + assert 'TOTAL' not in out + assert '"ETH/BTC", // ' in out From 20904f1ca49d3d59d65eaf35b8699d1a33c025ee Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 30 Oct 2021 17:05:12 +0200 Subject: [PATCH 36/64] Add tests for new command --- freqtrade/commands/optimize_commands.py | 8 +++---- freqtrade/optimize/optimize_reports.py | 2 +- tests/commands/test_commands.py | 28 +++++++++++++++++++------ tests/optimize/test_optimize_reports.py | 13 ++++++------ 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/freqtrade/commands/optimize_commands.py b/freqtrade/commands/optimize_commands.py index c35e78153..4acc0d939 100644 --- a/freqtrade/commands/optimize_commands.py +++ b/freqtrade/commands/optimize_commands.py @@ -3,11 +3,9 @@ from typing import Any, Dict from freqtrade import constants from freqtrade.configuration import setup_utils_configuration -from freqtrade.data.btanalysis import load_backtest_stats from freqtrade.enums import RunMode from freqtrade.exceptions import OperationalException from freqtrade.misc import round_coin_value -from freqtrade.optimize.optimize_reports import show_backtest_results, show_filtered_pairlist logger = logging.getLogger(__name__) @@ -63,11 +61,13 @@ def start_backtest_show(args: Dict[str, Any]) -> None: config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) + from freqtrade.optimize.optimize_reports import show_backtest_results, show_sorted_pairlist + from freqtrade.data.btanalysis import load_backtest_stats + results = load_backtest_stats(config['exportfilename']) - # print(results) show_backtest_results(config, results) - show_filtered_pairlist(config, results) + show_sorted_pairlist(config, results) def start_hyperopt(args: Dict[str, Any]) -> None: diff --git a/freqtrade/optimize/optimize_reports.py b/freqtrade/optimize/optimize_reports.py index d6fcf8a04..09de655ef 100644 --- a/freqtrade/optimize/optimize_reports.py +++ b/freqtrade/optimize/optimize_reports.py @@ -737,7 +737,7 @@ def show_backtest_results(config: Dict, backtest_stats: Dict): print('\nFor more details, please look at the detail tables above') -def show_filtered_pairlist(config: Dict, backtest_stats: Dict): +def show_sorted_pairlist(config: Dict, backtest_stats: Dict): if config.get('backtest_show_pair_list', False): for strategy, results in backtest_stats['strategy'].items(): print(f"Pairs for Strategy {strategy}: \n[") diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 6e717afdf..fcccca539 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -8,12 +8,12 @@ from zipfile import ZipFile import arrow import pytest -from freqtrade.commands import (start_convert_data, start_convert_trades, start_create_userdir, - start_download_data, start_hyperopt_list, start_hyperopt_show, - start_install_ui, start_list_data, start_list_exchanges, - start_list_markets, start_list_strategies, start_list_timeframes, - start_new_strategy, start_show_trades, start_test_pairlist, - start_trading, start_webserver) +from freqtrade.commands import (start_backtest_show, start_convert_data, start_convert_trades, + start_create_userdir, start_download_data, start_hyperopt_list, + start_hyperopt_show, start_install_ui, start_list_data, + start_list_exchanges, start_list_markets, start_list_strategies, + start_list_timeframes, start_new_strategy, start_show_trades, + start_test_pairlist, start_trading, start_webserver) from freqtrade.commands.deploy_commands import (clean_ui_subdir, download_and_install_ui, get_ui_download_url, read_ui_version) from freqtrade.configuration import setup_utils_configuration @@ -1389,3 +1389,19 @@ def test_show_trades(mocker, fee, capsys, caplog): with pytest.raises(OperationalException, match=r"--db-url is required for this command."): start_show_trades(pargs) + + +def test_backtest_show(mocker, testdatadir, capsys): + sbr = mocker.patch('freqtrade.optimize.optimize_reports.show_backtest_results') + args = [ + "backtest-show", + "--export-filename", + f"{testdatadir / 'backtest-result_new.json'}", + "--show-pair-list" + ] + pargs = get_args(args) + pargs['config'] = None + start_backtest_show(pargs) + assert sbr.call_count == 1 + out, err = capsys.readouterr() + assert "Pairs for Strategy" in out diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py index 7f5f5e8a9..e56572522 100644 --- a/tests/optimize/test_optimize_reports.py +++ b/tests/optimize/test_optimize_reports.py @@ -10,7 +10,8 @@ from arrow import Arrow from freqtrade.configuration import TimeRange from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN from freqtrade.data import history -from freqtrade.data.btanalysis import get_latest_backtest_filename, load_backtest_data, load_backtest_stats +from freqtrade.data.btanalysis import (get_latest_backtest_filename, load_backtest_data, + load_backtest_stats) from freqtrade.edge import PairInfo from freqtrade.enums import SellType from freqtrade.optimize.optimize_reports import (_get_resample_from_period, generate_backtest_stats, @@ -19,9 +20,9 @@ from freqtrade.optimize.optimize_reports import (_get_resample_from_period, gene generate_periodic_breakdown_stats, generate_sell_reason_stats, generate_strategy_comparison, - generate_trading_stats, show_filtered_pairlist, store_backtest_stats, - text_table_bt_results, text_table_sell_reason, - text_table_strategy) + generate_trading_stats, show_sorted_pairlist, + store_backtest_stats, text_table_bt_results, + text_table_sell_reason, text_table_strategy) from freqtrade.resolvers.strategy_resolver import StrategyResolver from tests.data.test_history import _backup_file, _clean_test_file @@ -409,12 +410,12 @@ def test__get_resample_from_period(): _get_resample_from_period('noooo') -def test_show_filtered_pairlist(testdatadir, default_conf, capsys): +def test_show_sorted_pairlist(testdatadir, default_conf, capsys): filename = testdatadir / "backtest-result_new.json" bt_data = load_backtest_stats(filename) default_conf['backtest_show_pair_list'] = True - show_filtered_pairlist(default_conf, bt_data) + show_sorted_pairlist(default_conf, bt_data) out, err = capsys.readouterr() assert 'Pairs for Strategy StrategyTestV2: \n[' in out From c15f73aa1f35aaea7db1d701ed6d745fd80ecbb6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Oct 2021 09:55:19 +0100 Subject: [PATCH 37/64] Rename command to backtesting-show --- docs/utils.md | 6 +++--- freqtrade/commands/__init__.py | 2 +- freqtrade/commands/arguments.py | 28 ++++++++++++------------- freqtrade/commands/optimize_commands.py | 4 ++-- tests/commands/test_commands.py | 8 +++---- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index 6934e0a5c..4a032db26 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -586,9 +586,9 @@ Adding `--show-pair-list` outputs a sorted pair list you can easily copy/paste i Only using winning pairs can lead to an overfitted strategy, which will not work well on future data. Make sure to extensively test your strategy in dry-run before risking real money. ``` -usage: freqtrade backtest-show [-h] [-v] [--logfile FILE] [-V] [-c PATH] - [-d PATH] [--userdir PATH] - [--export-filename PATH] [--show-pair-list] +usage: freqtrade backtesting-show [-h] [-v] [--logfile FILE] [-V] [-c PATH] + [-d PATH] [--userdir PATH] + [--export-filename PATH] [--show-pair-list] optional arguments: -h, --help show this help message and exit diff --git a/freqtrade/commands/__init__.py b/freqtrade/commands/__init__.py index ba977b6bd..129836000 100644 --- a/freqtrade/commands/__init__.py +++ b/freqtrade/commands/__init__.py @@ -16,7 +16,7 @@ from freqtrade.commands.hyperopt_commands import start_hyperopt_list, start_hype from freqtrade.commands.list_commands import (start_list_exchanges, start_list_markets, start_list_strategies, start_list_timeframes, start_show_trades) -from freqtrade.commands.optimize_commands import (start_backtest_show, start_backtesting, +from freqtrade.commands.optimize_commands import (start_backtesting, start_backtesting_show, start_edge, start_hyperopt) from freqtrade.commands.pairlist_commands import start_test_pairlist from freqtrade.commands.plot_commands import start_plot_dataframe, start_plot_profit diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 9d14bb38d..032f7dd51 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -175,15 +175,15 @@ class Arguments: self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot') self._build_args(optionlist=['version'], parser=self.parser) - from freqtrade.commands import (start_backtest_show, start_backtesting, start_convert_data, - start_convert_trades, start_create_userdir, - start_download_data, start_edge, start_hyperopt, - start_hyperopt_list, start_hyperopt_show, start_install_ui, - start_list_data, start_list_exchanges, start_list_markets, - start_list_strategies, start_list_timeframes, - start_new_config, start_new_strategy, start_plot_dataframe, - start_plot_profit, start_show_trades, start_test_pairlist, - start_trading, start_webserver) + from freqtrade.commands import (start_backtesting, start_backtesting_show, + start_convert_data, start_convert_trades, + start_create_userdir, start_download_data, start_edge, + start_hyperopt, start_hyperopt_list, start_hyperopt_show, + start_install_ui, start_list_data, start_list_exchanges, + start_list_markets, start_list_strategies, + start_list_timeframes, start_new_config, start_new_strategy, + start_plot_dataframe, start_plot_profit, start_show_trades, + start_test_pairlist, start_trading, start_webserver) subparsers = self.parser.add_subparsers(dest='command', # Use custom message when no subhandler is added @@ -267,14 +267,14 @@ class Arguments: backtesting_cmd.set_defaults(func=start_backtesting) self._build_args(optionlist=ARGS_BACKTEST, parser=backtesting_cmd) - # Add backtest-show subcommand - backtest_show_cmd = subparsers.add_parser( - 'backtest-show', + # Add backtesting-show subcommand + backtesting_show_cmd = subparsers.add_parser( + 'backtesting-show', help='Show past Backtest results', parents=[_common_parser], ) - backtest_show_cmd.set_defaults(func=start_backtest_show) - self._build_args(optionlist=ARGS_BACKTEST_SHOW, parser=backtest_show_cmd) + backtesting_show_cmd.set_defaults(func=start_backtesting_show) + self._build_args(optionlist=ARGS_BACKTEST_SHOW, parser=backtesting_show_cmd) # Add edge subcommand edge_cmd = subparsers.add_parser('edge', help='Edge module.', diff --git a/freqtrade/commands/optimize_commands.py b/freqtrade/commands/optimize_commands.py index 4acc0d939..f230b696c 100644 --- a/freqtrade/commands/optimize_commands.py +++ b/freqtrade/commands/optimize_commands.py @@ -54,15 +54,15 @@ def start_backtesting(args: Dict[str, Any]) -> None: backtesting.start() -def start_backtest_show(args: Dict[str, Any]) -> None: +def start_backtesting_show(args: Dict[str, Any]) -> None: """ Show previous backtest result """ config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE) - from freqtrade.optimize.optimize_reports import show_backtest_results, show_sorted_pairlist from freqtrade.data.btanalysis import load_backtest_stats + from freqtrade.optimize.optimize_reports import show_backtest_results, show_sorted_pairlist results = load_backtest_stats(config['exportfilename']) diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index fcccca539..e0d0cc38d 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -8,7 +8,7 @@ from zipfile import ZipFile import arrow import pytest -from freqtrade.commands import (start_backtest_show, start_convert_data, start_convert_trades, +from freqtrade.commands import (start_backtesting_show, start_convert_data, start_convert_trades, start_create_userdir, start_download_data, start_hyperopt_list, start_hyperopt_show, start_install_ui, start_list_data, start_list_exchanges, start_list_markets, start_list_strategies, @@ -1391,17 +1391,17 @@ def test_show_trades(mocker, fee, capsys, caplog): start_show_trades(pargs) -def test_backtest_show(mocker, testdatadir, capsys): +def test_backtesting_show(mocker, testdatadir, capsys): sbr = mocker.patch('freqtrade.optimize.optimize_reports.show_backtest_results') args = [ - "backtest-show", + "backtesting-show", "--export-filename", f"{testdatadir / 'backtest-result_new.json'}", "--show-pair-list" ] pargs = get_args(args) pargs['config'] = None - start_backtest_show(pargs) + start_backtesting_show(pargs) assert sbr.call_count == 1 out, err = capsys.readouterr() assert "Pairs for Strategy" in out From dffe76f10935e1c88141d85e00c3ad1204863738 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 31 Oct 2021 10:42:42 +0100 Subject: [PATCH 38/64] 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 {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']})\n") if len(output + stat_line) >= MAX_TELEGRAM_MESSAGE_LENGTH: @@ -896,7 +896,7 @@ class Telegram(RPCHandler): stat_line = ( f"{i+1}.\t {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']})\n") if len(output + stat_line) >= MAX_TELEGRAM_MESSAGE_LENGTH: @@ -931,7 +931,7 @@ class Telegram(RPCHandler): stat_line = ( f"{i+1}.\t {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']})\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 :* `Shows the buy_tag performance`\n" "*/sells :* `Shows the sell reason performance`\n" - "*/mix_tag :* `Shows combined buy tag + sell reason performance`\n" + "*/mix_tags :* `Shows combined buy tag + sell reason performance`\n" "*/trades [limit]:* `Lists last closed trades (limited to 10 by default)`\n" "*/profit []:* `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 Date: Sun, 31 Oct 2021 10:51:56 +0100 Subject: [PATCH 39/64] 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, From 3d59289b0929deaea54bcaa9c872a3365dc93fe7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 03:01:03 +0000 Subject: [PATCH 40/64] Bump filelock from 3.3.1 to 3.3.2 Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.3.1 to 3.3.2. - [Release notes](https://github.com/tox-dev/py-filelock/releases) - [Changelog](https://github.com/tox-dev/py-filelock/blob/main/docs/changelog.rst) - [Commits](https://github.com/tox-dev/py-filelock/compare/3.3.1...3.3.2) --- updated-dependencies: - dependency-name: filelock dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-hyperopt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index 288d3efad..9beb17b4a 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -5,7 +5,7 @@ scipy==1.7.1 scikit-learn==1.0 scikit-optimize==0.9.0 -filelock==3.3.1 +filelock==3.3.2 joblib==1.1.0 psutil==5.8.0 progressbar2==3.55.0 From e2041ddb70a4bef7f31b9e4a19e43c96904e4a37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 03:01:10 +0000 Subject: [PATCH 41/64] Bump ccxt from 1.59.2 to 1.59.77 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.59.2 to 1.59.77. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg) - [Commits](https://github.com/ccxt/ccxt/compare/1.59.2...1.59.77) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f242fb9b1..ef43797bc 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.21.3 pandas==1.3.4 pandas-ta==0.3.14b -ccxt==1.59.2 +ccxt==1.59.77 # Pin cryptography for now due to rust build errors with piwheels cryptography==35.0.0 aiohttp==3.7.4.post0 From 45f7093e520e4ad7a9e8b69d5ee841043d1ec5d0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 03:01:14 +0000 Subject: [PATCH 42/64] Bump mkdocs-material from 7.3.4 to 7.3.6 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 7.3.4 to 7.3.6. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/7.3.4...7.3.6) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 72d1d0494..40269b109 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,4 +1,4 @@ mkdocs==1.2.3 -mkdocs-material==7.3.4 +mkdocs-material==7.3.6 mdx_truly_sane_lists==1.2 pymdown-extensions==9.0 From 46d4418e85abc58b640e895a7c482d0929227b85 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 03:01:23 +0000 Subject: [PATCH 43/64] Bump scikit-learn from 1.0 to 1.0.1 Bumps [scikit-learn](https://github.com/scikit-learn/scikit-learn) from 1.0 to 1.0.1. - [Release notes](https://github.com/scikit-learn/scikit-learn/releases) - [Commits](https://github.com/scikit-learn/scikit-learn/compare/1.0...1.0.1) --- updated-dependencies: - dependency-name: scikit-learn dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-hyperopt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index 288d3efad..99d5576bd 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -3,7 +3,7 @@ # Required for hyperopt scipy==1.7.1 -scikit-learn==1.0 +scikit-learn==1.0.1 scikit-optimize==0.9.0 filelock==3.3.1 joblib==1.1.0 From 6623dfe7da88d80f43c405a58718b7e7db3b2b95 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 11:07:06 +0100 Subject: [PATCH 44/64] Improve CORS documentation --- docs/assets/frequi_url.png | Bin 0 -> 11391 bytes docs/rest-api.md | 27 ++++++++++++++++++++++----- 2 files changed, 22 insertions(+), 5 deletions(-) create mode 100644 docs/assets/frequi_url.png diff --git a/docs/assets/frequi_url.png b/docs/assets/frequi_url.png new file mode 100644 index 0000000000000000000000000000000000000000..bd6ef52b62d3e0cd8013922e22bea795828fe802 GIT binary patch literal 11391 zcma)Cg;!Kxv>rkbke0>)r9nViLP5H_JES`tIuwxZ&LM~HZV(uRA*8#z8M^23d+YrH z@2*?xuJ!G+@4ow-`|Su*R+PfVB*z2*0N66p;;H}u(#FfZ=4<4ab7m1T&C3PDNm|Dh z0Kn<{??6go!Jz;Er~xwK-_$(}kC%P*)n?a)&y-xmT*M}5QGWsm2!Mg6-|B;HSVD>8 zrimAI=R1!NAdB_h#mg3l3yxZCzBgGJ%bkr66~~RB$ws{&4TQFqtc{qBjP+jwIJZ?q z@m^EM_602kEp6QlVB>9|DCPEhtyFd1W9}|xW-QTyy-!+p4L#+ws*s%3EaObpaIQh(fO;pNw;{hHk%SdEUl=hm@&SvMN442|H?ONm8REwi z`T|4bY#faE!@kge)5j&dY>x8)gk06a_SZa>?i3OSHYu3hcXpg{(e&PVZyFUwZxx>| zEIK}4I$_-K;R1mk4j#=azGJk)Z+|tTy-1Hof}XetJjxA(p?g$@dOARVp|7A)ZT@^D z8d>U=^o0p!Ba$~gg@C~JyRWxhD!T7O;WQk+y~bIJmR%!V+)hGkp4~q%Jl6s<9-MuR zN~FoRkG+-vF(LRSqhEsMZ>S-pVf!Tw*1ZBT0~deFsvfi=69zVG7R#Rt3IA+Io-PRj z9U1fQmXMcf9<}4|EqXScQ7Aey`Y7hb46JG!LQ6PGQj<~95huG!NZMU@{D@Y7PCGSW zcM&*?(&^Q?PYUhl+vja}_I6ep!+f-V1$^q@GGDW?uX_H%0SXi|_TJk(h~3Cl2=?K5 zEs0AEL)sKdr0>VwH`Z}`*Pdj9Very3Cjhjvvh_D)OflP(G9NqX#Rpt>6JB09W$IC7 z0LcL;bS5N(Shqxs@_dtz3LCpz;}kH=2MWNsCAMnyPTGA9V~th4-x_(&yLVoN*stnq z{59te@QAv9{?*>}T+Pb+ghbPg?7zbe`oN*5Lugj!)N0h$c>#>b5VQ=~R!_H!Nj& zRUST@aKKr>z$TkDoE7{C!W?|M#_i-$+A)&H3#3o=6(d`onz&EcZMxS&AAPY|`^B}V zzAAcwQdWxZE(F^aDKRk#aVR3A7J($~dAH|8n zHW3Q?;A2y=t zOCG$i-Od>&nInpVU2keXif_xrJE6cNL}fpr3V^9rEi^8BUcJUYG$ zag5s*a~;I4`@{Vs&**kbRo1{J+@nwIP)(AN{`}#6s580yfo~ky$a=g0weJBs=6ON< zT72z!e67+`DX+b@`(gR_u0I%OFssT~xdV)wlI8r4)nQLEQ{BsjQQv2xz!j#Io8Hq3 zSZ0a}=&0F03)fLAuB}ixws8%9TCNvM$>avA&t7;Mg0+p!0jQ<*s@DZ$wrFjG_BgV= zj_-_!nnb-0Q4k#^Q!oxolaB$^m0hUqjCgIP3UF~5HlA#uC+SwT;8WWXX32!NWrawcLYYN6vdTR0BV!s_e@QURi6E~CpZI#*>~NP zgbbRN*jmZUMt1qhKPZ2^-%n;xvuSj^Q;`63|JZJ2Xc7t>0Iw{UYS-`IG5jDg^k3f( zU&;APDkH}N_*`L7w2FWoTC!bA?+9IALZf2T)!Uu6bAmqcIC{cZ7P~G)QkaTdf=m(S_j>?C}&!B-DwHsr+ZIU8rz<*t@N@V#g4G6C&Y%?v$3hC|j2NwvthM?lMG7 zlKOUrh^5wC;bCQAawE2%eds~Axx1|mnF!o;ZaW0IdG-@IjW67;@URNr9BUg(?`Y^# zu<_w|YTM-<7=QvoW1^Uz52>EY(0TT;2{RsQJibBnK@tA@()y}Y{F?hl9B4}sROE-G zwZ}=b^ui5hjE9?MW|gfdbyqj8?8faf$7HM!4SbU;nt`kqeB9OQlcNJgMR`)gjeAi} zDqTEv_#armr5zxWy(81(5UWfJa^lP7aP=!E+TO;_UR= z;=k&XUO^-NBi|)|2L#Y-J8;$4u5*yi*hX&U^V9}_8h@NE#NMhjqIbvXl^pHVZHnd3 z*m|AZKq4&y=@H9kud}1Gt?Lzt+LGp;BmcaWOoV#4u^Z|@QGbrZ>A-HQP7Y!?H+(+q z6?aoR;~nVTb6r_^C5Z^aa{DSxqAuRtQkY@|LBN*}@4wW3W%2eLjj&1u{IcF9$0cy> z9uU;mJQvP#4c+x}kUyXbn#k+ZvJubXyc+FC=sQ(Lr!j@4k%dJN^==!M-r)*0dRf~< ziobqc_&$$5clCv}SxOxS_e|Q4!W2V;zKH@joM{#>E3quE+x5?f6A%x&*LUH|Z8t|> z#z7J{OSnYoUY3vvRo}}@m9)$fz2<@r_f`LUAE~#J;%Uw>XhpHD6s|qw> zX>H$oGUt@z0C=Iazd)FPEfdVk8SLws^}R#xw)R!DLYveA3OPkm9qq+$DLAN=wP!V6~8p~8rqlPWEj{FG|}Z{m#(Y(;wl?v(^{Zn z{`j@E7cyXIW$kOGRO7#FH6wiG3J3u`jdX2b3g1Zt>_SNe+i3puKFUdjzsBVPfgVz# z^z0-DkNTDHIKcKKb+P4Ym0=FA@&-I;|WK2R3 zEzUljGH6}@3t`$_0kf2O+s>{#5P#M)NO(BT`!yWhMODX7`g3@Z0*yL86cwi~FxcgJ zE`^U@8*rfG9N(D#wd``&)K}Dy_a+y;PJi1Q(K=vdhF^1=v5Iq}b6$#z)ZEJAek{yi zpR3PC-!06!0p;rCnifkS-rzY{eR^b)ajHfTsP(evGw^=GUYpx30xTJ=bxi(C0(xIk zU^&7*~xax6#rLelw=AOS6fc=H zYPTb4YAYVk1nkwRRm#4>-yVnH~kdMyrLpy{DTX+V|i#tCr8G$(2bjfn#QN#MR z$hJ)EiOWRDC=`)mZBO(Ih&+3nyY|1A8G}HeT2Ac`SQ$wfM5pi4(C!aorUl$uU?j9b zgXXpW{@zB`Yu_2uIZu+W=Ac_zO(3UCsd3zM&<2oUch9UM&PV<27OFv$zZ;q$w5LyN zczpM9ymIb0dBIC*9rji2EAzgCh%_VSuXN4NnL^NYMt*&-)APquq5CsI?^Q0Hk75kC z|B=I_o60$r8S%NB9U9Kpx%jcA7P&O7H0s zi=wdE?(_X37x=SM`_p2)0!33hoM*0FLCVOl!_+L=-*wi!%m?05R?foq{p8@(PB|oW z?Z{f7;U>6?@*UulGJIz=f9%VB#%#)GVT9Z`7sJmU$C~Y4a3OlK#5qbK-dcIsbAR75I1|=k!N38M7iG`2N7ty&;-zGKDhjj1Cf6oIuNx7W92_jD%LnHp zMiac>r@bJBX`bB!@^sE!b6Psuy?6q^%L*EkOjSh*t4IkAqekF;fJ>x$$fH!h{HZ4? zAfrEa{N#VXFz=`@{c0R&zDIrguP%1)Q|=j=SZ>(Kv2oj}(}ASD%PFKG`A2hb4x?UC zygqDWRSpIH{T33#b9PMB+|P5{#?Y^D&p*FgL-KD{97VrJ;2Hf^f#BgyqQ(pm%1E_* zb&YTKUh!T-654Dg*s<8~jv)AT`{%!~{PnLOQTSU$|E=>7^zPn~SM=YGdpF54Nx#%a zQbU0MMbWSOdw@;e-95Kj@;L5NLNVy1?O%I0*PnTq;>CKU`Om)K0wjEO-( z;k2q>c|2@GIVdOtcRGkutf&x^7ywYXmjT6CVf_u5V%OyM1c-{SOxT4PmrB3nz$duo zR>#&9VXg!Ehyd^l0nfTG0Kk_pK6_vUrTYR$HO7+kx@-ze32VFd;2U2X6vvHe^CA=i zf+=PZ%=Y-E>C_<|d_rHrV3XWg5imrO#lfl$+8Z*Uu*8I+R8sPJTSZrA=z zmo4!2Z4AK~Bj)QRVQLvXnz+bCS&5(t*MAMaCt$q^84vc1{;$@kedos{I1NsM5;?=9i5qU;$v zjl%~Z0O;a;d+WTbi{3U)TN=hh>!CuvX(YKMBm{Ia5i|8__YitB_DCJdB#!pQ!9fTp z1e{39eqB2r%8nVfp8?!|L(q3ASt&~U`d8|Vv^*i|H+frH2*xjJ2oJ#|>NmVe@-XXP z)S=cYyDDPUj6i;Y_07pE zI4&$PbS*vX_2M!xI(&0%@@M__0@50~+qpu@5;zHmd$w&F9xC@!{GjX8e=^eppxkCs z`(2iXHXXvk?DnI~y0oU0(XIEtZ)9<%r?alMDg!u&5lsJ$^8^8oKvicsX8157#b)=U zFj5o}`x%P^zS%lZBi$y_#k?L)7cd*{6K>K`@4>XY-gG8;o!9*qSX4^+XS75 ztMC1HG^I}!IIEbrND74ZdESUS|I6nJk`+byv_Ogo zmw7EN?@sE#e_1)aoeBmWMk;TEVbPFb#fs9(=i9$=G=+t(6BAb>BMyAtvrCt9r(I)h zQwt_~p^0t?NlCKO@3DCMG7% z&dvv^sh2u+1+v>fu}M_Z^nIHJpJv~B_V2No?8_s4FV2;t_Tr*sWs9oV+9*g=fI!NHGoK7!Cu7! z7Lw3Zw3m%EBrvadZ?AZ_$&TOs#7AF$=}3o3C9d?~fQ!pgl7Wsk@iilK9q%*cCON$M ztEXE4KsjBnC24z_B5u0DcXnQ6#{n!!rDNto8P$!l(Fi zjr(TCReG?sgiVXA@k*!JDq&;j014!6KO+x8P5AFYJ2v~`d3{LLMVMn8r-iM9WSnj( za@i@T-IKPkJ|E);dh^3S1&>bq4diL`lp{qhE9FQ4WgRRMM_j(jRU@@+WWe087#VBc zS}HO7ilOt2azFCh!kog^ztw-#TPjvJnU@%YNX8j0|$PtfD-3 zW0Sw8^J883CLk{_&!VUxRQyBCfa}gM*>Gt0JI`m<$XBd0!4L917Uk0`%U_e~teiC+ zFJ|J?bv28PGjlA2uC5C8^5?=YqVyE!NM)o=!a)zEpOyvDdFx%2;(Q&qYqd6u7k;;n zpiyk$+ny)yd=6SkerKP7xVqbEU&Nf>xuQCZ0%%*>$21WrI~aOqbk-nQU0dyNjsZl3p#l!AX_o7Sv>Aj@Op6Eo}nTlK*&|(O{+lfN|M0 z-LWh8TCTv>tWLVvpc^}_ERTy!H8R*V*QmBNJA>$S6uyr&lm zK5^W7MMLosNJTYe70vUeJ3~w|AXB$i{bGQ8WJBJ(^1gi(m@~Gm<>7>prbO()r)LM`iV_s>NBsW)pjP zIY*=Ie*{{U%O5&+8pGvvsm$4r^Co)xF_HQ|?Sw-cht|c2W$}SYg!$^@#^p2Nl7UJZ z#^ON(TLXVJxL>6#&rSPQw>X~JN*2+H?*@ev8At7$V1{rSSuCq8uV9?yYwgpD>UMKR zPR=>Z&Er>dgtKl>lc&*2RZRRv;llDH;5a4+fRVv5XVTs)f!9jHln3;)Q&W%6H(gym zcbp27F+%5mH~J&K0C5kmQvw#Kn1pO4ugBXe)3KGGdjc1ftXE1okN}0% zJQf|UtzOlq$&HM*a&Pq>SAHfntWf*FzaK8fuo)&~wLev)3y5ULHqt2;3qPpLpdVWR zJ|k={ewAv7J&5UBr<2x!Qwxe8As6@|ciGYL*YZ~?}Og@{4Rz5H5;$6fdae+fm> zJ@^WF&60hXF@7-u*QdD_iUyee(dL3ByZg{rFI~r@Rd&FiaWS(mTbY%x3vy#E`>VJZ z|L^t+3h*x~iWO zHu$`(Z7GKlGN8S5#NCn;8?tTN5Pa0KY~Y~${w_?`jSMukK&Fh@qRufDAd(8Csj}iZ zauOjT%c|0#7!k|wO=OnsCtUz>3tG1@8)=d_y^cZgN+f^EY$`k+|9Hw*$`94M&@4a|0%3*sBmM-%c;5nTLzK=xGh zaI_Z7k0XNEPV-|hKmDV4gBqu*mdMCU_C~PvC^l%Dp)`W=dRj69a2I1?H{EvIAjqi9AY*jaTT=q3cNK?X~mvl6x zEV(kGnK-|wi#PF0PWF!*7VJL6?PU|O+#g>PGALR#EFipYO>{q=oSvR?=?QUt$WcI! zi9upO{pM6xTnPy9biZ%%f0eMl1WoT;@3LQ(44`;lQCdPZWJO)Ts3Mbb*vPjiF}22% z9O+r^`QaQaPHy?g}&wM5kNM&U&W~%{` z)YMNccHTOG@a*=Shi4g}0ETgLt{w?1R>E6X&PNVHK>K;yJPH=lGX+wUB+N7FjUeQ` z3T-N&CI?tgSv4_D1T3toJ(v|z{b<+4edt|cJ5@B4UX{UQW?7e!qaGRrK!FwFqATg9 zx9Q56pNkgiPfhRU1Y=f1fwo1x*l$wXNLRUx?={7AswhM(U7Ogrb!m3BaI$#0DLQ2*HqNckEV=uFX3@CRPUHN>}uMLuEPXPhyhbzi4Y_v*Iebi=Z`1)ytP+lX2ZvA zHjhg0Cje{Q3qJR66ZIL;Q3nfS7;s6v=7|Wp-%tGwHXd&1UB~R!HE}kT=dh=`69>3m zw@0$>kMFzG{IPWP3IwP|p<11mogcs9+|i|QK|@Rg$r0taB!B7Fuc4FA>&Z=}_yyBl zJmf;A4)b8?MPf##Pp~^ zr|a}b!=W3TDyal#HvP2IpKC?dP>5P^*V+9JCH08Im5UGqDiixwedVl6h=}fu_bN~c3s55~nzj(a7cp3&7S8~HUN=L><#Nu8a%Fk)p}RYLlNsm&N`^uW=GPgkP& zn7K~3J~{LyBU2X}qfNia^wizdY@jpZi&^Kb(%0EOGcM)!X^e zY^m{eDd{DMhTfr%85*`eYJ{DKbU8)zZiYfC8Krzh*n}Yl++wxg^ReYwbUArQ&MPCq zx#qRfas%yxf6pPFa=?Z0{CLH&7UkTcSJnNobF=o&ft210AGiDHDj{bSyKb*2-0A}- zZZUaUIo|+9q;0e7J&VB4-r4Jb>5x`&RRR{mpO~b4qcf4^K4=E;2xJ0;SyeJBUolzC zD3xb$cj%8h|6;$@M~*rQ1L>5|ZBj~s-QTB7h-=>&zbyM#Z-BEnt>?D zjW@DxvMrKqV=^k+sn~)x56D!wNSx29U!OVH$a3WZJCW80_x6J(F*LX{-*~Z?(EQO_ zR*QANnfUilxoL4^7hb`;ShG2R==641Sf#tJeWgk^1-EiGgAh*>g(`?Y=@gjXSDF9O zwyhw$BX~c#;@L1*0m$^KBUEw8qI_uY%>@+b)MEUQFRtccl5O3!8_!-p#GpyE{<^AL zwu1IoY=Y@cOyz`27AonZ>Rwy)?o}uxUEyZ1*{K~RCK0@N%il0uo zzC4#E4OZQVHGwn_=ai?gVVI<7+AxUgC7n-HP28l5HpUx4gq+$Qbyq`S|4+J4pM)qt zpH|a}!hQ+OrmdXcd5F^d=8UONCA6xmLL`ERpxy}`DN$Rj4#HOC_>}kpDp&kK)Fb%# zAtj86%`yqaX+BE1^!vAJPop|XPE*9X(_E>b-Sj)W-~q=eW*&9$*xY_9sr_(tI`q)3 zxFyUhG9pV}i=41U%ut-$Vno93gIx;J+ME=la8Nee^RATRkIOGzcZ1WoFF|$X^Rc7< zLml|($lvcuSSp%wbg2C)*a&e-kn~I77AM*-;F?%l{@(!}Va#vxOYE_{*Fkb^=bb+ zo)eB)9U4U|S5iN#L&q{x61VI8x%v3seUJUhzNxbPH%5#$j3|nc)WVjx6z5i0FRD!c?yIlRB@bW}vVIWScG57< z|LQhVnN)HkF%+M~RZFP)?700nJ-dLDpa1rQfT*a{U)sTS{F_3$UD*7>PNNNRz~olI z2X){#q%|pVNy|ZWqulRM&g01MWUM5w2F+jmFS3^(LY1bkLCO?{_p1N*z*dMFwDN=) zkbJ_-l2jQblQ8ZQMypmdgKyUYB9t^Pv?zf_lg6D5F_ zc2zK42!AKKQI%_gFhR@Kk`_jbPavR47XOZ}@TYD2h^-?_v|MUslx#BUAYJXz+a#!Y zMQS`#%v;uYap}EvMDfp35DQ@`b9kIY=&uinD#12PlamJ90Bu(d+_ErLOBU;vP{n8! zOKf)pT^fdpwX94KqcmGw0ZVW_%z@le(lwG7(%iFVB)}+Dfvmh_oGLGA_R>dTKn~Cz zF*dO32smR*^>V5eTKN8W3Kq@}f9?O$8Z{J6xvqx%E!Mc3|LpYXVbve;WSQ-^QzU$E za!4rrb=4gs&PE(6Li^|FO=zFhnPm>a%if~o88uidRV7twAUfQVzeTIwzq_s?UlyE zj@@ac#bG-CU|V>x*4Pb)o3vjk?>o&&C%u!}bG0@CVj`lqJ~@@bbAzQ*@WTdC8k7-b z8r~;peCj3gade2*+i7x~>reIK5G;)ytHMI>)u)+hrL1|wFZu@Ri7IZVm`DQ99QU|s5Luf{DO zoas0$c<-Cb=Qcc&#P4mtNJ4*n>-8I1F@HqYym*zTC*K>eQO-Z81JM7t=|Voz(H|w2 z()321x4wix!*-{PlmwRA#v;(qzr;S@KM`O3=tRVtv*tqX9od%7s`d0JWg2xXnIPm9 zrTgY{l)-P)Q*~cRIfN{1oS$hB6Unw#S8j}gxh3ZBgr=SHF2ktTP(LS|rM*;@p=K9yApgP$vzy12AIg)sypwb$e9@-|K`Iw@3=7$R{+gPgn#NChW zUjdBucP4<$QYqzmCbj9fGmQ%4iT+UnCLLpBf}6pv{s;d~Z9)fQRH{_eocLcf>cNE> z%+Bd&;$cdqx5kAknZj$*)%TPFizk z$We?sGTn4A{$834Om*S=OSJyTy4M$}U95uIkNLw&zX{I+c&r zvAfM=Eq>938qnjC8exeefk8FC08X6@#dP2iUYUl z)`mkmS3p3@{?H`yQWJtLsURp$ zpHLuVi-pV6DUVo11bjDv-fKpD7(YX;bN@m=;S|w)_#jG+*R0Q0*MC0d-|2eLw>zQt z{ngDyz5%D)LKF!5KRtHFg(vEV&(*_JaX@KaQKS%P#1;V`pwIVQE<; znw!e!X!sHe`TF*<`?Vc&3zN3ohzd#EjK6;|116@jJPN7P#zlV5^nlh z2=3;mkm4oR(*SymKVhrsKdE1D?7R(41Bx*sO~HXb+Fv!_M Date: Mon, 1 Nov 2021 13:39:25 +0100 Subject: [PATCH 45/64] Don't force timeframe in config in config generator --- freqtrade/commands/build_config_commands.py | 8 ++++++++ freqtrade/templates/base_config.json.j2 | 3 +-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index 34ae35aff..ad38d2291 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -83,11 +83,19 @@ def ask_user_config() -> Dict[str, Any]: if val == UNLIMITED_STAKE_AMOUNT else val }, + { + "type": "select", + "name": "timeframe_in_config", + "message": "Tim", + "choices": ["Have the strategy define timeframe.", "Override in configuration."] + }, { "type": "text", "name": "timeframe", "message": "Please insert your desired timeframe (e.g. 5m):", "default": "5m", + "when": lambda x: x["timeframe_in_config"] == 'Override in configuration.' + }, { "type": "text", diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index 68eebdbd4..e2fa1c63e 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -10,8 +10,7 @@ "stake_currency": "{{ stake_currency }}", "stake_amount": {{ stake_amount }}, "tradable_balance_ratio": 0.99, - "fiat_display_currency": "{{ fiat_display_currency }}", - "timeframe": "{{ timeframe }}", + "fiat_display_currency": "{{ fiat_display_currency }}",{{ ('\n "timeframe": "' + timeframe + '",') if timeframe else '' }} "dry_run": {{ dry_run | lower }}, "cancel_open_orders_on_exit": false, "unfilledtimeout": { From 74e8b28991598957088a9e1f8eef2d6362535323 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 13:54:12 +0100 Subject: [PATCH 46/64] Improve FAQ with outdated history message closes #5819 --- docs/faq.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/faq.md b/docs/faq.md index 99c0c2c75..10ae41870 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -93,6 +93,18 @@ If this happens for all pairs in the pairlist, this might indicate a recent exch Irrespectively of the reason, Freqtrade will fill up these candles with "empty" candles, where open, high, low and close are set to the previous candle close - and volume is empty. In a chart, this will look like a `_` - and is aligned with how exchanges usually represent 0 volume candles. +### I'm getting "Outdated history for pair xxx" in the log + +The bot is trying to tell you that it got an outdated last candle (not the last complete candle). +As a consequence, Freqtrade will not enter a trade for this pair - as trading on old information is usually not what is desired. + +This warning can point to one of the below problems: + +* Exchange downtime -> Check your exchange status page / blog / twitter feed for details. +* Wrong system time -> Ensure your system-time is correct. +* Barely traded pair -> Check the pair on the exchange webpage, look at the timeframe your strategy uses. If the pair does not have any volume in some candles (usually visualized with a "volume 0" bar, and a "_" as candle), this pair did not have any trades in this timeframe. These pairs should ideally be avoided, as they can cause problems with order-filling. +* API problem -> API returns wrong data (this only here for completeness, and should not happen with supported exchanges). + ### I'm getting the "RESTRICTED_MARKET" message in the log Currently known to happen for US Bittrex users. From 3056be3a1db4f509241703825cd29624ada97da5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 20:04:52 +0100 Subject: [PATCH 47/64] document prerequisites for exchange listing --- docs/developer.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/developer.md b/docs/developer.md index a6c9ec322..da47d903c 100644 --- a/docs/developer.md +++ b/docs/developer.md @@ -252,6 +252,9 @@ Completing these tests successfully a good basis point (it's a requirement, actu Also try to use `freqtrade download-data` for an extended timerange and verify that the data downloaded correctly (no holes, the specified timerange was actually downloaded). +These are prerequisites to have an exchange listed as either Supported or Community tested (listed on the homepage). +The below are "extras", which will make an exchange better (feature-complete) - but are not absolutely necessary for either of the 2 categories. + ### Stoploss On Exchange Check if the new exchange supports Stoploss on Exchange orders through their API. From 7ae9b901747ca36861b67379ad65d146db9ec9bf Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 1 Nov 2021 20:12:34 +0100 Subject: [PATCH 48/64] Further clarify backtesting trailing stop logic part of #5816 --- docs/backtesting.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/backtesting.md b/docs/backtesting.md index 37724b02a..75f1ad6f8 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -478,6 +478,7 @@ Since backtesting lacks some detailed information about what happens within a ca - Low happens before high for stoploss, protecting capital first - Trailing stoploss - Trailing Stoploss is only adjusted if it's below the candle's low (otherwise it would be triggered) + - On trade entry candles that trigger trailing stoploss, the "minimum offset" (`stop_positive_offset`) is assumed (instead of high) - and the stop is calculated from this point - High happens first - adjusting stoploss - Low uses the adjusted stoploss (so sells with large high-low difference are backtested correctly) - ROI applies before trailing-stop, ensuring profits are "top-capped" at ROI if both ROI and trailing stop applies From f365e68706099acbb4b83b87d06f8182bf84ebb1 Mon Sep 17 00:00:00 2001 From: Theagainmen <24569139+Theagainmen@users.noreply.github.com> Date: Mon, 1 Nov 2021 23:07:16 +0100 Subject: [PATCH 49/64] [docs] Update RateLimit value [small] ## Summary Fix very small mistake in docs, that might confuse people. Let me know if this is the correct value now, there is still another 3100 in there, which I think makes sense there and is correct. ## Quick changelog Changed the `rateLimit` 3100 value to 200, to match the 200ms and thus 0.2s delay. --- docs/exchanges.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index badaa484a..d00c88d8e 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -41,12 +41,12 @@ In case of problems related to rate-limits (usually DDOS Exceptions in your logs "ccxt_config": {"enableRateLimit": true}, "ccxt_async_config": { "enableRateLimit": true, - "rateLimit": 3100 + "rateLimit": 200 }, ``` This configuration enables kraken, as well as rate-limiting to avoid bans from the exchange. -`"rateLimit": 3100` defines a wait-event of 0.2s between each call. This can also be completely disabled by setting `"enableRateLimit"` to false. +`"rateLimit": 200` defines a wait-event of 0.2s between each call. This can also be completely disabled by setting `"enableRateLimit"` to false. !!! Note Optimal settings for rate-limiting depend on the exchange and the size of the whitelist, so an ideal parameter will vary on many other settings. From e78df59e308965800c3c8185bbe2b920aa62c273 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Nov 2021 19:49:53 +0100 Subject: [PATCH 50/64] Configure candle length for OKEX --- freqtrade/exchange/__init__.py | 1 + freqtrade/exchange/okex.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 freqtrade/exchange/okex.py diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index b08213d28..614e8ad68 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -19,3 +19,4 @@ from freqtrade.exchange.gateio import Gateio from freqtrade.exchange.hitbtc import Hitbtc from freqtrade.exchange.kraken import Kraken from freqtrade.exchange.kucoin import Kucoin +from freqtrade.exchange.okex import Okex diff --git a/freqtrade/exchange/okex.py b/freqtrade/exchange/okex.py new file mode 100644 index 000000000..5ab6f3147 --- /dev/null +++ b/freqtrade/exchange/okex.py @@ -0,0 +1,18 @@ +import logging +from typing import Dict + +from freqtrade.exchange import Exchange + + +logger = logging.getLogger(__name__) + + +class Okex(Exchange): + """ + Okex exchange class. Contains adjustments needed for Freqtrade to work + with this exchange. + """ + + _ft_has: Dict = { + "ohlcv_candle_limit": 100, + } From 161a3fac154770d1be998e182b65af3f15a65c75 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Nov 2021 20:08:56 +0100 Subject: [PATCH 51/64] Run exchange-enabled tests against okex --- requirements.txt | 2 +- tests/exchange/test_ccxt_compat.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index ef43797bc..e2f6a3162 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.21.3 pandas==1.3.4 pandas-ta==0.3.14b -ccxt==1.59.77 +ccxt==1.60.11 # Pin cryptography for now due to rust build errors with piwheels cryptography==35.0.0 aiohttp==3.7.4.post0 diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index d71dbe015..2f629528c 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -47,6 +47,11 @@ EXCHANGES = { 'hasQuoteVolume': True, 'timeframe': '5m', }, + 'okex': { + 'pair': 'BTC/USDT', + 'hasQuoteVolume': True, + 'timeframe': '5m', + }, } From 1fefb132e00e6b92ac28f1a28f209422dd8b0a7e Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 2 Nov 2021 20:26:38 +0100 Subject: [PATCH 52/64] Improve wording in documentation --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/ISSUE_TEMPLATE/question.md | 2 +- README.md | 14 +++++++------- docs/backtesting.md | 2 +- docs/faq.md | 2 +- docs/index.md | 2 +- docs/stoploss.md | 2 +- docs/strategy-advanced.md | 4 ++-- docs/strategy-customization.md | 6 +++--- docs/webhook-config.md | 2 +- 10 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index dacd9e673..54c9eab50 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -9,7 +9,7 @@ assignees: ''