Merge pull request #7092 from freqtrade/fix/hyperopt_inherit

hyperopt inherit fix
This commit is contained in:
Matthias 2022-07-16 15:17:14 +02:00 committed by GitHub
commit 3eb2131d0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 73 additions and 21 deletions

View File

@ -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:
""" """

View File

@ -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

View File

@ -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))

View File

@ -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