extract resolvers to IResolvers and it's own package

This commit is contained in:
Matthias 2018-11-24 20:00:02 +01:00
parent e442390b1b
commit 21a093bcdb
11 changed files with 107 additions and 69 deletions

View File

@ -21,9 +21,9 @@ from freqtrade.wallets import Wallets
from freqtrade.edge import Edge
from freqtrade.persistence import Trade
from freqtrade.rpc import RPCManager, RPCMessageType
from freqtrade.resolvers import StrategyResolver
from freqtrade.state import State
from freqtrade.strategy.interface import SellType
from freqtrade.strategy.resolver import IStrategy, StrategyResolver
from freqtrade.strategy.interface import SellType, IStrategy
from freqtrade.exchange.exchange_helpers import order_book_to_dataframe

View File

@ -20,8 +20,8 @@ from freqtrade.configuration import Configuration
from freqtrade.exchange import Exchange
from freqtrade.misc import file_dump_json
from freqtrade.persistence import Trade
from freqtrade.strategy.interface import SellType
from freqtrade.strategy.resolver import IStrategy, StrategyResolver
from freqtrade.resolvers import StrategyResolver
from freqtrade.strategy.interface import SellType, IStrategy
logger = logging.getLogger(__name__)

View File

@ -12,7 +12,7 @@ from freqtrade.edge import Edge
from freqtrade.configuration import Configuration
from freqtrade.arguments import Arguments
from freqtrade.exchange import Exchange
from freqtrade.strategy.resolver import StrategyResolver
from freqtrade.resolvers import StrategyResolver
logger = logging.getLogger(__name__)

View File

@ -0,0 +1,2 @@
from freqtrade.resolvers.iresolver import IResolver # noqa: F401
from freqtrade.resolvers.strategyresolver import StrategyResolver # noqa: F401

View File

@ -0,0 +1,72 @@
# pragma pylint: disable=attribute-defined-outside-init
"""
This module load custom hyperopts
"""
import importlib.util
import inspect
import logging
import os
from typing import Optional, Dict, Type, Any
from freqtrade.constants import DEFAULT_HYPEROPT
from freqtrade.optimize.hyperopt_interface import IHyperOpt
logger = logging.getLogger(__name__)
class IResolver(object):
"""
This class contains all the logic to load custom hyperopt class
"""
def __init__(self, object_type, config: Optional[Dict] = None) -> None:
"""
Load the custom class from config parameter
:param config: configuration dictionary or None
"""
config = config or {}
@staticmethod
def _get_valid_objects(object_type, module_path: str,
object_name: str) -> Optional[Type[Any]]:
"""
Returns a list of all possible objects for the given module_path of type oject_type
:param object_type: object_type (class)
:param module_path: absolute path to the module
:param object_name: Class name of the object
:return: Tuple with (name, class) or None
"""
# Generate spec based on absolute path
spec = importlib.util.spec_from_file_location('unknown', module_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) # type: ignore # importlib does not use typehints
valid_objects_gen = (
obj for name, obj in inspect.getmembers(module, inspect.isclass)
if object_name == name and object_type in obj.__bases__
)
return next(valid_objects_gen, None)
@staticmethod
def _search_object(directory: str, object_type, object_name: str,
kwargs: dict) -> Optional[Any]:
"""
Search for the objectname in the given directory
:param directory: relative or absolute directory path
:return: object instance
"""
logger.debug('Searching for %s %s in \'%s\'', object_type.__name__, object_name, directory)
for entry in os.listdir(directory):
# Only consider python files
if not entry.endswith('.py'):
logger.debug('Ignoring %s', entry)
continue
obj = IResolver._get_valid_objects(
object_type, os.path.abspath(os.path.join(directory, entry)), object_name
)
if obj:
return obj(**kwargs)
return None

View File

