Merge pull request #808 from xmatthias/mypy_typecheck

add mypy typechecking
This commit is contained in:
Matthias 2018-06-03 10:43:55 +02:00 committed by GitHub
commit fff7ec1dab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 96 additions and 111 deletions

1
.gitignore vendored
View File

@ -90,3 +90,4 @@ target/
.vscode .vscode
.pytest_cache/ .pytest_cache/
.mypy_cache/

View File

@ -13,7 +13,7 @@ addons:
install: install:
- ./install_ta-lib.sh - ./install_ta-lib.sh
- export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH - 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 -r requirements.txt
- pip install -e . - pip install -e .
jobs: jobs:
@ -26,6 +26,7 @@ jobs:
- cp config.json.example config.json - cp config.json.example config.json
- python freqtrade/main.py hyperopt -e 5 - python freqtrade/main.py hyperopt -e 5
- script: flake8 freqtrade - script: flake8 freqtrade
- script: mypy freqtrade
after_success: after_success:
- coveralls - coveralls
notifications: notifications:

View File

@ -12,7 +12,7 @@ from pandas import DataFrame, to_datetime
from freqtrade import constants from freqtrade import constants
from freqtrade.exchange import get_ticker_history from freqtrade.exchange import get_ticker_history
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.strategy.resolver import StrategyResolver from freqtrade.strategy.resolver import StrategyResolver, IStrategy
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -37,7 +37,7 @@ class Analyze(object):
:param config: Bot configuration (use the one from Configuration()) :param config: Bot configuration (use the one from Configuration())
""" """
self.config = config self.config = config
self.strategy = StrategyResolver(self.config).strategy self.strategy: IStrategy = StrategyResolver(self.config).strategy
@staticmethod @staticmethod
def parse_ticker_dataframe(ticker: list) -> DataFrame: def parse_ticker_dataframe(ticker: list) -> DataFrame:

View File

