exchange refactor

This commit is contained in:
hroff-1902 2019-08-22 20:48:29 +03:00
parent 447bcf98e1
commit 845af384de
12 changed files with 83 additions and 48 deletions

View File

@ -6,12 +6,12 @@ Common Interface for bot and strategy to access data.
""" """
import logging import logging
from pathlib import Path from pathlib import Path
from typing import List, Tuple from typing import List, Optional, Tuple
from pandas import DataFrame from pandas import DataFrame
from freqtrade.data.history import load_pair_history from freqtrade.data.history import load_pair_history
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange, get_exchange
from freqtrade.state import RunMode from freqtrade.state import RunMode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -19,9 +19,9 @@ logger = logging.getLogger(__name__)
class DataProvider(): class DataProvider():
def __init__(self, config: dict, exchange: Exchange) -> None: def __init__(self, config: dict, exchange_name: Optional[str] = None) -> None:
self._config = config self._config = config
self._exchange = exchange self._exchange: Exchange = get_exchange(config, exchange_name)
def refresh(self, def refresh(self,
pairlist: List[Tuple[str, str]], pairlist: List[Tuple[str, str]],

View File

@ -1,4 +1,6 @@
from freqtrade.exchange.exchange import Exchange # noqa: F401 from freqtrade.exchange.exchange import Exchange # noqa: F401
from freqtrade.exchange.exchange import (get_exchange, # noqa: F401
delete_exchange)
from freqtrade.exchange.exchange import (get_exchange_bad_reason, # noqa: F401 from freqtrade.exchange.exchange import (get_exchange_bad_reason, # noqa: F401
is_exchange_bad, is_exchange_bad,
is_exchange_available, is_exchange_available,

View File

@ -21,6 +21,7 @@ from freqtrade import (DependencyException, InvalidOrderException,
from freqtrade.data.converter import parse_ticker_dataframe from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.misc import deep_merge_dicts from freqtrade.misc import deep_merge_dicts
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -772,6 +773,39 @@ class Exchange(object):
raise OperationalException(e) from e raise OperationalException(e) from e
# Store Exchange objects created
__exchanges: Dict[str, Exchange] = {}
def get_exchange(config, exchange_name: Optional[str] = None) -> Exchange:
"""
Return Exchange object for exchange with name `exchange_name`.
Creates Exchange if it was not created before.
"""
from freqtrade.resolvers import ExchangeResolver
name = exchange_name or config['exchange']['name']
if name not in __exchanges:
logger.info(f"Creating Exchange object for '{name}'...")
__exchanges[name] = ExchangeResolver(name, config).exchange
logger.info(f"Exchange object for '{name}' created.")
return __exchanges[name]
def delete_exchange(config, exchange_name: Optional[str] = None):
"""
Delete Exchange object for exchange with name `exchange_name`.
"""
name = exchange_name or config['exchange']['name']
logger.info(f"Deleting Exchange object for '{name}'...")
if name in __exchanges:
del __exchanges[name]
logger.info(f"Exchange object for '{name}' deleted.")
else:
logger.warning(f"Exchange object for '{name}' was not found, cannot be deleted.")
def is_exchange_bad(exchange_name: str) -> bool: def is_exchange_bad(exchange_name: str) -> bool:
return exchange_name in BAD_EXCHANGES return exchange_name in BAD_EXCHANGES

View File

@ -17,10 +17,10 @@ from freqtrade.data.converter import order_book_to_dataframe
from freqtrade.data.dataprovider import DataProvider from freqtrade.data.dataprovider import DataProvider
from freqtrade.edge import Edge from freqtrade.edge import Edge
from freqtrade.configuration import validate_config_consistency from freqtrade.configuration import validate_config_consistency
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date from freqtrade.exchange import Exchange, get_exchange, timeframe_to_minutes, timeframe_to_next_date
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.rpc import RPCManager, RPCMessageType
from freqtrade.resolvers import ExchangeResolver, StrategyResolver, PairListResolver from freqtrade.resolvers import StrategyResolver, PairListResolver
from freqtrade.state import State, RunMode from freqtrade.state import State, RunMode
from freqtrade.strategy.interface import SellType, IStrategy from freqtrade.strategy.interface import SellType, IStrategy
from freqtrade.wallets import Wallets from freqtrade.wallets import Wallets
@ -57,10 +57,10 @@ class FreqtradeBot(object):
self.rpc: RPCManager = RPCManager(self) self.rpc: RPCManager = RPCManager(self)
self.exchange = ExchangeResolver(self.config['exchange']['name'], self.config).exchange self.exchange: Exchange = get_exchange(self.config)
self.wallets = Wallets(self.config, self.exchange) self.wallets = Wallets(self.config)
self.dataprovider = DataProvider(self.config, self.exchange) self.dataprovider = DataProvider(self.config)
# Attach Dataprovider to Strategy baseclass # Attach Dataprovider to Strategy baseclass
IStrategy.dp = self.dataprovider IStrategy.dp = self.dataprovider

View File

@ -15,10 +15,10 @@ from freqtrade import OperationalException
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.data import history from freqtrade.data import history
from freqtrade.data.dataprovider import DataProvider from freqtrade.data.dataprovider import DataProvider
from freqtrade.exchange import timeframe_to_minutes from freqtrade.exchange import Exchange, get_exchange, timeframe_to_minutes
from freqtrade.misc import file_dump_json from freqtrade.misc import file_dump_json
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.resolvers import StrategyResolver
from freqtrade.state import RunMode from freqtrade.state import RunMode
from freqtrade.strategy.interface import IStrategy, SellType from freqtrade.strategy.interface import IStrategy, SellType
from tabulate import tabulate from tabulate import tabulate
@ -64,11 +64,11 @@ class Backtesting(object):
self.config['dry_run'] = True self.config['dry_run'] = True
self.strategylist: List[IStrategy] = [] self.strategylist: List[IStrategy] = []
self.exchange = ExchangeResolver(self.config['exchange']['name'], self.config).exchange exchange: Exchange = get_exchange(self.config)
self.fee = self.exchange.get_fee() self.fee = exchange.get_fee()
if self.config.get('runmode') != RunMode.HYPEROPT: if self.config.get('runmode') != RunMode.HYPEROPT:
self.dataprovider = DataProvider(self.config, self.exchange) self.dataprovider = DataProvider(self.config)
IStrategy.dp = self.dataprovider IStrategy.dp = self.dataprovider
if self.config.get('strategy_list', None): if self.config.get('strategy_list', None):
@ -406,12 +406,13 @@ class Backtesting(object):
timerange = TimeRange.parse_timerange(None if self.config.get( timerange = TimeRange.parse_timerange(None if self.config.get(
'timerange') is None else str(self.config.get('timerange'))) 'timerange') is None else str(self.config.get('timerange')))
exchange: Exchange = get_exchange(self.config)
data = history.load_data( data = history.load_data(
datadir=Path(self.config['datadir']) if self.config.get('datadir') else None, datadir=Path(self.config['datadir']) if self.config.get('datadir') else None,
pairs=pairs, pairs=pairs,
ticker_interval=self.ticker_interval, ticker_interval=self.ticker_interval,
refresh_pairs=self.config.get('refresh_pairs', False), refresh_pairs=self.config.get('refresh_pairs', False),
exchange=self.exchange, exchange=exchange,
timerange=timerange, timerange=timerange,
) )

View File

@ -10,7 +10,7 @@ from freqtrade import constants
from freqtrade.edge import Edge from freqtrade.edge import Edge
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange, get_exchange
from freqtrade.resolvers import StrategyResolver from freqtrade.resolvers import StrategyResolver
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -35,7 +35,7 @@ class EdgeCli(object):
self.config['exchange']['uid'] = '' self.config['exchange']['uid'] = ''
self.config['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT self.config['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
self.config['dry_run'] = True self.config['dry_run'] = True
self.exchange = Exchange(self.config) self.exchange: Exchange = get_exchange(self.config)
self.strategy = StrategyResolver(self.config).strategy self.strategy = StrategyResolver(self.config).strategy
self.edge = Edge(config, self.exchange, self.strategy) self.edge = Edge(config, self.exchange, self.strategy)

View File

@ -24,6 +24,7 @@ from skopt.space import Dimension
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.data.history import load_data, get_timeframe from freqtrade.data.history import load_data, get_timeframe
from freqtrade.exchange import Exchange, get_exchange
from freqtrade.optimize.backtesting import Backtesting from freqtrade.optimize.backtesting import Backtesting
# Import IHyperOptLoss to allow users import from this file # Import IHyperOptLoss to allow users import from this file
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F4 from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F4
@ -342,12 +343,13 @@ class Hyperopt(Backtesting):
def start(self) -> None: def start(self) -> None:
timerange = TimeRange.parse_timerange(None if self.config.get( timerange = TimeRange.parse_timerange(None if self.config.get(
'timerange') is None else str(self.config.get('timerange'))) 'timerange') is None else str(self.config.get('timerange')))
exchange: Exchange = get_exchange(self.config)
data = load_data( data = load_data(
datadir=Path(self.config['datadir']) if self.config.get('datadir') else None, datadir=Path(self.config['datadir']) if self.config.get('datadir') else None,
pairs=self.config['exchange']['pair_whitelist'], pairs=self.config['exchange']['pair_whitelist'],
ticker_interval=self.ticker_interval, ticker_interval=self.ticker_interval,
refresh_pairs=self.config.get('refresh_pairs', False), refresh_pairs=self.config.get('refresh_pairs', False),
exchange=self.exchange, exchange=exchange,
timerange=timerange timerange=timerange
) )
@ -371,9 +373,6 @@ class Hyperopt(Backtesting):
dump(preprocessed, self.tickerdata_pickle) dump(preprocessed, self.tickerdata_pickle)
# We don't need exchange instance anymore while running hyperopt
self.exchange = None # type: ignore
self.load_previous_results() self.load_previous_results()
cpus = cpu_count() cpus = cpu_count()

View File

@ -1,6 +1,6 @@
import logging import logging
from pathlib import Path from pathlib import Path
from typing import Dict, List, Optional from typing import Dict, List
import pandas as pd import pandas as pd
@ -8,8 +8,9 @@ from freqtrade.configuration import TimeRange
from freqtrade.data import history from freqtrade.data import history
from freqtrade.data.btanalysis import (combine_tickers_with_mean, from freqtrade.data.btanalysis import (combine_tickers_with_mean,
create_cum_profit, load_trades) create_cum_profit, load_trades)
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange, get_exchange
from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.resolvers import StrategyResolver
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -19,7 +20,7 @@ try:
from plotly.offline import plot from plotly.offline import plot
import plotly.graph_objects as go import plotly.graph_objects as go
except ImportError: except ImportError:
logger.exception("Module plotly not found \n Please install using `pip install plotly`") logger.exception("Module plotly not found.\nPlease install using `pip install plotly`.")
exit(1) exit(1)
@ -28,12 +29,11 @@ def init_plotscript(config):
Initialize objects needed for plotting Initialize objects needed for plotting
:return: Dict with tickers, trades, pairs and strategy :return: Dict with tickers, trades, pairs and strategy
""" """
exchange: Optional[Exchange] = None exchange: Exchange = None
# Exchange is only needed when downloading data! # Exchange is only needed when downloading data!
if config.get("refresh_pairs", False): if config.get("refresh_pairs", False):
exchange = ExchangeResolver(config.get('exchange', {}).get('name'), exchange = get_exchange(config)
config).exchange
strategy = StrategyResolver(config).strategy strategy = StrategyResolver(config).strategy
if "pairs" in config: if "pairs" in config:

View File

@ -3,7 +3,6 @@ This module loads custom exchanges
""" """
import logging import logging
from freqtrade.exchange import Exchange
import freqtrade.exchange as exchanges import freqtrade.exchange as exchanges
from freqtrade.resolvers import IResolver from freqtrade.resolvers import IResolver
@ -29,10 +28,10 @@ class ExchangeResolver(IResolver):
logger.info( logger.info(
f"No {exchange_name} specific subclass found. Using the generic class instead.") f"No {exchange_name} specific subclass found. Using the generic class instead.")
if not hasattr(self, "exchange"): if not hasattr(self, "exchange"):
self.exchange = Exchange(config) self.exchange = exchanges.Exchange(config)
def _load_exchange( def _load_exchange(
self, exchange_name: str, kwargs: dict) -> Exchange: self, exchange_name: str, kwargs: dict) -> exchanges.Exchange:
""" """
Loads the specified exchange. Loads the specified exchange.
Only checks for exchanges exported in freqtrade.exchanges Only checks for exchanges exported in freqtrade.exchanges

View File

@ -14,7 +14,7 @@ def test_ohlcv(mocker, default_conf, ticker_history):
exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history
exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history
dp = DataProvider(default_conf, exchange) dp = DataProvider(default_conf)
assert dp.runmode == RunMode.DRY_RUN assert dp.runmode == RunMode.DRY_RUN
assert ticker_history.equals(dp.ohlcv("UNITTEST/BTC", ticker_interval)) assert ticker_history.equals(dp.ohlcv("UNITTEST/BTC", ticker_interval))
assert isinstance(dp.ohlcv("UNITTEST/BTC", ticker_interval), DataFrame) assert isinstance(dp.ohlcv("UNITTEST/BTC", ticker_interval), DataFrame)
@ -27,12 +27,12 @@ def test_ohlcv(mocker, default_conf, ticker_history):
assert dp.ohlcv("UNITTEST/BTC", ticker_interval).equals(dp.ohlcv("UNITTEST/BTC")) assert dp.ohlcv("UNITTEST/BTC", ticker_interval).equals(dp.ohlcv("UNITTEST/BTC"))
default_conf["runmode"] = RunMode.LIVE default_conf["runmode"] = RunMode.LIVE
dp = DataProvider(default_conf, exchange) dp = DataProvider(default_conf)
assert dp.runmode == RunMode.LIVE assert dp.runmode == RunMode.LIVE
assert isinstance(dp.ohlcv("UNITTEST/BTC", ticker_interval), DataFrame) assert isinstance(dp.ohlcv("UNITTEST/BTC", ticker_interval), DataFrame)
default_conf["runmode"] = RunMode.BACKTEST default_conf["runmode"] = RunMode.BACKTEST
dp = DataProvider(default_conf, exchange) dp = DataProvider(default_conf)
assert dp.runmode == RunMode.BACKTEST assert dp.runmode == RunMode.BACKTEST
assert dp.ohlcv("UNITTEST/BTC", ticker_interval).empty assert dp.ohlcv("UNITTEST/BTC", ticker_interval).empty
@ -41,7 +41,7 @@ def test_historic_ohlcv(mocker, default_conf, ticker_history):
historymock = MagicMock(return_value=ticker_history) historymock = MagicMock(return_value=ticker_history)
mocker.patch("freqtrade.data.dataprovider.load_pair_history", historymock) mocker.patch("freqtrade.data.dataprovider.load_pair_history", historymock)
dp = DataProvider(default_conf, None) dp = DataProvider(default_conf)
data = dp.historic_ohlcv("UNITTEST/BTC", "5m") data = dp.historic_ohlcv("UNITTEST/BTC", "5m")
assert isinstance(data, DataFrame) assert isinstance(data, DataFrame)
assert historymock.call_count == 1 assert historymock.call_count == 1
@ -57,7 +57,7 @@ def test_get_pair_dataframe(mocker, default_conf, ticker_history):
exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history
exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history
dp = DataProvider(default_conf, exchange) dp = DataProvider(default_conf)
assert dp.runmode == RunMode.DRY_RUN assert dp.runmode == RunMode.DRY_RUN
assert ticker_history.equals(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval)) assert ticker_history.equals(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval))
assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame) assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame)
@ -70,7 +70,7 @@ def test_get_pair_dataframe(mocker, default_conf, ticker_history):
ticker_interval).equals(dp.get_pair_dataframe("UNITTEST/BTC")) ticker_interval).equals(dp.get_pair_dataframe("UNITTEST/BTC"))
default_conf["runmode"] = RunMode.LIVE default_conf["runmode"] = RunMode.LIVE
dp = DataProvider(default_conf, exchange) dp = DataProvider(default_conf)
assert dp.runmode == RunMode.LIVE assert dp.runmode == RunMode.LIVE
assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame) assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame)
assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty
@ -78,7 +78,7 @@ def test_get_pair_dataframe(mocker, default_conf, ticker_history):
historymock = MagicMock(return_value=ticker_history) historymock = MagicMock(return_value=ticker_history)
mocker.patch("freqtrade.data.dataprovider.load_pair_history", historymock) mocker.patch("freqtrade.data.dataprovider.load_pair_history", historymock)
default_conf["runmode"] = RunMode.BACKTEST default_conf["runmode"] = RunMode.BACKTEST
dp = DataProvider(default_conf, exchange) dp = DataProvider(default_conf)
assert dp.runmode == RunMode.BACKTEST assert dp.runmode == RunMode.BACKTEST
assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame) assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame)
# assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty # assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty
@ -90,7 +90,7 @@ def test_available_pairs(mocker, default_conf, ticker_history):
exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history
exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history
dp = DataProvider(default_conf, exchange) dp = DataProvider(default_conf)
assert len(dp.available_pairs) == 2 assert len(dp.available_pairs) == 2
assert dp.available_pairs == [ assert dp.available_pairs == [
("XRP/BTC", ticker_interval), ("XRP/BTC", ticker_interval),
@ -108,7 +108,7 @@ def test_refresh(mocker, default_conf, ticker_history):
pairs_non_trad = [("ETH/USDT", ticker_interval), ("BTC/TUSD", "1h")] pairs_non_trad = [("ETH/USDT", ticker_interval), ("BTC/TUSD", "1h")]
dp = DataProvider(default_conf, exchange) dp = DataProvider(default_conf)
dp.refresh(pairs) dp.refresh(pairs)
assert refresh_mock.call_count == 1 assert refresh_mock.call_count == 1

View File

@ -9,8 +9,7 @@ import arrow
from freqtrade.configuration import Configuration, TimeRange from freqtrade.configuration import Configuration, TimeRange
from freqtrade.configuration.directory_operations import create_userdata_dir from freqtrade.configuration.directory_operations import create_userdata_dir
from freqtrade.data.history import download_pair_history from freqtrade.data.history import download_pair_history
from freqtrade.exchange import available_exchanges from freqtrade.exchange import Exchange, get_exchange, available_exchanges
from freqtrade.resolvers import ExchangeResolver
from freqtrade.state import RunMode from freqtrade.state import RunMode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -79,7 +78,7 @@ def start_download_data(args: Namespace) -> None:
try: try:
# Init exchange # Init exchange
exchange = ExchangeResolver(config['exchange']['name'], config).exchange exchange: Exchange = get_exchange(config)
for pair in config["pairs"]: for pair in config["pairs"]:
if pair not in exchange.markets: if pair not in exchange.markets:

View File

@ -2,8 +2,8 @@
""" Wallet """ """ Wallet """
import logging import logging
from typing import Dict, NamedTuple from typing import Dict, NamedTuple, Optional
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange, get_exchange
from freqtrade import constants from freqtrade import constants
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -19,9 +19,9 @@ class Wallet(NamedTuple):
class Wallets(object): class Wallets(object):
def __init__(self, config: dict, exchange: Exchange) -> None: def __init__(self, config: dict, exchange_name: Optional[str] = None) -> None:
self._config = config self._config = config
self._exchange = exchange self._exchange_name = exchange_name
self._wallets: Dict[str, Wallet] = {} self._wallets: Dict[str, Wallet] = {}
self.update() self.update()
@ -61,7 +61,8 @@ class Wallets(object):
def update(self) -> None: def update(self) -> None:
balances = self._exchange.get_balances() exchange: Exchange = get_exchange(self._config, self._exchange_name)
balances = exchange.get_balances()
for currency in balances: for currency in balances:
self._wallets[currency] = Wallet( self._wallets[currency] = Wallet(