Update forcesell to work as forceexit

This commit is contained in:
Matthias 2022-01-26 20:17:00 +01:00
parent 066fb3ce00
commit 0a52d79208
6 changed files with 37 additions and 29 deletions

View File

@ -285,7 +285,7 @@ class ForceEnterPayload(BaseModel):
stakeamount: Optional[float] stakeamount: Optional[float]
class ForceSellPayload(BaseModel): class ForceExitPayload(BaseModel):
tradeid: str tradeid: str
ordertype: Optional[OrderTypeValues] ordertype: Optional[OrderTypeValues]

View File

@ -15,7 +15,7 @@ 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, BlacklistResponse, Count, Daily,
DeleteLockRequest, DeleteTrade, ForceEnterPayload, DeleteLockRequest, DeleteTrade, ForceEnterPayload,
ForceEnterResponse, ForceSellPayload, Locks, Logs, ForceEnterResponse, ForceExitPayload, Locks, Logs,
OpenTradeSchema, PairHistory, PerformanceEntry, OpenTradeSchema, PairHistory, PerformanceEntry,
Ping, PlotConfig, Profit, ResultMsg, ShowConfig, Ping, PlotConfig, Profit, ResultMsg, ShowConfig,
Stats, StatusMsg, StrategyListResponse, Stats, StatusMsg, StrategyListResponse,
@ -152,10 +152,11 @@ def forceentry(payload: ForceEnterPayload, rpc: RPC = Depends(get_rpc)):
{"status": f"Error entering {payload.side} trade for pair {payload.pair}."}) {"status": f"Error entering {payload.side} trade for pair {payload.pair}."})
@router.post('/forceexit', response_model=ResultMsg, tags=['trading'])
@router.post('/forcesell', response_model=ResultMsg, tags=['trading']) @router.post('/forcesell', response_model=ResultMsg, tags=['trading'])
def forcesell(payload: ForceSellPayload, rpc: RPC = Depends(get_rpc)): def forcesell(payload: ForceExitPayload, rpc: RPC = Depends(get_rpc)):
ordertype = payload.ordertype.value if payload.ordertype else None ordertype = payload.ordertype.value if payload.ordertype else None
return rpc._rpc_forcesell(payload.tradeid, ordertype) return rpc._rpc_forceexit(payload.tradeid, ordertype)
@router.get('/blacklist', response_model=BlacklistResponse, tags=['info', 'pairlist']) @router.get('/blacklist', response_model=BlacklistResponse, tags=['info', 'pairlist'])

View File

@ -658,7 +658,7 @@ class RPC:
return {'status': 'No more buy will occur from now. Run /reload_config to reset.'} return {'status': 'No more buy will occur from now. Run /reload_config to reset.'}
def _rpc_forcesell(self, trade_id: str, ordertype: Optional[str] = None) -> Dict[str, str]: def _rpc_forceexit(self, trade_id: str, ordertype: Optional[str] = None) -> Dict[str, str]:
""" """
Handler for forcesell <id>. Handler for forcesell <id>.
Sells the given trade at current price Sells the given trade at current price

View File

