Merge pull request #3611 from thopd88/telegram-delete-command
Add telegram /delete command
This commit is contained in:
commit
e2643103b6
@ -46,7 +46,7 @@ secrets.token_hex()
|
|||||||
|
|
||||||
### Configuration with docker
|
### Configuration with docker
|
||||||
|
|
||||||
If you run your bot using docker, you'll need to have the bot listen to incomming connections. The security is then handled by docker.
|
If you run your bot using docker, you'll need to have the bot listen to incoming connections. The security is then handled by docker.
|
||||||
|
|
||||||
``` json
|
``` json
|
||||||
"api_server": {
|
"api_server": {
|
||||||
@ -106,26 +106,29 @@ python3 scripts/rest_client.py --config rest_config.json <command> [optional par
|
|||||||
|
|
||||||
## Available commands
|
## Available commands
|
||||||
|
|
||||||
| Command | Default | Description |
|
| Command | Description |
|
||||||
|----------|---------|-------------|
|
|----------|-------------|
|
||||||
| `start` | | Starts the trader
|
| `ping` | Simple command testing the API Readiness - requires no authentication.
|
||||||
| `stop` | | Stops the trader
|
| `start` | Starts the trader
|
||||||
| `stopbuy` | | Stops the trader from opening new trades. Gracefully closes open trades according to their rules.
|
| `stop` | Stops the trader
|
||||||
| `reload_config` | | Reloads the configuration file
|
| `stopbuy` | Stops the trader from opening new trades. Gracefully closes open trades according to their rules.
|
||||||
| `show_config` | | Shows part of the current configuration with relevant settings to operation
|
| `reload_config` | Reloads the configuration file
|
||||||
| `status` | | Lists all open trades
|
| `trades` | List last trades.
|
||||||
| `count` | | Displays number of trades used and available
|
| `delete_trade <trade_id>` | Remove trade from the database. Tries to close open orders. Requires manual handling of this trade on the exchange.
|
||||||
| `profit` | | Display a summary of your profit/loss from close trades and some stats about your performance
|
| `show_config` | Shows part of the current configuration with relevant settings to operation
|
||||||
| `forcesell <trade_id>` | | Instantly sells the given trade (Ignoring `minimum_roi`).
|
| `status` | Lists all open trades
|
||||||
| `forcesell all` | | Instantly sells all open trades (Ignoring `minimum_roi`).
|
| `count` | Displays number of trades used and available
|
||||||
| `forcebuy <pair> [rate]` | | Instantly buys the given pair. Rate is optional. (`forcebuy_enable` must be set to True)
|
| `profit` | Display a summary of your profit/loss from close trades and some stats about your performance
|
||||||
| `performance` | | Show performance of each finished trade grouped by pair
|
| `forcesell <trade_id>` | Instantly sells the given trade (Ignoring `minimum_roi`).
|
||||||
| `balance` | | Show account balance per currency
|
| `forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`).
|
||||||
| `daily <n>` | 7 | Shows profit or loss per day, over the last n days
|
| `forcebuy <pair> [rate]` | Instantly buys the given pair. Rate is optional. (`forcebuy_enable` must be set to True)
|
||||||
| `whitelist` | | Show the current whitelist
|
| `performance` | Show performance of each finished trade grouped by pair
|
||||||
| `blacklist [pair]` | | Show the current blacklist, or adds a pair to the blacklist.
|
| `balance` | Show account balance per currency
|
||||||
| `edge` | | Show validated pairs by Edge if it is enabled.
|
| `daily <n>` | Shows profit or loss per day, over the last n days (n defaults to 7)
|
||||||
| `version` | | Show version
|
| `whitelist` | Show the current whitelist
|
||||||
|
| `blacklist [pair]` | Show the current blacklist, or adds a pair to the blacklist.
|
||||||
|
| `edge` | Show validated pairs by Edge if it is enabled.
|
||||||
|
| `version` | Show version
|
||||||
|
|
||||||
Possible commands can be listed from the rest-client script using the `help` command.
|
Possible commands can be listed from the rest-client script using the `help` command.
|
||||||
|
|
||||||
|
@ -47,29 +47,30 @@ Per default, the Telegram bot shows predefined commands. Some commands
|
|||||||
are only available by sending them to the bot. The table below list the
|
are only available by sending them to the bot. The table below list the
|
||||||
official commands. You can ask at any moment for help with `/help`.
|
official commands. You can ask at any moment for help with `/help`.
|
||||||
|
|
||||||
| Command | Default | Description |
|
| Command | Description |
|
||||||
|----------|---------|-------------|
|
|----------|-------------|
|
||||||
| `/start` | | Starts the trader
|
| `/start` | Starts the trader
|
||||||
| `/stop` | | Stops the trader
|
| `/stop` | Stops the trader
|
||||||
| `/stopbuy` | | Stops the trader from opening new trades. Gracefully closes open trades according to their rules.
|
| `/stopbuy` | Stops the trader from opening new trades. Gracefully closes open trades according to their rules.
|
||||||
| `/reload_config` | | Reloads the configuration file
|
| `/reload_config` | Reloads the configuration file
|
||||||
| `/show_config` | | Shows part of the current configuration with relevant settings to operation
|
| `/show_config` | Shows part of the current configuration with relevant settings to operation
|
||||||
| `/status` | | Lists all open trades
|
| `/status` | Lists all open trades
|
||||||
| `/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 (**)
|
| `/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.
|
| `/trades [limit]` | List all recently closed trades in a table format.
|
||||||
| `/count` | | Displays number of trades used and available
|
| `/delete <trade_id>` | Delete a specific trade from the Database. Tries to close open orders. Requires manual handling of this trade on the exchange.
|
||||||
| `/profit` | | Display a summary of your profit/loss from close trades and some stats about your performance
|
| `/count` | Displays number of trades used and available
|
||||||
| `/forcesell <trade_id>` | | Instantly sells the given trade (Ignoring `minimum_roi`).
|
| `/profit` | Display a summary of your profit/loss from close trades and some stats about your performance
|
||||||
| `/forcesell all` | | Instantly sells all open trades (Ignoring `minimum_roi`).
|
| `/forcesell <trade_id>` | Instantly sells the given trade (Ignoring `minimum_roi`).
|
||||||
| `/forcebuy <pair> [rate]` | | Instantly buys the given pair. Rate is optional. (`forcebuy_enable` must be set to True)
|
| `/forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`).
|
||||||
| `/performance` | | Show performance of each finished trade grouped by pair
|
| `/forcebuy <pair> [rate]` | Instantly buys the given pair. Rate is optional. (`forcebuy_enable` must be set to True)
|
||||||
| `/balance` | | Show account balance per currency
|
| `/performance` | Show performance of each finished trade grouped by pair
|
||||||
| `/daily <n>` | 7 | Shows profit or loss per day, over the last n days
|
| `/balance` | Show account balance per currency
|
||||||
| `/whitelist` | | Show the current whitelist
|
| `/daily <n>` | Shows profit or loss per day, over the last n days (n defaults to 7)
|
||||||
| `/blacklist [pair]` | | Show the current blacklist, or adds a pair to the blacklist.
|
| `/whitelist` | Show the current whitelist
|
||||||
| `/edge` | | Show validated pairs by Edge if it is enabled.
|
| `/blacklist [pair]` | Show the current blacklist, or adds a pair to the blacklist.
|
||||||
| `/help` | | Show help message
|
| `/edge` | Show validated pairs by Edge if it is enabled.
|
||||||
| `/version` | | Show version
|
| `/help` | Show help message
|
||||||
|
| `/version` | Show version
|
||||||
|
|
||||||
## Telegram commands in action
|
## Telegram commands in action
|
||||||
|
|
||||||
@ -114,6 +115,7 @@ For each open trade, the bot will send you the following message.
|
|||||||
### /status table
|
### /status table
|
||||||
|
|
||||||
Return the status of all open trades in a table format.
|
Return the status of all open trades in a table format.
|
||||||
|
|
||||||
```
|
```
|
||||||
ID Pair Since Profit
|
ID Pair Since Profit
|
||||||
---- -------- ------- --------
|
---- -------- ------- --------
|
||||||
@ -124,6 +126,7 @@ Return the status of all open trades in a table format.
|
|||||||
### /count
|
### /count
|
||||||
|
|
||||||
Return the number of trades used and available.
|
Return the number of trades used and available.
|
||||||
|
|
||||||
```
|
```
|
||||||
current max
|
current max
|
||||||
--------- -----
|
--------- -----
|
||||||
@ -209,7 +212,7 @@ Shows the current whitelist
|
|||||||
|
|
||||||
Shows the current blacklist.
|
Shows the current blacklist.
|
||||||
If Pair is set, then this pair will be added to the pairlist.
|
If Pair is set, then this pair will be added to the pairlist.
|
||||||
Also supports multiple pairs, seperated by a space.
|
Also supports multiple pairs, separated by a space.
|
||||||
Use `/reload_config` to reset the blacklist.
|
Use `/reload_config` to reset the blacklist.
|
||||||
|
|
||||||
> Using blacklist `StaticPairList` with 2 pairs
|
> Using blacklist `StaticPairList` with 2 pairs
|
||||||
@ -217,7 +220,7 @@ Use `/reload_config` to reset the blacklist.
|
|||||||
|
|
||||||
### /edge
|
### /edge
|
||||||
|
|
||||||
Shows pairs validated by Edge along with their corresponding winrate, expectancy and stoploss values.
|
Shows pairs validated by Edge along with their corresponding win-rate, expectancy and stoploss values.
|
||||||
|
|
||||||
> **Edge only validated following pairs:**
|
> **Edge only validated following pairs:**
|
||||||
```
|
```
|
||||||
|
@ -56,7 +56,7 @@ def require_login(func: Callable[[Any, Any], Any]):
|
|||||||
|
|
||||||
|
|
||||||
# Type should really be Callable[[ApiServer], Any], but that will create a circular dependency
|
# Type should really be Callable[[ApiServer], Any], but that will create a circular dependency
|
||||||
def rpc_catch_errors(func: Callable[[Any], Any]):
|
def rpc_catch_errors(func: Callable[..., Any]):
|
||||||
|
|
||||||
def func_wrapper(obj, *args, **kwargs):
|
def func_wrapper(obj, *args, **kwargs):
|
||||||
|
|
||||||
@ -200,6 +200,8 @@ class ApiServer(RPC):
|
|||||||
view_func=self._ping, methods=['GET'])
|
view_func=self._ping, methods=['GET'])
|
||||||
self.app.add_url_rule(f'{BASE_URI}/trades', 'trades',
|
self.app.add_url_rule(f'{BASE_URI}/trades', 'trades',
|
||||||
view_func=self._trades, methods=['GET'])
|
view_func=self._trades, methods=['GET'])
|
||||||
|
self.app.add_url_rule(f'{BASE_URI}/trades/<int:tradeid>', 'trades_delete',
|
||||||
|
view_func=self._trades_delete, methods=['DELETE'])
|
||||||
# Combined actions and infos
|
# Combined actions and infos
|
||||||
self.app.add_url_rule(f'{BASE_URI}/blacklist', 'blacklist', view_func=self._blacklist,
|
self.app.add_url_rule(f'{BASE_URI}/blacklist', 'blacklist', view_func=self._blacklist,
|
||||||
methods=['GET', 'POST'])
|
methods=['GET', 'POST'])
|
||||||
@ -424,6 +426,19 @@ class ApiServer(RPC):
|
|||||||
results = self._rpc_trade_history(limit)
|
results = self._rpc_trade_history(limit)
|
||||||
return self.rest_dump(results)
|
return self.rest_dump(results)
|
||||||
|
|
||||||
|
@require_login
|
||||||
|
@rpc_catch_errors
|
||||||
|
def _trades_delete(self, tradeid):
|
||||||
|
"""
|
||||||
|
Handler for DELETE /trades/<tradeid> endpoint.
|
||||||
|
Removes the trade from the database (tries to cancel open orders first!)
|
||||||
|
get:
|
||||||
|
param:
|
||||||
|
tradeid: Numeric trade-id assigned to the trade.
|
||||||
|
"""
|
||||||
|
result = self._rpc_delete(tradeid)
|
||||||
|
return self.rest_dump(result)
|
||||||
|
|
||||||
@require_login
|
@require_login
|
||||||
@rpc_catch_errors
|
@rpc_catch_errors
|
||||||
def _whitelist(self):
|
def _whitelist(self):
|
||||||
|
@ -6,14 +6,14 @@ from abc import abstractmethod
|
|||||||
from datetime import date, datetime, timedelta
|
from datetime import date, datetime, timedelta
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from math import isnan
|
from math import isnan
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||||
|
|
||||||
import arrow
|
import arrow
|
||||||
from numpy import NAN, mean
|
from numpy import NAN, mean
|
||||||
|
|
||||||
from freqtrade.exceptions import ExchangeError, PricingError
|
from freqtrade.exceptions import (ExchangeError, InvalidOrderException,
|
||||||
|
PricingError)
|
||||||
from freqtrade.exchange import timeframe_to_msecs, timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs
|
||||||
from freqtrade.misc import shorten_date
|
from freqtrade.misc import shorten_date
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
||||||
@ -538,6 +538,46 @@ class RPC:
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _rpc_delete(self, trade_id: str) -> Dict[str, Union[str, int]]:
|
||||||
|
"""
|
||||||
|
Handler for delete <id>.
|
||||||
|
Delete the given trade and close eventually existing open orders.
|
||||||
|
"""
|
||||||
|
with self._freqtrade._sell_lock:
|
||||||
|
c_count = 0
|
||||||
|
trade = Trade.get_trades(trade_filter=[Trade.id == trade_id]).first()
|
||||||
|
if not trade:
|
||||||
|
logger.warning('delete trade: Invalid argument received')
|
||||||
|
raise RPCException('invalid argument')
|
||||||
|
|
||||||
|
# Try cancelling regular order if that exists
|
||||||
|
if trade.open_order_id:
|
||||||
|
try:
|
||||||
|
self._freqtrade.exchange.cancel_order(trade.open_order_id, trade.pair)
|
||||||
|
c_count += 1
|
||||||
|
except (ExchangeError, InvalidOrderException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# cancel stoploss on exchange ...
|
||||||
|
if (self._freqtrade.strategy.order_types.get('stoploss_on_exchange')
|
||||||
|
and trade.stoploss_order_id):
|
||||||
|
try:
|
||||||
|
self._freqtrade.exchange.cancel_stoploss_order(trade.stoploss_order_id,
|
||||||
|
trade.pair)
|
||||||
|
c_count += 1
|
||||||
|
except (ExchangeError, InvalidOrderException):
|
||||||
|
pass
|
||||||
|
|
||||||
|
Trade.session.delete(trade)
|
||||||
|
Trade.session.flush()
|
||||||
|
self._freqtrade.wallets.update()
|
||||||
|
return {
|
||||||
|
'result': 'success',
|
||||||
|
'trade_id': trade_id,
|
||||||
|
'result_msg': f'Deleted trade {trade_id}. Closed {c_count} open orders.',
|
||||||
|
'cancel_order_count': c_count,
|
||||||
|
}
|
||||||
|
|
||||||
def _rpc_performance(self) -> List[Dict[str, Any]]:
|
def _rpc_performance(self) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Handler for performance.
|
Handler for performance.
|
||||||
|
@ -94,6 +94,7 @@ class Telegram(RPC):
|
|||||||
CommandHandler('forcesell', self._forcesell),
|
CommandHandler('forcesell', self._forcesell),
|
||||||
CommandHandler('forcebuy', self._forcebuy),
|
CommandHandler('forcebuy', self._forcebuy),
|
||||||
CommandHandler('trades', self._trades),
|
CommandHandler('trades', self._trades),
|
||||||
|
CommandHandler('delete', self._delete_trade),
|
||||||
CommandHandler('performance', self._performance),
|
CommandHandler('performance', self._performance),
|
||||||
CommandHandler('daily', self._daily),
|
CommandHandler('daily', self._daily),
|
||||||
CommandHandler('count', self._count),
|
CommandHandler('count', self._count),
|
||||||
@ -533,6 +534,27 @@ class Telegram(RPC):
|
|||||||
except RPCException as e:
|
except RPCException as e:
|
||||||
self._send_msg(str(e))
|
self._send_msg(str(e))
|
||||||
|
|
||||||
|
@authorized_only
|
||||||
|
def _delete_trade(self, update: Update, context: CallbackContext) -> None:
|
||||||
|
"""
|
||||||
|
Handler for /delete <id>.
|
||||||
|
Delete the given trade
|
||||||
|
:param bot: telegram bot
|
||||||
|
:param update: message update
|
||||||
|
:return: None
|
||||||
|
"""
|
||||||
|
|
||||||
|
trade_id = context.args[0] if len(context.args) > 0 else None
|
||||||
|
try:
|
||||||
|
msg = self._rpc_delete(trade_id)
|
||||||
|
self._send_msg((
|
||||||
|
'`{result_msg}`\n'
|
||||||
|
'Please make sure to take care of this asset on the exchange manually.'
|
||||||
|
).format(**msg))
|
||||||
|
|
||||||
|
except RPCException as e:
|
||||||
|
self._send_msg(str(e))
|
||||||
|
|
||||||
@authorized_only
|
@authorized_only
|
||||||
def _performance(self, update: Update, context: CallbackContext) -> None:
|
def _performance(self, update: Update, context: CallbackContext) -> None:
|
||||||
"""
|
"""
|
||||||
@ -651,6 +673,7 @@ class Telegram(RPC):
|
|||||||
"*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, "
|
"*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, "
|
||||||
"regardless of profit`\n"
|
"regardless of profit`\n"
|
||||||
f"{forcebuy_text if self._config.get('forcebuy_enable', False) else ''}"
|
f"{forcebuy_text if self._config.get('forcebuy_enable', False) else ''}"
|
||||||
|
"*/delete <trade_id>:* `Instantly delete the given trade in the database`\n"
|
||||||
"*/performance:* `Show performance of each finished trade grouped by pair`\n"
|
"*/performance:* `Show performance of each finished trade grouped by pair`\n"
|
||||||
"*/daily <n>:* `Shows profit or loss per day, over the last n days`\n"
|
"*/daily <n>:* `Shows profit or loss per day, over the last n days`\n"
|
||||||
"*/count:* `Show number of trades running compared to allowed number of trades`"
|
"*/count:* `Show number of trades running compared to allowed number of trades`"
|
||||||
|
@ -62,6 +62,9 @@ class FtRestClient():
|
|||||||
def _get(self, apipath, params: dict = None):
|
def _get(self, apipath, params: dict = None):
|
||||||
return self._call("GET", apipath, params=params)
|
return self._call("GET", apipath, params=params)
|
||||||
|
|
||||||
|
def _delete(self, apipath, params: dict = None):
|
||||||
|
return self._call("DELETE", apipath, params=params)
|
||||||
|
|
||||||
def _post(self, apipath, params: dict = None, data: dict = None):
|
def _post(self, apipath, params: dict = None, data: dict = None):
|
||||||
return self._call("POST", apipath, params=params, data=data)
|
return self._call("POST", apipath, params=params, data=data)
|
||||||
|
|
||||||
@ -164,6 +167,15 @@ class FtRestClient():
|
|||||||
"""
|
"""
|
||||||
return self._get("trades", params={"limit": limit} if limit else 0)
|
return self._get("trades", params={"limit": limit} if limit else 0)
|
||||||
|
|
||||||
|
def delete_trade(self, trade_id):
|
||||||
|
"""Delete trade from the database.
|
||||||
|
Tries to close open orders. Requires manual handling of this asset on the exchange.
|
||||||
|
|
||||||
|
:param trade_id: Deletes the trade with this ID from the database.
|
||||||
|
:return: json object
|
||||||
|
"""
|
||||||
|
return self._delete("trades/{}".format(trade_id))
|
||||||
|
|
||||||
def whitelist(self):
|
def whitelist(self):
|
||||||
"""Show the current whitelist.
|
"""Show the current whitelist.
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import pytest
|
|||||||
from numpy import isnan
|
from numpy import isnan
|
||||||
|
|
||||||
from freqtrade.edge import PairInfo
|
from freqtrade.edge import PairInfo
|
||||||
from freqtrade.exceptions import ExchangeError, TemporaryError
|
from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
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
|
||||||
@ -291,6 +291,61 @@ def test_rpc_trade_history(mocker, default_conf, markets, fee):
|
|||||||
assert trades['trades'][0]['pair'] == 'XRP/BTC'
|
assert trades['trades'][0]['pair'] == 'XRP/BTC'
|
||||||
|
|
||||||
|
|
||||||
|
def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog):
|
||||||
|
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||||
|
stoploss_mock = MagicMock()
|
||||||
|
cancel_mock = MagicMock()
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
markets=PropertyMock(return_value=markets),
|
||||||
|
cancel_order=cancel_mock,
|
||||||
|
cancel_stoploss_order=stoploss_mock,
|
||||||
|
)
|
||||||
|
|
||||||
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
|
freqtradebot.strategy.order_types['stoploss_on_exchange'] = True
|
||||||
|
create_mock_trades(fee)
|
||||||
|
rpc = RPC(freqtradebot)
|
||||||
|
with pytest.raises(RPCException, match='invalid argument'):
|
||||||
|
rpc._rpc_delete('200')
|
||||||
|
|
||||||
|
create_mock_trades(fee)
|
||||||
|
trades = Trade.query.all()
|
||||||
|
trades[1].stoploss_order_id = '1234'
|
||||||
|
trades[2].stoploss_order_id = '1234'
|
||||||
|
assert len(trades) > 2
|
||||||
|
|
||||||
|
res = rpc._rpc_delete('1')
|
||||||
|
assert isinstance(res, dict)
|
||||||
|
assert res['result'] == 'success'
|
||||||
|
assert res['trade_id'] == '1'
|
||||||
|
assert res['cancel_order_count'] == 1
|
||||||
|
assert cancel_mock.call_count == 1
|
||||||
|
assert stoploss_mock.call_count == 0
|
||||||
|
cancel_mock.reset_mock()
|
||||||
|
stoploss_mock.reset_mock()
|
||||||
|
|
||||||
|
res = rpc._rpc_delete('2')
|
||||||
|
assert isinstance(res, dict)
|
||||||
|
assert cancel_mock.call_count == 1
|
||||||
|
assert stoploss_mock.call_count == 1
|
||||||
|
assert res['cancel_order_count'] == 2
|
||||||
|
|
||||||
|
stoploss_mock = mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
|
||||||
|
side_effect=InvalidOrderException)
|
||||||
|
|
||||||
|
res = rpc._rpc_delete('3')
|
||||||
|
assert stoploss_mock.call_count == 1
|
||||||
|
stoploss_mock.reset_mock()
|
||||||
|
|
||||||
|
cancel_mock = mocker.patch('freqtrade.exchange.Exchange.cancel_order',
|
||||||
|
side_effect=InvalidOrderException)
|
||||||
|
|
||||||
|
res = rpc._rpc_delete('4')
|
||||||
|
assert cancel_mock.call_count == 1
|
||||||
|
assert stoploss_mock.call_count == 0
|
||||||
|
|
||||||
|
|
||||||
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
||||||
limit_buy_order, limit_sell_order, mocker) -> None:
|
limit_buy_order, limit_sell_order, mocker) -> None:
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
|
@ -50,6 +50,12 @@ def client_get(client, url):
|
|||||||
'Origin': 'http://example.com'})
|
'Origin': 'http://example.com'})
|
||||||
|
|
||||||
|
|
||||||
|
def client_delete(client, url):
|
||||||
|
# Add fake Origin to ensure CORS kicks in
|
||||||
|
return client.delete(url, headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS),
|
||||||
|
'Origin': 'http://example.com'})
|
||||||
|
|
||||||
|
|
||||||
def assert_response(response, expected_code=200, needs_cors=True):
|
def assert_response(response, expected_code=200, needs_cors=True):
|
||||||
assert response.status_code == expected_code
|
assert response.status_code == expected_code
|
||||||
assert response.content_type == "application/json"
|
assert response.content_type == "application/json"
|
||||||
@ -352,7 +358,7 @@ def test_api_daily(botclient, mocker, ticker, fee, markets):
|
|||||||
assert rc.json['data'][0]['date'] == str(datetime.utcnow().date())
|
assert rc.json['data'][0]['date'] == str(datetime.utcnow().date())
|
||||||
|
|
||||||
|
|
||||||
def test_api_trades(botclient, mocker, ticker, fee, markets):
|
def test_api_trades(botclient, mocker, fee, markets):
|
||||||
ftbot, client = botclient
|
ftbot, client = botclient
|
||||||
patch_get_signal(ftbot, (True, False))
|
patch_get_signal(ftbot, (True, False))
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -376,6 +382,47 @@ def test_api_trades(botclient, mocker, ticker, fee, markets):
|
|||||||
assert rc.json['trades_count'] == 1
|
assert rc.json['trades_count'] == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_delete_trade(botclient, mocker, fee, markets):
|
||||||
|
ftbot, client = botclient
|
||||||
|
patch_get_signal(ftbot, (True, False))
|
||||||
|
stoploss_mock = MagicMock()
|
||||||
|
cancel_mock = MagicMock()
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.exchange.Exchange',
|
||||||
|
markets=PropertyMock(return_value=markets),
|
||||||
|
cancel_order=cancel_mock,
|
||||||
|
cancel_stoploss_order=stoploss_mock,
|
||||||
|
)
|
||||||
|
rc = client_delete(client, f"{BASE_URI}/trades/1")
|
||||||
|
# Error - trade won't exist yet.
|
||||||
|
assert_response(rc, 502)
|
||||||
|
|
||||||
|
create_mock_trades(fee)
|
||||||
|
ftbot.strategy.order_types['stoploss_on_exchange'] = True
|
||||||
|
trades = Trade.query.all()
|
||||||
|
trades[1].stoploss_order_id = '1234'
|
||||||
|
assert len(trades) > 2
|
||||||
|
|
||||||
|
rc = client_delete(client, f"{BASE_URI}/trades/1")
|
||||||
|
assert_response(rc)
|
||||||
|
assert rc.json['result_msg'] == 'Deleted trade 1. Closed 1 open orders.'
|
||||||
|
assert len(trades) - 1 == len(Trade.query.all())
|
||||||
|
assert cancel_mock.call_count == 1
|
||||||
|
|
||||||
|
cancel_mock.reset_mock()
|
||||||
|
rc = client_delete(client, f"{BASE_URI}/trades/1")
|
||||||
|
# Trade is gone now.
|
||||||
|
assert_response(rc, 502)
|
||||||
|
assert cancel_mock.call_count == 0
|
||||||
|
|
||||||
|
assert len(trades) - 1 == len(Trade.query.all())
|
||||||
|
rc = client_delete(client, f"{BASE_URI}/trades/2")
|
||||||
|
assert_response(rc)
|
||||||
|
assert rc.json['result_msg'] == 'Deleted trade 2. Closed 2 open orders.'
|
||||||
|
assert len(trades) - 2 == len(Trade.query.all())
|
||||||
|
assert stoploss_mock.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
||||||
ftbot, client = botclient
|
ftbot, client = botclient
|
||||||
patch_get_signal(ftbot, (True, False))
|
patch_get_signal(ftbot, (True, False))
|
||||||
|
@ -74,9 +74,9 @@ 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'], "
|
||||||
"['performance'], ['daily'], ['count'], ['reload_config', 'reload_conf'], "
|
"['delete'], ['performance'], ['daily'], ['count'], ['reload_config', "
|
||||||
"['show_config', 'show_conf'], ['stopbuy'], ['whitelist'], ['blacklist'], "
|
"'reload_conf'], ['show_config', 'show_conf'], ['stopbuy'], "
|
||||||
"['edge'], ['help'], ['version']]")
|
"['whitelist'], ['blacklist'], ['edge'], ['help'], ['version']]")
|
||||||
|
|
||||||
assert log_has(message_str, caplog)
|
assert log_has(message_str, caplog)
|
||||||
|
|
||||||
@ -1177,6 +1177,33 @@ def test_telegram_trades(mocker, update, default_conf, fee):
|
|||||||
assert "<pre>" in msg_mock.call_args_list[0][0][0]
|
assert "<pre>" in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_telegram_delete_trade(mocker, update, default_conf, fee):
|
||||||
|
msg_mock = MagicMock()
|
||||||
|
mocker.patch.multiple(
|
||||||
|
'freqtrade.rpc.telegram.Telegram',
|
||||||
|
_init=MagicMock(),
|
||||||
|
_send_msg=msg_mock
|
||||||
|
)
|
||||||
|
|
||||||
|
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||||
|
telegram = Telegram(freqtradebot)
|
||||||
|
context = MagicMock()
|
||||||
|
context.args = []
|
||||||
|
|
||||||
|
telegram._delete_trade(update=update, context=context)
|
||||||
|
assert "invalid argument" in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
msg_mock.reset_mock()
|
||||||
|
create_mock_trades(fee)
|
||||||
|
|
||||||
|
context = MagicMock()
|
||||||
|
context.args = [1]
|
||||||
|
telegram._delete_trade(update=update, context=context)
|
||||||
|
msg_mock.call_count == 1
|
||||||
|
assert "Deleted trade 1." in msg_mock.call_args_list[0][0][0]
|
||||||
|
assert "Please make sure to take care of this asset" in msg_mock.call_args_list[0][0][0]
|
||||||
|
|
||||||
|
|
||||||
def test_help_handle(default_conf, update, mocker) -> None:
|
def test_help_handle(default_conf, update, mocker) -> None:
|
||||||
msg_mock = MagicMock()
|
msg_mock = MagicMock()
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
|
Loading…
Reference in New Issue
Block a user