Skip to content

Commit

Permalink
Make execute_sell() use SellCheckTuple for sell reason.
Browse files Browse the repository at this point in the history
  • Loading branch information
rokups committed Apr 25, 2021
1 parent a90e795 commit bfad4e8
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 26 deletions.
20 changes: 10 additions & 10 deletions freqtrade/freqtradebot.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.rpc import RPCManager, RPCMessageType
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.wallets import Wallets

Expand Down Expand Up @@ -850,7 +850,8 @@ def create_stoploss_order(self, trade: Trade, stop_price: float) -> bool:
trade.stoploss_order_id = None
logger.error(f'Unable to place a stoploss order on exchange. {e}')
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:
trade.stoploss_order_id = None
Expand Down Expand Up @@ -961,7 +962,7 @@ def _check_and_execute_sell(self, trade: Trade, sell_rate: float,

if should_sell.sell_flag:
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 False

Expand Down Expand Up @@ -1150,8 +1151,7 @@ def _safe_sell_amount(self, pair: str, amount: float) -> float:
raise DependencyException(
f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}")

def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType,
custom_reason: Optional[str] = None) -> bool:
def execute_sell(self, trade: Trade, limit: float, sell_reason: SellCheckTuple) -> bool:
"""
Executes a limit sell for the given trade and limit
:param trade: Trade instance
Expand All @@ -1162,7 +1162,7 @@ def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType,
:return: True if it succeeds (supported) False (not supported)
"""
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'

# if stoploss is on exchange and we are on dry_run mode,
Expand All @@ -1179,10 +1179,10 @@ def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType,
logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}")

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!)
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,
# but we allow this value to be changed)
order_type = self.strategy.order_types.get("forcesell", order_type)
Expand All @@ -1193,7 +1193,7 @@ def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType,
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,
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}")
return False

Expand All @@ -1216,7 +1216,7 @@ def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType,
trade.open_order_id = order['id']
trade.sell_order_status = ''
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
if order.get('status', 'unknown') == 'closed':
self.update_trade_state(trade, trade.open_order_id, order)
Expand Down
5 changes: 3 additions & 2 deletions freqtrade/rpc/rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
from freqtrade.state import State
from freqtrade.strategy.interface import SellType
from freqtrade.strategy.interface import SellCheckTuple, SellType


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -554,7 +554,8 @@ def _exec_forcesell(trade: Trade) -> None:
if not fully_canceled:
# Get current rate and execute sell
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 ----

if self._freqtrade.state != State.RUNNING:
Expand Down
16 changes: 11 additions & 5 deletions freqtrade/strategy/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from abc import ABC, abstractmethod
from datetime import datetime, timedelta, timezone
from enum import Enum
from typing import Dict, List, NamedTuple, Optional, Tuple, Union
from typing import Dict, List, Optional, Tuple, Union

import arrow
from pandas import DataFrame
Expand Down Expand Up @@ -54,13 +54,18 @@ def __str__(self):
return self.value


class SellCheckTuple(NamedTuple):
class SellCheckTuple(object):
"""
NamedTuple for Sell type + reason
"""
sell_flag: bool
sell_flag: bool # TODO: Remove?
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):
Expand Down Expand Up @@ -594,7 +599,8 @@ def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool,

if sell_signal != SellType.NONE:
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)

if stoplossflag.sell_flag:
Expand Down
22 changes: 13 additions & 9 deletions tests/test_freqtradebot.py
Original file line number Diff line number Diff line change
Expand Up @@ -2606,14 +2606,16 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
fetch_ticker=ticker_sell_up
)
# 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 freqtrade.strategy.confirm_trade_exit.call_count == 1

# Repatch with 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 rpc_mock.call_count == 1
Expand Down Expand Up @@ -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'],
sell_reason=SellType.STOP_LOSS)
sell_reason=SellCheckTuple(sell_flag=True, sell_type=SellType.STOP_LOSS))

assert rpc_mock.call_count == 2
last_msg = rpc_mock.call_args_list[-1][0][0]
Expand Down Expand Up @@ -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
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
last_msg = rpc_mock.call_args_list[-1][0][0]
Expand Down Expand Up @@ -2774,7 +2776,7 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, c
trade.stoploss_order_id = "abcd"

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 log_has('Could not cancel stoploss order abcd', caplog)

Expand Down Expand Up @@ -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'],
sell_reason=SellType.SELL_SIGNAL)
sell_reason=SellCheckTuple(sell_flag=True, sell_type=SellType.STOP_LOSS))

trade = Trade.query.first()
assert trade
Expand Down Expand Up @@ -2929,7 +2931,8 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
)
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 trade.close_profit == 0.0620716
Expand Down Expand Up @@ -2983,8 +2986,9 @@ def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee,
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'],
sell_reason=SellType.ROI)
sell_reason=sell_reason)
assert mock_insuf.call_count == 1


Expand Down Expand Up @@ -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'],
sell_reason=SellType.STOP_LOSS)
sell_reason=SellCheckTuple(sell_flag=True, sell_type=SellType.STOP_LOSS))
trade.close(ticker_sell_down()['bid'])
assert freqtrade.strategy.is_pair_locked(trade.pair)

Expand Down

0 comments on commit bfad4e8

Please sign in to comment.