Merge branch 'develop' into data_handler

This commit is contained in:
Matthias
2019-12-30 19:40:43 +01:00
53 changed files with 319 additions and 156 deletions

View File

@@ -11,34 +11,3 @@ if __version__ == 'develop':
except Exception:
# git not available, ignore
pass
class DependencyException(Exception):
"""
Indicates that an assumed dependency is not met.
This could happen when there is currently not enough money on the account.
"""
class OperationalException(Exception):
"""
Requires manual intervention and will usually stop the bot.
This happens when an exchange returns an unexpected error during runtime
or given configuration is invalid.
"""
class InvalidOrderException(Exception):
"""
This is returned when the order is not valid. Example:
If stoploss on exchange order is hit, then trying to cancel the order
should return this exception.
"""
class TemporaryError(Exception):
"""
Temporary network or exchange related error.
This could happen when an exchange is congested, unavailable, or the user
has networking problems. Usually resolves itself after a time.
"""

View File

@@ -1,9 +1,9 @@
import logging
from typing import Any, Dict
from freqtrade import OperationalException
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import (available_exchanges, get_exchange_bad_reason,
is_exchange_known_ccxt, is_exchange_bad,
is_exchange_bad, is_exchange_known_ccxt,
is_exchange_officially_supported)
from freqtrade.state import RunMode

View File

@@ -4,7 +4,8 @@ from typing import Any, Dict
from jsonschema import Draft4Validator, validators
from jsonschema.exceptions import ValidationError, best_match
from freqtrade import constants, OperationalException
from freqtrade import constants
from freqtrade.exceptions import OperationalException
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)

View File

@@ -7,15 +7,16 @@ from copy import deepcopy
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional
from freqtrade import OperationalException, constants
from freqtrade import constants
from freqtrade.configuration.check_exchange import check_exchange
from freqtrade.configuration.deprecated_settings import process_temporary_deprecated_settings
from freqtrade.configuration.directory_operations import (create_datadir,
create_userdata_dir)
from freqtrade.configuration.load_config import load_config_file
from freqtrade.exceptions import OperationalException
from freqtrade.loggers import setup_logging
from freqtrade.misc import deep_merge_dicts, json_load
from freqtrade.state import RunMode, TRADING_MODES, NON_UTIL_MODES
from freqtrade.state import NON_UTIL_MODES, TRADING_MODES, RunMode
logger = logging.getLogger(__name__)

View File

@@ -5,7 +5,7 @@ Functions to handle deprecated settings
import logging
from typing import Any, Dict
from freqtrade import OperationalException
from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__)

View File

@@ -3,7 +3,7 @@ import shutil
from pathlib import Path
from typing import Any, Dict, Optional
from freqtrade import OperationalException
from freqtrade.exceptions import OperationalException
from freqtrade.constants import USER_DATA_FILES
logger = logging.getLogger(__name__)

View File

@@ -6,7 +6,7 @@ import logging
import sys
from typing import Any, Dict
from freqtrade import OperationalException
from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__)

View File

@@ -7,11 +7,11 @@ from typing import Dict, List, Optional, Tuple
import arrow
from pandas import DataFrame
from freqtrade import OperationalException
from freqtrade.configuration import TimeRange
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS
from freqtrade.data.converter import parse_ticker_dataframe, trades_to_ohlcv
from freqtrade.data.history.idatahandler import IDataHandler, get_datahandler
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import Exchange
logger = logging.getLogger(__name__)

View File

@@ -8,12 +8,12 @@ import numpy as np
import utils_find_1st as utf1st
from pandas import DataFrame
from freqtrade import constants, OperationalException
from freqtrade import constants
from freqtrade.configuration import TimeRange
from freqtrade.data import history
from freqtrade.exceptions import OperationalException
from freqtrade.strategy.interface import SellType
logger = logging.getLogger(__name__)

37
freqtrade/exceptions.py Normal file
View File

