Merge pull request #7092 from freqtrade/fix/hyperopt_inherit
hyperopt inherit fix
This commit is contained in:
		| @@ -175,23 +175,11 @@ class Exchange: | |||||||
|         logger.info(f'Using Exchange "{self.name}"') |         logger.info(f'Using Exchange "{self.name}"') | ||||||
|  |  | ||||||
|         if validate: |         if validate: | ||||||
|             # Check if timeframe is available |  | ||||||
|             self.validate_timeframes(config.get('timeframe')) |  | ||||||
|  |  | ||||||
|             # Initial markets load |             # Initial markets load | ||||||
|             self._load_markets() |             self._load_markets() | ||||||
|  |             self.validate_config(config) | ||||||
|             # Check if all pairs are available |  | ||||||
|             self.validate_stakecurrency(config['stake_currency']) |  | ||||||
|             if not exchange_config.get('skip_pair_validation'): |  | ||||||
|                 self.validate_pairs(config['exchange']['pair_whitelist']) |  | ||||||
|             self.validate_ordertypes(config.get('order_types', {})) |  | ||||||
|             self.validate_order_time_in_force(config.get('order_time_in_force', {})) |  | ||||||
|             self.required_candle_call_count = self.validate_required_startup_candles( |             self.required_candle_call_count = self.validate_required_startup_candles( | ||||||
|                 config.get('startup_candle_count', 0), config.get('timeframe', '')) |                 config.get('startup_candle_count', 0), config.get('timeframe', '')) | ||||||
|             self.validate_trading_mode_and_margin_mode(self.trading_mode, self.margin_mode) |  | ||||||
|             self.validate_pricing(config['exit_pricing']) |  | ||||||
|             self.validate_pricing(config['entry_pricing']) |  | ||||||
|  |  | ||||||
|         # Converts the interval provided in minutes in config to seconds |         # Converts the interval provided in minutes in config to seconds | ||||||
|         self.markets_refresh_interval: int = exchange_config.get( |         self.markets_refresh_interval: int = exchange_config.get( | ||||||
| @@ -214,6 +202,20 @@ class Exchange: | |||||||
|             logger.info("Closing async ccxt session.") |             logger.info("Closing async ccxt session.") | ||||||
|             self.loop.run_until_complete(self._api_async.close()) |             self.loop.run_until_complete(self._api_async.close()) | ||||||
|  |  | ||||||
|  |     def validate_config(self, config): | ||||||
|  |         # Check if timeframe is available | ||||||
|  |         self.validate_timeframes(config.get('timeframe')) | ||||||
|  |  | ||||||
|  |         # Check if all pairs are available | ||||||
|  |         self.validate_stakecurrency(config['stake_currency']) | ||||||
|  |         if not config['exchange'].get('skip_pair_validation'): | ||||||
|  |             self.validate_pairs(config['exchange']['pair_whitelist']) | ||||||
|  |         self.validate_ordertypes(config.get('order_types', {})) | ||||||
|  |         self.validate_order_time_in_force(config.get('order_time_in_force', {})) | ||||||
|  |         self.validate_trading_mode_and_margin_mode(self.trading_mode, self.margin_mode) | ||||||
|  |         self.validate_pricing(config['exit_pricing']) | ||||||
|  |         self.validate_pricing(config['entry_pricing']) | ||||||
|  |  | ||||||
|     def _init_ccxt(self, exchange_config: Dict[str, Any], ccxt_module: CcxtModuleType = ccxt, |     def _init_ccxt(self, exchange_config: Dict[str, Any], ccxt_module: CcxtModuleType = ccxt, | ||||||
|                    ccxt_kwargs: Dict = {}) -> ccxt.Exchange: |                    ccxt_kwargs: Dict = {}) -> ccxt.Exchange: | ||||||
|         """ |         """ | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ This module contains the hyperopt logic | |||||||
|  |  | ||||||
| import logging | import logging | ||||||
| import random | import random | ||||||
|  | import sys | ||||||
| import warnings | import warnings | ||||||
| from datetime import datetime, timezone | from datetime import datetime, timezone | ||||||
| from math import ceil | from math import ceil | ||||||
| @@ -17,6 +18,7 @@ import rapidjson | |||||||
| from colorama import Fore, Style | from colorama import Fore, Style | ||||||
| from colorama import init as colorama_init | from colorama import init as colorama_init | ||||||
| from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects | from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects | ||||||
|  | from joblib.externals import cloudpickle | ||||||
| from pandas import DataFrame | from pandas import DataFrame | ||||||
|  |  | ||||||
| from freqtrade.constants import DATETIME_PRINT_FORMAT, FTHYPT_FILEVERSION, LAST_BT_RESULT_FN | from freqtrade.constants import DATETIME_PRINT_FORMAT, FTHYPT_FILEVERSION, LAST_BT_RESULT_FN | ||||||
| @@ -87,6 +89,7 @@ class Hyperopt: | |||||||
|         self.backtesting._set_strategy(self.backtesting.strategylist[0]) |         self.backtesting._set_strategy(self.backtesting.strategylist[0]) | ||||||
|         self.custom_hyperopt.strategy = self.backtesting.strategy |         self.custom_hyperopt.strategy = self.backtesting.strategy | ||||||
|  |  | ||||||
|  |         self.hyperopt_pickle_magic(self.backtesting.strategy.__class__.__bases__) | ||||||
|         self.custom_hyperoptloss: IHyperOptLoss = HyperOptLossResolver.load_hyperoptloss( |         self.custom_hyperoptloss: IHyperOptLoss = HyperOptLossResolver.load_hyperoptloss( | ||||||
|             self.config) |             self.config) | ||||||
|         self.calculate_loss = self.custom_hyperoptloss.hyperopt_loss_function |         self.calculate_loss = self.custom_hyperoptloss.hyperopt_loss_function | ||||||
| @@ -137,6 +140,17 @@ class Hyperopt: | |||||||
|                 logger.info(f"Removing `{p}`.") |                 logger.info(f"Removing `{p}`.") | ||||||
|                 p.unlink() |                 p.unlink() | ||||||
|  |  | ||||||
|  |     def hyperopt_pickle_magic(self, bases) -> None: | ||||||
|  |         """ | ||||||
|  |         Hyperopt magic to allow strategy inheritance across files. | ||||||
|  |         For this to properly work, we need to register the module of the imported class | ||||||
|  |         to pickle as value. | ||||||
|  |         """ | ||||||
|  |         for modules in bases: | ||||||
|  |             if modules.__name__ != 'IStrategy': | ||||||
|  |                 cloudpickle.register_pickle_by_value(sys.modules[modules.__module__]) | ||||||
|  |                 self.hyperopt_pickle_magic(modules.__bases__) | ||||||
|  |  | ||||||
|     def _get_params_dict(self, dimensions: List[Dimension], raw_params: List[Any]) -> Dict: |     def _get_params_dict(self, dimensions: List[Dimension], raw_params: List[Any]) -> Dict: | ||||||
|  |  | ||||||
|         # Ensure the number of dimensions match |         # Ensure the number of dimensions match | ||||||
|   | |||||||
| @@ -112,11 +112,8 @@ def patch_exchange( | |||||||
|     mock_supported_modes=True |     mock_supported_modes=True | ||||||
| ) -> None: | ) -> None: | ||||||
|     mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock(return_value={})) |     mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock(return_value={})) | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) |     mocker.patch('freqtrade.exchange.Exchange.validate_config', MagicMock()) | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) |     mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock()) |  | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency', MagicMock()) |  | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.validate_pricing') |  | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value=id)) |     mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value=id)) | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value=id.title())) |     mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value=id.title())) | ||||||
|     mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=2)) |     mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=2)) | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| # pragma pylint: disable=missing-docstring,W0212,C0103 | # pragma pylint: disable=missing-docstring,W0212,C0103 | ||||||
| from datetime import datetime, timedelta | from datetime import datetime, timedelta | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from unittest.mock import ANY, MagicMock | from unittest.mock import ANY, MagicMock, PropertyMock | ||||||
|  |  | ||||||
| import pandas as pd | import pandas as pd | ||||||
| import pytest | import pytest | ||||||
| @@ -18,8 +18,8 @@ from freqtrade.optimize.hyperopt_tools import HyperoptTools | |||||||
| from freqtrade.optimize.optimize_reports import generate_strategy_stats | from freqtrade.optimize.optimize_reports import generate_strategy_stats | ||||||
| from freqtrade.optimize.space import SKDecimal | from freqtrade.optimize.space import SKDecimal | ||||||
| from freqtrade.strategy import IntParameter | from freqtrade.strategy import IntParameter | ||||||
| from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, log_has, log_has_re, patch_exchange, | from tests.conftest import (CURRENT_TEST_STRATEGY, get_args, get_markets, log_has, log_has_re, | ||||||
|                             patched_configuration_load_config_file) |                             patch_exchange, patched_configuration_load_config_file) | ||||||
|  |  | ||||||
|  |  | ||||||
| def generate_result_metrics(): | def generate_result_metrics(): | ||||||
| @@ -855,7 +855,7 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None: | |||||||
|         'strategy': 'HyperoptableStrategy', |         'strategy': 'HyperoptableStrategy', | ||||||
|         'user_data_dir': Path(tmpdir), |         'user_data_dir': Path(tmpdir), | ||||||
|         'hyperopt_random_state': 42, |         'hyperopt_random_state': 42, | ||||||
|         'spaces': ['all'] |         'spaces': ['all'], | ||||||
|     }) |     }) | ||||||
|     hyperopt = Hyperopt(hyperopt_conf) |     hyperopt = Hyperopt(hyperopt_conf) | ||||||
|     hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0) |     hyperopt.backtesting.exchange.get_max_leverage = MagicMock(return_value=1.0) | ||||||
| @@ -883,6 +883,45 @@ def test_in_strategy_auto_hyperopt(mocker, hyperopt_conf, tmpdir, fee) -> None: | |||||||
|         hyperopt.get_optimizer([], 2) |         hyperopt.get_optimizer([], 2) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_in_strategy_auto_hyperopt_with_parallel(mocker, hyperopt_conf, tmpdir, fee) -> None: | ||||||
|  |     mocker.patch('freqtrade.exchange.Exchange.validate_config', MagicMock()) | ||||||
|  |     mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) | ||||||
|  |     mocker.patch('freqtrade.exchange.Exchange._load_markets') | ||||||
|  |     mocker.patch('freqtrade.exchange.Exchange.markets', | ||||||
|  |                  PropertyMock(return_value=get_markets())) | ||||||
|  |     (Path(tmpdir) / 'hyperopt_results').mkdir(parents=True) | ||||||
|  |     # No hyperopt needed | ||||||
|  |     hyperopt_conf.update({ | ||||||
|  |         'strategy': 'HyperoptableStrategy', | ||||||
|  |         'user_data_dir': Path(tmpdir), | ||||||
|  |         'hyperopt_random_state': 42, | ||||||
|  |         'spaces': ['all'], | ||||||
|  |         # Enforce parallelity | ||||||
|  |         'epochs': 2, | ||||||
|  |         'hyperopt_jobs': 2, | ||||||
|  |         'fee': fee.return_value, | ||||||
|  |     }) | ||||||
|  |     hyperopt = Hyperopt(hyperopt_conf) | ||||||
|  |     hyperopt.backtesting.exchange.get_max_leverage = lambda *x, **xx: 1.0 | ||||||
|  |     hyperopt.backtesting.exchange.get_min_pair_stake_amount = lambda *x, **xx: 1.0 | ||||||
|  |     hyperopt.backtesting.exchange.get_max_pair_stake_amount = lambda *x, **xx: 100.0 | ||||||
|  |  | ||||||
|  |     assert isinstance(hyperopt.custom_hyperopt, HyperOptAuto) | ||||||
|  |     assert isinstance(hyperopt.backtesting.strategy.buy_rsi, IntParameter) | ||||||
|  |     assert hyperopt.backtesting.strategy.bot_loop_started is True | ||||||
|  |  | ||||||
|  |     assert hyperopt.backtesting.strategy.buy_rsi.in_space is True | ||||||
|  |     assert hyperopt.backtesting.strategy.buy_rsi.value == 35 | ||||||
|  |     assert hyperopt.backtesting.strategy.sell_rsi.value == 74 | ||||||
|  |     assert hyperopt.backtesting.strategy.protection_cooldown_lookback.value == 30 | ||||||
|  |     buy_rsi_range = hyperopt.backtesting.strategy.buy_rsi.range | ||||||
|  |     assert isinstance(buy_rsi_range, range) | ||||||
|  |     # Range from 0 - 50 (inclusive) | ||||||
|  |     assert len(list(buy_rsi_range)) == 51 | ||||||
|  |  | ||||||
|  |     hyperopt.start() | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_SKDecimal(): | def test_SKDecimal(): | ||||||
|     space = SKDecimal(1, 2, decimals=2) |     space = SKDecimal(1, 2, decimals=2) | ||||||
|     assert 1.5 in space |     assert 1.5 in space | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user