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
from pathlib import Path
from typing import List, Tuple
from typing import List, Optional, Tuple
from pandas import DataFrame
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
logger = logging.getLogger(__name__)
@ -19,9 +19,9 @@ logger = logging.getLogger(__name__)
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._exchange = exchange
self._exchange: Exchange = get_exchange(config, exchange_name)
def refresh(self,
pairlist: List[Tuple[str, str]],

View File

@ -1,4 +1,6 @@
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
is_exchange_bad,
is_exchange_available,

View File

@ -21,6 +21,7 @@ from freqtrade import (DependencyException, InvalidOrderException,
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.misc import deep_merge_dicts
logger = logging.getLogger(__name__)
@ -772,6 +773,39 @@ class Exchange(object):
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:
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.edge import Edge
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.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.strategy.interface import SellType, IStrategy
from freqtrade.wallets import Wallets
@ -57,10 +57,10 @@ class FreqtradeBot(object):
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.dataprovider = DataProvider(self.config, self.exchange)
self.wallets = Wallets(self.config)
self.dataprovider = DataProvider(self.config)
# Attach Dataprovider to Strategy baseclass
IStrategy.dp = self.dataprovider

View File

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

View File

@ -10,7 +10,7 @@ from freqtrade import constants
from freqtrade.edge import Edge
from freqtrade.configuration import TimeRange
from freqtrade.exchange import Exchange
from freqtrade.exchange import Exchange, get_exchange
from freqtrade.resolvers import StrategyResolver
logger = logging.getLogger(__name__)
@ -35,7 +35,7 @@ class EdgeCli(object):
self.config['exchange']['uid'] = ''
self.config['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
self.config['dry_run'] = True
self.exchange = Exchange(self.config)
self.exchange: Exchange = get_exchange(self.config)
self.strategy = StrategyResolver(self.config).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.data.history import load_data, get_timeframe
from freqtrade.exchange import Exchange, get_exchange
from freqtrade.optimize.backtesting import Backtesting
# Import IHyperOptLoss to allow users import from this file
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F4
@ -342,12 +343,13 @@ class Hyperopt(Backtesting):
def start(self) -> None:
timerange = TimeRange.parse_timerange(None if self.config.get(
'timerange') is None else str(self.config.get('timerange')))
exchange: Exchange = get_exchange(self.config)
data = load_data(
datadir=Path(self.config['datadir']) if self.config.get('datadir') else None,
pairs=self.config['exchange']['pair_whitelist'],
ticker_interval=self.ticker_interval,
refresh_pairs=self.config.get('refresh_pairs', False),
exchange=self.exchange,
exchange=exchange,
timerange=timerange
)
@ -371,9 +373,6 @@ class Hyperopt(Backtesting):
dump(preprocessed, self.tickerdata_pickle)
# We don't need exchange instance anymore while running hyperopt
self.exchange = None # type: ignore
self.load_previous_results()
cpus = cpu_count()

View File

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

View File

@ -3,7 +3,6 @@ This module loads custom exchanges
"""
import logging
from freqtrade.exchange import Exchange
import freqtrade.exchange as exchanges
from freqtrade.resolvers import IResolver
@ -29,10 +28,10 @@ class ExchangeResolver(IResolver):
logger.info(
f"No {exchange_name} specific subclass found. Using the generic class instead.")
if not hasattr(self, "exchange"):
self.exchange = Exchange(config)
self.exchange = exchanges.Exchange(config)
def _load_exchange(
self, exchange_name: str, kwargs: dict) -> Exchange:
self, exchange_name: str, kwargs: dict) -> exchanges.Exchange:
"""
Loads the specified exchange.
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[("UNITTEST/BTC", ticker_interval)] = ticker_history
dp = DataProvider(default_conf, exchange)
dp = DataProvider(default_conf)
assert dp.runmode == RunMode.DRY_RUN
assert ticker_history.equals(dp.ohlcv("UNITTEST/BTC", ticker_interval))
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"))
default_conf["runmode"] = RunMode.LIVE
dp = DataProvider(default_conf, exchange)
dp = DataProvider(default_conf)
assert dp.runmode == RunMode.LIVE
assert isinstance(dp.ohlcv("UNITTEST/BTC", ticker_interval), DataFrame)
default_conf["runmode"] = RunMode.BACKTEST
dp = DataProvider(default_conf, exchange)
dp = DataProvider(default_conf)
assert dp.runmode == RunMode.BACKTEST
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)
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")
assert isinstance(data, DataFrame)
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[("UNITTEST/BTC", ticker_interval)] = ticker_history
dp = DataProvider(default_conf, exchange)
dp = DataProvider(default_conf)
assert dp.runmode == RunMode.DRY_RUN
assert ticker_history.equals(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval))
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"))
default_conf["runmode"] = RunMode.LIVE
dp = DataProvider(default_conf, exchange)
dp = DataProvider(default_conf)
assert dp.runmode == RunMode.LIVE
assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame)
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)
mocker.patch("freqtrade.data.dataprovider.load_pair_history", historymock)
default_conf["runmode"] = RunMode.BACKTEST
dp = DataProvider(default_conf, exchange)
dp = DataProvider(default_conf)
assert dp.runmode == RunMode.BACKTEST
assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame)
# 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[("UNITTEST/BTC", ticker_interval)] = ticker_history
dp = DataProvider(default_conf, exchange)
dp = DataProvider(default_conf)
assert len(dp.available_pairs) == 2
assert dp.available_pairs == [
("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")]
dp = DataProvider(default_conf, exchange)
dp = DataProvider(default_conf)
dp.refresh(pairs)
assert refresh_mock.call_count == 1

View File

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

View File

@ -2,8 +2,8 @@
""" Wallet """
import logging
from typing import Dict, NamedTuple
from freqtrade.exchange import Exchange
from typing import Dict, NamedTuple, Optional
from freqtrade.exchange import Exchange, get_exchange
from freqtrade import constants
logger = logging.getLogger(__name__)
@ -19,9 +19,9 @@ class Wallet(NamedTuple):
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._exchange = exchange
self._exchange_name = exchange_name
self._wallets: Dict[str, Wallet] = {}
self.update()
@ -61,7 +61,8 @@ class Wallets(object):
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:
self._wallets[currency] = Wallet(