@@ -0,0 +1,37 @@
class FreqtradeException(Exception):
"""
Freqtrade base exception. Handled at the outermost level.
All other exception types are subclasses of this exception type.
"""
class OperationalException(FreqtradeException):
"""
Requires manual intervention and will stop the bot.
Most of the time, this is caused by an invalid Configuration.
"""
class DependencyException(FreqtradeException):
"""
Indicates that an assumed dependency is not met.
This could happen when there is currently not enough money on the account.
"""
class InvalidOrderException(FreqtradeException):
"""
This is returned when the order is not valid. Example:
If stoploss on exchange order is hit, then trying to cancel the order
should return this exception.
"""
class TemporaryError(FreqtradeException):
"""
Temporary network or exchange related error.
This could happen when an exchange is congested, unavailable, or the user
has networking problems. Usually resolves itself after a time.
"""

View File

@@ -4,8 +4,8 @@ from typing import Dict
import ccxt
from freqtrade import (DependencyException, InvalidOrderException,
OperationalException, TemporaryError)
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
OperationalException, TemporaryError)
from freqtrade.exchange import Exchange
logger = logging.getLogger(__name__)

View File

@@ -1,6 +1,6 @@
import logging
from freqtrade import DependencyException, TemporaryError
from freqtrade.exceptions import DependencyException, TemporaryError
logger = logging.getLogger(__name__)

View File

@@ -17,9 +17,9 @@ import ccxt.async_support as ccxt_async
from ccxt.base.decimal_to_precision import ROUND_DOWN, ROUND_UP
from pandas import DataFrame
from freqtrade import (DependencyException, InvalidOrderException,
OperationalException, TemporaryError)
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
OperationalException, TemporaryError)
from freqtrade.exchange.common import BAD_EXCHANGES, retrier, retrier_async
from freqtrade.misc import deep_merge_dicts

View File

@@ -4,7 +4,7 @@ from typing import Dict
import ccxt
from freqtrade import OperationalException, TemporaryError
from freqtrade.exceptions import OperationalException, TemporaryError
from freqtrade.exchange import Exchange
from freqtrade.exchange.exchange import retrier

View File

