From 4f29e16a5023f4d9a287a3c14ba66acc01ec27d6 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste LE STANG Date: Sat, 9 Dec 2017 13:58:59 +0100 Subject: [PATCH 1/7] Self building our ticker history by updating using the latest ticker and checking the ticker consistency --- freqtrade/exchange/bittrex.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/freqtrade/exchange/bittrex.py b/freqtrade/exchange/bittrex.py index aa74cba80..fbf098767 100644 --- a/freqtrade/exchange/bittrex.py +++ b/freqtrade/exchange/bittrex.py @@ -12,7 +12,7 @@ logger = logging.getLogger(__name__) _API: _Bittrex = None _API_V2: _Bittrex = None _EXCHANGE_CONF: dict = {} - +_cache: dict = dict() class Bittrex(Exchange): """ @@ -22,6 +22,7 @@ class Bittrex(Exchange): BASE_URL: str = 'https://www.bittrex.com' PAIR_DETAIL_METHOD: str = BASE_URL + '/Market/Index' + def __init__(self, config: dict) -> None: global _API, _API_V2, _EXCHANGE_CONF @@ -98,6 +99,7 @@ class Bittrex(Exchange): 'last': float(data['result']['Last']), } + def get_ticker_history(self, pair: str, tick_interval: int) -> List[Dict]: if tick_interval == 1: interval = 'oneMin' @@ -105,9 +107,27 @@ class Bittrex(Exchange): interval = 'fiveMin' else: raise ValueError('Cannot parse tick_interval: {}'.format(tick_interval)) - - data = _API_V2.get_candles(pair.replace('_', '-'), interval) - + if pair in _cache.keys(): + # pair is in cache retriev lastest candle + sdata = _API_V2.get_latest_candle(pair.replace('_', '-'), interval) + if not sdata.get('result'): + raise ContentDecodingError('{message} params=({pair})'.format( + message='Got invalid response from bittrex', + pair=pair)) + data = _cache[pair] + #this is the latest results we had + old_ticker = data['result'][-1]; + #check timestamp is newer ... + if (sdata['result'][0]['T'] > old_ticker['T']) : + data['result'].append(sdata['result'][0]) + elif (sdata['result'][0]['T'] == old_ticker['T']) : + #if volume has changed, update the latest result with the new one + if (sdata['result'][0]['V'] > old_ticker['V']) : + data['result'][-1] = sdata['result'][0] + else : + data = _API_V2.get_candles(pair.replace('_', '-'), interval) + # Update the value in cache + _cache[pair] = data # These sanity check are necessary because bittrex cannot keep their API stable. if not data.get('result'): raise ContentDecodingError('{message} params=({pair})'.format( From 3c2e3f1f56cb829fcd3239f1e6c7842ec26d8999 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste LE STANG Date: Sun, 31 Dec 2017 14:38:28 +0100 Subject: [PATCH 2/7] Removing old data --- freqtrade/exchange/bittrex.py | 90 +++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 40 deletions(-) diff --git a/freqtrade/exchange/bittrex.py b/freqtrade/exchange/bittrex.py index fbf098767..363b40d03 100644 --- a/freqtrade/exchange/bittrex.py +++ b/freqtrade/exchange/bittrex.py @@ -14,6 +14,7 @@ _API_V2: _Bittrex = None _EXCHANGE_CONF: dict = {} _cache: dict = dict() + class Bittrex(Exchange): """ Bittrex API wrapper. @@ -22,7 +23,6 @@ class Bittrex(Exchange): BASE_URL: str = 'https://www.bittrex.com' PAIR_DETAIL_METHOD: str = BASE_URL + '/Market/Index' - def __init__(self, config: dict) -> None: global _API, _API_V2, _EXCHANGE_CONF @@ -48,21 +48,17 @@ class Bittrex(Exchange): def buy(self, pair: str, rate: float, amount: float) -> str: data = _API.buy_limit(pair.replace('_', '-'), amount, rate) if not data['success']: - raise OperationalException('{message} params=({pair}, {rate}, {amount})'.format( - message=data['message'], - pair=pair, - rate=rate, - amount=amount)) + raise OperationalException( + '{message} params=({pair}, {rate}, {amount})'.format( + message=data['message'], pair=pair, rate=rate, amount=amount)) return data['result']['uuid'] def sell(self, pair: str, rate: float, amount: float) -> str: data = _API.sell_limit(pair.replace('_', '-'), amount, rate) if not data['success']: - raise OperationalException('{message} params=({pair}, {rate}, {amount})'.format( - message=data['message'], - pair=pair, - rate=rate, - amount=amount)) + raise OperationalException( + '{message} params=({pair}, {rate}, {amount})'.format( + message=data['message'], pair=pair, rate=rate, amount=amount)) return data['result']['uuid'] def get_balance(self, currency: str) -> float: @@ -76,7 +72,9 @@ class Bittrex(Exchange): def get_balances(self): data = _API.get_balances() if not data['success']: - raise OperationalException('{message}'.format(message=data['message'])) + raise OperationalException( + '{message}'.format( + message=data['message'])) return data['result'] def get_ticker(self, pair: str) -> dict: @@ -99,36 +97,40 @@ class Bittrex(Exchange): 'last': float(data['result']['Last']), } - def get_ticker_history(self, pair: str, tick_interval: int) -> List[Dict]: if tick_interval == 1: interval = 'oneMin' elif tick_interval == 5: interval = 'fiveMin' else: - raise ValueError('Cannot parse tick_interval: {}'.format(tick_interval)) + raise ValueError( + 'Cannot parse tick_interval: {}'.format(tick_interval)) if pair in _cache.keys(): - # pair is in cache retriev lastest candle - sdata = _API_V2.get_latest_candle(pair.replace('_', '-'), interval) - if not sdata.get('result'): - raise ContentDecodingError('{message} params=({pair})'.format( - message='Got invalid response from bittrex', - pair=pair)) - data = _cache[pair] - #this is the latest results we had - old_ticker = data['result'][-1]; - #check timestamp is newer ... - if (sdata['result'][0]['T'] > old_ticker['T']) : - data['result'].append(sdata['result'][0]) - elif (sdata['result'][0]['T'] == old_ticker['T']) : - #if volume has changed, update the latest result with the new one - if (sdata['result'][0]['V'] > old_ticker['V']) : - data['result'][-1] = sdata['result'][0] - else : - data = _API_V2.get_candles(pair.replace('_', '-'), interval) + # pair is in cache retriev lastest candle + sdata = _API_V2.get_latest_candle(pair.replace('_', '-'), interval) + if not sdata.get('result'): + raise ContentDecodingError('{message} params=({pair})'.format( + message='Got invalid response from bittrex', + pair=pair)) + data = _cache[pair] + # this is the latest results we had + old_ticker = data['result'][-1] + # check timestamp is newer ... + if (sdata['result'][0]['T'] > old_ticker['T']): + data['result'].append(sdata['result'][0]) + # avoid habinf to much data to analyze + data['result'].pop(0) + elif (sdata['result'][0]['T'] == old_ticker['T']): + # if volume has changed, update the latest result with the new + # one + if (sdata['result'][0]['V'] > old_ticker['V']): + data['result'][-1] = sdata['result'][0] + else: + data = _API_V2.get_candles(pair.replace('_', '-'), interval) # Update the value in cache _cache[pair] = data - # These sanity check are necessary because bittrex cannot keep their API stable. + # These sanity check are necessary because bittrex cannot keep their + # API stable. if not data.get('result'): raise ContentDecodingError('{message} params=({pair})'.format( message='Got invalid response from bittrex', @@ -137,9 +139,10 @@ class Bittrex(Exchange): for prop in ['C', 'V', 'O', 'H', 'L', 'T']: for tick in data['result']: if prop not in tick.keys(): - raise ContentDecodingError('{message} params=({pair})'.format( - message='Required property {} not present in response'.format(prop), - pair=pair)) + raise ContentDecodingError( + '{message} params=({pair})'.format( + message='Required property {} not present in response'.format(prop), + pair=pair)) if not data['success']: raise OperationalException('{message} params=({pair})'.format( @@ -174,24 +177,31 @@ class Bittrex(Exchange): order_id=order_id)) def get_pair_detail_url(self, pair: str) -> str: - return self.PAIR_DETAIL_METHOD + '?MarketName={}'.format(pair.replace('_', '-')) + return self.PAIR_DETAIL_METHOD + \ + '?MarketName={}'.format(pair.replace('_', '-')) def get_markets(self) -> List[str]: data = _API.get_markets() if not data['success']: - raise OperationalException('{message}'.format(message=data['message'])) + raise OperationalException( + '{message}'.format( + message=data['message'])) return [m['MarketName'].replace('-', '_') for m in data['result']] def get_market_summaries(self) -> List[Dict]: data = _API.get_market_summaries() if not data['success']: - raise OperationalException('{message}'.format(message=data['message'])) + raise OperationalException( + '{message}'.format( + message=data['message'])) return data['result'] def get_wallet_health(self) -> List[Dict]: data = _API_V2.get_wallet_health() if not data['success']: - raise OperationalException('{message}'.format(message=data['message'])) + raise OperationalException( + '{message}'.format( + message=data['message'])) return [{ 'Currency': entry['Health']['Currency'], 'IsActive': entry['Health']['IsActive'], From f5564a7419a18228179db9e5a84f8291b5c044a8 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste LE STANG Date: Sun, 31 Dec 2017 15:45:13 +0100 Subject: [PATCH 3/7] Fixing a bad merge --- 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 c0048b07d..ec48a046b 100644 --- a/freqtrade/exchange/bittrex.py +++ b/freqtrade/exchange/bittrex.py @@ -94,7 +94,7 @@ class Bittrex(Exchange): data = _API.get_balances() if not data['success']: Bittrex._validate_response(data) - raise OperationalException('{message}'.format(message=data['message']) + raise OperationalException('{message}'.format(message=data['message'])) return data['result'] From 644d846b79aff780f97dc9a22b01b29507b46966 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste LE STANG Date: Sun, 31 Dec 2017 16:13:59 +0100 Subject: [PATCH 4/7] Let's make flake8 appy --- freqtrade/exchange/bittrex.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/freqtrade/exchange/bittrex.py b/freqtrade/exchange/bittrex.py index ec48a046b..f8a4b563d 100644 --- a/freqtrade/exchange/bittrex.py +++ b/freqtrade/exchange/bittrex.py @@ -88,7 +88,6 @@ class Bittrex(Exchange): message=data['message'], currency=currency)) return float(data['result']['Balance'] or 0.0) - def get_balances(self): data = _API.get_balances() @@ -97,7 +96,6 @@ 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']: From 90ad21cfaf9e80f7d7ae8b53ac60d72231d80cbd Mon Sep 17 00:00:00 2001 From: Jean-Baptiste LE STANG Date: Sun, 31 Dec 2017 17:34:29 +0100 Subject: [PATCH 5/7] Refactoring to overcome flake8 complexity --- freqtrade/exchange/bittrex.py | 51 +++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/freqtrade/exchange/bittrex.py b/freqtrade/exchange/bittrex.py index f8a4b563d..5b3b9c28d 100644 --- a/freqtrade/exchange/bittrex.py +++ b/freqtrade/exchange/bittrex.py @@ -88,7 +88,7 @@ class Bittrex(Exchange): message=data['message'], currency=currency)) return float(data['result']['Balance'] or 0.0) - + def get_balances(self): data = _API.get_balances() if not data['success']: @@ -117,6 +117,28 @@ class Bittrex(Exchange): 'last': float(data['result']['Last']), } + def update_with_latest_ticker(self, pair: str) -> List[Dict]: + # pair is in cache retrieve latest candle + sdata = _API_V2.get_latest_candle(pair.replace('_', '-'), interval) + if not sdata.get('result'): + raise ContentDecodingError('{message} params=({pair})'.format( + message='Got invalid response from bittrex', + pair=pair)) + data = _cache[pair] + # this is the latest results we had + old_ticker = data['result'][-1] + # check timestamp is newer ... + if (sdata['result'][0]['T'] > old_ticker['T']): + data['result'].append(sdata['result'][0]) + # avoid having to much data to analyze + data['result'].pop(0) + elif (sdata['result'][0]['T'] == old_ticker['T']): + # if volume has changed, update the latest result with the new + # one + if (sdata['result'][0]['V'] > old_ticker['V']): + data['result'][-1] = sdata['result'][0] + return data + def get_ticker_history(self, pair: str, tick_interval: int) -> List[Dict]: if tick_interval == 1: interval = 'oneMin' @@ -126,35 +148,18 @@ class Bittrex(Exchange): raise ValueError( 'Cannot parse tick_interval: {}'.format(tick_interval)) if pair in _cache.keys(): - # pair is in cache retriev lastest candle - sdata = _API_V2.get_latest_candle(pair.replace('_', '-'), interval) - if not sdata.get('result'): - raise ContentDecodingError('{message} params=({pair})'.format( - message='Got invalid response from bittrex', - pair=pair)) - data = _cache[pair] - # this is the latest results we had - old_ticker = data['result'][-1] - # check timestamp is newer ... - if (sdata['result'][0]['T'] > old_ticker['T']): - data['result'].append(sdata['result'][0]) - # avoid habinf to much data to analyze - data['result'].pop(0) - elif (sdata['result'][0]['T'] == old_ticker['T']): - # if volume has changed, update the latest result with the new - # one - if (sdata['result'][0]['V'] > old_ticker['V']): - data['result'][-1] = sdata['result'][0] + _cache[pair] = self.update_with_latest_ticker(pair) else: - data = _API_V2.get_candles(pair.replace('_', '-'), interval) - # Update the value in cache - _cache[pair] = data + _cache[pair] = _API_V2.get_candles(pair.replace('_', '-'), interval) # These sanity check are necessary because bittrex cannot keep their # API stable. if not data.get('result'): raise ContentDecodingError('{message} params=({pair})'.format( message='Got invalid response from bittrex', pair=pair)) + + # Update the value in cache + _cache[pair] = data for prop in ['C', 'V', 'O', 'H', 'L', 'T']: for tick in data['result']: From ce260e649384b78d327107eaf1a91f3287f475ea Mon Sep 17 00:00:00 2001 From: Jean-Baptiste LE STANG Date: Sun, 31 Dec 2017 17:43:16 +0100 Subject: [PATCH 6/7] errfff ... no comment --- freqtrade/exchange/bittrex.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/bittrex.py b/freqtrade/exchange/bittrex.py index 5b3b9c28d..b765dfb61 100644 --- a/freqtrade/exchange/bittrex.py +++ b/freqtrade/exchange/bittrex.py @@ -148,9 +148,9 @@ class Bittrex(Exchange): raise ValueError( 'Cannot parse tick_interval: {}'.format(tick_interval)) if pair in _cache.keys(): - _cache[pair] = self.update_with_latest_ticker(pair) + data = self.update_with_latest_ticker(pair) else: - _cache[pair] = _API_V2.get_candles(pair.replace('_', '-'), interval) + data = _API_V2.get_candles(pair.replace('_', '-'), interval) # These sanity check are necessary because bittrex cannot keep their # API stable. if not data.get('result'): From c2bc297e1462fdedb10dd2fb323ea649bdc85f0f Mon Sep 17 00:00:00 2001 From: Jean-Baptiste LE STANG Date: Sun, 31 Dec 2017 17:46:03 +0100 Subject: [PATCH 7/7] time to do something else ... --- freqtrade/exchange/bittrex.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/bittrex.py b/freqtrade/exchange/bittrex.py index b765dfb61..d66233125 100644 --- a/freqtrade/exchange/bittrex.py +++ b/freqtrade/exchange/bittrex.py @@ -117,7 +117,7 @@ class Bittrex(Exchange): 'last': float(data['result']['Last']), } - def update_with_latest_ticker(self, pair: str) -> List[Dict]: + def update_with_latest_ticker(self, pair: str, interval) -> List[Dict]: # pair is in cache retrieve latest candle sdata = _API_V2.get_latest_candle(pair.replace('_', '-'), interval) if not sdata.get('result'): @@ -148,7 +148,7 @@ class Bittrex(Exchange): raise ValueError( 'Cannot parse tick_interval: {}'.format(tick_interval)) if pair in _cache.keys(): - data = self.update_with_latest_ticker(pair) + data = self.update_with_latest_ticker(pair, interval) else: data = _API_V2.get_candles(pair.replace('_', '-'), interval) # These sanity check are necessary because bittrex cannot keep their