Merge pull request #6055 from freqtrade/blacklist_delete

Add Blacklist delete
This commit is contained in:
Matthias 2021-12-16 13:41:18 +01:00 committed by GitHub
commit b2fc3e814e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 98 additions and 18 deletions

View File

@ -3,7 +3,7 @@ from copy import deepcopy
from pathlib import Path from pathlib import Path
from typing import List, Optional from typing import List, Optional
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends, Query
from fastapi.exceptions import HTTPException from fastapi.exceptions import HTTPException
from freqtrade import __version__ from freqtrade import __version__
@ -30,7 +30,8 @@ logger = logging.getLogger(__name__)
# Pre-1.1, no version was provided # Pre-1.1, no version was provided
# Version increments should happen in "small" steps (1.1, 1.12, ...) unless big changes happen. # Version increments should happen in "small" steps (1.1, 1.12, ...) unless big changes happen.
# 1.11: forcebuy and forcesell accept ordertype # 1.11: forcebuy and forcesell accept ordertype
API_VERSION = 1.11 # 1.12: add blacklist delete endpoint
API_VERSION = 1.12
# Public API, requires no auth. # Public API, requires no auth.
router_public = APIRouter() router_public = APIRouter()
@ -157,6 +158,13 @@ def blacklist_post(payload: BlacklistPayload, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_blacklist(payload.blacklist) return rpc._rpc_blacklist(payload.blacklist)
@router.delete('/blacklist', response_model=BlacklistResponse, tags=['info', 'pairlist'])
def blacklist_delete(pairs_to_delete: List[str] = Query([]), rpc: RPC = Depends(get_rpc)):
"""Provide a list of pairs to delete from the blacklist"""
return rpc._rpc_blacklist_delete(pairs_to_delete)
@router.get('/whitelist', response_model=WhitelistResponse, tags=['info', 'pairlist']) @router.get('/whitelist', response_model=WhitelistResponse, tags=['info', 'pairlist'])
def whitelist(rpc: RPC = Depends(get_rpc)): def whitelist(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_whitelist() return rpc._rpc_whitelist()

View File

@ -860,6 +860,20 @@ class RPC:
} }
return res return res
def _rpc_blacklist_delete(self, delete: List[str]) -> Dict:
""" Removes pairs from currently active blacklist """
errors = {}
for pair in delete:
if pair in self._freqtrade.pairlists.blacklist:
self._freqtrade.pairlists.blacklist.remove(pair)
else:
errors[pair] = {
'error_msg': f"Pair {pair} is not in the current blacklist."
}
resp = self._rpc_blacklist()
resp['errors'] = errors
return resp
def _rpc_blacklist(self, add: List[str] = None) -> Dict: def _rpc_blacklist(self, add: List[str] = None) -> Dict:
""" Returns the currently active blacklist""" """ Returns the currently active blacklist"""
errors = {} errors = {}

View File

