diff --git a/.travis.yml b/.travis.yml index 88121945f..981eedcf8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,12 +13,12 @@ addons: install: - ./install_ta-lib.sh - export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH -- pip install --upgrade flake8 coveralls pytest-random-order mypy +- pip install --upgrade flake8 coveralls pytest-random-order pytest-asyncio mypy - pip install -r requirements.txt - pip install -e . jobs: include: - - script: + - script: - pytest --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/ - coveralls - script: diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 8e88a7f5f..522636d22 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -334,7 +334,7 @@ class Exchange(object): logger.info("returning cached ticker-data for %s", pair) return self._cached_ticker[pair] - async def async_get_tickers_history(self, pairs, tick_interval): + async def async_get_tickers_history(self, pairs, tick_interval) -> List[Tuple[str, List]]: # COMMENTED CODE IS FOR DISCUSSION: where should we close the loop on async ? # loop = asyncio.new_event_loop() # asyncio.set_event_loop(loop) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 365d145ca..f53bc2836 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -55,7 +55,7 @@ class FreqtradeBot(object): self.persistence = None self.exchange = Exchange(self.config) self._init_modules() - self._klines = {} + self._klines: Dict[str, List[Dict]] = {} def _init_modules(self) -> None: """ diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index dfd624393..488281eee 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -6,7 +6,7 @@ import logging from abc import ABC, abstractmethod from datetime import datetime from enum import Enum -from typing import Dict, List, NamedTuple, Tuple +from typing import Dict, List, NamedTuple, Optional, Tuple import warnings import arrow @@ -118,7 +118,7 @@ class IStrategy(ABC): dataframe = self.advise_sell(dataframe, metadata) return dataframe - def get_signal(self, pair: str, interval: str, ticker_hist: List[Dict]) -> Tuple[bool, bool]: + def get_signal(self, pair: str, interval: str, ticker_hist: Optional[List[Dict]]) -> Tuple[bool, bool]: """ Calculates current signal based several technical analysis indicators :param pair: pair in format ANT/BTC diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index d327b97c7..cf62a48c8 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -27,6 +27,20 @@ def ccxt_exceptionhandlers(mocker, default_conf, api_mock, fun, mock_ccxt_fun, * assert api_mock.__dict__[mock_ccxt_fun].call_count == 1 +async def async_ccxt_exception(mocker, default_conf, api_mock, fun, mock_ccxt_fun, **kwargs): + with pytest.raises(TemporaryError): + api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + await getattr(exchange, fun)(**kwargs) + assert api_mock.__dict__[mock_ccxt_fun].call_count == 1 + + with pytest.raises(OperationalException): + api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + await getattr(exchange, fun)(**kwargs) + assert api_mock.__dict__[mock_ccxt_fun].call_count == 1 + + def test_init(default_conf, mocker, caplog): caplog.set_level(logging.INFO) get_patched_exchange(mocker, default_conf) @@ -515,6 +529,92 @@ def test_get_ticker(default_conf, mocker): exchange.get_ticker(pair='ETH/BTC', refresh=True) +@pytest.mark.asyncio +async def test_async_get_ticker_history(default_conf, mocker): + tick = [ + [ + 1511686200000, # unix timestamp ms + 1, # open + 2, # high + 3, # low + 4, # close + 5, # volume (in quote currency) + ] + ] + + async def async_fetch_ohlcv(pair, timeframe, since): + return tick + + exchange = get_patched_exchange(mocker, default_conf) + # Monkey-patch async function + exchange._api_async.fetch_ohlcv = async_fetch_ohlcv + + exchange = Exchange(default_conf) + pair = 'ETH/BTC' + res = await exchange.async_get_ticker_history(pair, "5m") + assert type(res) is tuple + assert len(res) == 2 + assert res[0] == pair + assert res[1] == tick + + await async_ccxt_exception(mocker, default_conf, MagicMock(), + "async_get_ticker_history", "fetch_ohlcv", + pair='ABCD/BTC', tick_interval=default_conf['ticker_interval']) + + api_mock = MagicMock() + with pytest.raises(OperationalException, match=r'Could not fetch ticker data*'): + api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError) + exchange = get_patched_exchange(mocker, default_conf, api_mock) + await exchange.async_get_ticker_history(pair, "5m") + + +@pytest.mark.asyncio +async def test_async_get_tickers_history(default_conf, mocker): + tick = [ + [ + 1511686200000, # unix timestamp ms + 1, # open + 2, # high + 3, # low + 4, # close + 5, # volume (in quote currency) + ] + ] + + async def async_fetch_ohlcv(pair, timeframe, since): + return tick + + exchange = get_patched_exchange(mocker, default_conf) + # Monkey-patch async function + exchange._api_async.fetch_ohlcv = async_fetch_ohlcv + + exchange = Exchange(default_conf) + pairs = ['ETH/BTC', 'XRP/BTC'] + res = await exchange.async_get_tickers_history(pairs, "5m") + assert type(res) is list + assert len(res) == 2 + assert type(res[0]) is tuple + assert res[0][0] == pairs[0] + assert res[0][1] == tick + assert res[1][0] == pairs[1] + assert res[1][1] == tick + + # await async_ccxt_exception(mocker, default_conf, MagicMock(), + # "async_get_tickers_history", "fetch_ohlcv", + # pairs=pairs, tick_interval=default_conf['ticker_interval']) + + # api_mock = MagicMock() + # with pytest.raises(OperationalException, match=r'Could not fetch ticker data*'): + # api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError) + # exchange = get_patched_exchange(mocker, default_conf, api_mock) + # await exchange.async_get_tickers_history('ETH/BTC', "5m") + + +def test_refresh_tickers(): + # TODO: Implement test for this + pass + + def make_fetch_ohlcv_mock(data): def fetch_ohlcv_mock(pair, timeframe, since): if since: diff --git a/requirements.txt b/requirements.txt index 964da51e3..57dcf78e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,6 +15,7 @@ TA-Lib==0.4.17 pytest==3.6.4 pytest-mock==1.10.0 pytest-cov==2.5.1 +pytest-asyncio==0.9.0 tabulate==0.8.2 coinmarketcap==5.0.3