Make execute_sell() use SellCheckTuple for sell reason.

This commit is contained in:
Rokas Kupstys 2021-04-21 10:11:54 +03:00
parent a90e795695
commit bfad4e82ad
4 changed files with 37 additions and 26 deletions

View File

@ -28,7 +28,7 @@ from freqtrade.plugins.protectionmanager import ProtectionManager
from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.rpc import RPCManager, RPCMessageType
from freqtrade.state import State from freqtrade.state import State
from freqtrade.strategy.interface import IStrategy, SellType from freqtrade.strategy.interface import IStrategy, SellCheckTuple, SellType
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from freqtrade.wallets import Wallets from freqtrade.wallets import Wallets
@ -850,7 +850,8 @@ class FreqtradeBot(LoggingMixin):
trade.stoploss_order_id = None trade.stoploss_order_id = None
logger.error(f'Unable to place a stoploss order on exchange. {e}') logger.error(f'Unable to place a stoploss order on exchange. {e}')
logger.warning('Selling the trade forcefully') logger.warning('Selling the trade forcefully')
self.execute_sell(trade, trade.stop_loss, sell_reason=SellType.EMERGENCY_SELL) self.execute_sell(trade, trade.stop_loss, sell_reason=SellCheckTuple(
sell_flag=True, sell_type=SellType.EMERGENCY_SELL))
except ExchangeError: except ExchangeError:
trade.stoploss_order_id = None trade.stoploss_order_id = None
@ -961,7 +962,7 @@ class FreqtradeBot(LoggingMixin):
if should_sell.sell_flag: if should_sell.sell_flag:
logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}') logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}')
self.execute_sell(trade, sell_rate, should_sell.sell_type, should_sell.sell_reason) self.execute_sell(trade, sell_rate, should_sell)
return True return True
return False return False
@ -1150,8 +1151,7 @@ class FreqtradeBot(LoggingMixin):
raise DependencyException( raise DependencyException(
f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}") f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}")
def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType, def execute_sell(self, trade: Trade, limit: float, sell_reason: SellCheckTuple) -> bool:
custom_reason: Optional[str] = None) -> bool:
""" """
Executes a limit sell for the given trade and limit Executes a limit sell for the given trade and limit
:param trade: Trade instance :param trade: Trade instance
@ -1162,7 +1162,7 @@ class FreqtradeBot(LoggingMixin):
:return: True if it succeeds (supported) False (not supported) :return: True if it succeeds (supported) False (not supported)
""" """
sell_type = 'sell' sell_type = 'sell'
if sell_reason in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): if sell_reason.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
sell_type = 'stoploss' sell_type = 'stoploss'
# if stoploss is on exchange and we are on dry_run mode, # if stoploss is on exchange and we are on dry_run mode,
@ -1179,10 +1179,10 @@ class FreqtradeBot(LoggingMixin):
logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}") logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}")
order_type = self.strategy.order_types[sell_type] order_type = self.strategy.order_types[sell_type]
if sell_reason == SellType.EMERGENCY_SELL: if sell_reason.sell_type == SellType.EMERGENCY_SELL:
# Emergency sells (default to market!) # Emergency sells (default to market!)
order_type = self.strategy.order_types.get("emergencysell", "market") order_type = self.strategy.order_types.get("emergencysell", "market")
if sell_reason == SellType.FORCE_SELL: if sell_reason.sell_type == SellType.FORCE_SELL:
# Force sells (default to the sell_type defined in the strategy, # Force sells (default to the sell_type defined in the strategy,
# but we allow this value to be changed) # but we allow this value to be changed)
order_type = self.strategy.order_types.get("forcesell", order_type) order_type = self.strategy.order_types.get("forcesell", order_type)
@ -1193,7 +1193,7 @@ class FreqtradeBot(LoggingMixin):
if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)( if not strategy_safe_wrapper(self.strategy.confirm_trade_exit, default_retval=True)(
pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit, pair=trade.pair, trade=trade, order_type=order_type, amount=amount, rate=limit,
time_in_force=time_in_force, time_in_force=time_in_force,
sell_reason=sell_reason.value): sell_reason=sell_reason.sell_type.value):
logger.info(f"User requested abortion of selling {trade.pair}") logger.info(f"User requested abortion of selling {trade.pair}")
return False return False
@ -1216,7 +1216,7 @@ class FreqtradeBot(LoggingMixin):
trade.open_order_id = order['id'] trade.open_order_id = order['id']
trade.sell_order_status = '' trade.sell_order_status = ''
trade.close_rate_requested = limit trade.close_rate_requested = limit
trade.sell_reason = custom_reason or sell_reason.value trade.sell_reason = sell_reason.sell_reason
# In case of market sell orders the order can be closed immediately # In case of market sell orders the order can be closed immediately
if order.get('status', 'unknown') == 'closed': if order.get('status', 'unknown') == 'closed':
self.update_trade_state(trade, trade.open_order_id, order) self.update_trade_state(trade, trade.open_order_id, order)