@@ -12,17 +12,17 @@ from typing import Any, Dict, List, Optional, Tuple
import arrow
from requests.exceptions import RequestException
from freqtrade import (DependencyException, InvalidOrderException, __version__,
constants, persistence)
from freqtrade import __version__, constants, persistence
from freqtrade.configuration import validate_config_consistency
from freqtrade.data.converter import order_book_to_dataframe
from freqtrade.data.dataprovider import DataProvider
from freqtrade.edge import Edge
from freqtrade.exceptions import DependencyException, InvalidOrderException
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date
from freqtrade.pairlist.pairlistmanager import PairListManager
from freqtrade.persistence import Trade
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.rpc import RPCManager, RPCMessageType
from freqtrade.pairlist.pairlistmanager import PairListManager
from freqtrade.state import State
from freqtrade.strategy.interface import IStrategy, SellType
from freqtrade.wallets import Wallets
@@ -136,7 +136,7 @@ class FreqtradeBot:
self.process_maybe_execute_sells(trades)
# Then looking for buy opportunities
if len(trades) < self.config['max_open_trades']:
if self.get_free_open_trades():
self.process_maybe_execute_buys()
# Check and handle any timed out open orders
@@ -173,6 +173,14 @@ class FreqtradeBot:
"""
return [(pair, self.config['ticker_interval']) for pair in pairs]
def get_free_open_trades(self):
"""
Return the number of free open trades slots or 0 if
max number of open trades reached
"""
open_trades = len(Trade.get_open_trades())
return max(0, self.config['max_open_trades'] - open_trades)
def get_target_bid(self, pair: str, tick: Dict = None) -> float:
"""
Calculates bid target between current ask price and last price
@@ -204,14 +212,14 @@ class FreqtradeBot:
return used_rate
def _get_trade_stake_amount(self, pair) -> Optional[float]:
def get_trade_stake_amount(self, pair) -> Optional[float]:
"""
Check if stake amount can be fulfilled with the available balance
for the stake currency
:return: float: Stake Amount
Calculate stake amount for the trade
:return: float: Stake amount
"""
stake_amount: Optional[float]
if self.edge:
return self.edge.stake_amount(
stake_amount = self.edge.stake_amount(
pair,
self.wallets.get_free(self.config['stake_currency']),
self.wallets.get_total(self.config['stake_currency']),
@@ -219,18 +227,31 @@ class FreqtradeBot:
)
else:
stake_amount = self.config['stake_amount']
if stake_amount == constants.UNLIMITED_STAKE_AMOUNT:
stake_amount = self._calculate_unlimited_stake_amount()
return self._check_available_stake_amount(stake_amount)
def _calculate_unlimited_stake_amount(self) -> Optional[float]:
"""
Calculate stake amount for "unlimited" stake amount
:return: None if max number of trades reached
"""
free_open_trades = self.get_free_open_trades()
if not free_open_trades:
return None
available_amount = self.wallets.get_free(self.config['stake_currency'])
return available_amount / free_open_trades
def _check_available_stake_amount(self, stake_amount: Optional[float]) -> Optional[float]:
"""
Check if stake amount can be fulfilled with the available balance
for the stake currency
:return: float: Stake amount
"""
available_amount = self.wallets.get_free(self.config['stake_currency'])
if stake_amount == constants.UNLIMITED_STAKE_AMOUNT:
open_trades = len(Trade.get_open_trades())
if open_trades >= self.config['max_open_trades']:
logger.warning("Can't open a new trade: max number of trades is reached")
return None
return available_amount / (self.config['max_open_trades'] - open_trades)
# Check if stake_amount is fulfilled
if available_amount < stake_amount:
if stake_amount is not None and available_amount < stake_amount:
raise DependencyException(
f"Available balance ({available_amount} {self.config['stake_currency']}) is "
f"lower than stake amount ({stake_amount} {self.config['stake_currency']})"
@@ -299,18 +320,23 @@ class FreqtradeBot:
buycount = 0
# running get_signal on historical data fetched
for _pair in whitelist:
if self.strategy.is_pair_locked(_pair):
logger.info(f"Pair {_pair} is currently locked.")
for pair in whitelist:
if self.strategy.is_pair_locked(pair):
logger.info(f"Pair {pair} is currently locked.")
continue
(buy, sell) = self.strategy.get_signal(
_pair, self.strategy.ticker_interval,
self.dataprovider.ohlcv(_pair, self.strategy.ticker_interval))
pair, self.strategy.ticker_interval,
self.dataprovider.ohlcv(pair, self.strategy.ticker_interval))
if buy and not sell and len(Trade.get_open_trades()) < self.config['max_open_trades']:
stake_amount = self._get_trade_stake_amount(_pair)
if buy and not sell:
if not self.get_free_open_trades():
logger.debug("Can't open a new trade: max number of trades is reached")
continue
stake_amount = self.get_trade_stake_amount(pair)
if not stake_amount:
logger.debug("Stake amount is 0, ignoring possible trade for {pair}.")
continue
logger.info(f"Buy signal found: about create a new trade with stake_amount: "
@@ -320,11 +346,11 @@ class FreqtradeBot:
get('check_depth_of_market', {})
if (bidstrat_check_depth_of_market.get('enabled', False)) and\
(bidstrat_check_depth_of_market.get('bids_to_ask_delta', 0) > 0):
if self._check_depth_of_market_buy(_pair, bidstrat_check_depth_of_market):
buycount += self.execute_buy(_pair, stake_amount)
if self._check_depth_of_market_buy(pair, bidstrat_check_depth_of_market):
buycount += self.execute_buy(pair, stake_amount)
continue
buycount += self.execute_buy(_pair, stake_amount)
buycount += self.execute_buy(pair, stake_amount)
return buycount > 0
@@ -351,7 +377,6 @@ class FreqtradeBot:
:param pair: pair for which we want to create a LIMIT_BUY
:return: None
"""
pair_s = pair.replace('_', '/')
stake_currency = self.config['stake_currency']
fiat_currency = self.config.get('fiat_display_currency', None)
time_in_force = self.strategy.order_time_in_force['buy']
@@ -362,10 +387,10 @@ class FreqtradeBot:
# Calculate amount
buy_limit_requested = self.get_target_bid(pair)
min_stake_amount = self._get_min_pair_stake_amount(pair_s, buy_limit_requested)
min_stake_amount = self._get_min_pair_stake_amount(pair, buy_limit_requested)
if min_stake_amount is not None and min_stake_amount > stake_amount:
logger.warning(
f"Can't open a new trade for {pair_s}: stake amount "
f"Can't open a new trade for {pair}: stake amount "
f"is too small ({stake_amount} < {min_stake_amount})"
)
return False
@@ -388,7 +413,7 @@ class FreqtradeBot:
if float(order['filled']) == 0:
logger.warning('Buy %s order with time in force %s for %s is %s by %s.'
' zero amount is fulfilled.',
order_tif, order_type, pair_s, order_status, self.exchange.name)
order_tif, order_type, pair, order_status, self.exchange.name)
return False
else:
# the order is partially fulfilled
@@ -396,7 +421,7 @@ class FreqtradeBot:
# if the order is fulfilled fully or partially
logger.warning('Buy %s order with time in force %s for %s is %s by %s.'
' %s amount fulfilled out of %s (%s remaining which is canceled).',
order_tif, order_type, pair_s, order_status, self.exchange.name,
order_tif, order_type, pair, order_status, self.exchange.name,
order['filled'], order['amount'], order['remaining']
)
stake_amount = order['cost']
@@ -413,7 +438,7 @@ class FreqtradeBot:
self.rpc.send_msg({
'type': RPCMessageType.BUY_NOTIFICATION,
'exchange': self.exchange.name.capitalize(),
'pair': pair_s,
'pair': pair,
'limit': buy_limit_filled_price,
'order_type': order_type,
'stake_amount': stake_amount,
@@ -892,6 +917,27 @@ class FreqtradeBot:
# TODO: figure out how to handle partially complete sell orders
return False
def _safe_sell_amount(self, pair: str, amount: float) -> float:
"""
Get sellable amount.
Should be trade.amount - but will fall back to the available amount if necessary.
This should cover cases where get_real_amount() was not able to update the amount
for whatever reason.
:param pair: Pair we're trying to sell
:param amount: amount we expect to be available
:return: amount to sell
:raise: DependencyException: if available balance is not within 2% of the available amount.
"""
wallet_amount = self.wallets.get_free(pair.split('/')[0])
logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}")
if wallet_amount > amount:
return amount
elif wallet_amount > amount * 0.98:
logger.info(f"{pair} - Falling back to wallet-amount.")
return wallet_amount
else:
raise DependencyException("Not enough amount to sell.")
def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType) -> None:
"""
Executes a limit sell for the given trade and limit
@@ -922,10 +968,12 @@ class FreqtradeBot:
# Emergencysells (default to market!)
ordertype = self.strategy.order_types.get("emergencysell", "market")
amount = self._safe_sell_amount(trade.pair, trade.amount)
# Execute sell and update trade record
order = self.exchange.sell(pair=str(trade.pair),
ordertype=ordertype,
amount=trade.amount, rate=limit,
amount=amount, rate=limit,
time_in_force=self.strategy.order_time_in_force['sell']
)

View File

@@ -5,7 +5,7 @@ from logging import Formatter
from logging.handlers import RotatingFileHandler, SysLogHandler
from typing import Any, Dict, List
from freqtrade import OperationalException
from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__)

View File

@@ -4,6 +4,7 @@ Main Freqtrade bot script.
Read the documentation to know what cli arguments you need.
"""
from freqtrade.exceptions import FreqtradeException, OperationalException
import sys
# check min. python version
if sys.version_info < (3, 6):
@@ -13,7 +14,6 @@ if sys.version_info < (3, 6):
import logging
from typing import Any, List
from freqtrade import OperationalException
from freqtrade.configuration import Arguments
@@ -50,7 +50,7 @@ def main(sysargv: List[str] = None) -> None:
except KeyboardInterrupt:
logger.info('SIGINT received, aborting ...')
return_code = 0
except OperationalException as e:
except FreqtradeException as e:
logger.error(str(e))
return_code = 2
except Exception:

View File

@@ -1,11 +1,11 @@
import logging
from typing import Any, Dict
from freqtrade import DependencyException, constants, OperationalException
from freqtrade import constants
from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.state import RunMode
from freqtrade.utils import setup_utils_configuration
logger = logging.getLogger(__name__)

View File

@@ -12,12 +12,12 @@ from typing import Any, Dict, List, NamedTuple, Optional
from pandas import DataFrame
from tabulate import tabulate
from freqtrade import OperationalException
from freqtrade.configuration import (TimeRange, remove_credentials,
validate_config_consistency)
from freqtrade.data import history
from freqtrade.data.converter import trim_dataframe
from freqtrade.data.dataprovider import DataProvider
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
from freqtrade.misc import file_dump_json
from freqtrade.persistence import Trade

View File

@@ -12,8 +12,7 @@ from freqtrade import constants
from freqtrade.configuration import (TimeRange, remove_credentials,
validate_config_consistency)
from freqtrade.edge import Edge
from freqtrade.exchange import Exchange
from freqtrade.resolvers import StrategyResolver
from freqtrade.resolvers import StrategyResolver, ExchangeResolver
logger = logging.getLogger(__name__)
@@ -33,7 +32,7 @@ class EdgeCli:
# Reset keys for edge
remove_credentials(self.config)
self.config['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
self.exchange = Exchange(self.config)
self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config)
self.strategy = StrategyResolver.load_strategy(self.config)
validate_config_consistency(self.config)

View File

@@ -22,14 +22,14 @@ from joblib import (Parallel, cpu_count, delayed, dump, load,
wrap_non_picklable_objects)
from pandas import DataFrame
from freqtrade import OperationalException
from freqtrade.data.history import get_timerange
from freqtrade.data.converter import trim_dataframe
from freqtrade.data.history import get_timerange
from freqtrade.exceptions import OperationalException
from freqtrade.misc import plural, round_dict
from freqtrade.optimize.backtesting import Backtesting
# Import IHyperOpt and IHyperOptLoss to allow unpickling classes from these modules
from freqtrade.optimize.hyperopt_interface import IHyperOpt # noqa: F4
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F4
from freqtrade.optimize.hyperopt_interface import IHyperOpt # noqa: F401
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F401
from freqtrade.resolvers.hyperopt_resolver import (HyperOptLossResolver,
HyperOptResolver)

View File

@@ -4,17 +4,15 @@ This module defines the interface to apply for hyperopt
"""
import logging
import math
from abc import ABC
from typing import Dict, Any, Callable, List
from typing import Any, Callable, Dict, List
from skopt.space import Categorical, Dimension, Integer, Real
from freqtrade import OperationalException
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_minutes
from freqtrade.misc import round_dict
logger = logging.getLogger(__name__)

View File

@@ -8,7 +8,7 @@ import logging
from datetime import datetime
from typing import Dict, List
from freqtrade import OperationalException
from freqtrade.exceptions import OperationalException
from freqtrade.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__)