@ -3,7 +3,6 @@
"""
This module load custom strategies
"""
import importlib.util
import inspect
import logging
import os
@ -11,16 +10,17 @@ import tempfile
from base64 import urlsafe_b64decode
from collections import OrderedDict
from pathlib import Path
from typing import Dict, Optional, Type
from typing import Dict, Optional
from freqtrade import constants
from freqtrade.resolvers import IResolver
from freqtrade.strategy import import_strategy
from freqtrade.strategy.interface import IStrategy
logger = logging.getLogger(__name__)
class StrategyResolver(object):
class StrategyResolver(IResolver):
"""
This class contains all the logic to load custom strategy class
"""
@ -103,7 +103,8 @@ class StrategyResolver(object):
:param extra_dir: additional directory to search for the given strategy
:return: Strategy instance or None
"""
current_path = os.path.dirname(os.path.realpath(__file__))
current_path = os.path.join(os.path.dirname(os.path.dirname(os.path.realpath(__file__))), 'strategy')
abs_paths = [
os.path.join(os.getcwd(), 'user_data', 'strategies'),
current_path,
@ -131,7 +132,8 @@ class StrategyResolver(object):
for path in abs_paths:
try:
strategy = self._search_strategy(path, strategy_name=strategy_name, config=config)
strategy = self._search_object(directory=path, object_type=IStrategy,
object_name=strategy_name, kwargs={'config': config})
if strategy:
logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path)
strategy._populate_fun_len = len(
@ -149,43 +151,3 @@ class StrategyResolver(object):
"Impossible to load Strategy '{}'. This class does not exist"
" or contains Python code errors".format(strategy_name)
)
@staticmethod
def _get_valid_strategies(module_path: str, strategy_name: str) -> Optional[Type[IStrategy]]:
"""
Returns a list of all possible strategies for the given module_path
:param module_path: absolute path to the module
:param strategy_name: Class name of the strategy
:return: Tuple with (name, class) or None
"""
# Generate spec based on absolute path
spec = importlib.util.spec_from_file_location('unknown', module_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) # type: ignore # importlib does not use typehints
valid_strategies_gen = (
obj for name, obj in inspect.getmembers(module, inspect.isclass)
if strategy_name == name and IStrategy in obj.__bases__
)
return next(valid_strategies_gen, None)
@staticmethod
def _search_strategy(directory: str, strategy_name: str, config: dict) -> Optional[IStrategy]:
"""
Search for the strategy_name in the given directory
:param directory: relative or absolute directory path
:return: name of the strategy class
"""
logger.debug('Searching for strategy %s in \'%s\'', strategy_name, directory)
for entry in os.listdir(directory):
# Only consider python files
if not entry.endswith('.py'):
logger.debug('Ignoring %s', entry)
continue
strategy = StrategyResolver._get_valid_strategies(
os.path.abspath(os.path.join(directory, entry)), strategy_name
)
if strategy:
return strategy(config)
return None

View File

@ -7,7 +7,7 @@ import pytest
from freqtrade.optimize.__init__ import load_tickerdata_file
from freqtrade.optimize.hyperopt import Hyperopt, start
from freqtrade.strategy.resolver import StrategyResolver
from freqtrade.resolvers import StrategyResolver
from freqtrade.tests.conftest import log_has, patch_exchange
from freqtrade.tests.optimize.test_backtesting import get_args

View File

@ -10,7 +10,7 @@ from pandas import DataFrame
from freqtrade.strategy import import_strategy
from freqtrade.strategy.default_strategy import DefaultStrategy
from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy.resolver import StrategyResolver
from freqtrade.resolvers import StrategyResolver
def test_import_strategy(caplog):
@ -44,17 +44,19 @@ def test_search_strategy():
path.realpath(__file__)), '..', '..', 'strategy'
)
assert isinstance(
StrategyResolver._search_strategy(
default_location,
config=default_config,
strategy_name='DefaultStrategy'
StrategyResolver._search_object(
directory=default_location,
object_type=IStrategy,
kwargs={'config': default_config},
object_name='DefaultStrategy'
),
IStrategy
)
assert StrategyResolver._search_strategy(
default_location,
config=default_config,
strategy_name='NotFoundStrategy'
assert StrategyResolver._search_object(
directory=default_location,
object_type=IStrategy,
kwargs={'config': default_config},
object_name='NotFoundStrategy'
) is None
@ -77,7 +79,7 @@ def test_load_strategy_invalid_directory(result, caplog):
resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir)
assert (
'freqtrade.strategy.resolver',
'freqtrade.resolvers.strategyresolver',
logging.WARNING,
'Path "{}" does not exist'.format(extra_dir),
) in caplog.record_tuples
@ -128,7 +130,7 @@ def test_strategy_override_minimal_roi(caplog):
resolver = StrategyResolver(config)
assert resolver.strategy.minimal_roi[0] == 0.5
assert ('freqtrade.strategy.resolver',
assert ('freqtrade.resolvers.strategyresolver',
logging.INFO,
"Override strategy 'minimal_roi' with value in config file: {'0': 0.5}."
) in caplog.record_tuples
@ -143,7 +145,7 @@ def test_strategy_override_stoploss(caplog):
resolver = StrategyResolver(config)
assert resolver.strategy.stoploss == -0.5
assert ('freqtrade.strategy.resolver',
assert ('freqtrade.resolvers.strategyresolver',
logging.INFO,
"Override strategy 'stoploss' with value in config file: -0.5."
) in caplog.record_tuples
@ -159,7 +161,7 @@ def test_strategy_override_ticker_interval(caplog):
resolver = StrategyResolver(config)
assert resolver.strategy.ticker_interval == 60
assert ('freqtrade.strategy.resolver',
assert ('freqtrade.resolvers.strategyresolver',
logging.INFO,
"Override strategy 'ticker_interval' with value in config file: 60."
) in caplog.record_tuples
@ -175,7 +177,7 @@ def test_strategy_override_process_only_new_candles(caplog):
resolver = StrategyResolver(config)
assert resolver.strategy.process_only_new_candles
assert ('freqtrade.strategy.resolver',
assert ('freqtrade.resolvers.strategyresolver',
logging.INFO,
"Override process_only_new_candles 'process_only_new_candles' "
"with value in config file: True."
@ -201,7 +203,7 @@ def test_strategy_override_order_types(caplog):
for method in ['buy', 'sell', 'stoploss']:
assert resolver.strategy.order_types[method] == order_types[method]
assert ('freqtrade.strategy.resolver',
assert ('freqtrade.resolvers.strategyresolver',
logging.INFO,
"Override strategy 'order_types' with value in config file:"
" {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit'}."

View File

@ -3,7 +3,7 @@
import pandas
from freqtrade.optimize import load_data
from freqtrade.strategy.resolver import StrategyResolver
from freqtrade.resolvers import StrategyResolver
_pairs = ['ETH/BTC']

View File

@ -44,7 +44,7 @@ from freqtrade.arguments import Arguments, TimeRange
from freqtrade.exchange import Exchange
from freqtrade.optimize.backtesting import setup_configuration
from freqtrade.persistence import Trade
from freqtrade.strategy.resolver import StrategyResolver
from freqtrade.resolvers import StrategyResolver
logger = logging.getLogger(__name__)
_CONF: Dict[str, Any] = {}

View File

@ -27,7 +27,7 @@ import plotly.graph_objs as go
from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration
from freqtrade import constants
from freqtrade.strategy.resolver import StrategyResolver
from freqtrade.resolvers import StrategyResolver
import freqtrade.optimize as optimize
import freqtrade.misc as misc