@ -25,6 +25,7 @@ from freqtrade.__init__ import __version__
from freqtrade.constants import DUST_PER_COIN from freqtrade.constants import DUST_PER_COIN
from freqtrade.enums import RPCMessageType from freqtrade.enums import RPCMessageType
from freqtrade.enums.signaltype import SignalDirection from freqtrade.enums.signaltype import SignalDirection
from freqtrade.enums.tradingmode import TradingMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.misc import chunks, plural, round_coin_value from freqtrade.misc import chunks, plural, round_coin_value
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
@ -115,7 +116,8 @@ class Telegram(RPCHandler):
r'/stopbuy$', r'/reload_config$', r'/show_config$', r'/stopbuy$', r'/reload_config$', r'/show_config$',
r'/logs$', r'/whitelist$', r'/blacklist$', r'/bl_delete$', 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'/edge$', r'/help$', r'/version$'] r'/forcebuy$', r'/forcelong$', r'/forceshort$',
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]
@ -152,7 +154,7 @@ class Telegram(RPCHandler):
CommandHandler('balance', self._balance), CommandHandler('balance', self._balance),
CommandHandler('start', self._start), CommandHandler('start', self._start),
CommandHandler('stop', self._stop), CommandHandler('stop', self._stop),
CommandHandler('forcesell', self._forcesell), CommandHandler(['forcesell', 'forceexit'], self._forceexit),
CommandHandler(['forcebuy', 'forcelong'], partial(self._forcebuy, order_side=SignalDirection.LONG)), CommandHandler(['forcebuy', 'forcelong'], partial(self._forcebuy, order_side=SignalDirection.LONG)),
CommandHandler('forceshort', partial(self._forcebuy, order_side=SignalDirection.SHORT)), CommandHandler('forceshort', partial(self._forcebuy, order_side=SignalDirection.SHORT)),
CommandHandler('trades', self._trades), CommandHandler('trades', self._trades),
@ -849,7 +851,7 @@ class Telegram(RPCHandler):
self._send_msg('Status: `{status}`'.format(**msg)) self._send_msg('Status: `{status}`'.format(**msg))
@authorized_only @authorized_only
def _forcesell(self, update: Update, context: CallbackContext) -> None: def _forceexit(self, update: Update, context: CallbackContext) -> None:
""" """
Handler for /forcesell <id>. Handler for /forcesell <id>.
Sells the given trade at current price Sells the given trade at current price
@ -863,7 +865,7 @@ class Telegram(RPCHandler):
self._send_msg("You must specify a trade-id or 'all'.") self._send_msg("You must specify a trade-id or 'all'.")
return return
try: try:
msg = self._rpc._rpc_forcesell(trade_id) msg = self._rpc._rpc_forceexit(trade_id)
self._send_msg('Forcesell Result: `{result}`'.format(**msg)) self._send_msg('Forcesell Result: `{result}`'.format(**msg))
except RPCException as e: except RPCException as e:
@ -1279,16 +1281,21 @@ class Telegram(RPCHandler):
:param update: message update :param update: message update
:return: None :return: None
""" """
forcebuy_text = ("*/forcebuy <pair> [<rate>]:* `Instantly buys the given pair. " forcebuy_text = ("*/forcelong <pair> [<rate>]:* `Instantly buys the given pair. "
"Optionally takes a rate at which to buy " "Optionally takes a rate at which to buy "
"(only applies to limit orders).` \n") "(only applies to limit orders).` \n"
)
if self._rpc_._freqtrade.trading_mode != TradingMode.SPOT:
forcebuy_text += ("*/forceshort <pair> [<rate>]:* `Instantly shorts the given pair. "
"Optionally takes a rate at which to sell "
"(only applies to limit orders).` \n")
message = ( message = (
"_BotControl_\n" "_BotControl_\n"
"------------\n" "------------\n"
"*/start:* `Starts the trader`\n" "*/start:* `Starts the trader`\n"
"*/stop:* Stops the trader\n" "*/stop:* Stops the trader\n"
"*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" "*/stopbuy:* `Stops buying, but handles open trades gracefully` \n"
"*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, " "*/forceexit <trade_id>|all:* `Instantly exits 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" "*/delete <trade_id>:* `Instantly delete the given trade in the database`\n"

View File

@ -687,7 +687,7 @@ def test_rpc_stopbuy(mocker, default_conf) -> None:
assert freqtradebot.config['max_open_trades'] == 0 assert freqtradebot.config['max_open_trades'] == 0
def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None: def test_rpc_forceexit(default_conf, ticker, fee, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock()) mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
cancel_order_mock = MagicMock() cancel_order_mock = MagicMock()
@ -714,29 +714,29 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
with pytest.raises(RPCException, match=r'.*trader is not running*'): with pytest.raises(RPCException, match=r'.*trader is not running*'):
rpc._rpc_forcesell(None) rpc._rpc_forceexit(None)
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
with pytest.raises(RPCException, match=r'.*invalid argument*'): with pytest.raises(RPCException, match=r'.*invalid argument*'):
rpc._rpc_forcesell(None) rpc._rpc_forceexit(None)
msg = rpc._rpc_forcesell('all') msg = rpc._rpc_forceexit('all')
assert msg == {'result': 'Created sell orders for all open trades.'} assert msg == {'result': 'Created sell orders for all open trades.'}
freqtradebot.enter_positions() freqtradebot.enter_positions()
msg = rpc._rpc_forcesell('all') msg = rpc._rpc_forceexit('all')
assert msg == {'result': 'Created sell orders for all open trades.'} assert msg == {'result': 'Created sell orders for all open trades.'}
freqtradebot.enter_positions() freqtradebot.enter_positions()
msg = rpc._rpc_forcesell('2') msg = rpc._rpc_forceexit('2')
assert msg == {'result': 'Created sell order for trade 2.'} assert msg == {'result': 'Created sell order for trade 2.'}
freqtradebot.state = State.STOPPED freqtradebot.state = State.STOPPED
with pytest.raises(RPCException, match=r'.*trader is not running*'): with pytest.raises(RPCException, match=r'.*trader is not running*'):
rpc._rpc_forcesell(None) rpc._rpc_forceexit(None)
with pytest.raises(RPCException, match=r'.*trader is not running*'): with pytest.raises(RPCException, match=r'.*trader is not running*'):
rpc._rpc_forcesell('all') rpc._rpc_forceexit('all')
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
assert cancel_order_mock.call_count == 0 assert cancel_order_mock.call_count == 0
@ -765,7 +765,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
) )
# check that the trade is called, which is done by ensuring exchange.cancel_order is called # check that the trade is called, which is done by ensuring exchange.cancel_order is called
# and trade amount is updated # and trade amount is updated
rpc._rpc_forcesell('3') rpc._rpc_forceexit('3')
assert cancel_order_mock.call_count == 1 assert cancel_order_mock.call_count == 1
assert trade.amount == filled_amount assert trade.amount == filled_amount
@ -793,7 +793,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
} }
) )
# check that the trade is called, which is done by ensuring exchange.cancel_order is called # check that the trade is called, which is done by ensuring exchange.cancel_order is called
msg = rpc._rpc_forcesell('4') msg = rpc._rpc_forceexit('4')
assert msg == {'result': 'Created sell order for trade 4.'} assert msg == {'result': 'Created sell order for trade 4.'}
assert cancel_order_mock.call_count == 2 assert cancel_order_mock.call_count == 2
assert trade.amount == amount assert trade.amount == amount
@ -810,7 +810,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
'filled': 0.0 'filled': 0.0
} }
) )
msg = rpc._rpc_forcesell('3') msg = rpc._rpc_forceexit('3')
assert msg == {'result': 'Created sell order for trade 3.'} assert msg == {'result': 'Created sell order for trade 3.'}
# status quo, no exchange calls # status quo, no exchange calls
assert cancel_order_mock.call_count == 3 assert cancel_order_mock.call_count == 3