@ -111,9 +111,9 @@ class Telegram(RPCHandler):
r'/daily$', r'/daily \d+$', r'/profit$', r'/profit \d+', r'/daily$', r'/daily \d+$', r'/profit$', r'/profit \d+',
r'/stats$', r'/count$', r'/locks$', r'/balance$', r'/stats$', r'/count$', r'/locks$', r'/balance$',
r'/stopbuy$', r'/reload_config$', r'/show_config$', r'/stopbuy$', r'/reload_config$', r'/show_config$',
r'/logs$', r'/whitelist$', r'/blacklist$', r'/edge$', r'/logs$', r'/whitelist$', r'/blacklist$', r'/bl_delete$',
r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$', r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$',
r'/forcebuy$', r'/help$', r'/version$'] r'/forcebuy$', r'/edge$', r'/help$', r'/version$']
# Create keys for generation # Create keys for generation
valid_keys_print = [k.replace('$', '') for k in valid_keys] valid_keys_print = [k.replace('$', '') for k in valid_keys]
@ -170,6 +170,7 @@ class Telegram(RPCHandler):
CommandHandler('stopbuy', self._stopbuy), CommandHandler('stopbuy', self._stopbuy),
CommandHandler('whitelist', self._whitelist), CommandHandler('whitelist', self._whitelist),
CommandHandler('blacklist', self._blacklist), CommandHandler('blacklist', self._blacklist),
CommandHandler(['blacklist_delete', 'bl_delete'], self._blacklist_delete),
CommandHandler('logs', self._logs), CommandHandler('logs', self._logs),
CommandHandler('edge', self._edge), CommandHandler('edge', self._edge),
CommandHandler('help', self._help), CommandHandler('help', self._help),
@ -1162,9 +1163,9 @@ class Telegram(RPCHandler):
Handler for /blacklist Handler for /blacklist
Shows the currently active blacklist Shows the currently active blacklist
""" """
try: self.send_blacklist_msg(self._rpc._rpc_blacklist(context.args))
blacklist = self._rpc._rpc_blacklist(context.args) def send_blacklist_msg(self, blacklist: Dict):
errmsgs = [] errmsgs = []
for pair, error in blacklist['errors'].items(): for pair, error in blacklist['errors'].items():
errmsgs.append(f"Error adding `{pair}` to blacklist: `{error['error_msg']}`") errmsgs.append(f"Error adding `{pair}` to blacklist: `{error['error_msg']}`")
@ -1176,8 +1177,14 @@ class Telegram(RPCHandler):
logger.debug(message) logger.debug(message)
self._send_msg(message) self._send_msg(message)
except RPCException as e:
self._send_msg(str(e)) @authorized_only
def _blacklist_delete(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /bl_delete
Deletes pair(s) from current blacklist
"""
self.send_blacklist_msg(self._rpc._rpc_blacklist_delete(context.args or []))
@authorized_only @authorized_only
def _logs(self, update: Update, context: CallbackContext) -> None: def _logs(self, update: Update, context: CallbackContext) -> None:
@ -1258,6 +1265,8 @@ class Telegram(RPCHandler):
"*/whitelist:* `Show current whitelist` \n" "*/whitelist:* `Show current whitelist` \n"
"*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs " "*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs "
"to the blacklist.` \n" "to the blacklist.` \n"
"*/blacklist_delete [pairs]| /bl_delete [pairs]:* "
"`Delete pair / pattern from blacklist. Will reset on reload_conf.` \n"
"*/reload_config:* `Reload configuration file` \n" "*/reload_config:* `Reload configuration file` \n"
"*/unlock <pair|id>:* `Unlock this Pair (or this lock id if it's numeric)`\n" "*/unlock <pair|id>:* `Unlock this Pair (or this lock id if it's numeric)`\n"

View File

@ -1225,6 +1225,16 @@ def test_rpc_blacklist(mocker, default_conf) -> None:
assert 'errors' in ret assert 'errors' in ret
assert isinstance(ret['errors'], dict) assert isinstance(ret['errors'], dict)
ret = rpc._rpc_blacklist_delete(["DOGE/BTC", 'HOT/BTC'])
assert 'StaticPairList' in ret['method']
assert len(ret['blacklist']) == 2
assert ret['blacklist'] == default_conf['exchange']['pair_blacklist']
assert ret['blacklist'] == ['ETH/BTC', 'XRP/.*']
assert ret['blacklist_expanded'] == ['ETH/BTC', 'XRP/BTC', 'XRP/USDT']
assert 'errors' in ret
assert isinstance(ret['errors'], dict)
def test_rpc_edge_disabled(mocker, default_conf) -> None: def test_rpc_edge_disabled(mocker, default_conf) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())

View File

@ -955,6 +955,38 @@ def test_api_blacklist(botclient, mocker):
"errors": {}, "errors": {},
} }
rc = client_delete(client, f"{BASE_URI}/blacklist?pairs_to_delete=DOGE/BTC")
assert_response(rc)
assert rc.json() == {"blacklist": ["HOT/BTC", "ETH/BTC", "XRP/.*"],
"blacklist_expanded": ["ETH/BTC", "XRP/BTC", "XRP/USDT"],
"length": 3,
"method": ["StaticPairList"],
"errors": {},
}
rc = client_delete(client, f"{BASE_URI}/blacklist?pairs_to_delete=NOTHING/BTC")
assert_response(rc)
assert rc.json() == {"blacklist": ["HOT/BTC", "ETH/BTC", "XRP/.*"],
"blacklist_expanded": ["ETH/BTC", "XRP/BTC", "XRP/USDT"],
"length": 3,
"method": ["StaticPairList"],
"errors": {
"NOTHING/BTC": {
"error_msg": "Pair NOTHING/BTC is not in the current blacklist."
}
},
}
rc = client_delete(
client,
f"{BASE_URI}/blacklist?pairs_to_delete=HOT/BTC&pairs_to_delete=ETH/BTC")
assert_response(rc)
assert rc.json() == {"blacklist": ["XRP/.*"],
"blacklist_expanded": ["XRP/BTC", "XRP/USDT"],
"length": 1,
"method": ["StaticPairList"],
"errors": {},
}
def test_api_whitelist(botclient): def test_api_whitelist(botclient):
ftbot, client = botclient ftbot, client = botclient

View File

@ -98,7 +98,7 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
"['stats'], ['daily'], ['weekly'], ['monthly'], " "['stats'], ['daily'], ['weekly'], ['monthly'], "
"['count'], ['locks'], ['unlock', 'delete_locks'], " "['count'], ['locks'], ['unlock', 'delete_locks'], "
"['reload_config', 'reload_conf'], ['show_config', 'show_conf'], " "['reload_config', 'reload_conf'], ['show_config', 'show_conf'], "
"['stopbuy'], ['whitelist'], ['blacklist'], " "['stopbuy'], ['whitelist'], ['blacklist'], ['blacklist_delete', 'bl_delete'], "
"['logs'], ['edge'], ['help'], ['version']" "['logs'], ['edge'], ['help'], ['version']"
"]") "]")
@ -1470,6 +1470,13 @@ def test_blacklist_static(default_conf, update, mocker) -> None:
in msg_mock.call_args_list[0][0][0]) in msg_mock.call_args_list[0][0][0])
assert freqtradebot.pairlists.blacklist == ["DOGE/BTC", "HOT/BTC", "ETH/BTC", "XRP/.*"] assert freqtradebot.pairlists.blacklist == ["DOGE/BTC", "HOT/BTC", "ETH/BTC", "XRP/.*"]
msg_mock.reset_mock()
context.args = ["DOGE/BTC"]
telegram._blacklist_delete(update=update, context=context)
assert msg_mock.call_count == 1
assert ("Blacklist contains 3 pairs\n`HOT/BTC, ETH/BTC, XRP/.*`"
in msg_mock.call_args_list[0][0][0])
def test_telegram_logs(default_conf, update, mocker) -> None: def test_telegram_logs(default_conf, update, mocker) -> None:
mocker.patch.multiple( mocker.patch.multiple(