stable/freqtrade/resolvers/strategy_resolver.py

142 lines
5.7 KiB
Python
Raw Normal View History

# pragma pylint: disable=attribute-defined-outside-init
2018-01-28 05:26:57 +00:00
"""
This module load custom strategies
"""
import inspect
2018-03-25 19:37:14 +00:00
import logging
2018-07-30 20:00:08 +00:00
import tempfile
from base64 import urlsafe_b64decode
2018-02-06 14:31:50 +00:00
from collections import OrderedDict
2018-07-30 20:00:08 +00:00
from pathlib import Path
from typing import Dict, Optional
2018-03-17 21:44:47 +00:00
from freqtrade import constants
from freqtrade.resolvers import IResolver
from freqtrade.strategy import import_strategy
2018-01-15 08:35:11 +00:00
from freqtrade.strategy.interface import IStrategy
2018-03-25 19:37:14 +00:00
logger = logging.getLogger(__name__)
2018-03-24 20:56:20 +00:00
class StrategyResolver(IResolver):
2018-01-28 05:26:57 +00:00
"""
This class contains all the logic to load custom strategy class
"""
__slots__ = ['strategy']
2018-03-24 17:14:05 +00:00
def __init__(self, config: Optional[Dict] = None) -> None:
2018-01-28 05:26:57 +00:00
"""
Load the custom class from config parameter
2018-03-25 18:24:56 +00:00
:param config: configuration dictionary or None
2018-01-28 05:26:57 +00:00
"""
2018-03-24 17:14:05 +00:00
config = config or {}
2018-01-15 08:35:11 +00:00
# Verify the strategy is in the configuration, otherwise fallback to the default strategy
strategy_name = config.get('strategy') or constants.DEFAULT_STRATEGY
2018-05-31 20:17:46 +00:00
self.strategy: IStrategy = self._load_strategy(strategy_name,
config=config,
2018-05-31 20:17:46 +00:00
extra_dir=config.get('strategy_path'))
2018-01-15 08:35:11 +00:00
# Set attributes
# Check if we need to override configuration
2019-01-05 06:22:19 +00:00
attributes = ["minimal_roi",
"ticker_interval",
"stoploss",
"trailing_stop",
"trailing_stop_positive",
"trailing_stop_positive_offset",
"process_only_new_candles",
"order_types",
"order_time_in_force"
]
for attribute in attributes:
self._override_attribute_helper(config, attribute)
2019-01-05 06:24:15 +00:00
if attribute in config:
logger.info("Strategy using %s: %s", attribute, config[attribute])
2019-01-05 06:22:19 +00:00
2019-01-05 06:10:25 +00:00
# Sort and apply type conversions
self.strategy.minimal_roi = OrderedDict(sorted(
{int(key): value for (key, value) in self.strategy.minimal_roi.items()}.items(),
key=lambda t: t[0]))
self.strategy.stoploss = float(self.strategy.stoploss)
2018-08-09 17:24:00 +00:00
self._strategy_sanity_validations()
def _override_attribute_helper(self, config, attribute: str):
if attribute in config:
setattr(self.strategy, attribute, config[attribute])
logger.info("Override strategy '%s' with value in config file: %s.",
attribute, config[attribute])
elif hasattr(self.strategy, attribute):
config[attribute] = getattr(self.strategy, attribute)
2018-11-25 21:02:59 +00:00
def _strategy_sanity_validations(self):
if not all(k in self.strategy.order_types for k in constants.REQUIRED_ORDERTYPES):
raise ImportError(f"Impossible to load Strategy '{self.strategy.__class__.__name__}'. "
f"Order-types mapping is incomplete.")
2018-11-25 21:02:59 +00:00
if not all(k in self.strategy.order_time_in_force for k in constants.REQUIRED_ORDERTIF):
raise ImportError(f"Impossible to load Strategy '{self.strategy.__class__.__name__}'. "
f"Order-time-in-force mapping is incomplete.")
2018-03-25 14:28:04 +00:00
def _load_strategy(
self, strategy_name: str, config: dict, extra_dir: Optional[str] = None) -> IStrategy:
2018-01-15 08:35:11 +00:00
"""
Search and loads the specified strategy.
2018-01-15 08:35:11 +00:00
:param strategy_name: name of the module to import
:param config: configuration for the strategy
2018-03-25 14:28:04 +00:00
:param extra_dir: additional directory to search for the given strategy
:return: Strategy instance or None
2018-01-15 08:35:11 +00:00
"""
2018-11-24 19:39:16 +00:00
current_path = Path(__file__).parent.parent.joinpath('strategy').resolve()
2018-03-25 14:28:04 +00:00
abs_paths = [
2018-11-24 19:39:16 +00:00
Path.cwd().joinpath('user_data/strategies'),
2018-03-25 14:28:04 +00:00
current_path,
]
if extra_dir:
# Add extra strategy directory on top of search paths
2018-11-24 19:39:16 +00:00
abs_paths.insert(0, Path(extra_dir).resolve())
2018-03-25 14:28:04 +00:00
2018-07-05 21:40:04 +00:00
if ":" in strategy_name:
2018-07-30 20:00:08 +00:00
logger.info("loading base64 endocded strategy")
strat = strategy_name.split(":")
if len(strat) == 2:
temp = Path(tempfile.mkdtemp("freq", "strategy"))
name = strat[0] + ".py"
temp.joinpath(name).write_text(urlsafe_b64decode(strat[1]).decode('utf-8'))
temp.joinpath("__init__.py").touch()
2018-11-24 19:39:16 +00:00
strategy_name = strat[0]
# register temp path with the bot
2018-11-24 19:39:16 +00:00
abs_paths.insert(0, temp.resolve())
for _path in abs_paths:
try:
strategy = self._search_object(directory=_path, object_type=IStrategy,
object_name=strategy_name, kwargs={'config': config})
if strategy:
2018-11-24 19:39:16 +00:00
logger.info('Using resolved strategy %s from \'%s\'', strategy_name, _path)
strategy._populate_fun_len = len(
inspect.getfullargspec(strategy.populate_indicators).args)
strategy._buy_fun_len = len(
inspect.getfullargspec(strategy.populate_buy_trend).args)
strategy._sell_fun_len = len(
inspect.getfullargspec(strategy.populate_sell_trend).args)
return import_strategy(strategy, config=config)
except FileNotFoundError:
2018-11-24 19:39:16 +00:00
logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd()))
2018-03-25 14:28:04 +00:00
raise ImportError(
"Impossible to load Strategy '{}'. This class does not exist"
" or contains Python code errors".format(strategy_name)
)