View File

@ -24,7 +24,7 @@ from freqtrade.persistence.models import PairLock
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
from freqtrade.state import State from freqtrade.state import State
from freqtrade.strategy.interface import SellType from freqtrade.strategy.interface import SellCheckTuple, SellType
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -554,7 +554,8 @@ class RPC:
if not fully_canceled: if not fully_canceled:
# Get current rate and execute sell # Get current rate and execute sell
current_rate = self._freqtrade.get_sell_rate(trade.pair, False) current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
self._freqtrade.execute_sell(trade, current_rate, SellType.FORCE_SELL) sell_reason = SellCheckTuple(sell_flag=True, sell_type=SellType.FORCE_SELL)
self._freqtrade.execute_sell(trade, current_rate, sell_reason)
# ---- EOF def _exec_forcesell ---- # ---- EOF def _exec_forcesell ----
if self._freqtrade.state != State.RUNNING: if self._freqtrade.state != State.RUNNING:

View File

@ -7,7 +7,7 @@ import warnings
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from enum import Enum from enum import Enum
from typing import Dict, List, NamedTuple, Optional, Tuple, Union from typing import Dict, List, Optional, Tuple, Union
import arrow import arrow
from pandas import DataFrame from pandas import DataFrame
@ -54,13 +54,18 @@ class SellType(Enum):
return self.value return self.value
class SellCheckTuple(NamedTuple): class SellCheckTuple(object):
""" """
NamedTuple for Sell type + reason NamedTuple for Sell type + reason
""" """
sell_flag: bool sell_flag: bool # TODO: Remove?
sell_type: SellType sell_type: SellType
sell_reason: Optional[str] = None sell_reason: Optional[str]
def __init__(self, sell_flag: bool, sell_type: SellType, sell_reason: Optional[str] = None):
self.sell_flag = sell_flag
self.sell_type = sell_type
self.sell_reason = sell_reason or sell_type.value
class IStrategy(ABC, HyperStrategyMixin): class IStrategy(ABC, HyperStrategyMixin):
@ -594,7 +599,8 @@ class IStrategy(ABC, HyperStrategyMixin):
if sell_signal != SellType.NONE: if sell_signal != SellType.NONE:
logger.debug(f"{trade.pair} - Sell signal received. sell_flag=True, " logger.debug(f"{trade.pair} - Sell signal received. sell_flag=True, "
f"sell_type={sell_signal}, custom_reason={custom_reason}") f"sell_type=SellType.{sell_signal.name}" +
(f", custom_reason={custom_reason}" if custom_reason else ""))
return SellCheckTuple(sell_flag=True, sell_type=sell_signal, sell_reason=custom_reason) return SellCheckTuple(sell_flag=True, sell_type=sell_signal, sell_reason=custom_reason)
if stoplossflag.sell_flag: if stoplossflag.sell_flag:

View File