@ -17,9 +17,9 @@ class Arguments(object):
Arguments Class. Manage the arguments received by the cli 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.args = args
self.parsed_arg = None self.parsed_arg: Optional[argparse.Namespace] = None
self.parser = argparse.ArgumentParser(description=description) self.parser = argparse.ArgumentParser(description=description)
def _load_args(self) -> None: def _load_args(self) -> None:
@ -211,7 +211,8 @@ class Arguments(object):
self.hyperopt_options(hyperopt_cmd) self.hyperopt_options(hyperopt_cmd)
@staticmethod @staticmethod
def parse_timerange(text: str) -> Optional[Tuple[List, int, 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 Parse the value of the argument --timerange to determine what is the range desired
:param text: value from --timerange :param text: value from --timerange
@ -234,23 +235,23 @@ class Arguments(object):
if match: # Regex has matched if match: # Regex has matched
rvals = match.groups() rvals = match.groups()
index = 0 index = 0
start = None start: Optional[int] = None
stop = None stop: Optional[int] = None
if stype[0]: if stype[0]:
start = rvals[index] starts = rvals[index]
if stype[0] == 'date': if stype[0] == 'date':
start = int(start) if len(start) == 10 \ start = int(starts) if len(starts) == 10 \
else arrow.get(start, 'YYYYMMDD').timestamp else arrow.get(starts, 'YYYYMMDD').timestamp
else: else:
start = int(start) start = int(starts)
index += 1 index += 1
if stype[1]: if stype[1]:
stop = rvals[index] stops = rvals[index]
if stype[1] == 'date': if stype[1] == 'date':
stop = int(stop) if len(stop) == 10 \ stop = int(stops) if len(stops) == 10 \
else arrow.get(stop, 'YYYYMMDD').timestamp else arrow.get(stops, 'YYYYMMDD').timestamp
else: else:
stop = int(stop) stop = int(stops)
return stype, start, stop return stype, start, stop
raise Exception('Incorrect syntax for timerange "%s"' % text) raise Exception('Incorrect syntax for timerange "%s"' % text)

View File

@ -5,7 +5,7 @@ This module contains the configuration class
import json import json
import logging import logging
from argparse import Namespace from argparse import Namespace
from typing import Dict, Any from typing import Optional, Dict, Any
from jsonschema import Draft4Validator, validate from jsonschema import Draft4Validator, validate
from jsonschema.exceptions import ValidationError, best_match from jsonschema.exceptions import ValidationError, best_match
import ccxt import ccxt
@ -23,7 +23,7 @@ class Configuration(object):
""" """
def __init__(self, args: Namespace) -> None: def __init__(self, args: Namespace) -> None:
self.args = args self.args = args
self.config = None self.config: Optional[Dict[str, Any]] = None
def load_config(self) -> Dict[str, Any]: def load_config(self) -> Dict[str, Any]:
""" """
@ -192,7 +192,7 @@ class Configuration(object):
validate(conf, constants.CONF_SCHEMA) validate(conf, constants.CONF_SCHEMA)
return conf return conf
except ValidationError as exception: except ValidationError as exception:
logger.fatal( logger.critical(
'Invalid configuration. See config.json.example. Reason: %s', 'Invalid configuration. See config.json.example. Reason: %s',
exception exception
) )

View File

@ -290,7 +290,7 @@ def get_ticker_history(pair: str, tick_interval: str, since_ms: Optional[int] =
# chached data was already downloaded # chached data was already downloaded
till_time_ms = min(till_time_ms, arrow.utcnow().shift(minutes=-10).timestamp * 1000) 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: while not since_ms or since_ms < till_time_ms:
data_part = _API.fetch_ohlcv(pair, timeframe=tick_interval, since=since_ms) data_part = _API.fetch_ohlcv(pair, timeframe=tick_interval, since=since_ms)

View File

@ -5,7 +5,7 @@ e.g BTC to USD
import logging import logging
import time import time
from typing import Dict from typing import Dict, List
from coinmarketcap import Market from coinmarketcap import Market
from requests.exceptions import RequestException from requests.exceptions import RequestException
@ -34,7 +34,7 @@ class CryptoFiat(object):
self.price = 0.0 self.price = 0.0
# Private attributes # Private attributes
self._expiration = 0 self._expiration = 0.0
self.crypto_symbol = crypto_symbol.upper() self.crypto_symbol = crypto_symbol.upper()
self.fiat_symbol = fiat_symbol.upper() self.fiat_symbol = fiat_symbol.upper()
@ -65,7 +65,7 @@ class CryptoToFiatConverter(object):
This object is also a Singleton This object is also a Singleton
""" """
__instance = None __instance = None
_coinmarketcap = None _coinmarketcap: Market = None
# Constants # Constants
SUPPORTED_FIAT = [ SUPPORTED_FIAT = [
@ -87,7 +87,7 @@ class CryptoToFiatConverter(object):
return CryptoToFiatConverter.__instance return CryptoToFiatConverter.__instance
def __init__(self) -> None: def __init__(self) -> None:
self._pairs = [] self._pairs: List[CryptoFiat] = []
self._load_cryptomap() self._load_cryptomap()
def _load_cryptomap(self) -> None: def _load_cryptomap(self) -> None:

View File

@ -33,7 +33,7 @@ class FreqtradeBot(object):
This is from here the bot start its logic. 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 Init all variables and object the bot need to work
:param config: configuration dict, you can use the Configuration.get_config() :param config: configuration dict, you can use the Configuration.get_config()
@ -51,9 +51,9 @@ class FreqtradeBot(object):
# Init objects # Init objects
self.config = config self.config = config
self.analyze = None self.analyze = Analyze(self.config)
self.fiat_converter = None self.fiat_converter = CryptoToFiatConverter()
self.rpc = None self.rpc: RPCManager = RPCManager(self)
self.persistence = None self.persistence = None
self.exchange = None self.exchange = None
@ -66,9 +66,6 @@ class FreqtradeBot(object):
:return: None :return: None
""" """
# Initialize all modules # Initialize all modules
self.analyze = Analyze(self.config)
self.fiat_converter = CryptoToFiatConverter()
self.rpc = RPCManager(self)
persistence.init(self.config, db_url) persistence.init(self.config, db_url)
exchange.init(self.config) exchange.init(self.config)
@ -93,7 +90,7 @@ class FreqtradeBot(object):
persistence.cleanup() persistence.cleanup()
return True 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 Trading routine that must be run at each loop
:param old_state: the previous service state from the previous call :param old_state: the previous service state from the previous call

View File

@ -13,7 +13,7 @@ def went_down(series: Series) -> bool:
return series < series.shift(1) 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 magic = pi * sqrt(2) / smoothing
a1 = exp(-magic) a1 = exp(-magic)
coeff2 = 2 * a1 * cos(magic) coeff2 = 2 * a1 * cos(magic)

View File

@ -83,7 +83,7 @@ def file_dump_json(filename, data, is_zip=False) -> None:
json.dump(data, fp, default=str) 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. convert MS date to readable format.
: epoch-string in ms : epoch-string in ms

View File

@ -4,8 +4,8 @@ import gzip
import json import json
import logging import logging
import os import os
from typing import Optional, List, Dict, Tuple, Any
import arrow import arrow
from typing import Optional, List, Dict, Tuple
from freqtrade import misc, constants from freqtrade import misc, constants
from freqtrade.exchange import get_ticker_history from freqtrade.exchange import get_ticker_history
@ -144,7 +144,9 @@ def download_pairs(datadir, pairs: List[str],
def load_cached_data_for_updating(filename: str, def load_cached_data_for_updating(filename: str,
tick_interval: 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 Load cached data and choose what part of the data should be updated
""" """

View File

@ -33,18 +33,6 @@ class Backtesting(object):
""" """
def __init__(self, config: Dict[str, Any]) -> None: def __init__(self, config: Dict[str, Any]) -> None:
self.config = config 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.analyze = Analyze(self.config)
self.ticker_interval = self.analyze.strategy.ticker_interval self.ticker_interval = self.analyze.strategy.ticker_interval
self.tickerdata_to_dataframe = self.analyze.tickerdata_to_dataframe self.tickerdata_to_dataframe = self.analyze.tickerdata_to_dataframe
@ -78,7 +66,7 @@ class Backtesting(object):
Generates and returns a text table for the given backtest data and the results dataframe Generates and returns a text table for the given backtest data and the results dataframe
:return: pretty printed table with tabulate as str :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') floatfmt = ('s', 'd', '.2f', '.8f', '.1f')
tabular_data = [] tabular_data = []
@ -168,7 +156,7 @@ class Backtesting(object):
record = args.get('record', None) record = args.get('record', None)
records = [] records = []
trades = [] trades = []
trade_count_lock = {} trade_count_lock: Dict = {}
for pair, pair_data in processed.items(): for pair, pair_data in processed.items():
pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run
@ -230,8 +218,9 @@ class Backtesting(object):
else: else:
logger.info('Using local backtesting data (using whitelist in given config) ...') logger.info('Using local backtesting data (using whitelist in given config) ...')
timerange = Arguments.parse_timerange(self.config.get('timerange')) timerange = Arguments.parse_timerange(None if self.config.get(
data = optimize.load_data( 'timerange') is None else str(self.config.get('timerange')))
data = optimize.load_data( # type: ignore # timerange will be refactored
self.config['datadir'], self.config['datadir'],
pairs=pairs, pairs=pairs,
ticker_interval=self.ticker_interval, ticker_interval=self.ticker_interval,

View File

@ -14,7 +14,7 @@ from argparse import Namespace
from functools import reduce from functools import reduce
from math import exp from math import exp
from operator import itemgetter from operator import itemgetter
from typing import Dict, Any, Callable from typing import Dict, Any, Callable, Optional
import numpy import numpy
import talib.abstract as ta import talib.abstract as ta
@ -60,7 +60,7 @@ class Hyperopt(Backtesting):
self.expected_max_profit = 3.0 self.expected_max_profit = 3.0
# Configuration and data used by hyperopt # Configuration and data used by hyperopt
self.processed = None self.processed: Optional[Dict[str, Any]] = None
# Hyperopt Trials # Hyperopt Trials
self.trials_file = os.path.join('user_data', 'hyperopt_trials.pickle') 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 Return the space to use during Hyperopt
""" """
spaces = {} spaces: Dict = {}
if self.has_space('buy'): if self.has_space('buy'):
spaces = {**spaces, **Hyperopt.indicator_space()} spaces = {**spaces, **Hyperopt.indicator_space()}
if self.has_space('roi'): if self.has_space('roi'):
@ -495,16 +495,17 @@ class Hyperopt(Backtesting):
) )
def start(self) -> None: def start(self) -> None:
timerange = Arguments.parse_timerange(self.config.get('timerange')) timerange = Arguments.parse_timerange(None if self.config.get(
data = load_data( 'timerange') is None else str(self.config.get('timerange')))
datadir=self.config.get('datadir'), data = load_data( # type: ignore # timerange will be refactored
datadir=str(self.config.get('datadir')),
pairs=self.config['exchange']['pair_whitelist'], pairs=self.config['exchange']['pair_whitelist'],
ticker_interval=self.ticker_interval, ticker_interval=self.ticker_interval,
timerange=timerange timerange=timerange
) )
if self.has_space('buy'): 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) self.processed = self.tickerdata_to_dataframe(data)
if self.config.get('mongodb'): if self.config.get('mongodb'):

View File

@ -5,7 +5,7 @@ This module contains the class to persist trades into SQLite
import logging import logging
from datetime import datetime from datetime import datetime
from decimal import Decimal, getcontext from decimal import Decimal, getcontext
from typing import Dict, Optional from typing import Dict, Optional, Any
import arrow import arrow
from sqlalchemy import (Boolean, Column, DateTime, Float, Integer, String, from sqlalchemy import (Boolean, Column, DateTime, Float, Integer, String,
@ -21,7 +21,7 @@ from sqlalchemy import inspect
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
_CONF = {} _CONF = {}
_DECL_BASE = declarative_base() _DECL_BASE: Any = declarative_base()
def init(config: dict, engine: Optional[Engine] = None) -> None: def init(config: dict, engine: Optional[Engine] = None) -> None:

View File

@ -2,9 +2,9 @@
This module contains class to define a RPC communications This module contains class to define a RPC communications
""" """
import logging import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta, date
from decimal import Decimal from decimal import Decimal
from typing import Tuple, Any from typing import Dict, Tuple, Any
import arrow import arrow
import sqlalchemy as sql import sqlalchemy as sql
@ -114,7 +114,7 @@ class RPC(object):
self, timescale: int, self, timescale: int,
stake_currency: str, fiat_display_currency: str) -> Tuple[bool, Any]: stake_currency: str, fiat_display_currency: str) -> Tuple[bool, Any]:
today = datetime.utcnow().date() today = datetime.utcnow().date()
profit_days = {} profit_days: Dict[date, Dict] = {}
if not (isinstance(timescale, int) and timescale > 0): if not (isinstance(timescale, int) and timescale > 0):
return True, '*Daily [n]:* `must be an integer greater than 0`' return True, '*Daily [n]:* `must be an integer greater than 0`'
@ -172,7 +172,7 @@ class RPC(object):
durations = [] durations = []
for trade in trades: for trade in trades:
current_rate = None current_rate: float = 0.0
if not trade.open_rate: if not trade.open_rate:
continue continue
@ -278,7 +278,7 @@ class RPC(object):
value = fiat.convert_amount(total, 'BTC', symbol) value = fiat.convert_amount(total, 'BTC', symbol)
return False, (output, total, symbol, value) return False, (output, total, symbol, value)
def rpc_start(self) -> (bool, str): def rpc_start(self) -> Tuple[bool, str]:
""" """
Handler for start. Handler for start.
""" """
@ -288,7 +288,7 @@ class RPC(object):
self.freqtrade.state = State.RUNNING self.freqtrade.state = State.RUNNING
return False, '`Starting trader ...`' return False, '`Starting trader ...`'
def rpc_stop(self) -> (bool, str): def rpc_stop(self) -> Tuple[bool, str]:
""" """
Handler for stop. Handler for stop.
""" """

View File

@ -1,6 +1,7 @@
""" """
This module contains class to manage RPC communications (Telegram, Slack, ...) This module contains class to manage RPC communications (Telegram, Slack, ...)
""" """
from typing import Any, List
import logging import logging
from freqtrade.rpc.telegram import Telegram from freqtrade.rpc.telegram import Telegram
@ -21,8 +22,8 @@ class RPCManager(object):
""" """
self.freqtrade = freqtrade self.freqtrade = freqtrade
self.registered_modules = [] self.registered_modules: List[str] = []
self.telegram = None self.telegram: Any = None
self._init() self._init()
def _init(self) -> None: def _init(self) -> None:

View File

@ -18,7 +18,7 @@ from freqtrade.rpc.rpc import RPC
logger = logging.getLogger(__name__) 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 Decorator to check if the message comes from the correct chat_id
:param command_handler: Telegram CommandHandler :param command_handler: Telegram CommandHandler
@ -65,7 +65,7 @@ class Telegram(RPC):
""" """
super().__init__(freqtrade) super().__init__(freqtrade)
self._updater = None self._updater: Updater = None
self._config = freqtrade.config self._config = freqtrade.config
self._init() self._init()

View File

@ -2,7 +2,7 @@
IStrategy interface IStrategy interface
This module defines the interface to apply for strategies This module defines the interface to apply for strategies
""" """
from typing import Dict
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from pandas import DataFrame from pandas import DataFrame
@ -16,9 +16,13 @@ class IStrategy(ABC):
Attributes you can use: Attributes you can use:
minimal_roi -> Dict: Minimal ROI designed for the strategy minimal_roi -> Dict: Minimal ROI designed for the strategy
stoploss -> float: optimal stoploss 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 @abstractmethod
def populate_indicators(self, dataframe: DataFrame) -> DataFrame: def populate_indicators(self, dataframe: DataFrame) -> DataFrame:
""" """

View File

@ -33,7 +33,8 @@ class StrategyResolver(object):
# Verify the strategy is in the configuration, otherwise fallback to the default strategy # Verify the strategy is in the configuration, otherwise fallback to the default strategy
strategy_name = config.get('strategy') or constants.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 # Set attributes
# Check if we need to override configuration # Check if we need to override configuration
@ -61,7 +62,7 @@ class StrategyResolver(object):
self.strategy.stoploss = float(self.strategy.stoploss) self.strategy.stoploss = float(self.strategy.stoploss)
def _load_strategy( 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. Search and loads the specified strategy.
:param strategy_name: name of the module to import :param strategy_name: name of the module to import
@ -101,7 +102,7 @@ class StrategyResolver(object):
# Generate spec based on absolute path # Generate spec based on absolute path
spec = importlib.util.spec_from_file_location('user_data.strategies', module_path) spec = importlib.util.spec_from_file_location('user_data.strategies', module_path)
module = importlib.util.module_from_spec(spec) module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) spec.loader.exec_module(module) # type: ignore # importlib does not use typehints
valid_strategies_gen = ( valid_strategies_gen = (
obj for name, obj in inspect.getmembers(module, inspect.isclass) obj for name, obj in inspect.getmembers(module, inspect.isclass)

View File

@ -286,23 +286,6 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
assert start_mock.call_count == 1 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: def test_backtesting_init(mocker, default_conf) -> None:
""" """
Test Backtesting._init() method Test Backtesting._init() method

View File

@ -6,6 +6,7 @@ Unit test file for configuration.py
import json import json
from copy import deepcopy from copy import deepcopy
from unittest.mock import MagicMock from unittest.mock import MagicMock
from argparse import Namespace
import pytest import pytest
from jsonschema import ValidationError from jsonschema import ValidationError
@ -37,7 +38,7 @@ def test_load_config_invalid_pair(default_conf) -> None:
conf['exchange']['pair_whitelist'].append('ETH-BTC') conf['exchange']['pair_whitelist'].append('ETH-BTC')
with pytest.raises(ValidationError, match=r'.*does not match.*'): with pytest.raises(ValidationError, match=r'.*does not match.*'):
configuration = Configuration([]) configuration = Configuration(Namespace())
configuration._validate_config(conf) configuration._validate_config(conf)
@ -49,7 +50,7 @@ def test_load_config_missing_attributes(default_conf) -> None:
conf.pop('exchange') conf.pop('exchange')
with pytest.raises(ValidationError, match=r'.*\'exchange\' is a required property.*'): with pytest.raises(ValidationError, match=r'.*\'exchange\' is a required property.*'):
configuration = Configuration([]) configuration = Configuration(Namespace())
configuration._validate_config(conf) configuration._validate_config(conf)
@ -61,7 +62,7 @@ def test_load_config_file(default_conf, mocker, caplog) -> None:
read_data=json.dumps(default_conf) read_data=json.dumps(default_conf)
)) ))
configuration = Configuration([]) configuration = Configuration(Namespace())
validated_conf = configuration._load_config_file('somefile') validated_conf = configuration._load_config_file('somefile')
assert file_mock.call_count == 1 assert file_mock.call_count == 1
assert validated_conf.items() >= default_conf.items() 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) read_data=json.dumps(conf)
)) ))
Configuration([])._load_config_file('somefile') Configuration(Namespace())._load_config_file('somefile')
assert file_mock.call_count == 1 assert file_mock.call_count == 1
assert log_has('Validating configuration ...', caplog.record_tuples) assert log_has('Validating configuration ...', caplog.record_tuples)
@ -92,7 +93,7 @@ def test_load_config_file_exception(mocker, caplog) -> None:
'freqtrade.configuration.open', 'freqtrade.configuration.open',
MagicMock(side_effect=FileNotFoundError('File not found')) MagicMock(side_effect=FileNotFoundError('File not found'))
) )
configuration = Configuration([]) configuration = Configuration(Namespace())
with pytest.raises(SystemExit): with pytest.raises(SystemExit):
configuration._load_config_file('somefile') 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) read_data=json.dumps(default_conf)
)) ))
args = [ arglist = [
'--dynamic-whitelist', '10', '--dynamic-whitelist', '10',
'--strategy', 'TestStrategy', '--strategy', 'TestStrategy',
'--strategy-path', '/some/path', '--strategy-path', '/some/path',
'--dry-run-db', '--dry-run-db',
] ]
args = Arguments(args, '').get_parsed_arg() args = Arguments(arglist, '').get_parsed_arg()
configuration = Configuration(args) configuration = Configuration(args)
validated_conf = configuration.load_config() validated_conf = configuration.load_config()
@ -174,12 +175,12 @@ def test_show_info(default_conf, mocker, caplog) -> None:
read_data=json.dumps(default_conf) read_data=json.dumps(default_conf)
)) ))
args = [ arglist = [
'--dynamic-whitelist', '10', '--dynamic-whitelist', '10',
'--strategy', 'TestStrategy', '--strategy', 'TestStrategy',
'--dry-run-db' '--dry-run-db'
] ]
args = Arguments(args, '').get_parsed_arg() args = Arguments(arglist, '').get_parsed_arg()
configuration = Configuration(args) configuration = Configuration(args)
configuration.get_config() configuration.get_config()
@ -202,8 +203,8 @@ def test_show_info(default_conf, mocker, caplog) -> None:
) )
# Test the Dry run condition # Test the Dry run condition
configuration.config.update({'dry_run': False}) configuration.config.update({'dry_run': False}) # type: ignore
configuration._load_common_config(configuration.config) configuration._load_common_config(configuration.config) # type: ignore
assert log_has( assert log_has(
'Dry run is disabled. (--dry_run_db ignored)', 'Dry run is disabled. (--dry_run_db ignored)',
caplog.record_tuples caplog.record_tuples
@ -218,13 +219,13 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
read_data=json.dumps(default_conf) read_data=json.dumps(default_conf)
)) ))
args = [ arglist = [
'--config', 'config.json', '--config', 'config.json',
'--strategy', 'DefaultStrategy', '--strategy', 'DefaultStrategy',
'backtesting' 'backtesting'
] ]
args = Arguments(args, '').get_parsed_arg() args = Arguments(arglist, '').get_parsed_arg()
configuration = Configuration(args) configuration = Configuration(args)
config = configuration.get_config() 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) read_data=json.dumps(default_conf)
)) ))
args = [ arglist = [
'--config', 'config.json', '--config', 'config.json',
'--strategy', 'DefaultStrategy', '--strategy', 'DefaultStrategy',
'--datadir', '/foo/bar', '--datadir', '/foo/bar',
@ -275,7 +276,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
'--export', '/bar/foo' '--export', '/bar/foo'
] ]
args = Arguments(args, '').get_parsed_arg() args = Arguments(arglist, '').get_parsed_arg()
configuration = Configuration(args) configuration = Configuration(args)
config = configuration.get_config() config = configuration.get_config()
@ -326,14 +327,14 @@ def test_hyperopt_with_arguments(mocker, default_conf, caplog) -> None:
read_data=json.dumps(default_conf) read_data=json.dumps(default_conf)
)) ))
args = [ arglist = [
'hyperopt', 'hyperopt',
'--epochs', '10', '--epochs', '10',
'--use-mongodb', '--use-mongodb',
'--spaces', 'all', '--spaces', 'all',
] ]
args = Arguments(args, '').get_parsed_arg() args = Arguments(arglist, '').get_parsed_arg()
configuration = Configuration(args) configuration = Configuration(args)
config = configuration.get_config() config = configuration.get_config()
@ -357,7 +358,7 @@ def test_check_exchange(default_conf) -> None:
Test the configuration validator with a missing attribute Test the configuration validator with a missing attribute
""" """
conf = deepcopy(default_conf) conf = deepcopy(default_conf)
configuration = Configuration([]) configuration = Configuration(Namespace())
# Test a valid exchange # Test a valid exchange
conf.get('exchange').update({'name': 'BITTREX'}) conf.get('exchange').update({'name': 'BITTREX'})

View File

@ -2,3 +2,6 @@
#ignore = #ignore =
max-line-length = 100 max-line-length = 100
max-complexity = 12 max-complexity = 12
[mypy]
ignore_missing_imports = True