Add telegram pair unlocking
This commit is contained in:
		| @@ -146,6 +146,7 @@ official commands. You can ask at any moment for help with `/help`. | |||||||
| | `/delete <trade_id>` | Delete a specific trade from the Database. Tries to close open orders. Requires manual handling of this trade on the exchange. | | `/delete <trade_id>` | Delete a specific trade from the Database. Tries to close open orders. Requires manual handling of this trade on the exchange. | ||||||
| | `/count` | Displays number of trades used and available | | `/count` | Displays number of trades used and available | ||||||
| | `/locks` | Show currently locked pairs. | | `/locks` | Show currently locked pairs. | ||||||
|  | | `/unlock <pair | lock_id>` | 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 | | `/profit` | Display a summary of your profit/loss from close trades and some stats about your performance | ||||||
| | `/forcesell <trade_id>` | Instantly sells the given trade  (Ignoring `minimum_roi`). | | `/forcesell <trade_id>` | Instantly sells the given trade  (Ignoring `minimum_roi`). | ||||||
| | `/forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`). | | `/forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`). | ||||||
|   | |||||||
| @@ -11,13 +11,14 @@ from freqtrade.data.history import get_datahandler | |||||||
| from freqtrade.exceptions import OperationalException | from freqtrade.exceptions import OperationalException | ||||||
| from freqtrade.rpc import RPC | from freqtrade.rpc import RPC | ||||||
| from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, Balances, BlacklistPayload, | from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, Balances, BlacklistPayload, | ||||||
|                                                   BlacklistResponse, Count, Daily, DeleteLockRequest, DeleteTrade, |                                                   BlacklistResponse, Count, Daily, | ||||||
|                                                   ForceBuyPayload, ForceBuyResponse, |                                                   DeleteLockRequest, DeleteTrade, ForceBuyPayload, | ||||||
|                                                   ForceSellPayload, Locks, Logs, OpenTradeSchema, |                                                   ForceBuyResponse, ForceSellPayload, Locks, Logs, | ||||||
|                                                   PairHistory, PerformanceEntry, Ping, PlotConfig, |                                                   OpenTradeSchema, PairHistory, PerformanceEntry, | ||||||
|                                                   Profit, ResultMsg, ShowConfig, Stats, StatusMsg, |                                                   Ping, PlotConfig, Profit, ResultMsg, ShowConfig, | ||||||
|                                                   StrategyListResponse, StrategyResponse, |                                                   Stats, StatusMsg, StrategyListResponse, | ||||||
|                                                   TradeResponse, Version, WhitelistResponse) |                                                   StrategyResponse, TradeResponse, Version, | ||||||
|  |                                                   WhitelistResponse) | ||||||
| from freqtrade.rpc.api_server.deps import get_config, get_rpc, get_rpc_optional | from freqtrade.rpc.api_server.deps import get_config, get_rpc, get_rpc_optional | ||||||
| from freqtrade.rpc.rpc import RPCException | from freqtrade.rpc.rpc import RPCException | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ This module manage Telegram communication | |||||||
| import json | import json | ||||||
| import logging | import logging | ||||||
| from datetime import timedelta | from datetime import timedelta | ||||||
|  | from html import escape | ||||||
| from itertools import chain | from itertools import chain | ||||||
| from typing import Any, Callable, Dict, List, Union | from typing import Any, Callable, Dict, List, Union | ||||||
|  |  | ||||||
| @@ -144,6 +145,7 @@ class Telegram(RPCHandler): | |||||||
|             CommandHandler('daily', self._daily), |             CommandHandler('daily', self._daily), | ||||||
|             CommandHandler('count', self._count), |             CommandHandler('count', self._count), | ||||||
|             CommandHandler('locks', self._locks), |             CommandHandler('locks', self._locks), | ||||||
|  |             CommandHandler(['unlock', 'delete_locks'], self._delete_locks), | ||||||
|             CommandHandler(['reload_config', 'reload_conf'], self._reload_config), |             CommandHandler(['reload_config', 'reload_conf'], self._reload_config), | ||||||
|             CommandHandler(['show_config', 'show_conf'], self._show_config), |             CommandHandler(['show_config', 'show_conf'], self._show_config), | ||||||
|             CommandHandler('stopbuy', self._stopbuy), |             CommandHandler('stopbuy', self._stopbuy), | ||||||
| @@ -722,17 +724,39 @@ class Telegram(RPCHandler): | |||||||
|         try: |         try: | ||||||
|             locks = self._rpc._rpc_locks() |             locks = self._rpc._rpc_locks() | ||||||
|             message = tabulate([[ |             message = tabulate([[ | ||||||
|  |                 lock['id'], | ||||||
|                 lock['pair'], |                 lock['pair'], | ||||||
|                 lock['lock_end_time'], |                 lock['lock_end_time'], | ||||||
|                 lock['reason']] for lock in locks['locks']], |                 lock['reason']] for lock in locks['locks']], | ||||||
|                 headers=['Pair', 'Until', 'Reason'], |                 headers=['ID', 'Pair', 'Until', 'Reason'], | ||||||
|                 tablefmt='simple') |                 tablefmt='simple') | ||||||
|             message = "<pre>{}</pre>".format(message) |             message = f"<pre>{escape(message)}</pre>" | ||||||
|             logger.debug(message) |             logger.debug(message) | ||||||
|             self._send_msg(message, parse_mode=ParseMode.HTML) |             self._send_msg(message, parse_mode=ParseMode.HTML) | ||||||
|         except RPCException as e: |         except RPCException as e: | ||||||
|             self._send_msg(str(e)) |             self._send_msg(str(e)) | ||||||
|  |  | ||||||
|  |     @authorized_only | ||||||
|  |     def _delete_locks(self, update: Update, context: CallbackContext) -> None: | ||||||
|  |         """ | ||||||
|  |         Handler for /delete_locks. | ||||||
|  |         Returns the currently active locks | ||||||
|  |         """ | ||||||
|  |         try: | ||||||
|  |             arg = context.args[0] if context.args and len(context.args) > 0 else None | ||||||
|  |             lockid = None | ||||||
|  |             pair = None | ||||||
|  |             if arg: | ||||||
|  |                 try: | ||||||
|  |                     lockid = int(arg) | ||||||
|  |                 except ValueError: | ||||||
|  |                     pair = arg | ||||||
|  |  | ||||||
|  |             self._rpc._rpc_delete_lock(lockid=lockid, pair=pair) | ||||||
|  |             self._locks(update, context) | ||||||
|  |         except RPCException as e: | ||||||
|  |             self._send_msg(str(e)) | ||||||
|  |  | ||||||
|     @authorized_only |     @authorized_only | ||||||
|     def _whitelist(self, update: Update, context: CallbackContext) -> None: |     def _whitelist(self, update: Update, context: CallbackContext) -> None: | ||||||
|         """ |         """ | ||||||
| @@ -850,6 +874,7 @@ class Telegram(RPCHandler): | |||||||
|                    "Avg. holding durationsfor buys and sells.`\n" |                    "Avg. holding durationsfor buys and sells.`\n" | ||||||
|                    "*/count:* `Show number of active trades compared to allowed number of trades`\n" |                    "*/count:* `Show number of active trades compared to allowed number of trades`\n" | ||||||
|                    "*/locks:* `Show currently locked pairs`\n" |                    "*/locks:* `Show currently locked pairs`\n" | ||||||
|  |                    "*/unlock <pair|id>:* `Unlock this Pair (or this lock id if it's numeric)`\n" | ||||||
|                    "*/balance:* `Show account balance per currency`\n" |                    "*/balance:* `Show account balance per currency`\n" | ||||||
|                    "*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" |                    "*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" | ||||||
|                    "*/reload_config:* `Reload configuration file` \n" |                    "*/reload_config:* `Reload configuration file` \n" | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ | |||||||
| # pragma pylint: disable=invalid-sequence-index, invalid-name, too-many-arguments | # pragma pylint: disable=invalid-sequence-index, invalid-name, too-many-arguments | ||||||
|  |  | ||||||
| from datetime import datetime, timedelta, timezone | from datetime import datetime, timedelta, timezone | ||||||
| from freqtrade.persistence.pairlock_middleware import PairLocks |  | ||||||
| from unittest.mock import ANY, MagicMock, PropertyMock | from unittest.mock import ANY, MagicMock, PropertyMock | ||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
| @@ -11,6 +10,7 @@ from numpy import isnan | |||||||
| from freqtrade.edge import PairInfo | from freqtrade.edge import PairInfo | ||||||
| from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError | from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError | ||||||
| from freqtrade.persistence import Trade | from freqtrade.persistence import Trade | ||||||
|  | from freqtrade.persistence.pairlock_middleware import PairLocks | ||||||
| from freqtrade.rpc import RPC, RPCException | from freqtrade.rpc import RPC, RPCException | ||||||
| from freqtrade.rpc.fiat_convert import CryptoToFiatConverter | from freqtrade.rpc.fiat_convert import CryptoToFiatConverter | ||||||
| from freqtrade.state import State | from freqtrade.state import State | ||||||
|   | |||||||
| @@ -92,7 +92,8 @@ def test_telegram_init(default_conf, mocker, caplog) -> None: | |||||||
|     message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], " |     message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], " | ||||||
|                    "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], " |                    "['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], " | ||||||
|                    "['delete'], ['performance'], ['stats'], ['daily'], ['count'], ['locks'], " |                    "['delete'], ['performance'], ['stats'], ['daily'], ['count'], ['locks'], " | ||||||
|                    "['reload_config', 'reload_conf'], ['show_config', 'show_conf'], ['stopbuy'], " |                    "['unlock', 'delete_locks'], ['reload_config', 'reload_conf'], " | ||||||
|  |                    "['show_config', 'show_conf'], ['stopbuy'], " | ||||||
|                    "['whitelist'], ['blacklist'], ['logs'], ['edge'], ['help'], ['version']" |                    "['whitelist'], ['blacklist'], ['logs'], ['edge'], ['help'], ['version']" | ||||||
|                    "]") |                    "]") | ||||||
|  |  | ||||||
| @@ -981,6 +982,16 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None | |||||||
|     assert 'deadbeef' in msg_mock.call_args_list[0][0][0] |     assert 'deadbeef' in msg_mock.call_args_list[0][0][0] | ||||||
|     assert 'randreason' in msg_mock.call_args_list[0][0][0] |     assert 'randreason' in msg_mock.call_args_list[0][0][0] | ||||||
|  |  | ||||||
|  |     context = MagicMock() | ||||||
|  |     context.args = ['XRP/BTC'] | ||||||
|  |     msg_mock.reset_mock() | ||||||
|  |     telegram._delete_locks(update=update, context=context) | ||||||
|  |  | ||||||
|  |     assert 'ETH/BTC' in msg_mock.call_args_list[0][0][0] | ||||||
|  |     assert 'randreason' in msg_mock.call_args_list[0][0][0] | ||||||
|  |     assert 'XRP/BTC' not in msg_mock.call_args_list[0][0][0] | ||||||
|  |     assert 'deadbeef' not in msg_mock.call_args_list[0][0][0] | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_whitelist_static(default_conf, update, mocker) -> None: | def test_whitelist_static(default_conf, update, mocker) -> None: | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user