From df726a54f89af8f99cb5dd3969ba280187d22d58 Mon Sep 17 00:00:00 2001 From: Stefano Ariestasia Date: Fri, 25 Feb 2022 00:20:53 +0000 Subject: [PATCH 001/114] cater for case where sell limit order expired --- freqtrade/rpc/telegram.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index da613fab8..e71b5a9b9 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -448,7 +448,8 @@ class Telegram(RPCHandler): "*Current Pair:* {pair}", "*Amount:* `{amount} ({stake_amount} {base_currency})`", "*Entry Tag:* `{buy_tag}`" if r['buy_tag'] else "", - "*Exit Reason:* `{sell_reason}`" if r['sell_reason'] else "", + "*Exit Reason:* `{sell_reason}`" + if (r['sell_reason'] and not r['is_open']) else "", ] if position_adjust: From 1d57ce19ebf3ba7be55aaebecb7db9db8d00c4cb Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 2 Feb 2022 19:45:49 +0100 Subject: [PATCH 002/114] Move stoploss -limit implemenentation to exchange class, as this seems to be used by multiple exchanges. --- freqtrade/exchange/binance.py | 64 +----------------------------- freqtrade/exchange/exchange.py | 71 +++++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 68 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 4ba30b626..a195788dd 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -3,12 +3,8 @@ import logging from typing import Dict, List, Tuple import arrow -import ccxt -from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException, - OperationalException, TemporaryError) from freqtrade.exchange import Exchange -from freqtrade.exchange.common import retrier logger = logging.getLogger(__name__) @@ -18,6 +14,7 @@ class Binance(Exchange): _ft_has: Dict = { "stoploss_on_exchange": True, + "stoploss_order_type": "stop_loss_limit", "order_time_in_force": ['gtc', 'fok', 'ioc'], "time_in_force_parameter": "timeInForce", "ohlcv_candle_limit": 1000, @@ -33,65 +30,6 @@ class Binance(Exchange): """ return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice']) - @retrier(retries=0) - def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: - """ - creates a stoploss limit order. - this stoploss-limit is binance-specific. - It may work with a limited number of other exchanges, but this has not been tested yet. - """ - # Limit price threshold: As limit price should always be below stop-price - limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) - rate = stop_price * limit_price_pct - - ordertype = "stop_loss_limit" - - stop_price = self.price_to_precision(pair, stop_price) - - # Ensure rate is less than stop price - if stop_price <= rate: - raise OperationalException( - 'In stoploss limit order, stop price should be more than limit price') - - if self._config['dry_run']: - dry_order = self.create_dry_run_order( - pair, ordertype, "sell", amount, stop_price) - return dry_order - - try: - params = self._params.copy() - params.update({'stopPrice': stop_price}) - - amount = self.amount_to_precision(pair, amount) - - rate = self.price_to_precision(pair, rate) - - order = self._api.create_order(symbol=pair, type=ordertype, side='sell', - amount=amount, price=rate, params=params) - logger.info('stoploss limit order added for %s. ' - 'stop price: %s. limit: %s', pair, stop_price, rate) - self._log_exchange_response('create_stoploss_order', order) - return order - except ccxt.InsufficientFunds as e: - raise InsufficientFundsError( - f'Insufficient funds to create {ordertype} sell order on market {pair}. ' - f'Tried to sell amount {amount} at rate {rate}. ' - f'Message: {e}') from e - except ccxt.InvalidOrder as e: - # Errors: - # `binance Order would trigger immediately.` - raise InvalidOrderException( - f'Could not create {ordertype} sell order on market {pair}. ' - f'Tried to sell amount {amount} at rate {rate}. ' - f'Message: {e}') from e - except ccxt.DDoSProtection as e: - raise DDosProtection(e) from e - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e - except ccxt.BaseError as e: - raise OperationalException(e) from e - async def _async_get_historic_ohlcv(self, pair: str, timeframe: str, since_ms: int, is_new_pair: bool = False, raise_: bool = False diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index a2217a02e..cd4c2ce83 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -791,18 +791,79 @@ class Exchange: """ raise OperationalException(f"stoploss is not implemented for {self.name}.") + @retrier(retries=0) def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: """ creates a stoploss order. + creates a stoploss limit order. + Should an exchange support more ordertypes, the exchange should implement this method, + using `order_types.get('stoploss', 'market')` to get the correct ordertype (e.g. FTX). + The precise ordertype is determined by the order_types dict or exchange default. - Since ccxt does not unify stoploss-limit orders yet, this needs to be implemented in each - exchange's subclass. + The exception below should never raise, since we disallow starting the bot in validate_ordertypes() - Note: Changes to this interface need to be applied to all sub-classes too. - """ - raise OperationalException(f"stoploss is not implemented for {self.name}.") + This may work with a limited number of other exchanges, but correct working + needs to be tested individually. + WARNING: setting `stoploss_on_exchange` to True will NOT auto-enable stoploss on exchange. + `stoploss_adjust` must still be implemented for this to work. + """ + if not self._ft_has['stoploss_on_exchange']: + raise OperationalException(f"stoploss is not implemented for {self.name}.") + + # Limit price threshold: As limit price should always be below stop-price + limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) + rate = stop_price * limit_price_pct + + ordertype = self._ft_has["stoploss_order_type"] + + stop_price = self.price_to_precision(pair, stop_price) + + # Ensure rate is less than stop price + if stop_price <= rate: + raise OperationalException( + 'In stoploss limit order, stop price should be more than limit price') + + if self._config['dry_run']: + dry_order = self.create_dry_run_order( + pair, ordertype, "sell", amount, stop_price) + return dry_order + + try: + params = self._params.copy() + # Verify if stopPrice works for your exchange! + params.update({'stopPrice': stop_price}) + + amount = self.amount_to_precision(pair, amount) + + rate = self.price_to_precision(pair, rate) + + order = self._api.create_order(symbol=pair, type=ordertype, side='sell', + amount=amount, price=rate, params=params) + logger.info(f"stoploss limit order added for {pair}. " + f"stop price: {stop_price}. limit: {rate}") + self._log_exchange_response('create_stoploss_order', order) + return order + except ccxt.InsufficientFunds as e: + raise InsufficientFundsError( + f'Insufficient funds to create {ordertype} sell order on market {pair}. ' + f'Tried to sell amount {amount} at rate {rate}. ' + f'Message: {e}') from e + except ccxt.InvalidOrder as e: + # Errors: + # `Order would trigger immediately.` + raise InvalidOrderException( + f'Could not create {ordertype} sell order on market {pair}. ' + f'Tried to sell amount {amount} at rate {rate}. ' + f'Message: {e}') from e + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e @retrier(retries=API_FETCH_ORDER_RETRY_COUNT) def fetch_order(self, order_id: str, pair: str) -> Dict: From ea197b79caaceb7f4f72ff9cfe2b2e069e4a16e2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 2 Feb 2022 20:40:40 +0100 Subject: [PATCH 003/114] Add some more logic to stoploss --- freqtrade/exchange/binance.py | 2 +- freqtrade/exchange/exchange.py | 28 +++++++++++++++++++--------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index a195788dd..37ead6dd8 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -14,7 +14,7 @@ class Binance(Exchange): _ft_has: Dict = { "stoploss_on_exchange": True, - "stoploss_order_type": "stop_loss_limit", + "stoploss_order_types": {"limit": "stop_loss_limit"}, "order_time_in_force": ['gtc', 'fok', 'ioc'], "time_in_force_parameter": "timeInForce", "ohlcv_candle_limit": 1000, diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index cd4c2ce83..d8644dcb9 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -791,13 +791,18 @@ class Exchange: """ raise OperationalException(f"stoploss is not implemented for {self.name}.") + def _get_stop_params(self, ordertype: str, stop_price: float) -> Dict: + params = self._params.copy() + # Verify if stopPrice works for your exchange! + params.update({'stopPrice': stop_price}) + return params + @retrier(retries=0) def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: """ creates a stoploss order. - creates a stoploss limit order. - Should an exchange support more ordertypes, the exchange should implement this method, - using `order_types.get('stoploss', 'market')` to get the correct ordertype (e.g. FTX). + requires `_ft_has['stoploss_order_types']` to be set as a dict mapping limit and market + to the corresponding exchange type. The precise ordertype is determined by the order_types dict or exchange default. @@ -812,12 +817,18 @@ class Exchange: if not self._ft_has['stoploss_on_exchange']: raise OperationalException(f"stoploss is not implemented for {self.name}.") - # Limit price threshold: As limit price should always be below stop-price + user_order_type = order_types.get('stoploss', 'market') + if user_order_type in self._ft_has["stoploss_order_types"].keys(): + ordertype = self._ft_has["stoploss_order_types"][user_order_type] + else: + # Otherwise pick only one available + ordertype = list(self._ft_has["stoploss_order_types"].values())[0] + + # if user_order_type == 'limit': + # Limit price threshold: As limit price should always be below stop-price limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) rate = stop_price * limit_price_pct - ordertype = self._ft_has["stoploss_order_type"] - stop_price = self.price_to_precision(pair, stop_price) # Ensure rate is less than stop price @@ -826,14 +837,13 @@ class Exchange: 'In stoploss limit order, stop price should be more than limit price') if self._config['dry_run']: + # TODO: will this work if ordertype is limit?? dry_order = self.create_dry_run_order( pair, ordertype, "sell", amount, stop_price) return dry_order try: - params = self._params.copy() - # Verify if stopPrice works for your exchange! - params.update({'stopPrice': stop_price}) + params = self._get_stop_params(ordertype=ordertype, stop_price=stop_price) amount = self.amount_to_precision(pair, amount) From 7ba92086c96a8b453b881825be1c2f789ff7b8f4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 3 Feb 2022 06:55:58 +0100 Subject: [PATCH 004/114] Make stoploss method more flexible --- freqtrade/exchange/exchange.py | 27 ++++++++++++++------------- freqtrade/freqtradebot.py | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index d8644dcb9..60fd1ded4 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -823,27 +823,28 @@ class Exchange: else: # Otherwise pick only one available ordertype = list(self._ft_has["stoploss_order_types"].values())[0] + user_order_type = list(self._ft_has["stoploss_order_types"].keys())[0] - # if user_order_type == 'limit': + stop_price_norm = self.price_to_precision(pair, stop_price) + rate = None + if user_order_type == 'limit': # Limit price threshold: As limit price should always be below stop-price - limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) - rate = stop_price * limit_price_pct + limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) + rate = stop_price * limit_price_pct - stop_price = self.price_to_precision(pair, stop_price) - - # Ensure rate is less than stop price - if stop_price <= rate: - raise OperationalException( - 'In stoploss limit order, stop price should be more than limit price') + # Ensure rate is less than stop price + if stop_price_norm <= rate: + raise OperationalException( + 'In stoploss limit order, stop price should be more than limit price') if self._config['dry_run']: # TODO: will this work if ordertype is limit?? dry_order = self.create_dry_run_order( - pair, ordertype, "sell", amount, stop_price) + pair, ordertype, "sell", amount, stop_price_norm) return dry_order try: - params = self._get_stop_params(ordertype=ordertype, stop_price=stop_price) + params = self._get_stop_params(ordertype=ordertype, stop_price=stop_price_norm) amount = self.amount_to_precision(pair, amount) @@ -851,7 +852,7 @@ class Exchange: order = self._api.create_order(symbol=pair, type=ordertype, side='sell', amount=amount, price=rate, params=params) - logger.info(f"stoploss limit order added for {pair}. " + logger.info(f"stoploss {user_order_type} order added for {pair}. " f"stop price: {stop_price}. limit: {rate}") self._log_exchange_response('create_stoploss_order', order) return order @@ -871,7 +872,7 @@ class Exchange: raise DDosProtection(e) from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( - f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e + f'Could not place stoploss order due to {e.__class__.__name__}. Message: {e}') from e except ccxt.BaseError as e: raise OperationalException(e) from e diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 20fd833eb..70cbc32b7 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -900,7 +900,7 @@ class FreqtradeBot(LoggingMixin): return False - def handle_trailing_stoploss_on_exchange(self, trade: Trade, order: dict) -> None: + def handle_trailing_stoploss_on_exchange(self, trade: Trade, order: Dict) -> None: """ Check to see if stoploss on exchange should be updated in case of trailing stoploss on exchange From 768b526c3867758c00234653d750eb9944f4eb8c Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 3 Feb 2022 07:57:58 +0100 Subject: [PATCH 005/114] Add kucoin stoploss on exchange --- freqtrade/exchange/kucoin.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/freqtrade/exchange/kucoin.py b/freqtrade/exchange/kucoin.py index 2884669a6..efb76f0e3 100644 --- a/freqtrade/exchange/kucoin.py +++ b/freqtrade/exchange/kucoin.py @@ -19,8 +19,27 @@ class Kucoin(Exchange): """ _ft_has: Dict = { + "stoploss_on_exchange": True, + "stoploss_order_types": {"limit": "limit", "market": "market"}, "l2_limit_range": [20, 100], "l2_limit_range_required": False, "order_time_in_force": ['gtc', 'fok', 'ioc'], "time_in_force_parameter": "timeInForce", } + + def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + """ + Verify stop_loss against stoploss-order value (limit or price) + Returns True if adjustment is necessary. + """ + # TODO: since kucoin uses Limit orders, changes to models will be required. + return order['info']['stop'] is not None and stop_loss > float(order['stopPrice']) + + def _get_stop_params(self, ordertype: str, stop_price: float) -> Dict: + + params = self._params.copy() + params.update({ + 'stopPrice': stop_price, + 'stop': 'loss' + }) + return params From 020729cf50b51eae5da8a77f0d97ea5e1a48b6d7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Feb 2022 06:53:51 +0100 Subject: [PATCH 006/114] update docs about kucoin stoploss --- docs/exchanges.md | 4 ++++ docs/stoploss.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index e79abf220..a758245d2 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -177,6 +177,10 @@ Kucoin requires a passphrase for each api key, you will therefore need to add th Kucoin supports [time_in_force](configuration.md#understand-order_time_in_force). +!!! Tip "Stoploss on Exchange" + Kucoin supports `stoploss_on_exchange` and can use both stop-loss-market and stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it. + You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type of stoploss shall be used. + ### Kucoin Blacklists For Kucoin, please add `"KCS/"` to your blacklist to avoid issues. diff --git a/docs/stoploss.md b/docs/stoploss.md index 4d28846f1..0158e0365 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -24,7 +24,7 @@ These modes can be configured with these values: ``` !!! Note - Stoploss on exchange is only supported for Binance (stop-loss-limit), Kraken (stop-loss-market, stop-loss-limit) and FTX (stop limit and stop-market) as of now. + Stoploss on exchange is only supported for Binance (stop-loss-limit), Kraken (stop-loss-market, stop-loss-limit), FTX (stop limit and stop-market) and kucoin (stop-limit and stop-market) as of now. Do not set too low/tight stoploss value if using stop loss on exchange! If set to low/tight then you have greater risk of missing fill on the order and stoploss will not work. From 07491990976ee885c7db63d469935cf83aa8cb38 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Feb 2022 07:08:15 +0100 Subject: [PATCH 007/114] Add stoploss tests for kucoin --- freqtrade/exchange/exchange.py | 6 +- freqtrade/exchange/kucoin.py | 2 +- tests/exchange/test_kucoin.py | 120 +++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 tests/exchange/test_kucoin.py diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 60fd1ded4..b470f8ff2 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -836,6 +836,7 @@ class Exchange: if stop_price_norm <= rate: raise OperationalException( 'In stoploss limit order, stop price should be more than limit price') + rate = self.price_to_precision(pair, rate) if self._config['dry_run']: # TODO: will this work if ordertype is limit?? @@ -848,8 +849,6 @@ class Exchange: amount = self.amount_to_precision(pair, amount) - rate = self.price_to_precision(pair, rate) - order = self._api.create_order(symbol=pair, type=ordertype, side='sell', amount=amount, price=rate, params=params) logger.info(f"stoploss {user_order_type} order added for {pair}. " @@ -872,7 +871,8 @@ class Exchange: raise DDosProtection(e) from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: raise TemporaryError( - f'Could not place stoploss order due to {e.__class__.__name__}. Message: {e}') from e + f"Could not place stoploss order due to {e.__class__.__name__}. " + f"Message: {e}") from e except ccxt.BaseError as e: raise OperationalException(e) from e diff --git a/freqtrade/exchange/kucoin.py b/freqtrade/exchange/kucoin.py index efb76f0e3..037ca5f9a 100644 --- a/freqtrade/exchange/kucoin.py +++ b/freqtrade/exchange/kucoin.py @@ -33,7 +33,7 @@ class Kucoin(Exchange): Returns True if adjustment is necessary. """ # TODO: since kucoin uses Limit orders, changes to models will be required. - return order['info']['stop'] is not None and stop_loss > float(order['stopPrice']) + return order['info'].get('stop') is not None and stop_loss > float(order['stopPrice']) def _get_stop_params(self, ordertype: str, stop_price: float) -> Dict: diff --git a/tests/exchange/test_kucoin.py b/tests/exchange/test_kucoin.py new file mode 100644 index 000000000..87f9ae8d9 --- /dev/null +++ b/tests/exchange/test_kucoin.py @@ -0,0 +1,120 @@ +from random import randint +from unittest.mock import MagicMock + +import ccxt +import pytest + +from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException +from tests.conftest import get_patched_exchange +from tests.exchange.test_exchange import ccxt_exceptionhandlers + + +@pytest.mark.parametrize('order_type', ['market', 'limit']) +@pytest.mark.parametrize('limitratio,expected', [ + (None, 220 * 0.99), + (0.99, 220 * 0.99), + (0.98, 220 * 0.98), +]) +def test_stoploss_order_kucoin(default_conf, mocker, limitratio, expected, order_type): + api_mock = MagicMock() + order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) + + api_mock.create_order = MagicMock(return_value={ + 'id': order_id, + 'info': { + 'foo': 'bar' + } + }) + default_conf['dry_run'] = False + 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, 'kucoin') + if order_type == 'limit': + with pytest.raises(OperationalException): + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, + order_types={ + 'stoploss': order_type, + 'stoploss_on_exchange_limit_ratio': 1.05}) + + api_mock.create_order.reset_mock() + order_types = {'stoploss': order_type} + if limitratio is not None: + order_types.update({'stoploss_on_exchange_limit_ratio': limitratio}) + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types) + + assert 'id' in order + assert 'info' in order + assert order['id'] == order_id + assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' + assert api_mock.create_order.call_args_list[0][1]['type'] == order_type + assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell' + assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 + # Price should be 1% below stopprice + if order_type == 'limit': + assert api_mock.create_order.call_args_list[0][1]['price'] == expected + else: + assert api_mock.create_order.call_args_list[0][1]['price'] is None + + assert api_mock.create_order.call_args_list[0][1]['params'] == { + 'stopPrice': 220, + 'stop': 'loss' + } + + # test exception handling + with pytest.raises(DependencyException): + api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + with pytest.raises(InvalidOrderException): + api_mock.create_order = MagicMock( + side_effect=ccxt.InvalidOrder("kucoin Order would trigger immediately.")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kucoin') + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kucoin", + "stoploss", "create_order", retries=1, + pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + +def test_stoploss_order_dry_run_kucoin(default_conf, mocker): + api_mock = MagicMock() + order_type = 'market' + default_conf['dry_run'] = True + 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, 'kucoin') + + with pytest.raises(OperationalException): + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, + order_types={'stoploss': 'limit', + 'stoploss_on_exchange_limit_ratio': 1.05}) + + api_mock.create_order.reset_mock() + + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + assert 'id' in order + assert 'info' in order + assert 'type' in order + + assert order['type'] == order_type + assert order['price'] == 220 + assert order['amount'] == 1 + + +def test_stoploss_adjust_kucoin(mocker, default_conf): + exchange = get_patched_exchange(mocker, default_conf, id='kucoin') + order = { + 'type': 'limit', + 'price': 1500, + 'stopPrice': 1500, + 'info': {'stopPrice': 1500, 'stop': "limit"}, + } + assert exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1499, order) + # Test with invalid order case + order['info']['stop'] = None + assert not exchange.stoploss_adjust(1501, order) From 6caa5f7131eedc1f2cbc6b760e487c1064952af7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 25 Feb 2022 19:10:16 +0100 Subject: [PATCH 008/114] Update dry-run behaviour --- freqtrade/exchange/exchange.py | 19 ++++++++++++------- freqtrade/exchange/ftx.py | 2 +- freqtrade/exchange/kraken.py | 2 +- freqtrade/exchange/kucoin.py | 1 - freqtrade/freqtradebot.py | 4 ++-- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index b470f8ff2..760a1dd32 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -600,7 +600,8 @@ class Exchange: # Dry-run methods def create_dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, - rate: float, params: Dict = {}) -> Dict[str, Any]: + rate: float, params: Dict = {}, + stop_loss: bool = False) -> Dict[str, Any]: order_id = f'dry_run_{side}_{datetime.now().timestamp()}' _amount = self.amount_to_precision(pair, amount) dry_order: Dict[str, Any] = { @@ -616,14 +617,17 @@ class Exchange: 'remaining': _amount, 'datetime': arrow.utcnow().strftime('%Y-%m-%dT%H:%M:%S.%fZ'), 'timestamp': arrow.utcnow().int_timestamp * 1000, - 'status': "closed" if ordertype == "market" else "open", + 'status': "closed" if ordertype == "market" and not stop_loss else "open", 'fee': None, 'info': {} } - if dry_order["type"] in ["stop_loss_limit", "stop-loss-limit"]: + if stop_loss: dry_order["info"] = {"stopPrice": dry_order["price"]} + dry_order["stopPrice"] = dry_order["price"] + # Workaround to avoid filling stoploss orders immediately + dry_order["ft_order_type"] = "stoploss" - if dry_order["type"] == "market": + if dry_order["type"] == "market" and not dry_order.get("ft_order_type"): # Update market order pricing average = self.get_dry_market_fill_price(pair, side, amount, rate) dry_order.update({ @@ -714,7 +718,9 @@ class Exchange: """ Check dry-run limit order fill and update fee (if it filled). """ - if order['status'] != "closed" and order['type'] in ["limit"]: + if (order['status'] != "closed" + and order['type'] in ["limit"] + and not order.get('ft_order_type')): pair = order['symbol'] if self._is_dry_limit_order_filled(pair, order['side'], order['price']): order.update({ @@ -839,9 +845,8 @@ class Exchange: rate = self.price_to_precision(pair, rate) if self._config['dry_run']: - # TODO: will this work if ordertype is limit?? dry_order = self.create_dry_run_order( - pair, ordertype, "sell", amount, stop_price_norm) + pair, ordertype, "sell", amount, stop_price_norm, stop_loss=True) return dry_order try: diff --git a/freqtrade/exchange/ftx.py b/freqtrade/exchange/ftx.py index a8bf9abac..a346216b3 100644 --- a/freqtrade/exchange/ftx.py +++ b/freqtrade/exchange/ftx.py @@ -56,7 +56,7 @@ class Ftx(Exchange): if self._config['dry_run']: dry_order = self.create_dry_run_order( - pair, ordertype, "sell", amount, stop_price) + pair, ordertype, "sell", amount, stop_price, stop_loss=True) return dry_order try: diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index f4c8ca275..6a033f133 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -101,7 +101,7 @@ class Kraken(Exchange): if self._config['dry_run']: dry_order = self.create_dry_run_order( - pair, ordertype, "sell", amount, stop_price) + pair, ordertype, "sell", amount, stop_price, stop_loss=True) return dry_order try: diff --git a/freqtrade/exchange/kucoin.py b/freqtrade/exchange/kucoin.py index 037ca5f9a..e55f49cce 100644 --- a/freqtrade/exchange/kucoin.py +++ b/freqtrade/exchange/kucoin.py @@ -32,7 +32,6 @@ class Kucoin(Exchange): Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ - # TODO: since kucoin uses Limit orders, changes to models will be required. return order['info'].get('stop') is not None and stop_loss > float(order['stopPrice']) def _get_stop_params(self, ordertype: str, stop_price: float) -> Dict: diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 70cbc32b7..e3214a61e 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1170,8 +1170,8 @@ class FreqtradeBot(LoggingMixin): # if stoploss is on exchange and we are on dry_run mode, # we consider the sell price stop price - if self.config['dry_run'] and sell_type == 'stoploss' \ - and self.strategy.order_types['stoploss_on_exchange']: + if (self.config['dry_run'] and sell_type == 'stoploss' + and self.strategy.order_types['stoploss_on_exchange']): limit = trade.stop_loss # set custom_exit_price if available From 3942b30ebf5b33ed5854b25a5b9ae91650c8ff1d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 26 Feb 2022 08:34:23 +0100 Subject: [PATCH 009/114] Add kraken TODO --- freqtrade/exchange/kraken.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/exchange/kraken.py b/freqtrade/exchange/kraken.py index 6a033f133..8cec2500e 100644 --- a/freqtrade/exchange/kraken.py +++ b/freqtrade/exchange/kraken.py @@ -86,6 +86,8 @@ class Kraken(Exchange): """ Creates a stoploss market order. Stoploss market orders is the only stoploss type supported by kraken. + TODO: investigate if this can be combined with generic implementation + (careful, prices are reversed) """ params = self._params.copy() From 2ec1a7b3707fa122699ff2ee43bf32d60e047b52 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 14 Jan 2022 19:39:09 +0100 Subject: [PATCH 010/114] Add huobi exchange class --- freqtrade/exchange/__init__.py | 1 + freqtrade/exchange/huobi.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 freqtrade/exchange/huobi.py diff --git a/freqtrade/exchange/__init__.py b/freqtrade/exchange/__init__.py index 9dc2b8480..2b9ed47ea 100644 --- a/freqtrade/exchange/__init__.py +++ b/freqtrade/exchange/__init__.py @@ -18,6 +18,7 @@ from freqtrade.exchange.exchange import (available_exchanges, ccxt_exchanges, from freqtrade.exchange.ftx import Ftx from freqtrade.exchange.gateio import Gateio from freqtrade.exchange.hitbtc import Hitbtc +from freqtrade.exchange.huobi import Huobi from freqtrade.exchange.kraken import Kraken from freqtrade.exchange.kucoin import Kucoin from freqtrade.exchange.okx import Okx diff --git a/freqtrade/exchange/huobi.py b/freqtrade/exchange/huobi.py new file mode 100644 index 000000000..90865539e --- /dev/null +++ b/freqtrade/exchange/huobi.py @@ -0,0 +1,19 @@ +""" Huobi exchange subclass """ +import logging +from typing import Dict + +from freqtrade.exchange import Exchange + + +logger = logging.getLogger(__name__) + + +class Huobi(Exchange): + """ + Huobi exchange class. Contains adjustments needed for Freqtrade to work + with this exchange. + """ + + _ft_has: Dict = { + "ohlcv_candle_limit": 2000, + } From ee7bc557277910a47984d23616adc38613b9e4d6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 16 Jan 2022 13:17:00 +0100 Subject: [PATCH 011/114] Add huobi to Exchange setup --- freqtrade/commands/build_config_commands.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/freqtrade/commands/build_config_commands.py b/freqtrade/commands/build_config_commands.py index 4c722c810..ca55dbbc4 100644 --- a/freqtrade/commands/build_config_commands.py +++ b/freqtrade/commands/build_config_commands.py @@ -108,10 +108,11 @@ def ask_user_config() -> Dict[str, Any]: "binance", "binanceus", "bittrex", - "kraken", "ftx", - "kucoin", "gateio", + "huobi", + "kraken", + "kucoin", "okx", Separator(), "other", From 9504b3eb059dd8fcd4b7da63af453f03b6b21edc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 16 Jan 2022 13:39:19 +0100 Subject: [PATCH 012/114] Improve huobi config generation --- freqtrade/templates/subtemplates/exchange_huobi.j2 | 12 ++++++++++++ tests/exchange/test_exchange.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 freqtrade/templates/subtemplates/exchange_huobi.j2 diff --git a/freqtrade/templates/subtemplates/exchange_huobi.j2 b/freqtrade/templates/subtemplates/exchange_huobi.j2 new file mode 100644 index 000000000..3cb521785 --- /dev/null +++ b/freqtrade/templates/subtemplates/exchange_huobi.j2 @@ -0,0 +1,12 @@ +"exchange": { + "name": "{{ exchange_name | lower }}", + "key": "{{ exchange_key }}", + "secret": "{{ exchange_secret }}", + "ccxt_config": {}, + "ccxt_async_config": {}, + "pair_whitelist": [ + ], + "pair_blacklist": [ + "HT/.*" + ] +} diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 33f34ba3c..527e8050b 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -166,7 +166,7 @@ def test_exchange_resolver(default_conf, mocker, caplog): mocker.patch('freqtrade.exchange.Exchange.validate_timeframes') mocker.patch('freqtrade.exchange.Exchange.validate_stakecurrency') - exchange = ExchangeResolver.load_exchange('huobi', default_conf) + exchange = ExchangeResolver.load_exchange('zaif', default_conf) assert isinstance(exchange, Exchange) assert log_has_re(r"No .* specific subclass found. Using the generic class instead.", caplog) caplog.clear() From 292c350885a9c4df760c5da42f037ff1a93602da Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 30 Jan 2022 14:15:07 +0100 Subject: [PATCH 013/114] Add stoploss support for huobi --- docs/exchanges.md | 7 ++- docs/stoploss.md | 2 +- freqtrade/exchange/huobi.py | 75 ++++++++++++++++++++++++ tests/exchange/test_huobi.py | 109 +++++++++++++++++++++++++++++++++++ 4 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 tests/exchange/test_huobi.py diff --git a/docs/exchanges.md b/docs/exchanges.md index a758245d2..53af35736 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -57,7 +57,7 @@ This configuration enables kraken, as well as rate-limiting to avoid bans from t Binance supports [time_in_force](configuration.md#understand-order_time_in_force). !!! Tip "Stoploss on Exchange" - Binance supports `stoploss_on_exchange` and uses stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it. + Binance supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange.. ### Binance Blacklist @@ -71,6 +71,11 @@ Binance has been split into 2, and users must use the correct ccxt exchange ID f * [binance.com](https://www.binance.com/) - International users. Use exchange id: `binance`. * [binance.us](https://www.binance.us/) - US based users. Use exchange id: `binanceus`. +## Huobi + +!!! Tip "Stoploss on Exchange" + Huobi supports `stoploss_on_exchange` and uses `stop-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange. + ## Kraken !!! Tip "Stoploss on Exchange" diff --git a/docs/stoploss.md b/docs/stoploss.md index 0158e0365..d0e106d8f 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -24,7 +24,7 @@ These modes can be configured with these values: ``` !!! Note - Stoploss on exchange is only supported for Binance (stop-loss-limit), Kraken (stop-loss-market, stop-loss-limit), FTX (stop limit and stop-market) and kucoin (stop-limit and stop-market) as of now. + Stoploss on exchange is only supported for Binance (stop-loss-limit), Huobi (stop-limit), Kraken (stop-loss-market, stop-loss-limit), FTX (stop limit and stop-market) and kucoin (stop-limit and stop-market) as of now. Do not set too low/tight stoploss value if using stop loss on exchange! If set to low/tight then you have greater risk of missing fill on the order and stoploss will not work. diff --git a/freqtrade/exchange/huobi.py b/freqtrade/exchange/huobi.py index 90865539e..609f2994b 100644 --- a/freqtrade/exchange/huobi.py +++ b/freqtrade/exchange/huobi.py @@ -2,7 +2,12 @@ import logging from typing import Dict +import ccxt + +from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException, + OperationalException, TemporaryError) from freqtrade.exchange import Exchange +from freqtrade.exchange.common import retrier logger = logging.getLogger(__name__) @@ -15,5 +20,75 @@ class Huobi(Exchange): """ _ft_has: Dict = { + "stoploss_on_exchange": True, "ohlcv_candle_limit": 2000, } + + def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + """ + Verify stop_loss against stoploss-order value (limit or price) + Returns True if adjustment is necessary. + """ + return order['type'] == 'stop' and stop_loss > float(order['stopPrice']) + + @retrier(retries=0) + def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: + """ + creates a stoploss limit order. + this stoploss-limit is huobi-specific. + TODO: Compare this with other stoploss implementations - + """ + # Limit price threshold: As limit price should always be below stop-price + limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) + rate = stop_price * limit_price_pct + + ordertype = "stop-limit" + + stop_price = self.price_to_precision(pair, stop_price) + + # Ensure rate is less than stop price + if stop_price <= rate: + raise OperationalException( + 'In stoploss limit order, stop price should be more than limit price') + + if self._config['dry_run']: + dry_order = self.create_dry_run_order( + pair, ordertype, "sell", amount, stop_price) + return dry_order + + try: + params = self._params.copy() + params.update({ + "stop-price": stop_price, + "operator": "lte", + }) + + amount = self.amount_to_precision(pair, amount) + + rate = self.price_to_precision(pair, rate) + + order = self._api.create_order(symbol=pair, type=ordertype, side='sell', + amount=amount, price=rate, params=params) + logger.info('stoploss limit order added for %s. ' + 'stop price: %s. limit: %s', pair, stop_price, rate) + self._log_exchange_response('create_stoploss_order', order) + return order + except ccxt.InsufficientFunds as e: + raise InsufficientFundsError( + f'Insufficient funds to create {ordertype} sell order on market {pair}. ' + f'Tried to sell amount {amount} at rate {rate}. ' + f'Message: {e}') from e + except ccxt.InvalidOrder as e: + # Errors: + # `Order would trigger immediately.` + raise InvalidOrderException( + f'Could not create {ordertype} sell order on market {pair}. ' + f'Tried to sell amount {amount} at rate {rate}. ' + f'Message: {e}') from e + except ccxt.DDoSProtection as e: + raise DDosProtection(e) from e + except (ccxt.NetworkError, ccxt.ExchangeError) as e: + raise TemporaryError( + f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e + except ccxt.BaseError as e: + raise OperationalException(e) from e diff --git a/tests/exchange/test_huobi.py b/tests/exchange/test_huobi.py new file mode 100644 index 000000000..8d2a35489 --- /dev/null +++ b/tests/exchange/test_huobi.py @@ -0,0 +1,109 @@ +from random import randint +from unittest.mock import MagicMock + +import ccxt +import pytest + +from freqtrade.exceptions import DependencyException, InvalidOrderException, OperationalException +from tests.conftest import get_patched_exchange +from tests.exchange.test_exchange import ccxt_exceptionhandlers + + +@pytest.mark.parametrize('limitratio,expected', [ + (None, 220 * 0.99), + (0.99, 220 * 0.99), + (0.98, 220 * 0.98), +]) +def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected): + api_mock = MagicMock() + order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) + order_type = 'stop-limit' + + api_mock.create_order = MagicMock(return_value={ + 'id': order_id, + 'info': { + 'foo': 'bar' + } + }) + default_conf['dry_run'] = False + 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, 'huobi') + + with pytest.raises(OperationalException): + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, + order_types={'stoploss_on_exchange_limit_ratio': 1.05}) + + api_mock.create_order.reset_mock() + order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio} + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types) + + assert 'id' in order + assert 'info' in order + assert order['id'] == order_id + assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC' + assert api_mock.create_order.call_args_list[0][1]['type'] == order_type + assert api_mock.create_order.call_args_list[0][1]['side'] == 'sell' + assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 + # Price should be 1% below stopprice + assert api_mock.create_order.call_args_list[0][1]['price'] == expected + assert api_mock.create_order.call_args_list[0][1]['params'] == {"stop-price": 220, + "operator": "lte", + } + + # test exception handling + with pytest.raises(DependencyException): + api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'huobi') + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + with pytest.raises(InvalidOrderException): + api_mock.create_order = MagicMock( + side_effect=ccxt.InvalidOrder("binance Order would trigger immediately.")) + exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') + exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + ccxt_exceptionhandlers(mocker, default_conf, api_mock, "huobi", + "stoploss", "create_order", retries=1, + pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + +def test_stoploss_order_dry_run_huobi(default_conf, mocker): + api_mock = MagicMock() + order_type = 'stop-limit' + default_conf['dry_run'] = True + 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, 'huobi') + + with pytest.raises(OperationalException): + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, + order_types={'stoploss_on_exchange_limit_ratio': 1.05}) + + api_mock.create_order.reset_mock() + + order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + + assert 'id' in order + assert 'info' in order + assert 'type' in order + + assert order['type'] == order_type + assert order['price'] == 220 + assert order['amount'] == 1 + + +def test_stoploss_adjust_huobi(mocker, default_conf): + exchange = get_patched_exchange(mocker, default_conf, id='huobi') + order = { + 'type': 'stop', + 'price': 1500, + 'stopPrice': '1500', + } + assert exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1499, order) + # Test with invalid order case + order['type'] = 'stop_loss' + assert not exchange.stoploss_adjust(1501, order) From 1b91be08fece53febdc24deac9a442105ca21c99 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 2 Feb 2022 17:01:51 +0100 Subject: [PATCH 014/114] Add huobi to list of supported exchanges --- README.md | 3 ++- docs/exchanges.md | 12 ++++++------ docs/index.md | 3 ++- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9b25775af..245a56133 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,8 @@ Please read the [exchange specific notes](docs/exchanges.md) to learn about even - [X] [FTX](https://ftx.com) - [X] [Gate.io](https://www.gate.io/ref/6266643) - [X] [Kraken](https://kraken.com/) -- [X] [OKX](https://www.okx.com/) +- [X] [OKX](https://okx.com/) (Former OKEX) +- [X] [Huobi](http://huobi.com/) - [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_ ### Community tested diff --git a/docs/exchanges.md b/docs/exchanges.md index 53af35736..8adf19081 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -71,11 +71,6 @@ Binance has been split into 2, and users must use the correct ccxt exchange ID f * [binance.com](https://www.binance.com/) - International users. Use exchange id: `binance`. * [binance.us](https://www.binance.us/) - US based users. Use exchange id: `binanceus`. -## Huobi - -!!! Tip "Stoploss on Exchange" - Huobi supports `stoploss_on_exchange` and uses `stop-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange. - ## Kraken !!! Tip "Stoploss on Exchange" @@ -191,7 +186,12 @@ Kucoin supports [time_in_force](configuration.md#understand-order_time_in_force) For Kucoin, please add `"KCS/"` to your blacklist to avoid issues. Accounts having KCS accounts use this to pay for fees - if your first trade happens to be on `KCS`, further trades will consume this position and make the initial KCS trade unsellable as the expected amount is not there anymore. -## OKX +## Huobi + +!!! Tip "Stoploss on Exchange" + Huobi supports `stoploss_on_exchange` and uses `stop-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange. + +## OKX (former OKEX) OKX requires a passphrase for each api key, you will therefore need to add this key into the configuration so your exchange section looks as follows: diff --git a/docs/index.md b/docs/index.md index 9fb302a91..134e00c4b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -47,7 +47,8 @@ Please read the [exchange specific notes](exchanges.md) to learn about eventual, - [X] [FTX](https://ftx.com) - [X] [Gate.io](https://www.gate.io/ref/6266643) - [X] [Kraken](https://kraken.com/) -- [X] [OKX](https://www.okx.com/) +- [X] [OKX](https://okx.com/) (Former OKEX) +- [X] [Huobi](http://huobi.com/) - [ ] [potentially many others through ccxt](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_ ### Community tested From f3421dfa9fec2d00a81821de922fad5f553a1f7b Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 2 Feb 2022 20:04:17 +0100 Subject: [PATCH 015/114] Use unified stopPrice argument --- freqtrade/exchange/huobi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/exchange/huobi.py b/freqtrade/exchange/huobi.py index 609f2994b..a56efc4a6 100644 --- a/freqtrade/exchange/huobi.py +++ b/freqtrade/exchange/huobi.py @@ -59,7 +59,7 @@ class Huobi(Exchange): try: params = self._params.copy() params.update({ - "stop-price": stop_price, + "stopPrice": stop_price, "operator": "lte", }) From a1f2f6ddebd869fbc70f31e42b5d74c50d9a6b66 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 25 Feb 2022 15:50:45 +0100 Subject: [PATCH 016/114] Updates required for huobi datadownload --- freqtrade/exchange/huobi.py | 2 +- requirements.txt | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/freqtrade/exchange/huobi.py b/freqtrade/exchange/huobi.py index a56efc4a6..50629160b 100644 --- a/freqtrade/exchange/huobi.py +++ b/freqtrade/exchange/huobi.py @@ -21,7 +21,7 @@ class Huobi(Exchange): _ft_has: Dict = { "stoploss_on_exchange": True, - "ohlcv_candle_limit": 2000, + "ohlcv_candle_limit": 1000, } def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: diff --git a/requirements.txt b/requirements.txt index c50f14666..a8ff2f645 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.22.2 pandas==1.4.1 pandas-ta==0.3.14b -ccxt==1.73.70 +ccxt==1.74.17 # Pin cryptography for now due to rust build errors with piwheels cryptography==36.0.1 aiohttp==3.8.1 diff --git a/setup.py b/setup.py index b46396385..ec41228c1 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,7 @@ setup( ], install_requires=[ # from requirements.txt - 'ccxt>=1.66.32', + 'ccxt>=1.74.17', 'SQLAlchemy', 'python-telegram-bot>=13.4', 'arrow>=0.17.0', From 14d49e85afda407ff632fb4517b9f89bf13f0fe5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 26 Feb 2022 10:44:38 +0100 Subject: [PATCH 017/114] Update Huobi stoploss to shared method --- freqtrade/exchange/huobi.py | 73 ++++-------------------------------- tests/exchange/test_huobi.py | 2 +- 2 files changed, 9 insertions(+), 66 deletions(-) diff --git a/freqtrade/exchange/huobi.py b/freqtrade/exchange/huobi.py index 50629160b..71c69a9a2 100644 --- a/freqtrade/exchange/huobi.py +++ b/freqtrade/exchange/huobi.py @@ -2,12 +2,7 @@ import logging from typing import Dict -import ccxt - -from freqtrade.exceptions import (DDosProtection, InsufficientFundsError, InvalidOrderException, - OperationalException, TemporaryError) from freqtrade.exchange import Exchange -from freqtrade.exchange.common import retrier logger = logging.getLogger(__name__) @@ -21,6 +16,7 @@ class Huobi(Exchange): _ft_has: Dict = { "stoploss_on_exchange": True, + "stoploss_order_types": {"limit": "stop-limit"}, "ohlcv_candle_limit": 1000, } @@ -31,64 +27,11 @@ class Huobi(Exchange): """ return order['type'] == 'stop' and stop_loss > float(order['stopPrice']) - @retrier(retries=0) - def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict: - """ - creates a stoploss limit order. - this stoploss-limit is huobi-specific. - TODO: Compare this with other stoploss implementations - - """ - # Limit price threshold: As limit price should always be below stop-price - limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99) - rate = stop_price * limit_price_pct + def _get_stop_params(self, ordertype: str, stop_price: float) -> Dict: - ordertype = "stop-limit" - - stop_price = self.price_to_precision(pair, stop_price) - - # Ensure rate is less than stop price - if stop_price <= rate: - raise OperationalException( - 'In stoploss limit order, stop price should be more than limit price') - - if self._config['dry_run']: - dry_order = self.create_dry_run_order( - pair, ordertype, "sell", amount, stop_price) - return dry_order - - try: - params = self._params.copy() - params.update({ - "stopPrice": stop_price, - "operator": "lte", - }) - - amount = self.amount_to_precision(pair, amount) - - rate = self.price_to_precision(pair, rate) - - order = self._api.create_order(symbol=pair, type=ordertype, side='sell', - amount=amount, price=rate, params=params) - logger.info('stoploss limit order added for %s. ' - 'stop price: %s. limit: %s', pair, stop_price, rate) - self._log_exchange_response('create_stoploss_order', order) - return order - except ccxt.InsufficientFunds as e: - raise InsufficientFundsError( - f'Insufficient funds to create {ordertype} sell order on market {pair}. ' - f'Tried to sell amount {amount} at rate {rate}. ' - f'Message: {e}') from e - except ccxt.InvalidOrder as e: - # Errors: - # `Order would trigger immediately.` - raise InvalidOrderException( - f'Could not create {ordertype} sell order on market {pair}. ' - f'Tried to sell amount {amount} at rate {rate}. ' - f'Message: {e}') from e - except ccxt.DDoSProtection as e: - raise DDosProtection(e) from e - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e - except ccxt.BaseError as e: - raise OperationalException(e) from e + params = self._params.copy() + params.update({ + "stopPrice": stop_price, + "operator": "lte", + }) + return params diff --git a/tests/exchange/test_huobi.py b/tests/exchange/test_huobi.py index 8d2a35489..b39b5ab30 100644 --- a/tests/exchange/test_huobi.py +++ b/tests/exchange/test_huobi.py @@ -48,7 +48,7 @@ def test_stoploss_order_huobi(default_conf, mocker, limitratio, expected): assert api_mock.create_order.call_args_list[0][1]['amount'] == 1 # Price should be 1% below stopprice assert api_mock.create_order.call_args_list[0][1]['price'] == expected - assert api_mock.create_order.call_args_list[0][1]['params'] == {"stop-price": 220, + assert api_mock.create_order.call_args_list[0][1]['params'] == {"stopPrice": 220, "operator": "lte", } From 41316abb55f1b42116a1c51b6d47ffaf4fcf6340 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 26 Feb 2022 11:04:50 +0100 Subject: [PATCH 018/114] Sort supported exchanges alphabetically --- README.md | 4 ++-- docs/index.md | 4 ++-- freqtrade/exchange/exchange.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 245a56133..5c3ac1be5 100644 --- a/README.md +++ b/README.md @@ -30,13 +30,13 @@ hesitate to read the source code and understand the mechanism of this bot. Please read the [exchange specific notes](docs/exchanges.md) to learn about eventual, special configurations needed for each exchange. -- [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#binance-blacklist)) +- [X] [Binance](https://www.binance.com/) - [X] [Bittrex](https://bittrex.com/) - [X] [FTX](https://ftx.com) - [X] [Gate.io](https://www.gate.io/ref/6266643) +- [X] [Huobi](http://huobi.com/) - [X] [Kraken](https://kraken.com/) - [X] [OKX](https://okx.com/) (Former OKEX) -- [X] [Huobi](http://huobi.com/) - [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_ ### Community tested diff --git a/docs/index.md b/docs/index.md index 134e00c4b..32b19bd94 100644 --- a/docs/index.md +++ b/docs/index.md @@ -42,13 +42,13 @@ Freqtrade is a free and open source crypto trading bot written in Python. It is Please read the [exchange specific notes](exchanges.md) to learn about eventual, special configurations needed for each exchange. -- [X] [Binance](https://www.binance.com/) ([*Note for binance users](exchanges.md#binance-blacklist)) +- [X] [Binance](https://www.binance.com/) - [X] [Bittrex](https://bittrex.com/) - [X] [FTX](https://ftx.com) - [X] [Gate.io](https://www.gate.io/ref/6266643) +- [X] [Huobi](http://huobi.com/) - [X] [Kraken](https://kraken.com/) - [X] [OKX](https://okx.com/) (Former OKEX) -- [X] [Huobi](http://huobi.com/) - [ ] [potentially many others through ccxt](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_ ### Community tested diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 760a1dd32..da89a7c8a 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -1664,7 +1664,7 @@ def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = Non def is_exchange_officially_supported(exchange_name: str) -> bool: - return exchange_name in ['bittrex', 'binance', 'kraken', 'ftx', 'gateio', 'okx'] + return exchange_name in ['binance', 'bittrex', 'ftx', 'gateio', 'huobi', 'kraken', 'okx'] def ccxt_exchanges(ccxt_module: CcxtModuleType = None) -> List[str]: From 0ebf40f39042bdc139bc4969f7d2cce3c0022aae Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 27 Feb 2022 15:57:44 +0100 Subject: [PATCH 019/114] Don't call amount_to_precision twice on entry --- freqtrade/freqtradebot.py | 1 - tests/rpc/test_rpc.py | 4 ++-- tests/test_freqtradebot.py | 10 +++++----- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index e3214a61e..91581e557 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -542,7 +542,6 @@ class FreqtradeBot(LoggingMixin): entry_tag=buy_tag): logger.info(f"User requested abortion of buying {pair}") return False - amount = self.exchange.amount_to_precision(pair, amount) order = self.exchange.create_order(pair=pair, ordertype=order_type, side="buy", amount=amount, rate=enter_limit_requested, time_in_force=time_in_force) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index dd6c969ed..7d0704d2f 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -79,7 +79,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'close_rate': None, 'current_rate': 1.099e-05, 'amount': 91.07468123, - 'amount_requested': 91.07468123, + 'amount_requested': 91.07468124, 'stake_amount': 0.001, 'trade_duration': None, 'trade_duration_s': None, @@ -154,7 +154,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'close_rate': None, 'current_rate': ANY, 'amount': 91.07468123, - 'amount_requested': 91.07468123, + 'amount_requested': 91.07468124, 'trade_duration': ANY, 'trade_duration_s': ANY, 'stake_amount': 0.001, diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index d433998a1..7e56a96e6 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -727,7 +727,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_buy_order_usdt, call_args = buy_mm.call_args_list[0][1] assert call_args['pair'] == pair assert call_args['rate'] == bid - assert call_args['amount'] == round(stake_amount / bid, 8) + assert call_args['amount'] == stake_amount / bid buy_rate_mock.reset_mock() # Should create an open trade with an open order id @@ -748,7 +748,7 @@ def test_execute_entry(mocker, default_conf_usdt, fee, limit_buy_order_usdt, call_args = buy_mm.call_args_list[1][1] assert call_args['pair'] == pair assert call_args['rate'] == fix_price - assert call_args['amount'] == round(stake_amount / fix_price, 8) + assert call_args['amount'] == stake_amount / fix_price # In case of closed order limit_buy_order_usdt['status'] = 'closed' @@ -1266,7 +1266,7 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf_usdt, fee, cancel_order_mock.assert_called_once_with(100, 'ETH/USDT') stoploss_order_mock.assert_called_once_with( - amount=27.39726027, + amount=pytest.approx(27.39726027), pair='ETH/USDT', order_types=freqtrade.strategy.order_types, stop_price=4.4 * 0.95 @@ -1458,7 +1458,7 @@ def test_handle_stoploss_on_exchange_custom_stop( cancel_order_mock.assert_called_once_with(100, 'ETH/USDT') stoploss_order_mock.assert_called_once_with( - amount=31.57894736, + amount=pytest.approx(31.57894736), pair='ETH/USDT', order_types=freqtrade.strategy.order_types, stop_price=4.4 * 0.96 @@ -1583,7 +1583,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, assert trade.stop_loss == 4.4 * 0.99 cancel_order_mock.assert_called_once_with(100, 'NEO/BTC') stoploss_order_mock.assert_called_once_with( - amount=11.41438356, + amount=pytest.approx(11.41438356), pair='NEO/BTC', order_types=freqtrade.strategy.order_types, stop_price=4.4 * 0.99 From 1ac360674cf250bbcc9dbd3c5604a2f285024332 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 27 Feb 2022 17:37:46 +0100 Subject: [PATCH 020/114] Update Readme quickstart #6472 --- README.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5c3ac1be5..166e4833a 100644 --- a/README.md +++ b/README.md @@ -69,15 +69,9 @@ Please find the complete documentation on the [freqtrade website](https://www.fr ## Quick start -Freqtrade provides a Linux/macOS script to install all dependencies and help you to configure the bot. +Please refer to the [Docker Quickstart documentation](https://www.freqtrade.io/en/stable/docker_quickstart/) on how to get started quickly. -```bash -git clone -b develop https://github.com/freqtrade/freqtrade.git -cd freqtrade -./setup.sh --install -``` - -For any other type of installation please refer to [Installation doc](https://www.freqtrade.io/en/stable/installation/). +For further (native) installation methods, please refer to the [Installation documentation page](https://www.freqtrade.io/en/stable/installation/). ## Basic Usage From 590944a6004807479b4de3d83c599a7619d113dd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Feb 2022 03:01:17 +0000 Subject: [PATCH 021/114] Bump mkdocs-material from 8.2.1 to 8.2.3 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 8.2.1 to 8.2.3. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/8.2.1...8.2.3) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 3e7fa2044..839485629 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,4 +1,4 @@ mkdocs==1.2.3 -mkdocs-material==8.2.1 +mkdocs-material==8.2.3 mdx_truly_sane_lists==1.2 pymdown-extensions==9.2 From faf6a35ad7aec5643768fbf3c1115fc6cfdbde81 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Feb 2022 03:01:20 +0000 Subject: [PATCH 022/114] Bump types-requests from 2.27.10 to 2.27.11 Bumps [types-requests](https://github.com/python/typeshed) from 2.27.10 to 2.27.11. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index c52032a60..9fc8a18ad 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -22,7 +22,7 @@ nbconvert==6.4.2 # mypy types types-cachetools==4.2.9 types-filelock==3.2.5 -types-requests==2.27.10 +types-requests==2.27.11 types-tabulate==0.8.5 # Extensions to datetime library From 42fbec4172f255d66afac957b8959b1018bef81b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Feb 2022 03:01:29 +0000 Subject: [PATCH 023/114] Bump ccxt from 1.74.17 to 1.74.43 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.74.17 to 1.74.43. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg) - [Commits](https://github.com/ccxt/ccxt/compare/1.74.17...1.74.43) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a8ff2f645..d3d96c39e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.22.2 pandas==1.4.1 pandas-ta==0.3.14b -ccxt==1.74.17 +ccxt==1.74.43 # Pin cryptography for now due to rust build errors with piwheels cryptography==36.0.1 aiohttp==3.8.1 From 207b211e5e28d957d15803dd0d61cb4d6c0b7ed8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Feb 2022 03:01:38 +0000 Subject: [PATCH 024/114] Bump fastapi from 0.74.0 to 0.74.1 Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.74.0 to 0.74.1. - [Release notes](https://github.com/tiangolo/fastapi/releases) - [Commits](https://github.com/tiangolo/fastapi/compare/0.74.0...0.74.1) --- updated-dependencies: - dependency-name: fastapi dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a8ff2f645..ad5c271ca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,7 +31,7 @@ python-rapidjson==1.6 sdnotify==0.3.2 # API Server -fastapi==0.74.0 +fastapi==0.74.1 uvicorn==0.17.5 pyjwt==2.3.0 aiofiles==0.8.0 From 68bc2a610743374978a37f767dc3787a4f9b5584 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 27 Feb 2022 11:56:22 +0100 Subject: [PATCH 025/114] Add huobi to ccxt compat tests --- freqtrade/exchange/huobi.py | 2 ++ tests/exchange/test_ccxt_compat.py | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/huobi.py b/freqtrade/exchange/huobi.py index 71c69a9a2..d07e13497 100644 --- a/freqtrade/exchange/huobi.py +++ b/freqtrade/exchange/huobi.py @@ -18,6 +18,8 @@ class Huobi(Exchange): "stoploss_on_exchange": True, "stoploss_order_types": {"limit": "stop-limit"}, "ohlcv_candle_limit": 1000, + "l2_limit_range": [5, 10, 20], + "l2_limit_range_required": False, } def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index 09523bd59..877d53fe7 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -59,6 +59,12 @@ EXCHANGES = { 'hasQuoteVolume': True, 'timeframe': '5m', }, + 'huobi': { + 'pair': 'BTC/USDT', + 'stake_currency': 'USDT', + 'hasQuoteVolume': True, + 'timeframe': '5m', + }, 'bitvavo': { 'pair': 'BTC/EUR', 'stake_currency': 'EUR', @@ -140,7 +146,10 @@ class TestCCXTExchange(): else: next_limit = exchange.get_next_limit_in_list( val, l2_limit_range, l2_limit_range_required) - if next_limit is None or next_limit > 200: + if next_limit is None: + assert len(l2['asks']) > 100 + assert len(l2['asks']) > 100 + elif next_limit > 200: # Large orderbook sizes can be a problem for some exchanges (bitrex ...) assert len(l2['asks']) > 200 assert len(l2['asks']) > 200 From f26247e8e026901cc74c7602107fbb57d5115c23 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 1 Mar 2022 19:08:04 +0100 Subject: [PATCH 026/114] Revert wrong version string --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 54cecbec2..2747efc96 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = '2022.1' +__version__ = 'develop' if __version__ == 'develop': From a2c9879375e744b1bd7fba216f61c302ca45294e Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 1 Mar 2022 19:30:16 +0100 Subject: [PATCH 027/114] Reset sell-reason if order is cancelled --- freqtrade/freqtradebot.py | 1 + tests/test_freqtradebot.py | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 99872ff0b..8e26c4c3a 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1108,6 +1108,7 @@ class FreqtradeBot(LoggingMixin): trade.close_date = None trade.is_open = True trade.open_order_id = None + trade.sell_reason = None else: # TODO: figure out how to handle partially complete sell orders reason = constants.CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN'] diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index d7b47174b..997ec5159 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2549,9 +2549,12 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee) -> None: exchange='binance', open_rate=0.245441, open_order_id="123456", - open_date=arrow.utcnow().datetime, + open_date=arrow.utcnow().shift(days=-2).datetime, fee_open=fee.return_value, fee_close=fee.return_value, + close_rate=0.555, + close_date=arrow.utcnow().datetime, + sell_reason="sell_reason_whatever", ) order = {'remaining': 1, 'amount': 1, @@ -2560,6 +2563,8 @@ def test_handle_cancel_exit_limit(mocker, default_conf_usdt, fee) -> None: assert freqtrade.handle_cancel_exit(trade, order, reason) assert cancel_order_mock.call_count == 1 assert send_msg_mock.call_count == 1 + assert trade.close_rate == None + assert trade.sell_reason is None send_msg_mock.reset_mock() From 69cfb0b278d1d78b528216ecd1f4ab4bc995014e Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 1 Mar 2022 19:32:25 +0100 Subject: [PATCH 028/114] Revert change to telegram - this should be handled at the source --- freqtrade/rpc/telegram.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index a4310e3a0..69f7f2858 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -452,8 +452,7 @@ class Telegram(RPCHandler): "*Current Pair:* {pair}", "*Amount:* `{amount} ({stake_amount} {base_currency})`", "*Entry Tag:* `{buy_tag}`" if r['buy_tag'] else "", - "*Exit Reason:* `{sell_reason}`" - if (r['sell_reason'] and not r['is_open']) else "", + "*Exit Reason:* `{sell_reason}`" if r['sell_reason'] else "", ] if position_adjust: From 54165662cec1e7b53ded4f00769ae94b02ef3ee9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 1 Mar 2022 19:41:26 +0100 Subject: [PATCH 029/114] Don't require unfilledtimeout, it's optional. --- freqtrade/constants.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 077be51f7..066a07c62 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -440,7 +440,6 @@ SCHEMA_TRADE_REQUIRED = [ 'dry_run_wallet', 'ask_strategy', 'bid_strategy', - 'unfilledtimeout', 'stoploss', 'minimal_roi', 'internals', @@ -456,7 +455,6 @@ SCHEMA_BACKTEST_REQUIRED = [ 'dry_run_wallet', 'dataformat_ohlcv', 'dataformat_trades', - 'unfilledtimeout', ] SCHEMA_MINIMAL_REQUIRED = [ From f74de1cca34ae22e7d173feeac8dd375b812ee72 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 1 Mar 2022 19:46:13 +0100 Subject: [PATCH 030/114] Improve Backtesting "wrong setup" message to include tradable_balance --- freqtrade/commands/optimize_commands.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/freqtrade/commands/optimize_commands.py b/freqtrade/commands/optimize_commands.py index f230b696c..1bfd384fc 100644 --- a/freqtrade/commands/optimize_commands.py +++ b/freqtrade/commands/optimize_commands.py @@ -25,12 +25,16 @@ def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[ RunMode.HYPEROPT: 'hyperoptimization', } if method in no_unlimited_runmodes.keys(): + wallet_size = config['dry_run_wallet'] * config['tradable_balance_ratio'] + # tradable_balance_ratio if (config['stake_amount'] != constants.UNLIMITED_STAKE_AMOUNT - and config['stake_amount'] > config['dry_run_wallet']): - wallet = round_coin_value(config['dry_run_wallet'], config['stake_currency']) + and config['stake_amount'] > wallet_size): + wallet = round_coin_value(wallet_size, config['stake_currency']) stake = round_coin_value(config['stake_amount'], config['stake_currency']) - raise OperationalException(f"Starting balance ({wallet}) " - f"is smaller than stake_amount {stake}.") + raise OperationalException( + f"Starting balance ({wallet}) is smaller than stake_amount {stake}. " + f"Wallet is calculated as `dry_run_wallet * tradable_balance_ratio`." + ) return config From abc8854b5a1b5d3161241685b26509329db5c4be Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Tue, 1 Mar 2022 17:36:11 -0600 Subject: [PATCH 031/114] setup.sh install gettext for mac --- setup.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.sh b/setup.sh index c642a654d..865a1cc89 100755 --- a/setup.sh +++ b/setup.sh @@ -132,6 +132,9 @@ function install_macos() { echo_block "Installing Brew" /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" fi + + brew install gettext + #Gets number after decimal in python version version=$(egrep -o 3.\[0-9\]+ <<< $PYTHON | sed 's/3.//g') From 71be547d82629d748436f34c19097094d3313e87 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 2 Mar 2022 06:26:00 +0100 Subject: [PATCH 032/114] Bump ccxt to 1.74.63 closes #6484 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c524eb49d..bcdcb02a9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.22.2 pandas==1.4.1 pandas-ta==0.3.14b -ccxt==1.74.43 +ccxt==1.74.63 # Pin cryptography for now due to rust build errors with piwheels cryptography==36.0.1 aiohttp==3.8.1 From 17c9c3caf33d14abfce3a03ec19774a88193f01d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 26 Feb 2022 16:10:54 +0100 Subject: [PATCH 033/114] Enable orders via API --- freqtrade/persistence/models.py | 7 ++++--- freqtrade/rpc/api_server/api_schemas.py | 18 ++++++++++++++++++ freqtrade/rpc/api_server/api_v1.py | 3 ++- tests/rpc/test_rpc.py | 4 ++-- tests/rpc/test_rpc_apiserver.py | 3 +++ 5 files changed, 29 insertions(+), 6 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 559c7e94a..39f430124 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -120,7 +120,7 @@ class Order(_DECL_BASE): ft_pair: str = Column(String(25), nullable=False) ft_is_open = Column(Boolean, nullable=False, default=True, index=True) - order_id = Column(String(255), nullable=False, index=True) + order_id: str = Column(String(255), nullable=False, index=True) status = Column(String(255), nullable=True) symbol = Column(String(25), nullable=True) order_type: str = Column(String(50), nullable=True) @@ -193,6 +193,9 @@ class Order(_DECL_BASE): def to_json(self) -> Dict[str, Any]: return { + 'pair': self.ft_pair, + 'order_id': self.order_id, + 'status': self.status, 'amount': self.amount, 'average': round(self.average, 8) if self.average else 0, 'safe_price': self.safe_price, @@ -209,10 +212,8 @@ class Order(_DECL_BASE): 'order_filled_timestamp': int(self.order_filled_date.replace( tzinfo=timezone.utc).timestamp() * 1000) if self.order_filled_date else None, 'order_type': self.order_type, - 'pair': self.ft_pair, 'price': self.price, 'remaining': self.remaining, - 'status': self.status, } def close_bt_order(self, close_date: datetime): diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index e22cf82b3..2914cd688 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -177,6 +177,22 @@ class ShowConfig(BaseModel): max_entry_position_adjustment: int +class OrderSchema(BaseModel): + pair: str + order_id: str + status: str + remaining: float + amount: float + safe_price: float + cost: float + filled: float + ft_order_side: str + order_type: str + is_open: bool + order_timestamp: Optional[int] + order_filled_timestamp: Optional[int] + + class TradeSchema(BaseModel): trade_id: int pair: str @@ -224,6 +240,8 @@ class TradeSchema(BaseModel): min_rate: Optional[float] max_rate: Optional[float] open_order_id: Optional[str] + filled_entry_orders: List[OrderSchema] + filled_exit_orders: List[OrderSchema] class OpenTradeSchema(TradeSchema): diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index f072e2b14..6379150ee 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -32,7 +32,8 @@ logger = logging.getLogger(__name__) # 1.11: forcebuy and forcesell accept ordertype # 1.12: add blacklist delete endpoint # 1.13: forcebuy supports stake_amount -API_VERSION = 1.13 +# 1.14: Add entry/exit orders to trade response +API_VERSION = 1.14 # Public API, requires no auth. router_public = APIRouter() diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 7d0704d2f..8b3865172 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -114,7 +114,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy', 'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY, 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, - 'is_open': False, 'pair': 'ETH/BTC', + 'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY, 'remaining': ANY, 'status': ANY}], 'filled_exit_orders': [] } @@ -189,7 +189,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy', 'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY, 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, - 'is_open': False, 'pair': 'ETH/BTC', + 'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY, 'remaining': ANY, 'status': ANY}], 'filled_exit_orders': [] } diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index de7dca47b..d78ab2b39 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -902,6 +902,9 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'buy_tag': None, 'timeframe': 5, 'exchange': 'binance', + 'filled_entry_orders': [], + 'filled_exit_orders': [], + } mocker.patch('freqtrade.exchange.Exchange.get_rate', From e9456cdf15d834461f259f4942a70930aff71717 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 27 Feb 2022 16:54:14 +0100 Subject: [PATCH 034/114] Update trade response to use a single Order object --- freqtrade/persistence/models.py | 12 ++---------- freqtrade/rpc/api_server/api_schemas.py | 3 +-- freqtrade/rpc/telegram.py | 8 +++++--- tests/rpc/test_rpc.py | 6 ++---- tests/rpc/test_rpc_apiserver.py | 4 ++-- tests/rpc/test_rpc_telegram.py | 2 +- tests/test_persistence.py | 6 ++---- 7 files changed, 15 insertions(+), 26 deletions(-) diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 39f430124..af093a1eb 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -340,14 +340,7 @@ class LocalTrade(): def to_json(self) -> Dict[str, Any]: filled_orders = self.select_filled_orders() - filled_entries = [] - filled_exits = [] - if len(filled_orders) > 0: - for order in filled_orders: - if order.ft_order_side == 'buy': - filled_entries.append(order.to_json()) - if order.ft_order_side == 'sell': - filled_exits.append(order.to_json()) + orders = [order.to_json() for order in filled_orders] return { 'trade_id': self.id, @@ -412,8 +405,7 @@ class LocalTrade(): 'max_rate': self.max_rate, 'open_order_id': self.open_order_id, - 'filled_entry_orders': filled_entries, - 'filled_exit_orders': filled_exits, + 'orders': orders, } @staticmethod diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index 2914cd688..32c7e9214 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -240,8 +240,7 @@ class TradeSchema(BaseModel): min_rate: Optional[float] max_rate: Optional[float] open_order_id: Optional[str] - filled_entry_orders: List[OrderSchema] - filled_exit_orders: List[OrderSchema] + orders: List[OrderSchema] class OpenTradeSchema(TradeSchema): diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 69f7f2858..5a20520dd 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -379,6 +379,8 @@ class Telegram(RPCHandler): first_avg = filled_orders[0]["safe_price"] for x, order in enumerate(filled_orders): + if order['ft_order_side'] != 'buy': + continue cur_entry_datetime = arrow.get(order["order_filled_date"]) cur_entry_amount = order["amount"] cur_entry_average = order["safe_price"] @@ -444,7 +446,7 @@ class Telegram(RPCHandler): messages = [] for r in results: r['open_date_hum'] = arrow.get(r['open_date']).humanize() - r['num_entries'] = len(r['filled_entry_orders']) + r['num_entries'] = len([o for o in r['orders'] if o['ft_order_side'] == 'buy']) r['sell_reason'] = r.get('sell_reason', "") lines = [ "*Trade ID:* `{trade_id}`" + @@ -488,8 +490,8 @@ class Telegram(RPCHandler): lines.append("*Open Order:* `{open_order}`") lines_detail = self._prepare_entry_details( - r['filled_entry_orders'], r['base_currency'], r['is_open']) - lines.extend((lines_detail if (len(r['filled_entry_orders']) > 1) else "")) + r['orders'], r['base_currency'], r['is_open']) + lines.extend(lines_detail if lines_detail else "") # Filter empty lines using list-comprehension messages.append("\n".join([line for line in lines if line]).format(**r)) diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 8b3865172..6bfee8e86 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -109,14 +109,13 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'stoploss_entry_dist_ratio': -0.10448878, 'open_order': None, 'exchange': 'binance', - 'filled_entry_orders': [{ + 'orders': [{ 'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05, 'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy', 'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY, 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, 'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY, 'remaining': ANY, 'status': ANY}], - 'filled_exit_orders': [] } mocker.patch('freqtrade.exchange.Exchange.get_rate', @@ -184,14 +183,13 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None: 'stoploss_entry_dist_ratio': -0.10448878, 'open_order': None, 'exchange': 'binance', - 'filled_entry_orders': [{ + 'orders': [{ 'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05, 'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy', 'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY, 'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05, 'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY, 'remaining': ANY, 'status': ANY}], - 'filled_exit_orders': [] } diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index d78ab2b39..84a18440e 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -902,8 +902,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets): 'buy_tag': None, 'timeframe': 5, 'exchange': 'binance', - 'filled_entry_orders': [], - 'filled_exit_orders': [], + 'orders': [ANY], } @@ -1092,6 +1091,7 @@ def test_api_forcebuy(botclient, mocker, fee): 'buy_tag': None, 'timeframe': 5, 'exchange': 'binance', + 'orders': [], } diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index ccf61f91b..f53f48cc2 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -203,7 +203,7 @@ def test_telegram_status(default_conf, update, mocker) -> None: 'stop_loss_ratio': -0.0001, 'open_order': '(limit buy rem=0.00000000)', 'is_open': True, - 'filled_entry_orders': [] + 'orders': [] }]), ) diff --git a/tests/test_persistence.py b/tests/test_persistence.py index 0f00bd4bb..32253d1cb 100644 --- a/tests/test_persistence.py +++ b/tests/test_persistence.py @@ -901,8 +901,7 @@ def test_to_json(default_conf, fee): 'buy_tag': None, 'timeframe': None, 'exchange': 'binance', - 'filled_entry_orders': [], - 'filled_exit_orders': [] + 'orders': [], } # Simulate dry_run entries @@ -970,8 +969,7 @@ def test_to_json(default_conf, fee): 'buy_tag': 'buys_signal_001', 'timeframe': None, 'exchange': 'binance', - 'filled_entry_orders': [], - 'filled_exit_orders': [] + 'orders': [], } From c0e12d632f269b3043ccdc08c4355733de4daa6e Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 3 Mar 2022 19:19:10 +0100 Subject: [PATCH 035/114] Add FTX ref links --- README.md | 2 +- docs/index.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 166e4833a..efa334a27 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Please read the [exchange specific notes](docs/exchanges.md) to learn about even - [X] [Binance](https://www.binance.com/) - [X] [Bittrex](https://bittrex.com/) -- [X] [FTX](https://ftx.com) +- [X] [FTX](https://ftx.com/#a=2258149) - [X] [Gate.io](https://www.gate.io/ref/6266643) - [X] [Huobi](http://huobi.com/) - [X] [Kraken](https://kraken.com/) diff --git a/docs/index.md b/docs/index.md index 32b19bd94..2aa80c240 100644 --- a/docs/index.md +++ b/docs/index.md @@ -44,7 +44,7 @@ Please read the [exchange specific notes](exchanges.md) to learn about eventual, - [X] [Binance](https://www.binance.com/) - [X] [Bittrex](https://bittrex.com/) -- [X] [FTX](https://ftx.com) +- [X] [FTX](https://ftx.com/#a=2258149) - [X] [Gate.io](https://www.gate.io/ref/6266643) - [X] [Huobi](http://huobi.com/) - [X] [Kraken](https://kraken.com/) From be4bc4955c2ffb4e72e603bb92fb62123a8767e2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 5 Mar 2022 14:11:21 +0100 Subject: [PATCH 036/114] Explicitly check for None to determine if initial stoploss was set closes #6460 --- freqtrade/constants.py | 2 +- freqtrade/persistence/models.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/freqtrade/constants.py b/freqtrade/constants.py index 066a07c62..200917d43 100644 --- a/freqtrade/constants.py +++ b/freqtrade/constants.py @@ -140,7 +140,7 @@ CONF_SCHEMA = { 'minProperties': 1 }, 'amount_reserve_percent': {'type': 'number', 'minimum': 0.0, 'maximum': 0.5}, - 'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True}, + 'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True, 'minimum': -1}, 'trailing_stop': {'type': 'boolean'}, 'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1}, 'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1}, diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index af093a1eb..92d1def30 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -304,7 +304,7 @@ class LocalTrade(): # absolute value of the initial stop loss initial_stop_loss: float = 0.0 # percentage value of the initial stop loss - initial_stop_loss_pct: float = 0.0 + initial_stop_loss_pct: Optional[float] = None # stoploss order id which is on exchange stoploss_order_id: Optional[str] = None # last update time of the stoploss order on exchange @@ -446,7 +446,8 @@ class LocalTrade(): new_loss = float(current_price * (1 - abs(stoploss))) # no stop loss assigned yet - if not self.stop_loss: + # if not self.stop_loss: + if self.initial_stop_loss_pct is None: logger.debug(f"{self.pair} - Assigning new stoploss...") self._set_new_stoploss(new_loss, stoploss) self.initial_stop_loss = new_loss @@ -786,6 +787,7 @@ class LocalTrade(): logger.info(f"Stoploss for {trade} needs adjustment...") # Force reset of stoploss trade.stop_loss = None + trade.initial_stop_loss_pct = None trade.adjust_stop_loss(trade.open_rate, desired_stoploss) logger.info(f"New stoploss: {trade.stop_loss}.") From d74e3091de415cf7ee7722b7afe14968feab1cec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Mar 2022 03:01:09 +0000 Subject: [PATCH 037/114] Bump fastapi from 0.74.1 to 0.75.0 Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.74.1 to 0.75.0. - [Release notes](https://github.com/tiangolo/fastapi/releases) - [Commits](https://github.com/tiangolo/fastapi/compare/0.74.1...0.75.0) --- updated-dependencies: - dependency-name: fastapi dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bcdcb02a9..2be9bfe21 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,7 +31,7 @@ python-rapidjson==1.6 sdnotify==0.3.2 # API Server -fastapi==0.74.1 +fastapi==0.75.0 uvicorn==0.17.5 pyjwt==2.3.0 aiofiles==0.8.0 From 708def3d96289c33b297c1b083b2aead3dbc4e37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Mar 2022 03:01:15 +0000 Subject: [PATCH 038/114] Bump sqlalchemy from 1.4.31 to 1.4.32 Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 1.4.31 to 1.4.32. - [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases) - [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES) - [Commits](https://github.com/sqlalchemy/sqlalchemy/commits) --- updated-dependencies: - dependency-name: sqlalchemy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bcdcb02a9..875a3e885 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ ccxt==1.74.63 # Pin cryptography for now due to rust build errors with piwheels cryptography==36.0.1 aiohttp==3.8.1 -SQLAlchemy==1.4.31 +SQLAlchemy==1.4.32 python-telegram-bot==13.11 arrow==1.2.2 cachetools==4.2.2 From 67a8b8b6317eea1512dda812134736f5e9bfdfc5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Mar 2022 03:01:19 +0000 Subject: [PATCH 039/114] Bump pytest-asyncio from 0.18.1 to 0.18.2 Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.18.1 to 0.18.2. - [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases) - [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.18.1...v0.18.2) --- updated-dependencies: - dependency-name: pytest-asyncio dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 9fc8a18ad..f81844d26 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,7 +8,7 @@ flake8==4.0.1 flake8-tidy-imports==4.6.0 mypy==0.931 pytest==7.0.1 -pytest-asyncio==0.18.1 +pytest-asyncio==0.18.2 pytest-cov==3.0.0 pytest-mock==3.7.0 pytest-random-order==1.0.4 From 1d63bb66a98332a006af255dfe2a2c51d59d9fe2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Mar 2022 03:01:23 +0000 Subject: [PATCH 040/114] Bump mkdocs-material from 8.2.3 to 8.2.5 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 8.2.3 to 8.2.5. - [Release notes](https://github.com/squidfunk/mkdocs-material/releases) - [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG) - [Commits](https://github.com/squidfunk/mkdocs-material/compare/8.2.3...8.2.5) --- updated-dependencies: - dependency-name: mkdocs-material dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 839485629..0ca0e4b63 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,4 +1,4 @@ mkdocs==1.2.3 -mkdocs-material==8.2.3 +mkdocs-material==8.2.5 mdx_truly_sane_lists==1.2 pymdown-extensions==9.2 From f1d2cb9ce42f2ae8c1b8d62c7092ffbffa4c832e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Mar 2022 03:01:25 +0000 Subject: [PATCH 041/114] Bump types-cachetools from 4.2.9 to 4.2.10 Bumps [types-cachetools](https://github.com/python/typeshed) from 4.2.9 to 4.2.10. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-cachetools dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 9fc8a18ad..1e50be196 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -20,7 +20,7 @@ time-machine==2.6.0 nbconvert==6.4.2 # mypy types -types-cachetools==4.2.9 +types-cachetools==4.2.10 types-filelock==3.2.5 types-requests==2.27.11 types-tabulate==0.8.5 From 0c8dd7e50233110b414dc5afac8e6ca87d16a15f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Mar 2022 03:01:33 +0000 Subject: [PATCH 042/114] Bump ccxt from 1.74.63 to 1.75.12 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.74.63 to 1.75.12. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg) - [Commits](https://github.com/ccxt/ccxt/compare/1.74.63...1.75.12) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bcdcb02a9..769b0de4a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.22.2 pandas==1.4.1 pandas-ta==0.3.14b -ccxt==1.74.63 +ccxt==1.75.12 # Pin cryptography for now due to rust build errors with piwheels cryptography==36.0.1 aiohttp==3.8.1 From 25964f70d8dd560852905c04e406e1c45455f86a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Mar 2022 03:12:15 +0000 Subject: [PATCH 043/114] Bump actions/checkout from 2 to 3 Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 12 ++++++------ .github/workflows/docker_update_readme.yml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 407894bd2..6b576bd5d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: python-version: ["3.8", "3.9", "3.10"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v2 @@ -118,7 +118,7 @@ jobs: python-version: ["3.8", "3.9", "3.10"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v2 @@ -210,7 +210,7 @@ jobs: python-version: ["3.8", "3.9", "3.10"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v2 @@ -262,7 +262,7 @@ jobs: docs_check: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Documentation syntax run: | @@ -325,7 +325,7 @@ jobs: if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'release') && github.repository == 'freqtrade/freqtrade' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python uses: actions/setup-python@v2 @@ -405,7 +405,7 @@ jobs: if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'release') && github.repository == 'freqtrade/freqtrade' steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Extract branch name shell: bash diff --git a/.github/workflows/docker_update_readme.yml b/.github/workflows/docker_update_readme.yml index 95a6e82e2..ebb773ad7 100644 --- a/.github/workflows/docker_update_readme.yml +++ b/.github/workflows/docker_update_readme.yml @@ -8,7 +8,7 @@ jobs: dockerHubDescription: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v3 - name: Docker Hub Description uses: peter-evans/dockerhub-description@v2.4.3 env: From 3c83d8c74a8078dc0779614318454f63597e4fe9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Mar 2022 05:24:08 +0000 Subject: [PATCH 044/114] Bump actions/setup-python from 2 to 3 Bumps [actions/setup-python](https://github.com/actions/setup-python) from 2 to 3. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](https://github.com/actions/setup-python/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/setup-python dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6b576bd5d..216a53bc1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} @@ -121,7 +121,7 @@ jobs: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} @@ -213,7 +213,7 @@ jobs: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: ${{ matrix.python-version }} @@ -269,7 +269,7 @@ jobs: ./tests/test_docs.sh - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: 3.8 @@ -328,7 +328,7 @@ jobs: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v3 with: python-version: 3.8 From 17041b78fc1ba574fcd9de865e25e5658c23e298 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 7 Mar 2022 19:39:15 +0100 Subject: [PATCH 045/114] Add stoploss-limit-ratio to full config sample --- config_examples/config_full.example.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config_examples/config_full.example.json b/config_examples/config_full.example.json index d675fb1a9..7931476b4 100644 --- a/config_examples/config_full.example.json +++ b/config_examples/config_full.example.json @@ -56,7 +56,8 @@ "forcebuy": "market", "stoploss": "market", "stoploss_on_exchange": false, - "stoploss_on_exchange_interval": 60 + "stoploss_on_exchange_interval": 60, + "stoploss_on_exchange_limit_ratio": 0.99 }, "order_time_in_force": { "buy": "gtc", From f2ed6165e936fc2b44c4fb5d24bc0b62ae774ea3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 8 Mar 2022 19:35:30 +0100 Subject: [PATCH 046/114] convert price to precision price before verifying stoploss adjustment closes #6504 --- freqtrade/freqtradebot.py | 4 +++- tests/test_freqtradebot.py | 26 ++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 207bfbc12..ac6ad65de 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -907,7 +907,9 @@ class FreqtradeBot(LoggingMixin): :param order: Current on exchange stoploss order :return: None """ - if self.exchange.stoploss_adjust(trade.stop_loss, order): + stoploss_norm = self.exchange.price_to_precision(trade.pair, trade.stop_loss) + + if self.exchange.stoploss_adjust(stoploss_norm, order): # we check if the update is necessary update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60) if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() >= update_beat: diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 7ed1a68f8..e5f8d3694 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1360,6 +1360,32 @@ def test_handle_stoploss_on_exchange_trailing_error( assert log_has_re(r"Could not create trailing stoploss order for pair ETH/USDT\..*", caplog) +def test_stoploss_on_exchange_price_rounding( + mocker, default_conf_usdt, fee, open_trade_usdt) -> None: + patch_RPCManager(mocker) + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + get_fee=fee, + ) + price_mock = MagicMock(side_effect=lambda p, s: int(s)) + stoploss_mock = MagicMock(return_value={'id': '13434334'}) + adjust_mock = MagicMock(return_value=False) + mocker.patch.multiple( + 'freqtrade.exchange.Binance', + stoploss=stoploss_mock, + stoploss_adjust=adjust_mock, + price_to_precision=price_mock, + ) + freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + open_trade_usdt.stoploss_order_id = '13434334' + open_trade_usdt.stop_loss = 222.55 + + freqtrade.handle_trailing_stoploss_on_exchange(open_trade_usdt, {}) + assert price_mock.call_count == 1 + assert adjust_mock.call_count == 1 + assert adjust_mock.call_args_list[0][0][0] == 222 + + @pytest.mark.usefixtures("init_persistence") def test_handle_stoploss_on_exchange_custom_stop( mocker, default_conf_usdt, fee, limit_buy_order_usdt, limit_sell_order_usdt) -> None: From 61182f849bd252b5b38a91c48fe29b25e7f01ebb Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Wed, 9 Mar 2022 00:45:10 -0600 Subject: [PATCH 047/114] exchange.fetch_order and exchange.cancel_order added params argument --- freqtrade/exchange/exchange.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index da89a7c8a..81d20fd21 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -376,7 +376,7 @@ class Exchange: raise OperationalException( 'Could not load markets, therefore cannot start. ' 'Please investigate the above error for more details.' - ) + ) quote_currencies = self.get_quote_currencies() if stake_currency not in quote_currencies: raise OperationalException( @@ -882,11 +882,11 @@ class Exchange: raise OperationalException(e) from e @retrier(retries=API_FETCH_ORDER_RETRY_COUNT) - def fetch_order(self, order_id: str, pair: str) -> Dict: + def fetch_order(self, order_id: str, pair: str, params={}) -> Dict: if self._config['dry_run']: return self.fetch_dry_run_order(order_id) try: - order = self._api.fetch_order(order_id, pair) + order = self._api.fetch_order(order_id, pair, params=params) self._log_exchange_response('fetch_order', order) return order except ccxt.OrderNotFound as e: @@ -929,7 +929,7 @@ class Exchange: and order.get('filled') == 0.0) @retrier - def cancel_order(self, order_id: str, pair: str) -> Dict: + def cancel_order(self, order_id: str, pair: str, params={}) -> Dict: if self._config['dry_run']: try: order = self.fetch_dry_run_order(order_id) @@ -940,7 +940,7 @@ class Exchange: return {} try: - order = self._api.cancel_order(order_id, pair) + order = self._api.cancel_order(order_id, pair, params=params) self._log_exchange_response('cancel_order', order) return order except ccxt.InvalidOrder as e: From e3ced55f5c2979c30d3ad849ef6e20a000bd12da Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Wed, 9 Mar 2022 00:45:50 -0600 Subject: [PATCH 048/114] gateio.fetch_order and gateio.cancel_order --- freqtrade/exchange/gateio.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/gateio.py b/freqtrade/exchange/gateio.py index 7e1f21921..7580281cf 100644 --- a/freqtrade/exchange/gateio.py +++ b/freqtrade/exchange/gateio.py @@ -31,4 +31,18 @@ class Gateio(Exchange): if any(v == 'market' for k, v in order_types.items()): raise OperationalException( - f'Exchange {self.name} does not support market orders.') + f'Exchange {self.name} does not support market orders.') + + def fetch_stoploss_order(self, order_id: str, pair: str, params={}) -> Dict: + return self.fetch_order( + order_id=order_id, + pair=pair, + params={'stop': True} + ) + + def cancel_stoploss_order(self, order_id: str, pair: str, params={}) -> Dict: + return self.cancel_order( + order_id=order_id, + pair=pair, + params={'stop': True} + ) From ae4742afcb78d58d30ec88125cdb526204780a62 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Wed, 9 Mar 2022 00:59:28 -0600 Subject: [PATCH 049/114] test_fetch_stoploss_order_gateio and test_cancel_stoploss_order_gateio --- tests/exchange/test_gateio.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/exchange/test_gateio.py b/tests/exchange/test_gateio.py index 6f7862909..a3ffcad65 100644 --- a/tests/exchange/test_gateio.py +++ b/tests/exchange/test_gateio.py @@ -1,8 +1,11 @@ +from unittest.mock import MagicMock + import pytest from freqtrade.exceptions import OperationalException from freqtrade.exchange import Gateio from freqtrade.resolvers.exchange_resolver import ExchangeResolver +from tests.conftest import get_patched_exchange def test_validate_order_types_gateio(default_conf, mocker): @@ -26,3 +29,29 @@ def test_validate_order_types_gateio(default_conf, mocker): with pytest.raises(OperationalException, match=r'Exchange .* does not support market orders.'): ExchangeResolver.load_exchange('gateio', default_conf, True) + + +def test_fetch_stoploss_order_gateio(default_conf, mocker): + exchange = get_patched_exchange(mocker, default_conf, id='gateio') + + fetch_order_mock = MagicMock() + exchange.fetch_order = fetch_order_mock + + exchange.fetch_stoploss_order('1234', 'ETH/BTC') + assert fetch_order_mock.call_count == 1 + assert fetch_order_mock.call_args_list[0][1]['order_id'] == '1234' + assert fetch_order_mock.call_args_list[0][1]['pair'] == 'ETH/BTC' + assert fetch_order_mock.call_args_list[0][1]['params'] == {'stop': True} + + +def test_cancel_stoploss_order_gateio(default_conf, mocker): + exchange = get_patched_exchange(mocker, default_conf, id='gateio') + + cancel_order_mock = MagicMock() + exchange.cancel_order = cancel_order_mock + + exchange.cancel_stoploss_order('1234', 'ETH/BTC') + assert cancel_order_mock.call_count == 1 + assert cancel_order_mock.call_args_list[0][1]['order_id'] == '1234' + assert cancel_order_mock.call_args_list[0][1]['pair'] == 'ETH/BTC' + assert cancel_order_mock.call_args_list[0][1]['params'] == {'stop': True} From d47274066ecdf09e5364773efd32853e2a763d7c Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Wed, 9 Mar 2022 01:05:21 -0600 Subject: [PATCH 050/114] Added stoploss_on_exchange flag to gateio --- freqtrade/exchange/gateio.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/exchange/gateio.py b/freqtrade/exchange/gateio.py index 7580281cf..9f857c344 100644 --- a/freqtrade/exchange/gateio.py +++ b/freqtrade/exchange/gateio.py @@ -22,6 +22,7 @@ class Gateio(Exchange): _ft_has: Dict = { "ohlcv_candle_limit": 1000, "ohlcv_volume_currency": "quote", + "stoploss_on_exchange": True, } _headers = {'X-Gate-Channel-Id': 'freqtrade'} From 6e10439f908a11a41501b3dad216f1ad8e90b80a Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 9 Mar 2022 17:35:41 +0100 Subject: [PATCH 051/114] Map usdt fiat to correct coingecko fiat --- freqtrade/rpc/fiat_convert.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/rpc/fiat_convert.py b/freqtrade/rpc/fiat_convert.py index 82a6a4778..70f3647b6 100644 --- a/freqtrade/rpc/fiat_convert.py +++ b/freqtrade/rpc/fiat_convert.py @@ -23,6 +23,7 @@ coingecko_mapping = { 'eth': 'ethereum', 'bnb': 'binancecoin', 'sol': 'solana', + 'usdt': 'tether', } From 6bb93bdc25d3a00c97aca3eb2ab6a8355f1ad268 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Wed, 9 Mar 2022 15:47:16 -0600 Subject: [PATCH 052/114] moved binance.stoploss_adjust to exchange class --- freqtrade/exchange/binance.py | 7 ------- freqtrade/exchange/exchange.py | 2 +- tests/exchange/test_exchange.py | 12 ++++++++++++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 37ead6dd8..ecdd002b2 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -23,13 +23,6 @@ class Binance(Exchange): "l2_limit_range": [5, 10, 20, 50, 100, 500, 1000], } - def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: - """ - Verify stop_loss against stoploss-order value (limit or price) - Returns True if adjustment is necessary. - """ - return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice']) - async def _async_get_historic_ohlcv(self, pair: str, timeframe: str, since_ms: int, is_new_pair: bool = False, raise_: bool = False diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 81d20fd21..8db261f63 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -795,7 +795,7 @@ class Exchange: Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ - raise OperationalException(f"stoploss is not implemented for {self.name}.") + return stop_loss > float(order['stopPrice']) def _get_stop_params(self, ordertype: str, stop_price: float) -> Dict: params = self._params.copy() diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 527e8050b..1b33e836b 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -3058,3 +3058,15 @@ def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_r ]) def test_calculate_backoff(retrycount, max_retries, expected): assert calculate_backoff(retrycount, max_retries) == expected + + +@pytest.mark.parametrize('exchange_name', ['binance', 'gateio']) +def test_stoploss_adjust(mocker, default_conf, exchange_name): + exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) + order = { + 'type': 'stop_loss_limit', + 'price': 1500, + 'stopPrice': 1500, + } + assert exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1499, order) From 7db28b1b1697b937af0106668250670a1c22ca94 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Wed, 9 Mar 2022 15:54:17 -0600 Subject: [PATCH 053/114] gateio stoploss docs --- docs/exchanges.md | 3 +++ docs/stoploss.md | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 8adf19081..df11a5971 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -210,6 +210,9 @@ OKX requires a passphrase for each api key, you will therefore need to add this ## Gate.io +!!! Tip "Stoploss on Exchange" + Binance supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange.. + Gate.io allows the use of `POINT` to pay for fees. As this is not a tradable currency (no regular market available), automatic fee calculations will fail (and default to a fee of 0). The configuration parameter `exchange.unknown_fee_rate` can be used to specify the exchange rate between Point and the stake currency. Obviously, changing the stake-currency will also require changes to this value. diff --git a/docs/stoploss.md b/docs/stoploss.md index d0e106d8f..62081b540 100644 --- a/docs/stoploss.md +++ b/docs/stoploss.md @@ -24,7 +24,7 @@ These modes can be configured with these values: ``` !!! Note - Stoploss on exchange is only supported for Binance (stop-loss-limit), Huobi (stop-limit), Kraken (stop-loss-market, stop-loss-limit), FTX (stop limit and stop-market) and kucoin (stop-limit and stop-market) as of now. + Stoploss on exchange is only supported for Binance (stop-loss-limit), Huobi (stop-limit), Kraken (stop-loss-market, stop-loss-limit), FTX (stop limit and stop-market) Gateio (stop-limit), and Kucoin (stop-limit and stop-market) as of now. Do not set too low/tight stoploss value if using stop loss on exchange! If set to low/tight then you have greater risk of missing fill on the order and stoploss will not work. From 6f4d6079023fc75987f7c08087ea4d9e16020387 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Wed, 9 Mar 2022 19:31:51 -0600 Subject: [PATCH 054/114] stoploss_adjust fixed breaking tests --- tests/exchange/test_binance.py | 14 -------------- tests/exchange/test_exchange.py | 3 --- tests/test_freqtradebot.py | 4 +--- 3 files changed, 1 insertion(+), 20 deletions(-) diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index d88ae9b1d..bf9340217 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -94,20 +94,6 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker): assert order['amount'] == 1 -def test_stoploss_adjust_binance(mocker, default_conf): - exchange = get_patched_exchange(mocker, default_conf, id='binance') - order = { - 'type': 'stop_loss_limit', - 'price': 1500, - 'info': {'stopPrice': 1500}, - } - assert exchange.stoploss_adjust(1501, order) - assert not exchange.stoploss_adjust(1499, order) - # Test with invalid order case - order['type'] = 'stop_loss' - assert not exchange.stoploss_adjust(1501, order) - - @pytest.mark.asyncio async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog): ohlcv = [ diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 1b33e836b..fa9020ba4 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2698,9 +2698,6 @@ def test_stoploss_order_unsupported_exchange(default_conf, mocker): with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) - with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): - exchange.stoploss_adjust(1, {}) - def test_merge_ft_has_dict(default_conf, mocker): mocker.patch.multiple('freqtrade.exchange.Exchange', diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index e5f8d3694..492e55715 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1563,9 +1563,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, 'type': 'stop_loss_limit', 'price': 3, 'average': 2, - 'info': { - 'stopPrice': '2.178' - } + 'stopPrice': '2.178' }) mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hanging) From 9ff52c0a93666daeaf00b46c64a82bc8a953f38d Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 11 Mar 2022 08:00:18 +0100 Subject: [PATCH 055/114] Add test for emergencysell behaviour --- tests/test_freqtradebot.py | 47 +++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index e5f8d3694..1aeb56cdd 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -926,12 +926,10 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, }), create_order=MagicMock(side_effect=[ {'id': limit_buy_order_usdt['id']}, - {'id': limit_sell_order_usdt['id']}, + limit_sell_order_usdt, + # {'id': limit_sell_order_usdt['id']}, ]), get_fee=fee, - ) - mocker.patch.multiple( - 'freqtrade.exchange.Binance', stoploss=stoploss ) freqtrade = FreqtradeBot(default_conf_usdt) @@ -956,7 +954,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, trade.stoploss_order_id = 100 hanging_stoploss_order = MagicMock(return_value={'status': 'open'}) - mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', hanging_stoploss_order) + mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', hanging_stoploss_order) assert freqtrade.handle_stoploss_on_exchange(trade) is False assert trade.stoploss_order_id == 100 @@ -969,7 +967,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, trade.stoploss_order_id = 100 canceled_stoploss_order = MagicMock(return_value={'status': 'canceled'}) - mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', canceled_stoploss_order) + mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', canceled_stoploss_order) stoploss.reset_mock() assert freqtrade.handle_stoploss_on_exchange(trade) is False @@ -1001,7 +999,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, 'average': 2, 'amount': limit_buy_order_usdt['amount'], }) - mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', stoploss_order_hit) + mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hit) assert freqtrade.handle_stoploss_on_exchange(trade) is True assert log_has_re(r'STOP_LOSS_LIMIT is hit for Trade\(id=1, .*\)\.', caplog) assert trade.stoploss_order_id is None @@ -1009,7 +1007,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, caplog.clear() mocker.patch( - 'freqtrade.exchange.Binance.stoploss', + 'freqtrade.exchange.Exchange.stoploss', side_effect=ExchangeError() ) trade.is_open = True @@ -1021,9 +1019,9 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, # It should try to add stoploss order trade.stoploss_order_id = 100 stoploss.reset_mock() - mocker.patch('freqtrade.exchange.Binance.fetch_stoploss_order', + mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', side_effect=InvalidOrderException()) - mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss) + mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss) freqtrade.handle_stoploss_on_exchange(trade) assert stoploss.call_count == 1 @@ -1033,10 +1031,37 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, trade.is_open = False stoploss.reset_mock() mocker.patch('freqtrade.exchange.Exchange.fetch_order') - mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss) + mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss) assert freqtrade.handle_stoploss_on_exchange(trade) is False assert stoploss.call_count == 0 + # Seventh case: emergency exit triggered + # Trailing stop should not act anymore + stoploss_order_cancelled = MagicMock(side_effect=[{ + 'id': "100", + 'status': 'canceled', + 'type': 'stop_loss_limit', + 'price': 3, + 'average': 2, + 'amount': limit_buy_order_usdt['amount'], + 'info': {'stopPrice': 22}, + }]) + trade.stoploss_order_id = 100 + trade.is_open = True + trade.stoploss_last_update = arrow.utcnow().shift(hours=-1).datetime + trade.stop_loss = 24 + freqtrade.config['trailing_stop'] = True + stoploss = MagicMock(side_effect=InvalidOrderException()) + + mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order_with_result', + side_effect=InvalidOrderException()) + mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_cancelled) + mocker.patch('freqtrade.exchange.Exchange.stoploss', stoploss) + assert freqtrade.handle_stoploss_on_exchange(trade) is False + assert trade.stoploss_order_id is None + assert trade.is_open is False + assert trade.sell_reason == str(SellType.EMERGENCY_SELL) + def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, limit_buy_order_usdt, limit_sell_order_usdt) -> None: From 24f480b4ced4a9e0ea9ec09c93bf38911d57feef Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 11 Mar 2022 08:27:42 +0100 Subject: [PATCH 056/114] Double-check stoploss behaviour closes #6508 --- freqtrade/freqtradebot.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ac6ad65de..16864f814 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -873,11 +873,15 @@ class FreqtradeBot(LoggingMixin): stop_price = trade.open_rate * (1 + stoploss) if self.create_stoploss_order(trade=trade, stop_price=stop_price): + # The above will return False if the placement failed and the trade was force-sold. + # in which case the trade will be closed - which we must check below. trade.stoploss_last_update = datetime.utcnow() return False # If stoploss order is canceled for some reason we add it - if stoploss_order and stoploss_order['status'] in ('canceled', 'cancelled'): + if (trade.is_open + and stoploss_order + and stoploss_order['status'] in ('canceled', 'cancelled')): if self.create_stoploss_order(trade=trade, stop_price=trade.stop_loss): return False else: @@ -887,7 +891,7 @@ class FreqtradeBot(LoggingMixin): # Finally we check if stoploss on exchange should be moved up because of trailing. # Triggered Orders are now real orders - so don't replace stoploss anymore if ( - stoploss_order + trade.is_open and stoploss_order and stoploss_order.get('status_stop') != 'triggered' and (self.config.get('trailing_stop', False) or self.config.get('use_custom_stoploss', False)) From 11c76c3c89ed2b9090997161a764080f6d27ff1f Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 11 Mar 2022 17:59:57 +0100 Subject: [PATCH 057/114] Check if timeframe is available before calling exchange closes #6517 --- freqtrade/exchange/exchange.py | 39 ++++++++++++++++++++------------- tests/conftest.py | 2 ++ tests/exchange/test_exchange.py | 7 ++++++ 3 files changed, 33 insertions(+), 15 deletions(-) diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index da89a7c8a..cfd12622b 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -9,7 +9,7 @@ import logging from copy import deepcopy from datetime import datetime, timedelta, timezone from math import ceil -from typing import Any, Dict, List, Optional, Tuple +from typing import Any, Coroutine, Dict, List, Optional, Tuple import arrow import ccxt @@ -1371,6 +1371,22 @@ class Exchange: data = sorted(data, key=lambda x: x[0]) return pair, timeframe, data + def _build_coroutine(self, pair: str, timeframe: str, since_ms: Optional[int]) -> Coroutine: + if not since_ms and self.required_candle_call_count > 1: + # Multiple calls for one pair - to get more history + one_call = timeframe_to_msecs(timeframe) * self.ohlcv_candle_limit(timeframe) + move_to = one_call * self.required_candle_call_count + now = timeframe_to_next_date(timeframe) + since_ms = int((now - timedelta(seconds=move_to // 1000)).timestamp() * 1000) + + if since_ms: + return self._async_get_historic_ohlcv( + pair, timeframe, since_ms=since_ms, raise_=True) + else: + # One call ... "regular" refresh + return self._async_get_candle_history( + pair, timeframe, since_ms=since_ms) + def refresh_latest_ohlcv(self, pair_list: ListPairsWithTimeframes, *, since_ms: Optional[int] = None, cache: bool = True ) -> Dict[Tuple[str, str], DataFrame]: @@ -1389,22 +1405,15 @@ class Exchange: cached_pairs = [] # Gather coroutines to run for pair, timeframe in set(pair_list): + if timeframe not in self.timeframes: + logger.warning( + f"Cannot download ({pair}, {timeframe}) combination as this timeframe is " + f"not available on {self.name}. Available timeframes are " + f"{', '.join(self.timeframes)}.") + continue if ((pair, timeframe) not in self._klines or not cache or self._now_is_time_to_refresh(pair, timeframe)): - if not since_ms and self.required_candle_call_count > 1: - # Multiple calls for one pair - to get more history - one_call = timeframe_to_msecs(timeframe) * self.ohlcv_candle_limit(timeframe) - move_to = one_call * self.required_candle_call_count - now = timeframe_to_next_date(timeframe) - since_ms = int((now - timedelta(seconds=move_to // 1000)).timestamp() * 1000) - - if since_ms: - input_coroutines.append(self._async_get_historic_ohlcv( - pair, timeframe, since_ms=since_ms, raise_=True)) - else: - # One call ... "regular" refresh - input_coroutines.append(self._async_get_candle_history( - pair, timeframe, since_ms=since_ms)) + input_coroutines.append(self._build_coroutine(pair, timeframe, since_ms)) else: logger.debug( "Using cached candle (OHLCV) data for pair %s, timeframe %s ...", diff --git a/tests/conftest.py b/tests/conftest.py index ae35b0326..57122c01c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -107,6 +107,8 @@ def patch_exchange(mocker, api_mock=None, id='binance', mock_markets=True) -> No mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) else: mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock()) + mocker.patch('freqtrade.exchange.Exchange.timeframes', PropertyMock( + return_value=['5m', '15m', '1h', '1d'])) def get_patched_exchange(mocker, config, api_mock=None, id='binance', diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 527e8050b..2bd84942d 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1692,6 +1692,13 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: cache=False) assert len(res) == 3 assert exchange._api_async.fetch_ohlcv.call_count == 3 + exchange._api_async.fetch_ohlcv.reset_mock() + caplog.clear() + # Call with invalid timeframe + res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '3m')],cache=False) + assert not res + assert len(res) == 0 + assert log_has_re(r'Cannot download \(IOTA\/ETH, 3m\).*', caplog) @pytest.mark.asyncio From 7825d855cd10e589103afd614199c042e85fc3a1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 11 Mar 2022 19:28:15 +0100 Subject: [PATCH 058/114] Fix flake8 error in tests --- tests/exchange/test_exchange.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index 2bd84942d..ff8383997 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -1695,7 +1695,7 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None: exchange._api_async.fetch_ohlcv.reset_mock() caplog.clear() # Call with invalid timeframe - res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '3m')],cache=False) + res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '3m')], cache=False) assert not res assert len(res) == 0 assert log_has_re(r'Cannot download \(IOTA\/ETH, 3m\).*', caplog) From f343036e6695be138d07be3ca8e59c507379b8c5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 12 Mar 2022 19:23:20 +0100 Subject: [PATCH 059/114] Add stoploss-ordertypes mapping for gateio --- freqtrade/exchange/gateio.py | 1 + 1 file changed, 1 insertion(+) diff --git a/freqtrade/exchange/gateio.py b/freqtrade/exchange/gateio.py index 9f857c344..ca57f85b3 100644 --- a/freqtrade/exchange/gateio.py +++ b/freqtrade/exchange/gateio.py @@ -22,6 +22,7 @@ class Gateio(Exchange): _ft_has: Dict = { "ohlcv_candle_limit": 1000, "ohlcv_volume_currency": "quote", + "stoploss_order_types": {"limit": "limit"}, "stoploss_on_exchange": True, } From 2ba79a32a02abb5a08b3fa3c18a296e6b3cb43ae Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 12 Mar 2022 19:42:40 -0600 Subject: [PATCH 060/114] Update docs/exchanges.md Co-authored-by: Matthias --- docs/exchanges.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index df11a5971..c2368170d 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -211,7 +211,7 @@ OKX requires a passphrase for each api key, you will therefore need to add this ## Gate.io !!! Tip "Stoploss on Exchange" - Binance supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange.. + Gate.io supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange.. Gate.io allows the use of `POINT` to pay for fees. As this is not a tradable currency (no regular market available), automatic fee calculations will fail (and default to a fee of 0). The configuration parameter `exchange.unknown_fee_rate` can be used to specify the exchange rate between Point and the stake currency. Obviously, changing the stake-currency will also require changes to this value. From 7e7e5963724e56ed052cb607df2cf4c145867eb0 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 12 Mar 2022 20:07:50 -0600 Subject: [PATCH 061/114] Revert "moved binance.stoploss_adjust to exchange class" This reverts commit 6bb93bdc25d3a00c97aca3eb2ab6a8355f1ad268. --- freqtrade/exchange/binance.py | 7 +++++++ freqtrade/exchange/exchange.py | 2 +- tests/exchange/test_exchange.py | 12 ------------ 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index ecdd002b2..37ead6dd8 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -23,6 +23,13 @@ class Binance(Exchange): "l2_limit_range": [5, 10, 20, 50, 100, 500, 1000], } + def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + """ + Verify stop_loss against stoploss-order value (limit or price) + Returns True if adjustment is necessary. + """ + return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice']) + async def _async_get_historic_ohlcv(self, pair: str, timeframe: str, since_ms: int, is_new_pair: bool = False, raise_: bool = False diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 211531c34..a502ad034 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -795,7 +795,7 @@ class Exchange: Verify stop_loss against stoploss-order value (limit or price) Returns True if adjustment is necessary. """ - return stop_loss > float(order['stopPrice']) + raise OperationalException(f"stoploss is not implemented for {self.name}.") def _get_stop_params(self, ordertype: str, stop_price: float) -> Dict: params = self._params.copy() diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index db4d66fa5..b4b19a6d4 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -3062,15 +3062,3 @@ def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_r ]) def test_calculate_backoff(retrycount, max_retries, expected): assert calculate_backoff(retrycount, max_retries) == expected - - -@pytest.mark.parametrize('exchange_name', ['binance', 'gateio']) -def test_stoploss_adjust(mocker, default_conf, exchange_name): - exchange = get_patched_exchange(mocker, default_conf, id=exchange_name) - order = { - 'type': 'stop_loss_limit', - 'price': 1500, - 'stopPrice': 1500, - } - assert exchange.stoploss_adjust(1501, order) - assert not exchange.stoploss_adjust(1499, order) From 91549d3254a481bc187865b4d48d4406cc68013b Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 12 Mar 2022 20:07:56 -0600 Subject: [PATCH 062/114] Revert "stoploss_adjust fixed breaking tests" This reverts commit 6f4d6079023fc75987f7c08087ea4d9e16020387. --- tests/exchange/test_binance.py | 14 ++++++++++++++ tests/exchange/test_exchange.py | 3 +++ tests/test_freqtradebot.py | 4 +++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index bf9340217..d88ae9b1d 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -94,6 +94,20 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker): assert order['amount'] == 1 +def test_stoploss_adjust_binance(mocker, default_conf): + exchange = get_patched_exchange(mocker, default_conf, id='binance') + order = { + 'type': 'stop_loss_limit', + 'price': 1500, + 'info': {'stopPrice': 1500}, + } + assert exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1499, order) + # Test with invalid order case + order['type'] = 'stop_loss' + assert not exchange.stoploss_adjust(1501, order) + + @pytest.mark.asyncio async def test__async_get_historic_ohlcv_binance(default_conf, mocker, caplog): ohlcv = [ diff --git a/tests/exchange/test_exchange.py b/tests/exchange/test_exchange.py index b4b19a6d4..ff8383997 100644 --- a/tests/exchange/test_exchange.py +++ b/tests/exchange/test_exchange.py @@ -2705,6 +2705,9 @@ def test_stoploss_order_unsupported_exchange(default_conf, mocker): with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}) + with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"): + exchange.stoploss_adjust(1, {}) + def test_merge_ft_has_dict(default_conf, mocker): mocker.patch.multiple('freqtrade.exchange.Exchange', diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index caa9a824f..1aeb56cdd 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1588,7 +1588,9 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, 'type': 'stop_loss_limit', 'price': 3, 'average': 2, - 'stopPrice': '2.178' + 'info': { + 'stopPrice': '2.178' + } }) mocker.patch('freqtrade.exchange.Exchange.fetch_stoploss_order', stoploss_order_hanging) From 843606c9cbd4563beba7fbf405f03ddc012508c1 Mon Sep 17 00:00:00 2001 From: Sam Germain Date: Sat, 12 Mar 2022 20:14:23 -0600 Subject: [PATCH 063/114] gateio stoploss adjust --- freqtrade/exchange/gateio.py | 7 +++++++ tests/exchange/test_gateio.py | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/freqtrade/exchange/gateio.py b/freqtrade/exchange/gateio.py index ca57f85b3..bfe996e86 100644 --- a/freqtrade/exchange/gateio.py +++ b/freqtrade/exchange/gateio.py @@ -48,3 +48,10 @@ class Gateio(Exchange): pair=pair, params={'stop': True} ) + + def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool: + """ + Verify stop_loss against stoploss-order value (limit or price) + Returns True if adjustment is necessary. + """ + return stop_loss > float(order['stopPrice']) diff --git a/tests/exchange/test_gateio.py b/tests/exchange/test_gateio.py index a3ffcad65..ce356be8c 100644 --- a/tests/exchange/test_gateio.py +++ b/tests/exchange/test_gateio.py @@ -55,3 +55,13 @@ def test_cancel_stoploss_order_gateio(default_conf, mocker): assert cancel_order_mock.call_args_list[0][1]['order_id'] == '1234' assert cancel_order_mock.call_args_list[0][1]['pair'] == 'ETH/BTC' assert cancel_order_mock.call_args_list[0][1]['params'] == {'stop': True} + + +def test_stoploss_adjust_gateio(mocker, default_conf): + exchange = get_patched_exchange(mocker, default_conf, id='gateio') + order = { + 'price': 1500, + 'stopPrice': 1500, + } + assert exchange.stoploss_adjust(1501, order) + assert not exchange.stoploss_adjust(1499, order) From 9107819c9518a9075e8feeab43b896171ead0bfe Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 13 Mar 2022 14:42:15 +0100 Subject: [PATCH 064/114] Fix order migration "forgetting" average --- freqtrade/persistence/migrations.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index ef64a2b27..2da24b748 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -174,16 +174,17 @@ def drop_orders_table(engine, table_back_name: str): def migrate_orders_table(engine, table_back_name: str, cols_order: List): ft_fee_base = get_column_def(cols_order, 'ft_fee_base', 'null') + average = get_column_def(cols_order, 'average', 'null') # let SQLAlchemy create the schema as required with engine.begin() as connection: connection.execute(text(f""" insert into orders ( id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, - status, symbol, order_type, side, price, amount, filled, average, remaining, cost, - order_date, order_filled_date, order_update_date, ft_fee_base) + status, symbol, order_type, side, price, amount, filled, average, remaining, + cost, order_date, order_filled_date, order_update_date, ft_fee_base) select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, - status, symbol, order_type, side, price, amount, filled, null average, remaining, cost, - order_date, order_filled_date, order_update_date, {ft_fee_base} + status, symbol, order_type, side, price, amount, filled, {average} average, remaining, + cost, order_date, order_filled_date, order_update_date, {ft_fee_base} ft_fee_base from {table_back_name} """)) From b8b56d95f39164256f61f7aee5bb146816e5dfc9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 13 Mar 2022 14:57:32 +0100 Subject: [PATCH 065/114] Update missleading docstring --- freqtrade/plugins/pairlist/VolatilityFilter.py | 2 +- freqtrade/plugins/pairlist/rangestabilityfilter.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/plugins/pairlist/VolatilityFilter.py b/freqtrade/plugins/pairlist/VolatilityFilter.py index 20b899c5f..8a7eeeca8 100644 --- a/freqtrade/plugins/pairlist/VolatilityFilter.py +++ b/freqtrade/plugins/pairlist/VolatilityFilter.py @@ -90,7 +90,7 @@ class VolatilityFilter(IPairList): """ Validate trading range :param pair: Pair that's currently validated - :param ticker: ticker dict as returned from ccxt.fetch_tickers() + :param daily_candles: Downloaded daily candles :return: True if the pair can stay, false if it should be removed """ # Check symbol in cache diff --git a/freqtrade/plugins/pairlist/rangestabilityfilter.py b/freqtrade/plugins/pairlist/rangestabilityfilter.py index 314056fbb..e17ec2dab 100644 --- a/freqtrade/plugins/pairlist/rangestabilityfilter.py +++ b/freqtrade/plugins/pairlist/rangestabilityfilter.py @@ -88,7 +88,7 @@ class RangeStabilityFilter(IPairList): """ Validate trading range :param pair: Pair that's currently validated - :param ticker: ticker dict as returned from ccxt.fetch_tickers() + :param daily_candles: Downloaded daily candles :return: True if the pair can stay, false if it should be removed """ # Check symbol in cache From 7146122f4adc97b4534428300a5e34c3b025879e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 13 Mar 2022 15:06:32 +0100 Subject: [PATCH 066/114] Update docstring --- freqtrade/plugins/pairlist/AgeFilter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/plugins/pairlist/AgeFilter.py b/freqtrade/plugins/pairlist/AgeFilter.py index 5627d82ce..a6d5ec79b 100644 --- a/freqtrade/plugins/pairlist/AgeFilter.py +++ b/freqtrade/plugins/pairlist/AgeFilter.py @@ -98,7 +98,7 @@ class AgeFilter(IPairList): """ Validate age for the ticker :param pair: Pair that's currently validated - :param ticker: ticker dict as returned from ccxt.fetch_tickers() + :param daily_candles: Downloaded daily candles :return: True if the pair can stay, false if it should be removed """ # Check symbol in cache From 3133be19e33077968cd069344f4e860b10132091 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 13 Mar 2022 15:23:01 +0100 Subject: [PATCH 067/114] Update precisionfilter to use last instead of ask or bid. --- freqtrade/plugins/pairlist/PrecisionFilter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/plugins/pairlist/PrecisionFilter.py b/freqtrade/plugins/pairlist/PrecisionFilter.py index a3c262e8c..521f38635 100644 --- a/freqtrade/plugins/pairlist/PrecisionFilter.py +++ b/freqtrade/plugins/pairlist/PrecisionFilter.py @@ -51,7 +51,7 @@ class PrecisionFilter(IPairList): :param ticker: ticker dict as returned from ccxt.fetch_tickers() :return: True if the pair can stay, false if it should be removed """ - stop_price = ticker['ask'] * self._stoploss + stop_price = ticker['last'] * self._stoploss # Adjust stop-prices to precision sp = self._exchange.price_to_precision(pair, stop_price) From 32c06f4a05c4c804f98be8a923f8fda7f53f13b8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 13 Mar 2022 16:45:11 +0100 Subject: [PATCH 068/114] Improve test --- tests/test_periodiccache.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_periodiccache.py b/tests/test_periodiccache.py index f874f9041..b2bd8ba2b 100644 --- a/tests/test_periodiccache.py +++ b/tests/test_periodiccache.py @@ -26,7 +26,9 @@ def test_ttl_cache(): assert 'a' in cache1h t.move_to("2021-09-01 05:59:59 +00:00") + assert 'a' not in cache assert 'a' in cache1h t.move_to("2021-09-01 06:00:00 +00:00") + assert 'a' not in cache assert 'a' not in cache1h From c63b5fbbbf22807526da4eb34411287eeb415be9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 13 Mar 2022 17:53:52 +0100 Subject: [PATCH 069/114] Use last to get rates for /balance endpoints --- freqtrade/rpc/rpc.py | 2 +- tests/rpc/test_rpc.py | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 7a602978e..3d4fffbc9 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -582,7 +582,7 @@ class RPC: else: try: pair = self._freqtrade.exchange.get_valid_pair_combination(coin, stake_currency) - rate = tickers.get(pair, {}).get('bid', None) + rate = tickers.get(pair, {}).get('last', None) if rate: if pair.startswith(stake_currency) and not pair.endswith(stake_currency): rate = 1.0 / rate diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index 6bfee8e86..d738760be 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -605,8 +605,8 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): rpc._fiat_converter = CryptoToFiatConverter() result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency']) - assert prec_satoshi(result['total'], 12.309096315) - assert prec_satoshi(result['value'], 184636.44472997) + assert prec_satoshi(result['total'], 12.30909624) + assert prec_satoshi(result['value'], 184636.443606915) assert tickers.call_count == 1 assert tickers.call_args_list[0][1]['cached'] is True assert 'USD' == result['symbol'] @@ -624,17 +624,16 @@ def test_rpc_balance_handle(default_conf, mocker, tickers): 'est_stake': 0.30794, 'used': 4.0, 'stake': 'BTC', - }, {'free': 5.0, 'balance': 10.0, 'currency': 'USDT', - 'est_stake': 0.0011563153318162476, + 'est_stake': 0.0011562404610161968, 'used': 5.0, 'stake': 'BTC', } ] - assert result['total'] == 12.309096315331816 + assert result['total'] == 12.309096240461017 def test_rpc_start(mocker, default_conf) -> None: From be5b0acfbda471a966b967cea1a6c6c213641cbf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 03:01:00 +0000 Subject: [PATCH 070/114] Bump pytest from 7.0.1 to 7.1.0 Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.0.1 to 7.1.0. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.0.1...7.1.0) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 5c94ac7c8..1d393538b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ coveralls==3.3.1 flake8==4.0.1 flake8-tidy-imports==4.6.0 mypy==0.931 -pytest==7.0.1 +pytest==7.1.0 pytest-asyncio==0.18.2 pytest-cov==3.0.0 pytest-mock==3.7.0 From 7764ad1541fcc81e40ab3b94ad33ca4a0612ea6e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 03:01:03 +0000 Subject: [PATCH 071/114] Bump types-cachetools from 4.2.10 to 5.0.0 Bumps [types-cachetools](https://github.com/python/typeshed) from 4.2.10 to 5.0.0. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-cachetools dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 5c94ac7c8..0f9d4d105 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -20,7 +20,7 @@ time-machine==2.6.0 nbconvert==6.4.2 # mypy types -types-cachetools==4.2.10 +types-cachetools==5.0.0 types-filelock==3.2.5 types-requests==2.27.11 types-tabulate==0.8.5 From 3a0ad2f26e2b96a2210f4da44ce08e767c4054cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 03:01:07 +0000 Subject: [PATCH 072/114] Bump uvicorn from 0.17.5 to 0.17.6 Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.17.5 to 0.17.6. - [Release notes](https://github.com/encode/uvicorn/releases) - [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md) - [Commits](https://github.com/encode/uvicorn/compare/0.17.5...0.17.6) --- updated-dependencies: - dependency-name: uvicorn dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f8b6049f1..095735180 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,7 +32,7 @@ sdnotify==0.3.2 # API Server fastapi==0.75.0 -uvicorn==0.17.5 +uvicorn==0.17.6 pyjwt==2.3.0 aiofiles==0.8.0 psutil==5.9.0 From 3fc1c94aba6bdb7045153d793aab9b1427c73c8e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 03:01:14 +0000 Subject: [PATCH 073/114] Bump ccxt from 1.75.12 to 1.76.5 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.75.12 to 1.76.5. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg) - [Commits](https://github.com/ccxt/ccxt/compare/1.75.12...1.76.5) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f8b6049f1..245fa63d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.22.2 pandas==1.4.1 pandas-ta==0.3.14b -ccxt==1.75.12 +ccxt==1.76.5 # Pin cryptography for now due to rust build errors with piwheels cryptography==36.0.1 aiohttp==3.8.1 From 4cbdc9a74ffe6cc39002b03739335f4a4d58cbb1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 03:01:17 +0000 Subject: [PATCH 074/114] Bump types-requests from 2.27.11 to 2.27.12 Bumps [types-requests](https://github.com/python/typeshed) from 2.27.11 to 2.27.12. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 5c94ac7c8..beae48283 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -22,7 +22,7 @@ nbconvert==6.4.2 # mypy types types-cachetools==4.2.10 types-filelock==3.2.5 -types-requests==2.27.11 +types-requests==2.27.12 types-tabulate==0.8.5 # Extensions to datetime library From a7133f1974bf3f873eef7eb441ea1c924a85b10b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 03:01:20 +0000 Subject: [PATCH 075/114] Bump nbconvert from 6.4.2 to 6.4.4 Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 6.4.2 to 6.4.4. - [Release notes](https://github.com/jupyter/nbconvert/releases) - [Commits](https://github.com/jupyter/nbconvert/compare/6.4.2...6.4.4) --- updated-dependencies: - dependency-name: nbconvert dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 5c94ac7c8..26c873b1f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -17,7 +17,7 @@ isort==5.10.1 time-machine==2.6.0 # Convert jupyter notebooks to markdown documents -nbconvert==6.4.2 +nbconvert==6.4.4 # mypy types types-cachetools==4.2.10 From 3fbe4a9944369ad9b1e829838709ac53bf5a6914 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 03:01:30 +0000 Subject: [PATCH 076/114] Bump numpy from 1.22.2 to 1.22.3 Bumps [numpy](https://github.com/numpy/numpy) from 1.22.2 to 1.22.3. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/main/doc/HOWTO_RELEASE.rst.txt) - [Commits](https://github.com/numpy/numpy/compare/v1.22.2...v1.22.3) --- updated-dependencies: - dependency-name: numpy dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f8b6049f1..b9c6d3d06 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -numpy==1.22.2 +numpy==1.22.3 pandas==1.4.1 pandas-ta==0.3.14b From 404d700a74b919cfefb467bae9b1910d5a0f056a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 14 Mar 2022 06:23:48 +0100 Subject: [PATCH 077/114] Raise min-requirement for ccxt --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ec41228c1..a89e717a1 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,7 @@ setup( ], install_requires=[ # from requirements.txt - 'ccxt>=1.74.17', + 'ccxt>=1.76.5', 'SQLAlchemy', 'python-telegram-bot>=13.4', 'arrow>=0.17.0', From 5462ff0ebc9f6ad09af17db0bc46d6be3bfd23f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 05:25:16 +0000 Subject: [PATCH 078/114] Bump mypy from 0.931 to 0.940 Bumps [mypy](https://github.com/python/mypy) from 0.931 to 0.940. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.931...v0.940) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0c3622f72..c2f3eae8a 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,7 +6,7 @@ coveralls==3.3.1 flake8==4.0.1 flake8-tidy-imports==4.6.0 -mypy==0.931 +mypy==0.940 pytest==7.1.0 pytest-asyncio==0.18.2 pytest-cov==3.0.0 From 18030a30e7d4e081a893eb37ef9501e0a20648d1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 14 Mar 2022 19:21:58 +0100 Subject: [PATCH 079/114] Add exchange parameter to test-pairlist command This will allow for quick tests of the same pairlist config against multiple exchanges. --- docs/utils.md | 15 ++++++++++----- freqtrade/commands/arguments.py | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/utils.md b/docs/utils.md index c6e795e60..a28a0f456 100644 --- a/docs/utils.md +++ b/docs/utils.md @@ -517,20 +517,25 @@ Requires a configuration with specified `pairlists` attribute. Can be used to generate static pairlists to be used during backtesting / hyperopt. ``` -usage: freqtrade test-pairlist [-h] [-c PATH] +usage: freqtrade test-pairlist [-h] [-v] [-c PATH] [--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] - [-1] [--print-json] + [-1] [--print-json] [--exchange EXCHANGE] optional arguments: -h, --help show this help message and exit + -v, --verbose Verbose mode (-vv for more, -vvv to get all messages). -c PATH, --config PATH - Specify configuration file (default: `config.json`). - Multiple --config options may be used. Can be set to - `-` to read config from stdin. + Specify configuration file (default: + `userdir/config.json` or `config.json` whichever + exists). Multiple --config options may be used. Can be + set to `-` to read config from stdin. --quote QUOTE_CURRENCY [QUOTE_CURRENCY ...] Specify quote currency(-ies). Space-separated list. -1, --one-column Print output in one column. --print-json Print list of pairs or market symbols in JSON format. + --exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no + config is provided. + ``` ### Examples diff --git a/freqtrade/commands/arguments.py b/freqtrade/commands/arguments.py index 201ec09bf..28f7d7148 100644 --- a/freqtrade/commands/arguments.py +++ b/freqtrade/commands/arguments.py @@ -51,7 +51,7 @@ ARGS_LIST_PAIRS = ["exchange", "print_list", "list_pairs_print_json", "print_one "print_csv", "base_currencies", "quote_currencies", "list_pairs_all"] ARGS_TEST_PAIRLIST = ["verbosity", "config", "quote_currencies", "print_one_column", - "list_pairs_print_json"] + "list_pairs_print_json", "exchange"] ARGS_CREATE_USERDIR = ["user_data_dir", "reset"] From 89aae71c32c79d3cb3e21382ad237f86406d5cb2 Mon Sep 17 00:00:00 2001 From: Kavinkumar <33546454+mkavinkumar1@users.noreply.github.com> Date: Tue, 15 Mar 2022 11:41:39 +0530 Subject: [PATCH 080/114] correcting docs for pricing of ask strategy --- docs/includes/pricing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/includes/pricing.md b/docs/includes/pricing.md index ed8a45e68..4505d7fec 100644 --- a/docs/includes/pricing.md +++ b/docs/includes/pricing.md @@ -88,9 +88,9 @@ When selling with the orderbook enabled (`ask_strategy.use_order_book=True`), Fr #### Sell price without Orderbook enabled -When not using orderbook (`ask_strategy.use_order_book=False`), the price at the `ask_strategy.price_side` side (defaults to `"ask"`) from the ticker will be used as the sell price. +The following section uses `side` as the configured `ask_strategy.price_side`. -When not using orderbook (`ask_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price. +When not using orderbook (`ask_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's above the `last` traded price from the ticker. Otherwise (when the `side` price is below the `last` price), it calculates a rate between `side` and `last` price. The `ask_strategy.bid_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the last price and values between those interpolate between `side` and last price. From 47317e0f0659aaf8c126076ee0eaef6319f3f765 Mon Sep 17 00:00:00 2001 From: Joe Schr Date: Fri, 11 Mar 2022 17:53:29 +0100 Subject: [PATCH 081/114] version: use 'contains' to check for "develop" instead of literal comparison --- freqtrade/__init__.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index 2747efc96..d57cc9852 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,27 +1,14 @@ """ Freqtrade bot """ -__version__ = 'develop' - -if __version__ == 'develop': +__version__ = 'dev' +if 'dev' in __version__: try: import subprocess - __version__ = 'develop-' + subprocess.check_output( + __version__ = __version__ + '-' + subprocess.check_output( ['git', 'log', '--format="%h"', '-n 1'], stderr=subprocess.DEVNULL).decode("utf-8").rstrip().strip('"') - # from datetime import datetime - # last_release = subprocess.check_output( - # ['git', 'tag'] - # ).decode('utf-8').split()[-1].split(".") - # # Releases are in the format "2020.1" - we increment the latest version for dev. - # prefix = f"{last_release[0]}.{int(last_release[1]) + 1}" - # dev_version = int(datetime.now().timestamp() // 1000) - # __version__ = f"{prefix}.dev{dev_version}" - - # subprocess.check_output( - # ['git', 'log', '--format="%h"', '-n 1'], - # stderr=subprocess.DEVNULL).decode("utf-8").rstrip().strip('"') except Exception: # pragma: no cover # git not available, ignore try: From 6024fa482e1b09bae8e85b3afc9fc58e483c1512 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 17 Mar 2022 07:41:08 +0100 Subject: [PATCH 082/114] Use brackets to break IF lines --- freqtrade/edge/edge_positioning.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/freqtrade/edge/edge_positioning.py b/freqtrade/edge/edge_positioning.py index 1950f0d08..08f43598d 100644 --- a/freqtrade/edge/edge_positioning.py +++ b/freqtrade/edge/edge_positioning.py @@ -219,9 +219,11 @@ class Edge: """ final = [] for pair, info in self._cached_pairs.items(): - if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \ - info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)) and \ - pair in pairs: + if ( + info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) + and info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)) + and pair in pairs + ): final.append(pair) if self._final_pairs != final: @@ -246,8 +248,8 @@ class Edge: """ final = [] for pair, info in self._cached_pairs.items(): - if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \ - info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)): + if (info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and + info.winrate > float(self.edge_config.get('minimum_winrate', 0.60))): final.append({ 'Pair': pair, 'Winrate': info.winrate, From 96bf82dbc64b824617f75ca8556f2f2d0e4d9446 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 17 Mar 2022 17:06:10 +0100 Subject: [PATCH 083/114] Remove gateio broker program --- freqtrade/exchange/gateio.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/freqtrade/exchange/gateio.py b/freqtrade/exchange/gateio.py index bfe996e86..d0fd787b7 100644 --- a/freqtrade/exchange/gateio.py +++ b/freqtrade/exchange/gateio.py @@ -26,8 +26,6 @@ class Gateio(Exchange): "stoploss_on_exchange": True, } - _headers = {'X-Gate-Channel-Id': 'freqtrade'} - def validate_ordertypes(self, order_types: Dict) -> None: super().validate_ordertypes(order_types) From f55db8e262f2f64758682a9a5b96abbab6051d0b Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 17 Mar 2022 20:21:10 +0100 Subject: [PATCH 084/114] Spreadfilter should fail to start if fetchTickers is not supported --- freqtrade/plugins/pairlist/SpreadFilter.py | 7 +++++++ tests/plugins/test_pairlist.py | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/freqtrade/plugins/pairlist/SpreadFilter.py b/freqtrade/plugins/pairlist/SpreadFilter.py index ad0c0f0be..d1f88d2a5 100644 --- a/freqtrade/plugins/pairlist/SpreadFilter.py +++ b/freqtrade/plugins/pairlist/SpreadFilter.py @@ -4,6 +4,7 @@ Spread pair list filter import logging from typing import Any, Dict +from freqtrade.exceptions import OperationalException from freqtrade.plugins.pairlist.IPairList import IPairList @@ -20,6 +21,12 @@ class SpreadFilter(IPairList): self._max_spread_ratio = pairlistconfig.get('max_spread_ratio', 0.005) self._enabled = self._max_spread_ratio != 0 + if not self._exchange.exchange_has('fetchTickers'): + raise OperationalException( + 'Exchange does not support fetchTickers, therefore SpreadFilter cannot be used.' + 'Please edit your config and restart the bot.' + ) + @property def needstickers(self) -> bool: """ diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 52158a889..346bf1792 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -782,6 +782,18 @@ def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None get_patched_freqtradebot(mocker, default_conf) +def test_pair_whitelist_not_supported_Spread(mocker, default_conf, tickers) -> None: + default_conf['pairlists'] = [{'method': 'StaticPairList'}, {'method': 'SpreadFilter'}] + + mocker.patch.multiple('freqtrade.exchange.Exchange', + get_tickers=tickers, + exchange_has=MagicMock(return_value=False), + ) + + with pytest.raises(OperationalException, + match=r'Exchange does not support fetchTickers, .*'): + get_patched_freqtradebot(mocker, default_conf) + @pytest.mark.parametrize("pairlist", AVAILABLE_PAIRLISTS) def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): whitelist_conf['pairlists'][0]['method'] = pairlist From 73fc344eb1d666cbf7f435abc8b5505e9f952798 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 18 Mar 2022 06:38:54 +0100 Subject: [PATCH 085/114] Improve wording in docs --- docs/bot-basics.md | 2 +- tests/plugins/test_pairlist.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/bot-basics.md b/docs/bot-basics.md index 8c6303063..4b5ba3a5b 100644 --- a/docs/bot-basics.md +++ b/docs/bot-basics.md @@ -24,7 +24,7 @@ By default, loop runs every few seconds (`internals.process_throttle_secs`) and * Fetch open trades from persistence. * Calculate current list of tradable pairs. -* Download ohlcv data for the pairlist including all [informative pairs](strategy-customization.md#get-data-for-non-tradeable-pairs) +* Download OHLCV data for the pairlist including all [informative pairs](strategy-customization.md#get-data-for-non-tradeable-pairs) This step is only executed once per Candle to avoid unnecessary network traffic. * Call `bot_loop_start()` strategy callback. * Analyze strategy per pair. diff --git a/tests/plugins/test_pairlist.py b/tests/plugins/test_pairlist.py index 346bf1792..08ba892fe 100644 --- a/tests/plugins/test_pairlist.py +++ b/tests/plugins/test_pairlist.py @@ -794,6 +794,7 @@ def test_pair_whitelist_not_supported_Spread(mocker, default_conf, tickers) -> N match=r'Exchange does not support fetchTickers, .*'): get_patched_freqtradebot(mocker, default_conf) + @pytest.mark.parametrize("pairlist", AVAILABLE_PAIRLISTS) def test_pairlist_class(mocker, whitelist_conf, markets, pairlist): whitelist_conf['pairlists'][0]['method'] = pairlist From 95f69b905aec70c2e13b9def10dae6c0275df8ab Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 09:00:53 +0100 Subject: [PATCH 086/114] Remove ticker_interval support --- docs/backtesting.md | 4 ++-- docs/configuration.md | 2 +- docs/edge.md | 2 +- docs/hyperopt.md | 2 +- docs/plotting.md | 4 ++-- docs/strategy-customization.md | 2 +- freqtrade/commands/cli_options.py | 4 ++-- freqtrade/optimize/backtesting.py | 2 +- freqtrade/optimize/hyperopt_interface.py | 7 ++----- freqtrade/resolvers/hyperopt_resolver.py | 1 - freqtrade/resolvers/strategy_resolver.py | 4 ---- freqtrade/strategy/interface.py | 3 +-- 12 files changed, 14 insertions(+), 23 deletions(-) diff --git a/docs/backtesting.md b/docs/backtesting.md index e7846b1f8..11608aad9 100644 --- a/docs/backtesting.md +++ b/docs/backtesting.md @@ -26,7 +26,7 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH] optional arguments: -h, --help show this help message and exit - -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME + -i TIMEFRAME, --timeframe TIMEFRAME Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). --timerange TIMERANGE Specify what timerange of data to use. @@ -63,7 +63,7 @@ optional arguments: `30m`, `1h`, `1d`). --strategy-list STRATEGY_LIST [STRATEGY_LIST ...] Provide a space-separated list of strategies to - backtest. Please note that ticker-interval needs to be + backtest. Please note that timeframe needs to be set either in config or via command line. When using this together with `--export trades`, the strategy- name is injected into the filename (so `backtest- diff --git a/docs/configuration.md b/docs/configuration.md index d702fe8f9..2cb5dfa93 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -86,7 +86,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade).
*Defaults to `false`.*
**Datatype:** Boolean | `last_stake_amount_min_ratio` | Defines minimum stake amount that has to be left and executed. Applies only to the last stake amount when it's amended to a reduced value (i.e. if `amend_last_stake_amount` is set to `true`). [More information below](#configuring-amount-per-trade).
*Defaults to `0.5`.*
**Datatype:** Float (as ratio) | `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals.
*Defaults to `0.05` (5%).*
**Datatype:** Positive Float as ratio. -| `timeframe` | The timeframe (former ticker interval) to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** String +| `timeframe` | The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy).
**Datatype:** String | `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency).
**Datatype:** String | `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode.
*Defaults to `true`.*
**Datatype:** Boolean | `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in Dry Run mode.
*Defaults to `1000`.*
**Datatype:** Float diff --git a/docs/edge.md b/docs/edge.md index 4402d767f..e92abf40f 100644 --- a/docs/edge.md +++ b/docs/edge.md @@ -222,7 +222,7 @@ usage: freqtrade edge [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] optional arguments: -h, --help show this help message and exit - -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME + -i TIMEFRAME, --timeframe TIMEFRAME Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). --timerange TIMERANGE Specify what timerange of data to use. diff --git a/docs/hyperopt.md b/docs/hyperopt.md index 19d8cd692..c8cb118f7 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -55,7 +55,7 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] optional arguments: -h, --help show this help message and exit - -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME + -i TIMEFRAME, --timeframe TIMEFRAME Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). --timerange TIMERANGE Specify what timerange of data to use. diff --git a/docs/plotting.md b/docs/plotting.md index ccfbb12cb..004dd7821 100644 --- a/docs/plotting.md +++ b/docs/plotting.md @@ -65,7 +65,7 @@ optional arguments: _today.json` --timerange TIMERANGE Specify what timerange of data to use. - -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME + -i TIMEFRAME, --timeframe TIMEFRAME Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). --no-trades Skip using trades from backtesting file and DB. @@ -330,7 +330,7 @@ optional arguments: --trade-source {DB,file} Specify the source for trades (Can be DB or file (backtest file)) Default: file - -i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME + -i TIMEFRAME, --timeframe TIMEFRAME Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`). --auto-open Automatically open generated plot. diff --git a/docs/strategy-customization.md b/docs/strategy-customization.md index e6eff2416..045a55c5b 100644 --- a/docs/strategy-customization.md +++ b/docs/strategy-customization.md @@ -325,7 +325,7 @@ stoploss = -0.10 For the full documentation on stoploss features, look at the dedicated [stoploss page](stoploss.md). -### Timeframe (formerly ticker interval) +### Timeframe This is the set of candles the bot should download and use for the analysis. Common values are `"1m"`, `"5m"`, `"15m"`, `"1h"`, however all values supported by your exchange should work. diff --git a/freqtrade/commands/cli_options.py b/freqtrade/commands/cli_options.py index 11fcc6b81..f30c25ba1 100644 --- a/freqtrade/commands/cli_options.py +++ b/freqtrade/commands/cli_options.py @@ -117,7 +117,7 @@ AVAILABLE_CLI_OPTIONS = { ), # Optimize common "timeframe": Arg( - '-i', '--timeframe', '--ticker-interval', + '-i', '--timeframe', help='Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).', ), "timerange": Arg( @@ -169,7 +169,7 @@ AVAILABLE_CLI_OPTIONS = { "strategy_list": Arg( '--strategy-list', help='Provide a space-separated list of strategies to backtest. ' - 'Please note that ticker-interval needs to be set either in config ' + 'Please note that timeframe needs to be set either in config ' 'or via command line. When using this together with `--export trades`, ' 'the strategy-name is injected into the filename ' '(so `backtest-data.json` becomes `backtest-data-SampleStrategy.json`', diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index eca643732..76f0e68f4 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -87,7 +87,7 @@ class Backtesting: validate_config_consistency(self.config) if "timeframe" not in self.config: - raise OperationalException("Timeframe (ticker interval) needs to be set in either " + raise OperationalException("Timeframe needs to be set in either " "configuration or as cli argument `--timeframe 5m`") self.timeframe = str(self.config.get('timeframe')) self.timeframe_min = timeframe_to_minutes(self.timeframe) diff --git a/freqtrade/optimize/hyperopt_interface.py b/freqtrade/optimize/hyperopt_interface.py index 01ffd7844..b1c68caca 100644 --- a/freqtrade/optimize/hyperopt_interface.py +++ b/freqtrade/optimize/hyperopt_interface.py @@ -29,15 +29,13 @@ class IHyperOpt(ABC): Class attributes you can use: timeframe -> int: value of the timeframe to use for the strategy """ - ticker_interval: str # DEPRECATED timeframe: str strategy: IStrategy def __init__(self, config: dict) -> None: self.config = config - # Assign ticker_interval to be used in hyperopt - IHyperOpt.ticker_interval = str(config['timeframe']) # DEPRECATED + # Assign timeframe to be used in hyperopt IHyperOpt.timeframe = str(config['timeframe']) def generate_estimator(self, dimensions: List[Dimension], **kwargs) -> EstimatorType: @@ -192,7 +190,7 @@ class IHyperOpt(ABC): Categorical([True, False], name='trailing_only_offset_is_reached'), ] - # This is needed for proper unpickling the class attribute ticker_interval + # This is needed for proper unpickling the class attribute timeframe # which is set to the actual value by the resolver. # Why do I still need such shamanic mantras in modern python? def __getstate__(self): @@ -202,5 +200,4 @@ class IHyperOpt(ABC): def __setstate__(self, state): self.__dict__.update(state) - IHyperOpt.ticker_interval = state['timeframe'] IHyperOpt.timeframe = state['timeframe'] diff --git a/freqtrade/resolvers/hyperopt_resolver.py b/freqtrade/resolvers/hyperopt_resolver.py index 6f0263e93..e3c234f60 100644 --- a/freqtrade/resolvers/hyperopt_resolver.py +++ b/freqtrade/resolvers/hyperopt_resolver.py @@ -44,7 +44,6 @@ class HyperOptLossResolver(IResolver): extra_dir=config.get('hyperopt_path')) # Assign timeframe to be used in hyperopt - hyperoptloss.__class__.ticker_interval = str(config['timeframe']) hyperoptloss.__class__.timeframe = str(config['timeframe']) return hyperoptloss diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index e9fcc3496..48cbd03cf 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -145,10 +145,6 @@ class StrategyResolver(IResolver): """ Normalize attributes to have the correct type. """ - # Assign deprecated variable - to not break users code relying on this. - if hasattr(strategy, 'timeframe'): - strategy.ticker_interval = strategy.timeframe - # Sort and apply type conversions if hasattr(strategy, 'minimal_roi'): strategy.minimal_roi = dict(sorted( diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 2f3657059..1eea84676 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -55,7 +55,7 @@ class IStrategy(ABC, HyperStrategyMixin): Attributes you can use: minimal_roi -> Dict: Minimal ROI designed for the strategy stoploss -> float: optimal stoploss designed for the strategy - timeframe -> str: value of the timeframe (ticker interval) to use with the strategy + timeframe -> str: value of the timeframe to use with the strategy """ # Strategy interface version # Default to version 2 @@ -81,7 +81,6 @@ class IStrategy(ABC, HyperStrategyMixin): use_custom_stoploss: bool = False # associated timeframe - ticker_interval: str # DEPRECATED timeframe: str # Optional order types From eb08b921802a6db6c2faa53df36e501a4a64e4af Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 09:01:18 +0100 Subject: [PATCH 087/114] Raise exception when ticker_interval is set. --- freqtrade/configuration/deprecated_settings.py | 11 +++-------- tests/optimize/test_backtesting.py | 9 ++++----- tests/optimize/test_edge_cli.py | 4 +--- tests/optimize/test_hyperopt.py | 1 - tests/strategy/test_strategy_loading.py | 2 -- tests/test_arguments.py | 6 +++--- tests/test_configuration.py | 16 ++++------------ 7 files changed, 15 insertions(+), 34 deletions(-) diff --git a/freqtrade/configuration/deprecated_settings.py b/freqtrade/configuration/deprecated_settings.py index 5efe26bd2..cafa8957b 100644 --- a/freqtrade/configuration/deprecated_settings.py +++ b/freqtrade/configuration/deprecated_settings.py @@ -100,16 +100,11 @@ def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: "from the edge configuration." ) if 'ticker_interval' in config: - logger.warning( - "DEPRECATED: " + + raise OperationalException( + "DEPRECATED: 'ticker_interval' detected. " "Please use 'timeframe' instead of 'ticker_interval." ) - if 'timeframe' in config: - raise OperationalException( - "Both 'timeframe' and 'ticker_interval' detected." - "Please remove 'ticker_interval' from your configuration to continue operating." - ) - config['timeframe'] = config['ticker_interval'] if 'protections' in config: logger.warning("DEPRECATED: Setting 'protections' in the configuration is deprecated.") diff --git a/tests/optimize/test_backtesting.py b/tests/optimize/test_backtesting.py index a8998eb63..7a72747c0 100644 --- a/tests/optimize/test_backtesting.py +++ b/tests/optimize/test_backtesting.py @@ -314,16 +314,15 @@ def test_backtesting_init_no_timeframe(mocker, default_conf, caplog) -> None: patch_exchange(mocker) del default_conf['timeframe'] default_conf['strategy_list'] = ['StrategyTestV2', - 'SampleStrategy'] + 'HyperoptableStrategy'] mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5)) - with pytest.raises(OperationalException): + with pytest.raises(OperationalException, + match=r"Timeframe needs to be set in either configuration"): Backtesting(default_conf) - log_has("Ticker-interval needs to be set in either configuration " - "or as cli argument `--ticker-interval 5m`", caplog) -def test_data_with_fee(default_conf, mocker, testdatadir) -> None: +def test_data_with_fee(default_conf, mocker) -> None: patch_exchange(mocker) default_conf['fee'] = 0.1234 diff --git a/tests/optimize/test_edge_cli.py b/tests/optimize/test_edge_cli.py index 18d5f1c76..466a5f1cd 100644 --- a/tests/optimize/test_edge_cli.py +++ b/tests/optimize/test_edge_cli.py @@ -6,8 +6,7 @@ from unittest.mock import MagicMock from freqtrade.commands.optimize_commands import setup_optimize_configuration, start_edge from freqtrade.enums import RunMode from freqtrade.optimize.edge_cli import EdgeCli -from tests.conftest import (get_args, log_has, log_has_re, patch_exchange, - patched_configuration_load_config_file) +from tests.conftest import get_args, log_has, patch_exchange, patched_configuration_load_config_file def test_setup_optimize_configuration_without_arguments(mocker, default_conf, caplog) -> None: @@ -30,7 +29,6 @@ def test_setup_optimize_configuration_without_arguments(mocker, default_conf, ca assert 'datadir' in config assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert 'timeframe' in config - assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog) assert 'timerange' not in config assert 'stoploss_range' not in config diff --git a/tests/optimize/test_hyperopt.py b/tests/optimize/test_hyperopt.py index 2328585dd..cc551277a 100644 --- a/tests/optimize/test_hyperopt.py +++ b/tests/optimize/test_hyperopt.py @@ -63,7 +63,6 @@ def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, ca assert 'datadir' in config assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) assert 'timeframe' in config - assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog) assert 'position_stacking' not in config assert not log_has('Parameter --enable-position-stacking detected ...', caplog) diff --git a/tests/strategy/test_strategy_loading.py b/tests/strategy/test_strategy_loading.py index 3590c3e01..3fe14a8de 100644 --- a/tests/strategy/test_strategy_loading.py +++ b/tests/strategy/test_strategy_loading.py @@ -111,7 +111,6 @@ def test_strategy(result, default_conf): assert default_conf['stoploss'] == -0.10 assert strategy.timeframe == '5m' - assert strategy.ticker_interval == '5m' assert default_conf['timeframe'] == '5m' df_indicators = strategy.advise_indicators(result, metadata=metadata) @@ -376,7 +375,6 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog): assert strategy._sell_fun_len == 2 assert strategy.INTERFACE_VERSION == 1 assert strategy.timeframe == '5m' - assert strategy.ticker_interval == '5m' indicator_df = strategy.advise_indicators(result, metadata=metadata) assert isinstance(indicator_df, DataFrame) diff --git a/tests/test_arguments.py b/tests/test_arguments.py index fca5c6ab9..ba9d154e5 100644 --- a/tests/test_arguments.py +++ b/tests/test_arguments.py @@ -111,17 +111,17 @@ def test_parse_args_strategy_path_invalid() -> None: def test_parse_args_backtesting_invalid() -> None: with pytest.raises(SystemExit, match=r'2'): - Arguments(['backtesting --ticker-interval']).get_parsed_arg() + Arguments(['backtesting --timeframe']).get_parsed_arg() with pytest.raises(SystemExit, match=r'2'): - Arguments(['backtesting --ticker-interval', 'abc']).get_parsed_arg() + Arguments(['backtesting --timeframe', 'abc']).get_parsed_arg() def test_parse_args_backtesting_custom() -> None: args = [ 'backtesting', '-c', 'test_conf.json', - '--ticker-interval', '1m', + '--timeframe', '1m', '--strategy-list', 'StrategyTestV2', 'SampleStrategy' diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 0a6935649..1cd9b0ff7 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -443,7 +443,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non '--strategy', 'StrategyTestV2', '--datadir', '/foo/bar', '--userdir', "/tmp/freqtrade", - '--ticker-interval', '1m', + '--timeframe', '1m', '--enable-position-stacking', '--disable-max-market-positions', '--timerange', ':100', @@ -494,7 +494,7 @@ def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> Non arglist = [ 'backtesting', '--config', 'config.json', - '--ticker-interval', '1m', + '--timeframe', '1m', '--export', 'trades', '--strategy-list', 'StrategyTestV2', @@ -1320,22 +1320,14 @@ def test_process_removed_setting(mocker, default_conf, caplog): def test_process_deprecated_ticker_interval(default_conf, caplog): message = "DEPRECATED: Please use 'timeframe' instead of 'ticker_interval." config = deepcopy(default_conf) + process_temporary_deprecated_settings(config) assert not log_has(message, caplog) del config['timeframe'] config['ticker_interval'] = '15m' - process_temporary_deprecated_settings(config) - assert log_has(message, caplog) - assert config['ticker_interval'] == '15m' - - config = deepcopy(default_conf) - # Have both timeframe and ticker interval in config - # Can also happen when using ticker_interval in configuration, and --timeframe as cli argument - config['timeframe'] = '5m' - config['ticker_interval'] = '4h' with pytest.raises(OperationalException, - match=r"Both 'timeframe' and 'ticker_interval' detected."): + match=r"DEPRECATED: 'ticker_interval' detected. Please use.*"): process_temporary_deprecated_settings(config) From 0f76b2373380ff30772ffaa07d940cfc9d71c6df Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 09:03:43 +0100 Subject: [PATCH 088/114] update deprecation message for ticker_interval --- docs/deprecated.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/deprecated.md b/docs/deprecated.md index eaf85bfbf..87c8a2b38 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -24,6 +24,10 @@ Please refer to [pairlists](plugins.md#pairlists-and-pairlist-handlers) instead. Did only download the latest 500 candles, so was ineffective in getting good backtest data. Removed in 2019-7-dev (develop branch) and in freqtrade 2019.8. +### `ticker_interval` (now `timeframe`) + +Support for `ticker_interval` terminology was deprecated in 2020.6 in favor of `timeframe` - and compatibility code was removed in 2022.3. + ### Allow running multiple pairlists in sequence The former `"pairlist"` section in the configuration has been removed, and is replaced by `"pairlists"` - being a list to specify a sequence of pairlists. From aceaa3faec80bba3ec7de35bcc50e0aee6cff58e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 09:30:54 +0100 Subject: [PATCH 089/114] remove last ticker_interval compatibility shim --- freqtrade/resolvers/strategy_resolver.py | 8 -------- tests/strategy/strats/legacy_strategy_v1.py | 4 +--- tests/strategy/test_strategy_loading.py | 3 --- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/freqtrade/resolvers/strategy_resolver.py b/freqtrade/resolvers/strategy_resolver.py index 48cbd03cf..8ad7cdb59 100644 --- a/freqtrade/resolvers/strategy_resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -45,14 +45,6 @@ class StrategyResolver(IResolver): strategy_name, config=config, extra_dir=config.get('strategy_path')) - if hasattr(strategy, 'ticker_interval') and not hasattr(strategy, 'timeframe'): - # Assign ticker_interval to timeframe to keep compatibility - if 'timeframe' not in config: - logger.warning( - "DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'." - ) - strategy.timeframe = strategy.ticker_interval - if strategy._ft_params_from_file: # Set parameters from Hyperopt results file params = strategy._ft_params_from_file diff --git a/tests/strategy/strats/legacy_strategy_v1.py b/tests/strategy/strats/legacy_strategy_v1.py index ebfce632b..d7ed2014b 100644 --- a/tests/strategy/strats/legacy_strategy_v1.py +++ b/tests/strategy/strats/legacy_strategy_v1.py @@ -31,9 +31,7 @@ class TestStrategyLegacyV1(IStrategy): # This attribute will be overridden if the config file contains "stoploss" stoploss = -0.10 - # Optimal timeframe for the strategy - # Keep the legacy value here to test compatibility - ticker_interval = '5m' + timeframe = '5m' def populate_indicators(self, dataframe: DataFrame) -> DataFrame: """ diff --git a/tests/strategy/test_strategy_loading.py b/tests/strategy/test_strategy_loading.py index 3fe14a8de..cc924d1c2 100644 --- a/tests/strategy/test_strategy_loading.py +++ b/tests/strategy/test_strategy_loading.py @@ -388,9 +388,6 @@ def test_call_deprecated_function(result, monkeypatch, default_conf, caplog): assert isinstance(selldf, DataFrame) assert 'sell' in selldf - assert log_has("DEPRECATED: Please migrate to using 'timeframe' instead of 'ticker_interval'.", - caplog) - def test_strategy_interface_versioning(result, monkeypatch, default_conf): default_conf.update({'strategy': 'StrategyTestV2'}) From 8556e6a0534e0e1e1ab1aeee53e99f23c078fb03 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 09:30:11 +0100 Subject: [PATCH 090/114] Automatically assign buy-tag to force-buys closes #6544 --- docs/telegram-usage.md | 1 + freqtrade/rpc/api_server/api_v1.py | 2 +- freqtrade/rpc/rpc.py | 2 +- tests/rpc/test_rpc.py | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index c7f9c58f6..232885ed2 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -277,6 +277,7 @@ Starting capital is either taken from the `available_capital` setting, or calcul > **BITTREX:** Buying ETH/BTC with limit `0.03400000` (`1.000000 ETH`, `225.290 USD`) Omitting the pair will open a query asking for the pair to buy (based on the current whitelist). +Trades crated through `/forcebuy` will have the buy-tag of `forceentry`. ![Telegram force-buy screenshot](assets/telegram_forcebuy.png) diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 6379150ee..5a34385da 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -137,7 +137,7 @@ def show_config(rpc: Optional[RPC] = Depends(get_rpc_optional), config=Depends(g def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)): ordertype = payload.ordertype.value if payload.ordertype else None stake_amount = payload.stakeamount if payload.stakeamount else None - entry_tag = payload.entry_tag if payload.entry_tag else None + entry_tag = payload.entry_tag if payload.entry_tag else 'forceentry' trade = rpc._rpc_forcebuy(payload.pair, payload.price, ordertype, stake_amount, entry_tag) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 3d4fffbc9..7fd419a5b 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -713,7 +713,7 @@ class RPC: def _rpc_forcebuy(self, pair: str, price: Optional[float], order_type: Optional[str] = None, stake_amount: Optional[float] = None, - buy_tag: Optional[str] = None) -> Optional[Trade]: + buy_tag: Optional[str] = 'forceentry') -> Optional[Trade]: """ Handler for forcebuy Buys a pair trade at the given or current price diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py index d738760be..003b43ad2 100644 --- a/tests/rpc/test_rpc.py +++ b/tests/rpc/test_rpc.py @@ -1147,6 +1147,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> pair = 'LTC/BTC' trade = rpc._rpc_forcebuy(pair, 0.0001, order_type='limit', stake_amount=0.05) assert trade.stake_amount == 0.05 + assert trade.buy_tag == 'forceentry' # Test not buying pair = 'XRP/BTC' From fcec071a0804574dc9e03d8a45ebf8dcc8fa665b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 09:42:34 +0100 Subject: [PATCH 091/114] Use order date to fetch trades using the trade open-date may fail in case of several trade-entries spread over a longer timeperiod. closes #6551 --- freqtrade/freqtradebot.py | 15 ++++++++------- tests/test_freqtradebot.py | 31 ++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 16864f814..1c2b7208f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1428,14 +1428,14 @@ class FreqtradeBot(LoggingMixin): def handle_order_fee(self, trade: Trade, order_obj: Order, order: Dict[str, Any]) -> None: # Try update amount (binance-fix) try: - new_amount = self.get_real_amount(trade, order) + new_amount = self.get_real_amount(trade, order, order_obj) if not isclose(safe_value_fallback(order, 'filled', 'amount'), new_amount, abs_tol=constants.MATH_CLOSE_PREC): order_obj.ft_fee_base = trade.amount - new_amount except DependencyException as exception: logger.warning("Could not update trade amount: %s", exception) - def get_real_amount(self, trade: Trade, order: Dict) -> float: + def get_real_amount(self, trade: Trade, order: Dict, order_obj: Order) -> float: """ Detect and update trade fee. Calls trade.update_fee() upon correct detection. @@ -1453,7 +1453,7 @@ class FreqtradeBot(LoggingMixin): # use fee from order-dict if possible if self.exchange.order_has_fee(order): fee_cost, fee_currency, fee_rate = self.exchange.extract_cost_curr_rate(order) - logger.info(f"Fee for Trade {trade} [{order.get('side')}]: " + logger.info(f"Fee for Trade {trade} [{order_obj.ft_order_side}]: " f"{fee_cost:.8g} {fee_currency} - rate: {fee_rate}") if fee_rate is None or fee_rate < 0.02: # Reject all fees that report as > 2%. @@ -1465,17 +1465,18 @@ class FreqtradeBot(LoggingMixin): return self.apply_fee_conditional(trade, trade_base_currency, amount=order_amount, fee_abs=fee_cost) return order_amount - return self.fee_detection_from_trades(trade, order, order_amount, order.get('trades', [])) + return self.fee_detection_from_trades( + trade, order, order_obj, order_amount, order.get('trades', [])) - def fee_detection_from_trades(self, trade: Trade, order: Dict, order_amount: float, - trades: List) -> float: + def fee_detection_from_trades(self, trade: Trade, order: Dict, order_obj: Order, + order_amount: float, trades: List) -> float: """ fee-detection fallback to Trades. Either uses provided trades list or the result of fetch_my_trades to get correct fee. """ if not trades: trades = self.exchange.get_trades_for_order( - self.exchange.get_order_id_conditional(order), trade.pair, trade.open_date) + self.exchange.get_order_id_conditional(order), trade.pair, order_obj.order_date) if len(trades) == 0: logger.info("Applying fee on amount for %s failed: myTrade-Dict empty found", trade) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 1aeb56cdd..bafed8488 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -3568,9 +3568,9 @@ def test_get_real_amount_quote(default_conf_usdt, trades_for_order, buy_order_fe open_order_id="123456" ) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') # Amount is reduced by "fee" - assert freqtrade.get_real_amount(trade, buy_order_fee) == amount - (amount * 0.001) + assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == amount - (amount * 0.001) assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' 'open_rate=0.24544100, open_since=closed) (from 8.0 to 7.992).', caplog) @@ -3594,8 +3594,9 @@ def test_get_real_amount_quote_dust(default_conf_usdt, trades_for_order, buy_ord freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) walletmock.reset_mock() + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') # Amount is kept as is - assert freqtrade.get_real_amount(trade, buy_order_fee) == amount + assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == amount assert walletmock.call_count == 1 assert log_has_re(r'Fee amount for Trade.* was in base currency ' '- Eating Fee 0.008 into dust', caplog) @@ -3616,8 +3617,9 @@ def test_get_real_amount_no_trade(default_conf_usdt, buy_order_fee, caplog, mock ) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') # Amount is reduced by "fee" - assert freqtrade.get_real_amount(trade, buy_order_fee) == amount + assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == amount assert log_has('Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' 'open_rate=0.24544100, open_since=closed) failed: myTrade-Dict empty found', caplog) @@ -3668,7 +3670,8 @@ def test_get_real_amount( mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', side_effect=ExchangeError) caplog.clear() - assert freqtrade.get_real_amount(trade, buy_order) == amount - fee_reduction_amount + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') + assert freqtrade.get_real_amount(trade, buy_order, order_obj) == amount - fee_reduction_amount if expected_log: assert log_has(expected_log, caplog) @@ -3715,7 +3718,8 @@ def test_get_real_amount_multi( # Amount is reduced by "fee" expected_amount = amount - (amount * fee_reduction_amount) - assert freqtrade.get_real_amount(trade, buy_order_fee) == expected_amount + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') + assert freqtrade.get_real_amount(trade, buy_order_fee, order_obj) == expected_amount assert log_has( ( 'Applying fee on amount for Trade(id=None, pair=LTC/ETH, amount=8.00000000, ' @@ -3750,8 +3754,9 @@ def test_get_real_amount_invalid_order(default_conf_usdt, trades_for_order, buy_ ) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') # Amount does not change - assert freqtrade.get_real_amount(trade, limit_buy_order_usdt) == amount + assert freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj) == amount def test_get_real_amount_fees_order(default_conf_usdt, market_buy_order_usdt_doublefee, @@ -3773,7 +3778,8 @@ def test_get_real_amount_fees_order(default_conf_usdt, market_buy_order_usdt_dou # Amount does not change assert trade.fee_open == 0.0025 - assert freqtrade.get_real_amount(trade, market_buy_order_usdt_doublefee) == 30.0 + order_obj = Order.parse_from_ccxt_object(market_buy_order_usdt_doublefee, 'LTC/ETH', 'buy') + assert freqtrade.get_real_amount(trade, market_buy_order_usdt_doublefee, order_obj) == 30.0 assert tfo_mock.call_count == 0 # Fetch fees from trades dict if available to get "proper" values assert round(trade.fee_open, 4) == 0.001 @@ -3797,9 +3803,10 @@ def test_get_real_amount_wrong_amount(default_conf_usdt, trades_for_order, buy_o ) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') # Amount does not change with pytest.raises(DependencyException, match=r"Half bought\? Amounts don't match"): - freqtrade.get_real_amount(trade, limit_buy_order_usdt) + freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj) def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_order, buy_order_fee, @@ -3821,9 +3828,10 @@ def test_get_real_amount_wrong_amount_rounding(default_conf_usdt, trades_for_ord ) freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) + order_obj = Order.parse_from_ccxt_object(buy_order_fee, 'LTC/ETH', 'buy') # Amount changes by fee amount. assert isclose( - freqtrade.get_real_amount(trade, limit_buy_order_usdt), + freqtrade.get_real_amount(trade, limit_buy_order_usdt, order_obj), amount - (amount * 0.001), abs_tol=MATH_CLOSE_PREC, ) @@ -3847,7 +3855,8 @@ def test_get_real_amount_open_trade(default_conf_usdt, fee, mocker): 'side': 'buy', } freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) - assert freqtrade.get_real_amount(trade, order) == amount + order_obj = Order.parse_from_ccxt_object(order, 'LTC/ETH', 'buy') + assert freqtrade.get_real_amount(trade, order, order_obj) == amount @pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [ From 49e087df5b79d90381ac9ee64d0eaac88e044f74 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 13:07:06 +0100 Subject: [PATCH 092/114] Allow Strategy subclassing in different files by enabling local imports --- docs/strategy-advanced.md | 9 ++-- freqtrade/resolvers/iresolver.py | 58 ++++++++++++++++------- tests/strategy/strats/strategy_test_v2.py | 2 +- 3 files changed, 45 insertions(+), 24 deletions(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 3793abacf..fa1c09560 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -164,16 +164,15 @@ class MyAwesomeStrategy2(MyAwesomeStrategy): Both attributes and methods may be overridden, altering behavior of the original strategy in a way you need. !!! Note "Parent-strategy in different files" - If you have the parent-strategy in a different file, you'll need to add the following to the top of your "child"-file to ensure proper loading, otherwise freqtrade may not be able to load the parent strategy correctly. + If you have the parent-strategy in a different file, you can still import the strategy. + Assuming `myawesomestrategy.py` is the filename, and `MyAwesomeStrategy` the strategy you need to import: ``` python - import sys - from pathlib import Path - sys.path.append(str(Path(__file__).parent)) - from myawesomestrategy import MyAwesomeStrategy ``` + This is the recommended way to derive strategies to avoid problems with hyperopt parameter files. + ## Embedding Strategies Freqtrade provides you with an easy way to embed the strategy into your configuration file. diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index c6f97c976..8d132da70 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -6,6 +6,7 @@ This module load custom objects import importlib.util import inspect import logging +import sys from pathlib import Path from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, Union @@ -15,6 +16,22 @@ from freqtrade.exceptions import OperationalException logger = logging.getLogger(__name__) +class PathModifier: + def __init__(self, path: Path): + self.path = path + + def __enter__(self): + """Inject path to allow importing with relative imports.""" + sys.path.insert(0, str(self.path)) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """Undo insertion of local path.""" + str_path = str(self.path) + if str_path in sys.path: + sys.path.remove(str_path) + + class IResolver: """ This class contains all the logic to load custom classes @@ -57,27 +74,32 @@ class IResolver: # Generate spec based on absolute path # Pass object_name as first argument to have logging print a reasonable name. - spec = importlib.util.spec_from_file_location(object_name or "", str(module_path)) - if not spec: - return iter([None]) + with PathModifier(module_path.parent): - module = importlib.util.module_from_spec(spec) - try: - spec.loader.exec_module(module) # type: ignore # importlib does not use typehints - except (ModuleNotFoundError, SyntaxError, ImportError, NameError) as err: - # Catch errors in case a specific module is not installed - logger.warning(f"Could not import {module_path} due to '{err}'") - if enum_failed: + spec = importlib.util.spec_from_file_location(module_path.stem or "", str(module_path)) + if not spec: return iter([None]) - valid_objects_gen = ( - (obj, inspect.getsource(module)) for - name, obj in inspect.getmembers( - module, inspect.isclass) if ((object_name is None or object_name == name) - and issubclass(obj, cls.object_type) - and obj is not cls.object_type) - ) - return valid_objects_gen + module = importlib.util.module_from_spec(spec) + try: + spec.loader.exec_module(module) # type: ignore # importlib does not use typehints + except (ModuleNotFoundError, SyntaxError, ImportError, NameError) as err: + # Catch errors in case a specific module is not installed + logger.warning(f"Could not import {module_path} due to '{err}'") + if enum_failed: + return iter([None]) + + valid_objects_gen = ( + (obj, inspect.getsource(module)) for + name, obj in inspect.getmembers( + module, inspect.isclass) if ((object_name is None or object_name == name) + and issubclass(obj, cls.object_type) + and obj is not cls.object_type + and obj.__module__ == module_path.stem or "" + ) + ) + # The __module__ check ensures we only use strategies that are defined in this folder. + return valid_objects_gen @classmethod def _search_object(cls, directory: Path, *, object_name: str, add_source: bool = False diff --git a/tests/strategy/strats/strategy_test_v2.py b/tests/strategy/strats/strategy_test_v2.py index c57becdad..59f1f569e 100644 --- a/tests/strategy/strats/strategy_test_v2.py +++ b/tests/strategy/strats/strategy_test_v2.py @@ -7,7 +7,7 @@ from pandas import DataFrame import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.persistence import Trade -from freqtrade.strategy.interface import IStrategy +from freqtrade.strategy import IStrategy class StrategyTestV2(IStrategy): From 6ec7b84b92ec24264d5f573f8289cd466322196e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 13:12:26 +0100 Subject: [PATCH 093/114] Modify hyperoptable strategy to use relative importing --- .../strategy/strats/hyperoptable_strategy.py | 88 +------------------ 1 file changed, 3 insertions(+), 85 deletions(-) diff --git a/tests/strategy/strats/hyperoptable_strategy.py b/tests/strategy/strats/hyperoptable_strategy.py index 88bdd078e..dc6b03a3e 100644 --- a/tests/strategy/strats/hyperoptable_strategy.py +++ b/tests/strategy/strats/hyperoptable_strategy.py @@ -1,14 +1,13 @@ # pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement -import talib.abstract as ta from pandas import DataFrame +from strategy_test_v2 import StrategyTestV2 import freqtrade.vendor.qtpylib.indicators as qtpylib -from freqtrade.strategy import (BooleanParameter, DecimalParameter, IntParameter, IStrategy, - RealParameter) +from freqtrade.strategy import BooleanParameter, DecimalParameter, IntParameter, RealParameter -class HyperoptableStrategy(IStrategy): +class HyperoptableStrategy(StrategyTestV2): """ Default Strategy provided by freqtrade bot. Please do not modify this strategy, it's intended for internal use only. @@ -16,38 +15,6 @@ class HyperoptableStrategy(IStrategy): or strategy repository https://github.com/freqtrade/freqtrade-strategies for samples and inspiration. """ - INTERFACE_VERSION = 2 - - # Minimal ROI designed for the strategy - minimal_roi = { - "40": 0.0, - "30": 0.01, - "20": 0.02, - "0": 0.04 - } - - # Optimal stoploss designed for the strategy - stoploss = -0.10 - - # Optimal ticker interval for the strategy - timeframe = '5m' - - # Optional order type mapping - order_types = { - 'buy': 'limit', - 'sell': 'limit', - 'stoploss': 'limit', - 'stoploss_on_exchange': False - } - - # Number of candles the strategy requires before producing valid signals - startup_candle_count: int = 20 - - # Optional time in force for orders - order_time_in_force = { - 'buy': 'gtc', - 'sell': 'gtc', - } buy_params = { 'buy_rsi': 35, @@ -91,55 +58,6 @@ class HyperoptableStrategy(IStrategy): """ return [] - def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: - """ - Adds several different TA indicators to the given DataFrame - - Performance Note: For the best performance be frugal on the number of indicators - you are using. Let uncomment only the indicator you are using in your strategies - or your hyperopt configuration, otherwise you will waste your memory and CPU usage. - :param dataframe: Dataframe with data from the exchange - :param metadata: Additional information, like the currently traded pair - :return: a Dataframe with all mandatory indicators for the strategies - """ - - # Momentum Indicator - # ------------------------------------ - - # ADX - dataframe['adx'] = ta.ADX(dataframe) - - # MACD - macd = ta.MACD(dataframe) - dataframe['macd'] = macd['macd'] - dataframe['macdsignal'] = macd['macdsignal'] - dataframe['macdhist'] = macd['macdhist'] - - # Minus Directional Indicator / Movement - dataframe['minus_di'] = ta.MINUS_DI(dataframe) - - # Plus Directional Indicator / Movement - dataframe['plus_di'] = ta.PLUS_DI(dataframe) - - # RSI - dataframe['rsi'] = ta.RSI(dataframe) - - # Stoch fast - stoch_fast = ta.STOCHF(dataframe) - dataframe['fastd'] = stoch_fast['fastd'] - dataframe['fastk'] = stoch_fast['fastk'] - - # Bollinger bands - bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) - dataframe['bb_lowerband'] = bollinger['lower'] - dataframe['bb_middleband'] = bollinger['mid'] - dataframe['bb_upperband'] = bollinger['upper'] - - # EMA - Exponential Moving Average - dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) - - return dataframe - def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe From e9c4e6a69d9c173b30c6b4d2263ad658b1950b42 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 20 Mar 2022 13:14:52 +0100 Subject: [PATCH 094/114] Update derived strategy documentation --- docs/strategy-advanced.md | 11 +++++++---- freqtrade/resolvers/iresolver.py | 6 +++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index fa1c09560..533402528 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -146,7 +146,7 @@ def version(self) -> str: The strategies can be derived from other strategies. This avoids duplication of your custom strategy code. You can use this technique to override small parts of your main strategy, leaving the rest untouched: -``` python +``` python title="user_data/strategies/myawesomestrategy.py" class MyAwesomeStrategy(IStrategy): ... stoploss = 0.13 @@ -155,6 +155,10 @@ class MyAwesomeStrategy(IStrategy): # should be in any custom strategy... ... +``` + +``` python title="user_data/strategies/MyAwesomeStrategy2.py" +from myawesomestrategy import MyAwesomeStrategy class MyAwesomeStrategy2(MyAwesomeStrategy): # Override something stoploss = 0.08 @@ -163,16 +167,15 @@ class MyAwesomeStrategy2(MyAwesomeStrategy): Both attributes and methods may be overridden, altering behavior of the original strategy in a way you need. +While keeping the subclass in the same file is technically possible, it can lead to some problems with hyperopt parameter files. + !!! Note "Parent-strategy in different files" If you have the parent-strategy in a different file, you can still import the strategy. Assuming `myawesomestrategy.py` is the filename, and `MyAwesomeStrategy` the strategy you need to import: ``` python - from myawesomestrategy import MyAwesomeStrategy ``` - This is the recommended way to derive strategies to avoid problems with hyperopt parameter files. - ## Embedding Strategies Freqtrade provides you with an easy way to embed the strategy into your configuration file. diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py index 8d132da70..3ab461041 100644 --- a/freqtrade/resolvers/iresolver.py +++ b/freqtrade/resolvers/iresolver.py @@ -75,8 +75,8 @@ class IResolver: # Generate spec based on absolute path # Pass object_name as first argument to have logging print a reasonable name. with PathModifier(module_path.parent): - - spec = importlib.util.spec_from_file_location(module_path.stem or "", str(module_path)) + module_name = module_path.stem or "" + spec = importlib.util.spec_from_file_location(module_name, str(module_path)) if not spec: return iter([None]) @@ -95,7 +95,7 @@ class IResolver: module, inspect.isclass) if ((object_name is None or object_name == name) and issubclass(obj, cls.object_type) and obj is not cls.object_type - and obj.__module__ == module_path.stem or "" + and obj.__module__ == module_name ) ) # The __module__ check ensures we only use strategies that are defined in this folder. From f5e71a67fabdbb6b403e9ed87f94c2fa773effb9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 03:01:11 +0000 Subject: [PATCH 095/114] Bump pymdown-extensions from 9.2 to 9.3 Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 9.2 to 9.3. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/9.2...9.3) --- updated-dependencies: - dependency-name: pymdown-extensions dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- docs/requirements-docs.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 0ca0e4b63..01ce559ff 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,4 +1,4 @@ mkdocs==1.2.3 mkdocs-material==8.2.5 mdx_truly_sane_lists==1.2 -pymdown-extensions==9.2 +pymdown-extensions==9.3 From 1bc5d449a205c34c1672b0e0684b2936e884f1e7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 03:01:16 +0000 Subject: [PATCH 096/114] Bump cryptography from 36.0.1 to 36.0.2 Bumps [cryptography](https://github.com/pyca/cryptography) from 36.0.1 to 36.0.2. - [Release notes](https://github.com/pyca/cryptography/releases) - [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pyca/cryptography/compare/36.0.1...36.0.2) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f0f030e78..e1c052353 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ pandas-ta==0.3.14b ccxt==1.76.5 # Pin cryptography for now due to rust build errors with piwheels -cryptography==36.0.1 +cryptography==36.0.2 aiohttp==3.8.1 SQLAlchemy==1.4.32 python-telegram-bot==13.11 From 59c7403b1214cb21ba5a377bafe1b3191e825cac Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 03:01:23 +0000 Subject: [PATCH 097/114] Bump urllib3 from 1.26.8 to 1.26.9 Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.8 to 1.26.9. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/1.26.9/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.26.8...1.26.9) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f0f030e78..c112a37b5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,7 +11,7 @@ python-telegram-bot==13.11 arrow==1.2.2 cachetools==4.2.2 requests==2.27.1 -urllib3==1.26.8 +urllib3==1.26.9 jsonschema==4.4.0 TA-Lib==0.4.24 technical==1.3.0 From 5a136f04dfcdc7f7e3b4a6534ecd6c83a0abf373 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 03:01:33 +0000 Subject: [PATCH 098/114] Bump ccxt from 1.76.5 to 1.76.65 Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.76.5 to 1.76.65. - [Release notes](https://github.com/ccxt/ccxt/releases) - [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg) - [Commits](https://github.com/ccxt/ccxt/compare/1.76.5...1.76.65) --- updated-dependencies: - dependency-name: ccxt dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f0f030e78..00ff46088 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.22.3 pandas==1.4.1 pandas-ta==0.3.14b -ccxt==1.76.5 +ccxt==1.76.65 # Pin cryptography for now due to rust build errors with piwheels cryptography==36.0.1 aiohttp==3.8.1 From 1a37100bd45a11c6665749b5ff028e0adac14a1c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 03:01:41 +0000 Subject: [PATCH 099/114] Bump types-python-dateutil from 2.8.9 to 2.8.10 Bumps [types-python-dateutil](https://github.com/python/typeshed) from 2.8.9 to 2.8.10. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-python-dateutil dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index c2f3eae8a..d9ca8c6ae 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -26,4 +26,4 @@ types-requests==2.27.12 types-tabulate==0.8.5 # Extensions to datetime library -types-python-dateutil==2.8.9 \ No newline at end of file +types-python-dateutil==2.8.10 \ No newline at end of file From 03090d8f3ffc34dc32f6d957385e2a52b894ad49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 03:01:45 +0000 Subject: [PATCH 100/114] Bump pytest from 7.1.0 to 7.1.1 Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.1.0 to 7.1.1. - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.1.0...7.1.1) --- updated-dependencies: - dependency-name: pytest dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index c2f3eae8a..3d87763aa 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -7,7 +7,7 @@ coveralls==3.3.1 flake8==4.0.1 flake8-tidy-imports==4.6.0 mypy==0.940 -pytest==7.1.0 +pytest==7.1.1 pytest-asyncio==0.18.2 pytest-cov==3.0.0 pytest-mock==3.7.0 From 057db5aaabe4356e465986650469d6b1b953836e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 05:30:51 +0000 Subject: [PATCH 101/114] Bump types-requests from 2.27.12 to 2.27.14 Bumps [types-requests](https://github.com/python/typeshed) from 2.27.12 to 2.27.14. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-requests dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index d9ca8c6ae..614921019 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -22,7 +22,7 @@ nbconvert==6.4.4 # mypy types types-cachetools==5.0.0 types-filelock==3.2.5 -types-requests==2.27.12 +types-requests==2.27.14 types-tabulate==0.8.5 # Extensions to datetime library From 7170b585f9a3c9e665bfe0c3fb5697e820a266d2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 08:06:25 +0000 Subject: [PATCH 102/114] Bump mypy from 0.940 to 0.941 Bumps [mypy](https://github.com/python/mypy) from 0.940 to 0.941. - [Release notes](https://github.com/python/mypy/releases) - [Commits](https://github.com/python/mypy/compare/v0.940...v0.941) --- updated-dependencies: - dependency-name: mypy dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index ca625238e..af19187fa 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,7 +6,7 @@ coveralls==3.3.1 flake8==4.0.1 flake8-tidy-imports==4.6.0 -mypy==0.940 +mypy==0.941 pytest==7.1.1 pytest-asyncio==0.18.2 pytest-cov==3.0.0 From c28e0b0d0cd8f252a93acd854701c658fdc2a3bc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Mar 2022 08:06:41 +0000 Subject: [PATCH 103/114] Bump types-tabulate from 0.8.5 to 0.8.6 Bumps [types-tabulate](https://github.com/python/typeshed) from 0.8.5 to 0.8.6. - [Release notes](https://github.com/python/typeshed/releases) - [Commits](https://github.com/python/typeshed/commits) --- updated-dependencies: - dependency-name: types-tabulate dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index ca625238e..8bd1b68ba 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -23,7 +23,7 @@ nbconvert==6.4.4 types-cachetools==5.0.0 types-filelock==3.2.5 types-requests==2.27.14 -types-tabulate==0.8.5 +types-tabulate==0.8.6 # Extensions to datetime library types-python-dateutil==2.8.10 \ No newline at end of file From 487d3e891e454542e3f438986f080b2b852fae2f Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 21 Mar 2022 19:41:34 +0100 Subject: [PATCH 104/114] Revert version to develop for now --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index d57cc9852..f8be8f66f 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = 'dev' +__version__ = 'develop' if 'dev' in __version__: try: From e7418cdcdb9cf7916ff0a5464113c20817fe7486 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 22 Mar 2022 19:03:22 +0100 Subject: [PATCH 105/114] Remove obsolete note box closes #6581 --- docs/strategy-advanced.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/strategy-advanced.md b/docs/strategy-advanced.md index 533402528..b1f154355 100644 --- a/docs/strategy-advanced.md +++ b/docs/strategy-advanced.md @@ -167,14 +167,7 @@ class MyAwesomeStrategy2(MyAwesomeStrategy): Both attributes and methods may be overridden, altering behavior of the original strategy in a way you need. -While keeping the subclass in the same file is technically possible, it can lead to some problems with hyperopt parameter files. - -!!! Note "Parent-strategy in different files" - If you have the parent-strategy in a different file, you can still import the strategy. - Assuming `myawesomestrategy.py` is the filename, and `MyAwesomeStrategy` the strategy you need to import: - - ``` python - ``` +While keeping the subclass in the same file is technically possible, it can lead to some problems with hyperopt parameter files, we therefore recommend to use separate strategy files, and import the parent strategy as shown above. ## Embedding Strategies From 094676def4255dc30e271ea274454484b848d66d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=AE=AE=E0=AE=A9=E0=AF=8B=E0=AE=9C=E0=AF=8D=E0=AE=95?= =?UTF-8?q?=E0=AF=81=E0=AE=AE=E0=AE=BE=E0=AE=B0=E0=AF=8D=20=E0=AE=AA?= =?UTF-8?q?=E0=AE=B4=E0=AE=A9=E0=AE=BF=E0=AE=9A=E0=AF=8D=E0=AE=9A=E0=AE=BE?= =?UTF-8?q?=E0=AE=AE=E0=AE=BF?= Date: Wed, 23 Mar 2022 20:47:55 +0530 Subject: [PATCH 106/114] Removed old datetime keys and added timestamp --- tests/conftest.py | 56 +++++++++++++---------------------------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 57122c01c..4192dc1cc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1019,7 +1019,6 @@ def limit_buy_order_open(): 'type': 'limit', 'side': 'buy', 'symbol': 'mocked', - 'datetime': arrow.utcnow().isoformat(), 'timestamp': arrow.utcnow().int_timestamp, 'price': 0.00001099, 'amount': 90.99181073, @@ -1046,7 +1045,7 @@ def market_buy_order(): 'type': 'market', 'side': 'buy', 'symbol': 'mocked', - 'datetime': arrow.utcnow().isoformat(), + 'timestamp': arrow.utcnow().int_timestamp, 'price': 0.00004099, 'amount': 91.99181073, 'filled': 91.99181073, @@ -1062,7 +1061,7 @@ def market_sell_order(): 'type': 'market', 'side': 'sell', 'symbol': 'mocked', - 'datetime': arrow.utcnow().isoformat(), + 'timestamp': arrow.utcnow().int_timestamp, 'price': 0.00004173, 'amount': 91.99181073, 'filled': 91.99181073, @@ -1078,7 +1077,7 @@ def limit_buy_order_old(): 'type': 'limit', 'side': 'buy', 'symbol': 'mocked', - 'datetime': str(arrow.utcnow().shift(minutes=-601).datetime), + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'price': 0.00001099, 'amount': 90.99181073, 'filled': 0.0, @@ -1094,7 +1093,7 @@ def limit_sell_order_old(): 'type': 'limit', 'side': 'sell', 'symbol': 'ETH/BTC', - 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'price': 0.00001099, 'amount': 90.99181073, 'filled': 0.0, @@ -1110,7 +1109,7 @@ def limit_buy_order_old_partial(): 'type': 'limit', 'side': 'buy', 'symbol': 'ETH/BTC', - 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'price': 0.00001099, 'amount': 90.99181073, 'filled': 23.0, @@ -1140,7 +1139,7 @@ def limit_buy_order_canceled_empty(request): 'id': '1234512345', 'clientOrderId': None, 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, - 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', 'type': 'limit', @@ -1161,7 +1160,7 @@ def limit_buy_order_canceled_empty(request): 'id': 'AZNPFF-4AC4N-7MKTAT', 'clientOrderId': None, 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, - 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'lastTradeTimestamp': None, 'status': 'canceled', 'symbol': 'LTC/USDT', @@ -1182,7 +1181,7 @@ def limit_buy_order_canceled_empty(request): 'id': '1234512345', 'clientOrderId': 'alb1234123', 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, - 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', 'type': 'limit', @@ -1203,7 +1202,7 @@ def limit_buy_order_canceled_empty(request): 'id': '1234512345', 'clientOrderId': 'alb1234123', 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, - 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', 'type': 'limit', @@ -1227,7 +1226,7 @@ def limit_sell_order_open(): 'type': 'limit', 'side': 'sell', 'symbol': 'mocked', - 'datetime': arrow.utcnow().isoformat(), + 'timestamp': arrow.utcnow().int_timestamp, 'timestamp': arrow.utcnow().int_timestamp, 'price': 0.00001173, 'amount': 90.99181073, @@ -1274,7 +1273,6 @@ def order_book_l2(): [0.04402, 37.64] ], 'timestamp': None, - 'datetime': None, 'nonce': 288004540 }) @@ -1308,7 +1306,6 @@ def order_book_l2_usd(): [25.578, 78.614] ], 'timestamp': None, - 'datetime': None, 'nonce': 2372149736 }) @@ -1355,7 +1352,6 @@ def tickers(): 'ETH/BTC': { 'symbol': 'ETH/BTC', 'timestamp': 1522014806207, - 'datetime': '2018-03-25T21:53:26.207Z', 'high': 0.061697, 'low': 0.060531, 'bid': 0.061588, @@ -1377,7 +1373,6 @@ def tickers(): 'TKN/BTC': { 'symbol': 'TKN/BTC', 'timestamp': 1522014806169, - 'datetime': '2018-03-25T21:53:26.169Z', 'high': 0.01885, 'low': 0.018497, 'bid': 0.018799, @@ -1394,7 +1389,6 @@ def tickers(): 'BLK/BTC': { 'symbol': 'BLK/BTC', 'timestamp': 1522014806072, - 'datetime': '2018-03-25T21:53:26.720Z', 'high': 0.007745, 'low': 0.007512, 'bid': 0.007729, @@ -1416,7 +1410,6 @@ def tickers(): 'LTC/BTC': { 'symbol': 'LTC/BTC', 'timestamp': 1523787258992, - 'datetime': '2018-04-15T10:14:19.992Z', 'high': 0.015978, 'low': 0.0157, 'bid': 0.015954, @@ -1438,7 +1431,6 @@ def tickers(): 'BTT/BTC': { 'symbol': 'BTT/BTC', 'timestamp': 1550936557206, - 'datetime': '2019-02-23T15:42:37.206Z', 'high': 0.00000026, 'low': 0.00000024, 'bid': 0.00000024, @@ -1460,7 +1452,6 @@ def tickers(): 'HOT/BTC': { 'symbol': 'HOT/BTC', 'timestamp': 1572273518661, - 'datetime': '2019-10-28T14:38:38.661Z', 'high': 0.00000011, 'low': 0.00000009, 'bid': 0.0000001, @@ -1482,7 +1473,6 @@ def tickers(): 'FUEL/BTC': { 'symbol': 'FUEL/BTC', 'timestamp': 1572340250771, - 'datetime': '2019-10-29T09:10:50.771Z', 'high': 0.00000040, 'low': 0.00000035, 'bid': 0.00000036, @@ -1504,7 +1494,6 @@ def tickers(): 'BTC/USDT': { 'symbol': 'BTC/USDT', 'timestamp': 1573758371399, - 'datetime': '2019-11-14T19:06:11.399Z', 'high': 8800.0, 'low': 8582.6, 'bid': 8648.16, @@ -1526,7 +1515,6 @@ def tickers(): 'ETH/USDT': { 'symbol': 'ETH/USDT', 'timestamp': 1522014804118, - 'datetime': '2018-03-25T21:53:24.118Z', 'high': 530.88, 'low': 512.0, 'bid': 529.73, @@ -1548,7 +1536,6 @@ def tickers(): 'TKN/USDT': { 'symbol': 'TKN/USDT', 'timestamp': 1522014806198, - 'datetime': '2018-03-25T21:53:26.198Z', 'high': 8718.0, 'low': 8365.77, 'bid': 8603.64, @@ -1570,7 +1557,6 @@ def tickers(): 'BLK/USDT': { 'symbol': 'BLK/USDT', 'timestamp': 1522014806145, - 'datetime': '2018-03-25T21:53:26.145Z', 'high': 66.95, 'low': 63.38, 'bid': 66.473, @@ -1592,7 +1578,6 @@ def tickers(): 'LTC/USDT': { 'symbol': 'LTC/USDT', 'timestamp': 1523787257812, - 'datetime': '2018-04-15T10:14:18.812Z', 'high': 129.94, 'low': 124.0, 'bid': 129.28, @@ -1614,7 +1599,6 @@ def tickers(): 'XRP/BTC': { 'symbol': 'XRP/BTC', 'timestamp': 1573758257534, - 'datetime': '2019-11-14T19:04:17.534Z', 'high': 3.126e-05, 'low': 3.061e-05, 'bid': 3.093e-05, @@ -1726,7 +1710,6 @@ def trades_for_order(): 'isBestMatch': True }, 'timestamp': 1521663363189, - 'datetime': '2018-03-21T20:16:03.189Z', 'symbol': 'LTC/USDT', 'id': '34567', 'order': '123456', @@ -1762,7 +1745,6 @@ def fetch_trades_result(): 'm': False, 'M': True}, 'timestamp': 1565798399463, - 'datetime': '2019-08-14T15:59:59.463Z', 'symbol': 'ETH/BTC', 'id': '126181329', 'order': None, @@ -1782,7 +1764,6 @@ def fetch_trades_result(): 'm': False, 'M': True}, 'timestamp': 1565798399629, - 'datetime': '2019-08-14T15:59:59.629Z', 'symbol': 'ETH/BTC', 'id': '126181330', 'order': None, @@ -1802,7 +1783,6 @@ def fetch_trades_result(): 'm': True, 'M': True}, 'timestamp': 1565798399752, - 'datetime': '2019-08-14T15:59:59.752Z', 'symbol': 'ETH/BTC', 'id': '126181331', 'order': None, @@ -1822,7 +1802,6 @@ def fetch_trades_result(): 'm': True, 'M': True}, 'timestamp': 1565798399862, - 'datetime': '2019-08-14T15:59:59.862Z', 'symbol': 'ETH/BTC', 'id': '126181332', 'order': None, @@ -1842,7 +1821,6 @@ def fetch_trades_result(): 'm': True, 'M': True}, 'timestamp': 1565798399872, - 'datetime': '2019-08-14T15:59:59.872Z', 'symbol': 'ETH/BTC', 'id': '126181333', 'order': None, @@ -1859,7 +1837,6 @@ def fetch_trades_result(): def trades_for_order2(): return [{'info': {}, 'timestamp': 1521663363189, - 'datetime': '2018-03-21T20:16:03.189Z', 'symbol': 'LTC/ETH', 'id': '34567', 'order': '123456', @@ -1871,7 +1848,6 @@ def trades_for_order2(): 'fee': {'cost': 0.004, 'currency': 'LTC'}}, {'info': {}, 'timestamp': 1521663363189, - 'datetime': '2018-03-21T20:16:03.189Z', 'symbol': 'LTC/ETH', 'id': '34567', 'order': '123456', @@ -1890,7 +1866,7 @@ def buy_order_fee(): 'type': 'limit', 'side': 'buy', 'symbol': 'mocked', - 'datetime': str(arrow.utcnow().shift(minutes=-601).datetime), + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'price': 0.245441, 'amount': 8.0, 'cost': 1.963528, @@ -2187,7 +2163,7 @@ def limit_buy_order_usdt_open(): 'type': 'limit', 'side': 'buy', 'symbol': 'mocked', - 'datetime': arrow.utcnow().isoformat(), + 'timestamp': arrow.utcnow().int_timestamp, 'timestamp': arrow.utcnow().int_timestamp, 'price': 2.00, 'amount': 30.0, @@ -2214,7 +2190,7 @@ def limit_sell_order_usdt_open(): 'type': 'limit', 'side': 'sell', 'symbol': 'mocked', - 'datetime': arrow.utcnow().isoformat(), + 'timestamp': arrow.utcnow().int_timestamp, 'timestamp': arrow.utcnow().int_timestamp, 'price': 2.20, 'amount': 30.0, @@ -2240,7 +2216,7 @@ def market_buy_order_usdt(): 'type': 'market', 'side': 'buy', 'symbol': 'mocked', - 'datetime': arrow.utcnow().isoformat(), + 'timestamp': arrow.utcnow().int_timestamp, 'price': 2.00, 'amount': 30.0, 'filled': 30.0, @@ -2261,7 +2237,6 @@ def market_buy_order_usdt_doublefee(market_buy_order_usdt): ] order['trades'] = [{ 'timestamp': None, - 'datetime': None, 'symbol': 'ETH/USDT', 'id': None, 'order': '123', @@ -2274,7 +2249,6 @@ def market_buy_order_usdt_doublefee(market_buy_order_usdt): 'fee': {'cost': 0.00025125, 'currency': 'BNB'} }, { 'timestamp': None, - 'datetime': None, 'symbol': 'ETH/USDT', 'id': None, 'order': '123', @@ -2296,7 +2270,7 @@ def market_sell_order_usdt(): 'type': 'market', 'side': 'sell', 'symbol': 'mocked', - 'datetime': arrow.utcnow().isoformat(), + 'timestamp': arrow.utcnow().int_timestamp, 'price': 2.20, 'amount': 30.0, 'filled': 30.0, From dae9f4d877b921c935ea71a095ff85f9a0fffe80 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 24 Mar 2022 06:48:59 +0100 Subject: [PATCH 107/114] Update doc clarity, partially revert prior commit --- docs/includes/pricing.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/includes/pricing.md b/docs/includes/pricing.md index 4505d7fec..103df6cd3 100644 --- a/docs/includes/pricing.md +++ b/docs/includes/pricing.md @@ -51,9 +51,9 @@ When buying with the orderbook enabled (`bid_strategy.use_order_book=True`), Fre #### Buy price without Orderbook enabled -The following section uses `side` as the configured `bid_strategy.price_side`. +The following section uses `side` as the configured `bid_strategy.price_side` (defaults to `"bid"`). -When not using orderbook (`bid_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price. +When not using orderbook (`bid_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price based on `bid_strategy.ask_last_balance`.. The `bid_strategy.ask_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the `last` price and values between those interpolate between ask and last price. @@ -88,9 +88,9 @@ When selling with the orderbook enabled (`ask_strategy.use_order_book=True`), Fr #### Sell price without Orderbook enabled -The following section uses `side` as the configured `ask_strategy.price_side`. +The following section uses `side` as the configured `ask_strategy.price_side` (defaults to `"ask"`). -When not using orderbook (`ask_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's above the `last` traded price from the ticker. Otherwise (when the `side` price is below the `last` price), it calculates a rate between `side` and `last` price. +When not using orderbook (`ask_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's above the `last` traded price from the ticker. Otherwise (when the `side` price is below the `last` price), it calculates a rate between `side` and `last` price based on `ask_strategy.bid_last_balance`. The `ask_strategy.bid_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the last price and values between those interpolate between `side` and last price. From d94b84e38cf155f37a77f88ebb5cc2cc03d190d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=AE=AE=E0=AE=A9=E0=AF=8B=E0=AE=9C=E0=AF=8D=E0=AE=95?= =?UTF-8?q?=E0=AF=81=E0=AE=AE=E0=AE=BE=E0=AE=B0=E0=AF=8D=20=E0=AE=AA?= =?UTF-8?q?=E0=AE=B4=E0=AE=A9=E0=AE=BF=E0=AE=9A=E0=AF=8D=E0=AE=9A=E0=AE=BE?= =?UTF-8?q?=E0=AE=AE=E0=AE=BF?= Date: Fri, 25 Mar 2022 08:55:58 +0530 Subject: [PATCH 108/114] datetime included again --- tests/conftest.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 4192dc1cc..e3c0cf070 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1020,6 +1020,7 @@ def limit_buy_order_open(): 'side': 'buy', 'symbol': 'mocked', 'timestamp': arrow.utcnow().int_timestamp, + 'datetime': arrow.utcnow().isoformat(), 'price': 0.00001099, 'amount': 90.99181073, 'filled': 0.0, @@ -1046,6 +1047,7 @@ def market_buy_order(): 'side': 'buy', 'symbol': 'mocked', 'timestamp': arrow.utcnow().int_timestamp, + 'datetime': arrow.utcnow().isoformat(), 'price': 0.00004099, 'amount': 91.99181073, 'filled': 91.99181073, @@ -1062,6 +1064,7 @@ def market_sell_order(): 'side': 'sell', 'symbol': 'mocked', 'timestamp': arrow.utcnow().int_timestamp, + 'datetime': arrow.utcnow().isoformat(), 'price': 0.00004173, 'amount': 91.99181073, 'filled': 91.99181073, @@ -1077,6 +1080,7 @@ def limit_buy_order_old(): 'type': 'limit', 'side': 'buy', 'symbol': 'mocked', + 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, 'price': 0.00001099, 'amount': 90.99181073, @@ -1094,6 +1098,7 @@ def limit_sell_order_old(): 'side': 'sell', 'symbol': 'ETH/BTC', 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, + 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'price': 0.00001099, 'amount': 90.99181073, 'filled': 0.0, @@ -1110,6 +1115,7 @@ def limit_buy_order_old_partial(): 'side': 'buy', 'symbol': 'ETH/BTC', 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, + 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'price': 0.00001099, 'amount': 90.99181073, 'filled': 23.0, @@ -1389,6 +1395,7 @@ def tickers(): 'BLK/BTC': { 'symbol': 'BLK/BTC', 'timestamp': 1522014806072, + 'datetime': '2018-03-25T21:53:26.072Z', 'high': 0.007745, 'low': 0.007512, 'bid': 0.007729, @@ -1867,6 +1874,7 @@ def buy_order_fee(): 'side': 'buy', 'symbol': 'mocked', 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, + 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'price': 0.245441, 'amount': 8.0, 'cost': 1.963528, @@ -2217,6 +2225,7 @@ def market_buy_order_usdt(): 'side': 'buy', 'symbol': 'mocked', 'timestamp': arrow.utcnow().int_timestamp, + 'datetime': arrow.utcnow().isoformat(), 'price': 2.00, 'amount': 30.0, 'filled': 30.0, @@ -2271,6 +2280,7 @@ def market_sell_order_usdt(): 'side': 'sell', 'symbol': 'mocked', 'timestamp': arrow.utcnow().int_timestamp, + 'datetime': arrow.utcnow().isoformat(), 'price': 2.20, 'amount': 30.0, 'filled': 30.0, From 3f98fcb0db723e4a6ded6dda7eae32f02da89b6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=AE=AE=E0=AE=A9=E0=AF=8B=E0=AE=9C=E0=AF=8D=E0=AE=95?= =?UTF-8?q?=E0=AF=81=E0=AE=AE=E0=AE=BE=E0=AE=B0=E0=AF=8D=20=E0=AE=AA?= =?UTF-8?q?=E0=AE=B4=E0=AE=A9=E0=AE=BF=E0=AE=9A=E0=AF=8D=E0=AE=9A=E0=AE=BE?= =?UTF-8?q?=E0=AE=AE=E0=AE=BF?= Date: Fri, 25 Mar 2022 09:19:39 +0530 Subject: [PATCH 109/114] all datetime included again --- tests/conftest.py | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index e3c0cf070..7dbd13bc3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1145,7 +1145,7 @@ def limit_buy_order_canceled_empty(request): 'id': '1234512345', 'clientOrderId': None, 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, + 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', 'type': 'limit', @@ -1166,7 +1166,7 @@ def limit_buy_order_canceled_empty(request): 'id': 'AZNPFF-4AC4N-7MKTAT', 'clientOrderId': None, 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, + 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'status': 'canceled', 'symbol': 'LTC/USDT', @@ -1187,7 +1187,7 @@ def limit_buy_order_canceled_empty(request): 'id': '1234512345', 'clientOrderId': 'alb1234123', 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, + 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', 'type': 'limit', @@ -1208,7 +1208,7 @@ def limit_buy_order_canceled_empty(request): 'id': '1234512345', 'clientOrderId': 'alb1234123', 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, + 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', 'type': 'limit', @@ -1232,7 +1232,7 @@ def limit_sell_order_open(): 'type': 'limit', 'side': 'sell', 'symbol': 'mocked', - 'timestamp': arrow.utcnow().int_timestamp, + 'datetime': arrow.utcnow().isoformat(), 'timestamp': arrow.utcnow().int_timestamp, 'price': 0.00001173, 'amount': 90.99181073, @@ -1279,6 +1279,7 @@ def order_book_l2(): [0.04402, 37.64] ], 'timestamp': None, + 'datetime': None, 'nonce': 288004540 }) @@ -1312,6 +1313,7 @@ def order_book_l2_usd(): [25.578, 78.614] ], 'timestamp': None, + 'datetime': None, 'nonce': 2372149736 }) @@ -1358,6 +1360,7 @@ def tickers(): 'ETH/BTC': { 'symbol': 'ETH/BTC', 'timestamp': 1522014806207, + 'datetime': '2018-03-25T21:53:26.207Z', 'high': 0.061697, 'low': 0.060531, 'bid': 0.061588, @@ -1379,6 +1382,7 @@ def tickers(): 'TKN/BTC': { 'symbol': 'TKN/BTC', 'timestamp': 1522014806169, + 'datetime': '2018-03-25T21:53:26.169Z', 'high': 0.01885, 'low': 0.018497, 'bid': 0.018799, @@ -1417,6 +1421,7 @@ def tickers(): 'LTC/BTC': { 'symbol': 'LTC/BTC', 'timestamp': 1523787258992, + 'datetime': '2018-04-15T10:14:19.992Z', 'high': 0.015978, 'low': 0.0157, 'bid': 0.015954, @@ -1438,6 +1443,7 @@ def tickers(): 'BTT/BTC': { 'symbol': 'BTT/BTC', 'timestamp': 1550936557206, + 'datetime': '2019-02-23T15:42:37.206Z', 'high': 0.00000026, 'low': 0.00000024, 'bid': 0.00000024, @@ -1459,6 +1465,7 @@ def tickers(): 'HOT/BTC': { 'symbol': 'HOT/BTC', 'timestamp': 1572273518661, + 'datetime': '2019-10-28T14:38:38.661Z', 'high': 0.00000011, 'low': 0.00000009, 'bid': 0.0000001, @@ -1480,6 +1487,7 @@ def tickers(): 'FUEL/BTC': { 'symbol': 'FUEL/BTC', 'timestamp': 1572340250771, + 'datetime': '2019-10-29T09:10:50.771Z', 'high': 0.00000040, 'low': 0.00000035, 'bid': 0.00000036, @@ -1501,6 +1509,7 @@ def tickers(): 'BTC/USDT': { 'symbol': 'BTC/USDT', 'timestamp': 1573758371399, + 'datetime': '2019-11-14T19:06:11.399Z', 'high': 8800.0, 'low': 8582.6, 'bid': 8648.16, @@ -1522,6 +1531,7 @@ def tickers(): 'ETH/USDT': { 'symbol': 'ETH/USDT', 'timestamp': 1522014804118, + 'datetime': '2018-03-25T21:53:24.118Z', 'high': 530.88, 'low': 512.0, 'bid': 529.73, @@ -1543,6 +1553,7 @@ def tickers(): 'TKN/USDT': { 'symbol': 'TKN/USDT', 'timestamp': 1522014806198, + 'datetime': '2018-03-25T21:53:26.198Z', 'high': 8718.0, 'low': 8365.77, 'bid': 8603.64, @@ -1564,6 +1575,7 @@ def tickers(): 'BLK/USDT': { 'symbol': 'BLK/USDT', 'timestamp': 1522014806145, + 'datetime': '2018-03-25T21:53:26.145Z', 'high': 66.95, 'low': 63.38, 'bid': 66.473, @@ -1585,6 +1597,7 @@ def tickers(): 'LTC/USDT': { 'symbol': 'LTC/USDT', 'timestamp': 1523787257812, + 'datetime': '2018-04-15T10:14:18.812Z', 'high': 129.94, 'low': 124.0, 'bid': 129.28, @@ -1606,6 +1619,7 @@ def tickers(): 'XRP/BTC': { 'symbol': 'XRP/BTC', 'timestamp': 1573758257534, + 'datetime': '2019-11-14T19:04:17.534Z', 'high': 3.126e-05, 'low': 3.061e-05, 'bid': 3.093e-05, @@ -1717,6 +1731,7 @@ def trades_for_order(): 'isBestMatch': True }, 'timestamp': 1521663363189, + 'datetime': '2018-03-21T20:16:03.189Z', 'symbol': 'LTC/USDT', 'id': '34567', 'order': '123456', @@ -1752,6 +1767,7 @@ def fetch_trades_result(): 'm': False, 'M': True}, 'timestamp': 1565798399463, + 'datetime': '2019-08-14T15:59:59.463Z', 'symbol': 'ETH/BTC', 'id': '126181329', 'order': None, @@ -1771,6 +1787,7 @@ def fetch_trades_result(): 'm': False, 'M': True}, 'timestamp': 1565798399629, + 'datetime': '2019-08-14T15:59:59.629Z', 'symbol': 'ETH/BTC', 'id': '126181330', 'order': None, @@ -1790,6 +1807,7 @@ def fetch_trades_result(): 'm': True, 'M': True}, 'timestamp': 1565798399752, + 'datetime': '2019-08-14T15:59:59.752Z', 'symbol': 'ETH/BTC', 'id': '126181331', 'order': None, @@ -1809,6 +1827,7 @@ def fetch_trades_result(): 'm': True, 'M': True}, 'timestamp': 1565798399862, + 'datetime': '2019-08-14T15:59:59.862Z', 'symbol': 'ETH/BTC', 'id': '126181332', 'order': None, @@ -1828,6 +1847,7 @@ def fetch_trades_result(): 'm': True, 'M': True}, 'timestamp': 1565798399872, + 'datetime': '2019-08-14T15:59:59.872Z', 'symbol': 'ETH/BTC', 'id': '126181333', 'order': None, @@ -1844,6 +1864,7 @@ def fetch_trades_result(): def trades_for_order2(): return [{'info': {}, 'timestamp': 1521663363189, + 'datetime': '2018-03-21T20:16:03.189Z', 'symbol': 'LTC/ETH', 'id': '34567', 'order': '123456', @@ -1855,6 +1876,7 @@ def trades_for_order2(): 'fee': {'cost': 0.004, 'currency': 'LTC'}}, {'info': {}, 'timestamp': 1521663363189, + 'datetime': '2018-03-21T20:16:03.189Z', 'symbol': 'LTC/ETH', 'id': '34567', 'order': '123456', @@ -2171,7 +2193,7 @@ def limit_buy_order_usdt_open(): 'type': 'limit', 'side': 'buy', 'symbol': 'mocked', - 'timestamp': arrow.utcnow().int_timestamp, + 'datetime': arrow.utcnow().isoformat(), 'timestamp': arrow.utcnow().int_timestamp, 'price': 2.00, 'amount': 30.0, @@ -2198,7 +2220,7 @@ def limit_sell_order_usdt_open(): 'type': 'limit', 'side': 'sell', 'symbol': 'mocked', - 'timestamp': arrow.utcnow().int_timestamp, + 'datetime': arrow.utcnow().isoformat(), 'timestamp': arrow.utcnow().int_timestamp, 'price': 2.20, 'amount': 30.0, @@ -2246,6 +2268,7 @@ def market_buy_order_usdt_doublefee(market_buy_order_usdt): ] order['trades'] = [{ 'timestamp': None, + 'datetime': None, 'symbol': 'ETH/USDT', 'id': None, 'order': '123', @@ -2258,6 +2281,7 @@ def market_buy_order_usdt_doublefee(market_buy_order_usdt): 'fee': {'cost': 0.00025125, 'currency': 'BNB'} }, { 'timestamp': None, + 'datetime': None, 'symbol': 'ETH/USDT', 'id': None, 'order': '123', From a55bc9c1e4898b970f85ae477094ccb9c98212d6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 25 Mar 2022 08:02:27 +0100 Subject: [PATCH 110/114] Pin jinja in docs requirements --- docs/requirements-docs.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/requirements-docs.txt b/docs/requirements-docs.txt index 01ce559ff..3afc212d3 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -2,3 +2,4 @@ mkdocs==1.2.3 mkdocs-material==8.2.5 mdx_truly_sane_lists==1.2 pymdown-extensions==9.3 +jinja2==3.0.3 From 2cb24ed310aad514081f881ec0f3333f0cb66f4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=AE=AE=E0=AE=A9=E0=AF=8B=E0=AE=9C=E0=AF=8D=E0=AE=95?= =?UTF-8?q?=E0=AF=81=E0=AE=AE=E0=AE=BE=E0=AE=B0=E0=AF=8D=20=E0=AE=AA?= =?UTF-8?q?=E0=AE=B4=E0=AE=A9=E0=AE=BF=E0=AE=9A=E0=AF=8D=E0=AE=9A=E0=AE=BE?= =?UTF-8?q?=E0=AE=AE=E0=AE=BF?= Date: Fri, 25 Mar 2022 13:45:05 +0530 Subject: [PATCH 111/114] Added in ms Just multiplied by 1000 as minuting checking in ms is not performed --- tests/conftest.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 7dbd13bc3..0f01d7e4a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1019,7 +1019,7 @@ def limit_buy_order_open(): 'type': 'limit', 'side': 'buy', 'symbol': 'mocked', - 'timestamp': arrow.utcnow().int_timestamp, + 'timestamp': arrow.utcnow().int_timestamp * 1000, 'datetime': arrow.utcnow().isoformat(), 'price': 0.00001099, 'amount': 90.99181073, @@ -1046,7 +1046,7 @@ def market_buy_order(): 'type': 'market', 'side': 'buy', 'symbol': 'mocked', - 'timestamp': arrow.utcnow().int_timestamp, + 'timestamp': arrow.utcnow().int_timestamp * 1000, 'datetime': arrow.utcnow().isoformat(), 'price': 0.00004099, 'amount': 91.99181073, @@ -1063,7 +1063,7 @@ def market_sell_order(): 'type': 'market', 'side': 'sell', 'symbol': 'mocked', - 'timestamp': arrow.utcnow().int_timestamp, + 'timestamp': arrow.utcnow().int_timestamp * 1000, 'datetime': arrow.utcnow().isoformat(), 'price': 0.00004173, 'amount': 91.99181073, @@ -1081,7 +1081,7 @@ def limit_buy_order_old(): 'side': 'buy', 'symbol': 'mocked', 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'price': 0.00001099, 'amount': 90.99181073, 'filled': 0.0, @@ -1097,7 +1097,7 @@ def limit_sell_order_old(): 'type': 'limit', 'side': 'sell', 'symbol': 'ETH/BTC', - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'price': 0.00001099, 'amount': 90.99181073, @@ -1114,7 +1114,7 @@ def limit_buy_order_old_partial(): 'type': 'limit', 'side': 'buy', 'symbol': 'ETH/BTC', - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'price': 0.00001099, 'amount': 90.99181073, @@ -1144,7 +1144,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': '1234512345', 'clientOrderId': None, - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', @@ -1165,7 +1165,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': 'AZNPFF-4AC4N-7MKTAT', 'clientOrderId': None, - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'status': 'canceled', @@ -1186,7 +1186,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': '1234512345', 'clientOrderId': 'alb1234123', - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', @@ -1207,7 +1207,7 @@ def limit_buy_order_canceled_empty(request): 'info': {}, 'id': '1234512345', 'clientOrderId': 'alb1234123', - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'lastTradeTimestamp': None, 'symbol': 'LTC/USDT', @@ -1233,7 +1233,7 @@ def limit_sell_order_open(): 'side': 'sell', 'symbol': 'mocked', 'datetime': arrow.utcnow().isoformat(), - 'timestamp': arrow.utcnow().int_timestamp, + 'timestamp': arrow.utcnow().int_timestamp * 1000, 'price': 0.00001173, 'amount': 90.99181073, 'filled': 0.0, @@ -1895,7 +1895,7 @@ def buy_order_fee(): 'type': 'limit', 'side': 'buy', 'symbol': 'mocked', - 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp, + 'timestamp': arrow.utcnow().shift(minutes=-601).int_timestamp * 1000, 'datetime': arrow.utcnow().shift(minutes=-601).isoformat(), 'price': 0.245441, 'amount': 8.0, @@ -2194,7 +2194,7 @@ def limit_buy_order_usdt_open(): 'side': 'buy', 'symbol': 'mocked', 'datetime': arrow.utcnow().isoformat(), - 'timestamp': arrow.utcnow().int_timestamp, + 'timestamp': arrow.utcnow().int_timestamp * 1000, 'price': 2.00, 'amount': 30.0, 'filled': 0.0, @@ -2221,7 +2221,7 @@ def limit_sell_order_usdt_open(): 'side': 'sell', 'symbol': 'mocked', 'datetime': arrow.utcnow().isoformat(), - 'timestamp': arrow.utcnow().int_timestamp, + 'timestamp': arrow.utcnow().int_timestamp * 1000, 'price': 2.20, 'amount': 30.0, 'filled': 0.0, @@ -2246,7 +2246,7 @@ def market_buy_order_usdt(): 'type': 'market', 'side': 'buy', 'symbol': 'mocked', - 'timestamp': arrow.utcnow().int_timestamp, + 'timestamp': arrow.utcnow().int_timestamp * 1000, 'datetime': arrow.utcnow().isoformat(), 'price': 2.00, 'amount': 30.0, @@ -2303,7 +2303,7 @@ def market_sell_order_usdt(): 'type': 'market', 'side': 'sell', 'symbol': 'mocked', - 'timestamp': arrow.utcnow().int_timestamp, + 'timestamp': arrow.utcnow().int_timestamp * 1000, 'datetime': arrow.utcnow().isoformat(), 'price': 2.20, 'amount': 30.0, From 81957e004df08bd3b9b2b81ca6a9ed59872768fd Mon Sep 17 00:00:00 2001 From: Matthias Date: Fri, 25 Mar 2022 15:38:38 +0100 Subject: [PATCH 112/114] Revert false update --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 0f01d7e4a..691584e74 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1399,7 +1399,7 @@ def tickers(): 'BLK/BTC': { 'symbol': 'BLK/BTC', 'timestamp': 1522014806072, - 'datetime': '2018-03-25T21:53:26.072Z', + 'datetime': '2018-03-25T21:53:26.720Z', 'high': 0.007745, 'low': 0.007512, 'bid': 0.007729, From 60f2a12bd9104fb7233fafda6052102265f89013 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 26 Mar 2022 08:23:02 +0100 Subject: [PATCH 113/114] Fix wrong datetime conversion --- tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 691584e74..0f01d7e4a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1399,7 +1399,7 @@ def tickers(): 'BLK/BTC': { 'symbol': 'BLK/BTC', 'timestamp': 1522014806072, - 'datetime': '2018-03-25T21:53:26.720Z', + 'datetime': '2018-03-25T21:53:26.072Z', 'high': 0.007745, 'low': 0.007512, 'bid': 0.007729, From fe6c62e144fb525f37b234ab125940c86cc8500a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 27 Mar 2022 15:09:16 +0200 Subject: [PATCH 114/114] Version bump 2022.3 --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index f6a9caa0e..349c1def1 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ Freqtrade bot """ -__version__ = '2022.2.2' +__version__ = '2022.3' if 'dev' in __version__: try: