From 9aa468adda4387106143bf0e2565f1ca24221edc Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 30 May 2018 22:01:29 +0200 Subject: [PATCH 01/29] fix for typehint --- freqtrade/fiat_convert.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/fiat_convert.py b/freqtrade/fiat_convert.py index 17882f51a..3d268fdbe 100644 --- a/freqtrade/fiat_convert.py +++ b/freqtrade/fiat_convert.py @@ -34,7 +34,7 @@ class CryptoFiat(object): self.price = 0.0 # Private attributes - self._expiration = 0 + self._expiration = 0.0 self.crypto_symbol = crypto_symbol.upper() self.fiat_symbol = fiat_symbol.upper() From 0d6dffdc7ecfa71f51816b46ef1c39239c9741b2 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 30 May 2018 22:09:03 +0200 Subject: [PATCH 02/29] fix typehinting --- freqtrade/fiat_convert.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/fiat_convert.py b/freqtrade/fiat_convert.py index 3d268fdbe..88eb702c9 100644 --- a/freqtrade/fiat_convert.py +++ b/freqtrade/fiat_convert.py @@ -5,7 +5,7 @@ e.g BTC to USD import logging import time -from typing import Dict +from typing import Dict, List from coinmarketcap import Market from requests.exceptions import RequestException @@ -65,7 +65,7 @@ class CryptoToFiatConverter(object): This object is also a Singleton """ __instance = None - _coinmarketcap = None + _coinmarketcap: Market = None # Constants SUPPORTED_FIAT = [ @@ -87,7 +87,7 @@ class CryptoToFiatConverter(object): return CryptoToFiatConverter.__instance def __init__(self) -> None: - self._pairs = [] + self._pairs: List[CryptoFiat] = [] self._load_cryptomap() def _load_cryptomap(self) -> None: From 88755fcded808c2c094b6ee317affbcd1894bf7c Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 30 May 2018 22:09:20 +0200 Subject: [PATCH 03/29] fix typing --- freqtrade/indicator_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/indicator_helpers.py b/freqtrade/indicator_helpers.py index 14519d7a2..50586578a 100644 --- a/freqtrade/indicator_helpers.py +++ b/freqtrade/indicator_helpers.py @@ -13,7 +13,7 @@ def went_down(series: Series) -> bool: return series < series.shift(1) -def ehlers_super_smoother(series: Series, smoothing: float = 6) -> type(Series): +def ehlers_super_smoother(series: Series, smoothing: float = 6) -> Series: magic = pi * sqrt(2) / smoothing a1 = exp(-magic) coeff2 = 2 * a1 * cos(magic) From 45909af7e003770d903460e40051d8b79d49655b Mon Sep 17 00:00:00 2001 From: xmatthias Date: Wed, 30 May 2018 22:38:09 +0200 Subject: [PATCH 04/29] type anotation fixes --- freqtrade/configuration.py | 6 +++--- freqtrade/misc.py | 2 +- freqtrade/optimize/__init__.py | 6 ++++-- freqtrade/persistence.py | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/freqtrade/configuration.py b/freqtrade/configuration.py index 43d0a0bf9..14c29e8fd 100644 --- a/freqtrade/configuration.py +++ b/freqtrade/configuration.py @@ -5,7 +5,7 @@ This module contains the configuration class import json import logging from argparse import Namespace -from typing import Dict, Any +from typing import Optional, Dict, Any from jsonschema import Draft4Validator, validate from jsonschema.exceptions import ValidationError, best_match import ccxt @@ -23,7 +23,7 @@ class Configuration(object): """ def __init__(self, args: Namespace) -> None: self.args = args - self.config = None + self.config: Optional[Dict[str, Any]] = None def load_config(self) -> Dict[str, Any]: """ @@ -192,7 +192,7 @@ class Configuration(object): validate(conf, constants.CONF_SCHEMA) return conf except ValidationError as exception: - logger.fatal( + logger.critical( 'Invalid configuration. See config.json.example. Reason: %s', exception ) diff --git a/freqtrade/misc.py b/freqtrade/misc.py index 225fb32df..90a1db42b 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -83,7 +83,7 @@ def file_dump_json(filename, data, is_zip=False) -> None: json.dump(data, fp, default=str) -def format_ms_time(date: str) -> str: +def format_ms_time(date: int) -> str: """ convert MS date to readable format. : epoch-string in ms diff --git a/freqtrade/optimize/__init__.py b/freqtrade/optimize/__init__.py index f6f1ba47a..58a4b07fe 100644 --- a/freqtrade/optimize/__init__.py +++ b/freqtrade/optimize/__init__.py @@ -4,8 +4,8 @@ import gzip import json import logging import os +from typing import Optional, List, Dict, Tuple, Any import arrow -from typing import Optional, List, Dict, Tuple from freqtrade import misc, constants from freqtrade.exchange import get_ticker_history @@ -139,7 +139,9 @@ def download_pairs(datadir, pairs: List[str], def load_cached_data_for_updating(filename: str, tick_interval: str, - timerange: Optional[Tuple[Tuple, int, int]]) -> Tuple[list, int]: + timerange: Optional[Tuple[Tuple, int, int]]) -> Tuple[ + List[Any], + Optional[int]]: """ Load cached data and choose what part of the data should be updated """ diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 2d497662e..c10599b3c 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -5,7 +5,7 @@ This module contains the class to persist trades into SQLite import logging from datetime import datetime from decimal import Decimal, getcontext -from typing import Dict, Optional +from typing import Dict, Optional, Any import arrow from sqlalchemy import (Boolean, Column, DateTime, Float, Integer, String, @@ -21,7 +21,7 @@ from sqlalchemy import inspect logger = logging.getLogger(__name__) _CONF = {} -_DECL_BASE = declarative_base() +_DECL_BASE = declarative_base() # type: Any def init(config: dict, engine: Optional[Engine] = None) -> None: From 48516e6e1e4a608ff3a0d898e0d6c286b2ecf1a0 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Thu, 31 May 2018 20:41:05 +0200 Subject: [PATCH 05/29] Add typehint --- freqtrade/arguments.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index afcb05511..cf7db5901 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -17,7 +17,7 @@ class Arguments(object): Arguments Class. Manage the arguments received by the cli """ - def __init__(self, args: List[str], description: str): + def __init__(self, args: List[str], description: str) -> None: self.args = args self.parsed_arg = None self.parser = argparse.ArgumentParser(description=description) From 4733aad7ff30220d1a135f3fce1c813b523ff6c6 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Thu, 31 May 2018 20:54:37 +0200 Subject: [PATCH 06/29] mypy_typing --- freqtrade/rpc/rpc.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index dd3eed001..df809cc2f 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -4,7 +4,7 @@ This module contains class to define a RPC communications import logging from datetime import datetime, timedelta from decimal import Decimal -from typing import Tuple, Any +from typing import Dict, Tuple, Any, Optional import arrow import sqlalchemy as sql @@ -114,7 +114,7 @@ class RPC(object): self, timescale: int, stake_currency: str, fiat_display_currency: str) -> Tuple[bool, Any]: today = datetime.utcnow().date() - profit_days = {} + profit_days: Dict[int, Dict] = {} if not (isinstance(timescale, int) and timescale > 0): return True, '*Daily [n]:* `must be an integer greater than 0`' @@ -172,7 +172,7 @@ class RPC(object): durations = [] for trade in trades: - current_rate = None + current_rate: Optional[float] = None if not trade.open_rate: continue @@ -278,7 +278,7 @@ class RPC(object): value = fiat.convert_amount(total, 'BTC', symbol) return False, (output, total, symbol, value) - def rpc_start(self) -> (bool, str): + def rpc_start(self) -> Tuple[bool, str]: """ Handler for start. """ @@ -288,7 +288,7 @@ class RPC(object): self.freqtrade.state = State.RUNNING return False, '`Starting trader ...`' - def rpc_stop(self) -> (bool, str): + def rpc_stop(self) -> Tuple[bool, str]: """ Handler for stop. """ From 0d251cbfdd497279b93e134cb8fc4b5534273895 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Thu, 31 May 2018 20:55:26 +0200 Subject: [PATCH 07/29] rpc type hints --- freqtrade/rpc/rpc_manager.py | 5 +++-- freqtrade/rpc/telegram.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/rpc/rpc_manager.py b/freqtrade/rpc/rpc_manager.py index 0299c793a..bedd8fdd4 100644 --- a/freqtrade/rpc/rpc_manager.py +++ b/freqtrade/rpc/rpc_manager.py @@ -1,6 +1,7 @@ """ This module contains class to manage RPC communications (Telegram, Slack, ...) """ +from typing import Any, Optional, List import logging from freqtrade.rpc.telegram import Telegram @@ -21,8 +22,8 @@ class RPCManager(object): """ self.freqtrade = freqtrade - self.registered_modules = [] - self.telegram = None + self.registered_modules: List[str] = [] + self.telegram: Any = None self._init() def _init(self) -> None: diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index c640fc77b..c110b9627 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -18,7 +18,7 @@ from freqtrade.rpc.rpc import RPC logger = logging.getLogger(__name__) -def authorized_only(command_handler: Callable[[Bot, Update], None]) -> Callable[..., Any]: +def authorized_only(command_handler: Callable[[Any, Bot, Update], None]) -> Callable[..., Any]: """ Decorator to check if the message comes from the correct chat_id :param command_handler: Telegram CommandHandler @@ -65,7 +65,7 @@ class Telegram(RPC): """ super().__init__(freqtrade) - self._updater = None + self._updater: Updater = None self._config = freqtrade.config self._init() From 1352f135d058df6be20c404a9fe81202d7da5945 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Thu, 31 May 2018 20:55:45 +0200 Subject: [PATCH 08/29] typing --- freqtrade/analyze.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index dcb5376ce..5756b845c 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -12,7 +12,7 @@ from pandas import DataFrame, to_datetime from freqtrade import constants from freqtrade.exchange import get_ticker_history from freqtrade.persistence import Trade -from freqtrade.strategy.resolver import StrategyResolver +from freqtrade.strategy.resolver import StrategyResolver, IStrategy logger = logging.getLogger(__name__) @@ -37,7 +37,7 @@ class Analyze(object): :param config: Bot configuration (use the one from Configuration()) """ self.config = config - self.strategy = StrategyResolver(self.config).strategy + self.strategy: IStrategy = StrategyResolver(self.config).strategy @staticmethod def parse_ticker_dataframe(ticker: list) -> DataFrame: From 4eb55acdbcea4cf53b8fc2405de2d6154d5a338d Mon Sep 17 00:00:00 2001 From: xmatthias Date: Thu, 31 May 2018 21:04:10 +0200 Subject: [PATCH 09/29] fix typing --- freqtrade/tests/test_configuration.py | 37 ++++++++++++++------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/freqtrade/tests/test_configuration.py b/freqtrade/tests/test_configuration.py index d27405d91..8ba8f8289 100644 --- a/freqtrade/tests/test_configuration.py +++ b/freqtrade/tests/test_configuration.py @@ -6,6 +6,7 @@ Unit test file for configuration.py import json from copy import deepcopy from unittest.mock import MagicMock +from argparse import Namespace import pytest from jsonschema import ValidationError @@ -37,7 +38,7 @@ def test_load_config_invalid_pair(default_conf) -> None: conf['exchange']['pair_whitelist'].append('ETH-BTC') with pytest.raises(ValidationError, match=r'.*does not match.*'): - configuration = Configuration([]) + configuration = Configuration(Namespace()) configuration._validate_config(conf) @@ -49,7 +50,7 @@ def test_load_config_missing_attributes(default_conf) -> None: conf.pop('exchange') with pytest.raises(ValidationError, match=r'.*\'exchange\' is a required property.*'): - configuration = Configuration([]) + configuration = Configuration(Namespace()) configuration._validate_config(conf) @@ -61,7 +62,7 @@ def test_load_config_file(default_conf, mocker, caplog) -> None: read_data=json.dumps(default_conf) )) - configuration = Configuration([]) + configuration = Configuration(Namespace()) validated_conf = configuration._load_config_file('somefile') assert file_mock.call_count == 1 assert validated_conf.items() >= default_conf.items() @@ -79,7 +80,7 @@ def test_load_config_max_open_trades_zero(default_conf, mocker, caplog) -> None: read_data=json.dumps(conf) )) - Configuration([])._load_config_file('somefile') + Configuration(Namespace())._load_config_file('somefile') assert file_mock.call_count == 1 assert log_has('Validating configuration ...', caplog.record_tuples) @@ -92,7 +93,7 @@ def test_load_config_file_exception(mocker, caplog) -> None: 'freqtrade.configuration.open', MagicMock(side_effect=FileNotFoundError('File not found')) ) - configuration = Configuration([]) + configuration = Configuration(Namespace()) with pytest.raises(SystemExit): configuration._load_config_file('somefile') @@ -128,13 +129,13 @@ def test_load_config_with_params(default_conf, mocker) -> None: read_data=json.dumps(default_conf) )) - args = [ + arglist = [ '--dynamic-whitelist', '10', '--strategy', 'TestStrategy', '--strategy-path', '/some/path', '--dry-run-db', ] - args = Arguments(args, '').get_parsed_arg() + args = Arguments(arglist, '').get_parsed_arg() configuration = Configuration(args) validated_conf = configuration.load_config() @@ -174,12 +175,12 @@ def test_show_info(default_conf, mocker, caplog) -> None: read_data=json.dumps(default_conf) )) - args = [ + arglist = [ '--dynamic-whitelist', '10', '--strategy', 'TestStrategy', '--dry-run-db' ] - args = Arguments(args, '').get_parsed_arg() + args = Arguments(arglist, '').get_parsed_arg() configuration = Configuration(args) configuration.get_config() @@ -202,8 +203,8 @@ def test_show_info(default_conf, mocker, caplog) -> None: ) # Test the Dry run condition - configuration.config.update({'dry_run': False}) - configuration._load_common_config(configuration.config) + configuration.config.update({'dry_run': False}) # type: ignore + configuration._load_common_config(configuration.config) # type: ignore assert log_has( 'Dry run is disabled. (--dry_run_db ignored)', caplog.record_tuples @@ -218,13 +219,13 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> read_data=json.dumps(default_conf) )) - args = [ + arglist = [ '--config', 'config.json', '--strategy', 'DefaultStrategy', 'backtesting' ] - args = Arguments(args, '').get_parsed_arg() + args = Arguments(arglist, '').get_parsed_arg() configuration = Configuration(args) config = configuration.get_config() @@ -262,7 +263,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non read_data=json.dumps(default_conf) )) - args = [ + arglist = [ '--config', 'config.json', '--strategy', 'DefaultStrategy', '--datadir', '/foo/bar', @@ -275,7 +276,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non '--export', '/bar/foo' ] - args = Arguments(args, '').get_parsed_arg() + args = Arguments(arglist, '').get_parsed_arg() configuration = Configuration(args) config = configuration.get_config() @@ -326,14 +327,14 @@ def test_hyperopt_with_arguments(mocker, default_conf, caplog) -> None: read_data=json.dumps(default_conf) )) - args = [ + arglist = [ 'hyperopt', '--epochs', '10', '--use-mongodb', '--spaces', 'all', ] - args = Arguments(args, '').get_parsed_arg() + args = Arguments(arglist, '').get_parsed_arg() configuration = Configuration(args) config = configuration.get_config() @@ -357,7 +358,7 @@ def test_check_exchange(default_conf) -> None: Test the configuration validator with a missing attribute """ conf = deepcopy(default_conf) - configuration = Configuration([]) + configuration = Configuration(Namespace()) # Test a valid exchange conf.get('exchange').update({'name': 'BITTREX'}) From 69006b8fe821948f191839762f9246cacaba9dc8 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Thu, 31 May 2018 21:08:11 +0200 Subject: [PATCH 10/29] flake8 --- freqtrade/persistence.py | 2 +- freqtrade/rpc/rpc_manager.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index c10599b3c..47a7ac4ab 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -5,7 +5,7 @@ This module contains the class to persist trades into SQLite import logging from datetime import datetime from decimal import Decimal, getcontext -from typing import Dict, Optional, Any +from typing import Dict, Optional import arrow from sqlalchemy import (Boolean, Column, DateTime, Float, Integer, String, diff --git a/freqtrade/rpc/rpc_manager.py b/freqtrade/rpc/rpc_manager.py index bedd8fdd4..58e9bf2b9 100644 --- a/freqtrade/rpc/rpc_manager.py +++ b/freqtrade/rpc/rpc_manager.py @@ -1,7 +1,7 @@ """ This module contains class to manage RPC communications (Telegram, Slack, ...) """ -from typing import Any, Optional, List +from typing import Any, List import logging from freqtrade.rpc.telegram import Telegram From 2976a50c58a1ad9e43a6f12fc9ff9ebc0c3f7f77 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Thu, 31 May 2018 21:10:15 +0200 Subject: [PATCH 11/29] fix typing --- freqtrade/persistence.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 47a7ac4ab..f9a7d1e3c 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -5,7 +5,7 @@ This module contains the class to persist trades into SQLite import logging from datetime import datetime from decimal import Decimal, getcontext -from typing import Dict, Optional +from typing import Dict, Optional, Any import arrow from sqlalchemy import (Boolean, Column, DateTime, Float, Integer, String, @@ -21,7 +21,7 @@ from sqlalchemy import inspect logger = logging.getLogger(__name__) _CONF = {} -_DECL_BASE = declarative_base() # type: Any +_DECL_BASE: Any = declarative_base() def init(config: dict, engine: Optional[Engine] = None) -> None: From c0cef7250d6bc394769b647b32405597cd5ce4e3 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Thu, 31 May 2018 21:22:46 +0200 Subject: [PATCH 12/29] typing - avoid variable reuse with differen ttype --- freqtrade/arguments.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index cf7db5901..2421abc9f 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -19,7 +19,7 @@ class Arguments(object): def __init__(self, args: List[str], description: str) -> None: self.args = args - self.parsed_arg = None + self.parsed_arg: Optional[argparse.Namespace] = None self.parser = argparse.ArgumentParser(description=description) def _load_args(self) -> None: @@ -211,7 +211,7 @@ class Arguments(object): self.hyperopt_options(hyperopt_cmd) @staticmethod - def parse_timerange(text: str) -> Optional[Tuple[List, int, int]]: + def parse_timerange(text: str) -> Optional[Tuple[Tuple, Optional[int], Optional[int]]]: """ Parse the value of the argument --timerange to determine what is the range desired :param text: value from --timerange @@ -231,21 +231,21 @@ class Arguments(object): if match: # Regex has matched rvals = match.groups() index = 0 - start = None - stop = None + start: Optional[int] = None + stop: Optional[int] = None if stype[0]: - start = rvals[index] + starts = rvals[index] if stype[0] == 'date': - start = arrow.get(start, 'YYYYMMDD').timestamp + start = arrow.get(starts, 'YYYYMMDD').timestamp else: - start = int(start) + start = int(starts) index += 1 if stype[1]: - stop = rvals[index] + stops = rvals[index] if stype[1] == 'date': - stop = arrow.get(stop, 'YYYYMMDD').timestamp + stop = arrow.get(stops, 'YYYYMMDD').timestamp else: - stop = int(stop) + stop = int(stops) return stype, start, stop raise Exception('Incorrect syntax for timerange "%s"' % text) From f4f821e88eeeda2b56d03004fe72a2764f6e68dc Mon Sep 17 00:00:00 2001 From: xmatthias Date: Thu, 31 May 2018 21:44:18 +0200 Subject: [PATCH 13/29] add typehints --- freqtrade/strategy/resolver.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index 8f4972919..23380dad9 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -33,7 +33,7 @@ class StrategyResolver(object): # 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')) + self.strategy: IStrategy = self._load_strategy(strategy_name, extra_dir=config.get('strategy_path')) # Set attributes # Check if we need to override configuration @@ -61,7 +61,7 @@ class StrategyResolver(object): self.strategy.stoploss = float(self.strategy.stoploss) def _load_strategy( - self, strategy_name: str, extra_dir: Optional[str] = None) -> Optional[IStrategy]: + self, strategy_name: str, extra_dir: Optional[str] = None) -> IStrategy: """ Search and loads the specified strategy. :param strategy_name: name of the module to import From cf34b84cf1c67a9bcc60f89b886e514d615d22f2 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Thu, 31 May 2018 21:59:22 +0200 Subject: [PATCH 14/29] add attributes with typehints --- freqtrade/strategy/interface.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index dcf665a02..4ae358c6f 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -2,7 +2,7 @@ IStrategy interface This module defines the interface to apply for strategies """ - +from typing import Dict from abc import ABC, abstractmethod from pandas import DataFrame @@ -16,9 +16,13 @@ class IStrategy(ABC): Attributes you can use: minimal_roi -> Dict: Minimal ROI designed for the strategy stoploss -> float: optimal stoploss designed for the strategy - ticker_interval -> int: value of the ticker interval to use for the strategy + ticker_interval -> str: value of the ticker interval to use for the strategy """ + minimal_roi: Dict + stoploss: float + ticker_interval: str + @abstractmethod def populate_indicators(self, dataframe: DataFrame) -> DataFrame: """ From 3fb1dd02f1204996da2c5cfc326cd719b64839a0 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Thu, 31 May 2018 22:00:46 +0200 Subject: [PATCH 15/29] add typehints and type: ignores --- freqtrade/analyze.py | 6 +++--- freqtrade/exchange/__init__.py | 2 +- freqtrade/optimize/hyperopt.py | 8 ++++---- freqtrade/strategy/resolver.py | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index 5756b845c..6334fd846 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -95,7 +95,7 @@ class Analyze(object): Return ticker interval to use :return: Ticker interval value to use """ - return self.strategy.ticker_interval + return self.strategy.ticker_interval # type: ignore def analyze_ticker(self, ticker_history: List[Dict]) -> DataFrame: """ @@ -195,13 +195,13 @@ class Analyze(object): :return True if bot should sell at current rate """ current_profit = trade.calc_profit_percent(current_rate) - if self.strategy.stoploss is not None and current_profit < self.strategy.stoploss: + if self.strategy.stoploss is not None and current_profit < self.strategy.stoploss: # type: ignore logger.debug('Stop loss hit.') return True # Check if time matches and current rate is above threshold time_diff = (current_time.timestamp() - trade.open_date.timestamp()) / 60 - for duration, threshold in self.strategy.minimal_roi.items(): + for duration, threshold in self.strategy.minimal_roi.items(): # type: ignore if time_diff <= duration: return False if current_profit > threshold: diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 109e3f7b2..b186f3611 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -290,7 +290,7 @@ def get_ticker_history(pair: str, tick_interval: str, since_ms: Optional[int] = # chached data was already downloaded till_time_ms = min(till_time_ms, arrow.utcnow().shift(minutes=-10).timestamp * 1000) - data = [] + data: List[Dict[Any, Any]] = [] while not since_ms or since_ms < till_time_ms: data_part = _API.fetch_ohlcv(pair, timeframe=tick_interval, since=since_ms) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 20fa5380d..b4f534d7c 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -14,7 +14,7 @@ from argparse import Namespace from functools import reduce from math import exp from operator import itemgetter -from typing import Dict, Any, Callable +from typing import Dict, Any, Callable, Optional import numpy import talib.abstract as ta @@ -60,7 +60,7 @@ class Hyperopt(Backtesting): self.expected_max_profit = 3.0 # Configuration and data used by hyperopt - self.processed = None + self.processed: Optional[Dict[str, Any]] = None # Hyperopt Trials self.trials_file = os.path.join('user_data', 'hyperopt_trials.pickle') @@ -344,7 +344,7 @@ class Hyperopt(Backtesting): """ Return the space to use during Hyperopt """ - spaces = {} + spaces: Dict = {} if self.has_space('buy'): spaces = {**spaces, **Hyperopt.indicator_space()} if self.has_space('roi'): @@ -503,7 +503,7 @@ class Hyperopt(Backtesting): ) if self.has_space('buy'): - self.analyze.populate_indicators = Hyperopt.populate_indicators + self.analyze.populate_indicators = Hyperopt.populate_indicators # type: ignore self.processed = self.tickerdata_to_dataframe(data) if self.config.get('mongodb'): diff --git a/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index 23380dad9..28465210c 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -101,7 +101,7 @@ class StrategyResolver(object): # 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) + spec.loader.exec_module(module) # type: ignore valid_strategies_gen = ( obj for name, obj in inspect.getmembers(module, inspect.isclass) From 41a47df93f81ef868f29f5cf6d9cc66d728e22bd Mon Sep 17 00:00:00 2001 From: xmatthias Date: Thu, 31 May 2018 22:09:31 +0200 Subject: [PATCH 16/29] setup travis to check mypy --- .travis.yml | 3 ++- setup.cfg | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6554f2095..1cff5c04b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ addons: install: - ./install_ta-lib.sh - export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH -- pip install --upgrade flake8 coveralls pytest-random-order +- pip install --upgrade flake8 coveralls pytest-random-order mypy - pip install -r requirements.txt - pip install -e . jobs: @@ -26,6 +26,7 @@ jobs: - cp config.json.example config.json - python freqtrade/main.py hyperopt -e 5 - script: flake8 freqtrade + - script: mypy freqtrade after_success: - coveralls notifications: diff --git a/setup.cfg b/setup.cfg index ba065a7c2..6ffad0824 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,3 +2,6 @@ #ignore = max-line-length = 100 max-complexity = 12 + +[mypy] +ignore_missing_imports = True From 633620a5e925f08e8b7c5e8f5e29317066aba45b Mon Sep 17 00:00:00 2001 From: xmatthias Date: Thu, 31 May 2018 22:15:18 +0200 Subject: [PATCH 17/29] exclude .mypy_cache --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e5ac932f8..219a9fb40 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,4 @@ target/ .vscode .pytest_cache/ +.mypy_cache/ From e28973c50a04dabd386342c9ff7b170616e2a6df Mon Sep 17 00:00:00 2001 From: xmatthias Date: Thu, 31 May 2018 22:17:46 +0200 Subject: [PATCH 18/29] fix flake8 --- freqtrade/analyze.py | 3 ++- freqtrade/strategy/resolver.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index 6334fd846..0a560c86e 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -195,7 +195,8 @@ class Analyze(object): :return True if bot should sell at current rate """ current_profit = trade.calc_profit_percent(current_rate) - if self.strategy.stoploss is not None and current_profit < self.strategy.stoploss: # type: ignore + if self.strategy.stoploss is not None \ + and current_profit < self.strategy.stoploss: # type: ignore logger.debug('Stop loss hit.') return True diff --git a/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index 28465210c..60427bcf4 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -33,7 +33,8 @@ class StrategyResolver(object): # Verify the strategy is in the configuration, otherwise fallback to the default strategy strategy_name = config.get('strategy') or constants.DEFAULT_STRATEGY - self.strategy: IStrategy = self._load_strategy(strategy_name, extra_dir=config.get('strategy_path')) + self.strategy: IStrategy = self._load_strategy(strategy_name, + extra_dir=config.get('strategy_path')) # Set attributes # Check if we need to override configuration From 4a322abd4d0736502b78d68b5d1611cb9b402931 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sat, 2 Jun 2018 13:43:51 +0200 Subject: [PATCH 19/29] Typecheck improvements --- freqtrade/optimize/backtesting.py | 6 +++--- freqtrade/optimize/hyperopt.py | 5 +++-- freqtrade/rpc/rpc.py | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 376730d0f..5a3b790c9 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -78,7 +78,7 @@ class Backtesting(object): Generates and returns a text table for the given backtest data and the results dataframe :return: pretty printed table with tabulate as str """ - stake_currency = self.config.get('stake_currency') + stake_currency = str(self.config.get('stake_currency')) floatfmt = ('s', 'd', '.2f', '.8f', '.1f') tabular_data = [] @@ -168,7 +168,7 @@ class Backtesting(object): record = args.get('record', None) records = [] trades = [] - trade_count_lock = {} + trade_count_lock: Dict = {} for pair, pair_data in processed.items(): pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run @@ -230,7 +230,7 @@ class Backtesting(object): else: logger.info('Using local backtesting data (using whitelist in given config) ...') - timerange = Arguments.parse_timerange(self.config.get('timerange')) + timerange = Arguments.parse_timerange(str(self.config.get('timerange'))) data = optimize.load_data( self.config['datadir'], pairs=pairs, diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index b4f534d7c..f6cbb270b 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -494,9 +494,10 @@ class Hyperopt(Backtesting): ) def start(self) -> None: - timerange = Arguments.parse_timerange(self.config.get('timerange')) + timerange = Arguments.parse_timerange(self.config.get('timerange') if self.config.get( + 'timerange') is None else str(self.config.get('timerange'))) data = load_data( - datadir=self.config.get('datadir'), + datadir=str(self.config.get('datadir')), pairs=self.config['exchange']['pair_whitelist'], ticker_interval=self.ticker_interval, timerange=timerange diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index df809cc2f..3e39657f2 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -2,7 +2,7 @@ This module contains class to define a RPC communications """ import logging -from datetime import datetime, timedelta +from datetime import datetime, timedelta, date from decimal import Decimal from typing import Dict, Tuple, Any, Optional @@ -114,7 +114,7 @@ class RPC(object): self, timescale: int, stake_currency: str, fiat_display_currency: str) -> Tuple[bool, Any]: today = datetime.utcnow().date() - profit_days: Dict[int, Dict] = {} + profit_days: Dict[date, Dict] = {} if not (isinstance(timescale, int) and timescale > 0): return True, '*Daily [n]:* `must be an integer greater than 0`' @@ -172,7 +172,7 @@ class RPC(object): durations = [] for trade in trades: - current_rate: Optional[float] = None + current_rate: float = 0.0 if not trade.open_rate: continue From 6106822d102ef696ef46831d1e5ef6bc8870a49e Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sat, 2 Jun 2018 13:44:41 +0200 Subject: [PATCH 20/29] typing --- freqtrade/freqtradebot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 7c955d423..50fe10667 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -33,7 +33,7 @@ class FreqtradeBot(object): This is from here the bot start its logic. """ - def __init__(self, config: Dict[str, Any], db_url: Optional[str] = None): + def __init__(self, config: Dict[str, Any], db_url: Optional[str] = None)-> None: """ Init all variables and object the bot need to work :param config: configuration dict, you can use the Configuration.get_config() @@ -93,7 +93,7 @@ class FreqtradeBot(object): persistence.cleanup() return True - def worker(self, old_state: None) -> State: + def worker(self, old_state: State = None) -> State: """ Trading routine that must be run at each loop :param old_state: the previous service state from the previous call From 6fc21e30e5388b67921a47fd389fc58764220dce Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sat, 2 Jun 2018 13:52:55 +0200 Subject: [PATCH 21/29] remove unused import --- freqtrade/rpc/rpc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 3e39657f2..715d38f95 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -4,7 +4,7 @@ This module contains class to define a RPC communications import logging from datetime import datetime, timedelta, date from decimal import Decimal -from typing import Dict, Tuple, Any, Optional +from typing import Dict, Tuple, Any import arrow import sqlalchemy as sql From d9e951447fc5bc7a43ea1d00c08fe040df0f4763 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sat, 2 Jun 2018 13:54:22 +0200 Subject: [PATCH 22/29] remove _init function in backtesting (and according test) --- freqtrade/optimize/backtesting.py | 12 ------------ freqtrade/tests/optimize/test_backtesting.py | 17 ----------------- 2 files changed, 29 deletions(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 5a3b790c9..0e77572a5 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -33,18 +33,6 @@ class Backtesting(object): """ def __init__(self, config: Dict[str, Any]) -> None: self.config = config - self.analyze = None - self.ticker_interval = None - self.tickerdata_to_dataframe = None - self.populate_buy_trend = None - self.populate_sell_trend = None - self._init() - - def _init(self) -> None: - """ - Init objects required for backtesting - :return: None - """ self.analyze = Analyze(self.config) self.ticker_interval = self.analyze.strategy.ticker_interval self.tickerdata_to_dataframe = self.analyze.tickerdata_to_dataframe diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index f17a0115e..5cc41845d 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -286,23 +286,6 @@ def test_start(mocker, fee, default_conf, caplog) -> None: assert start_mock.call_count == 1 -def test_backtesting__init__(mocker, default_conf) -> None: - """ - Test Backtesting.__init__() method - """ - init_mock = MagicMock() - mocker.patch('freqtrade.optimize.backtesting.Backtesting._init', init_mock) - - backtesting = Backtesting(default_conf) - assert backtesting.config == default_conf - assert backtesting.analyze is None - assert backtesting.ticker_interval is None - assert backtesting.tickerdata_to_dataframe is None - assert backtesting.populate_buy_trend is None - assert backtesting.populate_sell_trend is None - assert init_mock.call_count == 1 - - def test_backtesting_init(mocker, default_conf) -> None: """ Test Backtesting._init() method From 32300f6d5fe46dce5b29f9a934f568e765942840 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sat, 2 Jun 2018 13:55:06 +0200 Subject: [PATCH 23/29] don't initialize with None where it's not necessary --- freqtrade/freqtradebot.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 50fe10667..41841e911 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -51,9 +51,9 @@ class FreqtradeBot(object): # Init objects self.config = config - self.analyze = None - self.fiat_converter = None - self.rpc = None + self.analyze = Analyze(self.config) + self.fiat_converter = CryptoToFiatConverter() + self.rpc: RPCManager = RPCManager(self) self.persistence = None self.exchange = None @@ -66,9 +66,6 @@ class FreqtradeBot(object): :return: None """ # Initialize all modules - self.analyze = Analyze(self.config) - self.fiat_converter = CryptoToFiatConverter() - self.rpc = RPCManager(self) persistence.init(self.config, db_url) exchange.init(self.config) From 0a595190a3e622915defb62153ec3886ce5c4c54 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sat, 2 Jun 2018 13:59:35 +0200 Subject: [PATCH 24/29] fix last typechecks --- freqtrade/arguments.py | 3 ++- freqtrade/optimize/backtesting.py | 2 +- freqtrade/optimize/hyperopt.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/arguments.py b/freqtrade/arguments.py index 2421abc9f..fc917394e 100644 --- a/freqtrade/arguments.py +++ b/freqtrade/arguments.py @@ -211,7 +211,8 @@ class Arguments(object): self.hyperopt_options(hyperopt_cmd) @staticmethod - def parse_timerange(text: str) -> Optional[Tuple[Tuple, Optional[int], Optional[int]]]: + def parse_timerange(text: Optional[str]) -> Optional[Tuple[Tuple, + Optional[int], Optional[int]]]: """ Parse the value of the argument --timerange to determine what is the range desired :param text: value from --timerange diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 0e77572a5..78ab72988 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -219,7 +219,7 @@ class Backtesting(object): logger.info('Using local backtesting data (using whitelist in given config) ...') timerange = Arguments.parse_timerange(str(self.config.get('timerange'))) - data = optimize.load_data( + data = optimize.load_data( # type: ignore self.config['datadir'], pairs=pairs, ticker_interval=self.ticker_interval, diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index f6cbb270b..8b8e9576b 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -494,9 +494,9 @@ class Hyperopt(Backtesting): ) def start(self) -> None: - timerange = Arguments.parse_timerange(self.config.get('timerange') if self.config.get( + timerange = Arguments.parse_timerange(None if self.config.get( 'timerange') is None else str(self.config.get('timerange'))) - data = load_data( + data = load_data( # type: ignore datadir=str(self.config.get('datadir')), pairs=self.config['exchange']['pair_whitelist'], ticker_interval=self.ticker_interval, From 0007002c80822cba5085655a10ca22dd1678e251 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sat, 2 Jun 2018 14:07:54 +0200 Subject: [PATCH 25/29] fix test failure --- freqtrade/optimize/backtesting.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 78ab72988..ef69539db 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -218,7 +218,8 @@ class Backtesting(object): else: logger.info('Using local backtesting data (using whitelist in given config) ...') - timerange = Arguments.parse_timerange(str(self.config.get('timerange'))) + timerange = Arguments.parse_timerange(None if self.config.get( + 'timerange') is None else str(self.config.get('timerange'))) data = optimize.load_data( # type: ignore self.config['datadir'], pairs=pairs, From 884395415fc4e46221f0b0bac205744c8f9a4f16 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sat, 2 Jun 2018 14:10:15 +0200 Subject: [PATCH 26/29] remove type:ignore --- freqtrade/analyze.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index 0a560c86e..5756b845c 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -95,7 +95,7 @@ class Analyze(object): Return ticker interval to use :return: Ticker interval value to use """ - return self.strategy.ticker_interval # type: ignore + return self.strategy.ticker_interval def analyze_ticker(self, ticker_history: List[Dict]) -> DataFrame: """ @@ -195,14 +195,13 @@ class Analyze(object): :return True if bot should sell at current rate """ current_profit = trade.calc_profit_percent(current_rate) - if self.strategy.stoploss is not None \ - and current_profit < self.strategy.stoploss: # type: ignore + if self.strategy.stoploss is not None and current_profit < self.strategy.stoploss: logger.debug('Stop loss hit.') return True # Check if time matches and current rate is above threshold time_diff = (current_time.timestamp() - trade.open_date.timestamp()) / 60 - for duration, threshold in self.strategy.minimal_roi.items(): # type: ignore + for duration, threshold in self.strategy.minimal_roi.items(): if time_diff <= duration: return False if current_profit > threshold: From 3447e4bb9729d8f2c43240cc27ba2947471e7464 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sat, 2 Jun 2018 14:13:17 +0200 Subject: [PATCH 27/29] comment on ignore hint --- freqtrade/optimize/hyperopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 8b8e9576b..2497d6752 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -496,7 +496,7 @@ class Hyperopt(Backtesting): def start(self) -> None: timerange = Arguments.parse_timerange(None if self.config.get( 'timerange') is None else str(self.config.get('timerange'))) - data = load_data( # type: ignore + data = load_data( # type: ignore # timerange will be refactored datadir=str(self.config.get('datadir')), pairs=self.config['exchange']['pair_whitelist'], ticker_interval=self.ticker_interval, From f88729f0e855ef167027f64b6b85f07c94169f2d Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sat, 2 Jun 2018 14:14:28 +0200 Subject: [PATCH 28/29] add ignore comment --- freqtrade/optimize/backtesting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index ef69539db..a0aee78b1 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -220,7 +220,7 @@ class Backtesting(object): timerange = Arguments.parse_timerange(None if self.config.get( 'timerange') is None else str(self.config.get('timerange'))) - data = optimize.load_data( # type: ignore + data = optimize.load_data( # type: ignore # timerange will be refactored self.config['datadir'], pairs=pairs, ticker_interval=self.ticker_interval, From a8bf5092e86aaa84b28815ae3670ab852c037761 Mon Sep 17 00:00:00 2001 From: xmatthias Date: Sat, 2 Jun 2018 14:18:57 +0200 Subject: [PATCH 29/29] add ignore explanation --- freqtrade/strategy/resolver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/strategy/resolver.py b/freqtrade/strategy/resolver.py index 60427bcf4..3fd39bca3 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/strategy/resolver.py @@ -102,7 +102,7 @@ class StrategyResolver(object): # 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) # type: ignore + 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)