Merge branch 'develop' into feat/objectify-ccxt

This commit is contained in:
gcarq
2018-05-02 22:49:55 +02:00
36 changed files with 544 additions and 532 deletions

View File

@@ -7,8 +7,6 @@ import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.indicator_helpers import fishers_inverse
from freqtrade.strategy.interface import IStrategy
class_name = 'DefaultStrategy'
class DefaultStrategy(IStrategy):
"""

View File

@@ -33,7 +33,6 @@ class IStrategy(ABC):
Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
:return:
"""
@abstractmethod
@@ -41,5 +40,5 @@ class IStrategy(ABC):
"""
Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
:return: DataFrame with sell column
"""

View File

@@ -0,0 +1,130 @@
# pragma pylint: disable=attribute-defined-outside-init
"""
This module load custom strategies
"""
import importlib.util
import inspect
import logging
import os
from collections import OrderedDict
from typing import Optional, Dict, Type
from freqtrade import constants
from freqtrade.strategy.interface import IStrategy
logger = logging.getLogger(__name__)
class StrategyResolver(object):
"""
This class contains all the logic to load custom strategy class
"""
__slots__ = ['strategy']
def __init__(self, config: Optional[Dict] = None) -> None:
"""
Load the custom class from config parameter
:param config: configuration dictionary or None
"""
config = config or {}
# Verify the strategy is in the configuration, otherwise fallback to the default strategy
strategy_name = config.get('strategy') or constants.DEFAULT_STRATEGY
self.strategy = self._load_strategy(strategy_name, extra_dir=config.get('strategy_path'))
# Set attributes
# Check if we need to override configuration
if 'minimal_roi' in config:
self.strategy.minimal_roi = config['minimal_roi']
logger.info("Override strategy \'minimal_roi\' with value in config file.")
if 'stoploss' in config:
self.strategy.stoploss = config['stoploss']
logger.info(
"Override strategy \'stoploss\' with value in config file: %s.", config['stoploss']
)
if 'ticker_interval' in config:
self.strategy.ticker_interval = config['ticker_interval']
logger.info(
"Override strategy \'ticker_interval\' with value in config file: %s.",
config['ticker_interval']
)
# 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)
def _load_strategy(
self, strategy_name: str, extra_dir: Optional[str] = None) -> Optional[IStrategy]:
"""
Search and loads the specified strategy.
:param strategy_name: name of the module to import
: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__))
abs_paths = [
os.path.join(current_path, '..', '..', 'user_data', 'strategies'),
current_path,
]
if extra_dir:
# Add extra strategy directory on top of search paths
abs_paths.insert(0, extra_dir)
for path in abs_paths:
strategy = self._search_strategy(path, strategy_name)
if strategy:
logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path)
return strategy
raise ImportError(
"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('user_data.strategies', module_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
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) -> 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()
return None

View File

@@ -1,169 +0,0 @@
# pragma pylint: disable=attribute-defined-outside-init
"""
This module load custom strategies
"""
import importlib
import logging
import os
import sys
from collections import OrderedDict
from pandas import DataFrame
from freqtrade.constants import Constants
from freqtrade.strategy.interface import IStrategy
sys.path.insert(0, r'../../user_data/strategies')
logger = logging.getLogger(__name__)
class Strategy(object):
"""
This class contains all the logic to load custom strategy class
"""
def __init__(self, config: dict = {}) -> None:
"""
Load the custom class from config parameter
:param config:
:return:
"""
# Verify the strategy is in the configuration, otherwise fallback to the default strategy
if 'strategy' in config:
strategy = config['strategy']
else:
strategy = Constants.DEFAULT_STRATEGY
# Load the strategy
self._load_strategy(strategy)
# Set attributes
# Check if we need to override configuration
if 'minimal_roi' in config:
self.custom_strategy.minimal_roi = config['minimal_roi']
logger.info("Override strategy \'minimal_roi\' with value in config file.")
if 'stoploss' in config:
self.custom_strategy.stoploss = config['stoploss']
logger.info(
"Override strategy \'stoploss\' with value in config file: %s.", config['stoploss']
)
if 'ticker_interval' in config:
self.custom_strategy.ticker_interval = config['ticker_interval']
logger.info(
"Override strategy \'ticker_interval\' with value in config file: %s.",
config['ticker_interval']
)
# Minimal ROI designed for the strategy
self.minimal_roi = OrderedDict(sorted(
{int(key): value for (key, value) in self.custom_strategy.minimal_roi.items()}.items(),
key=lambda t: t[0])) # sort after converting to number
# Optimal stoploss designed for the strategy
self.stoploss = float(self.custom_strategy.stoploss)
self.ticker_interval = self.custom_strategy.ticker_interval
def _load_strategy(self, strategy_name: str) -> None:
"""
Search and load the custom strategy. If no strategy found, fallback on the default strategy
Set the object into self.custom_strategy
:param strategy_name: name of the module to import
:return: None
"""
try:
# Start by sanitizing the file name (remove any extensions)
strategy_name = self._sanitize_module_name(filename=strategy_name)
# Search where can be the strategy file
path = self._search_strategy(filename=strategy_name)
# Load the strategy
self.custom_strategy = self._load_class(path + strategy_name)
# Fallback to the default strategy
except (ImportError, TypeError) as error:
logger.error(
"Impossible to load Strategy 'user_data/strategies/%s.py'. This file does not exist"
" or contains Python code errors",
strategy_name
)
logger.error(
"The error is:\n%s.",
error
)
def _load_class(self, filename: str) -> IStrategy:
"""
Import a strategy as a module
:param filename: path to the strategy (path from freqtrade/strategy/)
:return: return the strategy class
"""
module = importlib.import_module(filename, __package__)
custom_strategy = getattr(module, module.class_name)
logger.info("Load strategy class: %s (%s.py)", module.class_name, filename)
return custom_strategy()
@staticmethod
def _sanitize_module_name(filename: str) -> str:
"""
Remove any extension from filename
:param filename: filename to sanatize
:return: return the filename without extensions
"""
filename = os.path.basename(filename)
filename = os.path.splitext(filename)[0]
return filename
@staticmethod
def _search_strategy(filename: str) -> str:
"""
Search for the Strategy file in different folder
1. search into the user_data/strategies folder
2. search into the freqtrade/strategy folder
3. if nothing found, return None
:param strategy_name: module name to search
:return: module path where is the strategy
"""
pwd = os.path.dirname(os.path.realpath(__file__)) + '/'
user_data = os.path.join(pwd, '..', '..', 'user_data', 'strategies', filename + '.py')
strategy_folder = os.path.join(pwd, filename + '.py')
path = None
if os.path.isfile(user_data):
path = 'user_data.strategies.'
elif os.path.isfile(strategy_folder):
path = '.'
return path
def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
"""
Populate indicators that will be used in the Buy and Sell strategy
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
:return: a Dataframe with all mandatory indicators for the strategies
"""
return self.custom_strategy.populate_indicators(dataframe)
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
"""
Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
:return:
"""
return self.custom_strategy.populate_buy_trend(dataframe)
def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame:
"""
Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame
:return: DataFrame with buy column
"""
return self.custom_strategy.populate_sell_trend(dataframe)