From 5a7008f377087c96d6d2213d37108f77fa75609c Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 30 Jan 2023 19:56:36 +0100 Subject: [PATCH 01/37] rename handle_timedout to handle_cancel_order --- freqtrade/freqtradebot.py | 6 +++--- tests/test_freqtradebot.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a558f7bf4..f41aa41f6 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -355,7 +355,7 @@ class FreqtradeBot(LoggingMixin): "Order is older than 5 days. Assuming order was fully cancelled.") fo = order.to_ccxt_object() fo['status'] = 'canceled' - self.handle_timedout_order(fo, order.trade) + self.handle_cancel_order(fo, order.trade) except ExchangeError as e: @@ -1253,11 +1253,11 @@ class FreqtradeBot(LoggingMixin): if not_closed: if fully_cancelled or (order_obj and self.strategy.ft_check_timed_out( trade, order_obj, datetime.now(timezone.utc))): - self.handle_timedout_order(order, trade) + self.handle_cancel_order(order, trade) else: self.replace_order(order, order_obj, trade) - def handle_timedout_order(self, order: Dict, trade: Trade) -> None: + def handle_cancel_order(self, order: Dict, trade: Trade) -> None: """ Check if current analyzed order timed out and cancel if necessary. :param order: Order dict grabbed with exchange.fetch_order() diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 91fc25a83..a070fce97 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -5028,7 +5028,7 @@ def test_startup_update_open_orders(mocker, default_conf_usdt, fee, caplog, is_s assert log_has_re(r"Error updating Order .*", caplog) mocker.patch('freqtrade.exchange.Exchange.fetch_order', side_effect=InvalidOrderException) - hto_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_timedout_order') + hto_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_order') # Orders which are no longer found after X days should be assumed as canceled. freqtrade.startup_update_open_orders() assert log_has_re(r"Order is older than \d days.*", caplog) From a704c434025b5c2daf38eff75de70b2827f110ab Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 31 Jan 2023 07:08:12 +0100 Subject: [PATCH 02/37] provide cancel-reason to handle_cancel_order --- freqtrade/freqtradebot.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index f41aa41f6..07c58cbee 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -355,7 +355,7 @@ class FreqtradeBot(LoggingMixin): "Order is older than 5 days. Assuming order was fully cancelled.") fo = order.to_ccxt_object() fo['status'] = 'canceled' - self.handle_cancel_order(fo, order.trade) + self.handle_cancel_order(fo, order.trade, constants.CANCEL_REASON['TIMEOUT']) except ExchangeError as e: @@ -1253,11 +1253,11 @@ class FreqtradeBot(LoggingMixin): if not_closed: if fully_cancelled or (order_obj and self.strategy.ft_check_timed_out( trade, order_obj, datetime.now(timezone.utc))): - self.handle_cancel_order(order, trade) + self.handle_cancel_order(order, trade, constants.CANCEL_REASON['TIMEOUT']) else: self.replace_order(order, order_obj, trade) - def handle_cancel_order(self, order: Dict, trade: Trade) -> None: + def handle_cancel_order(self, order: Dict, trade: Trade, reason: str) -> None: """ Check if current analyzed order timed out and cancel if necessary. :param order: Order dict grabbed with exchange.fetch_order() @@ -1265,10 +1265,10 @@ class FreqtradeBot(LoggingMixin): :return: None """ if order['side'] == trade.entry_side: - self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT']) + self.handle_cancel_enter(trade, order, reason) else: canceled = self.handle_cancel_exit( - trade, order, constants.CANCEL_REASON['TIMEOUT']) + trade, order, reason) canceled_count = trade.get_exit_order_count() max_timeouts = self.config.get('unfilledtimeout', {}).get('exit_timeout_count', 0) if canceled and max_timeouts > 0 and canceled_count >= max_timeouts: From c855e2d79c8547a13cacb44ea4525f0bfc1e6900 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 31 Jan 2023 07:09:03 +0100 Subject: [PATCH 03/37] Add delete open order endpoint --- freqtrade/rpc/api_server/api_v1.py | 6 ++++++ freqtrade/rpc/rpc.py | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index b64f6c0e8..7da98e79d 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -123,6 +123,12 @@ def trades_delete(tradeid: int, rpc: RPC = Depends(get_rpc)): return rpc._rpc_delete(tradeid) +@router.delete('/trades/{tradeid}/open-order', response_model=OpenTradeSchema, tags=['trading']) +def cancel_open_order(tradeid: int, rpc: RPC = Depends(get_rpc)): + rpc._rpc_cancel_open_order(tradeid) + return rpc._rpc_trade_status([tradeid])[0] + + # TODO: Missing response model @router.get('/edge', tags=['info']) def edge(rpc: RPC = Depends(get_rpc)): diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index f1dd3fe85..14048da4f 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -812,6 +812,29 @@ class RPC: else: raise RPCException(f'Failed to enter position for {pair}.') + def _rpc_cancel_open_order(self, trade_id: int): + if self._freqtrade.state != State.RUNNING: + raise RPCException('trader is not running') + with self._freqtrade._exit_lock: + # Query for trade + trade = Trade.get_trades( + trade_filter=[Trade.id == trade_id, Trade.is_open.is_(True), ] + ).first() + if not trade: + logger.warning('cancel_open_order: Invalid trade_id received.') + raise RPCException('Invalid trade_id') + if not trade.open_order_id: + logger.warning('cancel_open_order: No open order for trade_id.') + raise RPCException('No open order for trade_id') + + try: + order = self._freqtrade.exchange.fetch_order(trade.open_order_id, trade.pair) + except ExchangeError as e: + logger.info(f"Cannot query order for {trade} due to {e}.", exc_info=True) + raise RPCException("Order not found.") + self._freqtrade.handle_cancel_order(order, trade, CANCEL_REASON['USER_CANCEL']) + Trade.commit() + def _rpc_delete(self, trade_id: int) -> Dict[str, Union[str, int]]: """ Handler for delete . From c43e857cbc85c18f04ba39a37fd3ab05ee29b6a9 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 31 Jan 2023 07:09:07 +0100 Subject: [PATCH 04/37] Bump API version --- freqtrade/rpc/api_server/api_v1.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/api_server/api_v1.py b/freqtrade/rpc/api_server/api_v1.py index 7da98e79d..73bdde86b 100644 --- a/freqtrade/rpc/api_server/api_v1.py +++ b/freqtrade/rpc/api_server/api_v1.py @@ -41,7 +41,8 @@ logger = logging.getLogger(__name__) # 2.21: Add new_candle messagetype # 2.22: Add FreqAI to backtesting # 2.23: Allow plot config request in webserver mode -API_VERSION = 2.23 +# 2.24: Add cancel_open_order endpoint +API_VERSION = 2.24 # Public API, requires no auth. router_public = APIRouter() From 03302fa0b0bf3da027d57e06e3b815c80a5def79 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 31 Jan 2023 07:24:19 +0100 Subject: [PATCH 05/37] Add cancel_open_order to rest script --- scripts/rest_client.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/scripts/rest_client.py b/scripts/rest_client.py index 3cf2199fb..3a8823672 100755 --- a/scripts/rest_client.py +++ b/scripts/rest_client.py @@ -232,6 +232,14 @@ class FtRestClient(): """ return self._delete(f"trades/{trade_id}") + def cancel_open_order(self, trade_id): + """Cancel open order for trade. + + :param trade_id: Cancels open orders for this trade. + :return: json object + """ + return self._delete(f"trades/{trade_id}/open-order") + def whitelist(self): """Show the current whitelist. From 9e619ecc507f49a14590a94c2f821a75616fc1d0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 31 Jan 2023 07:25:44 +0100 Subject: [PATCH 06/37] Update rest api documentation --- docs/rest-api.md | 9 +++++++-- scripts/rest_client.py | 3 +-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/rest-api.md b/docs/rest-api.md index 62ad586dd..5f604ef43 100644 --- a/docs/rest-api.md +++ b/docs/rest-api.md @@ -163,7 +163,7 @@ python3 scripts/rest_client.py --config rest_config.json [optional par | `strategy ` | Get specific Strategy content. **Alpha** | `available_pairs` | List available backtest data. **Alpha** | `version` | Show version. -| `sysinfo` | Show informations about the system load. +| `sysinfo` | Show information about the system load. | `health` | Show bot health (last bot loop). !!! Warning "Alpha status" @@ -192,6 +192,11 @@ blacklist :param add: List of coins to add (example: "BNB/BTC") +cancel_open_order + Cancel open order for trade. + + :param trade_id: Cancels open orders for this trade. + count Return the amount of open trades. @@ -274,7 +279,6 @@ reload_config Reload configuration. show_config - Returns part of the configuration, relevant for trading operations. start @@ -320,6 +324,7 @@ version whitelist Show the current whitelist. + ``` ### Message WebSocket diff --git a/scripts/rest_client.py b/scripts/rest_client.py index 3a8823672..144d428e5 100755 --- a/scripts/rest_client.py +++ b/scripts/rest_client.py @@ -177,8 +177,7 @@ class FtRestClient(): return self._get("version") def show_config(self): - """ - Returns part of the configuration, relevant for trading operations. + """ Returns part of the configuration, relevant for trading operations. :return: json object containing the version """ return self._get("show_config") From 1bdc0e39176ad4d328e046358574762ffa9eb347 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 31 Jan 2023 18:09:40 +0100 Subject: [PATCH 07/37] Add coo command to telegram --- freqtrade/rpc/telegram.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index c02a4000a..662ece9c2 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -174,6 +174,7 @@ class Telegram(RPCHandler): self._force_enter, order_side=SignalDirection.SHORT)), CommandHandler('trades', self._trades), CommandHandler('delete', self._delete_trade), + CommandHandler(['coo', 'cancel_open_order'], self._cancel_open_order), CommandHandler('performance', self._performance), CommandHandler(['buys', 'entries'], self._enter_tag_performance), CommandHandler(['sells', 'exits'], self._exit_reason_performance), @@ -1144,10 +1145,25 @@ class Telegram(RPCHandler): raise RPCException("Trade-id not set.") trade_id = int(context.args[0]) msg = self._rpc._rpc_delete(trade_id) - self._send_msg(( + self._send_msg( f"`{msg['result_msg']}`\n" 'Please make sure to take care of this asset on the exchange manually.' - )) + ) + + @authorized_only + def _cancel_open_order(self, update: Update, context: CallbackContext) -> None: + """ + Handler for /cancel_open_order . + Cancel open order for tradeid + :param bot: telegram bot + :param update: message update + :return: None + """ + if not context.args or len(context.args) == 0: + raise RPCException("Trade-id not set.") + trade_id = int(context.args[0]) + self._rpc._rpc_cancel_open_order(trade_id) + self._send_msg('Open order canceled.') @authorized_only def _performance(self, update: Update, context: CallbackContext) -> None: From e291d1bb1785df9afae09898aa825107a0f9df62 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 31 Jan 2023 18:12:18 +0100 Subject: [PATCH 08/37] Document telegram /coo command --- docs/telegram-usage.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index db4a309d0..3f7c25c00 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -173,6 +173,7 @@ official commands. You can ask at any moment for help with `/help`. | `/status table` | List all open trades in a table format. Pending buy orders are marked with an asterisk (*) Pending sell orders are marked with a double asterisk (**) | `/trades [limit]` | List all recently closed trades in a table format. | `/delete ` | Delete a specific trade from the Database. Tries to close open orders. Requires manual handling of this trade on the exchange. +| `/cancel_open_order | /coo ` | Cancel an open order for a trade. | `/count` | Displays number of trades used and available | `/locks` | Show currently locked pairs. | `/unlock ` | Remove the lock for this pair (or for this lock id). From bd2839fa40b72c55c355b6198b730791fe42f189 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 31 Jan 2023 18:13:42 +0100 Subject: [PATCH 09/37] Reorder documentation --- docs/telegram-usage.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index 3f7c25c00..4626944c5 100644 --- a/docs/telegram-usage.md +++ b/docs/telegram-usage.md @@ -162,27 +162,33 @@ official commands. You can ask at any moment for help with `/help`. | Command | Description | |----------|-------------| +| **System commands** | `/start` | Starts the trader | `/stop` | Stops the trader | `/stopbuy | /stopentry` | Stops the trader from opening new trades. Gracefully closes open trades according to their rules. | `/reload_config` | Reloads the configuration file | `/show_config` | Shows part of the current configuration with relevant settings to operation | `/logs [limit]` | Show last log messages. +| `/help` | Show help message +| `/version` | Show version +| **Status** | | `/status` | Lists all open trades | `/status ` | Lists one or more specific trade. Separate multiple with a blank space. | `/status table` | List all open trades in a table format. Pending buy orders are marked with an asterisk (*) Pending sell orders are marked with a double asterisk (**) | `/trades [limit]` | List all recently closed trades in a table format. -| `/delete ` | Delete a specific trade from the Database. Tries to close open orders. Requires manual handling of this trade on the exchange. -| `/cancel_open_order | /coo ` | Cancel an open order for a trade. | `/count` | Displays number of trades used and available | `/locks` | Show currently locked pairs. | `/unlock ` | Remove the lock for this pair (or for this lock id). -| `/profit []` | Display a summary of your profit/loss from close trades and some stats about your performance, over the last n days (all trades by default) +| **Modify Trade states** | | `/forceexit | /fx ` | Instantly exits the given trade (Ignoring `minimum_roi`). | `/forceexit all | /fx all` | Instantly exits all open trades (Ignoring `minimum_roi`). | `/fx` | alias for `/forceexit` | `/forcelong [rate]` | Instantly buys the given pair. Rate is optional and only applies to limit orders. (`force_entry_enable` must be set to True) | `/forceshort [rate]` | Instantly shorts the given pair. Rate is optional and only applies to limit orders. This will only work on non-spot markets. (`force_entry_enable` must be set to True) +| `/delete ` | Delete a specific trade from the Database. Tries to close open orders. Requires manual handling of this trade on the exchange. +| `/cancel_open_order | /coo ` | Cancel an open order for a trade. +| **Metrics** | +| `/profit []` | Display a summary of your profit/loss from close trades and some stats about your performance, over the last n days (all trades by default) | `/performance` | Show performance of each finished trade grouped by pair | `/balance` | Show account balance per currency | `/daily ` | Shows profit or loss per day, over the last n days (n defaults to 7) @@ -194,8 +200,7 @@ official commands. You can ask at any moment for help with `/help`. | `/whitelist [sorted] [baseonly]` | Show the current whitelist. Optionally display in alphabetical order and/or with just the base currency of each pairing. | `/blacklist [pair]` | Show the current blacklist, or adds a pair to the blacklist. | `/edge` | Show validated pairs by Edge if it is enabled. -| `/help` | Show help message -| `/version` | Show version + ## Telegram commands in action From daafc1c90f986a1e9f8a53bae6ecd84a77c4ecea Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 31 Jan 2023 18:16:59 +0100 Subject: [PATCH 10/37] Update test and help --- freqtrade/rpc/telegram.py | 4 ++++ tests/rpc/test_rpc_telegram.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 662ece9c2..fbd675d02 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -1472,6 +1472,10 @@ class Telegram(RPCHandler): "*/fx |all:* `Alias to /forceexit`\n" f"{force_enter_text if self._config.get('force_entry_enable', False) else ''}" "*/delete :* `Instantly delete the given trade in the database`\n" + "*/cancel_open_order :* `Cancels open orders for trade. " + "Only valid when the trade has open orders.`\n" + "*/coo |all:* `Alias to /cancel_open_order`\n" + "*/whitelist [sorted] [baseonly]:* `Show current whitelist. Optionally in " "order and/or only displaying the base currency of each pairing.`\n" "*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs " diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 85475ae8e..a15620193 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -99,7 +99,7 @@ def test_telegram_init(default_conf, mocker, caplog) -> None: message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], " "['balance'], ['start'], ['stop'], " "['forcesell', 'forceexit', 'fx'], ['forcebuy', 'forcelong'], ['forceshort'], " - "['trades'], ['delete'], ['performance'], " + "['trades'], ['delete'], ['coo', 'cancel_open_order'], ['performance'], " "['buys', 'entries'], ['sells', 'exits'], ['mix_tags'], " "['stats'], ['daily'], ['weekly'], ['monthly'], " "['count'], ['locks'], ['unlock', 'delete_locks'], " From 1c47c118d6753ce77a24deac82909f2f8bfb112a Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 31 Jan 2023 18:26:51 +0100 Subject: [PATCH 11/37] Add cancel-order api test --- tests/rpc/test_rpc_apiserver.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index cd1a988d2..60c88ac0f 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -706,6 +706,38 @@ def test_api_delete_trade(botclient, mocker, fee, markets, is_short): assert_response(rc, 502) +@pytest.mark.parametrize('is_short', [True, False]) +def test_api_delete_open_order(botclient, mocker, fee, markets, ticker, is_short): + ftbot, client = botclient + patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short) + stoploss_mock = MagicMock() + cancel_mock = MagicMock() + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + markets=PropertyMock(return_value=markets), + fetch_ticker=ticker, + cancel_order=cancel_mock, + cancel_stoploss_order=stoploss_mock, + ) + + create_mock_trades(fee, is_short=is_short) + + trades = Trade.query.all() + Trade.commit() + assert len(trades) > 2 + trade = Trade.get_trades([Trade.id == 6]).first() + mocker.patch('freqtrade.exchange.Exchange.fetch_order', + return_value=trade.orders[-1].to_ccxt_object()) + + rc = client_delete(client, f"{BASE_URI}/trades/5/open-order") + assert_response(rc, 502) + assert 'No open order for trade_id' in rc.json()['error'] + + rc = client_delete(client, f"{BASE_URI}/trades/6/open-order") + assert_response(rc) + assert cancel_mock.call_count == 1 + + def test_api_logs(botclient): ftbot, client = botclient rc = client_get(client, f"{BASE_URI}/logs") From bbc663fce1d25dda07a5e137f27689edd5b3ea9d Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 31 Jan 2023 19:26:26 +0100 Subject: [PATCH 12/37] Add telegram test --- tests/rpc/test_rpc_telegram.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index a15620193..5e3c2bd18 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1678,6 +1678,40 @@ def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short): assert "Please make sure to take care of this asset" in msg_mock.call_args_list[0][0][0] +@pytest.mark.parametrize('is_short', [True, False]) +def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short, ticker): + + mocker.patch.multiple( + 'freqtrade.exchange.Exchange', + fetch_ticker=ticker, + ) + telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) + context = MagicMock() + context.args = [] + + telegram._cancel_open_order(update=update, context=context) + assert "Trade-id not set." in msg_mock.call_args_list[0][0][0] + + msg_mock.reset_mock() + create_mock_trades(fee, is_short=is_short) + + context = MagicMock() + context.args = [5] + telegram._cancel_open_order(update=update, context=context) + assert "No open order for trade_id" in msg_mock.call_args_list[0][0][0] + + msg_mock.reset_mock() + + trade = Trade.get_trades([Trade.id == 6]).first() + mocker.patch('freqtrade.exchange.Exchange.fetch_order', + return_value=trade.orders[-1].to_ccxt_object()) + context = MagicMock() + context.args = [6] + telegram._cancel_open_order(update=update, context=context) + assert msg_mock.call_count == 1 + assert "Open order canceled." in msg_mock.call_args_list[0][0][0] + + def test_help_handle(default_conf, update, mocker) -> None: telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) From 9cfbb21cd790702c7630775f03fe08352fc1ed65 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 31 Jan 2023 19:38:43 +0100 Subject: [PATCH 13/37] Improve error messages --- freqtrade/rpc/rpc.py | 4 ++-- tests/rpc/test_rpc_apiserver.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 14048da4f..ddf6018ad 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -822,10 +822,10 @@ class RPC: ).first() if not trade: logger.warning('cancel_open_order: Invalid trade_id received.') - raise RPCException('Invalid trade_id') + raise RPCException('Invalid trade_id.') if not trade.open_order_id: logger.warning('cancel_open_order: No open order for trade_id.') - raise RPCException('No open order for trade_id') + raise RPCException('No open order for trade_id.') try: order = self._freqtrade.exchange.fetch_order(trade.open_order_id, trade.pair) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 60c88ac0f..4d06811ed 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -720,6 +720,10 @@ def test_api_delete_open_order(botclient, mocker, fee, markets, ticker, is_short cancel_stoploss_order=stoploss_mock, ) + rc = client_delete(client, f"{BASE_URI}/trades/10/open-order") + assert_response(rc, 502) + assert 'Invalid trade_id.' in rc.json()['error'] + create_mock_trades(fee, is_short=is_short) trades = Trade.query.all() From 6012a5582881b8e4ec5e357588f2a03b7cf6cfeb Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 31 Jan 2023 19:40:42 +0100 Subject: [PATCH 14/37] Improve test --- tests/rpc/test_rpc_apiserver.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py index 4d06811ed..89a1b791d 100644 --- a/tests/rpc/test_rpc_apiserver.py +++ b/tests/rpc/test_rpc_apiserver.py @@ -725,17 +725,21 @@ def test_api_delete_open_order(botclient, mocker, fee, markets, ticker, is_short assert 'Invalid trade_id.' in rc.json()['error'] create_mock_trades(fee, is_short=is_short) - - trades = Trade.query.all() Trade.commit() - assert len(trades) > 2 - trade = Trade.get_trades([Trade.id == 6]).first() - mocker.patch('freqtrade.exchange.Exchange.fetch_order', - return_value=trade.orders[-1].to_ccxt_object()) rc = client_delete(client, f"{BASE_URI}/trades/5/open-order") assert_response(rc, 502) assert 'No open order for trade_id' in rc.json()['error'] + trade = Trade.get_trades([Trade.id == 6]).first() + mocker.patch('freqtrade.exchange.Exchange.fetch_order', + side_effect=ExchangeError) + rc = client_delete(client, f"{BASE_URI}/trades/6/open-order") + assert_response(rc, 502) + assert 'Order not found.' in rc.json()['error'] + + trade = Trade.get_trades([Trade.id == 6]).first() + mocker.patch('freqtrade.exchange.Exchange.fetch_order', + return_value=trade.orders[-1].to_ccxt_object()) rc = client_delete(client, f"{BASE_URI}/trades/6/open-order") assert_response(rc) From 5ed06cd79b90550cac84c69f7ced036adc7e3376 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Feb 2023 09:19:18 +0000 Subject: [PATCH 15/37] Bump mkdocs-material from 9.0.8 to 9.0.11 Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.0.8 to 9.0.11. - [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/9.0.8...9.0.11) --- 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 a2a947b50..6ab15e88b 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -1,6 +1,6 @@ markdown==3.3.7 mkdocs==1.4.2 -mkdocs-material==9.0.8 +mkdocs-material==9.0.11 mdx_truly_sane_lists==1.3 pymdown-extensions==9.9.1 jinja2==3.1.2 From 34711eb6835f515e95f0cdf378b6f62d04e2f9b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Feb 2023 09:19:25 +0000 Subject: [PATCH 16/37] Bump nbconvert from 7.2.8 to 7.2.9 Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 7.2.8 to 7.2.9. - [Release notes](https://github.com/jupyter/nbconvert/releases) - [Changelog](https://github.com/jupyter/nbconvert/blob/main/CHANGELOG.md) - [Commits](https://github.com/jupyter/nbconvert/compare/v7.2.8...v7.2.9) --- 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 a63756e97..fa280ee03 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -23,7 +23,7 @@ time-machine==2.9.0 httpx==0.23.3 # Convert jupyter notebooks to markdown documents -nbconvert==7.2.8 +nbconvert==7.2.9 # mypy types types-cachetools==5.2.1 From c61995aad95116a845f35e125a54bf5efa66ab3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Feb 2023 09:19:31 +0000 Subject: [PATCH 17/37] Bump plotly from 5.11.0 to 5.13.0 Bumps [plotly](https://github.com/plotly/plotly.py) from 5.11.0 to 5.13.0. - [Release notes](https://github.com/plotly/plotly.py/releases) - [Changelog](https://github.com/plotly/plotly.py/blob/master/CHANGELOG.md) - [Commits](https://github.com/plotly/plotly.py/compare/v5.11.0...v5.13.0) --- updated-dependencies: - dependency-name: plotly dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- requirements-plot.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-plot.txt b/requirements-plot.txt index 75e3234a1..b97d42fb6 100644 --- a/requirements-plot.txt +++ b/requirements-plot.txt @@ -1,4 +1,4 @@ # Include all requirements to run the bot. -r requirements.txt -plotly==5.11.0 +plotly==5.13.0 From b80d196d562ffbe9015acb4a64058a1b6ddd8631 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Feb 2023 09:19:39 +0000 Subject: [PATCH 18/37] Bump pre-commit from 2.21.0 to 3.0.4 Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 2.21.0 to 3.0.4. - [Release notes](https://github.com/pre-commit/pre-commit/releases) - [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md) - [Commits](https://github.com/pre-commit/pre-commit/compare/v2.21.0...v3.0.4) --- updated-dependencies: - dependency-name: pre-commit 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 a63756e97..de9f8d49f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -10,7 +10,7 @@ coveralls==3.3.1 flake8==6.0.0 flake8-tidy-imports==4.8.0 mypy==0.991 -pre-commit==2.21.0 +pre-commit==3.0.4 pytest==7.2.1 pytest-asyncio==0.20.3 pytest-cov==4.0.0 From e3f0e66b9a8a96e97bac63ee11d106d71dcffe6e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Feb 2023 09:19:44 +0000 Subject: [PATCH 19/37] Bump technical from 1.3.0 to 1.4.0 Bumps [technical](https://github.com/freqtrade/technical) from 1.3.0 to 1.4.0. - [Release notes](https://github.com/freqtrade/technical/releases) - [Commits](https://github.com/freqtrade/technical/compare/1.3.0...1.4.0) --- updated-dependencies: - dependency-name: technical 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 e6b5ca464..7202eee62 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,7 @@ requests==2.28.2 urllib3==1.26.14 jsonschema==4.17.3 TA-Lib==0.4.25 -technical==1.3.0 +technical==1.4.0 tabulate==0.9.0 pycoingecko==3.1.0 jinja2==3.1.2 From e38e41ab97fc87f547c5e4191deeaa9fc0886542 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 5 Feb 2023 09:20:10 +0000 Subject: [PATCH 20/37] Bump isort from 5.11.4 to 5.12.0 Bumps [isort](https://github.com/pycqa/isort) from 5.11.4 to 5.12.0. - [Release notes](https://github.com/pycqa/isort/releases) - [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md) - [Commits](https://github.com/pycqa/isort/compare/5.11.4...5.12.0) --- updated-dependencies: - dependency-name: isort 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 a63756e97..9cfce099c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -16,7 +16,7 @@ pytest-asyncio==0.20.3 pytest-cov==4.0.0 pytest-mock==3.10.0 pytest-random-order==1.1.0 -isort==5.11.4 +isort==5.12.0 # For datetime mocking time-machine==2.9.0 # fastapi testing From 61ba1a0dc7d705017d93398579ac20cb3c73ba6a Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 5 Feb 2023 14:38:59 +0100 Subject: [PATCH 21/37] Pin telegram in conda environment to <20 closes #8111 --- environment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/environment.yml b/environment.yml index 5298b2baa..5b039e7f7 100644 --- a/environment.yml +++ b/environment.yml @@ -12,7 +12,7 @@ dependencies: - py-find-1st - aiohttp - SQLAlchemy - - python-telegram-bot + - python-telegram-bot<20.0.0 - arrow - cachetools - requests From 8dde7ab6b85506b85b9a9f11e8d2beb7685a681d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 03:00:49 +0000 Subject: [PATCH 22/37] Bump ccxt from 2.7.12 to 2.7.45 Bumps [ccxt](https://github.com/ccxt/ccxt) from 2.7.12 to 2.7.45. - [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/2.7.12...2.7.45) --- 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 7202eee62..2047133cf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.24.1 pandas==1.5.3 pandas-ta==0.3.14b -ccxt==2.7.12 +ccxt==2.7.45 # Pin cryptography for now due to rust build errors with piwheels cryptography==38.0.1; platform_machine == 'armv7l' cryptography==39.0.0; platform_machine != 'armv7l' From 365522f5c83a1970ed1718af690fe497aa427a3f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 03:00:52 +0000 Subject: [PATCH 23/37] Bump types-cachetools from 5.2.1 to 5.3.0.0 Bumps [types-cachetools](https://github.com/python/typeshed) from 5.2.1 to 5.3.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-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 d024b38b9..3488fdbfd 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -26,7 +26,7 @@ httpx==0.23.3 nbconvert==7.2.9 # mypy types -types-cachetools==5.2.1 +types-cachetools==5.3.0.0 types-filelock==3.2.7 types-requests==2.28.11.8 types-tabulate==0.9.0.0 From f96cb4772726be9214c3db600e19c7095c458e09 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 03:01:17 +0000 Subject: [PATCH 24/37] Bump numpy from 1.24.1 to 1.24.2 Bumps [numpy](https://github.com/numpy/numpy) from 1.24.1 to 1.24.2. - [Release notes](https://github.com/numpy/numpy/releases) - [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst) - [Commits](https://github.com/numpy/numpy/compare/v1.24.1...v1.24.2) --- 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 7202eee62..67b886be1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -numpy==1.24.1 +numpy==1.24.2 pandas==1.5.3 pandas-ta==0.3.14b From c6601cbd892f581fde1324e722715f1167e34390 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Feb 2023 03:01:25 +0000 Subject: [PATCH 25/37] Bump pymdown-extensions from 9.9.1 to 9.9.2 Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 9.9.1 to 9.9.2. - [Release notes](https://github.com/facelessuser/pymdown-extensions/releases) - [Commits](https://github.com/facelessuser/pymdown-extensions/compare/9.9.1...9.9.2) --- updated-dependencies: - dependency-name: pymdown-extensions 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 6ab15e88b..0e3bf898f 100644 --- a/docs/requirements-docs.txt +++ b/docs/requirements-docs.txt @@ -2,5 +2,5 @@ markdown==3.3.7 mkdocs==1.4.2 mkdocs-material==9.0.11 mdx_truly_sane_lists==1.3 -pymdown-extensions==9.9.1 +pymdown-extensions==9.9.2 jinja2==3.1.2 From b6eb1f9395375de9baf7574fff8ac190d5851f5a Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 6 Feb 2023 07:09:59 +0100 Subject: [PATCH 26/37] Bump pre-commit --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9e502e97b..d1fd70280 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: mypy exclude: build_helpers additional_dependencies: - - types-cachetools==5.2.1 + - types-cachetools==5.3.0.0 - types-filelock==3.2.7 - types-requests==2.28.11.8 - types-tabulate==0.9.0.0 From a6adcb485ea26a0ffdb41aec2e0ddafae481e715 Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 6 Feb 2023 19:34:30 +0100 Subject: [PATCH 27/37] Bump several pre-commit hooks versions --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d1fd70280..d50506650 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pycqa/flake8 - rev: "4.0.1" + rev: "6.0.0" hooks: - id: flake8 # stages: [push] @@ -21,14 +21,14 @@ repos: # stages: [push] - repo: https://github.com/pycqa/isort - rev: "5.10.1" + rev: "5.12.0" hooks: - id: isort name: isort (python) # stages: [push] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v2.4.0 + rev: v4.4.0 hooks: - id: end-of-file-fixer exclude: | From 81619fb4a0594a6d65231c824b83394c395332fe Mon Sep 17 00:00:00 2001 From: Matthias Date: Mon, 6 Feb 2023 19:26:16 +0100 Subject: [PATCH 28/37] Properly use sqlalchemy column types --- freqtrade/persistence/pairlock.py | 4 +- freqtrade/persistence/trade_model.py | 100 +++++++++++++-------------- 2 files changed, 52 insertions(+), 52 deletions(-) diff --git a/freqtrade/persistence/pairlock.py b/freqtrade/persistence/pairlock.py index 926c641b0..938cd14bc 100644 --- a/freqtrade/persistence/pairlock.py +++ b/freqtrade/persistence/pairlock.py @@ -21,9 +21,9 @@ class PairLock(_DECL_BASE): side = Column(String(25), nullable=False, default="*") reason = Column(String(255), nullable=True) # Time the pair was locked (start time) - lock_time = Column(DateTime, nullable=False) + lock_time = Column(DateTime(), nullable=False) # Time until the pair is locked (end time) - lock_end_time = Column(DateTime, nullable=False, index=True) + lock_end_time = Column(DateTime(), nullable=False, index=True) active = Column(Boolean, nullable=False, default=True, index=True) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index a112771d3..535067084 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -46,31 +46,31 @@ class Order(_DECL_BASE): trade = relationship("Trade", back_populates="orders") # order_side can only be 'buy', 'sell' or 'stoploss' - ft_order_side: str = Column(String(25), nullable=False) - ft_pair: str = Column(String(25), nullable=False) + ft_order_side = Column(String(25), nullable=False) + ft_pair = Column(String(25), nullable=False) ft_is_open = Column(Boolean, nullable=False, default=True, index=True) - ft_amount = Column(Float, nullable=False) - ft_price = Column(Float, nullable=False) + ft_amount = Column(Float(), nullable=False) + ft_price = Column(Float(), nullable=False) - order_id: str = Column(String(255), nullable=False, index=True) + order_id = 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) + order_type = Column(String(50), nullable=True) side = Column(String(25), nullable=True) - price = Column(Float, nullable=True) - average = Column(Float, nullable=True) - amount = Column(Float, nullable=True) - filled = Column(Float, nullable=True) - remaining = Column(Float, nullable=True) - cost = Column(Float, nullable=True) - stop_price = Column(Float, nullable=True) - order_date = Column(DateTime, nullable=True, default=datetime.utcnow) - order_filled_date = Column(DateTime, nullable=True) - order_update_date = Column(DateTime, nullable=True) + price = Column(Float(), nullable=True) + average = Column(Float(), nullable=True) + amount = Column(Float(), nullable=True) + filled = Column(Float(), nullable=True) + remaining = Column(Float(), nullable=True) + cost = Column(Float(), nullable=True) + stop_price = Column(Float(), nullable=True) + order_date = Column(DateTime(), nullable=True, default=datetime.utcnow) + order_filled_date = Column(DateTime(), nullable=True) + order_update_date = Column(DateTime(), nullable=True) - funding_fee = Column(Float, nullable=True) + funding_fee = Column(Float(), nullable=True) - ft_fee_base = Column(Float, nullable=True) + ft_fee_base = Column(Float(), nullable=True) @property def order_date_utc(self) -> datetime: @@ -1177,44 +1177,44 @@ class Trade(_DECL_BASE, LocalTrade): base_currency = Column(String(25), nullable=True) stake_currency = Column(String(25), nullable=True) is_open = Column(Boolean, nullable=False, default=True, index=True) - fee_open = Column(Float, nullable=False, default=0.0) - fee_open_cost = Column(Float, nullable=True) + fee_open = Column(Float(), nullable=False, default=0.0) + fee_open_cost = Column(Float(), nullable=True) fee_open_currency = Column(String(25), nullable=True) - fee_close = Column(Float, nullable=False, default=0.0) - fee_close_cost = Column(Float, nullable=True) + fee_close = Column(Float(), nullable=False, default=0.0) + fee_close_cost = Column(Float(), nullable=True) fee_close_currency = Column(String(25), nullable=True) - open_rate: float = Column(Float) - open_rate_requested = Column(Float) + open_rate: float = Column(Float()) + open_rate_requested = Column(Float()) # open_trade_value - calculated via _calc_open_trade_value - open_trade_value = Column(Float) - close_rate: Optional[float] = Column(Float) - close_rate_requested = Column(Float) - realized_profit = Column(Float, default=0.0) - close_profit = Column(Float) - close_profit_abs = Column(Float) - stake_amount = Column(Float, nullable=False) - max_stake_amount = Column(Float) - amount = Column(Float) - amount_requested = Column(Float) - open_date = Column(DateTime, nullable=False, default=datetime.utcnow) - close_date = Column(DateTime) + open_trade_value = Column(Float()) + close_rate: Optional[float] = Column(Float()) + close_rate_requested = Column(Float()) + realized_profit = Column(Float(), default=0.0) + close_profit = Column(Float()) + close_profit_abs = Column(Float()) + stake_amount = Column(Float(), nullable=False) + max_stake_amount = Column(Float()) + amount = Column(Float()) + amount_requested = Column(Float()) + open_date = Column(DateTime(), nullable=False, default=datetime.utcnow) + close_date = Column(DateTime()) open_order_id = Column(String(255)) # absolute value of the stop loss - stop_loss = Column(Float, nullable=True, default=0.0) + stop_loss = Column(Float(), nullable=True, default=0.0) # percentage value of the stop loss - stop_loss_pct = Column(Float, nullable=True) + stop_loss_pct = Column(Float(), nullable=True) # absolute value of the initial stop loss - initial_stop_loss = Column(Float, nullable=True, default=0.0) + initial_stop_loss = Column(Float(), nullable=True, default=0.0) # percentage value of the initial stop loss - initial_stop_loss_pct = Column(Float, nullable=True) + initial_stop_loss_pct = Column(Float(), nullable=True) # stoploss order id which is on exchange stoploss_order_id = Column(String(255), nullable=True, index=True) # last update time of the stoploss order on exchange - stoploss_last_update = Column(DateTime, nullable=True) + stoploss_last_update = Column(DateTime(), nullable=True) # absolute value of the highest reached price - max_rate = Column(Float, nullable=True, default=0.0) + max_rate = Column(Float(), nullable=True, default=0.0) # Lowest price reached - min_rate = Column(Float, nullable=True) + min_rate = Column(Float(), nullable=True) exit_reason = Column(String(100), nullable=True) exit_order_status = Column(String(100), nullable=True) strategy = Column(String(100), nullable=True) @@ -1222,21 +1222,21 @@ class Trade(_DECL_BASE, LocalTrade): timeframe = Column(Integer, nullable=True) trading_mode = Column(Enum(TradingMode), nullable=True) - amount_precision = Column(Float, nullable=True) - price_precision = Column(Float, nullable=True) + amount_precision = Column(Float(), nullable=True) + price_precision = Column(Float(), nullable=True) precision_mode = Column(Integer, nullable=True) - contract_size = Column(Float, nullable=True) + contract_size = Column(Float(), nullable=True) # Leverage trading properties - leverage = Column(Float, nullable=True, default=1.0) + leverage = Column(Float(), nullable=True, default=1.0) is_short = Column(Boolean, nullable=False, default=False) - liquidation_price = Column(Float, nullable=True) + liquidation_price = Column(Float(), nullable=True) # Margin Trading Properties - interest_rate = Column(Float, nullable=False, default=0.0) + interest_rate = Column(Float(), nullable=False, default=0.0) # Futures properties - funding_fees = Column(Float, nullable=True, default=None) + funding_fees = Column(Float(), nullable=True, default=None) def __init__(self, **kwargs): super().__init__(**kwargs) From 67a2cd70863c688a0e6501668d54dc90c328626e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Feb 2023 04:07:21 +0000 Subject: [PATCH 29/37] Bump cryptography from 38.0.1 to 39.0.1 Bumps [cryptography](https://github.com/pyca/cryptography) from 38.0.1 to 39.0.1. - [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/38.0.1...39.0.1) --- updated-dependencies: - dependency-name: cryptography dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index acc952662..4f62e7ec4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pandas-ta==0.3.14b ccxt==2.7.45 # Pin cryptography for now due to rust build errors with piwheels cryptography==38.0.1; platform_machine == 'armv7l' -cryptography==39.0.0; platform_machine != 'armv7l' +cryptography==39.0.1; platform_machine != 'armv7l' aiohttp==3.8.3 SQLAlchemy==1.4.46 python-telegram-bot==13.15 From c15e10fe1fdce5ea6f55f0f7563d6c35bfd83027 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 7 Feb 2023 19:42:18 +0100 Subject: [PATCH 30/37] Improve logic for initially placed stoploss --- freqtrade/freqtradebot.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ad50f69ba..0db420758 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -1170,15 +1170,13 @@ class FreqtradeBot(LoggingMixin): # If enter order is fulfilled but there is no stoploss, we add a stoploss on exchange if not stoploss_order: - stoploss = ( - self.edge.stoploss(pair=trade.pair) - if self.edge else - trade.stop_loss_pct / trade.leverage - ) - if trade.is_short: - stop_price = trade.open_rate * (1 - stoploss) - else: - stop_price = trade.open_rate * (1 + stoploss) + stop_price = trade.stoploss_or_liquidation + if self.edge: + stoploss = self.edge.stoploss(pair=trade.pair) + stop_price = ( + trade.open_rate * (1 - stoploss) if trade.is_short + else 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. From e2d81b0ce0afd6e3c2ed007082d7ac70c27a3ed0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 7 Feb 2023 19:45:37 +0100 Subject: [PATCH 31/37] Skip binanceus ccxt test --- tests/exchange/test_ccxt_compat.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/exchange/test_ccxt_compat.py b/tests/exchange/test_ccxt_compat.py index c4757288d..86a44bbe9 100644 --- a/tests/exchange/test_ccxt_compat.py +++ b/tests/exchange/test_ccxt_compat.py @@ -468,9 +468,13 @@ class TestCCXTExchange(): def test_ccxt__async_get_candle_history(self, exchange: EXCHANGE_FIXTURE_TYPE): exc, exchangename = exchange - # For some weired reason, this test returns random lengths for bittrex. - if not exc._ft_has['ohlcv_has_history'] or exchangename in ('bittrex'): - return + if exchangename in ('binanceus', 'bittrex'): + # TODO: reenable binanceus test once downtime "ages out" (2023-02-06) + # For some weired reason, this test returns random lengths for bittrex. + pytest.skip("Exchange doesn't provide stable ohlcv history") + + if not exc._ft_has['ohlcv_has_history']: + pytest.skip("Exchange does not support candle history") pair = EXCHANGES[exchangename]['pair'] timeframe = EXCHANGES[exchangename]['timeframe'] self.ccxt__async_get_candle_history( From d19ee9c95f7044d1a7d8855ee4b132299e009121 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 7 Feb 2023 20:21:55 +0100 Subject: [PATCH 32/37] Update okx position mode terminology --- docs/exchanges.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/exchanges.md b/docs/exchanges.md index 5ceeccb19..997d012e1 100644 --- a/docs/exchanges.md +++ b/docs/exchanges.md @@ -243,8 +243,8 @@ OKX requires a passphrase for each api key, you will therefore need to add this OKX only provides 100 candles per api call. Therefore, the strategy will only have a pretty low amount of data available in backtesting mode. !!! Warning "Futures" - OKX Futures has the concept of "position mode" - which can be Net or long/short (hedge mode). - Freqtrade supports both modes (we recommend to use net mode) - but changing the mode mid-trading is not supported and will lead to exceptions and failures to place trades. + OKX Futures has the concept of "position mode" - which can be "Buy/Sell" or long/short (hedge mode). + Freqtrade supports both modes (we recommend to use Buy/Sell mode) - but changing the mode mid-trading is not supported and will lead to exceptions and failures to place trades. OKX also only provides MARK candles for the past ~3 months. Backtesting futures prior to that date will therefore lead to slight deviations, as funding-fees cannot be calculated correctly without this data. ## Gate.io From 997df2032e30059f6f26faef7742a8f54c920e7a Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 7 Feb 2023 20:37:06 +0100 Subject: [PATCH 33/37] Add response_log for set_leverage --- freqtrade/exchange/okx.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/exchange/okx.py b/freqtrade/exchange/okx.py index 6792c2cba..6d05622c4 100644 --- a/freqtrade/exchange/okx.py +++ b/freqtrade/exchange/okx.py @@ -118,13 +118,15 @@ class Okx(Exchange): if self.trading_mode != TradingMode.SPOT and self.margin_mode is not None: try: # TODO-lev: Test me properly (check mgnMode passed) - self._api.set_leverage( + res = self._api.set_leverage( leverage=leverage, symbol=pair, params={ "mgnMode": self.margin_mode.value, "posSide": self._get_posSide(side, False), }) + self._log_exchange_response('set_leverage', res) + except ccxt.DDoSProtection as e: raise DDosProtection(e) from e except (ccxt.NetworkError, ccxt.ExchangeError) as e: From 980ffa6bfb95c2511b533519b733b41b2134cd4b Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 7 Feb 2023 20:50:53 +0100 Subject: [PATCH 34/37] Add test for binance rounding leverage --- tests/exchange/test_binance.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/exchange/test_binance.py b/tests/exchange/test_binance.py index cb304f699..4ccfc7e9c 100644 --- a/tests/exchange/test_binance.py +++ b/tests/exchange/test_binance.py @@ -522,8 +522,15 @@ def test__set_leverage_binance(mocker, default_conf): api_mock.set_leverage = MagicMock() type(api_mock).has = PropertyMock(return_value={'setLeverage': True}) default_conf['dry_run'] = False - exchange = get_patched_exchange(mocker, default_conf, id="binance") - exchange._set_leverage(3.0, trading_mode=TradingMode.MARGIN) + default_conf['trading_mode'] = TradingMode.FUTURES + default_conf['margin_mode'] = MarginMode.ISOLATED + + exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") + exchange._set_leverage(3.2, 'BTC/USDT:USDT') + assert api_mock.set_leverage.call_count == 1 + # Leverage is rounded to 3. + assert api_mock.set_leverage.call_args_list[0][1]['leverage'] == 3 + assert api_mock.set_leverage.call_args_list[0][1]['symbol'] == 'BTC/USDT:USDT' ccxt_exceptionhandlers( mocker, From 102c1e799ccf86e215426689046ad0468eee1ab8 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 7 Feb 2023 20:51:55 +0100 Subject: [PATCH 35/37] realign binance set_leverage override --- freqtrade/exchange/binance.py | 28 +--------------------------- freqtrade/exchange/exchange.py | 5 ++++- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/freqtrade/exchange/binance.py b/freqtrade/exchange/binance.py index 22dfdc1d1..d362a75cd 100644 --- a/freqtrade/exchange/binance.py +++ b/freqtrade/exchange/binance.py @@ -32,6 +32,7 @@ class Binance(Exchange): _ft_has_futures: Dict = { "stoploss_order_types": {"limit": "stop", "market": "stop_market"}, "tickers_have_price": False, + "floor_leverage": True, } _supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [ @@ -83,33 +84,6 @@ class Binance(Exchange): except ccxt.BaseError as e: raise OperationalException(e) from e - @retrier - def _set_leverage( - self, - leverage: float, - pair: Optional[str] = None, - trading_mode: Optional[TradingMode] = None, - accept_fail: bool = False, - ): - """ - Set's the leverage before making a trade, in order to not - have the same leverage on every trade - """ - trading_mode = trading_mode or self.trading_mode - - if self._config['dry_run'] or trading_mode != TradingMode.FUTURES: - return - - try: - self._api.set_leverage(symbol=pair, leverage=round(leverage)) - except ccxt.DDoSProtection as e: - raise DDosProtection(e) from e - except (ccxt.NetworkError, ccxt.ExchangeError) as e: - raise TemporaryError( - f'Could not set leverage 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, candle_type: CandleType, is_new_pair: bool = False, raise_: bool = False, diff --git a/freqtrade/exchange/exchange.py b/freqtrade/exchange/exchange.py index 8ac2abf6c..5ee51d686 100644 --- a/freqtrade/exchange/exchange.py +++ b/freqtrade/exchange/exchange.py @@ -7,6 +7,7 @@ import inspect import logging from copy import deepcopy from datetime import datetime, timedelta, timezone +from math import floor from threading import Lock from typing import Any, Coroutine, Dict, List, Literal, Optional, Tuple, Union @@ -2494,7 +2495,9 @@ class Exchange: if self._config['dry_run'] or not self.exchange_has("setLeverage"): # Some exchanges only support one margin_mode type return - + if self._ft_has.get('floor_leverage', False) is True: + # Rounding for binance ... + leverage = floor(leverage) try: res = self._api.set_leverage(symbol=pair, leverage=leverage) self._log_exchange_response('set_leverage', res) From 3d22ad36b8c17013f3e65f6b2e03ca4239635eff Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 8 Feb 2023 07:06:03 +0100 Subject: [PATCH 36/37] Show Config should contain stoploss-on-exchange status --- freqtrade/rpc/api_server/api_schemas.py | 1 + freqtrade/rpc/rpc.py | 1 + 2 files changed, 2 insertions(+) diff --git a/freqtrade/rpc/api_server/api_schemas.py b/freqtrade/rpc/api_server/api_schemas.py index d96055b69..58f6ad583 100644 --- a/freqtrade/rpc/api_server/api_schemas.py +++ b/freqtrade/rpc/api_server/api_schemas.py @@ -168,6 +168,7 @@ class ShowConfig(BaseModel): max_open_trades: IntOrInf minimal_roi: Dict[str, Any] stoploss: Optional[float] + stoploss_on_exchange: bool trailing_stop: Optional[bool] trailing_stop_positive: Optional[float] trailing_stop_positive_offset: Optional[float] diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index ddf6018ad..83bffb779 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -122,6 +122,7 @@ class RPC: if config['max_open_trades'] != float('inf') else -1), 'minimal_roi': config['minimal_roi'].copy() if 'minimal_roi' in config else {}, 'stoploss': config.get('stoploss'), + 'stoploss_on_exchange': config.get('stoploss_on_exchange', False), 'trailing_stop': config.get('trailing_stop'), 'trailing_stop_positive': config.get('trailing_stop_positive'), 'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset'), From 8d156b2770a7f97157532a9117c2c058985ad969 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 8 Feb 2023 20:35:24 +0100 Subject: [PATCH 37/37] Bump ccxt to 2.7.66 closes #8132 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 4f62e7ec4..751617d1d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ numpy==1.24.2 pandas==1.5.3 pandas-ta==0.3.14b -ccxt==2.7.45 +ccxt==2.7.66 # Pin cryptography for now due to rust build errors with piwheels cryptography==38.0.1; platform_machine == 'armv7l' cryptography==39.0.1; platform_machine != 'armv7l'