Merge pull request #4596 from rokups/rk/hyper-strategy
Support for creating auto-hyperoptable strategies.
This commit is contained in:
@@ -195,6 +195,7 @@ AVAILABLE_CLI_OPTIONS = {
|
||||
'--hyperopt',
|
||||
help='Specify hyperopt class name which will be used by the bot.',
|
||||
metavar='NAME',
|
||||
required=False,
|
||||
),
|
||||
"hyperopt_path": Arg(
|
||||
'--hyperopt-path',
|
||||
|
@@ -26,6 +26,7 @@ from freqtrade.data.history import get_timerange
|
||||
from freqtrade.misc import file_dump_json, plural
|
||||
from freqtrade.optimize.backtesting import Backtesting
|
||||
# Import IHyperOpt and IHyperOptLoss to allow unpickling classes from these modules
|
||||
from freqtrade.optimize.hyperopt_auto import HyperOptAuto
|
||||
from freqtrade.optimize.hyperopt_interface import IHyperOpt # noqa: F401
|
||||
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F401
|
||||
from freqtrade.optimize.hyperopt_tools import HyperoptTools
|
||||
@@ -61,14 +62,18 @@ class Hyperopt:
|
||||
hyperopt = Hyperopt(config)
|
||||
hyperopt.start()
|
||||
"""
|
||||
custom_hyperopt: IHyperOpt
|
||||
|
||||
def __init__(self, config: Dict[str, Any]) -> None:
|
||||
self.config = config
|
||||
|
||||
self.backtesting = Backtesting(self.config)
|
||||
|
||||
self.custom_hyperopt = HyperOptResolver.load_hyperopt(self.config)
|
||||
self.custom_hyperopt.__class__.strategy = self.backtesting.strategy
|
||||
if not self.config.get('hyperopt'):
|
||||
self.custom_hyperopt = HyperOptAuto(self.config)
|
||||
else:
|
||||
self.custom_hyperopt = HyperOptResolver.load_hyperopt(self.config)
|
||||
self.custom_hyperopt.strategy = self.backtesting.strategy
|
||||
|
||||
self.custom_hyperoptloss = HyperOptLossResolver.load_hyperoptloss(self.config)
|
||||
self.calculate_loss = self.custom_hyperoptloss.hyperopt_loss_function
|
||||
|
89
freqtrade/optimize/hyperopt_auto.py
Normal file
89
freqtrade/optimize/hyperopt_auto.py
Normal file
@@ -0,0 +1,89 @@
|
||||
"""
|
||||
HyperOptAuto class.
|
||||
This module implements a convenience auto-hyperopt class, which can be used together with strategies
|
||||
that implement IHyperStrategy interface.
|
||||
"""
|
||||
from contextlib import suppress
|
||||
from typing import Any, Callable, Dict, List
|
||||
|
||||
from pandas import DataFrame
|
||||
|
||||
|
||||
with suppress(ImportError):
|
||||
from skopt.space import Dimension
|
||||
|
||||
from freqtrade.optimize.hyperopt_interface import IHyperOpt
|
||||
|
||||
|
||||
class HyperOptAuto(IHyperOpt):
|
||||
"""
|
||||
This class delegates functionality to Strategy(IHyperStrategy) and Strategy.HyperOpt classes.
|
||||
Most of the time Strategy.HyperOpt class would only implement indicator_space and
|
||||
sell_indicator_space methods, but other hyperopt methods can be overridden as well.
|
||||
"""
|
||||
|
||||
def buy_strategy_generator(self, params: Dict[str, Any]) -> Callable:
|
||||
def populate_buy_trend(dataframe: DataFrame, metadata: dict):
|
||||
for attr_name, attr in self.strategy.enumerate_parameters('buy'):
|
||||
if attr.optimize:
|
||||
# noinspection PyProtectedMember
|
||||
attr._set_value(params[attr_name])
|
||||
return self.strategy.populate_buy_trend(dataframe, metadata)
|
||||
|
||||
return populate_buy_trend
|
||||
|
||||
def sell_strategy_generator(self, params: Dict[str, Any]) -> Callable:
|
||||
def populate_buy_trend(dataframe: DataFrame, metadata: dict):
|
||||
for attr_name, attr in self.strategy.enumerate_parameters('sell'):
|
||||
if attr.optimize:
|
||||
# noinspection PyProtectedMember
|
||||
attr._set_value(params[attr_name])
|
||||
return self.strategy.populate_sell_trend(dataframe, metadata)
|
||||
|
||||
return populate_buy_trend
|
||||
|
||||
def _get_func(self, name) -> Callable:
|
||||
"""
|
||||
Return a function defined in Strategy.HyperOpt class, or one defined in super() class.
|
||||
:param name: function name.
|
||||
:return: a requested function.
|
||||
"""
|
||||
hyperopt_cls = getattr(self.strategy, 'HyperOpt', None)
|
||||
default_func = getattr(super(), name)
|
||||
if hyperopt_cls:
|
||||
return getattr(hyperopt_cls, name, default_func)
|
||||
else:
|
||||
return default_func
|
||||
|
||||
def _generate_indicator_space(self, category):
|
||||
for attr_name, attr in self.strategy.enumerate_parameters(category):
|
||||
if attr.optimize:
|
||||
yield attr.get_space(attr_name)
|
||||
|
||||
def _get_indicator_space(self, category, fallback_method_name):
|
||||
indicator_space = list(self._generate_indicator_space(category))
|
||||
if len(indicator_space) > 0:
|
||||
return indicator_space
|
||||
else:
|
||||
return self._get_func(fallback_method_name)()
|
||||
|
||||
def indicator_space(self) -> List['Dimension']:
|
||||
return self._get_indicator_space('buy', 'indicator_space')
|
||||
|
||||
def sell_indicator_space(self) -> List['Dimension']:
|
||||
return self._get_indicator_space('sell', 'sell_indicator_space')
|
||||
|
||||
def generate_roi_table(self, params: Dict) -> Dict[int, float]:
|
||||
return self._get_func('generate_roi_table')(params)
|
||||
|
||||
def roi_space(self) -> List['Dimension']:
|
||||
return self._get_func('roi_space')()
|
||||
|
||||
def stoploss_space(self) -> List['Dimension']:
|
||||
return self._get_func('stoploss_space')()
|
||||
|
||||
def generate_trailing_params(self, params: Dict) -> Dict:
|
||||
return self._get_func('generate_trailing_params')(params)
|
||||
|
||||
def trailing_space(self) -> List['Dimension']:
|
||||
return self._get_func('trailing_space')()
|
@@ -44,36 +44,31 @@ class IHyperOpt(ABC):
|
||||
IHyperOpt.ticker_interval = str(config['timeframe']) # DEPRECATED
|
||||
IHyperOpt.timeframe = str(config['timeframe'])
|
||||
|
||||
@staticmethod
|
||||
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
|
||||
def buy_strategy_generator(self, params: Dict[str, Any]) -> Callable:
|
||||
"""
|
||||
Create a buy strategy generator.
|
||||
"""
|
||||
raise OperationalException(_format_exception_message('buy_strategy_generator', 'buy'))
|
||||
|
||||
@staticmethod
|
||||
def sell_strategy_generator(params: Dict[str, Any]) -> Callable:
|
||||
def sell_strategy_generator(self, params: Dict[str, Any]) -> Callable:
|
||||
"""
|
||||
Create a sell strategy generator.
|
||||
"""
|
||||
raise OperationalException(_format_exception_message('sell_strategy_generator', 'sell'))
|
||||
|
||||
@staticmethod
|
||||
def indicator_space() -> List[Dimension]:
|
||||
def indicator_space(self) -> List[Dimension]:
|
||||
"""
|
||||
Create an indicator space.
|
||||
"""
|
||||
raise OperationalException(_format_exception_message('indicator_space', 'buy'))
|
||||
|
||||
@staticmethod
|
||||
def sell_indicator_space() -> List[Dimension]:
|
||||
def sell_indicator_space(self) -> List[Dimension]:
|
||||
"""
|
||||
Create a sell indicator space.
|
||||
"""
|
||||
raise OperationalException(_format_exception_message('sell_indicator_space', 'sell'))
|
||||
|
||||
@staticmethod
|
||||
def generate_roi_table(params: Dict) -> Dict[int, float]:
|
||||
def generate_roi_table(self, params: Dict) -> Dict[int, float]:
|
||||
"""
|
||||
Create a ROI table.
|
||||
|
||||
@@ -88,8 +83,7 @@ class IHyperOpt(ABC):
|
||||
|
||||
return roi_table
|
||||
|
||||
@staticmethod
|
||||
def roi_space() -> List[Dimension]:
|
||||
def roi_space(self) -> List[Dimension]:
|
||||
"""
|
||||
Create a ROI space.
|
||||
|
||||
@@ -109,7 +103,7 @@ class IHyperOpt(ABC):
|
||||
roi_t_alpha = 1.0
|
||||
roi_p_alpha = 1.0
|
||||
|
||||
timeframe_min = timeframe_to_minutes(IHyperOpt.ticker_interval)
|
||||
timeframe_min = timeframe_to_minutes(self.timeframe)
|
||||
|
||||
# We define here limits for the ROI space parameters automagically adapted to the
|
||||
# timeframe used by the bot:
|
||||
@@ -145,7 +139,7 @@ class IHyperOpt(ABC):
|
||||
'roi_p2': roi_limits['roi_p2_min'],
|
||||
'roi_p3': roi_limits['roi_p3_min'],
|
||||
}
|
||||
logger.info(f"Min roi table: {round_dict(IHyperOpt.generate_roi_table(p), 5)}")
|
||||
logger.info(f"Min roi table: {round_dict(self.generate_roi_table(p), 5)}")
|
||||
p = {
|
||||
'roi_t1': roi_limits['roi_t1_max'],
|
||||
'roi_t2': roi_limits['roi_t2_max'],
|
||||
@@ -154,7 +148,7 @@ class IHyperOpt(ABC):
|
||||
'roi_p2': roi_limits['roi_p2_max'],
|
||||
'roi_p3': roi_limits['roi_p3_max'],
|
||||
}
|
||||
logger.info(f"Max roi table: {round_dict(IHyperOpt.generate_roi_table(p), 5)}")
|
||||
logger.info(f"Max roi table: {round_dict(self.generate_roi_table(p), 5)}")
|
||||
|
||||
return [
|
||||
Integer(roi_limits['roi_t1_min'], roi_limits['roi_t1_max'], name='roi_t1'),
|
||||
@@ -165,8 +159,7 @@ class IHyperOpt(ABC):
|
||||
Real(roi_limits['roi_p3_min'], roi_limits['roi_p3_max'], name='roi_p3'),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def stoploss_space() -> List[Dimension]:
|
||||
def stoploss_space(self) -> List[Dimension]:
|
||||
"""
|
||||
Create a stoploss space.
|
||||
|
||||
@@ -177,8 +170,7 @@ class IHyperOpt(ABC):
|
||||
Real(-0.35, -0.02, name='stoploss'),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def generate_trailing_params(params: Dict) -> Dict:
|
||||
def generate_trailing_params(self, params: Dict) -> Dict:
|
||||
"""
|
||||
Create dict with trailing stop parameters.
|
||||
"""
|
||||
@@ -190,8 +182,7 @@ class IHyperOpt(ABC):
|
||||
'trailing_only_offset_is_reached': params['trailing_only_offset_is_reached'],
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def trailing_space() -> List[Dimension]:
|
||||
def trailing_space(self) -> List[Dimension]:
|
||||
"""
|
||||
Create a trailing stoploss space.
|
||||
|
||||
|
@@ -1,5 +1,7 @@
|
||||
# flake8: noqa: F401
|
||||
from freqtrade.exchange import (timeframe_to_minutes, timeframe_to_msecs, timeframe_to_next_date,
|
||||
timeframe_to_prev_date, timeframe_to_seconds)
|
||||
from freqtrade.strategy.hyper import (CategoricalParameter, DecimalParameter, IntParameter,
|
||||
RealParameter)
|
||||
from freqtrade.strategy.interface import IStrategy
|
||||
from freqtrade.strategy.strategy_helper import merge_informative_pair, stoploss_from_open
|
||||
|
269
freqtrade/strategy/hyper.py
Normal file
269
freqtrade/strategy/hyper.py
Normal file
@@ -0,0 +1,269 @@
|
||||
"""
|
||||
IHyperStrategy interface, hyperoptable Parameter class.
|
||||
This module defines a base class for auto-hyperoptable strategies.
|
||||
"""
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from contextlib import suppress
|
||||
from typing import Any, Iterator, Optional, Sequence, Tuple, Union
|
||||
|
||||
|
||||
with suppress(ImportError):
|
||||
from skopt.space import Integer, Real, Categorical
|
||||
|
||||
from freqtrade.exceptions import OperationalException
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseParameter(ABC):
|
||||
"""
|
||||
Defines a parameter that can be optimized by hyperopt.
|
||||
"""
|
||||
category: Optional[str]
|
||||
default: Any
|
||||
value: Any
|
||||
opt_range: Sequence[Any]
|
||||
|
||||
def __init__(self, *, opt_range: Sequence[Any], default: Any, space: Optional[str] = None,
|
||||
optimize: bool = True, load: bool = True, **kwargs):
|
||||
"""
|
||||
Initialize hyperopt-optimizable parameter.
|
||||
:param space: A parameter category. Can be 'buy' or 'sell'. This parameter is optional if
|
||||
parameter field
|
||||
name is prefixed with 'buy_' or 'sell_'.
|
||||
:param optimize: Include parameter in hyperopt optimizations.
|
||||
:param load: Load parameter value from {space}_params.
|
||||
:param kwargs: Extra parameters to skopt.space.(Integer|Real|Categorical).
|
||||
"""
|
||||
if 'name' in kwargs:
|
||||
raise OperationalException(
|
||||
'Name is determined by parameter field name and can not be specified manually.')
|
||||
self.category = space
|
||||
self._space_params = kwargs
|
||||
self.value = default
|
||||
self.opt_range = opt_range
|
||||
self.optimize = optimize
|
||||
self.load = load
|
||||
|
||||
def __repr__(self):
|
||||
return f'{self.__class__.__name__}({self.value})'
|
||||
|
||||
@abstractmethod
|
||||
def get_space(self, name: str) -> Union['Integer', 'Real', 'Categorical']:
|
||||
"""
|
||||
Get-space - will be used by Hyperopt to get the hyperopt Space
|
||||
"""
|
||||
|
||||
def _set_value(self, value: Any):
|
||||
"""
|
||||
Update current value. Used by hyperopt functions for the purpose where optimization and
|
||||
value spaces differ.
|
||||
:param value: A numerical value.
|
||||
"""
|
||||
self.value = value
|
||||
|
||||
|
||||
class IntParameter(BaseParameter):
|
||||
default: int
|
||||
value: int
|
||||
opt_range: Sequence[int]
|
||||
|
||||
def __init__(self, low: Union[int, Sequence[int]], high: Optional[int] = None, *, default: int,
|
||||
space: Optional[str] = None, optimize: bool = True, load: bool = True, **kwargs):
|
||||
"""
|
||||
Initialize hyperopt-optimizable integer parameter.
|
||||
:param low: Lower end (inclusive) of optimization space or [low, high].
|
||||
:param high: Upper end (inclusive) of optimization space.
|
||||
Must be none of entire range is passed first parameter.
|
||||
:param default: A default value.
|
||||
:param space: A parameter category. Can be 'buy' or 'sell'. This parameter is optional if
|
||||
parameter fieldname is prefixed with 'buy_' or 'sell_'.
|
||||
:param optimize: Include parameter in hyperopt optimizations.
|
||||
:param load: Load parameter value from {space}_params.
|
||||
:param kwargs: Extra parameters to skopt.space.Integer.
|
||||
"""
|
||||
if high is not None and isinstance(low, Sequence):
|
||||
raise OperationalException('IntParameter space invalid.')
|
||||
if high is None or isinstance(low, Sequence):
|
||||
if not isinstance(low, Sequence) or len(low) != 2:
|
||||
raise OperationalException('IntParameter space must be [low, high]')
|
||||
opt_range = low
|
||||
else:
|
||||
opt_range = [low, high]
|
||||
super().__init__(opt_range=opt_range, default=default, space=space, optimize=optimize,
|
||||
load=load, **kwargs)
|
||||
|
||||
def get_space(self, name: str) -> 'Integer':
|
||||
"""
|
||||
Create skopt optimization space.
|
||||
:param name: A name of parameter field.
|
||||
"""
|
||||
return Integer(*self.opt_range, name=name, **self._space_params)
|
||||
|
||||
|
||||
class RealParameter(BaseParameter):
|
||||
default: float
|
||||
value: float
|
||||
opt_range: Sequence[float]
|
||||
|
||||
def __init__(self, low: Union[float, Sequence[float]], high: Optional[float] = None, *,
|
||||
default: float, space: Optional[str] = None, optimize: bool = True,
|
||||
load: bool = True, **kwargs):
|
||||
"""
|
||||
Initialize hyperopt-optimizable floating point parameter with unlimited precision.
|
||||
:param low: Lower end (inclusive) of optimization space or [low, high].
|
||||
:param high: Upper end (inclusive) of optimization space.
|
||||
Must be none if entire range is passed first parameter.
|
||||
:param default: A default value.
|
||||
:param space: A parameter category. Can be 'buy' or 'sell'. This parameter is optional if
|
||||
parameter fieldname is prefixed with 'buy_' or 'sell_'.
|
||||
:param optimize: Include parameter in hyperopt optimizations.
|
||||
:param load: Load parameter value from {space}_params.
|
||||
:param kwargs: Extra parameters to skopt.space.Real.
|
||||
"""
|
||||
if high is not None and isinstance(low, Sequence):
|
||||
raise OperationalException(f'{self.__class__.__name__} space invalid.')
|
||||
if high is None or isinstance(low, Sequence):
|
||||
if not isinstance(low, Sequence) or len(low) != 2:
|
||||
raise OperationalException(f'{self.__class__.__name__} space must be [low, high]')
|
||||
opt_range = low
|
||||
else:
|
||||
opt_range = [low, high]
|
||||
super().__init__(opt_range=opt_range, default=default, space=space, optimize=optimize,
|
||||
load=load, **kwargs)
|
||||
|
||||
def get_space(self, name: str) -> 'Real':
|
||||
"""
|
||||
Create skopt optimization space.
|
||||
:param name: A name of parameter field.
|
||||
"""
|
||||
return Real(*self.opt_range, name=name, **self._space_params)
|
||||
|
||||
|
||||
class DecimalParameter(RealParameter):
|
||||
default: float
|
||||
value: float
|
||||
opt_range: Sequence[float]
|
||||
|
||||
def __init__(self, low: Union[float, Sequence[float]], high: Optional[float] = None, *,
|
||||
default: float, decimals: int = 3, space: Optional[str] = None,
|
||||
optimize: bool = True, load: bool = True, **kwargs):
|
||||
"""
|
||||
Initialize hyperopt-optimizable decimal parameter with a limited precision.
|
||||
:param low: Lower end (inclusive) of optimization space or [low, high].
|
||||
:param high: Upper end (inclusive) of optimization space.
|
||||
Must be none if entire range is passed first parameter.
|
||||
:param default: A default value.
|
||||
:param decimals: A number of decimals after floating point to be included in testing.
|
||||
:param space: A parameter category. Can be 'buy' or 'sell'. This parameter is optional if
|
||||
parameter fieldname is prefixed with 'buy_' or 'sell_'.
|
||||
:param optimize: Include parameter in hyperopt optimizations.
|
||||
:param load: Load parameter value from {space}_params.
|
||||
:param kwargs: Extra parameters to skopt.space.Real.
|
||||
"""
|
||||
self._decimals = decimals
|
||||
default = round(default, self._decimals)
|
||||
super().__init__(low=low, high=high, default=default, space=space, optimize=optimize,
|
||||
load=load, **kwargs)
|
||||
|
||||
def get_space(self, name: str) -> 'Integer':
|
||||
"""
|
||||
Create skopt optimization space.
|
||||
:param name: A name of parameter field.
|
||||
"""
|
||||
low = int(self.opt_range[0] * pow(10, self._decimals))
|
||||
high = int(self.opt_range[1] * pow(10, self._decimals))
|
||||
return Integer(low, high, name=name, **self._space_params)
|
||||
|
||||
def _set_value(self, value: int):
|
||||
"""
|
||||
Update current value. Used by hyperopt functions for the purpose where optimization and
|
||||
value spaces differ.
|
||||
:param value: An integer value.
|
||||
"""
|
||||
self.value = round(value * pow(0.1, self._decimals), self._decimals)
|
||||
|
||||
|
||||
class CategoricalParameter(BaseParameter):
|
||||
default: Any
|
||||
value: Any
|
||||
opt_range: Sequence[Any]
|
||||
|
||||
def __init__(self, categories: Sequence[Any], *, default: Optional[Any] = None,
|
||||
space: Optional[str] = None, optimize: bool = True, load: bool = True, **kwargs):
|
||||
"""
|
||||
Initialize hyperopt-optimizable parameter.
|
||||
:param categories: Optimization space, [a, b, ...].
|
||||
:param default: A default value. If not specified, first item from specified space will be
|
||||
used.
|
||||
:param space: A parameter category. Can be 'buy' or 'sell'. This parameter is optional if
|
||||
parameter field
|
||||
name is prefixed with 'buy_' or 'sell_'.
|
||||
:param optimize: Include parameter in hyperopt optimizations.
|
||||
:param load: Load parameter value from {space}_params.
|
||||
:param kwargs: Extra parameters to skopt.space.Categorical.
|
||||
"""
|
||||
if len(categories) < 2:
|
||||
raise OperationalException(
|
||||
'CategoricalParameter space must be [a, b, ...] (at least two parameters)')
|
||||
super().__init__(opt_range=categories, default=default, space=space, optimize=optimize,
|
||||
load=load, **kwargs)
|
||||
|
||||
def get_space(self, name: str) -> 'Categorical':
|
||||
"""
|
||||
Create skopt optimization space.
|
||||
:param name: A name of parameter field.
|
||||
"""
|
||||
return Categorical(self.opt_range, name=name, **self._space_params)
|
||||
|
||||
|
||||
class HyperStrategyMixin(object):
|
||||
"""
|
||||
A helper base class which allows HyperOptAuto class to reuse implementations of of buy/sell
|
||||
strategy logic.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Initialize hyperoptable strategy mixin.
|
||||
"""
|
||||
self._load_params(getattr(self, 'buy_params', None))
|
||||
self._load_params(getattr(self, 'sell_params', None))
|
||||
|
||||
def enumerate_parameters(self, category: str = None) -> Iterator[Tuple[str, BaseParameter]]:
|
||||
"""
|
||||
Find all optimizeable parameters and return (name, attr) iterator.
|
||||
:param category:
|
||||
:return:
|
||||
"""
|
||||
if category not in ('buy', 'sell', None):
|
||||
raise OperationalException('Category must be one of: "buy", "sell", None.')
|
||||
for attr_name in dir(self):
|
||||
if not attr_name.startswith('__'): # Ignore internals, not strictly necessary.
|
||||
attr = getattr(self, attr_name)
|
||||
if issubclass(attr.__class__, BaseParameter):
|
||||
if (category and attr_name.startswith(category + '_')
|
||||
and attr.category is not None and attr.category != category):
|
||||
raise OperationalException(
|
||||
f'Inconclusive parameter name {attr_name}, category: {attr.category}.')
|
||||
if (category is None or category == attr.category or
|
||||
(attr_name.startswith(category + '_') and attr.category is None)):
|
||||
yield attr_name, attr
|
||||
|
||||
def _load_params(self, params: dict) -> None:
|
||||
"""
|
||||
Set optimizeable parameter values.
|
||||
:param params: Dictionary with new parameter values.
|
||||
"""
|
||||
if not params:
|
||||
return
|
||||
for attr_name, attr in self.enumerate_parameters():
|
||||
if attr_name in params:
|
||||
if attr.load:
|
||||
attr.value = params[attr_name]
|
||||
logger.info(f'Strategy Parameter: {attr_name} = {attr.value}')
|
||||
else:
|
||||
logger.warning(f'Parameter "{attr_name}" exists, but is disabled. '
|
||||
f'Default value "{attr.value}" used.')
|
@@ -18,6 +18,7 @@ from freqtrade.exceptions import OperationalException, StrategyError
|
||||
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
|
||||
from freqtrade.exchange.exchange import timeframe_to_next_date
|
||||
from freqtrade.persistence import PairLocks, Trade
|
||||
from freqtrade.strategy.hyper import HyperStrategyMixin
|
||||
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
||||
from freqtrade.wallets import Wallets
|
||||
|
||||
@@ -59,7 +60,7 @@ class SellCheckTuple(NamedTuple):
|
||||
sell_type: SellType
|
||||
|
||||
|
||||
class IStrategy(ABC):
|
||||
class IStrategy(ABC, HyperStrategyMixin):
|
||||
"""
|
||||
Interface for freqtrade strategies
|
||||
Defines the mandatory structure must follow any custom strategies
|
||||
@@ -140,6 +141,7 @@ class IStrategy(ABC):
|
||||
self.config = config
|
||||
# Dict to determine if analysis is necessary
|
||||
self._last_candle_seen_per_pair: Dict[str, datetime] = {}
|
||||
super().__init__(config)
|
||||
|
||||
@abstractmethod
|
||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
|
@@ -1,4 +1,5 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
|
||||
# flake8: noqa: F401
|
||||
|
||||
# --- Do not remove these libs ---
|
||||
import numpy as np # noqa
|
||||
@@ -6,6 +7,7 @@ import pandas as pd # noqa
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy import IStrategy
|
||||
from freqtrade.strategy import CategoricalParameter, DecimalParameter, IntParameter
|
||||
|
||||
# --------------------------------
|
||||
# Add your lib to import here
|
||||
@@ -16,7 +18,7 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib
|
||||
class {{ strategy }}(IStrategy):
|
||||
"""
|
||||
This is a strategy template to get you started.
|
||||
More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md
|
||||
More information in https://www.freqtrade.io/en/latest/strategy-customization/
|
||||
|
||||
You can:
|
||||
:return: a Dataframe with all mandatory indicators for the strategies
|
||||
|
@@ -1,4 +1,5 @@
|
||||
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
|
||||
# flake8: noqa: F401
|
||||
# isort: skip_file
|
||||
# --- Do not remove these libs ---
|
||||
import numpy as np # noqa
|
||||
@@ -6,6 +7,7 @@ import pandas as pd # noqa
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.strategy import IStrategy
|
||||
from freqtrade.strategy import CategoricalParameter, DecimalParameter, IntParameter
|
||||
|
||||
# --------------------------------
|
||||
# Add your lib to import here
|
||||
@@ -53,6 +55,10 @@ class SampleStrategy(IStrategy):
|
||||
# trailing_stop_positive = 0.01
|
||||
# trailing_stop_positive_offset = 0.0 # Disabled / not configured
|
||||
|
||||
# Hyperoptable parameters
|
||||
buy_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True)
|
||||
sell_rsi = IntParameter(low=50, high=100, default=70, space='sell', optimize=True, load=True)
|
||||
|
||||
# Optimal timeframe for the strategy.
|
||||
timeframe = '5m'
|
||||
|
||||
@@ -340,7 +346,8 @@ class SampleStrategy(IStrategy):
|
||||
"""
|
||||
dataframe.loc[
|
||||
(
|
||||
(qtpylib.crossed_above(dataframe['rsi'], 30)) & # Signal: RSI crosses above 30
|
||||
# Signal: RSI crosses above 30
|
||||
(qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)) &
|
||||
(dataframe['tema'] <= dataframe['bb_middleband']) & # Guard: tema below BB middle
|
||||
(dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard: tema is raising
|
||||
(dataframe['volume'] > 0) # Make sure Volume is not 0
|
||||
@@ -358,7 +365,8 @@ class SampleStrategy(IStrategy):
|
||||
"""
|
||||
dataframe.loc[
|
||||
(
|
||||
(qtpylib.crossed_above(dataframe['rsi'], 70)) & # Signal: RSI crosses above 70
|
||||
# Signal: RSI crosses above 70
|
||||
(qtpylib.crossed_above(dataframe['rsi'], self.sell_rsi.value)) &
|
||||
(dataframe['tema'] > dataframe['bb_middleband']) & # Guard: tema above BB middle
|
||||
(dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard: tema is falling
|
||||
(dataframe['volume'] > 0) # Make sure Volume is not 0
|
||||
|
Reference in New Issue
Block a user