Update forcesell to work as forceexit
This commit is contained in:
parent
066fb3ce00
commit
0a52d79208
@ -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]
|
||||||
|
|
||||||
|
@ -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'])
|
||||||
|
@ -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
|
||||||
|
@ -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,8 +1281,13 @@ 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"
|
||||||
|
)
|
||||||
|
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")
|
"(only applies to limit orders).` \n")
|
||||||
message = (
|
message = (
|
||||||
"_BotControl_\n"
|
"_BotControl_\n"
|
||||||
@ -1288,7 +1295,7 @@ class Telegram(RPCHandler):
|
|||||||
"*/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"
|
||||||
|
@ -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
|
||||||
|
@ -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]
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user