From fa1f9bcdbd697274c50489a06c7c42e586db4999 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Jan 2020 14:37:45 +0100 Subject: [PATCH 1/9] expose precisionMode from exchange class --- freqtrade/exchange/exchange.py | 5 +++++ tests/conftest.py | 1 + 2 files changed, 6 insertions(+) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 3ef32db62..f29bc3c6f 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -188,6 +188,11 @@ class Exchange: self._load_markets() return self._api.markets + @property + def precisionMode(self) -> str: + """exchange ccxt precisionMode""" + return self._api.precisionMode + def get_markets(self, base_currencies: List[str] = None, quote_currencies: List[str] = None, pairs_only: bool = False, active_only: bool = False) -> Dict: """ diff --git a/tests/conftest.py b/tests/conftest.py index 7bb4cf4c9..ec53141c1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -62,6 +62,7 @@ def patch_exchange(mocker, api_mock=None, id='bittrex', mock_markets=True) -> No mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value=id)) mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value=id.title())) + mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=2)) if mock_markets: mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=get_markets())) From b60d7ad42fde62614c934abf14931b1103b04962 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Jan 2020 14:40:58 +0100 Subject: [PATCH 2/9] Use ccxt.decimal_to_precision instead of our own calculation --- freqtrade/exchange/exchange.py | 24 ++++++++++++++---------- tests/exchange/test_exchange.py | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index f29bc3c6f..e1b5c411d 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -7,14 +7,15 @@ import inspect import logging from copy import deepcopy from datetime import datetime, timezone -from math import ceil, floor +from math import ceil from random import randint from typing import Any, Dict, List, Optional, Tuple import arrow import ccxt import ccxt.async_support as ccxt_async -from ccxt.base.decimal_to_precision import ROUND_DOWN, ROUND_UP +from ccxt.base.decimal_to_precision import (ROUND, ROUND_DOWN, ROUND_UP, + TRUNCATE, decimal_to_precision) from pandas import DataFrame from freqtrade.data.converter import parse_ticker_dataframe @@ -367,26 +368,29 @@ class Exchange: """ return endpoint in self._api.has and self._api.has[endpoint] - def symbol_amount_prec(self, pair, amount: float): + def symbol_amount_prec(self, pair, amount: float) -> float: ''' Returns the amount to buy or sell to a precision the Exchange accepts Rounded down ''' if self.markets[pair]['precision']['amount']: - symbol_prec = self.markets[pair]['precision']['amount'] - big_amount = amount * pow(10, symbol_prec) - amount = floor(big_amount) / pow(10, symbol_prec) + amount = float(decimal_to_precision(amount, rounding_mode=TRUNCATE, + precision=self.markets[pair]['precision']['amount'], + counting_mode=self.precisionMode, + )) + return amount - def symbol_price_prec(self, pair, price: float): + def symbol_price_prec(self, pair, price: float) -> float: ''' Returns the price buying or selling with to the precision the Exchange accepts Rounds up ''' if self.markets[pair]['precision']['price']: - symbol_prec = self.markets[pair]['precision']['price'] - big_price = price * pow(10, symbol_prec) - price = ceil(big_price) / pow(10, symbol_prec) + price = float(decimal_to_precision(price, rounding_mode=ROUND, + precision=self.markets[pair]['precision']['price'], + counting_mode=self.precisionMode, + )) return price def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index cb40bdbd9..b90e94547 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -181,6 +181,11 @@ def test_symbol_amount_prec(default_conf, mocker): markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': 4}}}) exchange = get_patched_exchange(mocker, default_conf, id="binance") + # digits counting mode + # DECIMAL_PLACES = 2 + # SIGNIFICANT_DIGITS = 3 + # TICK_SIZE = 4 + mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=2)) mocker.patch('freqtrade.exchange.Exchange.markets', markets) amount = 2.34559 @@ -188,6 +193,12 @@ def test_symbol_amount_prec(default_conf, mocker): amount = exchange.symbol_amount_prec(pair, amount) assert amount == 2.3455 + markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': 0.0001}}}) + mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=4)) + mocker.patch('freqtrade.exchange.Exchange.markets', markets) + amount = exchange.symbol_amount_prec(pair, amount) + assert amount == 2.3455 + def test_symbol_price_prec(default_conf, mocker): ''' @@ -197,12 +208,20 @@ def test_symbol_price_prec(default_conf, mocker): exchange = get_patched_exchange(mocker, default_conf, id="binance") mocker.patch('freqtrade.exchange.Exchange.markets', markets) + mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=2)) price = 2.34559 pair = 'ETH/BTC' price = exchange.symbol_price_prec(pair, price) assert price == 2.3456 + markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': 0.0001}}}) + mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=4)) + mocker.patch('freqtrade.exchange.Exchange.markets', markets) + + price = exchange.symbol_price_prec(pair, price) + assert price == 2.3456 + def test_set_sandbox(default_conf, mocker): """ From 5fcab1eee8efce5bfa17d43f70542da65dcc4449 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 12 Jan 2020 14:55:05 +0100 Subject: [PATCH 3/9] Align method names to internal ccxt names These methods are reimplemented from ccxt so we can test their behaviour. --- freqtrade/exchange/binance.py | 6 ++--- freqtrade/exchange/exchange.py | 19 ++++++++-------- freqtrade/pairlist/PrecisionFilter.py | 4 ++-- tests/exchange/test_binance.py | 8 +++---- tests/exchange/test_exchange.py | 32 +++++++++++++-------------- tests/exchange/test_kraken.py | 8 +++---- tests/test_freqtradebot.py | 8 +++---- tests/test_integration.py | 8 +++---- 8 files changed, 47 insertions(+), 46 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 96f72fcf5..12326f083 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -41,7 +41,7 @@ class Binance(Exchange): """ ordertype = "stop_loss_limit" - stop_price = self.symbol_price_prec(pair, stop_price) + stop_price = self.price_to_precision(pair, stop_price) # Ensure rate is less than stop price if stop_price <= rate: @@ -57,9 +57,9 @@ class Binance(Exchange): params = self._params.copy() params.update({'stopPrice': stop_price}) - amount = self.symbol_amount_prec(pair, amount) + amount = self.amount_to_precision(pair, amount) - rate = self.symbol_price_prec(pair, rate) + rate = self.price_to_precision(pair, rate) order = self._api.create_order(pair, ordertype, 'sell', amount, rate, params) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index e1b5c411d..dd6f257fe 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -7,7 +7,6 @@ import inspect import logging from copy import deepcopy from datetime import datetime, timezone -from math import ceil from random import randint from typing import Any, Dict, List, Optional, Tuple @@ -368,10 +367,11 @@ class Exchange: """ return endpoint in self._api.has and self._api.has[endpoint] - def symbol_amount_prec(self, pair, amount: float) -> float: + def amount_to_precision(self, pair, amount: float) -> float: ''' Returns the amount to buy or sell to a precision the Exchange accepts - Rounded down + Reimplementation of ccxt internal methods - ensuring we can test the result is correct + based on our definitions. ''' if self.markets[pair]['precision']['amount']: amount = float(decimal_to_precision(amount, rounding_mode=TRUNCATE, @@ -381,10 +381,11 @@ class Exchange: return amount - def symbol_price_prec(self, pair, price: float) -> float: + def price_to_precision(self, pair, price: float) -> float: ''' Returns the price buying or selling with to the precision the Exchange accepts - Rounds up + Reimplementation of ccxt internal methods - ensuring we can test the result is correct + based on our definitions. ''' if self.markets[pair]['precision']['price']: price = float(decimal_to_precision(price, rounding_mode=ROUND, @@ -396,7 +397,7 @@ class Exchange: def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, rate: float, params: Dict = {}) -> Dict[str, Any]: order_id = f'dry_run_{side}_{randint(0, 10**6)}' - _amount = self.symbol_amount_prec(pair, amount) + _amount = self.amount_to_precision(pair, amount) dry_order = { "id": order_id, 'pair': pair, @@ -431,13 +432,13 @@ class Exchange: rate: float, params: Dict = {}) -> Dict: try: # Set the precision for amount and price(rate) as accepted by the exchange - amount = self.symbol_amount_prec(pair, amount) + amount = self.amount_to_precision(pair, amount) needs_price = (ordertype != 'market' or self._api.options.get("createMarketBuyOrderRequiresPrice", False)) - rate = self.symbol_price_prec(pair, rate) if needs_price else None + rate_for_order = self.price_to_precision(pair, rate) if needs_price else None return self._api.create_order(pair, ordertype, side, - amount, rate, params) + amount, rate_for_order, params) except ccxt.InsufficientFunds as e: raise DependencyException( diff --git a/freqtrade/pairlist/PrecisionFilter.py b/freqtrade/pairlist/PrecisionFilter.py index aedcc5a88..5d364795d 100644 --- a/freqtrade/pairlist/PrecisionFilter.py +++ b/freqtrade/pairlist/PrecisionFilter.py @@ -35,8 +35,8 @@ class PrecisionFilter(IPairList): """ stop_price = ticker['ask'] * stoploss # Adjust stop-prices to precision - sp = self._exchange.symbol_price_prec(ticker["symbol"], stop_price) - stop_gap_price = self._exchange.symbol_price_prec(ticker["symbol"], stop_price * 0.99) + sp = self._exchange.price_to_precision(ticker["symbol"], stop_price) + stop_gap_price = self._exchange.price_to_precision(ticker["symbol"], stop_price * 0.99) logger.debug(f"{ticker['symbol']} - {sp} : {stop_gap_price}") if sp <= stop_gap_price: logger.info(f"Removed {ticker['symbol']} from whitelist, " diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index 0a12c1cb1..4bc918c3d 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -22,8 +22,8 @@ def test_stoploss_limit_order(default_conf, mocker): }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') @@ -71,8 +71,8 @@ def test_stoploss_limit_order_dry_run(default_conf, mocker): api_mock = MagicMock() order_type = 'stop_loss_limit' default_conf['dry_run'] = True - mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index b90e94547..d666d29ac 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -173,7 +173,7 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog): ex.validate_order_time_in_force(tif2) -def test_symbol_amount_prec(default_conf, mocker): +def test_amount_to_precision(default_conf, mocker): ''' Test rounds down to 4 Decimal places ''' @@ -190,17 +190,17 @@ def test_symbol_amount_prec(default_conf, mocker): amount = 2.34559 pair = 'ETH/BTC' - amount = exchange.symbol_amount_prec(pair, amount) + amount = exchange.amount_to_precision(pair, amount) assert amount == 2.3455 markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': 0.0001}}}) mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=4)) mocker.patch('freqtrade.exchange.Exchange.markets', markets) - amount = exchange.symbol_amount_prec(pair, amount) + amount = exchange.amount_to_precision(pair, amount) assert amount == 2.3455 -def test_symbol_price_prec(default_conf, mocker): +def test_sprice_to_precision(default_conf, mocker): ''' Test rounds up to 4 decimal places ''' @@ -212,14 +212,14 @@ def test_symbol_price_prec(default_conf, mocker): price = 2.34559 pair = 'ETH/BTC' - price = exchange.symbol_price_prec(pair, price) + price = exchange.price_to_precision(pair, price) assert price == 2.3456 markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': 0.0001}}}) mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=4)) mocker.patch('freqtrade.exchange.Exchange.markets', markets) - price = exchange.symbol_price_prec(pair, price) + price = exchange.price_to_precision(pair, price) assert price == 2.3456 @@ -615,8 +615,8 @@ def test_create_order(default_conf, mocker, side, ordertype, rate, marketprice, } }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.create_order( @@ -656,8 +656,8 @@ def test_buy_prod(default_conf, mocker, exchange_name): } }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.buy(pair='ETH/BTC', ordertype=order_type, @@ -730,8 +730,8 @@ def test_buy_considers_time_in_force(default_conf, mocker, exchange_name): } }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order_type = 'limit' @@ -792,8 +792,8 @@ def test_sell_prod(default_conf, mocker, exchange_name): }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) @@ -856,8 +856,8 @@ def test_sell_considers_time_in_force(default_conf, mocker, exchange_name): }) api_mock.options = {} default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) order_type = 'limit' diff --git a/tests/exchange/test_kraken.py b/tests/exchange/test_kraken.py index 3ad62d85a..8490ee1a2 100644 --- a/tests/exchange/test_kraken.py +++ b/tests/exchange/test_kraken.py @@ -21,8 +21,8 @@ def test_buy_kraken_trading_agreement(default_conf, mocker): }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") order = exchange.buy(pair='ETH/BTC', ordertype=order_type, @@ -53,8 +53,8 @@ def test_sell_kraken_trading_agreement(default_conf, mocker): }) default_conf['dry_run'] = False - mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) - mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.amount_to_precision', lambda s, x, y: y) + mocker.patch('freqtrade.exchange.Exchange.price_to_precision', lambda s, x, y: y) exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 58f88198a..5267a267b 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2435,8 +2435,8 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke 'freqtrade.exchange.Exchange', fetch_ticker=ticker, get_fee=fee, - symbol_amount_prec=lambda s, x, y: y, - symbol_price_prec=lambda s, x, y: y, + amount_to_precision=lambda s, x, y: y, + price_to_precision=lambda s, x, y: y, stoploss_limit=stoploss_limit, cancel_order=cancel_order, ) @@ -2478,8 +2478,8 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, f 'freqtrade.exchange.Exchange', fetch_ticker=ticker, get_fee=fee, - symbol_amount_prec=lambda s, x, y: y, - symbol_price_prec=lambda s, x, y: y, + amount_to_precision=lambda s, x, y: y, + price_to_precision=lambda s, x, y: y, ) stoploss_limit = MagicMock(return_value={ diff --git a/tests/test_integration.py b/tests/test_integration.py index 98bf1862b..2b4a1b946 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -58,8 +58,8 @@ def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee, 'freqtrade.exchange.Exchange', fetch_ticker=ticker, get_fee=fee, - symbol_amount_prec=lambda s, x, y: y, - symbol_price_prec=lambda s, x, y: y, + amount_to_precision=lambda s, x, y: y, + price_to_precision=lambda s, x, y: y, get_order=stoploss_order_mock, cancel_order=cancel_order_mock, ) @@ -137,8 +137,8 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, limit_buy_order, moc 'freqtrade.exchange.Exchange', fetch_ticker=ticker, get_fee=fee, - symbol_amount_prec=lambda s, x, y: y, - symbol_price_prec=lambda s, x, y: y, + amount_to_precision=lambda s, x, y: y, + price_to_precision=lambda s, x, y: y, ) mocker.patch.multiple( From 797dc8a4dacf526e859af144d501d1732f935ca5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 14 Jan 2020 15:54:53 +0100 Subject: [PATCH 4/9] Add more detailed tests for amount_to_precision --- tests/exchange/test_exchange.py | 45 ++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index d666d29ac..379e7d091 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -173,12 +173,19 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog): ex.validate_order_time_in_force(tif2) -def test_amount_to_precision(default_conf, mocker): +@pytest.mark.parametrize("amount,precision,expected", [ + (2.34559, 4, 2.3455), + (2.34559, 5, 2.34559), + (2.34559, 3, 2.345), + (2.9999, 3, 2.999), + (2.9909, 3, 2.990), +]) +def test_amount_to_precision_decimal_places(default_conf, mocker, amount, precision, expected): ''' - Test rounds down to 4 Decimal places + Test rounds down ''' - markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': 4}}}) + markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': precision}}}) exchange = get_patched_exchange(mocker, default_conf, id="binance") # digits counting mode @@ -188,16 +195,36 @@ def test_amount_to_precision(default_conf, mocker): mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=2)) mocker.patch('freqtrade.exchange.Exchange.markets', markets) - amount = 2.34559 pair = 'ETH/BTC' - amount = exchange.amount_to_precision(pair, amount) - assert amount == 2.3455 + assert exchange.amount_to_precision(pair, amount) == expected - markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': 0.0001}}}) + +@pytest.mark.parametrize("amount,precision,expected", [ + (2.34559, 0.0001, 2.3455), + (2.34559, 0.00001, 2.34559), + (2.34559, 0.001, 2.345), + (2.9999, 0.001, 2.999), + (2.9909, 0.001, 2.990), + (2.9909, 0.005, 2.990), + (2.9999, 0.005, 2.995), +]) +def test_amount_to_precision_tick_size(default_conf, mocker, amount, precision, expected): + ''' + Test rounds down + ''' + + markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': precision}}}) + + exchange = get_patched_exchange(mocker, default_conf, id="binance") + # digits counting mode + # DECIMAL_PLACES = 2 + # SIGNIFICANT_DIGITS = 3 + # TICK_SIZE = 4 mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=4)) mocker.patch('freqtrade.exchange.Exchange.markets', markets) - amount = exchange.amount_to_precision(pair, amount) - assert amount == 2.3455 + + pair = 'ETH/BTC' + assert exchange.amount_to_precision(pair, amount) == expected def test_sprice_to_precision(default_conf, mocker): From 425ec53b28ec7d4b463f2f36b6b6c97fa10a99e8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 14 Jan 2020 16:01:35 +0100 Subject: [PATCH 5/9] Combine amount_to_precision tests into one --- tests/exchange/test_exchange.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 379e7d091..a131aaab5 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -173,14 +173,22 @@ def test_validate_order_time_in_force(default_conf, mocker, caplog): ex.validate_order_time_in_force(tif2) -@pytest.mark.parametrize("amount,precision,expected", [ - (2.34559, 4, 2.3455), - (2.34559, 5, 2.34559), - (2.34559, 3, 2.345), - (2.9999, 3, 2.999), - (2.9909, 3, 2.990), +@pytest.mark.parametrize("amount,precision_mode,precision,expected", [ + (2.34559, 2, 4, 2.3455), + (2.34559, 2, 5, 2.34559), + (2.34559, 2, 3, 2.345), + (2.9999, 2, 3, 2.999), + (2.9909, 2, 3, 2.990), + # Tests for Tick-size + (2.34559, 4, 0.0001, 2.3455), + (2.34559, 4, 0.00001, 2.34559), + (2.34559, 4, 0.001, 2.345), + (2.9999, 4, 0.001, 2.999), + (2.9909, 4, 0.001, 2.990), + (2.9909, 4, 0.005, 2.990), + (2.9999, 4, 0.005, 2.995), ]) -def test_amount_to_precision_decimal_places(default_conf, mocker, amount, precision, expected): +def test_amount_to_precision(default_conf, mocker, amount, precision_mode, precision, expected): ''' Test rounds down ''' @@ -192,7 +200,8 @@ def test_amount_to_precision_decimal_places(default_conf, mocker, amount, precis # DECIMAL_PLACES = 2 # SIGNIFICANT_DIGITS = 3 # TICK_SIZE = 4 - mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=2)) + mocker.patch('freqtrade.exchange.Exchange.precisionMode', + PropertyMock(return_value=precision_mode)) mocker.patch('freqtrade.exchange.Exchange.markets', markets) pair = 'ETH/BTC' From d7957bd7916b4a72e24c94018f0dd0aede45a9f9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 14 Jan 2020 16:04:39 +0100 Subject: [PATCH 6/9] add advanced tests for price_to_precision --- tests/exchange/test_exchange.py | 60 ++++++++++++--------------------- 1 file changed, 22 insertions(+), 38 deletions(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index a131aaab5..04cf2ff22 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -208,55 +208,39 @@ def test_amount_to_precision(default_conf, mocker, amount, precision_mode, preci assert exchange.amount_to_precision(pair, amount) == expected -@pytest.mark.parametrize("amount,precision,expected", [ - (2.34559, 0.0001, 2.3455), - (2.34559, 0.00001, 2.34559), - (2.34559, 0.001, 2.345), - (2.9999, 0.001, 2.999), - (2.9909, 0.001, 2.990), - (2.9909, 0.005, 2.990), - (2.9999, 0.005, 2.995), +@pytest.mark.parametrize("price,precision_mode,precision,expected", [ + (2.34559, 2, 4, 2.3456), + (2.34559, 2, 5, 2.34559), + (2.34559, 2, 3, 2.346), + (2.9999, 2, 3, 3.000), + (2.9909, 2, 3, 2.991), + # Tests for Tick_size + (2.34559, 4, 0.0001, 2.3456), + (2.34559, 4, 0.00001, 2.34559), + (2.34559, 4, 0.001, 2.346), + (2.9999, 4, 0.001, 3.000), + (2.9909, 4, 0.001, 2.991), + (2.9909, 4, 0.005, 2.99), + (2.9973, 4, 0.005, 2.995), + (2.9977, 4, 0.005, 3.0), ]) -def test_amount_to_precision_tick_size(default_conf, mocker, amount, precision, expected): +def test_price_to_precision(default_conf, mocker, price, precision_mode, precision, expected): ''' - Test rounds down + Test price to precision ''' - - markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'amount': precision}}}) + markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': precision}}}) exchange = get_patched_exchange(mocker, default_conf, id="binance") + mocker.patch('freqtrade.exchange.Exchange.markets', markets) # digits counting mode # DECIMAL_PLACES = 2 # SIGNIFICANT_DIGITS = 3 # TICK_SIZE = 4 - mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=4)) - mocker.patch('freqtrade.exchange.Exchange.markets', markets) + mocker.patch('freqtrade.exchange.Exchange.precisionMode', + PropertyMock(return_value=precision_mode)) pair = 'ETH/BTC' - assert exchange.amount_to_precision(pair, amount) == expected - - -def test_sprice_to_precision(default_conf, mocker): - ''' - Test rounds up to 4 decimal places - ''' - markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': 4}}}) - - exchange = get_patched_exchange(mocker, default_conf, id="binance") - mocker.patch('freqtrade.exchange.Exchange.markets', markets) - mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=2)) - - price = 2.34559 - pair = 'ETH/BTC' - price = exchange.price_to_precision(pair, price) - assert price == 2.3456 - - markets = PropertyMock(return_value={'ETH/BTC': {'precision': {'price': 0.0001}}}) - mocker.patch('freqtrade.exchange.Exchange.precisionMode', PropertyMock(return_value=4)) - mocker.patch('freqtrade.exchange.Exchange.markets', markets) - - price = exchange.price_to_precision(pair, price) - assert price == 2.3456 + assert exchange.price_to_precision(pair, price) == expected def test_set_sandbox(default_conf, mocker): From bea4ad8effa97626de4ae51d7f0e6c06c963a00f Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 14 Jan 2020 20:16:20 +0100 Subject: [PATCH 7/9] Revert price_to_precision to rounding up --- freqtrade/exchange/exchange.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index dd6f257fe..a7243d4d2 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -7,6 +7,7 @@ import inspect import logging from copy import deepcopy from datetime import datetime, timezone +from math import ceil from random import randint from typing import Any, Dict, List, Optional, Tuple @@ -14,7 +15,7 @@ import arrow import ccxt import ccxt.async_support as ccxt_async from ccxt.base.decimal_to_precision import (ROUND, ROUND_DOWN, ROUND_UP, - TRUNCATE, decimal_to_precision) + TRUNCATE, TICK_SIZE, decimal_to_precision) from pandas import DataFrame from freqtrade.data.converter import parse_ticker_dataframe @@ -383,15 +384,27 @@ class Exchange: def price_to_precision(self, pair, price: float) -> float: ''' - Returns the price buying or selling with to the precision the Exchange accepts - Reimplementation of ccxt internal methods - ensuring we can test the result is correct - based on our definitions. + Returns the price rounded up to the precision the Exchange accepts. + Partial Reimplementation of ccxt internal method decimal_to_precision(), + which does not support rounding up + TODO: If ccxt supports ROUND_UP for decimal_to_precision(), we could remove this and + align with amount_to_precision(). + Rounds up ''' if self.markets[pair]['precision']['price']: - price = float(decimal_to_precision(price, rounding_mode=ROUND, - precision=self.markets[pair]['precision']['price'], - counting_mode=self.precisionMode, - )) + # price = float(decimal_to_precision(price, rounding_mode=ROUND, + # precision=self.markets[pair]['precision']['price'], + # counting_mode=self.precisionMode, + # )) + if self.precisionMode == TICK_SIZE: + precision = self.markets[pair]['precision']['price'] + missing = price % precision + if missing != 0: + price = price - missing + precision + else: + symbol_prec = self.markets[pair]['precision']['price'] + big_price = price * pow(10, symbol_prec) + price = ceil(big_price) / pow(10, symbol_prec) return price def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, From 1e58cd70ad1253784a3f6d76997cf090c54362ab Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 14 Jan 2020 20:16:47 +0100 Subject: [PATCH 8/9] Adapt tests to round price up --- tests/exchange/test_exchange.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 04cf2ff22..c3cf7ae58 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -220,9 +220,13 @@ def test_amount_to_precision(default_conf, mocker, amount, precision_mode, preci (2.34559, 4, 0.001, 2.346), (2.9999, 4, 0.001, 3.000), (2.9909, 4, 0.001, 2.991), - (2.9909, 4, 0.005, 2.99), - (2.9973, 4, 0.005, 2.995), + (2.9909, 4, 0.005, 2.995), + (2.9973, 4, 0.005, 3.0), (2.9977, 4, 0.005, 3.0), + (234.43, 4, 0.5, 234.5), + (234.53, 4, 0.5, 235.0), + (0.891534, 4, 0.0001, 0.8916), + ]) def test_price_to_precision(default_conf, mocker, price, precision_mode, precision, expected): ''' @@ -240,7 +244,7 @@ def test_price_to_precision(default_conf, mocker, price, precision_mode, precisi PropertyMock(return_value=precision_mode)) pair = 'ETH/BTC' - assert exchange.price_to_precision(pair, price) == expected + assert pytest.approx(exchange.price_to_precision(pair, price)) == expected def test_set_sandbox(default_conf, mocker): From 4c823f12e3d740e274f80cbff4e6f37ef9e676ac Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 14 Jan 2020 20:25:58 +0100 Subject: [PATCH 9/9] Sort imports --- freqtrade/exchange/exchange.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index a7243d4d2..4c5c9194c 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -14,8 +14,8 @@ from typing import Any, Dict, List, Optional, Tuple import arrow import ccxt import ccxt.async_support as ccxt_async -from ccxt.base.decimal_to_precision import (ROUND, ROUND_DOWN, ROUND_UP, - TRUNCATE, TICK_SIZE, decimal_to_precision) +from ccxt.base.decimal_to_precision import (ROUND_DOWN, ROUND_UP, TICK_SIZE, + TRUNCATE, decimal_to_precision) from pandas import DataFrame from freqtrade.data.converter import parse_ticker_dataframe