@ -2606,14 +2606,16 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
fetch_ticker=ticker_sell_up fetch_ticker=ticker_sell_up
) )
# Prevented sell ... # Prevented sell ...
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sell_reason=SellType.ROI) freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
sell_reason=SellCheckTuple(sell_flag=True, sell_type=SellType.ROI))
assert rpc_mock.call_count == 0 assert rpc_mock.call_count == 0
assert freqtrade.strategy.confirm_trade_exit.call_count == 1 assert freqtrade.strategy.confirm_trade_exit.call_count == 1
# Repatch with true # Repatch with true
freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True) freqtrade.strategy.confirm_trade_exit = MagicMock(return_value=True)
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sell_reason=SellType.ROI) freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
sell_reason=SellCheckTuple(sell_flag=True, sell_type=SellType.ROI))
assert freqtrade.strategy.confirm_trade_exit.call_count == 1 assert freqtrade.strategy.confirm_trade_exit.call_count == 1
assert rpc_mock.call_count == 1 assert rpc_mock.call_count == 1
@ -2665,7 +2667,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
) )
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
sell_reason=SellType.STOP_LOSS) sell_reason=SellCheckTuple(sell_flag=True, sell_type=SellType.STOP_LOSS))
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
last_msg = rpc_mock.call_args_list[-1][0][0] last_msg = rpc_mock.call_args_list[-1][0][0]
@ -2722,7 +2724,7 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
trade.stop_loss = 0.00001099 * 0.99 trade.stop_loss = 0.00001099 * 0.99
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
sell_reason=SellType.STOP_LOSS) sell_reason=SellCheckTuple(sell_flag=True, sell_type=SellType.STOP_LOSS))
assert rpc_mock.call_count == 2 assert rpc_mock.call_count == 2
last_msg = rpc_mock.call_args_list[-1][0][0] last_msg = rpc_mock.call_args_list[-1][0][0]
@ -2774,7 +2776,7 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, c
trade.stoploss_order_id = "abcd" trade.stoploss_order_id = "abcd"
freqtrade.execute_sell(trade=trade, limit=1234, freqtrade.execute_sell(trade=trade, limit=1234,
sell_reason=SellType.STOP_LOSS) sell_reason=SellCheckTuple(sell_flag=True, sell_type=SellType.STOP_LOSS))
assert sellmock.call_count == 1 assert sellmock.call_count == 1
assert log_has('Could not cancel stoploss order abcd', caplog) assert log_has('Could not cancel stoploss order abcd', caplog)
@ -2824,7 +2826,7 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke
) )
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
sell_reason=SellType.SELL_SIGNAL) sell_reason=SellCheckTuple(sell_flag=True, sell_type=SellType.STOP_LOSS))
trade = Trade.query.first() trade = Trade.query.first()
assert trade assert trade
@ -2929,7 +2931,8 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
) )
freqtrade.config['order_types']['sell'] = 'market' freqtrade.config['order_types']['sell'] = 'market'
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], sell_reason=SellType.ROI) freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
sell_reason=SellCheckTuple(sell_flag=True, sell_type=SellType.ROI))
assert not trade.is_open assert not trade.is_open
assert trade.close_profit == 0.0620716 assert trade.close_profit == 0.0620716
@ -2983,8 +2986,9 @@ def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee,
fetch_ticker=ticker_sell_up fetch_ticker=ticker_sell_up
) )
sell_reason = SellCheckTuple(sell_flag=True, sell_type=SellType.ROI)
assert not freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'], assert not freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
sell_reason=SellType.ROI) sell_reason=sell_reason)
assert mock_insuf.call_count == 1 assert mock_insuf.call_count == 1
@ -3226,7 +3230,7 @@ def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplo
) )
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'], freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
sell_reason=SellType.STOP_LOSS) sell_reason=SellCheckTuple(sell_flag=True, sell_type=SellType.STOP_LOSS))
trade.close(ticker_sell_down()['bid']) trade.close(ticker_sell_down()['bid'])
assert freqtrade.strategy.is_pair_locked(trade.pair) assert freqtrade.strategy.is_pair_locked(trade.pair)