Update SellCheckTuple to new naming
This commit is contained in:
parent
62e8c7b5b7
commit
8d111d357a
@ -27,7 +27,7 @@ from freqtrade.plugins.pairlistmanager import PairListManager
|
|||||||
from freqtrade.plugins.protectionmanager import ProtectionManager
|
from freqtrade.plugins.protectionmanager import ProtectionManager
|
||||||
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
||||||
from freqtrade.rpc import RPCManager
|
from freqtrade.rpc import RPCManager
|
||||||
from freqtrade.strategy.interface import IStrategy, SellCheckTuple
|
from freqtrade.strategy.interface import IStrategy, ExitCheckTuple
|
||||||
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
|
||||||
|
|
||||||
@ -976,8 +976,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('Exiting the trade forcefully')
|
logger.warning('Exiting the trade forcefully')
|
||||||
self.execute_trade_exit(trade, trade.stop_loss, exit_check=SellCheckTuple(
|
self.execute_trade_exit(trade, trade.stop_loss, exit_check=ExitCheckTuple(
|
||||||
sell_type=SellType.EMERGENCY_SELL))
|
exit_type=SellType.EMERGENCY_SELL))
|
||||||
|
|
||||||
except ExchangeError:
|
except ExchangeError:
|
||||||
trade.stoploss_order_id = None
|
trade.stoploss_order_id = None
|
||||||
@ -1101,7 +1101,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
"""
|
"""
|
||||||
Check and execute trade exit
|
Check and execute trade exit
|
||||||
"""
|
"""
|
||||||
should_exit: SellCheckTuple = self.strategy.should_exit(
|
should_exit: ExitCheckTuple = self.strategy.should_exit(
|
||||||
trade,
|
trade,
|
||||||
exit_rate,
|
exit_rate,
|
||||||
datetime.now(timezone.utc),
|
datetime.now(timezone.utc),
|
||||||
@ -1110,8 +1110,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0
|
force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0
|
||||||
)
|
)
|
||||||
|
|
||||||
if should_exit.sell_flag:
|
if should_exit.exit_flag:
|
||||||
logger.info(f'Exit for {trade.pair} detected. Reason: {should_exit.sell_type}'
|
logger.info(f'Exit for {trade.pair} detected. Reason: {should_exit.exit_type}'
|
||||||
f'Tag: {exit_tag if exit_tag is not None else "None"}')
|
f'Tag: {exit_tag if exit_tag is not None else "None"}')
|
||||||
self.execute_trade_exit(trade, exit_rate, should_exit, exit_tag=exit_tag)
|
self.execute_trade_exit(trade, exit_rate, should_exit, exit_tag=exit_tag)
|
||||||
return True
|
return True
|
||||||
@ -1158,7 +1158,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
try:
|
try:
|
||||||
self.execute_trade_exit(
|
self.execute_trade_exit(
|
||||||
trade, order.get('price'),
|
trade, order.get('price'),
|
||||||
exit_check=SellCheckTuple(sell_type=SellType.EMERGENCY_SELL))
|
exit_check=ExitCheckTuple(exit_type=SellType.EMERGENCY_SELL))
|
||||||
except DependencyException as exception:
|
except DependencyException as exception:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
f'Unable to emergency sell trade {trade.pair}: {exception}')
|
f'Unable to emergency sell trade {trade.pair}: {exception}')
|
||||||
@ -1333,7 +1333,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
self,
|
self,
|
||||||
trade: Trade,
|
trade: Trade,
|
||||||
limit: float,
|
limit: float,
|
||||||
exit_check: SellCheckTuple,
|
exit_check: ExitCheckTuple,
|
||||||
*,
|
*,
|
||||||
exit_tag: Optional[str] = None,
|
exit_tag: Optional[str] = None,
|
||||||
ordertype: Optional[str] = None,
|
ordertype: Optional[str] = None,
|
||||||
@ -1352,7 +1352,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
open_date=trade.open_date,
|
open_date=trade.open_date,
|
||||||
)
|
)
|
||||||
exit_type = 'exit'
|
exit_type = 'exit'
|
||||||
if exit_check.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
|
if exit_check.exit_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
|
||||||
exit_type = 'stoploss'
|
exit_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,
|
||||||
@ -1376,7 +1376,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
trade = self.cancel_stoploss_on_exchange(trade)
|
trade = self.cancel_stoploss_on_exchange(trade)
|
||||||
|
|
||||||
order_type = ordertype or self.strategy.order_types[exit_type]
|
order_type = ordertype or self.strategy.order_types[exit_type]
|
||||||
if exit_check.sell_type == SellType.EMERGENCY_SELL:
|
if exit_check.exit_type == SellType.EMERGENCY_SELL:
|
||||||
# Emergency sells (default to market!)
|
# Emergency sells (default to market!)
|
||||||
order_type = self.strategy.order_types.get("emergencyexit", "market")
|
order_type = self.strategy.order_types.get("emergencyexit", "market")
|
||||||
|
|
||||||
@ -1385,8 +1385,8 @@ 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, exit_reason=exit_check.sell_reason,
|
time_in_force=time_in_force, exit_reason=exit_check.exit_reason,
|
||||||
sell_reason=exit_check.sell_reason, # sellreason -> compatibility
|
sell_reason=exit_check.exit_reason, # sellreason -> compatibility
|
||||||
current_time=datetime.now(timezone.utc)):
|
current_time=datetime.now(timezone.utc)):
|
||||||
logger.info(f"User requested abortion of exiting {trade.pair}")
|
logger.info(f"User requested abortion of exiting {trade.pair}")
|
||||||
return False
|
return False
|
||||||
@ -1415,7 +1415,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 = exit_tag or exit_check.sell_reason
|
trade.sell_reason = exit_tag or exit_check.exit_reason
|
||||||
|
|
||||||
# Lock pair for one candle to prevent immediate re-trading
|
# Lock pair for one candle to prevent immediate re-trading
|
||||||
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
|
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
|
||||||
|
@ -31,7 +31,7 @@ from freqtrade.persistence import LocalTrade, Order, PairLocks, Trade
|
|||||||
from freqtrade.plugins.pairlistmanager import PairListManager
|
from freqtrade.plugins.pairlistmanager import PairListManager
|
||||||
from freqtrade.plugins.protectionmanager import ProtectionManager
|
from freqtrade.plugins.protectionmanager import ProtectionManager
|
||||||
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
||||||
from freqtrade.strategy.interface import IStrategy, SellCheckTuple
|
from freqtrade.strategy.interface import IStrategy, ExitCheckTuple
|
||||||
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
|
||||||
|
|
||||||
@ -352,20 +352,20 @@ class Backtesting:
|
|||||||
data[pair] = df_analyzed[headers].values.tolist()
|
data[pair] = df_analyzed[headers].values.tolist()
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def _get_close_rate(self, sell_row: Tuple, trade: LocalTrade, sell: SellCheckTuple,
|
def _get_close_rate(self, sell_row: Tuple, trade: LocalTrade, sell: ExitCheckTuple,
|
||||||
trade_dur: int) -> float:
|
trade_dur: int) -> float:
|
||||||
"""
|
"""
|
||||||
Get close rate for backtesting result
|
Get close rate for backtesting result
|
||||||
"""
|
"""
|
||||||
# Special handling if high or low hit STOP_LOSS or ROI
|
# Special handling if high or low hit STOP_LOSS or ROI
|
||||||
if sell.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
|
if sell.exit_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
|
||||||
return self._get_close_rate_for_stoploss(sell_row, trade, sell, trade_dur)
|
return self._get_close_rate_for_stoploss(sell_row, trade, sell, trade_dur)
|
||||||
elif sell.sell_type == (SellType.ROI):
|
elif sell.exit_type == (SellType.ROI):
|
||||||
return self._get_close_rate_for_roi(sell_row, trade, sell, trade_dur)
|
return self._get_close_rate_for_roi(sell_row, trade, sell, trade_dur)
|
||||||
else:
|
else:
|
||||||
return sell_row[OPEN_IDX]
|
return sell_row[OPEN_IDX]
|
||||||
|
|
||||||
def _get_close_rate_for_stoploss(self, sell_row: Tuple, trade: LocalTrade, sell: SellCheckTuple,
|
def _get_close_rate_for_stoploss(self, sell_row: Tuple, trade: LocalTrade, sell: ExitCheckTuple,
|
||||||
trade_dur: int) -> float:
|
trade_dur: int) -> float:
|
||||||
# our stoploss was already lower than candle high,
|
# our stoploss was already lower than candle high,
|
||||||
# possibly due to a cancelled trade exit.
|
# possibly due to a cancelled trade exit.
|
||||||
@ -383,7 +383,7 @@ class Backtesting:
|
|||||||
# Special case: trailing triggers within same candle as trade opened. Assume most
|
# Special case: trailing triggers within same candle as trade opened. Assume most
|
||||||
# pessimistic price movement, which is moving just enough to arm stoploss and
|
# pessimistic price movement, which is moving just enough to arm stoploss and
|
||||||
# immediately going down to stop price.
|
# immediately going down to stop price.
|
||||||
if sell.sell_type == SellType.TRAILING_STOP_LOSS and trade_dur == 0:
|
if sell.exit_type == SellType.TRAILING_STOP_LOSS and trade_dur == 0:
|
||||||
if (
|
if (
|
||||||
not self.strategy.use_custom_stoploss and self.strategy.trailing_stop
|
not self.strategy.use_custom_stoploss and self.strategy.trailing_stop
|
||||||
and self.strategy.trailing_only_offset_is_reached
|
and self.strategy.trailing_only_offset_is_reached
|
||||||
@ -413,7 +413,7 @@ class Backtesting:
|
|||||||
# Set close_rate to stoploss
|
# Set close_rate to stoploss
|
||||||
return trade.stop_loss
|
return trade.stop_loss
|
||||||
|
|
||||||
def _get_close_rate_for_roi(self, sell_row: Tuple, trade: LocalTrade, sell: SellCheckTuple,
|
def _get_close_rate_for_roi(self, sell_row: Tuple, trade: LocalTrade, sell: ExitCheckTuple,
|
||||||
trade_dur: int) -> float:
|
trade_dur: int) -> float:
|
||||||
is_short = trade.is_short or False
|
is_short = trade.is_short or False
|
||||||
leverage = trade.leverage or 1.0
|
leverage = trade.leverage or 1.0
|
||||||
@ -521,7 +521,7 @@ class Backtesting:
|
|||||||
low=sell_row[LOW_IDX], high=sell_row[HIGH_IDX]
|
low=sell_row[LOW_IDX], high=sell_row[HIGH_IDX]
|
||||||
)
|
)
|
||||||
|
|
||||||
if sell.sell_flag:
|
if sell.exit_flag:
|
||||||
trade.close_date = sell_candle_time
|
trade.close_date = sell_candle_time
|
||||||
|
|
||||||
trade_dur = int((trade.close_date_utc - trade.open_date_utc).total_seconds() // 60)
|
trade_dur = int((trade.close_date_utc - trade.open_date_utc).total_seconds() // 60)
|
||||||
@ -532,7 +532,7 @@ class Backtesting:
|
|||||||
# call the custom exit price,with default value as previous closerate
|
# call the custom exit price,with default value as previous closerate
|
||||||
current_profit = trade.calc_profit_ratio(closerate)
|
current_profit = trade.calc_profit_ratio(closerate)
|
||||||
order_type = self.strategy.order_types['exit']
|
order_type = self.strategy.order_types['exit']
|
||||||
if sell.sell_type in (SellType.SELL_SIGNAL, SellType.CUSTOM_SELL):
|
if sell.exit_type in (SellType.SELL_SIGNAL, SellType.CUSTOM_SELL):
|
||||||
# Custom exit pricing only for sell-signals
|
# Custom exit pricing only for sell-signals
|
||||||
if order_type == 'limit':
|
if order_type == 'limit':
|
||||||
closerate = strategy_safe_wrapper(self.strategy.custom_exit_price,
|
closerate = strategy_safe_wrapper(self.strategy.custom_exit_price,
|
||||||
@ -553,12 +553,12 @@ class Backtesting:
|
|||||||
pair=trade.pair, trade=trade, order_type='limit', amount=trade.amount,
|
pair=trade.pair, trade=trade, order_type='limit', amount=trade.amount,
|
||||||
rate=closerate,
|
rate=closerate,
|
||||||
time_in_force=time_in_force,
|
time_in_force=time_in_force,
|
||||||
sell_reason=sell.sell_reason, # deprecated
|
sell_reason=sell.exit_reason, # deprecated
|
||||||
exit_reason=sell.sell_reason,
|
exit_reason=sell.exit_reason,
|
||||||
current_time=sell_candle_time):
|
current_time=sell_candle_time):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
trade.sell_reason = sell.sell_reason
|
trade.sell_reason = sell.exit_reason
|
||||||
|
|
||||||
# Checks and adds an exit tag, after checking that the length of the
|
# Checks and adds an exit tag, after checking that the length of the
|
||||||
# sell_row has the length for an exit tag column
|
# sell_row has the length for an exit tag column
|
||||||
|
@ -27,7 +27,7 @@ from freqtrade.persistence import PairLocks, Trade
|
|||||||
from freqtrade.persistence.models import PairLock
|
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.strategy.interface import SellCheckTuple
|
from freqtrade.strategy.interface import ExitCheckTuple
|
||||||
from freqtrade.wallets import PositionWallet, Wallet
|
from freqtrade.wallets import PositionWallet, Wallet
|
||||||
|
|
||||||
|
|
||||||
@ -707,7 +707,7 @@ class RPC:
|
|||||||
# Get current rate and execute sell
|
# Get current rate and execute sell
|
||||||
current_rate = self._freqtrade.exchange.get_rate(
|
current_rate = self._freqtrade.exchange.get_rate(
|
||||||
trade.pair, refresh=False, side=trade.exit_side)
|
trade.pair, refresh=False, side=trade.exit_side)
|
||||||
exit_check = SellCheckTuple(sell_type=SellType.FORCE_SELL)
|
exit_check = ExitCheckTuple(exit_type=SellType.FORCE_SELL)
|
||||||
order_type = ordertype or self._freqtrade.strategy.order_types.get(
|
order_type = ordertype or self._freqtrade.strategy.order_types.get(
|
||||||
"forceexit", self._freqtrade.strategy.order_types["exit"])
|
"forceexit", self._freqtrade.strategy.order_types["exit"])
|
||||||
|
|
||||||
|
@ -32,20 +32,20 @@ logger = logging.getLogger(__name__)
|
|||||||
CUSTOM_EXIT_MAX_LENGTH = 64
|
CUSTOM_EXIT_MAX_LENGTH = 64
|
||||||
|
|
||||||
|
|
||||||
class SellCheckTuple:
|
class ExitCheckTuple:
|
||||||
"""
|
"""
|
||||||
NamedTuple for Sell type + reason
|
NamedTuple for Exit type + reason
|
||||||
"""
|
"""
|
||||||
sell_type: SellType
|
exit_type: SellType
|
||||||
sell_reason: str = ''
|
exit_reason: str = ''
|
||||||
|
|
||||||
def __init__(self, sell_type: SellType, sell_reason: str = ''):
|
def __init__(self, exit_type: SellType, exit_reason: str = ''):
|
||||||
self.sell_type = sell_type
|
self.exit_type = exit_type
|
||||||
self.sell_reason = sell_reason or sell_type.value
|
self.exit_reason = exit_reason or exit_type.value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def sell_flag(self):
|
def exit_flag(self):
|
||||||
return self.sell_type != SellType.NONE
|
return self.exit_type != SellType.NONE
|
||||||
|
|
||||||
|
|
||||||
class IStrategy(ABC, HyperStrategyMixin):
|
class IStrategy(ABC, HyperStrategyMixin):
|
||||||
@ -848,7 +848,7 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
def should_exit(self, trade: Trade, rate: float, current_time: datetime, *,
|
def should_exit(self, trade: Trade, rate: float, current_time: datetime, *,
|
||||||
enter: bool, exit_: bool,
|
enter: bool, exit_: bool,
|
||||||
low: float = None, high: float = None,
|
low: float = None, high: float = None,
|
||||||
force_stoploss: float = 0) -> SellCheckTuple:
|
force_stoploss: float = 0) -> ExitCheckTuple:
|
||||||
"""
|
"""
|
||||||
This function evaluates if one of the conditions required to trigger an exit order
|
This function evaluates if one of the conditions required to trigger an exit order
|
||||||
has been reached, which can either be a stop-loss, ROI or exit-signal.
|
has been reached, which can either be a stop-loss, ROI or exit-signal.
|
||||||
@ -908,29 +908,29 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
logger.debug(f"{trade.pair} - Sell signal received. "
|
logger.debug(f"{trade.pair} - Sell signal received. "
|
||||||
f"sell_type=SellType.{sell_signal.name}" +
|
f"sell_type=SellType.{sell_signal.name}" +
|
||||||
(f", custom_reason={custom_reason}" if custom_reason else ""))
|
(f", custom_reason={custom_reason}" if custom_reason else ""))
|
||||||
return SellCheckTuple(sell_type=sell_signal, sell_reason=custom_reason)
|
return ExitCheckTuple(exit_type=sell_signal, exit_reason=custom_reason)
|
||||||
|
|
||||||
# Sequence:
|
# Sequence:
|
||||||
# Exit-signal
|
# Exit-signal
|
||||||
# ROI (if not stoploss)
|
# ROI (if not stoploss)
|
||||||
# Stoploss
|
# Stoploss
|
||||||
if roi_reached and stoplossflag.sell_type != SellType.STOP_LOSS:
|
if roi_reached and stoplossflag.exit_type != SellType.STOP_LOSS:
|
||||||
logger.debug(f"{trade.pair} - Required profit reached. sell_type=SellType.ROI")
|
logger.debug(f"{trade.pair} - Required profit reached. sell_type=SellType.ROI")
|
||||||
return SellCheckTuple(sell_type=SellType.ROI)
|
return ExitCheckTuple(exit_type=SellType.ROI)
|
||||||
|
|
||||||
if stoplossflag.sell_flag:
|
if stoplossflag.exit_flag:
|
||||||
|
|
||||||
logger.debug(f"{trade.pair} - Stoploss hit. sell_type={stoplossflag.sell_type}")
|
logger.debug(f"{trade.pair} - Stoploss hit. sell_type={stoplossflag.exit_type}")
|
||||||
return stoplossflag
|
return stoplossflag
|
||||||
|
|
||||||
# This one is noisy, commented out...
|
# This one is noisy, commented out...
|
||||||
# logger.debug(f"{trade.pair} - No exit signal.")
|
# logger.debug(f"{trade.pair} - No exit signal.")
|
||||||
return SellCheckTuple(sell_type=SellType.NONE)
|
return ExitCheckTuple(exit_type=SellType.NONE)
|
||||||
|
|
||||||
def stop_loss_reached(self, current_rate: float, trade: Trade,
|
def stop_loss_reached(self, current_rate: float, trade: Trade,
|
||||||
current_time: datetime, current_profit: float,
|
current_time: datetime, current_profit: float,
|
||||||
force_stoploss: float, low: float = None,
|
force_stoploss: float, low: float = None,
|
||||||
high: float = None) -> SellCheckTuple:
|
high: float = None) -> ExitCheckTuple:
|
||||||
"""
|
"""
|
||||||
Based on current profit of the trade and configured (trailing) stoploss,
|
Based on current profit of the trade and configured (trailing) stoploss,
|
||||||
decides to exit or not
|
decides to exit or not
|
||||||
@ -1008,9 +1008,9 @@ class IStrategy(ABC, HyperStrategyMixin):
|
|||||||
logger.debug(f"{trade.pair} - Trailing stop saved "
|
logger.debug(f"{trade.pair} - Trailing stop saved "
|
||||||
f"{new_stoploss:.6f}")
|
f"{new_stoploss:.6f}")
|
||||||
|
|
||||||
return SellCheckTuple(sell_type=sell_type)
|
return ExitCheckTuple(exit_type=sell_type)
|
||||||
|
|
||||||
return SellCheckTuple(sell_type=SellType.NONE)
|
return ExitCheckTuple(exit_type=SellType.NONE)
|
||||||
|
|
||||||
def min_roi_reached_entry(self, trade_dur: int) -> Tuple[Optional[int], Optional[float]]:
|
def min_roi_reached_entry(self, trade_dur: int) -> Tuple[Optional[int], Optional[float]]:
|
||||||
"""
|
"""
|
||||||
|
@ -18,7 +18,7 @@ from freqtrade.persistence import PairLocks, Trade
|
|||||||
from freqtrade.resolvers import StrategyResolver
|
from freqtrade.resolvers import StrategyResolver
|
||||||
from freqtrade.strategy.hyper import (BaseParameter, BooleanParameter, CategoricalParameter,
|
from freqtrade.strategy.hyper import (BaseParameter, BooleanParameter, CategoricalParameter,
|
||||||
DecimalParameter, IntParameter, RealParameter)
|
DecimalParameter, IntParameter, RealParameter)
|
||||||
from freqtrade.strategy.interface import SellCheckTuple
|
from freqtrade.strategy.interface import ExitCheckTuple
|
||||||
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
|
||||||
from tests.conftest import CURRENT_TEST_STRATEGY, TRADE_SIDES, log_has, log_has_re
|
from tests.conftest import CURRENT_TEST_STRATEGY, TRADE_SIDES, log_has, log_has_re
|
||||||
|
|
||||||
@ -455,23 +455,23 @@ def test_stop_loss_reached(default_conf, fee, profit, adjusted, expected, traili
|
|||||||
sl_flag = strategy.stop_loss_reached(current_rate=current_rate, trade=trade,
|
sl_flag = strategy.stop_loss_reached(current_rate=current_rate, trade=trade,
|
||||||
current_time=now, current_profit=profit,
|
current_time=now, current_profit=profit,
|
||||||
force_stoploss=0, high=None)
|
force_stoploss=0, high=None)
|
||||||
assert isinstance(sl_flag, SellCheckTuple)
|
assert isinstance(sl_flag, ExitCheckTuple)
|
||||||
assert sl_flag.sell_type == expected
|
assert sl_flag.exit_type == expected
|
||||||
if expected == SellType.NONE:
|
if expected == SellType.NONE:
|
||||||
assert sl_flag.sell_flag is False
|
assert sl_flag.exit_flag is False
|
||||||
else:
|
else:
|
||||||
assert sl_flag.sell_flag is True
|
assert sl_flag.exit_flag is True
|
||||||
assert round(trade.stop_loss, 2) == adjusted
|
assert round(trade.stop_loss, 2) == adjusted
|
||||||
current_rate2 = trade.open_rate * (1 + profit2)
|
current_rate2 = trade.open_rate * (1 + profit2)
|
||||||
|
|
||||||
sl_flag = strategy.stop_loss_reached(current_rate=current_rate2, trade=trade,
|
sl_flag = strategy.stop_loss_reached(current_rate=current_rate2, trade=trade,
|
||||||
current_time=now, current_profit=profit2,
|
current_time=now, current_profit=profit2,
|
||||||
force_stoploss=0, high=None)
|
force_stoploss=0, high=None)
|
||||||
assert sl_flag.sell_type == expected2
|
assert sl_flag.exit_type == expected2
|
||||||
if expected2 == SellType.NONE:
|
if expected2 == SellType.NONE:
|
||||||
assert sl_flag.sell_flag is False
|
assert sl_flag.exit_flag is False
|
||||||
else:
|
else:
|
||||||
assert sl_flag.sell_flag is True
|
assert sl_flag.exit_flag is True
|
||||||
assert round(trade.stop_loss, 2) == adjusted2
|
assert round(trade.stop_loss, 2) == adjusted2
|
||||||
|
|
||||||
strategy.custom_stoploss = original_stopvalue
|
strategy.custom_stoploss = original_stopvalue
|
||||||
@ -496,34 +496,34 @@ def test_custom_exit(default_conf, fee, caplog) -> None:
|
|||||||
enter=False, exit_=False,
|
enter=False, exit_=False,
|
||||||
low=None, high=None)
|
low=None, high=None)
|
||||||
|
|
||||||
assert res.sell_flag is False
|
assert res.exit_flag is False
|
||||||
assert res.sell_type == SellType.NONE
|
assert res.exit_type == SellType.NONE
|
||||||
|
|
||||||
strategy.custom_exit = MagicMock(return_value=True)
|
strategy.custom_exit = MagicMock(return_value=True)
|
||||||
res = strategy.should_exit(trade, 1, now,
|
res = strategy.should_exit(trade, 1, now,
|
||||||
enter=False, exit_=False,
|
enter=False, exit_=False,
|
||||||
low=None, high=None)
|
low=None, high=None)
|
||||||
assert res.sell_flag is True
|
assert res.exit_flag is True
|
||||||
assert res.sell_type == SellType.CUSTOM_SELL
|
assert res.exit_type == SellType.CUSTOM_SELL
|
||||||
assert res.sell_reason == 'custom_sell'
|
assert res.exit_reason == 'custom_sell'
|
||||||
|
|
||||||
strategy.custom_exit = MagicMock(return_value='hello world')
|
strategy.custom_exit = MagicMock(return_value='hello world')
|
||||||
|
|
||||||
res = strategy.should_exit(trade, 1, now,
|
res = strategy.should_exit(trade, 1, now,
|
||||||
enter=False, exit_=False,
|
enter=False, exit_=False,
|
||||||
low=None, high=None)
|
low=None, high=None)
|
||||||
assert res.sell_type == SellType.CUSTOM_SELL
|
assert res.exit_type == SellType.CUSTOM_SELL
|
||||||
assert res.sell_flag is True
|
assert res.exit_flag is True
|
||||||
assert res.sell_reason == 'hello world'
|
assert res.exit_reason == 'hello world'
|
||||||
|
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
strategy.custom_exit = MagicMock(return_value='h' * 100)
|
strategy.custom_exit = MagicMock(return_value='h' * 100)
|
||||||
res = strategy.should_exit(trade, 1, now,
|
res = strategy.should_exit(trade, 1, now,
|
||||||
enter=False, exit_=False,
|
enter=False, exit_=False,
|
||||||
low=None, high=None)
|
low=None, high=None)
|
||||||
assert res.sell_type == SellType.CUSTOM_SELL
|
assert res.exit_type == SellType.CUSTOM_SELL
|
||||||
assert res.sell_flag is True
|
assert res.exit_flag is True
|
||||||
assert res.sell_reason == 'h' * 64
|
assert res.exit_reason == 'h' * 64
|
||||||
assert log_has_re('Custom sell reason returned from custom_exit is too long.*', caplog)
|
assert log_has_re('Custom sell reason returned from custom_exit is too long.*', caplog)
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ from freqtrade.exceptions import (DependencyException, ExchangeError, Insufficie
|
|||||||
from freqtrade.freqtradebot import FreqtradeBot
|
from freqtrade.freqtradebot import FreqtradeBot
|
||||||
from freqtrade.persistence import Order, PairLocks, Trade
|
from freqtrade.persistence import Order, PairLocks, Trade
|
||||||
from freqtrade.persistence.models import PairLock
|
from freqtrade.persistence.models import PairLock
|
||||||
from freqtrade.strategy.interface import SellCheckTuple
|
from freqtrade.strategy.interface import ExitCheckTuple
|
||||||
from freqtrade.worker import Worker
|
from freqtrade.worker import Worker
|
||||||
from tests.conftest import (create_mock_trades, get_patched_freqtradebot, get_patched_worker,
|
from tests.conftest import (create_mock_trades, get_patched_freqtradebot, get_patched_worker,
|
||||||
log_has, log_has_re, patch_edge, patch_exchange, patch_get_signal,
|
log_has, log_has_re, patch_edge, patch_exchange, patch_get_signal,
|
||||||
@ -3100,7 +3100,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
|
|||||||
freqtrade.execute_trade_exit(
|
freqtrade.execute_trade_exit(
|
||||||
trade=trade,
|
trade=trade,
|
||||||
limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']),
|
limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']),
|
||||||
exit_check=SellCheckTuple(sell_type=SellType.ROI)
|
exit_check=ExitCheckTuple(exit_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
|
||||||
@ -3112,7 +3112,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
|
|||||||
freqtrade.execute_trade_exit(
|
freqtrade.execute_trade_exit(
|
||||||
trade=trade,
|
trade=trade,
|
||||||
limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']),
|
limit=(ticker_usdt_sell_down()['ask'] if is_short else ticker_usdt_sell_up()['bid']),
|
||||||
exit_check=SellCheckTuple(sell_type=SellType.ROI)
|
exit_check=ExitCheckTuple(exit_type=SellType.ROI)
|
||||||
)
|
)
|
||||||
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
||||||
|
|
||||||
@ -3173,7 +3173,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
|
|||||||
)
|
)
|
||||||
freqtrade.execute_trade_exit(
|
freqtrade.execute_trade_exit(
|
||||||
trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down)()['bid'],
|
trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down)()['bid'],
|
||||||
exit_check=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
exit_check=ExitCheckTuple(exit_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]
|
||||||
@ -3248,7 +3248,7 @@ def test_execute_trade_exit_custom_exit_price(
|
|||||||
freqtrade.execute_trade_exit(
|
freqtrade.execute_trade_exit(
|
||||||
trade=trade,
|
trade=trade,
|
||||||
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
||||||
exit_check=SellCheckTuple(sell_type=SellType.SELL_SIGNAL)
|
exit_check=ExitCheckTuple(exit_type=SellType.SELL_SIGNAL)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Sell price must be different to default bid price
|
# Sell price must be different to default bid price
|
||||||
@ -3319,7 +3319,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
|
|||||||
trade.stop_loss = 2.0 * 1.01 if is_short else 2.0 * 0.99
|
trade.stop_loss = 2.0 * 1.01 if is_short else 2.0 * 0.99
|
||||||
freqtrade.execute_trade_exit(
|
freqtrade.execute_trade_exit(
|
||||||
trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down())['bid'],
|
trade=trade, limit=(ticker_usdt_sell_up if is_short else ticker_usdt_sell_down())['bid'],
|
||||||
exit_check=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
exit_check=ExitCheckTuple(exit_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]
|
||||||
@ -3379,7 +3379,7 @@ def test_execute_trade_exit_sloe_cancel_exception(
|
|||||||
trade.stoploss_order_id = "abcd"
|
trade.stoploss_order_id = "abcd"
|
||||||
|
|
||||||
freqtrade.execute_trade_exit(trade=trade, limit=1234,
|
freqtrade.execute_trade_exit(trade=trade, limit=1234,
|
||||||
exit_check=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
exit_check=ExitCheckTuple(exit_type=SellType.STOP_LOSS))
|
||||||
assert create_order_mock.call_count == 2
|
assert create_order_mock.call_count == 2
|
||||||
assert log_has('Could not cancel stoploss order abcd', caplog)
|
assert log_has('Could not cancel stoploss order abcd', caplog)
|
||||||
|
|
||||||
@ -3434,7 +3434,7 @@ def test_execute_trade_exit_with_stoploss_on_exchange(
|
|||||||
freqtrade.execute_trade_exit(
|
freqtrade.execute_trade_exit(
|
||||||
trade=trade,
|
trade=trade,
|
||||||
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
||||||
exit_check=SellCheckTuple(sell_type=SellType.STOP_LOSS)
|
exit_check=ExitCheckTuple(exit_type=SellType.STOP_LOSS)
|
||||||
)
|
)
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
@ -3579,7 +3579,7 @@ def test_execute_trade_exit_market_order(
|
|||||||
freqtrade.execute_trade_exit(
|
freqtrade.execute_trade_exit(
|
||||||
trade=trade,
|
trade=trade,
|
||||||
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
||||||
exit_check=SellCheckTuple(sell_type=SellType.ROI)
|
exit_check=ExitCheckTuple(exit_type=SellType.ROI)
|
||||||
)
|
)
|
||||||
|
|
||||||
assert not trade.is_open
|
assert not trade.is_open
|
||||||
@ -3643,7 +3643,7 @@ def test_execute_trade_exit_insufficient_funds_error(default_conf_usdt, ticker_u
|
|||||||
fetch_ticker=ticker_usdt_sell_up
|
fetch_ticker=ticker_usdt_sell_up
|
||||||
)
|
)
|
||||||
|
|
||||||
sell_reason = SellCheckTuple(sell_type=SellType.ROI)
|
sell_reason = ExitCheckTuple(exit_type=SellType.ROI)
|
||||||
assert not freqtrade.execute_trade_exit(
|
assert not freqtrade.execute_trade_exit(
|
||||||
trade=trade,
|
trade=trade,
|
||||||
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
limit=ticker_usdt_sell_up()['ask' if is_short else 'bid'],
|
||||||
@ -3696,8 +3696,8 @@ def test_sell_profit_only(
|
|||||||
if sell_type == SellType.SELL_SIGNAL.value:
|
if sell_type == SellType.SELL_SIGNAL.value:
|
||||||
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
|
freqtrade.strategy.min_roi_reached = MagicMock(return_value=False)
|
||||||
else:
|
else:
|
||||||
freqtrade.strategy.stop_loss_reached = MagicMock(return_value=SellCheckTuple(
|
freqtrade.strategy.stop_loss_reached = MagicMock(return_value=ExitCheckTuple(
|
||||||
sell_type=SellType.NONE))
|
exit_type=SellType.NONE))
|
||||||
freqtrade.enter_positions()
|
freqtrade.enter_positions()
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
@ -3816,7 +3816,7 @@ def test_locked_pairs(default_conf_usdt, ticker_usdt, fee,
|
|||||||
freqtrade.execute_trade_exit(
|
freqtrade.execute_trade_exit(
|
||||||
trade=trade,
|
trade=trade,
|
||||||
limit=ticker_usdt_sell_down()['ask' if is_short else 'bid'],
|
limit=ticker_usdt_sell_down()['ask' if is_short else 'bid'],
|
||||||
exit_check=SellCheckTuple(sell_type=SellType.STOP_LOSS)
|
exit_check=ExitCheckTuple(exit_type=SellType.STOP_LOSS)
|
||||||
)
|
)
|
||||||
trade.close(ticker_usdt_sell_down()['bid'])
|
trade.close(ticker_usdt_sell_down()['bid'])
|
||||||
assert freqtrade.strategy.is_pair_locked(trade.pair)
|
assert freqtrade.strategy.is_pair_locked(trade.pair)
|
||||||
@ -5145,7 +5145,7 @@ def test_update_funding_fees(
|
|||||||
trade=trade,
|
trade=trade,
|
||||||
# The values of the next 2 params are irrelevant for this test
|
# The values of the next 2 params are irrelevant for this test
|
||||||
limit=ticker_usdt_sell_up()['bid'],
|
limit=ticker_usdt_sell_up()['bid'],
|
||||||
exit_check=SellCheckTuple(sell_type=SellType.ROI)
|
exit_check=ExitCheckTuple(exit_type=SellType.ROI)
|
||||||
)
|
)
|
||||||
assert trade.funding_fees == pytest.approx(sum(
|
assert trade.funding_fees == pytest.approx(sum(
|
||||||
trade.amount *
|
trade.amount *
|
||||||
|
@ -6,7 +6,7 @@ from freqtrade.enums import SellType
|
|||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from freqtrade.persistence.models import Order
|
from freqtrade.persistence.models import Order
|
||||||
from freqtrade.rpc.rpc import RPC
|
from freqtrade.rpc.rpc import RPC
|
||||||
from freqtrade.strategy.interface import SellCheckTuple
|
from freqtrade.strategy.interface import ExitCheckTuple
|
||||||
from tests.conftest import get_patched_freqtradebot, patch_get_signal
|
from tests.conftest import get_patched_freqtradebot, patch_get_signal
|
||||||
|
|
||||||
|
|
||||||
@ -53,8 +53,8 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
|||||||
side_effect=[stoploss_order_closed, stoploss_order_open, stoploss_order_open])
|
side_effect=[stoploss_order_closed, stoploss_order_open, stoploss_order_open])
|
||||||
# Sell 3rd trade (not called for the first trade)
|
# Sell 3rd trade (not called for the first trade)
|
||||||
should_sell_mock = MagicMock(side_effect=[
|
should_sell_mock = MagicMock(side_effect=[
|
||||||
SellCheckTuple(sell_type=SellType.NONE),
|
ExitCheckTuple(exit_type=SellType.NONE),
|
||||||
SellCheckTuple(sell_type=SellType.SELL_SIGNAL)]
|
ExitCheckTuple(exit_type=SellType.SELL_SIGNAL)]
|
||||||
)
|
)
|
||||||
cancel_order_mock = MagicMock()
|
cancel_order_mock = MagicMock()
|
||||||
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
|
mocker.patch('freqtrade.exchange.Binance.stoploss', stoploss)
|
||||||
@ -161,11 +161,11 @@ def test_forcebuy_last_unlimited(default_conf, ticker, fee, mocker, balance_rati
|
|||||||
_notify_exit=MagicMock(),
|
_notify_exit=MagicMock(),
|
||||||
)
|
)
|
||||||
should_sell_mock = MagicMock(side_effect=[
|
should_sell_mock = MagicMock(side_effect=[
|
||||||
SellCheckTuple(sell_type=SellType.NONE),
|
ExitCheckTuple(exit_type=SellType.NONE),
|
||||||
SellCheckTuple(sell_type=SellType.SELL_SIGNAL),
|
ExitCheckTuple(exit_type=SellType.SELL_SIGNAL),
|
||||||
SellCheckTuple(sell_type=SellType.NONE),
|
ExitCheckTuple(exit_type=SellType.NONE),
|
||||||
SellCheckTuple(sell_type=SellType.NONE),
|
ExitCheckTuple(exit_type=SellType.NONE),
|
||||||
SellCheckTuple(sell_type=SellType.NONE)]
|
ExitCheckTuple(exit_type=SellType.NONE)]
|
||||||
)
|
)
|
||||||
mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock)
|
mocker.patch("freqtrade.strategy.interface.IStrategy.should_exit", should_sell_mock)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user