stable/freqtrade/resolvers/strategy_resolver.py

180 lines
7.9 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
"""
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
2019-01-21 18:30:59 +00:00
from inspect import getfullargspec
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
2019-12-24 12:54:46 +00:00
from freqtrade.constants import (REQUIRED_ORDERTIF, REQUIRED_ORDERTYPES,
2020-02-02 15:12:23 +00:00
USERPATH_STRATEGIES)
from freqtrade.exceptions import OperationalException
from freqtrade.resolvers import IResolver
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 the logic to load custom strategy class
2018-01-28 05:26:57 +00:00
"""
2019-12-24 12:34:37 +00:00
object_type = IStrategy
2019-12-24 12:54:46 +00:00
object_type_str = "Strategy"
2020-02-02 15:12:23 +00:00
user_subdir = USERPATH_STRATEGIES
2019-12-24 12:54:46 +00:00
initial_search_path = Path(__file__).parent.parent.joinpath('strategy').resolve()
@staticmethod
def load_strategy(config: Optional[Dict] = None) -> IStrategy:
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 {}
2019-09-21 17:54:44 +00:00
if not config.get('strategy'):
raise OperationalException("No strategy set. Please use `--strategy` to specify "
"the strategy class to use.")
strategy_name = config['strategy']
strategy: IStrategy = StrategyResolver._load_strategy(
strategy_name, config=config,
extra_dir=config.get('strategy_path'))
# make sure ask_strategy dict is available
if 'ask_strategy' not in config:
config['ask_strategy'] = {}
2018-01-15 08:35:11 +00:00
# Set attributes
# Check if we need to override configuration
# (Attribute name, default, ask_strategy)
2019-03-14 06:56:21 +00:00
attributes = [("minimal_roi", {"0": 10.0}, False),
("ticker_interval", None, False),
("stoploss", None, False),
("trailing_stop", None, False),
("trailing_stop_positive", None, False),
("trailing_stop_positive_offset", 0.0, False),
("trailing_only_offset_is_reached", None, False),
("process_only_new_candles", None, False),
("order_types", None, False),
("order_time_in_force", None, False),
("stake_currency", None, False),
("stake_amount", None, False),
("startup_candle_count", None, False),
("unfilledtimeout", None, False),
("use_sell_signal", True, True),
2019-03-14 06:56:21 +00:00
("sell_profit_only", False, True),
("ignore_roi_if_buy_signal", False, True),
2019-01-05 06:22:19 +00:00
]
for attribute, default, ask_strategy in attributes:
if ask_strategy:
StrategyResolver._override_attribute_helper(strategy, config['ask_strategy'],
attribute, default)
else:
StrategyResolver._override_attribute_helper(strategy, config,
attribute, default)
2019-01-05 06:25:35 +00:00
# Loop this list again to have output combined
for attribute, _, exp in attributes:
if exp and attribute in config['ask_strategy']:
logger.info("Strategy using %s: %s", attribute, config['ask_strategy'][attribute])
elif attribute in config:
2019-01-05 06:24:15 +00:00
logger.info("Strategy using %s: %s", attribute, config[attribute])
# Sort and apply type conversions
strategy.minimal_roi = OrderedDict(sorted(
{int(key): value for (key, value) in strategy.minimal_roi.items()}.items(),
key=lambda t: t[0]))
strategy.stoploss = float(strategy.stoploss)
2018-08-09 17:24:00 +00:00
StrategyResolver._strategy_sanity_validations(strategy)
return strategy
@staticmethod
def _override_attribute_helper(strategy, config, attribute: str, default):
"""
Override attributes in the strategy.
Prevalence:
- Configuration
- Strategy
- default (if not None)
"""
if attribute in config:
setattr(strategy, attribute, config[attribute])
logger.info("Override strategy '%s' with value in config file: %s.",
attribute, config[attribute])
elif hasattr(strategy, attribute):
val = getattr(strategy, attribute)
# None's cannot exist in the config, so do not copy them
if val is not None:
config[attribute] = val
# Explicitly check for None here as other "falsy" values are possible
elif default is not None:
setattr(strategy, attribute, default)
config[attribute] = default
2018-11-25 21:02:59 +00:00
@staticmethod
def _strategy_sanity_validations(strategy):
2019-12-24 12:54:46 +00:00
if not all(k in strategy.order_types for k in REQUIRED_ORDERTYPES):
raise ImportError(f"Impossible to load Strategy '{strategy.__class__.__name__}'. "
f"Order-types mapping is incomplete.")
2019-12-24 12:54:46 +00:00
if not all(k in strategy.order_time_in_force for k in REQUIRED_ORDERTIF):
raise ImportError(f"Impossible to load Strategy '{strategy.__class__.__name__}'. "
2018-11-25 21:02:59 +00:00
f"Order-time-in-force mapping is incomplete.")
@staticmethod
def _load_strategy(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
"""
2019-12-24 12:54:46 +00:00
abs_paths = StrategyResolver.build_search_paths(config,
2020-02-02 15:12:23 +00:00
user_subdir=USERPATH_STRATEGIES,
2019-12-24 12:34:37 +00:00
extra_dir=extra_dir)
2018-03-25 14:28:04 +00:00
2018-07-05 21:40:04 +00:00
if ":" in strategy_name:
2019-06-26 19:23:16 +00:00
logger.info("loading base64 encoded 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())
2019-12-24 12:34:37 +00:00
strategy = StrategyResolver._load_object(paths=abs_paths,
object_name=strategy_name,
kwargs={'config': config})
if strategy:
strategy._populate_fun_len = len(getfullargspec(strategy.populate_indicators).args)
strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args)
strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args)
2019-08-26 17:44:33 +00:00
if any([x == 2 for x in [strategy._populate_fun_len,
strategy._buy_fun_len,
strategy._sell_fun_len]]):
strategy.INTERFACE_VERSION = 1
2019-09-07 23:43:02 +00:00
return strategy
2019-07-12 20:45:49 +00:00
raise OperationalException(
f"Impossible to load Strategy '{strategy_name}'. This class does not exist "
"or contains Python code errors."
2018-03-25 14:28:04 +00:00
)