diff --git a/config.json.example b/config.json.example index 2c464a925..6a4f20cd6 100644 --- a/config.json.example +++ b/config.json.example @@ -13,19 +13,19 @@ "key": "your_exchange_key", "secret": "your_exchange_secret", "pair_whitelist": [ - "BTC_ETH", - "BTC_LTC", - "BTC_ETC", - "BTC_DASH", - "BTC_ZEC", - "BTC_XLM", - "BTC_NXT", - "BTC_POWR", - "BTC_ADA", - "BTC_XMR" + "ETH/BTC", + "LTC/BTC", + "ETC/BTC", + "DASH/BTC", + "ZEC/BTC", + "XLM/BTC", + "NXT/BTC", + "POWR/BTC", + "ADA/BTC", + "XMR/BTC" ], "pair_blacklist": [ - "BTC_DOGE" + "DOGE/BTC" ] }, "experimental": { diff --git a/config_full.json.example b/config_full.json.example index c74b59660..d0f9d1260 100644 --- a/config_full.json.example +++ b/config_full.json.example @@ -21,19 +21,19 @@ "key": "your_exchange_key", "secret": "your_exchange_secret", "pair_whitelist": [ - "BTC_ETH", - "BTC_LTC", - "BTC_ETC", - "BTC_DASH", - "BTC_ZEC", - "BTC_XLM", - "BTC_NXT", - "BTC_POWR", - "BTC_ADA", - "BTC_XMR" + "ETH/BTC", + "LTC/BTC", + "ETC/BTC", + "DASH/BTC", + "ZEC/BTC", + "XLM/BTC", + "NXT/BTC", + "POWR/BTC", + "ADA/BTC", + "XMR/BTC" ], "pair_blacklist": [ - "BTC_DOGE" + "DOGE/BTC" ] }, "experimental": { diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index cd4515a3b..16f8b3b4a 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -14,3 +14,11 @@ class OperationalException(BaseException): Requires manual intervention. This happens when an exchange returns an unexpected error during runtime. """ + + +class NetworkException(BaseException): + """ + Network related error. + This could happen when an exchange is congested, unavailable, or the user + has networking problems. Usually resolves itself after a time. + """ diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index decb73628..e6e8023f0 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -51,6 +51,7 @@ class Analyze(object): unit='ms', utc=True, infer_datetime_format=True) + frame.sort_values('date', inplace=True) return frame diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 1b1d2f7aa..e2e22a358 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -6,11 +6,12 @@ import ccxt from random import randint from typing import List, Dict, Any, Optional from cachetools import cached, TTLCache +from datetime import datetime import arrow import requests -from freqtrade import OperationalException +from freqtrade import OperationalException, NetworkException logger = logging.getLogger(__name__) @@ -180,14 +181,25 @@ def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict: # @cached(TTLCache(maxsize=100, ttl=30)) @retrier -def get_ticker_history(pair: str, tick_interval) -> List[Dict]: +def get_ticker_history(pair: str, tick_interval) -> List[List]: # TODO: tickers need to be in format 1m,5m # fetch_ohlcv returns an [[datetime,o,h,l,c,v]] - if not _API.markets: - _API.load_markets() - ohlcv = _API.fetch_ohlcv(pair, str(tick_interval)+'m') + if 'fetchOHLCV' not in _API.has or not _API.has['fetchOHLCV']: + logger.warning('Exhange %s does not support fetching historical candlestick data.', + _API.name) + return [] - return ohlcv + try: + ohlcv = _API.fetch_ohlcv(pair, timeframe=str(tick_interval)+"m") + + return ohlcv + except IndexError as e: + logger.warning('Empty ticker history. Msg %s', str(e)) + except ccxt.NetworkError as e: + logger.warning('Could not load ticker history due to networking error. Message: %s', str(e)) + except ccxt.BaseError as e: + logger.warning('Could not fetch ticker data. Msg: %s', str(e)) + return [] def cancel_order(order_id: str) -> None: @@ -235,7 +247,7 @@ def get_fee_taker() -> float: def get_fee() -> float: - return _API.fees['trading'] + return get_fee_taker() def get_wallet_health() -> List[Dict]: diff --git a/freqtrade/misc.py b/freqtrade/misc.py index f5d045c44..bc04d6b88 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -72,3 +72,11 @@ def file_dump_json(filename, data) -> None: """ with open(filename, 'w') as fp: json.dump(data, fp, default=str) + + +def format_ms_time(date: str) -> str: + """ + convert MS date to readable format. + : epoch-string in ms + """ + return datetime.fromtimestamp(date/1000.0).strftime('%Y-%m-%dT%H:%M:%S') diff --git a/freqtrade/optimize/__init__.py b/freqtrade/optimize/__init__.py index a26744691..30be5dc33 100644 --- a/freqtrade/optimize/__init__.py +++ b/freqtrade/optimize/__init__.py @@ -35,7 +35,7 @@ def load_tickerdata_file( """ path = make_testdata_path(datadir) file = os.path.join(path, '{pair}-{ticker_interval}.json'.format( - pair=pair, + pair=pair.replace('/', '_'), ticker_interval=ticker_interval, )) gzipfile = file + '.gz' @@ -126,7 +126,7 @@ def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) -> interval ) - filepair = pair.replace("-", "_") + filepair = pair.replace("/", "_") filename = os.path.join(path, '{pair}-{interval}.json'.format( pair=filepair, interval=interval, @@ -135,8 +135,8 @@ def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) -> if os.path.isfile(filename): with open(filename, "rt") as file: data = json.load(file) - logger.debug("Current Start: %s", data[1]['T']) - logger.debug("Current End: %s", data[-1:][0]['T']) + logger.debug("Current Start: %s", misc.format_ms_time(data[1][0])) + logger.debug("Current End: %s", misc.format_ms_time(data[-1:][0][0])) else: data = [] logger.debug("Current Start: None") @@ -146,9 +146,9 @@ def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) -> for row in new_data: if row not in data: data.append(row) - logger.debug("New Start: %s", data[1]['T']) - logger.debug("New End: %s", data[-1:][0]['T']) - data = sorted(data, key=lambda data: data['T']) + logger.debug("New Start: %s", misc.format_ms_time(data[0][0])) + logger.debug("New End: %s", misc.format_ms_time(data[-1:][0][0])) + data = sorted(data, key=lambda data: data[0]) misc.file_dump_json(filename, data) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 6202edead..2b583da3c 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -53,7 +53,10 @@ class Backtesting(object): self.tickerdata_to_dataframe = self.analyze.tickerdata_to_dataframe self.populate_buy_trend = self.analyze.populate_buy_trend self.populate_sell_trend = self.analyze.populate_sell_trend - exchange.init({'key': '', 'secret': ''}) + # Reest keys for backtesting + self.config['exchange']['key'] = '' + self.config['exchange']['secret'] = '' + exchange.init(self.config) @staticmethod def get_timeframe(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]: diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 07dc45a3e..d3d2b4d9a 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -73,11 +73,11 @@ def default_conf(): "key": "key", "secret": "secret", "pair_whitelist": [ - "BTC_ETH", - "BTC_TKN", - "BTC_TRST", - "BTC_SWT", - "BTC_BCC" + "ETH/BTC", + "TKN/BTC", + "TRST/BTC", + "SWT/BTC", + "BCC/BTC" ] }, "telegram": { @@ -128,32 +128,31 @@ def ticker_sell_down(): @pytest.fixture def health(): - return MagicMock(return_value=[{ - 'Currency': 'BTC', - 'IsActive': True, - 'LastChecked': '2017-11-13T20:15:00.00', - 'Notice': None - }, { - 'Currency': 'ETH', - 'IsActive': True, - 'LastChecked': '2017-11-13T20:15:00.00', - 'Notice': None - }, { - 'Currency': 'TRST', - 'IsActive': True, - 'LastChecked': '2017-11-13T20:15:00.00', - 'Notice': None - }, { - 'Currency': 'SWT', - 'IsActive': True, - 'LastChecked': '2017-11-13T20:15:00.00', - 'Notice': None - }, { - 'Currency': 'BCC', - 'IsActive': False, - 'LastChecked': '2017-11-13T20:15:00.00', - 'Notice': None - }]) + return MagicMock(return_value={ + "ETH/BTC": { + 'base': 'ETH', + 'active': True, + 'LastChecked': '2017-11-13T20:15:00.00', + 'Notice': None + }, + "TRST/BTC": { + 'base': 'TRST', + 'active': True, + 'LastChecked': '2017-11-13T20:15:00.00', + 'Notice': None + }, + "SWT/BTC": { + 'base': 'SWT', + 'active': True, + 'LastChecked': '2017-11-13T20:15:00.00', + 'Notice': None + }, + "BCC/BTC": { + 'base': 'BCC', + 'active': False, + 'LastChecked': '2017-11-13T20:15:00.00', + 'Notice': None + }}) @pytest.fixture @@ -175,7 +174,7 @@ def limit_buy_order_old(): return { 'id': 'mocked_limit_buy_old', 'type': 'LIMIT_BUY', - 'pair': 'BTC_ETH', + 'pair': 'ETH/BTC', 'opened': str(arrow.utcnow().shift(minutes=-601).datetime), 'rate': 0.00001099, 'amount': 90.99181073, @@ -188,7 +187,7 @@ def limit_sell_order_old(): return { 'id': 'mocked_limit_sell_old', 'type': 'LIMIT_SELL', - 'pair': 'BTC_ETH', + 'pair': 'ETH/BTC', 'opened': str(arrow.utcnow().shift(minutes=-601).datetime), 'rate': 0.00001099, 'amount': 90.99181073, @@ -201,7 +200,7 @@ def limit_buy_order_old_partial(): return { 'id': 'mocked_limit_buy_old_partial', 'type': 'LIMIT_BUY', - 'pair': 'BTC_ETH', + 'pair': 'ETH/BTC', 'opened': str(arrow.utcnow().shift(minutes=-601).datetime), 'rate': 0.00001099, 'amount': 90.99181073, @@ -307,125 +306,149 @@ def get_market_summaries_data(): 8 entries. 4 BTC, 4 USTD :return: JSON market summaries """ - return [ - { - 'Ask': 1.316e-05, - 'BaseVolume': 5.72599471, - 'Bid': 1.3e-05, - 'Created': '2014-04-14T00:00:00', - 'High': 1.414e-05, - 'Last': 1.298e-05, - 'Low': 1.282e-05, - 'MarketName': 'BTC-XWC', - 'OpenBuyOrders': 2000, - 'OpenSellOrders': 1484, - 'PrevDay': 1.376e-05, - 'TimeStamp': '2018-02-05T01:32:40.493', - 'Volume': 424041.21418375 + return { + 'XWC/BTC': { + 'symbol': 'XWC/BTC', + 'info': { + 'Ask': 1.316e-05, + 'BaseVolume': 5.72599471, + 'Bid': 1.3e-05, + 'Created': '2014-04-14T00:00:00', + 'High': 1.414e-05, + 'Last': 1.298e-05, + 'Low': 1.282e-05, + 'MarketName': 'BTC-XWC', + 'OpenBuyOrders': 2000, + 'OpenSellOrders': 1484, + 'PrevDay': 1.376e-05, + 'TimeStamp': '2018-02-05T01:32:40.493', + 'Volume': 424041.21418375 + } }, - { - 'Ask': 0.00627051, - 'BaseVolume': 93.23302388, - 'Bid': 0.00618192, - 'Created': '2016-10-20T04:48:30.387', - 'High': 0.00669897, - 'Last': 0.00618192, - 'Low': 0.006, - 'MarketName': 'BTC-XZC', - 'OpenBuyOrders': 343, - 'OpenSellOrders': 2037, - 'PrevDay': 0.00668229, - 'TimeStamp': '2018-02-05T01:32:43.383', - 'Volume': 14863.60730702 + 'XZC/BTC': { + 'symbol': 'XZC/BTC', + 'info': { + 'Ask': 0.00627051, + 'BaseVolume': 93.23302388, + 'Bid': 0.00618192, + 'Created': '2016-10-20T04:48:30.387', + 'High': 0.00669897, + 'Last': 0.00618192, + 'Low': 0.006, + 'MarketName': 'BTC-XZC', + 'OpenBuyOrders': 343, + 'OpenSellOrders': 2037, + 'PrevDay': 0.00668229, + 'TimeStamp': '2018-02-05T01:32:43.383', + 'Volume': 14863.60730702 + } }, - { - 'Ask': 0.01137247, - 'BaseVolume': 383.55922657, - 'Bid': 0.01136006, - 'Created': '2016-11-15T20:29:59.73', - 'High': 0.012, - 'Last': 0.01137247, - 'Low': 0.01119883, - 'MarketName': 'BTC-ZCL', - 'OpenBuyOrders': 1332, - 'OpenSellOrders': 5317, - 'PrevDay': 0.01179603, - 'TimeStamp': '2018-02-05T01:32:42.773', - 'Volume': 33308.07358285 + 'ZCL/BTC': { + 'symbol': 'ZCL/BTC', + 'info': { + 'Ask': 0.01137247, + 'BaseVolume': 383.55922657, + 'Bid': 0.01136006, + 'Created': '2016-11-15T20:29:59.73', + 'High': 0.012, + 'Last': 0.01137247, + 'Low': 0.01119883, + 'MarketName': 'BTC-ZCL', + 'OpenBuyOrders': 1332, + 'OpenSellOrders': 5317, + 'PrevDay': 0.01179603, + 'TimeStamp': '2018-02-05T01:32:42.773', + 'Volume': 33308.07358285 + } }, - { - 'Ask': 0.04155821, - 'BaseVolume': 274.75369074, - 'Bid': 0.04130002, - 'Created': '2016-10-28T17:13:10.833', - 'High': 0.04354429, - 'Last': 0.041585, - 'Low': 0.0413, - 'MarketName': 'BTC-ZEC', - 'OpenBuyOrders': 863, - 'OpenSellOrders': 5579, - 'PrevDay': 0.0429, - 'TimeStamp': '2018-02-05T01:32:43.21', - 'Volume': 6479.84033259 + 'ZEC/BTC': { + 'symbol': 'ZEC/BTC', + 'info': { + 'Ask': 0.04155821, + 'BaseVolume': 274.75369074, + 'Bid': 0.04130002, + 'Created': '2016-10-28T17:13:10.833', + 'High': 0.04354429, + 'Last': 0.041585, + 'Low': 0.0413, + 'MarketName': 'BTC-ZEC', + 'OpenBuyOrders': 863, + 'OpenSellOrders': 5579, + 'PrevDay': 0.0429, + 'TimeStamp': '2018-02-05T01:32:43.21', + 'Volume': 6479.84033259 + } }, - { - 'Ask': 210.99999999, - 'BaseVolume': 615132.70989532, - 'Bid': 210.05503736, - 'Created': '2017-07-21T01:08:49.397', - 'High': 257.396, - 'Last': 211.0, - 'Low': 209.05333589, - 'MarketName': 'USDT-XMR', - 'OpenBuyOrders': 180, - 'OpenSellOrders': 1203, - 'PrevDay': 247.93528899, - 'TimeStamp': '2018-02-05T01:32:43.117', - 'Volume': 2688.17410793 + 'XMR/USDT': { + 'symbol': 'XMR/USDT', + 'info': { + 'Ask': 210.99999999, + 'BaseVolume': 615132.70989532, + 'Bid': 210.05503736, + 'Created': '2017-07-21T01:08:49.397', + 'High': 257.396, + 'Last': 211.0, + 'Low': 209.05333589, + 'MarketName': 'USDT-XMR', + 'OpenBuyOrders': 180, + 'OpenSellOrders': 1203, + 'PrevDay': 247.93528899, + 'TimeStamp': '2018-02-05T01:32:43.117', + 'Volume': 2688.17410793 + } }, - { - 'Ask': 0.79589979, - 'BaseVolume': 9349557.01853031, - 'Bid': 0.789226, - 'Created': '2017-07-14T17:10:10.737', - 'High': 0.977, - 'Last': 0.79589979, - 'Low': 0.781, - 'MarketName': 'USDT-XRP', - 'OpenBuyOrders': 1075, - 'OpenSellOrders': 6508, - 'PrevDay': 0.93300218, - 'TimeStamp': '2018-02-05T01:32:42.383', - 'Volume': 10801663.00788851 + 'XRP/USDT': { + 'symbol': 'XRP/USDT', + 'info': { + 'Ask': 0.79589979, + 'BaseVolume': 9349557.01853031, + 'Bid': 0.789226, + 'Created': '2017-07-14T17:10:10.737', + 'High': 0.977, + 'Last': 0.79589979, + 'Low': 0.781, + 'MarketName': 'USDT-XRP', + 'OpenBuyOrders': 1075, + 'OpenSellOrders': 6508, + 'PrevDay': 0.93300218, + 'TimeStamp': '2018-02-05T01:32:42.383', + 'Volume': 10801663.00788851 + } }, - { - 'Ask': 0.05154982, - 'BaseVolume': 2311087.71232136, - 'Bid': 0.05040107, - 'Created': '2017-12-29T19:29:18.357', - 'High': 0.06668561, - 'Last': 0.0508, - 'Low': 0.05006731, - 'MarketName': 'USDT-XVG', - 'OpenBuyOrders': 655, - 'OpenSellOrders': 5544, - 'PrevDay': 0.0627, - 'TimeStamp': '2018-02-05T01:32:41.507', - 'Volume': 40031424.2152716 + 'XVG/USDT': { + 'symbol': 'XVG/USDT', + 'info': { + 'Ask': 0.05154982, + 'BaseVolume': 2311087.71232136, + 'Bid': 0.05040107, + 'Created': '2017-12-29T19:29:18.357', + 'High': 0.06668561, + 'Last': 0.0508, + 'Low': 0.05006731, + 'MarketName': 'USDT-XVG', + 'OpenBuyOrders': 655, + 'OpenSellOrders': 5544, + 'PrevDay': 0.0627, + 'TimeStamp': '2018-02-05T01:32:41.507', + 'Volume': 40031424.2152716 + } }, - { - 'Ask': 332.65500022, - 'BaseVolume': 562911.87455665, - 'Bid': 330.00000001, - 'Created': '2017-07-14T17:10:10.673', - 'High': 401.59999999, - 'Last': 332.65500019, - 'Low': 330.0, - 'MarketName': 'USDT-ZEC', - 'OpenBuyOrders': 161, - 'OpenSellOrders': 1731, - 'PrevDay': 391.42, - 'TimeStamp': '2018-02-05T01:32:42.947', - 'Volume': 1571.09647946 + 'ZEC/USDT': { + 'symbol': 'ZEC/USDT', + 'info': { + 'Ask': 332.65500022, + 'BaseVolume': 562911.87455665, + 'Bid': 330.00000001, + 'Created': '2017-07-14T17:10:10.673', + 'High': 401.59999999, + 'Last': 332.65500019, + 'Low': 330.0, + 'MarketName': 'USDT-ZEC', + 'OpenBuyOrders': 161, + 'OpenSellOrders': 1731, + 'PrevDay': 391.42, + 'TimeStamp': '2018-02-05T01:32:42.947', + 'Volume': 1571.09647946 + } } - ] + } diff --git a/freqtrade/tests/rpc/test_rpc.py b/freqtrade/tests/rpc/test_rpc.py index 50943b1bc..191916488 100644 --- a/freqtrade/tests/rpc/test_rpc.py +++ b/freqtrade/tests/rpc/test_rpc.py @@ -59,7 +59,7 @@ def test_rpc_trade_status(default_conf, ticker, mocker) -> None: result_message = [ '*Trade ID:* `1`\n' '*Current Pair:* ' - '[BTC_ETH](https://www.bittrex.com/Market/Index?MarketName=BTC-ETH)\n' + '[ETH/BTC](https://bittrex.com/Market/Index?MarketName=BTC-ETH)\n' '*Open Since:* `just now`\n' '*Amount:* `90.99181074`\n' '*Open Rate:* `0.00001099`\n' @@ -70,7 +70,7 @@ def test_rpc_trade_status(default_conf, ticker, mocker) -> None: '*Open Order:* `(LIMIT_BUY rem=0.00000000)`' ] assert result == result_message - assert trade.find('[BTC_ETH]') >= 0 + assert trade.find('[ETH/BTC]') >= 0 def test_rpc_status_table(default_conf, ticker, mocker) -> None: @@ -102,7 +102,7 @@ def test_rpc_status_table(default_conf, ticker, mocker) -> None: freqtradebot.create_trade() (error, result) = rpc.rpc_status_table() assert 'just now' in result['Since'].all() - assert 'BTC_ETH' in result['Pair'].all() + assert 'ETH/BTC' in result['Pair'].all() assert '-0.59%' in result['Profit'].all() @@ -214,7 +214,7 @@ def test_rpc_trade_statistics( assert stats['first_trade_date'] == 'just now' assert stats['latest_trade_date'] == 'just now' assert stats['avg_duration'] == '0:00:00' - assert stats['best_pair'] == 'BTC_ETH' + assert stats['best_pair'] == 'ETH/BTC' assert prec_satoshi(stats['best_rate'], 6.2) @@ -274,7 +274,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, ticker_sell_u assert stats['first_trade_date'] == 'just now' assert stats['latest_trade_date'] == 'just now' assert stats['avg_duration'] == '0:00:00' - assert stats['best_pair'] == 'BTC_ETH' + assert stats['best_pair'] == 'ETH/BTC' assert prec_satoshi(stats['best_rate'], 6.2) @@ -509,7 +509,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, (error, res) = rpc.rpc_performance() assert not error assert len(res) == 1 - assert res[0]['pair'] == 'BTC_ETH' + assert res[0]['pair'] == 'ETH/BTC' assert res[0]['count'] == 1 assert prec_satoshi(res[0]['profit'], 6.2) diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 4796b077e..2df43f0fc 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -248,7 +248,8 @@ def test_status(default_conf, update, mocker, ticker) -> None: mocker.patch.multiple( 'freqtrade.freqtradebot.exchange', validate_pairs=MagicMock(), - get_ticker=ticker + get_ticker=ticker, + get_pair_detail_url=MagicMock() ) msg_mock = MagicMock() status_table = MagicMock() @@ -319,7 +320,7 @@ def test_status_handle(default_conf, update, ticker, mocker) -> None: telegram._status(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 - assert '[BTC_ETH]' in msg_mock.call_args_list[0][0][0] + assert '[ETH/BTC]' in msg_mock.call_args_list[0][0][0] def test_status_table_handle(default_conf, update, ticker, mocker) -> None: @@ -369,7 +370,7 @@ def test_status_table_handle(default_conf, update, ticker, mocker) -> None: fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ') assert int(fields[0]) == 1 - assert fields[1] == 'BTC_ETH' + assert fields[1] == 'ETH/BTC' assert msg_mock.call_count == 1 @@ -387,7 +388,8 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, mocker.patch.multiple( 'freqtrade.freqtradebot.exchange', validate_pairs=MagicMock(), - get_ticker=ticker + get_ticker=ticker, + get_pair_detail_url=MagicMock() ) msg_mock = MagicMock() mocker.patch.multiple( @@ -541,7 +543,7 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, assert '∙ `0.00006217 BTC (6.20%)`' in msg_mock.call_args_list[-1][0][0] assert '∙ `0.933 USD`' in msg_mock.call_args_list[-1][0][0] - assert '*Best Performing:* `BTC_ETH: 6.20%`' in msg_mock.call_args_list[-1][0][0] + assert '*Best Performing:* `ETH/BTC: 6.20%`' in msg_mock.call_args_list[-1][0][0] def test_telegram_balance_handle(default_conf, update, mocker) -> None: @@ -779,7 +781,7 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker) assert rpc_mock.call_count == 2 assert 'Selling' in rpc_mock.call_args_list[-1][0][0] - assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0] + assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] assert 'profit: 6.11%, 0.00006126' in rpc_mock.call_args_list[-1][0][0] @@ -822,7 +824,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, m assert rpc_mock.call_count == 2 assert 'Selling' in rpc_mock.call_args_list[-1][0][0] - assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0] + assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert '0.00001044' in rpc_mock.call_args_list[-1][0][0] assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] @@ -838,6 +840,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, mocker) -> None: mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock()) + mocker.patch('freqtrade.exchange.get_pair_detail_url', MagicMock()) mocker.patch.multiple( 'freqtrade.freqtradebot.exchange', validate_pairs=MagicMock(), @@ -942,7 +945,7 @@ def test_performance_handle(default_conf, update, ticker, limit_buy_order, telegram._performance(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 assert 'Performance' in msg_mock.call_args_list[0][0][0] - assert 'BTC_ETH\t6.20% (1)' in msg_mock.call_args_list[0][0][0] + assert 'ETH/BTC\t6.20% (1)' in msg_mock.call_args_list[0][0][0] def test_performance_handle_invalid(default_conf, update, mocker) -> None: diff --git a/freqtrade/tests/test_acl_pair.py b/freqtrade/tests/test_acl_pair.py index b5f52774d..174f1fde9 100644 --- a/freqtrade/tests/test_acl_pair.py +++ b/freqtrade/tests/test_acl_pair.py @@ -12,77 +12,87 @@ def whitelist_conf(): config['stake_currency'] = 'BTC' config['exchange']['pair_whitelist'] = [ - 'BTC_ETH', - 'BTC_TKN', - 'BTC_TRST', - 'BTC_SWT', - 'BTC_BCC' + 'ETH/BTC', + 'TKN/BTC', + 'TRST/BTC', + 'SWT/BTC', + 'BCC/BTC' ] config['exchange']['pair_blacklist'] = [ - 'BTC_BLK' + 'BLK/BTC' ] return config def get_market_summaries(): - return [{ - 'MarketName': 'BTC-TKN', - 'High': 0.00000919, - 'Low': 0.00000820, - 'Volume': 74339.61396015, - 'Last': 0.00000820, - 'BaseVolume': 1664, - 'TimeStamp': '2014-07-09T07:19:30.15', - 'Bid': 0.00000820, - 'Ask': 0.00000831, - 'OpenBuyOrders': 15, - 'OpenSellOrders': 15, - 'PrevDay': 0.00000821, - 'Created': '2014-03-20T06:00:00', - 'DisplayMarketName': '' - }, { - 'MarketName': 'BTC-ETH', - 'High': 0.00000072, - 'Low': 0.00000001, - 'Volume': 166340678.42280999, - 'Last': 0.00000005, - 'BaseVolume': 42, - 'TimeStamp': '2014-07-09T07:21:40.51', - 'Bid': 0.00000004, - 'Ask': 0.00000005, - 'OpenBuyOrders': 18, - 'OpenSellOrders': 18, - 'PrevDay': 0.00000002, - 'Created': '2014-05-30T07:57:49.637', - 'DisplayMarketName': '' - }, { - 'MarketName': 'BTC-BLK', - 'High': 0.00000072, - 'Low': 0.00000001, - 'Volume': 166340678.42280999, - 'Last': 0.00000005, - 'BaseVolume': 3, - 'TimeStamp': '2014-07-09T07:21:40.51', - 'Bid': 0.00000004, - 'Ask': 0.00000005, - 'OpenBuyOrders': 18, - 'OpenSellOrders': 18, - 'PrevDay': 0.00000002, - 'Created': '2014-05-30T07:57:49.637', - 'DisplayMarketName': '' - }] + return { + 'TKN/BTC': { + 'symbol': 'TKN/BTC', + 'info': { + 'High': 0.00000919, + 'Low': 0.00000820, + 'Volume': 74339.61396015, + 'Last': 0.00000820, + 'BaseVolume': 1664, + 'TimeStamp': '2014-07-09T07:19:30.15', + 'Bid': 0.00000820, + 'Ask': 0.00000831, + 'OpenBuyOrders': 15, + 'OpenSellOrders': 15, + 'PrevDay': 0.00000821, + 'Created': '2014-03-20T06:00:00', + 'DisplayMarketName': '' + } + }, + 'ETH/BTC': { + 'symbol': 'ETH/BTC', + 'info': { + 'High': 0.00000072, + 'Low': 0.00000001, + 'Volume': 166340678.42280999, + 'Last': 0.00000005, + 'BaseVolume': 42, + 'TimeStamp': '2014-07-09T07:21:40.51', + 'Bid': 0.00000004, + 'Ask': 0.00000005, + 'OpenBuyOrders': 18, + 'OpenSellOrders': 18, + 'PrevDay': 0.00000002, + 'Created': '2014-05-30T07:57:49.637', + 'DisplayMarketName': '' + } + }, + 'BLK/BTC': { + 'symbol': 'BLK/BTC', + 'info': { + 'High': 0.00000072, + 'Low': 0.00000001, + 'Volume': 166340678.42280999, + 'Last': 0.00000005, + 'BaseVolume': 3, + 'TimeStamp': '2014-07-09T07:21:40.51', + 'Bid': 0.00000004, + 'Ask': 0.00000005, + 'OpenBuyOrders': 18, + 'OpenSellOrders': 18, + 'PrevDay': 0.00000002, + 'Created': '2014-05-30T07:57:49.637', + 'DisplayMarketName': '' + }} + } def get_health(): - return [{'Currency': 'ETH', 'IsActive': True}, - {'Currency': 'TKN', 'IsActive': True}, - {'Currency': 'BLK', 'IsActive': True}] + return { + 'ETH/BTC': {'base': 'ETH', 'active': True}, + 'TKN/BTC': {'base': 'TKN', 'active': True}, + 'BLK/BTC': {'base': 'BLK', 'active': True}} def get_health_empty(): - return [] + return {} def test_refresh_market_pair_not_in_whitelist(mocker): @@ -92,10 +102,10 @@ def test_refresh_market_pair_not_in_whitelist(mocker): mocker.patch('freqtrade.freqtradebot.exchange.get_wallet_health', get_health) refreshedwhitelist = freqtradebot._refresh_whitelist( - conf['exchange']['pair_whitelist'] + ['BTC_XXX'] + conf['exchange']['pair_whitelist'] + ['XXX/BTC'] ) # List ordered by BaseVolume - whitelist = ['BTC_ETH', 'BTC_TKN'] + whitelist = ['ETH/BTC', 'TKN/BTC'] # Ensure all except those in whitelist are removed assert whitelist == refreshedwhitelist @@ -108,7 +118,7 @@ def test_refresh_whitelist(mocker): refreshedwhitelist = freqtradebot._refresh_whitelist(conf['exchange']['pair_whitelist']) # List ordered by BaseVolume - whitelist = ['BTC_ETH', 'BTC_TKN'] + whitelist = ['ETH/BTC', 'TKN/BTC'] # Ensure all except those in whitelist are removed assert whitelist == refreshedwhitelist @@ -123,7 +133,7 @@ def test_refresh_whitelist_dynamic(mocker): ) # argument: use the whitelist dynamically by exchange-volume - whitelist = ['BTC_TKN', 'BTC_ETH'] + whitelist = ['TKN/BTC', 'ETH/BTC'] refreshedwhitelist = freqtradebot._refresh_whitelist( freqtradebot._gen_pair_whitelist(conf['stake_currency']) diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index d58b428da..02edf3322 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -17,7 +17,6 @@ import requests from sqlalchemy import create_engine from freqtrade import DependencyException, OperationalException -from freqtrade.exchange import Exchanges from freqtrade.freqtradebot import FreqtradeBot from freqtrade.persistence import Trade from freqtrade.state import State @@ -216,15 +215,15 @@ def test_gen_pair_whitelist(mocker, default_conf, get_market_summaries_data) -> # Test to retrieved BTC sorted on BaseVolume whitelist = freqtrade._gen_pair_whitelist(base_currency='BTC') - assert whitelist == ['BTC_ZCL', 'BTC_ZEC', 'BTC_XZC', 'BTC_XWC'] + assert whitelist == ['ZCL/BTC', 'ZEC/BTC', 'XZC/BTC', 'XWC/BTC'] # Test to retrieved BTC sorted on OpenBuyOrders whitelist = freqtrade._gen_pair_whitelist(base_currency='BTC', key='OpenBuyOrders') - assert whitelist == ['BTC_XWC', 'BTC_ZCL', 'BTC_ZEC', 'BTC_XZC'] + assert whitelist == ['XWC/BTC', 'ZCL/BTC', 'ZEC/BTC', 'XZC/BTC'] # Test with USDT sorted on BaseVolume whitelist = freqtrade._gen_pair_whitelist(base_currency='USDT') - assert whitelist == ['USDT_XRP', 'USDT_XVG', 'USDT_XMR', 'USDT_ZEC'] + assert whitelist == ['XRP/USDT', 'XVG/USDT', 'XMR/USDT', 'ZEC/USDT'] # Test with ETH (our fixture does not have ETH, but Bittrex returns them) whitelist = freqtrade._gen_pair_whitelist(base_currency='ETH') @@ -263,7 +262,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker) -> None: assert trade.stake_amount == 0.001 assert trade.is_open assert trade.open_date is not None - assert trade.exchange == Exchanges.BITTREX.name + assert trade.exchange == 'BITTREX' # Simulate fulfilled LIMIT_BUY order for trade trade.update(limit_buy_order) @@ -333,8 +332,8 @@ def test_create_trade_no_pairs(default_conf, ticker, mocker) -> None: ) conf = deepcopy(default_conf) - conf['exchange']['pair_whitelist'] = ["BTC_ETH"] - conf['exchange']['pair_blacklist'] = ["BTC_ETH"] + conf['exchange']['pair_whitelist'] = ["ETH/BTC"] + conf['exchange']['pair_blacklist'] = ["ETH/BTC"] freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) freqtrade.create_trade() @@ -358,8 +357,8 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker) -> ) conf = deepcopy(default_conf) - conf['exchange']['pair_whitelist'] = ["BTC_ETH"] - conf['exchange']['pair_blacklist'] = ["BTC_ETH"] + conf['exchange']['pair_whitelist'] = ["ETH/BTC"] + conf['exchange']['pair_blacklist'] = ["ETH/BTC"] freqtrade = FreqtradeBot(conf, create_engine('sqlite://')) freqtrade.create_trade() @@ -425,7 +424,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order, assert trade.stake_amount == default_conf['stake_amount'] assert trade.is_open assert trade.open_date is not None - assert trade.exchange == Exchanges.BITTREX.name + assert trade.exchange == "BITTREX" assert trade.open_rate == 0.00001099 assert trade.amount == 90.99181073703367 @@ -793,7 +792,7 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, mo freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) trade_buy = Trade( - pair='BTC_ETH', + pair='ETH/BTC', open_rate=0.00001099, exchange='BITTREX', open_order_id='123456789', @@ -832,7 +831,7 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old, freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) trade_sell = Trade( - pair='BTC_ETH', + pair='ETH/BTC', open_rate=0.00001099, exchange='BITTREX', open_order_id='123456789', @@ -871,7 +870,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) trade_buy = Trade( - pair='BTC_ETH', + pair='ETH/BTC', open_rate=0.00001099, exchange='BITTREX', open_order_id='123456789', @@ -918,7 +917,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) - freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://')) trade_buy = Trade( - pair='BTC_ETH', + pair='ETH/BTC', open_rate=0.00001099, exchange='BITTREX', open_order_id='123456789', @@ -931,7 +930,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) - Trade.session.add(trade_buy) regexp = re.compile( - 'Cannot query order for Trade(id=1, pair=BTC_ETH, amount=90.99181073, ' + 'Cannot query order for Trade(id=1, pair=ETH/BTC, amount=90.99181073, ' 'open_rate=0.00001099, open_since=10 hours ago) due to Traceback (most ' 'recent call last):\n.*' ) @@ -1024,7 +1023,7 @@ def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker) -> None: assert rpc_mock.call_count == 2 assert 'Selling' in rpc_mock.call_args_list[-1][0][0] - assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0] + assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert 'Profit' in rpc_mock.call_args_list[-1][0][0] assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] @@ -1064,7 +1063,7 @@ def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker) -> No assert rpc_mock.call_count == 2 assert 'Selling' in rpc_mock.call_args_list[-1][0][0] - assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0] + assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert '0.00001044' in rpc_mock.call_args_list[-1][0][0] assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] @@ -1103,7 +1102,7 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, ticker_sell_up, assert rpc_mock.call_count == 2 assert 'Selling' in rpc_mock.call_args_list[-1][0][0] - assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0] + assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] assert '(profit: 6.11%, 0.00006126)' in rpc_mock.call_args_list[-1][0][0] @@ -1143,7 +1142,7 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, assert rpc_mock.call_count == 2 assert 'Selling' in rpc_mock.call_args_list[-1][0][0] - assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0] + assert '[ETH/BTC]' in rpc_mock.call_args_list[-1][0][0] assert '0.00001044' in rpc_mock.call_args_list[-1][0][0] assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index 70199b12a..c09774a37 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -4,7 +4,7 @@ import os import pytest from sqlalchemy import create_engine -from freqtrade.exchange import Exchanges +from freqtrade import exchange from freqtrade.persistence import Trade, init, clean_dry_run_db @@ -122,7 +122,7 @@ def test_update_with_bittrex(limit_buy_order, limit_sell_order): pair='BTC_ETH', stake_amount=0.001, fee=0.0025, - exchange=Exchanges.BITTREX, + exchange='bittrex', ) assert trade.open_order_id is None assert trade.open_rate is None @@ -146,10 +146,10 @@ def test_update_with_bittrex(limit_buy_order, limit_sell_order): def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order): trade = Trade( - pair='BTC_ETH', + pair='ETH/BTC', stake_amount=0.001, fee=0.0025, - exchange=Exchanges.BITTREX, + exchange='bittrex', ) trade.open_order_id = 'something' @@ -168,10 +168,10 @@ def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order): def test_calc_close_trade_price_exception(limit_buy_order): trade = Trade( - pair='BTC_ETH', + pair='ETH/BTC', stake_amount=0.001, fee=0.0025, - exchange=Exchanges.BITTREX, + exchange='bittrex', ) trade.open_order_id = 'something' @@ -181,10 +181,10 @@ def test_calc_close_trade_price_exception(limit_buy_order): def test_update_open_order(limit_buy_order): trade = Trade( - pair='BTC_ETH', + pair='ETH/BTC', stake_amount=1.00, fee=0.1, - exchange=Exchanges.BITTREX, + exchange='bittrex', ) assert trade.open_order_id is None @@ -203,10 +203,10 @@ def test_update_open_order(limit_buy_order): def test_update_invalid_order(limit_buy_order): trade = Trade( - pair='BTC_ETH', + pair='ETH/BTC', stake_amount=1.00, fee=0.1, - exchange=Exchanges.BITTREX, + exchange='bittrex', ) limit_buy_order['type'] = 'invalid' with pytest.raises(ValueError, match=r'Unknown order type'): @@ -215,10 +215,10 @@ def test_update_invalid_order(limit_buy_order): def test_calc_open_trade_price(limit_buy_order): trade = Trade( - pair='BTC_ETH', + pair='ETH/BTC', stake_amount=0.001, fee=0.0025, - exchange=Exchanges.BITTREX, + exchange='bittrex', ) trade.open_order_id = 'open_trade' trade.update(limit_buy_order) # Buy @ 0.00001099 @@ -232,10 +232,10 @@ def test_calc_open_trade_price(limit_buy_order): def test_calc_close_trade_price(limit_buy_order, limit_sell_order): trade = Trade( - pair='BTC_ETH', + pair='ETH/BTC', stake_amount=0.001, fee=0.0025, - exchange=Exchanges.BITTREX, + exchange='bittrex', ) trade.open_order_id = 'close_trade' trade.update(limit_buy_order) # Buy @ 0.00001099 @@ -253,10 +253,10 @@ def test_calc_close_trade_price(limit_buy_order, limit_sell_order): def test_calc_profit(limit_buy_order, limit_sell_order): trade = Trade( - pair='BTC_ETH', + pair='ETH/BTC', stake_amount=0.001, fee=0.0025, - exchange=Exchanges.BITTREX, + exchange='bittrex', ) trade.open_order_id = 'profit_percent' trade.update(limit_buy_order) # Buy @ 0.00001099 @@ -283,10 +283,10 @@ def test_calc_profit(limit_buy_order, limit_sell_order): def test_calc_profit_percent(limit_buy_order, limit_sell_order): trade = Trade( - pair='BTC_ETH', + pair='ETH/BTC', stake_amount=0.001, fee=0.0025, - exchange=Exchanges.BITTREX, + exchange='bittrex', ) trade.open_order_id = 'profit_percent' trade.update(limit_buy_order) # Buy @ 0.00001099 @@ -310,35 +310,35 @@ def test_clean_dry_run_db(default_conf): # Simulate dry_run entries trade = Trade( - pair='BTC_ETH', + pair='ETH/BTC', stake_amount=0.001, amount=123.0, fee=0.0025, open_rate=0.123, - exchange='BITTREX', + exchange='bittrex', open_order_id='dry_run_buy_12345' ) Trade.session.add(trade) trade = Trade( - pair='BTC_ETC', + pair='ETC/BTC', stake_amount=0.001, amount=123.0, fee=0.0025, open_rate=0.123, - exchange='BITTREX', + exchange='bittrex', open_order_id='dry_run_sell_12345' ) Trade.session.add(trade) # Simulate prod entry trade = Trade( - pair='BTC_ETC', + pair='ETC/BTC', stake_amount=0.001, amount=123.0, fee=0.0025, open_rate=0.123, - exchange='BITTREX', + exchange='bittrex', open_order_id='prod_buy_12345' ) Trade.session.add(trade)