From e10a3d1f9dca03d74c889a4065355f4a94afa727 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste LE STANG Date: Tue, 2 Jan 2018 10:56:42 +0100 Subject: [PATCH 1/6] get_ticker can return a cached value --- freqtrade/exchange/__init__.py | 4 +-- freqtrade/exchange/bittrex.py | 44 ++++++++++++++++++--------------- freqtrade/exchange/interface.py | 5 ++-- freqtrade/rpc/telegram.py | 8 +++--- 4 files changed, 33 insertions(+), 28 deletions(-) diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 7b5c0c753..d41c78921 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -134,8 +134,8 @@ def get_balances(): return _API.get_balances() -def get_ticker(pair: str) -> dict: - return _API.get_ticker(pair) +def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict: + return _API.get_ticker(pair, refresh) @cached(TTLCache(maxsize=100, ttl=30)) diff --git a/freqtrade/exchange/bittrex.py b/freqtrade/exchange/bittrex.py index 3714de070..c153091d0 100644 --- a/freqtrade/exchange/bittrex.py +++ b/freqtrade/exchange/bittrex.py @@ -1,5 +1,5 @@ import logging -from typing import List, Dict +from typing import List, Dict, Optional from bittrex.bittrex import Bittrex as _Bittrex, API_V2_0, API_V1_1 from requests.exceptions import ContentDecodingError @@ -38,6 +38,7 @@ class Bittrex(Exchange): calls_per_second=1, api_version=API_V2_0, ) + self.cached_ticker = {} @staticmethod def _validate_response(response) -> None: @@ -95,26 +96,29 @@ class Bittrex(Exchange): raise OperationalException('{message}'.format(message=data['message'])) return data['result'] - def get_ticker(self, pair: str) -> dict: - data = _API.get_ticker(pair.replace('_', '-')) - if not data['success']: - Bittrex._validate_response(data) - raise OperationalException('{message} params=({pair})'.format( - message=data['message'], - pair=pair)) + def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict: + data = _API.get_ticker(pair.replace('_', '-'), refresh) + if refresh: + if not data['success']: + Bittrex._validate_response(data) + raise OperationalException('{message} params=({pair})'.format( + message=data['message'], + pair=pair)) - if not data.get('result') \ - or not data['result'].get('Bid') \ - or not data['result'].get('Ask') \ - or not data['result'].get('Last'): - raise ContentDecodingError('{message} params=({pair})'.format( - message='Got invalid response from bittrex', - pair=pair)) - return { - 'bid': float(data['result']['Bid']), - 'ask': float(data['result']['Ask']), - 'last': float(data['result']['Last']), - } + if not data.get('result') \ + or not data['result'].get('Bid') \ + or not data['result'].get('Ask') \ + or not data['result'].get('Last'): + raise ContentDecodingError('{message} params=({pair})'.format( + message='Got invalid response from bittrex', + pair=pair)) + # Update the pair + self.cached_ticker[pair] = { + 'bid': float(data['result']['Bid']), + 'ask': float(data['result']['Ask']), + 'last': float(data['result']['Last']), + } + return self.cached_ticker[pair] def get_ticker_history(self, pair: str, tick_interval: int) -> List[Dict]: if tick_interval == 1: diff --git a/freqtrade/exchange/interface.py b/freqtrade/exchange/interface.py index a46b3c054..167a824d0 100644 --- a/freqtrade/exchange/interface.py +++ b/freqtrade/exchange/interface.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import List, Dict +from typing import List, Dict, Optional class Exchange(ABC): @@ -62,10 +62,11 @@ class Exchange(ABC): """ @abstractmethod - def get_ticker(self, pair: str) -> dict: + def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict: """ Gets ticker for given pair. :param pair: Pair as str, format: BTC_ETC + :param refresh: Shall we query a new value or a cached value is enought :return: dict, format: { 'bid': float, 'ask': float, diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 7636c2b8a..ba3dd3c3e 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -140,7 +140,7 @@ def _status(bot: Bot, update: Update) -> None: if trade.open_order_id: order = exchange.get_order(trade.open_order_id) # calculate profit and send message to user - current_rate = exchange.get_ticker(trade.pair)['bid'] + current_rate = exchange.get_ticker(trade.pair, False)['bid'] current_profit = trade.calc_profit_percent(current_rate) fmt_close_profit = '{:.2f}%'.format( round(trade.close_profit * 100, 2) @@ -193,7 +193,7 @@ def _status_table(bot: Bot, update: Update) -> None: trades_list = [] for trade in trades: # calculate profit and send message to user - current_rate = exchange.get_ticker(trade.pair)['bid'] + current_rate = exchange.get_ticker(trade.pair, False)['bid'] trades_list.append([ trade.id, trade.pair, @@ -301,7 +301,7 @@ def _profit(bot: Bot, update: Update) -> None: profit_closed_percent.append(profit_percent) else: # Get current rate - current_rate = exchange.get_ticker(trade.pair)['bid'] + current_rate = exchange.get_ticker(trade.pair, False)['bid'] profit_percent = trade.calc_profit_percent(rate=current_rate) profit_all_coin.append(trade.calc_profit(rate=Decimal(trade.close_rate or current_rate))) @@ -577,7 +577,7 @@ def _exec_forcesell(trade: Trade) -> None: return # Get current rate and execute sell - current_rate = exchange.get_ticker(trade.pair)['bid'] + current_rate = exchange.get_ticker(trade.pair, False)['bid'] from freqtrade.main import execute_sell execute_sell(trade, current_rate) From 165781a5459e01fbd03b02749ad1874dc85dd64e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste LE STANG Date: Tue, 2 Jan 2018 11:00:22 +0100 Subject: [PATCH 2/6] force refresh is the value has never been set --- freqtrade/exchange/bittrex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/bittrex.py b/freqtrade/exchange/bittrex.py index c153091d0..c1ea0608f 100644 --- a/freqtrade/exchange/bittrex.py +++ b/freqtrade/exchange/bittrex.py @@ -98,7 +98,7 @@ class Bittrex(Exchange): def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict: data = _API.get_ticker(pair.replace('_', '-'), refresh) - if refresh: + if refresh or pair not in self.cached_ticker.keys(): if not data['success']: Bittrex._validate_response(data) raise OperationalException('{message} params=({pair})'.format( From 90d3c0953685ba2c73c371a2bdc95a97251685e7 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste LE STANG Date: Tue, 2 Jan 2018 14:05:27 +0100 Subject: [PATCH 3/6] fixing refresh argument ... --- freqtrade/exchange/bittrex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/bittrex.py b/freqtrade/exchange/bittrex.py index c1ea0608f..139f2896f 100644 --- a/freqtrade/exchange/bittrex.py +++ b/freqtrade/exchange/bittrex.py @@ -97,7 +97,7 @@ class Bittrex(Exchange): return data['result'] def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict: - data = _API.get_ticker(pair.replace('_', '-'), refresh) + data = _API.get_ticker(pair.replace('_', '-')) if refresh or pair not in self.cached_ticker.keys(): if not data['success']: Bittrex._validate_response(data) From 5f696a0cce492fce1d565acda233a5b83f9b25b6 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste LE STANG Date: Tue, 2 Jan 2018 14:09:56 +0100 Subject: [PATCH 4/6] really fixing --- freqtrade/exchange/bittrex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/bittrex.py b/freqtrade/exchange/bittrex.py index 139f2896f..4883db037 100644 --- a/freqtrade/exchange/bittrex.py +++ b/freqtrade/exchange/bittrex.py @@ -97,8 +97,8 @@ class Bittrex(Exchange): return data['result'] def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict: - data = _API.get_ticker(pair.replace('_', '-')) if refresh or pair not in self.cached_ticker.keys(): + data = _API.get_ticker(pair.replace('_', '-')) if not data['success']: Bittrex._validate_response(data) raise OperationalException('{message} params=({pair})'.format( From 050e73d96086626961085475b1b310bdd681ae54 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste LE STANG Date: Wed, 3 Jan 2018 17:51:01 +0100 Subject: [PATCH 5/6] fix a typo in the description of get_ticker --- freqtrade/exchange/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/interface.py b/freqtrade/exchange/interface.py index 167a824d0..1be84abe5 100644 --- a/freqtrade/exchange/interface.py +++ b/freqtrade/exchange/interface.py @@ -66,7 +66,7 @@ class Exchange(ABC): """ Gets ticker for given pair. :param pair: Pair as str, format: BTC_ETC - :param refresh: Shall we query a new value or a cached value is enought + :param refresh: Shall we query a new value or a cached value is enough :return: dict, format: { 'bid': float, 'ask': float, From 75955fcc04b9b4d4ca4127085c67b59132dece39 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste LE STANG Date: Wed, 3 Jan 2018 17:58:08 +0100 Subject: [PATCH 6/6] Add a unitest and fix pep8 --- freqtrade/tests/exchange/test_exchange.py | 35 +++++++++++++++++------ 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/freqtrade/tests/exchange/test_exchange.py b/freqtrade/tests/exchange/test_exchange.py index 2da657642..e47bb56eb 100644 --- a/freqtrade/tests/exchange/test_exchange.py +++ b/freqtrade/tests/exchange/test_exchange.py @@ -11,7 +11,8 @@ from freqtrade.exchange import init, validate_pairs, buy, sell, get_balance, get def test_init(default_conf, mocker, caplog): - mocker.patch('freqtrade.exchange.validate_pairs', side_effect=lambda s: True) + mocker.patch('freqtrade.exchange.validate_pairs', + side_effect=lambda s: True) init(config=default_conf) assert ('freqtrade.exchange', logging.INFO, @@ -25,7 +26,7 @@ def test_init_exception(default_conf, mocker): with pytest.raises( OperationalException, match='Exchange {} is not supported'.format(default_conf['exchange']['name'])): - init(config=default_conf) + init(config=default_conf) def test_validate_pairs(default_conf, mocker): @@ -49,7 +50,8 @@ def test_validate_pairs_not_available(default_conf, mocker): def test_validate_pairs_not_compatible(default_conf, mocker): api_mock = MagicMock() - api_mock.get_markets = MagicMock(return_value=['BTC_ETH', 'BTC_TKN', 'BTC_TRST', 'BTC_SWT']) + api_mock.get_markets = MagicMock( + return_value=['BTC_ETH', 'BTC_TKN', 'BTC_TRST', 'BTC_SWT']) default_conf['stake_currency'] = 'ETH' mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch.dict('freqtrade.exchange._CONF', default_conf) @@ -79,7 +81,8 @@ def test_buy_dry_run(default_conf, mocker): def test_buy_prod(default_conf, mocker): api_mock = MagicMock() - api_mock.buy = MagicMock(return_value='dry_run_buy_{}'.format(randint(0, 10**6))) + api_mock.buy = MagicMock( + return_value='dry_run_buy_{}'.format(randint(0, 10**6))) mocker.patch('freqtrade.exchange._API', api_mock) default_conf['dry_run'] = False @@ -97,7 +100,8 @@ def test_sell_dry_run(default_conf, mocker): def test_sell_prod(default_conf, mocker): api_mock = MagicMock() - api_mock.sell = MagicMock(return_value='dry_run_sell_{}'.format(randint(0, 10**6))) + api_mock.sell = MagicMock( + return_value='dry_run_sell_{}'.format(randint(0, 10**6))) mocker.patch('freqtrade.exchange._API', api_mock) default_conf['dry_run'] = False @@ -141,7 +145,8 @@ def test_get_balances_prod(default_conf, mocker): } api_mock = MagicMock() - api_mock.get_balances = MagicMock(return_value=[balance_item, balance_item, balance_item]) + api_mock.get_balances = MagicMock( + return_value=[balance_item, balance_item, balance_item]) mocker.patch('freqtrade.exchange._API', api_mock) default_conf['dry_run'] = False @@ -163,7 +168,19 @@ def test_get_ticker(mocker, ticker): ticker = get_ticker(pair='BTC_ETH') assert ticker['bid'] == 0.00001098 assert ticker['ask'] == 0.00001099 + + # if not caching the result we should get the same ticker + ticker = get_ticker(pair='BTC_ETH', refresh=False) assert ticker['bid'] == 0.00001098 + assert ticker['ask'] == 0.00001099 + + # change the ticker + api_mock.get_ticker = MagicMock(return_value={"bid": 0, "ask": 1}) + mocker.patch('freqtrade.exchange._API', api_mock) + + ticker = get_ticker(pair='BTC_ETH', refresh=True) + assert ticker['bid'] == 0 + assert ticker['ask'] == 1 def test_cancel_order_dry_run(default_conf, mocker): @@ -174,7 +191,8 @@ def test_cancel_order_dry_run(default_conf, mocker): def test_get_name(default_conf, mocker): - mocker.patch('freqtrade.exchange.validate_pairs', side_effect=lambda s: True) + mocker.patch('freqtrade.exchange.validate_pairs', + side_effect=lambda s: True) default_conf['exchange']['name'] = 'bittrex' init(default_conf) @@ -182,7 +200,8 @@ def test_get_name(default_conf, mocker): def test_get_fee(default_conf, mocker): - mocker.patch('freqtrade.exchange.validate_pairs', side_effect=lambda s: True) + mocker.patch('freqtrade.exchange.validate_pairs', + side_effect=lambda s: True) init(default_conf) assert get_fee() == 0.0025