Merge pull request #293 from jblestang/fix_issue_278

The /status table command was getting slower when we had multiple trades opened
This commit is contained in:
Gérald LONLAS 2018-01-07 15:15:25 -08:00 committed by GitHub
commit fca6a09a41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 66 additions and 38 deletions

View File

@ -134,8 +134,8 @@ def get_balances():
return _API.get_balances() return _API.get_balances()
def get_ticker(pair: str) -> dict: def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict:
return _API.get_ticker(pair) return _API.get_ticker(pair, refresh)
@cached(TTLCache(maxsize=100, ttl=30)) @cached(TTLCache(maxsize=100, ttl=30))

View File

@ -1,5 +1,5 @@
import logging 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 bittrex.bittrex import Bittrex as _Bittrex, API_V2_0, API_V1_1
from requests.exceptions import ContentDecodingError from requests.exceptions import ContentDecodingError
@ -38,6 +38,7 @@ class Bittrex(Exchange):
calls_per_second=1, calls_per_second=1,
api_version=API_V2_0, api_version=API_V2_0,
) )
self.cached_ticker = {}
@staticmethod @staticmethod
def _validate_response(response) -> None: def _validate_response(response) -> None:
@ -95,26 +96,29 @@ class Bittrex(Exchange):
raise OperationalException('{message}'.format(message=data['message'])) raise OperationalException('{message}'.format(message=data['message']))
return data['result'] return data['result']
def get_ticker(self, pair: str) -> dict: 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():
if not data['success']: data = _API.get_ticker(pair.replace('_', '-'))
Bittrex._validate_response(data) if not data['success']:
raise OperationalException('{message} params=({pair})'.format( Bittrex._validate_response(data)
message=data['message'], raise OperationalException('{message} params=({pair})'.format(
pair=pair)) message=data['message'],
pair=pair))
if not data.get('result') \ if not data.get('result') \
or not data['result'].get('Bid') \ or not data['result'].get('Bid') \
or not data['result'].get('Ask') \ or not data['result'].get('Ask') \
or not data['result'].get('Last'): or not data['result'].get('Last'):
raise ContentDecodingError('{message} params=({pair})'.format( raise ContentDecodingError('{message} params=({pair})'.format(
message='Got invalid response from bittrex', message='Got invalid response from bittrex',
pair=pair)) pair=pair))
return { # Update the pair
'bid': float(data['result']['Bid']), self.cached_ticker[pair] = {
'ask': float(data['result']['Ask']), 'bid': float(data['result']['Bid']),
'last': float(data['result']['Last']), '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]: def get_ticker_history(self, pair: str, tick_interval: int) -> List[Dict]:
if tick_interval == 1: if tick_interval == 1:

View File

@ -1,5 +1,5 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import List, Dict from typing import List, Dict, Optional
class Exchange(ABC): class Exchange(ABC):
@ -62,10 +62,11 @@ class Exchange(ABC):
""" """
@abstractmethod @abstractmethod
def get_ticker(self, pair: str) -> dict: def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict:
""" """
Gets ticker for given pair. Gets ticker for given pair.
:param pair: Pair as str, format: BTC_ETC :param pair: Pair as str, format: BTC_ETC
:param refresh: Shall we query a new value or a cached value is enough
:return: dict, format: { :return: dict, format: {
'bid': float, 'bid': float,
'ask': float, 'ask': float,

View File

@ -140,7 +140,7 @@ def _status(bot: Bot, update: Update) -> None:
if trade.open_order_id: if trade.open_order_id:
order = exchange.get_order(trade.open_order_id) order = exchange.get_order(trade.open_order_id)
# calculate profit and send message to user # 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) current_profit = trade.calc_profit_percent(current_rate)
fmt_close_profit = '{:.2f}%'.format( fmt_close_profit = '{:.2f}%'.format(
round(trade.close_profit * 100, 2) round(trade.close_profit * 100, 2)
@ -193,7 +193,7 @@ def _status_table(bot: Bot, update: Update) -> None:
trades_list = [] trades_list = []
for trade in trades: for trade in trades:
# calculate profit and send message to user # 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([ trades_list.append([
trade.id, trade.id,
trade.pair, trade.pair,
@ -301,7 +301,7 @@ def _profit(bot: Bot, update: Update) -> None:
profit_closed_percent.append(profit_percent) profit_closed_percent.append(profit_percent)
else: else:
# Get current rate # 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_percent = trade.calc_profit_percent(rate=current_rate)
profit_all_coin.append(trade.calc_profit(rate=Decimal(trade.close_rate or current_rate))) profit_all_coin.append(trade.calc_profit(rate=Decimal(trade.close_rate or current_rate)))
@ -579,7 +579,7 @@ def _exec_forcesell(trade: Trade) -> None:
return return
# Get current rate and execute sell # 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 from freqtrade.main import execute_sell
execute_sell(trade, current_rate) execute_sell(trade, current_rate)

View File

@ -11,7 +11,8 @@ from freqtrade.exchange import init, validate_pairs, buy, sell, get_balance, get
def test_init(default_conf, mocker, caplog): 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) init(config=default_conf)
assert ('freqtrade.exchange', assert ('freqtrade.exchange',
logging.INFO, logging.INFO,
@ -25,7 +26,7 @@ def test_init_exception(default_conf, mocker):
with pytest.raises( with pytest.raises(
OperationalException, OperationalException,
match='Exchange {} is not supported'.format(default_conf['exchange']['name'])): 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): 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): def test_validate_pairs_not_compatible(default_conf, mocker):
api_mock = MagicMock() 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' default_conf['stake_currency'] = 'ETH'
mocker.patch('freqtrade.exchange._API', api_mock) mocker.patch('freqtrade.exchange._API', api_mock)
mocker.patch.dict('freqtrade.exchange._CONF', default_conf) 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): def test_buy_prod(default_conf, mocker):
api_mock = MagicMock() 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) mocker.patch('freqtrade.exchange._API', api_mock)
default_conf['dry_run'] = False default_conf['dry_run'] = False
@ -97,7 +100,8 @@ def test_sell_dry_run(default_conf, mocker):
def test_sell_prod(default_conf, mocker): def test_sell_prod(default_conf, mocker):
api_mock = MagicMock() 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) mocker.patch('freqtrade.exchange._API', api_mock)
default_conf['dry_run'] = False default_conf['dry_run'] = False
@ -141,7 +145,8 @@ def test_get_balances_prod(default_conf, mocker):
} }
api_mock = MagicMock() 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) mocker.patch('freqtrade.exchange._API', api_mock)
default_conf['dry_run'] = False default_conf['dry_run'] = False
@ -157,13 +162,29 @@ def test_get_balances_prod(default_conf, mocker):
def test_get_ticker(mocker, ticker): def test_get_ticker(mocker, ticker):
api_mock = MagicMock() api_mock = MagicMock()
api_mock.get_ticker = MagicMock(return_value=ticker()) tick = {"success": True, 'result': {'Bid': 0.00001098, 'Ask': 0.00001099, 'Last': 0.0001}}
mocker.patch('freqtrade.exchange._API', api_mock) api_mock.get_ticker = MagicMock(return_value=tick)
mocker.patch('freqtrade.exchange.bittrex._API', api_mock)
# retrieve original ticker
ticker = get_ticker(pair='BTC_ETH') ticker = get_ticker(pair='BTC_ETH')
assert ticker['bid'] == 0.00001098 assert ticker['bid'] == 0.00001098
assert ticker['ask'] == 0.00001099 assert ticker['ask'] == 0.00001099
# change the ticker
tick = {"success": True, 'result': {"Bid": 0.5, "Ask": 1, "Last": 42}}
api_mock.get_ticker = MagicMock(return_value=tick)
mocker.patch('freqtrade.exchange.bittrex._API', api_mock)
# 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['bid'] == 0.00001098
assert ticker['ask'] == 0.00001099
# force ticker refresh
ticker = get_ticker(pair='BTC_ETH', refresh=True)
assert ticker['bid'] == 0.5
assert ticker['ask'] == 1
def test_cancel_order_dry_run(default_conf, mocker): def test_cancel_order_dry_run(default_conf, mocker):
@ -174,7 +195,8 @@ def test_cancel_order_dry_run(default_conf, mocker):
def test_get_name(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' default_conf['exchange']['name'] = 'bittrex'
init(default_conf) init(default_conf)
@ -182,7 +204,8 @@ def test_get_name(default_conf, mocker):
def test_get_fee(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) init(default_conf)
assert get_fee() == 0.0025 assert get_fee() == 0.0025