diff --git a/config.json.example b/config.json.example index 46441e72d..8ebb092e1 100644 --- a/config.json.example +++ b/config.json.example @@ -23,7 +23,7 @@ "ask_strategy":{ "use_order_book": false, "order_book_min": 1, - "order_book_max": 9, + "order_book_max": 1, "use_sell_signal": true, "sell_profit_only": false, "ignore_roi_if_buy_signal": false diff --git a/config_binance.json.example b/config_binance.json.example index e2c9879b0..d324ce883 100644 --- a/config_binance.json.example +++ b/config_binance.json.example @@ -23,7 +23,7 @@ "ask_strategy":{ "use_order_book": false, "order_book_min": 1, - "order_book_max": 9, + "order_book_max": 1, "use_sell_signal": true, "sell_profit_only": false, "ignore_roi_if_buy_signal": false diff --git a/config_full.json.example b/config_full.json.example index f0414bd0d..181740b9a 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -38,7 +38,7 @@ "price_side": "ask", "use_order_book": false, "order_book_min": 1, - "order_book_max": 9, + "order_book_max": 1, "use_sell_signal": true, "sell_profit_only": false, "ignore_roi_if_buy_signal": false diff --git a/config_kraken.json.example b/config_kraken.json.example index 4f74d0b7d..dcf4c552a 100644 --- a/config_kraken.json.example +++ b/config_kraken.json.example @@ -23,7 +23,7 @@ "ask_strategy":{ "use_order_book": false, "order_book_min": 1, - "order_book_max": 9, + "order_book_max": 1, "use_sell_signal": true, "sell_profit_only": false, "ignore_roi_if_buy_signal": false diff --git a/docs/configuration.md b/docs/configuration.md index 42018a499..b0f4c7554 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -536,8 +536,14 @@ The idea here is to place the sell order early, to be ahead in the queue. A fixed slot (mirroring `bid_strategy.order_book_top`) can be defined by setting `ask_strategy.order_book_min` and `ask_strategy.order_book_max` to the same number. -!!! Warning "Orderbook and stoploss_on_exchange" - Using `ask_strategy.order_book_max` higher than 1 may increase the risk, since an eventual [stoploss on exchange](#understand-order_types) will be needed to be cancelled as soon as the order is placed. +!!! Warning "Order_book_max > 1 - increased risks for stoplosses!" + Using `ask_strategy.order_book_max` higher than 1 will increase the risk the stoploss on exchange is cancelled too early, since an eventual [stoploss on exchange](#understand-order_types) will be cancelled as soon as the order is placed. + Also, the sell order will remain on the exchange for `unfilledtimeout.sell` (or until it's filled) - which can lead to missed stoplosses (with or without using stoploss on exchange). + +!!! Warning "Order_book_max > 1 in dry-run" + Using `ask_strategy.order_book_max` higher than 1 will result in improper dry-run results (significantly better than real orders executed on exchange), since dry-run assumes orders to be filled almost instantly. + It is therefore advised to not use this setting for dry-runs. + #### Sell price without Orderbook enabled diff --git a/docs/hyperopt.md b/docs/hyperopt.md index abd7aa7ce..c5055a3a8 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -160,6 +160,9 @@ So let's write the buy strategy using these values: dataframe['macd'], dataframe['macdsignal'] )) + # Check that volume is not 0 + conditions.append(dataframe['volume'] > 0) + if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), diff --git a/docs/utils.md b/docs/utils.md index ec9ceeac9..269b9affd 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -429,6 +429,7 @@ usage: freqtrade hyperopt-list [-h] [-v] [--logfile FILE] [-V] [-c PATH] [--min-total-profit FLOAT] [--max-total-profit FLOAT] [--no-color] [--print-json] [--no-details] + [--export-csv FILE] optional arguments: -h, --help show this help message and exit @@ -450,6 +451,8 @@ optional arguments: useful if you are redirecting output to a file. --print-json Print best result detailization in JSON format. --no-details Do not print best epoch details. + --export-csv FILE Export to CSV-File. This will disable table print. + Example: --export-csv hyperopt.csv Common arguments: -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). @@ -458,9 +461,10 @@ Common arguments: details. -V, --version show program's version number and exit -c PATH, --config PATH - Specify configuration file (default: `config.json`). - Multiple --config options may be used. Can be set to - `-` to read config from stdin. + 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 diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 06acea69e..1a8cca72b 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -69,7 +69,8 @@ ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_list_min_avg_time", "hyperopt_list_max_avg_time", "hyperopt_list_min_avg_profit", "hyperopt_list_max_avg_profit", "hyperopt_list_min_total_profit", "hyperopt_list_max_total_profit", - "print_colorized", "print_json", "hyperopt_list_no_details"] + "print_colorized", "print_json", "hyperopt_list_no_details", + "export_csv"] ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index", "print_json", "hyperopt_show_no_header"] diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index e92acef30..42c697d56 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -221,6 +221,13 @@ AVAILABLE_CLI_OPTIONS = { action='store_true', default=False, ), + "export_csv": Arg( + '--export-csv', + help='Export to CSV-File.' + ' This will disable table print.' + ' Example: --export-csv hyperopt.csv', + metavar='FILE', + ), "hyperopt_jobs": Arg( '-j', '--job-workers', help='The number of concurrently running jobs for hyperoptimization ' diff --git a/freqtrade/commands/hyperopt_commands.py b/freqtrade/commands/hyperopt_commands.py index 4803f6885..5b2388252 100755 --- a/freqtrade/commands/hyperopt_commands.py +++ b/freqtrade/commands/hyperopt_commands.py @@ -21,6 +21,7 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: print_colorized = config.get('print_colorized', False) print_json = config.get('print_json', False) + export_csv = config.get('export_csv', None) no_details = config.get('hyperopt_list_no_details', False) no_header = False @@ -49,17 +50,23 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None: if print_colorized: colorama_init(autoreset=True) - try: - Hyperopt.print_result_table(config, trials, total_epochs, - not filteroptions['only_best'], print_colorized, 0) - except KeyboardInterrupt: - print('User interrupted..') + if not export_csv: + try: + Hyperopt.print_result_table(config, trials, total_epochs, + not filteroptions['only_best'], print_colorized, 0) + except KeyboardInterrupt: + print('User interrupted..') if trials and not no_details: sorted_trials = sorted(trials, key=itemgetter('loss')) results = sorted_trials[0] Hyperopt.print_epoch_details(results, total_epochs, print_json, no_header) + if trials and export_csv: + Hyperopt.export_csv_file( + config, trials, total_epochs, not filteroptions['only_best'], export_csv + ) + def start_hyperopt_show(args: Dict[str, Any]) -> None: """ diff --git a/freqtrade/commands/optimize_commands.py b/freqtrade/commands/optimize_commands.py index a2d1b4601..2fc605926 100644 --- a/freqtrade/commands/optimize_commands.py +++ b/freqtrade/commands/optimize_commands.py @@ -17,10 +17,15 @@ def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[ """ config = setup_utils_configuration(args, method) - if method == RunMode.BACKTEST: - if config['stake_amount'] == constants.UNLIMITED_STAKE_AMOUNT: - raise DependencyException('stake amount could not be "%s" for backtesting' % - constants.UNLIMITED_STAKE_AMOUNT) + no_unlimited_runmodes = { + RunMode.BACKTEST: 'backtesting', + RunMode.HYPEROPT: 'hyperoptimization', + } + if (method in no_unlimited_runmodes.keys() and + config['stake_amount'] == constants.UNLIMITED_STAKE_AMOUNT): + raise DependencyException( + f'The value of `stake_amount` cannot be set as "{constants.UNLIMITED_STAKE_AMOUNT}" ' + f'for {no_unlimited_runmodes[method]}') return config diff --git a/freqtrade/configuration/configuration.py b/freqtrade/configuration/configuration.py index 6a0441957..0645d72be 100644 --- a/freqtrade/configuration/configuration.py +++ b/freqtrade/configuration/configuration.py @@ -282,6 +282,9 @@ class Configuration: self._args_to_config(config, argname='print_json', logstring='Parameter --print-json detected ...') + self._args_to_config(config, argname='export_csv', + logstring='Parameter --export-csv detected: {}') + self._args_to_config(config, argname='hyperopt_jobs', logstring='Parameter -j/--job-workers detected: {}') diff --git a/freqtrade/data/history/idatahandler.py b/freqtrade/data/history/idatahandler.py index b08292604..ae050103b 100644 --- a/freqtrade/data/history/idatahandler.py +++ b/freqtrade/data/history/idatahandler.py @@ -145,27 +145,40 @@ class IDataHandler(ABC): if startup_candles > 0 and timerange_startup: timerange_startup.subtract_start(timeframe_to_seconds(timeframe) * startup_candles) - df = self._ohlcv_load(pair, timeframe, timerange=timerange_startup) - if df.empty: + pairdf = self._ohlcv_load(pair, timeframe, + timerange=timerange_startup) + if self._check_empty_df(pairdf, pair, timeframe, warn_no_data): + return pairdf + else: + enddate = df.iloc[-1]['date'] + + if timerange_startup: + self._validate_pairdata(pair, pairdf, timerange_startup) + pairdf = trim_dataframe(pairdf, timerange_startup) + if self._check_empty_df(pairdf, pair, timeframe, warn_no_data): + return pairdf + + # incomplete candles should only be dropped if we didn't trim the end beforehand. + pairdf = clean_ohlcv_dataframe(pairdf, timeframe, + pair=pair, + fill_missing=fill_missing, + drop_incomplete=(drop_incomplete and + enddate == pairdf.iloc[-1]['date'])) + self._check_empty_df(pairdf, pair, timeframe, warn_no_data) + return pairdf + + def _check_empty_df(self, pairdf: DataFrame, pair: str, timeframe: str, warn_no_data: bool): + """ + Warn on empty dataframe + """ + if pairdf.empty: if warn_no_data: logger.warning( f'No history data for pair: "{pair}", timeframe: {timeframe}. ' 'Use `freqtrade download-data` to download the data' ) - return df - else: - enddate = df.iloc[-1]['date'] - - if timerange_startup: - self._validate_pairdata(pair, df, timerange_startup) - df = trim_dataframe(df, timerange_startup) - - # incomplete candles should only be dropped if we didn't trim the end beforehand. - return clean_ohlcv_dataframe(df, timeframe, - pair=pair, - fill_missing=fill_missing, - drop_incomplete=(drop_incomplete and - enddate == df.iloc[-1]['date'])) + return True + return False def _validate_pairdata(self, pair, pairdata: DataFrame, timerange: TimeRange): """ diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index a6b13f93f..c6ac3acbc 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -23,6 +23,8 @@ from joblib import (Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects) from pandas import DataFrame, json_normalize, isna import tabulate +from os import path +import io from freqtrade.data.converter import trim_dataframe from freqtrade.data.history import get_timerange @@ -330,10 +332,10 @@ class Hyperopt: lambda x: '{}/{}'.format(str(x).rjust(len(str(total_epochs)), ' '), total_epochs) ) trials['Avg profit'] = trials['Avg profit'].apply( - lambda x: ('{:,.2f}%'.format(x)).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ') + lambda x: '{:,.2f}%'.format(x).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ') ) trials['Avg duration'] = trials['Avg duration'].apply( - lambda x: ('{:,.1f} m'.format(x)).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ') + lambda x: '{:,.1f} m'.format(x).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ') ) trials['Objective'] = trials['Objective'].apply( lambda x: '{:,.5f}'.format(x).rjust(8, ' ') if x != 100000 else "N/A".rjust(8, ' ') @@ -381,6 +383,62 @@ class Hyperopt: ) print(table) + @staticmethod + def export_csv_file(config: dict, results: list, total_epochs: int, highlight_best: bool, + csv_file: str) -> None: + """ + Log result to csv-file + """ + if not results: + return + + # Verification for overwrite + if path.isfile(csv_file): + logger.error("CSV-File already exists!") + return + + try: + io.open(csv_file, 'w+').close() + except IOError: + logger.error("Filed to create CSV-File!") + return + + trials = json_normalize(results, max_level=1) + trials['Best'] = '' + trials['Stake currency'] = config['stake_currency'] + trials = trials[['Best', 'current_epoch', 'results_metrics.trade_count', + 'results_metrics.avg_profit', 'results_metrics.total_profit', + 'Stake currency', 'results_metrics.profit', 'results_metrics.duration', + 'loss', 'is_initial_point', 'is_best']] + trials.columns = ['Best', 'Epoch', 'Trades', 'Avg profit', 'Total profit', 'Stake currency', + 'Profit', 'Avg duration', 'Objective', 'is_initial_point', 'is_best'] + trials['is_profit'] = False + trials.loc[trials['is_initial_point'], 'Best'] = '*' + trials.loc[trials['is_best'], 'Best'] = 'Best' + trials.loc[trials['Total profit'] > 0, 'is_profit'] = True + trials['Epoch'] = trials['Epoch'].astype(str) + trials['Trades'] = trials['Trades'].astype(str) + + trials['Total profit'] = trials['Total profit'].apply( + lambda x: '{:,.8f}'.format(x) if x != 0.0 else "" + ) + trials['Profit'] = trials['Profit'].apply( + lambda x: '{:,.2f}'.format(x) if not isna(x) else "" + ) + trials['Avg profit'] = trials['Avg profit'].apply( + lambda x: '{:,.2f}%'.format(x) if not isna(x) else "" + ) + trials['Avg duration'] = trials['Avg duration'].apply( + lambda x: '{:,.1f} m'.format(x) if not isna(x) else "" + ) + trials['Objective'] = trials['Objective'].apply( + lambda x: '{:,.5f}'.format(x) if x != 100000 else "" + ) + + trials = trials.drop(columns=['is_initial_point', 'is_best', 'is_profit']) + trials.to_csv(csv_file, index=False, header=True, mode='w', encoding='UTF-8') + print("CSV-File created!") + def has_space(self, space: str) -> bool: """ Tell if the space value is contained in the configuration diff --git a/freqtrade/optimize/hyperopt_loss_sharpe.py b/freqtrade/optimize/hyperopt_loss_sharpe.py index a4ec6f90a..29377bdd5 100644 --- a/freqtrade/optimize/hyperopt_loss_sharpe.py +++ b/freqtrade/optimize/hyperopt_loss_sharpe.py @@ -36,7 +36,7 @@ class SharpeHyperOptLoss(IHyperOptLoss): expected_returns_mean = total_profit.sum() / days_period up_stdev = np.std(total_profit) - if (np.std(total_profit) != 0.): + if up_stdev != 0: sharp_ratio = expected_returns_mean / up_stdev * np.sqrt(365) else: # Define high (negative) sharpe ratio to be clear that this is NOT optimal. diff --git a/freqtrade/optimize/hyperopt_loss_sharpe_daily.py b/freqtrade/optimize/hyperopt_loss_sharpe_daily.py index 5a8ebaa11..e4cd1d749 100644 --- a/freqtrade/optimize/hyperopt_loss_sharpe_daily.py +++ b/freqtrade/optimize/hyperopt_loss_sharpe_daily.py @@ -51,7 +51,7 @@ class SharpeHyperOptLossDaily(IHyperOptLoss): expected_returns_mean = total_profit.mean() up_stdev = total_profit.std() - if (up_stdev != 0.): + if up_stdev != 0: sharp_ratio = expected_returns_mean / up_stdev * math.sqrt(days_in_year) else: # Define high (negative) sharpe ratio to be clear that this is NOT optimal. diff --git a/freqtrade/optimize/hyperopt_loss_sortino.py b/freqtrade/optimize/hyperopt_loss_sortino.py index 83f644a43..d470a9977 100644 --- a/freqtrade/optimize/hyperopt_loss_sortino.py +++ b/freqtrade/optimize/hyperopt_loss_sortino.py @@ -39,7 +39,7 @@ class SortinoHyperOptLoss(IHyperOptLoss): results.loc[total_profit < 0, 'downside_returns'] = results['profit_percent'] down_stdev = np.std(results['downside_returns']) - if np.std(total_profit) != 0.0: + if down_stdev != 0: sortino_ratio = expected_returns_mean / down_stdev * np.sqrt(365) else: # Define high (negative) sortino ratio to be clear that this is NOT optimal. diff --git a/freqtrade/optimize/hyperopt_loss_sortino_daily.py b/freqtrade/optimize/hyperopt_loss_sortino_daily.py index 16dc26142..cd6a8bcc2 100644 --- a/freqtrade/optimize/hyperopt_loss_sortino_daily.py +++ b/freqtrade/optimize/hyperopt_loss_sortino_daily.py @@ -59,7 +59,7 @@ class SortinoHyperOptLossDaily(IHyperOptLoss): # where P = sum_daily["profit_percent_after_slippage"] down_stdev = math.sqrt((total_downside**2).sum() / len(total_downside)) - if (down_stdev != 0.): + if down_stdev != 0: sortino_ratio = expected_returns_mean / down_stdev * math.sqrt(days_in_year) else: # Define high (negative) sortino ratio to be clear that this is NOT optimal. diff --git a/freqtrade/pairlist/IPairList.py b/freqtrade/pairlist/IPairList.py index d45a329dd..35844a99e 100644 --- a/freqtrade/pairlist/IPairList.py +++ b/freqtrade/pairlist/IPairList.py @@ -67,21 +67,37 @@ class IPairList(ABC): """ @staticmethod - def verify_blacklist(pairlist: List[str], blacklist: List[str]) -> List[str]: + def verify_blacklist(pairlist: List[str], blacklist: List[str], + aswarning: bool) -> List[str]: """ Verify and remove items from pairlist - returning a filtered pairlist. + Logs a warning or info depending on `aswarning`. + Pairlists explicitly using this method shall use `aswarning=False`! + :param pairlist: Pairlist to validate + :param blacklist: Blacklist to validate pairlist against + :param aswarning: Log message as Warning or info + :return: pairlist - blacklisted pairs """ for pair in deepcopy(pairlist): if pair in blacklist: - logger.warning(f"Pair {pair} in your blacklist. Removing it from whitelist...") + if aswarning: + logger.warning(f"Pair {pair} in your blacklist. Removing it from whitelist...") + else: + logger.info(f"Pair {pair} in your blacklist. Removing it from whitelist...") pairlist.remove(pair) return pairlist - def _verify_blacklist(self, pairlist: List[str]) -> List[str]: + def _verify_blacklist(self, pairlist: List[str], aswarning: bool = True) -> List[str]: """ Proxy method to verify_blacklist for easy access for child classes. + Logs a warning or info depending on `aswarning`. + Pairlists explicitly using this method shall use aswarning=False! + :param pairlist: Pairlist to validate + :param aswarning: Log message as Warning or info. + :return: pairlist - blacklisted pairs """ - return IPairList.verify_blacklist(pairlist, self._pairlistmanager.blacklist) + return IPairList.verify_blacklist(pairlist, self._pairlistmanager.blacklist, + aswarning=aswarning) def _whitelist_for_active_markets(self, pairlist: List[str]) -> List[str]: """ @@ -113,6 +129,5 @@ class IPairList(ABC): if pair not in sanitized_whitelist: sanitized_whitelist.append(pair) - sanitized_whitelist = self._verify_blacklist(sanitized_whitelist) # We need to remove pairs that are unknown return sanitized_whitelist diff --git a/freqtrade/pairlist/VolumePairList.py b/freqtrade/pairlist/VolumePairList.py index d067d5e8a..9ce2adc9e 100644 --- a/freqtrade/pairlist/VolumePairList.py +++ b/freqtrade/pairlist/VolumePairList.py @@ -106,7 +106,7 @@ class VolumePairList(IPairList): # Validate whitelist to only have active market pairs pairs = self._whitelist_for_active_markets([s['symbol'] for s in sorted_tickers]) - pairs = self._verify_blacklist(pairs) + pairs = self._verify_blacklist(pairs, aswarning=False) # Limit to X number of pairs pairs = pairs[:self._number_pairs] logger.info(f"Searching {self._number_pairs} pairs: {pairs}") diff --git a/freqtrade/pairlist/pairlistmanager.py b/freqtrade/pairlist/pairlistmanager.py index 55828c6ef..5b4c5b602 100644 --- a/freqtrade/pairlist/pairlistmanager.py +++ b/freqtrade/pairlist/pairlistmanager.py @@ -91,6 +91,6 @@ class PairListManager(): pairlist = pl.filter_pairlist(pairlist, tickers) # Validation against blacklist happens after the pairlists to ensure blacklist is respected. - pairlist = IPairList.verify_blacklist(pairlist, self.blacklist) + pairlist = IPairList.verify_blacklist(pairlist, self.blacklist, True) self._whitelist = pairlist diff --git a/freqtrade/templates/base_config.json.j2 b/freqtrade/templates/base_config.json.j2 index 0049d59a0..134719273 100644 --- a/freqtrade/templates/base_config.json.j2 +++ b/freqtrade/templates/base_config.json.j2 @@ -24,7 +24,7 @@ "price_side": "ask", "use_order_book": false, "order_book_min": 1, - "order_book_max": 9, + "order_book_max": 1, "use_sell_signal": true, "sell_profit_only": false, "ignore_roi_if_buy_signal": false diff --git a/freqtrade/templates/base_hyperopt.py.j2 b/freqtrade/templates/base_hyperopt.py.j2 index 05ba08b81..ec787cbb6 100644 --- a/freqtrade/templates/base_hyperopt.py.j2 +++ b/freqtrade/templates/base_hyperopt.py.j2 @@ -21,7 +21,7 @@ class {{ hyperopt }}(IHyperOpt): """ This is a Hyperopt template to get you started. - More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md + More information in the documentation: https://www.freqtrade.io/en/latest/hyperopt/ You should: - Add any lib you need to build your hyperopt. @@ -29,11 +29,14 @@ class {{ hyperopt }}(IHyperOpt): You must keep: - The prototypes for the methods: populate_indicators, indicator_space, buy_strategy_generator. - The roi_space, generate_roi_table, stoploss_space methods are no longer required to be - copied in every custom hyperopt. However, you may override them if you need the - 'roi' and the 'stoploss' spaces that differ from the defaults offered by Freqtrade. - Sample implementation of these methods can be found in - https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/sample_hyperopt_advanced.py + 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 'roi' and 'stoploss' spaces that + differ from the defaults offered by Freqtrade. + Sample implementation of these methods will be copied to `user_data/hyperopts` when + creating the user-data directory using `freqtrade create-userdir --userdir user_data`, + or is available online under the following URL: + https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py. """ @staticmethod @@ -63,6 +66,9 @@ class {{ hyperopt }}(IHyperOpt): dataframe['close'], dataframe['sar'] )) + # Check that the candle had volume + conditions.append(dataframe['volume'] > 0) + if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), @@ -108,6 +114,9 @@ class {{ hyperopt }}(IHyperOpt): dataframe['sar'], dataframe['close'] )) + # Check that the candle had volume + conditions.append(dataframe['volume'] > 0) + if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), diff --git a/freqtrade/templates/sample_hyperopt.py b/freqtrade/templates/sample_hyperopt.py index f1dcb404a..0b6d030db 100644 --- a/freqtrade/templates/sample_hyperopt.py +++ b/freqtrade/templates/sample_hyperopt.py @@ -20,23 +20,28 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib class SampleHyperOpt(IHyperOpt): """ This is a sample Hyperopt to inspire you. - Feel free to customize it. - More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md + 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. + An easier way to get a new hyperopt file is by using + `freqtrade new-hyperopt --hyperopt MyCoolHyperopt`. + You must keep: - The prototypes for the methods: populate_indicators, indicator_space, buy_strategy_generator. - The roi_space, generate_roi_table, stoploss_space methods are no longer required to be - copied in every custom hyperopt. However, you may override them if you need the - 'roi' and the 'stoploss' spaces that differ from the defaults offered by Freqtrade. - Sample implementation of these methods can be found in - https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/sample_hyperopt_advanced.py + 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 'roi' and 'stoploss' spaces that + differ from the defaults offered by Freqtrade. + Sample implementation of these methods will be copied to `user_data/hyperopts` when + creating the user-data directory using `freqtrade create-userdir --userdir user_data`, + or is available online under the following URL: + https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py. """ @staticmethod @@ -73,6 +78,9 @@ class SampleHyperOpt(IHyperOpt): dataframe['close'], dataframe['sar'] )) + # Check that volume is not 0 + conditions.append(dataframe['volume'] > 0) + if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), @@ -133,6 +141,9 @@ class SampleHyperOpt(IHyperOpt): dataframe['sar'], dataframe['close'] )) + # Check that volume is not 0 + conditions.append(dataframe['volume'] > 0) + if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), diff --git a/freqtrade/templates/sample_hyperopt_advanced.py b/freqtrade/templates/sample_hyperopt_advanced.py index e66ef948b..7f05c4430 100644 --- a/freqtrade/templates/sample_hyperopt_advanced.py +++ b/freqtrade/templates/sample_hyperopt_advanced.py @@ -22,7 +22,7 @@ class AdvancedSampleHyperOpt(IHyperOpt): This is a sample hyperopt to inspire you. Feel free to customize it. - More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md + More information in the documentation: https://www.freqtrade.io/en/latest/hyperopt/ You should: - Rename the class name to some unique name. @@ -32,8 +32,9 @@ class AdvancedSampleHyperOpt(IHyperOpt): You must keep: - The prototypes for the methods: populate_indicators, indicator_space, buy_strategy_generator. - The roi_space, generate_roi_table, stoploss_space methods are no longer required to be - copied in every custom hyperopt. However, you may override them if you need the + 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. @@ -92,6 +93,9 @@ class AdvancedSampleHyperOpt(IHyperOpt): dataframe['close'], dataframe['sar'] )) + # Check that volume is not 0 + conditions.append(dataframe['volume'] > 0) + if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), @@ -152,6 +156,9 @@ class AdvancedSampleHyperOpt(IHyperOpt): dataframe['sar'], dataframe['close'] )) + # Check that volume is not 0 + conditions.append(dataframe['volume'] > 0) + if conditions: dataframe.loc[ reduce(lambda x, y: x & y, conditions), diff --git a/requirements-common.txt b/requirements-common.txt index a844e81bc..784eef93c 100644 --- a/requirements-common.txt +++ b/requirements-common.txt @@ -1,13 +1,13 @@ # requirements without requirements installable via conda # mainly used for Raspberry pi installs -ccxt==1.23.30 +ccxt==1.23.81 SQLAlchemy==1.3.13 python-telegram-bot==12.4.2 arrow==0.15.5 cachetools==4.0.0 requests==2.23.0 urllib3==1.25.8 -wrapt==1.12.0 +wrapt==1.12.1 jsonschema==3.2.0 TA-Lib==0.4.17 tabulate==0.8.6 @@ -30,4 +30,4 @@ flask==1.1.1 colorama==0.4.3 # Building config files interactively questionary==1.5.1 -prompt-toolkit==3.0.3 +prompt-toolkit==3.0.4 diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt index c713317ec..c7e586a33 100644 --- a/requirements-hyperopt.txt +++ b/requirements-hyperopt.txt @@ -3,7 +3,7 @@ # Required for hyperopt scipy==1.4.1 -scikit-learn==0.22.2 +scikit-learn==0.22.2.post1 scikit-optimize==0.7.4 filelock==3.0.12 joblib==0.14.1 diff --git a/requirements-plot.txt b/requirements-plot.txt index a70c3e0cf..381334a66 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,5 +1,5 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==4.5.2 +plotly==4.5.3 diff --git a/tests/commands/test_commands.py b/tests/commands/test_commands.py index 3e1c0a581..4530cd03d 100644 --- a/tests/commands/test_commands.py +++ b/tests/commands/test_commands.py @@ -902,6 +902,21 @@ def test_hyperopt_list(mocker, capsys, hyperopt_results): assert all(x not in captured.out for x in [" 1/12", " 3/12", " 4/12", " 5/12", " 7/12", " 8/12" " 9/12", " 10/12", " 11/12", " 12/12"]) + args = [ + "hyperopt-list", + "--no-details", + "--export-csv", "test_file.csv" + ] + pargs = get_args(args) + pargs['config'] = None + start_hyperopt_list(pargs) + captured = capsys.readouterr() + assert all(x in captured.out + for x in ["CSV-File created!"]) + f = Path("test_file.csv") + assert 'Best,1,2,-1.25%,-0.00125625,,-2.51,"3,930.0 m",0.43662' in f.read_text() + assert f.is_file() + f.unlink() def test_hyperopt_show(mocker, capsys, hyperopt_results): diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index 1b6e23ffa..f78b44704 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -241,7 +241,7 @@ def test_setup_optimize_configuration_unlimited_stake_amount(mocker, default_con '--strategy', 'DefaultStrategy', ] - with pytest.raises(DependencyException, match=r'.*stake amount.*'): + with pytest.raises(DependencyException, match=r'.`stake_amount`.*'): setup_optimize_configuration(get_args(args), RunMode.BACKTEST) diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index eefc6b28a..b5106be0c 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -10,10 +10,11 @@ import pytest from arrow import Arrow from filelock import Timeout +from freqtrade import constants from freqtrade.commands.optimize_commands import (setup_optimize_configuration, start_hyperopt) from freqtrade.data.history import load_data -from freqtrade.exceptions import OperationalException +from freqtrade.exceptions import DependencyException, OperationalException from freqtrade.optimize.default_hyperopt import DefaultHyperOpt from freqtrade.optimize.default_hyperopt_loss import DefaultHyperOptLoss from freqtrade.optimize.hyperopt import Hyperopt @@ -158,6 +159,21 @@ def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplo assert log_has('Parameter --print-all detected ...', caplog) +def test_setup_hyperopt_configuration_unlimited_stake_amount(mocker, default_conf, caplog) -> None: + default_conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT + + patched_configuration_load_config_file(mocker, default_conf) + + args = [ + 'hyperopt', + '--config', 'config.json', + '--hyperopt', 'DefaultHyperOpt', + ] + + with pytest.raises(DependencyException, match=r'.`stake_amount`.*'): + setup_optimize_configuration(get_args(args), RunMode.HYPEROPT) + + def test_hyperoptresolver(mocker, default_conf, caplog) -> None: patched_configuration_load_config_file(mocker, default_conf) diff --git a/tests/pairlist/test_pairlist.py b/tests/pairlist/test_pairlist.py index b8a4be037..1ce1151b7 100644 --- a/tests/pairlist/test_pairlist.py +++ b/tests/pairlist/test_pairlist.py @@ -240,8 +240,6 @@ def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): (['ETH/BTC', 'TKN/BTC', 'ETH/USDT'], "is not compatible with your stake currency"), # BCH/BTC not available (['ETH/BTC', 'TKN/BTC', 'BCH/BTC'], "is not compatible with exchange"), - # BLK/BTC in blacklist - (['ETH/BTC', 'TKN/BTC', 'BLK/BTC'], "in your blacklist. Removing "), # BTT/BTC is inactive (['ETH/BTC', 'TKN/BTC', 'BTT/BTC'], "Market is not active") ])