View File

@@ -4,11 +4,12 @@ Static List provider
Provides lists as configured in config.json
"""
from cachetools import TTLCache, cached
import logging
from typing import Dict, List
from freqtrade import OperationalException
from cachetools import TTLCache, cached
from freqtrade.exceptions import OperationalException
from freqtrade.pairlist.IPairList import IPairList
from freqtrade.resolvers import PairListResolver

View File

@@ -16,7 +16,7 @@ from sqlalchemy.orm.scoping import scoped_session
from sqlalchemy.orm.session import sessionmaker
from sqlalchemy.pool import StaticPool
from freqtrade import OperationalException
from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__)

View File

@@ -1,6 +1,6 @@
from typing import Any, Dict
from freqtrade import OperationalException
from freqtrade.exceptions import OperationalException
from freqtrade.state import RunMode
from freqtrade.utils import setup_utils_configuration

View File

@@ -7,8 +7,8 @@ import logging
from pathlib import Path
from typing import Dict
from freqtrade import OperationalException
from freqtrade.constants import DEFAULT_HYPEROPT_LOSS, USERPATH_HYPEROPTS
from freqtrade.exceptions import OperationalException
from freqtrade.optimize.hyperopt_interface import IHyperOpt
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss
from freqtrade.resolvers import IResolver

View File

@@ -9,7 +9,7 @@ import logging
from pathlib import Path
from typing import Any, Dict, Generator, List, Optional, Tuple, Type, Union
from freqtrade import OperationalException
from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__)

View File

@@ -11,9 +11,9 @@ from inspect import getfullargspec
from pathlib import Path
from typing import Dict, Optional
from freqtrade import OperationalException
from freqtrade.constants import (REQUIRED_ORDERTIF, REQUIRED_ORDERTYPES,
USERPATH_STRATEGY)
from freqtrade.exceptions import OperationalException
from freqtrade.resolvers import IResolver
from freqtrade.strategy.interface import IStrategy

View File

@@ -11,7 +11,7 @@ from typing import Any, Dict, List, Optional, Tuple
import arrow
from numpy import NAN, mean
from freqtrade import DependencyException, TemporaryError
from freqtrade.exceptions import DependencyException, TemporaryError
from freqtrade.misc import shorten_date
from freqtrade.persistence import Trade
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
@@ -142,7 +142,7 @@ class RPC:
def _rpc_status_table(self, stake_currency, fiat_display_currency: str) -> Tuple[List, List]:
trades = Trade.get_open_trades()
if not trades:
raise RPCException('no active order')
raise RPCException('no active trade')
else:
trades_list = []
for trade in trades:
@@ -462,7 +462,7 @@ class RPC:
raise RPCException(f'position for {pair} already open - id: {trade.id}')
# gen stake amount
stakeamount = self._freqtrade._get_trade_stake_amount(pair)
stakeamount = self._freqtrade.get_trade_stake_amount(pair)
# execute buy
if self._freqtrade.execute_buy(pair, stakeamount, price):

View File

@@ -47,6 +47,7 @@ class {{ strategy }}(IStrategy):
# Trailing stoploss
trailing_stop = False
# trailing_only_offset_is_reached = False
# trailing_stop_positive = 0.01
# trailing_stop_positive_offset = 0.0 # Disabled / not configured

View File

@@ -48,6 +48,7 @@ class SampleStrategy(IStrategy):
# Trailing stoploss
trailing_stop = False
# trailing_only_offset_is_reached = False
# trailing_stop_positive = 0.01
# trailing_stop_positive_offset = 0.0 # Disabled / not configured

View File

@@ -11,7 +11,6 @@ import rapidjson
from colorama import init as colorama_init
from tabulate import tabulate
from freqtrade import OperationalException
from freqtrade.configuration import (Configuration, TimeRange,
remove_credentials)
from freqtrade.configuration.directory_operations import (copy_sample_files,
@@ -22,6 +21,7 @@ from freqtrade.data.converter import (convert_ohlcv_format,
from freqtrade.data.history import (convert_trades_to_ohlcv,
refresh_backtest_ohlcv_data,
refresh_backtest_trades_data)
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import (available_exchanges, ccxt_exchanges,
market_is_active, symbol_is_pair)
from freqtrade.misc import plural, render_template

View File

@@ -8,9 +8,9 @@ from typing import Any, Callable, Dict, Optional
import sdnotify
from freqtrade import (OperationalException, TemporaryError, __version__,
constants)
from freqtrade import __version__, constants
from freqtrade.configuration import Configuration
from freqtrade.exceptions import OperationalException, TemporaryError
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.rpc import RPCMessageType
from freqtrade.state import State