View File

@ -940,7 +940,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
# /forcesell 1 # /forcesell 1
context = MagicMock() context = MagicMock()
context.args = ["1"] context.args = ["1"]
telegram._forcesell(update=update, context=context) telegram._forceexit(update=update, context=context)
assert msg_mock.call_count == 4 assert msg_mock.call_count == 4
last_msg = msg_mock.call_args_list[-2][0][0] last_msg = msg_mock.call_args_list[-2][0][0]
@ -1007,7 +1007,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
# /forcesell 1 # /forcesell 1
context = MagicMock() context = MagicMock()
context.args = ["1"] context.args = ["1"]
telegram._forcesell(update=update, context=context) telegram._forceexit(update=update, context=context)
assert msg_mock.call_count == 4 assert msg_mock.call_count == 4
@ -1065,7 +1065,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
# /forcesell all # /forcesell all
context = MagicMock() context = MagicMock()
context.args = ["all"] context.args = ["all"]
telegram._forcesell(update=update, context=context) telegram._forceexit(update=update, context=context)
# Called for each trade 2 times # Called for each trade 2 times
assert msg_mock.call_count == 8 assert msg_mock.call_count == 8
@ -1109,7 +1109,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
# /forcesell 1 # /forcesell 1
context = MagicMock() context = MagicMock()
context.args = ["1"] context.args = ["1"]
telegram._forcesell(update=update, context=context) telegram._forceexit(update=update, context=context)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert 'not running' in msg_mock.call_args_list[0][0][0] assert 'not running' in msg_mock.call_args_list[0][0][0]
@ -1118,7 +1118,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
freqtradebot.state = State.RUNNING freqtradebot.state = State.RUNNING
context = MagicMock() context = MagicMock()
context.args = [] context.args = []
telegram._forcesell(update=update, context=context) telegram._forceexit(update=update, context=context)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert "You must specify a trade-id or 'all'." in msg_mock.call_args_list[0][0][0] assert "You must specify a trade-id or 'all'." in msg_mock.call_args_list[0][0][0]
@ -1128,7 +1128,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
# /forcesell 123456 # /forcesell 123456
context = MagicMock() context = MagicMock()
context.args = ["123456"] context.args = ["123456"]
telegram._forcesell(update=update, context=context) telegram._forceexit(update=update, context=context)
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
assert 'invalid argument' in msg_mock.call_args_list[0][0][0] assert 'invalid argument' in msg_mock.call_args_list[0][0][0]