updated exit_positions, handle_trade, create_stoploss_order. Changed execute_sell, _check_and_execute_sell to execute_exit
This commit is contained in:
parent
051bdc5fd0
commit
6bbe63eb62
@ -28,20 +28,24 @@ class Binance(Exchange):
|
|||||||
|
|
||||||
maintenance_margin_formula = LiqFormula.BINANCE
|
maintenance_margin_formula = LiqFormula.BINANCE
|
||||||
|
|
||||||
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
|
def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Verify stop_loss against stoploss-order value (limit or price)
|
Verify stop_loss against stoploss-order value (limit or price)
|
||||||
Returns True if adjustment is necessary.
|
Returns True if adjustment is necessary.
|
||||||
|
:param side: "buy" or "sell"
|
||||||
"""
|
"""
|
||||||
|
# TODO-mg: Short support
|
||||||
return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice'])
|
return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice'])
|
||||||
|
|
||||||
@retrier(retries=0)
|
@retrier(retries=0)
|
||||||
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
|
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict, side: str) -> Dict:
|
||||||
"""
|
"""
|
||||||
creates a stoploss limit order.
|
creates a stoploss limit order.
|
||||||
this stoploss-limit is binance-specific.
|
this stoploss-limit is binance-specific.
|
||||||
It may work with a limited number of other exchanges, but this has not been tested yet.
|
It may work with a limited number of other exchanges, but this has not been tested yet.
|
||||||
|
:param side: "buy" or "sell"
|
||||||
"""
|
"""
|
||||||
|
# TODO-mg: Short support
|
||||||
# Limit price threshold: As limit price should always be below stop-price
|
# Limit price threshold: As limit price should always be below stop-price
|
||||||
limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99)
|
limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99)
|
||||||
rate = stop_price * limit_price_pct
|
rate = stop_price * limit_price_pct
|
||||||
|
@ -784,7 +784,7 @@ class Exchange:
|
|||||||
"""
|
"""
|
||||||
raise OperationalException(f"stoploss is not implemented for {self.name}.")
|
raise OperationalException(f"stoploss is not implemented for {self.name}.")
|
||||||
|
|
||||||
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
|
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict, side: str) -> Dict:
|
||||||
"""
|
"""
|
||||||
creates a stoploss order.
|
creates a stoploss order.
|
||||||
The precise ordertype is determined by the order_types dict or exchange default.
|
The precise ordertype is determined by the order_types dict or exchange default.
|
||||||
|
@ -34,21 +34,24 @@ class Ftx(Exchange):
|
|||||||
return (parent_check and
|
return (parent_check and
|
||||||
market.get('spot', False) is True)
|
market.get('spot', False) is True)
|
||||||
|
|
||||||
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
|
def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Verify stop_loss against stoploss-order value (limit or price)
|
Verify stop_loss against stoploss-order value (limit or price)
|
||||||
Returns True if adjustment is necessary.
|
Returns True if adjustment is necessary.
|
||||||
"""
|
"""
|
||||||
|
# TODO-mg: Short support
|
||||||
return order['type'] == 'stop' and stop_loss > float(order['price'])
|
return order['type'] == 'stop' and stop_loss > float(order['price'])
|
||||||
|
|
||||||
@retrier(retries=0)
|
@retrier(retries=0)
|
||||||
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
|
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict, side: str) -> Dict:
|
||||||
"""
|
"""
|
||||||
Creates a stoploss order.
|
Creates a stoploss order.
|
||||||
depending on order_types.stoploss configuration, uses 'market' or limit order.
|
depending on order_types.stoploss configuration, uses 'market' or limit order.
|
||||||
|
|
||||||
Limit orders are defined by having orderPrice set, otherwise a market order is used.
|
Limit orders are defined by having orderPrice set, otherwise a market order is used.
|
||||||
"""
|
"""
|
||||||
|
# TODO-mg: Short support
|
||||||
|
|
||||||
limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99)
|
limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99)
|
||||||
limit_rate = stop_price * limit_price_pct
|
limit_rate = stop_price * limit_price_pct
|
||||||
|
|
||||||
|
@ -71,20 +71,22 @@ class Kraken(Exchange):
|
|||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
raise OperationalException(e) from e
|
raise OperationalException(e) from e
|
||||||
|
|
||||||
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
|
def stoploss_adjust(self, stop_loss: float, order: Dict, side: str) -> bool:
|
||||||
"""
|
"""
|
||||||
Verify stop_loss against stoploss-order value (limit or price)
|
Verify stop_loss against stoploss-order value (limit or price)
|
||||||
Returns True if adjustment is necessary.
|
Returns True if adjustment is necessary.
|
||||||
"""
|
"""
|
||||||
|
# TODO-mg: Short support
|
||||||
return (order['type'] in ('stop-loss', 'stop-loss-limit')
|
return (order['type'] in ('stop-loss', 'stop-loss-limit')
|
||||||
and stop_loss > float(order['price']))
|
and stop_loss > float(order['price']))
|
||||||
|
|
||||||
@retrier(retries=0)
|
@retrier(retries=0)
|
||||||
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
|
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict, side: str) -> Dict:
|
||||||
"""
|
"""
|
||||||
Creates a stoploss market order.
|
Creates a stoploss market order.
|
||||||
Stoploss market orders is the only stoploss type supported by kraken.
|
Stoploss market orders is the only stoploss type supported by kraken.
|
||||||
"""
|
"""
|
||||||
|
# TODO-mg: Short support
|
||||||
params = self._params.copy()
|
params = self._params.copy()
|
||||||
|
|
||||||
if order_types.get('stoploss', 'market') == 'limit':
|
if order_types.get('stoploss', 'market') == 'limit':
|
||||||
|
@ -715,7 +715,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
"""
|
"""
|
||||||
Sends rpc notification when a buy/short cancel occurred.
|
Sends rpc notification when a buy/short cancel occurred.
|
||||||
"""
|
"""
|
||||||
current_rate = self.exchange.get_rate(trade.pair, refresh=False, side="buy")
|
current_rate = self.exchange.get_rate(trade.pair, refresh=False, side=trade.enter_side)
|
||||||
msg_type = RPCMessageType.SHORT_CANCEL if trade.is_short else RPCMessageType.BUY_CANCEL
|
msg_type = RPCMessageType.SHORT_CANCEL if trade.is_short else RPCMessageType.BUY_CANCEL
|
||||||
msg = {
|
msg = {
|
||||||
'trade_id': trade.id,
|
'trade_id': trade.id,
|
||||||
@ -795,33 +795,29 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
logger.debug('Handling %s ...', trade)
|
logger.debug('Handling %s ...', trade)
|
||||||
|
|
||||||
(enter_trade, exit_trade) = (False, False)
|
(enter_trade, exit_trade) = (False, False)
|
||||||
|
exit_signal_type = "exit_short" if trade.is_short else "sell"
|
||||||
|
|
||||||
if trade.is_short:
|
# TODO-mg: change to use_exit_signal, ignore_roi_if_enter_signal
|
||||||
# enter_signal = "short"
|
signal_config = (self.config.get('use_sell_signal', True)
|
||||||
exit_signal = "exit_short"
|
or self.config.get('ignore_roi_if_buy_signal', False))
|
||||||
signal_config = (self.config.get('use_exit_short_signal', True)
|
|
||||||
or self.config.get('ignore_roi_if_short_signal', False))
|
|
||||||
else:
|
|
||||||
# enter_signal = "buy"
|
|
||||||
exit_signal = "sell"
|
|
||||||
signal_config = (self.config.get('use_sell_signal', True)
|
|
||||||
or self.config.get('ignore_roi_if_buy_signal', False))
|
|
||||||
|
|
||||||
if (signal_config):
|
if (signal_config):
|
||||||
analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair,
|
analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(trade.pair,
|
||||||
self.strategy.timeframe)
|
self.strategy.timeframe)
|
||||||
|
|
||||||
|
# TODO-mg: Call get_short_signal for shorts
|
||||||
(enter_trade, exit_trade, _) = self.strategy.get_signal(
|
(enter_trade, exit_trade, _) = self.strategy.get_signal(
|
||||||
trade.pair, self.strategy.timeframe, analyzed_df)
|
trade.pair, self.strategy.timeframe, analyzed_df)
|
||||||
|
|
||||||
sell_rate = self.exchange.get_rate(trade.pair, refresh=True, side="sell")
|
logger.debug(f'checking {exit_signal_type}')
|
||||||
|
|
||||||
# TODO-mg: This will not works for shorts,
|
logger.debug(f'checking {exit_signal_type}')
|
||||||
# TODO-mg: update exchange.get_exit_rate and _check_and_execute_sell
|
exit_rate = self.exchange.get_rate(trade.pair, refresh=True, side=trade.exit_side)
|
||||||
if self._check_and_execute_sell(trade, sell_rate, enter_trade, exit_trade):
|
|
||||||
|
if self._check_and_execute_exit(trade, exit_rate, enter_trade, exit_trade):
|
||||||
return True
|
return True
|
||||||
# TODO-mg: end of non-working section
|
|
||||||
|
|
||||||
logger.debug(f'Found no {exit_signal} signal for %s.', trade)
|
logger.debug(f'Found no {exit_signal_type} signal for %s.', trade)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def create_stoploss_order(self, trade: Trade, stop_price: float) -> bool:
|
def create_stoploss_order(self, trade: Trade, stop_price: float) -> bool:
|
||||||
@ -834,7 +830,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
try:
|
try:
|
||||||
stoploss_order = self.exchange.stoploss(pair=trade.pair, amount=trade.amount,
|
stoploss_order = self.exchange.stoploss(pair=trade.pair, amount=trade.amount,
|
||||||
stop_price=stop_price,
|
stop_price=stop_price,
|
||||||
order_types=self.strategy.order_types)
|
order_types=self.strategy.order_types,
|
||||||
|
side=trade.exit_side)
|
||||||
|
|
||||||
order_obj = Order.parse_from_ccxt_object(stoploss_order, trade.pair, 'stoploss')
|
order_obj = Order.parse_from_ccxt_object(stoploss_order, trade.pair, 'stoploss')
|
||||||
trade.orders.append(order_obj)
|
trade.orders.append(order_obj)
|
||||||
@ -848,14 +845,9 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
except InvalidOrderException as e:
|
except InvalidOrderException as e:
|
||||||
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}')
|
||||||
if trade.is_short:
|
logger.warning('Exiting the trade forcefully')
|
||||||
logger.warning('Exiting(Buying) the trade forcefully')
|
self.execute_exit(trade, trade.stop_loss, sell_reason=SellCheckTuple(
|
||||||
# self.execute_exit_short(trade, trade.stop_loss, sell_reason=SellCheckTuple(
|
sell_type=SellType.EMERGENCY_SELL), side=trade.exit_side)
|
||||||
# sell_type=SellType.EMERGENCY_SELL))
|
|
||||||
else:
|
|
||||||
logger.warning('Selling the trade forcefully')
|
|
||||||
self.execute_sell(trade, trade.stop_loss, sell_reason=SellCheckTuple(
|
|
||||||
sell_type=SellType.EMERGENCY_SELL))
|
|
||||||
|
|
||||||
except ExchangeError:
|
except ExchangeError:
|
||||||
trade.stoploss_order_id = None
|
trade.stoploss_order_id = None
|
||||||
@ -947,7 +939,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
:param order: Current on exchange stoploss order
|
:param order: Current on exchange stoploss order
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
if self.exchange.stoploss_adjust(trade.stop_loss, order):
|
if self.exchange.stoploss_adjust(trade.stop_loss, order, side=trade.exit_side):
|
||||||
# we check if the update is necessary
|
# we check if the update is necessary
|
||||||
update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60)
|
update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60)
|
||||||
if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() >= update_beat:
|
if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() >= update_beat:
|
||||||
@ -967,20 +959,25 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
logger.warning(f"Could not create trailing stoploss order "
|
logger.warning(f"Could not create trailing stoploss order "
|
||||||
f"for pair {trade.pair}.")
|
f"for pair {trade.pair}.")
|
||||||
|
|
||||||
def _check_and_execute_sell(self, trade: Trade, sell_rate: float,
|
def _check_and_execute_exit(self, trade: Trade, exit_rate: float,
|
||||||
buy: bool, sell: bool) -> bool:
|
buy: bool, sell: bool) -> bool:
|
||||||
"""
|
"""
|
||||||
Check and execute sell
|
Check and execute sell
|
||||||
# TODO-mg: Update this for shorts
|
# TODO-mg: Update this for shorts
|
||||||
"""
|
"""
|
||||||
should_sell = self.strategy.should_sell(
|
exit = getattr(self.strategy, "should_exit_short") if trade.is_short else getattr(
|
||||||
trade, sell_rate, datetime.now(timezone.utc), buy, sell,
|
self.strategy, "should_sell")
|
||||||
|
|
||||||
|
should_exit = exit(
|
||||||
|
trade, exit_rate, datetime.now(timezone.utc), buy, sell,
|
||||||
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_sell.sell_flag:
|
if should_exit.sell_flag:
|
||||||
logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}')
|
# TODO-mg: Update to exit_type
|
||||||
self.execute_sell(trade, sell_rate, should_sell)
|
logger.info(
|
||||||
|
f'Executing {trade.exit_side} for {trade.pair}. Reason: {should_exit.sell_type}')
|
||||||
|
self.execute_exit(trade, exit_rate, should_exit, side="sell")
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -988,6 +985,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
"""
|
"""
|
||||||
Check if timeout is active, and if the order is still open and timed out
|
Check if timeout is active, and if the order is still open and timed out
|
||||||
"""
|
"""
|
||||||
|
# TODO-mg: Maybe update side in the next line
|
||||||
timeout = self.config.get('unfilledtimeout', {}).get(side)
|
timeout = self.config.get('unfilledtimeout', {}).get(side)
|
||||||
ordertime = arrow.get(order['datetime']).datetime
|
ordertime = arrow.get(order['datetime']).datetime
|
||||||
if timeout is not None:
|
if timeout is not None:
|
||||||
@ -1020,20 +1018,20 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
order['status'] == 'open' or fully_cancelled) and (
|
order['status'] == 'open' or fully_cancelled) and (
|
||||||
fully_cancelled
|
fully_cancelled
|
||||||
or self._check_timed_out(trade.enter_side, order)
|
or self._check_timed_out(trade.enter_side, order)
|
||||||
or strategy_safe_wrapper(self.strategy.check_buy_timeout,
|
or strategy_safe_wrapper(self.strategy.check_buy_timeout, # TODO-mg: maybe change to check_enter_timeout
|
||||||
default_retval=False)(pair=trade.pair,
|
default_retval=False)(pair=trade.pair,
|
||||||
trade=trade,
|
trade=trade,
|
||||||
order=order))):
|
order=order))):
|
||||||
self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||||
|
|
||||||
elif (order['side'] == 'sell' and (order['status'] == 'open' or fully_cancelled) and (
|
elif (order['side'] == trade.exit_side and (order['status'] == 'open' or fully_cancelled) and (
|
||||||
fully_cancelled
|
fully_cancelled
|
||||||
or self._check_timed_out('sell', order)
|
or self._check_timed_out(trade.exit_side, order)
|
||||||
or strategy_safe_wrapper(self.strategy.check_sell_timeout,
|
or strategy_safe_wrapper(self.strategy.check_sell_timeout, # TODO-mg: maybe change to check_exit_timeout
|
||||||
default_retval=False)(pair=trade.pair,
|
default_retval=False)(pair=trade.pair,
|
||||||
trade=trade,
|
trade=trade,
|
||||||
order=order))):
|
order=order))):
|
||||||
self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['TIMEOUT'])
|
||||||
|
|
||||||
def cancel_all_open_orders(self) -> None:
|
def cancel_all_open_orders(self) -> None:
|
||||||
"""
|
"""
|
||||||
@ -1048,11 +1046,11 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc())
|
logger.info('Cannot query order for %s due to %s', trade, traceback.format_exc())
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if order['side'] == 'buy':
|
if order['side'] == trade.enter_side:
|
||||||
self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
|
self.handle_cancel_enter(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
|
||||||
|
|
||||||
elif order['side'] == 'sell':
|
elif order['side'] == trade.exit_side:
|
||||||
self.handle_cancel_sell(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
|
self.handle_cancel_exit(trade, order, constants.CANCEL_REASON['ALL_CANCELLED'])
|
||||||
Trade.commit()
|
Trade.commit()
|
||||||
|
|
||||||
def handle_cancel_enter(self, trade: Trade, order: Dict, reason: str) -> bool:
|
def handle_cancel_enter(self, trade: Trade, order: Dict, reason: str) -> bool:
|
||||||
@ -1087,18 +1085,15 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
corder = order
|
corder = order
|
||||||
reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE']
|
reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE']
|
||||||
|
|
||||||
# TODO-mg: get this side variable working, fails in tests
|
side = trade.enter_side.capitalize()
|
||||||
# side = trade.enter_side[0].upper() + trade.enter_side[1:].lower()
|
logger.info('%s order %s for %s.', side, reason, trade)
|
||||||
logger.info('Buy order %s for %s.', reason, trade)
|
|
||||||
# logger.info(f'{side} order %s for %s.', reason, trade)
|
|
||||||
|
|
||||||
# Using filled to determine the filled amount
|
# Using filled to determine the filled amount
|
||||||
filled_amount = safe_value_fallback2(corder, order, 'filled', 'filled')
|
filled_amount = safe_value_fallback2(corder, order, 'filled', 'filled')
|
||||||
if isclose(filled_amount, 0.0, abs_tol=constants.MATH_CLOSE_PREC):
|
if isclose(filled_amount, 0.0, abs_tol=constants.MATH_CLOSE_PREC):
|
||||||
logger.info(
|
logger.info(
|
||||||
'Buy order fully cancelled. Removing %s from database.',
|
'%s order fully cancelled. Removing %s from database.',
|
||||||
# f'{side} order fully cancelled. Removing %s from database.',
|
side, trade
|
||||||
trade
|
|
||||||
)
|
)
|
||||||
# if trade is not partially completed, just delete the trade
|
# if trade is not partially completed, just delete the trade
|
||||||
trade.delete()
|
trade.delete()
|
||||||
@ -1116,20 +1111,18 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
self.update_trade_state(trade, trade.open_order_id, corder)
|
self.update_trade_state(trade, trade.open_order_id, corder)
|
||||||
|
|
||||||
trade.open_order_id = None
|
trade.open_order_id = None
|
||||||
# TODO-mg change to buy or short order
|
logger.info('Partial %s order timeout for %s.', trade.enter_side, trade)
|
||||||
logger.info('Partial buy order timeout for %s.', trade)
|
|
||||||
reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}"
|
reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}"
|
||||||
|
|
||||||
self.wallets.update()
|
self.wallets.update()
|
||||||
# TODO-mg change to buy or short order
|
# TODO-mg: Should short and exit_short be an order type?
|
||||||
# TODO-mg: Or should it be self.strategy.order_types['short'] or strategy.order_types['buy']
|
self._notify_open_cancel(trade, order_type=self.strategy.order_types[trade.enter_side],
|
||||||
self._notify_open_cancel(trade, order_type=self.strategy.order_types['buy'],
|
|
||||||
reason=reason)
|
reason=reason)
|
||||||
return was_trade_fully_canceled
|
return was_trade_fully_canceled
|
||||||
|
|
||||||
def handle_cancel_sell(self, trade: Trade, order: Dict, reason: str) -> str:
|
def handle_cancel_exit(self, trade: Trade, order: Dict, reason: str) -> str:
|
||||||
"""
|
"""
|
||||||
Sell cancel - cancel order and update trade
|
Sell/exit_short cancel - cancel order and update trade
|
||||||
:return: Reason for cancel
|
:return: Reason for cancel
|
||||||
"""
|
"""
|
||||||
# if trade is not partially completed, just cancel the order
|
# if trade is not partially completed, just cancel the order
|
||||||
@ -1141,12 +1134,13 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
trade.amount)
|
trade.amount)
|
||||||
trade.update_order(co)
|
trade.update_order(co)
|
||||||
except InvalidOrderException:
|
except InvalidOrderException:
|
||||||
logger.exception(f"Could not cancel sell order {trade.open_order_id}")
|
logger.exception(
|
||||||
|
f"Could not cancel {trade.exit_side} order {trade.open_order_id}")
|
||||||
return 'error cancelling order'
|
return 'error cancelling order'
|
||||||
logger.info('Sell order %s for %s.', reason, trade)
|
logger.info('%s order %s for %s.', trade.exit_side.capitalize(), reason, trade)
|
||||||
else:
|
else:
|
||||||
reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE']
|
reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE']
|
||||||
logger.info('Sell order %s for %s.', reason, trade)
|
logger.info('%s order %s for %s.', trade.exit_side.capitalize(), reason, trade)
|
||||||
trade.update_order(order)
|
trade.update_order(order)
|
||||||
|
|
||||||
trade.close_rate = None
|
trade.close_rate = None
|
||||||
@ -1163,12 +1157,12 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
self.wallets.update()
|
self.wallets.update()
|
||||||
self._notify_close_cancel(
|
self._notify_close_cancel(
|
||||||
trade,
|
trade,
|
||||||
order_type=self.strategy.order_types['sell'],
|
order_type=self.strategy.order_types[trade.exit_side],
|
||||||
reason=reason
|
reason=reason
|
||||||
)
|
)
|
||||||
return reason
|
return reason
|
||||||
|
|
||||||
def _safe_sell_amount(self, pair: str, amount: float) -> float:
|
def _safe_exit_amount(self, pair: str, amount: float) -> float:
|
||||||
"""
|
"""
|
||||||
Get sellable amount.
|
Get sellable amount.
|
||||||
Should be trade.amount - but will fall back to the available amount if necessary.
|
Should be trade.amount - but will fall back to the available amount if necessary.
|
||||||
@ -1179,6 +1173,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
:return: amount to sell
|
:return: amount to sell
|
||||||
:raise: DependencyException: if available balance is not within 2% of the available amount.
|
:raise: DependencyException: if available balance is not within 2% of the available amount.
|
||||||
"""
|
"""
|
||||||
|
# TODO-mg Maybe update?
|
||||||
# Update wallets to ensure amounts tied up in a stoploss is now free!
|
# Update wallets to ensure amounts tied up in a stoploss is now free!
|
||||||
self.wallets.update()
|
self.wallets.update()
|
||||||
trade_base_currency = self.exchange.get_pair_base_currency(pair)
|
trade_base_currency = self.exchange.get_pair_base_currency(pair)
|
||||||
@ -1191,9 +1186,15 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
return wallet_amount
|
return wallet_amount
|
||||||
else:
|
else:
|
||||||
raise DependencyException(
|
raise DependencyException(
|
||||||
f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}")
|
f"Not enough amount to exit trade. Trade-amount: {amount}, Wallet: {wallet_amount}")
|
||||||
|
|
||||||
def execute_sell(self, trade: Trade, limit: float, sell_reason: SellCheckTuple) -> bool:
|
def execute_exit(
|
||||||
|
self,
|
||||||
|
trade: Trade,
|
||||||
|
limit: float,
|
||||||
|
sell_reason: SellCheckTuple,
|
||||||
|
side: str
|
||||||
|
) -> 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
|
||||||
@ -1201,14 +1202,14 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
:param sell_reason: Reason the sell was triggered
|
:param sell_reason: Reason the sell was triggered
|
||||||
:return: True if it succeeds (supported) False (not supported)
|
:return: True if it succeeds (supported) False (not supported)
|
||||||
"""
|
"""
|
||||||
# TODO-mg: account for leverage and make this work for shorts
|
|
||||||
sell_type = 'sell'
|
exit_type = 'sell' # TODO-mg: Update to exit
|
||||||
if sell_reason.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
|
if sell_reason.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
|
||||||
sell_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,
|
||||||
# we consider the sell price stop price
|
# we consider the sell price stop price
|
||||||
if self.config['dry_run'] and sell_type == 'stoploss' \
|
if self.config['dry_run'] and exit_type == 'stoploss' \
|
||||||
and self.strategy.order_types['stoploss_on_exchange']:
|
and self.strategy.order_types['stoploss_on_exchange']:
|
||||||
limit = trade.stop_loss
|
limit = trade.stop_loss
|
||||||
|
|
||||||
@ -1221,8 +1222,8 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
except InvalidOrderException:
|
except InvalidOrderException:
|
||||||
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[exit_type]
|
||||||
if sell_reason.sell_type == SellType.EMERGENCY_SELL:
|
if sell_reason.sell_type == SellType.EMERGENCY_SELL: # TODO-mg update to exit_reason
|
||||||
# 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.sell_type == SellType.FORCE_SELL:
|
if sell_reason.sell_type == SellType.FORCE_SELL:
|
||||||
@ -1230,42 +1231,44 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
# 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)
|
||||||
|
|
||||||
amount = self._safe_sell_amount(trade.pair, trade.amount)
|
amount = self._safe_exit_amount(trade.pair, trade.amount)
|
||||||
time_in_force = self.strategy.order_time_in_force['sell']
|
time_in_force = self.strategy.order_time_in_force['sell'] # TODO-mg update to exit
|
||||||
|
|
||||||
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, sell_reason=sell_reason.sell_reason,
|
time_in_force=time_in_force, sell_reason=sell_reason.sell_reason,
|
||||||
current_time=datetime.now(timezone.utc)):
|
current_time=datetime.now(timezone.utc)): # TODO-mg: Update to exit
|
||||||
logger.info(f"User requested abortion of selling {trade.pair}")
|
logger.info(f"User requested abortion of exiting {trade.pair}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Execute sell and update trade record
|
# Execute sell and update trade record
|
||||||
order = self.exchange.create_order(pair=trade.pair,
|
order = self.exchange.create_order(
|
||||||
ordertype=order_type, side="sell",
|
pair=trade.pair,
|
||||||
amount=amount, rate=limit,
|
ordertype=order_type,
|
||||||
time_in_force=time_in_force
|
amount=amount, rate=limit,
|
||||||
)
|
time_in_force=time_in_force,
|
||||||
|
side=trade.exit_side
|
||||||
|
)
|
||||||
except InsufficientFundsError as e:
|
except InsufficientFundsError as e:
|
||||||
logger.warning(f"Unable to place order {e}.")
|
logger.warning(f"Unable to place order {e}.")
|
||||||
# Try to figure out what went wrong
|
# Try to figure out what went wrong
|
||||||
self.handle_insufficient_funds(trade)
|
self.handle_insufficient_funds(trade)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
order_obj = Order.parse_from_ccxt_object(order, trade.pair, 'sell')
|
order_obj = Order.parse_from_ccxt_object(order, trade.pair, trade.exit_side)
|
||||||
trade.orders.append(order_obj)
|
trade.orders.append(order_obj)
|
||||||
|
|
||||||
trade.open_order_id = order['id']
|
trade.open_order_id = order['id']
|
||||||
trade.sell_order_status = ''
|
trade.sell_order_status = '' # TODO-mg: Update to exit_order_status
|
||||||
trade.close_rate_requested = limit
|
trade.close_rate_requested = limit
|
||||||
trade.sell_reason = sell_reason.sell_reason
|
trade.sell_reason = sell_reason.sell_reason # TODO-mg: Update to exit_reason
|
||||||
# In case of market sell orders the order can be closed immediately
|
# In case of market exit 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)
|
||||||
Trade.commit()
|
Trade.commit()
|
||||||
|
|
||||||
# Lock pair for one candle to prevent immediate re-buys
|
# 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),
|
||||||
reason='Auto lock')
|
reason='Auto lock')
|
||||||
|
|
||||||
@ -1281,7 +1284,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
profit_trade = trade.calc_profit(rate=profit_rate)
|
profit_trade = trade.calc_profit(rate=profit_rate)
|
||||||
# Use cached rates here - it was updated seconds ago.
|
# Use cached rates here - it was updated seconds ago.
|
||||||
current_rate = self.exchange.get_rate(
|
current_rate = self.exchange.get_rate(
|
||||||
trade.pair, refresh=False, side="sell") if not fill else None
|
trade.pair, refresh=False, side=trade.exit_side) if not fill else None
|
||||||
profit_ratio = trade.calc_profit_ratio(profit_rate)
|
profit_ratio = trade.calc_profit_ratio(profit_rate)
|
||||||
gain = "profit" if profit_ratio > 0 else "loss"
|
gain = "profit" if profit_ratio > 0 else "loss"
|
||||||
|
|
||||||
@ -1300,7 +1303,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
'current_rate': current_rate,
|
'current_rate': current_rate,
|
||||||
'profit_amount': profit_trade,
|
'profit_amount': profit_trade,
|
||||||
'profit_ratio': profit_ratio,
|
'profit_ratio': profit_ratio,
|
||||||
'sell_reason': trade.sell_reason,
|
'sell_reason': trade.sell_reason, # TODO-mg: trade to exit_reason
|
||||||
'open_date': trade.open_date,
|
'open_date': trade.open_date,
|
||||||
'close_date': trade.close_date or datetime.utcnow(),
|
'close_date': trade.close_date or datetime.utcnow(),
|
||||||
'stake_currency': self.config['stake_currency'],
|
'stake_currency': self.config['stake_currency'],
|
||||||
@ -1319,14 +1322,14 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
"""
|
"""
|
||||||
Sends rpc notification when a sell cancel occurred.
|
Sends rpc notification when a sell cancel occurred.
|
||||||
"""
|
"""
|
||||||
if trade.sell_order_status == reason:
|
if trade.sell_order_status == reason: # TODO-mg: Update to exit_order_status
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
trade.sell_order_status = reason
|
trade.sell_order_status = reason # TODO-mg: Update to exit_order_status
|
||||||
|
|
||||||
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
|
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
|
||||||
profit_trade = trade.calc_profit(rate=profit_rate)
|
profit_trade = trade.calc_profit(rate=profit_rate)
|
||||||
current_rate = self.exchange.get_rate(trade.pair, refresh=False, side="sell")
|
current_rate = self.exchange.get_rate(trade.pair, refresh=False, side=trade.exit_side)
|
||||||
profit_ratio = trade.calc_profit_ratio(profit_rate)
|
profit_ratio = trade.calc_profit_ratio(profit_rate)
|
||||||
gain = "profit" if profit_ratio > 0 else "loss"
|
gain = "profit" if profit_ratio > 0 else "loss"
|
||||||
|
|
||||||
@ -1343,7 +1346,7 @@ class FreqtradeBot(LoggingMixin):
|
|||||||
'current_rate': current_rate,
|
'current_rate': current_rate,
|
||||||
'profit_amount': profit_trade,
|
'profit_amount': profit_trade,
|
||||||
'profit_ratio': profit_ratio,
|
'profit_ratio': profit_ratio,
|
||||||
'sell_reason': trade.sell_reason,
|
'sell_reason': trade.sell_reason, # TODO-mg: trade to exit_reason
|
||||||
'open_date': trade.open_date,
|
'open_date': trade.open_date,
|
||||||
'close_date': trade.close_date,
|
'close_date': trade.close_date,
|
||||||
'stake_currency': self.config['stake_currency'],
|
'stake_currency': self.config['stake_currency'],
|
||||||
|
@ -551,14 +551,14 @@ class RPC:
|
|||||||
|
|
||||||
if order['side'] == 'sell':
|
if order['side'] == 'sell':
|
||||||
# Cancel order - so it is placed anew with a fresh price.
|
# Cancel order - so it is placed anew with a fresh price.
|
||||||
self._freqtrade.handle_cancel_sell(trade, order, CANCEL_REASON['FORCE_SELL'])
|
self._freqtrade.handle_cancel_exit(trade, order, CANCEL_REASON['FORCE_SELL'])
|
||||||
|
|
||||||
if not fully_canceled:
|
if not fully_canceled:
|
||||||
# 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="sell")
|
trade.pair, refresh=False, side="sell")
|
||||||
sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL)
|
sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL)
|
||||||
self._freqtrade.execute_sell(trade, current_rate, sell_reason)
|
self._freqtrade.execute_exit(trade, current_rate, sell_reason, side="sell")
|
||||||
# ---- EOF def _exec_forcesell ----
|
# ---- EOF def _exec_forcesell ----
|
||||||
|
|
||||||
if self._freqtrade.state != State.RUNNING:
|
if self._freqtrade.state != State.RUNNING:
|
||||||
|
@ -32,12 +32,13 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
with pytest.raises(OperationalException):
|
||||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side="sell",
|
||||||
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
|
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
|
||||||
|
|
||||||
api_mock.create_order.reset_mock()
|
api_mock.create_order.reset_mock()
|
||||||
order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio}
|
order_types = {} if limitratio is None else {'stoploss_on_exchange_limit_ratio': limitratio}
|
||||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types=order_types)
|
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
||||||
|
order_types=order_types, side="sell")
|
||||||
|
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'info' in order
|
assert 'info' in order
|
||||||
@ -54,17 +55,17 @@ def test_stoploss_order_binance(default_conf, mocker, limitratio, expected):
|
|||||||
with pytest.raises(DependencyException):
|
with pytest.raises(DependencyException):
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
|
||||||
|
|
||||||
with pytest.raises(InvalidOrderException):
|
with pytest.raises(InvalidOrderException):
|
||||||
api_mock.create_order = MagicMock(
|
api_mock.create_order = MagicMock(
|
||||||
side_effect=ccxt.InvalidOrder("binance Order would trigger immediately."))
|
side_effect=ccxt.InvalidOrder("binance Order would trigger immediately."))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
|
||||||
|
|
||||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance",
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "binance",
|
||||||
"stoploss", "create_order", retries=1,
|
"stoploss", "create_order", retries=1,
|
||||||
pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
|
||||||
|
|
||||||
|
|
||||||
def test_stoploss_order_dry_run_binance(default_conf, mocker):
|
def test_stoploss_order_dry_run_binance(default_conf, mocker):
|
||||||
@ -77,12 +78,12 @@ def test_stoploss_order_dry_run_binance(default_conf, mocker):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
|
||||||
|
|
||||||
with pytest.raises(OperationalException):
|
with pytest.raises(OperationalException):
|
||||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side="sell",
|
||||||
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
|
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
|
||||||
|
|
||||||
api_mock.create_order.reset_mock()
|
api_mock.create_order.reset_mock()
|
||||||
|
|
||||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
|
||||||
|
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'info' in order
|
assert 'info' in order
|
||||||
@ -100,8 +101,8 @@ def test_stoploss_adjust_binance(mocker, default_conf):
|
|||||||
'price': 1500,
|
'price': 1500,
|
||||||
'info': {'stopPrice': 1500},
|
'info': {'stopPrice': 1500},
|
||||||
}
|
}
|
||||||
assert exchange.stoploss_adjust(1501, order)
|
assert exchange.stoploss_adjust(1501, order, side="sell")
|
||||||
assert not exchange.stoploss_adjust(1499, order)
|
assert not exchange.stoploss_adjust(1499, order, side="sell")
|
||||||
# Test with invalid order case
|
# Test with invalid order case
|
||||||
order['type'] = 'stop_loss'
|
order['type'] = 'stop_loss'
|
||||||
assert not exchange.stoploss_adjust(1501, order)
|
assert not exchange.stoploss_adjust(1501, order, side="sell")
|
||||||
|
@ -2530,7 +2530,7 @@ def test_get_fee(default_conf, mocker, exchange_name):
|
|||||||
def test_stoploss_order_unsupported_exchange(default_conf, mocker):
|
def test_stoploss_order_unsupported_exchange(default_conf, mocker):
|
||||||
exchange = get_patched_exchange(mocker, default_conf, id='bittrex')
|
exchange = get_patched_exchange(mocker, default_conf, id='bittrex')
|
||||||
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
|
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
|
||||||
|
|
||||||
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
|
with pytest.raises(OperationalException, match=r"stoploss is not implemented .*"):
|
||||||
exchange.stoploss_adjust(1, {})
|
exchange.stoploss_adjust(1, {})
|
||||||
|
@ -32,7 +32,7 @@ def test_stoploss_order_ftx(default_conf, mocker):
|
|||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
|
||||||
|
|
||||||
# stoploss_on_exchange_limit_ratio is irrelevant for ftx market orders
|
# stoploss_on_exchange_limit_ratio is irrelevant for ftx market orders
|
||||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190,
|
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=190, side="sell",
|
||||||
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
|
order_types={'stoploss_on_exchange_limit_ratio': 1.05})
|
||||||
|
|
||||||
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
|
assert api_mock.create_order.call_args_list[0][1]['symbol'] == 'ETH/BTC'
|
||||||
@ -47,7 +47,7 @@ def test_stoploss_order_ftx(default_conf, mocker):
|
|||||||
|
|
||||||
api_mock.create_order.reset_mock()
|
api_mock.create_order.reset_mock()
|
||||||
|
|
||||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
|
||||||
|
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'info' in order
|
assert 'info' in order
|
||||||
@ -61,7 +61,7 @@ def test_stoploss_order_ftx(default_conf, mocker):
|
|||||||
|
|
||||||
api_mock.create_order.reset_mock()
|
api_mock.create_order.reset_mock()
|
||||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
||||||
order_types={'stoploss': 'limit'})
|
order_types={'stoploss': 'limit'}, side="sell")
|
||||||
|
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'info' in order
|
assert 'info' in order
|
||||||
@ -78,17 +78,17 @@ def test_stoploss_order_ftx(default_conf, mocker):
|
|||||||
with pytest.raises(DependencyException):
|
with pytest.raises(DependencyException):
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
|
||||||
|
|
||||||
with pytest.raises(InvalidOrderException):
|
with pytest.raises(InvalidOrderException):
|
||||||
api_mock.create_order = MagicMock(
|
api_mock.create_order = MagicMock(
|
||||||
side_effect=ccxt.InvalidOrder("ftx Order would trigger immediately."))
|
side_effect=ccxt.InvalidOrder("ftx Order would trigger immediately."))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'ftx')
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
|
||||||
|
|
||||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "ftx",
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "ftx",
|
||||||
"stoploss", "create_order", retries=1,
|
"stoploss", "create_order", retries=1,
|
||||||
pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
|
||||||
|
|
||||||
|
|
||||||
def test_stoploss_order_dry_run_ftx(default_conf, mocker):
|
def test_stoploss_order_dry_run_ftx(default_conf, mocker):
|
||||||
@ -101,7 +101,7 @@ def test_stoploss_order_dry_run_ftx(default_conf, mocker):
|
|||||||
|
|
||||||
api_mock.create_order.reset_mock()
|
api_mock.create_order.reset_mock()
|
||||||
|
|
||||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
|
||||||
|
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'info' in order
|
assert 'info' in order
|
||||||
@ -118,11 +118,11 @@ def test_stoploss_adjust_ftx(mocker, default_conf):
|
|||||||
'type': STOPLOSS_ORDERTYPE,
|
'type': STOPLOSS_ORDERTYPE,
|
||||||
'price': 1500,
|
'price': 1500,
|
||||||
}
|
}
|
||||||
assert exchange.stoploss_adjust(1501, order)
|
assert exchange.stoploss_adjust(1501, order, side="sell")
|
||||||
assert not exchange.stoploss_adjust(1499, order)
|
assert not exchange.stoploss_adjust(1499, order, side="sell")
|
||||||
# Test with invalid order case ...
|
# Test with invalid order case ...
|
||||||
order['type'] = 'stop_loss_limit'
|
order['type'] = 'stop_loss_limit'
|
||||||
assert not exchange.stoploss_adjust(1501, order)
|
assert not exchange.stoploss_adjust(1501, order, side="sell")
|
||||||
|
|
||||||
|
|
||||||
def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order):
|
def test_fetch_stoploss_order(default_conf, mocker, limit_sell_order):
|
||||||
|
@ -183,7 +183,7 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype):
|
|||||||
|
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
||||||
|
|
||||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220,
|
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, side="sell",
|
||||||
order_types={'stoploss': ordertype,
|
order_types={'stoploss': ordertype,
|
||||||
'stoploss_on_exchange_limit_ratio': 0.99
|
'stoploss_on_exchange_limit_ratio': 0.99
|
||||||
})
|
})
|
||||||
@ -208,17 +208,17 @@ def test_stoploss_order_kraken(default_conf, mocker, ordertype):
|
|||||||
with pytest.raises(DependencyException):
|
with pytest.raises(DependencyException):
|
||||||
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
|
||||||
|
|
||||||
with pytest.raises(InvalidOrderException):
|
with pytest.raises(InvalidOrderException):
|
||||||
api_mock.create_order = MagicMock(
|
api_mock.create_order = MagicMock(
|
||||||
side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately."))
|
side_effect=ccxt.InvalidOrder("kraken Order would trigger immediately."))
|
||||||
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'kraken')
|
||||||
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
|
||||||
|
|
||||||
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken",
|
ccxt_exceptionhandlers(mocker, default_conf, api_mock, "kraken",
|
||||||
"stoploss", "create_order", retries=1,
|
"stoploss", "create_order", retries=1,
|
||||||
pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
|
||||||
|
|
||||||
|
|
||||||
def test_stoploss_order_dry_run_kraken(default_conf, mocker):
|
def test_stoploss_order_dry_run_kraken(default_conf, mocker):
|
||||||
@ -231,7 +231,7 @@ def test_stoploss_order_dry_run_kraken(default_conf, mocker):
|
|||||||
|
|
||||||
api_mock.create_order.reset_mock()
|
api_mock.create_order.reset_mock()
|
||||||
|
|
||||||
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={})
|
order = exchange.stoploss(pair='ETH/BTC', amount=1, stop_price=220, order_types={}, side="sell")
|
||||||
|
|
||||||
assert 'id' in order
|
assert 'id' in order
|
||||||
assert 'info' in order
|
assert 'info' in order
|
||||||
@ -248,8 +248,8 @@ def test_stoploss_adjust_kraken(mocker, default_conf):
|
|||||||
'type': STOPLOSS_ORDERTYPE,
|
'type': STOPLOSS_ORDERTYPE,
|
||||||
'price': 1500,
|
'price': 1500,
|
||||||
}
|
}
|
||||||
assert exchange.stoploss_adjust(1501, order)
|
assert exchange.stoploss_adjust(1501, order, side="sell")
|
||||||
assert not exchange.stoploss_adjust(1499, order)
|
assert not exchange.stoploss_adjust(1499, order, side="sell")
|
||||||
# Test with invalid order case ...
|
# Test with invalid order case ...
|
||||||
order['type'] = 'stop_loss_limit'
|
order['type'] = 'stop_loss_limit'
|
||||||
assert not exchange.stoploss_adjust(1501, order)
|
assert not exchange.stoploss_adjust(1501, order, side="sell")
|
||||||
|
@ -254,7 +254,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, caplog, mocker, edge_conf
|
|||||||
|
|
||||||
# stoploss shoud be hit
|
# stoploss shoud be hit
|
||||||
assert freqtrade.handle_trade(trade) is True
|
assert freqtrade.handle_trade(trade) is True
|
||||||
assert log_has('Executing Sell for NEO/BTC. Reason: stop_loss', caplog)
|
assert log_has('Executing sell for NEO/BTC. Reason: stop_loss', caplog)
|
||||||
assert trade.sell_reason == SellType.STOP_LOSS.value
|
assert trade.sell_reason == SellType.STOP_LOSS.value
|
||||||
|
|
||||||
|
|
||||||
@ -1158,7 +1158,7 @@ def test_create_stoploss_order_invalid_order(mocker, default_conf, caplog, fee,
|
|||||||
assert trade.stoploss_order_id is None
|
assert trade.stoploss_order_id is None
|
||||||
assert trade.sell_reason == SellType.EMERGENCY_SELL.value
|
assert trade.sell_reason == SellType.EMERGENCY_SELL.value
|
||||||
assert log_has("Unable to place a stoploss order on exchange. ", caplog)
|
assert log_has("Unable to place a stoploss order on exchange. ", caplog)
|
||||||
assert log_has("Selling the trade forcefully", caplog)
|
assert log_has("Exiting the trade forcefully", caplog)
|
||||||
|
|
||||||
# Should call a market sell
|
# Should call a market sell
|
||||||
assert create_order_mock.call_count == 2
|
assert create_order_mock.call_count == 2
|
||||||
@ -1312,7 +1312,8 @@ def test_handle_stoploss_on_exchange_trailing(mocker, default_conf, fee,
|
|||||||
stoploss_order_mock.assert_called_once_with(amount=85.32423208,
|
stoploss_order_mock.assert_called_once_with(amount=85.32423208,
|
||||||
pair='ETH/BTC',
|
pair='ETH/BTC',
|
||||||
order_types=freqtrade.strategy.order_types,
|
order_types=freqtrade.strategy.order_types,
|
||||||
stop_price=0.00002346 * 0.95)
|
stop_price=0.00002346 * 0.95,
|
||||||
|
side="sell")
|
||||||
|
|
||||||
# price fell below stoploss, so dry-run sells trade.
|
# price fell below stoploss, so dry-run sells trade.
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={
|
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={
|
||||||
@ -1495,7 +1496,8 @@ def test_handle_stoploss_on_exchange_custom_stop(mocker, default_conf, fee,
|
|||||||
stoploss_order_mock.assert_called_once_with(amount=85.32423208,
|
stoploss_order_mock.assert_called_once_with(amount=85.32423208,
|
||||||
pair='ETH/BTC',
|
pair='ETH/BTC',
|
||||||
order_types=freqtrade.strategy.order_types,
|
order_types=freqtrade.strategy.order_types,
|
||||||
stop_price=0.00002346 * 0.96)
|
stop_price=0.00002346 * 0.96,
|
||||||
|
side="sell")
|
||||||
|
|
||||||
# price fell below stoploss, so dry-run sells trade.
|
# price fell below stoploss, so dry-run sells trade.
|
||||||
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={
|
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={
|
||||||
@ -1616,7 +1618,8 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, caplog,
|
|||||||
stoploss_order_mock.assert_called_once_with(amount=2132892.49146757,
|
stoploss_order_mock.assert_called_once_with(amount=2132892.49146757,
|
||||||
pair='NEO/BTC',
|
pair='NEO/BTC',
|
||||||
order_types=freqtrade.strategy.order_types,
|
order_types=freqtrade.strategy.order_types,
|
||||||
stop_price=0.00002346 * 0.99)
|
stop_price=0.00002346 * 0.99,
|
||||||
|
side="sell")
|
||||||
|
|
||||||
|
|
||||||
def test_enter_positions(mocker, default_conf, caplog) -> None:
|
def test_enter_positions(mocker, default_conf, caplog) -> None:
|
||||||
@ -1975,7 +1978,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order_open,
|
|||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
trade.is_open = True
|
trade.is_open = True
|
||||||
|
|
||||||
# FIX: sniffing logs, suggest handle_trade should not execute_sell
|
# FIX: sniffing logs, suggest handle_trade should not execute_exit
|
||||||
# instead that responsibility should be moved out of handle_trade(),
|
# instead that responsibility should be moved out of handle_trade(),
|
||||||
# we might just want to check if we are in a sell condition without
|
# we might just want to check if we are in a sell condition without
|
||||||
# executing
|
# executing
|
||||||
@ -2422,7 +2425,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, open_trade, mocke
|
|||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.freqtradebot.FreqtradeBot',
|
'freqtrade.freqtradebot.FreqtradeBot',
|
||||||
handle_cancel_enter=MagicMock(),
|
handle_cancel_enter=MagicMock(),
|
||||||
handle_cancel_sell=MagicMock(),
|
handle_cancel_exit=MagicMock(),
|
||||||
)
|
)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
'freqtrade.exchange.Exchange',
|
'freqtrade.exchange.Exchange',
|
||||||
@ -2503,6 +2506,7 @@ def test_handle_cancel_enter_exchanges(mocker, caplog, default_conf,
|
|||||||
reason = CANCEL_REASON['TIMEOUT']
|
reason = CANCEL_REASON['TIMEOUT']
|
||||||
trade = MagicMock()
|
trade = MagicMock()
|
||||||
trade.pair = 'LTC/ETH'
|
trade.pair = 'LTC/ETH'
|
||||||
|
trade.enter_side = "buy"
|
||||||
assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason)
|
assert freqtrade.handle_cancel_enter(trade, limit_buy_order_canceled_empty, reason)
|
||||||
assert cancel_order_mock.call_count == 0
|
assert cancel_order_mock.call_count == 0
|
||||||
assert log_has_re(r'Buy order fully cancelled. Removing .* from database\.', caplog)
|
assert log_has_re(r'Buy order fully cancelled. Removing .* from database\.', caplog)
|
||||||
@ -2531,6 +2535,7 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf, limit_buy_order,
|
|||||||
trade = MagicMock()
|
trade = MagicMock()
|
||||||
trade.pair = 'LTC/USDT'
|
trade.pair = 'LTC/USDT'
|
||||||
trade.open_rate = 200
|
trade.open_rate = 200
|
||||||
|
trade.enter_side = "buy"
|
||||||
limit_buy_order['filled'] = 0.0
|
limit_buy_order['filled'] = 0.0
|
||||||
limit_buy_order['status'] = 'open'
|
limit_buy_order['status'] = 'open'
|
||||||
reason = CANCEL_REASON['TIMEOUT']
|
reason = CANCEL_REASON['TIMEOUT']
|
||||||
@ -2543,7 +2548,7 @@ def test_handle_cancel_enter_corder_empty(mocker, default_conf, limit_buy_order,
|
|||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
def test_handle_cancel_sell_limit(mocker, default_conf, fee) -> None:
|
def test_handle_cancel_exit_limit(mocker, default_conf, fee) -> None:
|
||||||
send_msg_mock = patch_RPCManager(mocker)
|
send_msg_mock = patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
cancel_order_mock = MagicMock()
|
cancel_order_mock = MagicMock()
|
||||||
@ -2569,26 +2574,26 @@ def test_handle_cancel_sell_limit(mocker, default_conf, fee) -> None:
|
|||||||
'amount': 1,
|
'amount': 1,
|
||||||
'status': "open"}
|
'status': "open"}
|
||||||
reason = CANCEL_REASON['TIMEOUT']
|
reason = CANCEL_REASON['TIMEOUT']
|
||||||
assert freqtrade.handle_cancel_sell(trade, order, reason)
|
assert freqtrade.handle_cancel_exit(trade, order, reason)
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
assert send_msg_mock.call_count == 1
|
assert send_msg_mock.call_count == 1
|
||||||
|
|
||||||
send_msg_mock.reset_mock()
|
send_msg_mock.reset_mock()
|
||||||
|
|
||||||
order['amount'] = 2
|
order['amount'] = 2
|
||||||
assert freqtrade.handle_cancel_sell(trade, order, reason
|
assert freqtrade.handle_cancel_exit(trade, order, reason
|
||||||
) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
|
) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
|
||||||
# Assert cancel_order was not called (callcount remains unchanged)
|
# Assert cancel_order was not called (callcount remains unchanged)
|
||||||
assert cancel_order_mock.call_count == 1
|
assert cancel_order_mock.call_count == 1
|
||||||
assert send_msg_mock.call_count == 1
|
assert send_msg_mock.call_count == 1
|
||||||
assert freqtrade.handle_cancel_sell(trade, order, reason
|
assert freqtrade.handle_cancel_exit(trade, order, reason
|
||||||
) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
|
) == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
|
||||||
# Message should not be iterated again
|
# Message should not be iterated again
|
||||||
assert trade.sell_order_status == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
|
assert trade.sell_order_status == CANCEL_REASON['PARTIALLY_FILLED_KEEP_OPEN']
|
||||||
assert send_msg_mock.call_count == 1
|
assert send_msg_mock.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None:
|
def test_handle_cancel_exit_cancel_exception(mocker, default_conf) -> None:
|
||||||
patch_RPCManager(mocker)
|
patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch(
|
mocker.patch(
|
||||||
@ -2601,10 +2606,10 @@ def test_handle_cancel_sell_cancel_exception(mocker, default_conf) -> None:
|
|||||||
order = {'remaining': 1,
|
order = {'remaining': 1,
|
||||||
'amount': 1,
|
'amount': 1,
|
||||||
'status': "open"}
|
'status': "open"}
|
||||||
assert freqtrade.handle_cancel_sell(trade, order, reason) == 'error cancelling order'
|
assert freqtrade.handle_cancel_exit(trade, order, reason) == 'error cancelling order'
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None:
|
def test_execute_exit_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None:
|
||||||
rpc_mock = patch_RPCManager(mocker)
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -2632,7 +2637,7 @@ 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'],
|
freqtrade.execute_exit(trade=trade, limit=ticker_sell_up()['bid'], side="sell",
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
sell_reason=SellCheckTuple(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
|
||||||
@ -2640,7 +2645,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
|
|||||||
# 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'],
|
freqtrade.execute_exit(trade=trade, limit=ticker_sell_up()['bid'], side="sell",
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
||||||
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
assert freqtrade.strategy.confirm_trade_exit.call_count == 1
|
||||||
|
|
||||||
@ -2668,7 +2673,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> N
|
|||||||
} == last_msg
|
} == last_msg
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) -> None:
|
def test_execute_exit_down(default_conf, ticker, fee, ticker_sell_down, mocker) -> None:
|
||||||
rpc_mock = patch_RPCManager(mocker)
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
mocker.patch.multiple(
|
mocker.patch.multiple(
|
||||||
@ -2693,7 +2698,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
|
|||||||
fetch_ticker=ticker_sell_down
|
fetch_ticker=ticker_sell_down
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
|
freqtrade.execute_exit(trade=trade, limit=ticker_sell_down()['bid'], side="sell",
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||||
|
|
||||||
assert rpc_mock.call_count == 2
|
assert rpc_mock.call_count == 2
|
||||||
@ -2720,7 +2725,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker)
|
|||||||
} == last_msg
|
} == last_msg
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fee,
|
def test_execute_exit_down_stoploss_on_exchange_dry_run(default_conf, ticker, fee,
|
||||||
ticker_sell_down, mocker) -> None:
|
ticker_sell_down, mocker) -> None:
|
||||||
rpc_mock = patch_RPCManager(mocker)
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
@ -2751,7 +2756,7 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
|
|||||||
# Setting trade stoploss to 0.01
|
# Setting trade stoploss to 0.01
|
||||||
|
|
||||||
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_exit(trade=trade, limit=ticker_sell_down()['bid'], side="sell",
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||||
|
|
||||||
assert rpc_mock.call_count == 2
|
assert rpc_mock.call_count == 2
|
||||||
@ -2779,7 +2784,7 @@ def test_execute_sell_down_stoploss_on_exchange_dry_run(default_conf, ticker, fe
|
|||||||
} == last_msg
|
} == last_msg
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, caplog) -> None:
|
def test_execute_exit_sloe_cancel_exception(mocker, default_conf, ticker, fee, caplog) -> None:
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
|
mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
|
||||||
side_effect=InvalidOrderException())
|
side_effect=InvalidOrderException())
|
||||||
@ -2806,13 +2811,13 @@ def test_execute_sell_sloe_cancel_exception(mocker, default_conf, ticker, fee, c
|
|||||||
freqtrade.config['dry_run'] = False
|
freqtrade.config['dry_run'] = False
|
||||||
trade.stoploss_order_id = "abcd"
|
trade.stoploss_order_id = "abcd"
|
||||||
|
|
||||||
freqtrade.execute_sell(trade=trade, limit=1234,
|
freqtrade.execute_exit(trade=trade, limit=1234, side="sell",
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
sell_reason=SellCheckTuple(sell_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)
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticker_sell_up,
|
def test_execute_exit_with_stoploss_on_exchange(default_conf, ticker, fee, ticker_sell_up,
|
||||||
mocker) -> None:
|
mocker) -> None:
|
||||||
|
|
||||||
default_conf['exchange']['name'] = 'binance'
|
default_conf['exchange']['name'] = 'binance'
|
||||||
@ -2857,7 +2862,7 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke
|
|||||||
fetch_ticker=ticker_sell_up
|
fetch_ticker=ticker_sell_up
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
freqtrade.execute_exit(trade=trade, limit=ticker_sell_up()['bid'], side="sell",
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
||||||
|
|
||||||
trade = Trade.query.first()
|
trade = Trade.query.first()
|
||||||
@ -2866,7 +2871,7 @@ def test_execute_sell_with_stoploss_on_exchange(default_conf, ticker, fee, ticke
|
|||||||
assert rpc_mock.call_count == 3
|
assert rpc_mock.call_count == 3
|
||||||
|
|
||||||
|
|
||||||
def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, fee,
|
def test_may_execute_exit_after_stoploss_on_exchange_hit(default_conf, ticker, fee,
|
||||||
mocker) -> None:
|
mocker) -> None:
|
||||||
default_conf['exchange']['name'] = 'binance'
|
default_conf['exchange']['name'] = 'binance'
|
||||||
rpc_mock = patch_RPCManager(mocker)
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
@ -2938,7 +2943,7 @@ def test_may_execute_sell_after_stoploss_on_exchange_hit(default_conf, ticker, f
|
|||||||
assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL
|
assert rpc_mock.call_args_list[2][0][0]['type'] == RPCMessageType.SELL
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_market_order(default_conf, ticker, fee,
|
def test_execute_exit_market_order(default_conf, ticker, fee,
|
||||||
ticker_sell_up, mocker) -> None:
|
ticker_sell_up, mocker) -> None:
|
||||||
rpc_mock = patch_RPCManager(mocker)
|
rpc_mock = patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
@ -2965,7 +2970,7 @@ 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'],
|
freqtrade.execute_exit(trade=trade, limit=ticker_sell_up()['bid'], side="sell",
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
sell_reason=SellCheckTuple(sell_type=SellType.ROI))
|
||||||
|
|
||||||
assert not trade.is_open
|
assert not trade.is_open
|
||||||
@ -2996,7 +3001,7 @@ def test_execute_sell_market_order(default_conf, ticker, fee,
|
|||||||
} == last_msg
|
} == last_msg
|
||||||
|
|
||||||
|
|
||||||
def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee,
|
def test_execute_exit_insufficient_funds_error(default_conf, ticker, fee,
|
||||||
ticker_sell_up, mocker) -> None:
|
ticker_sell_up, mocker) -> None:
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds')
|
mock_insuf = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_insufficient_funds')
|
||||||
@ -3024,8 +3029,8 @@ def test_execute_sell_insufficient_funds_error(default_conf, ticker, fee,
|
|||||||
)
|
)
|
||||||
|
|
||||||
sell_reason = SellCheckTuple(sell_type=SellType.ROI)
|
sell_reason = SellCheckTuple(sell_type=SellType.ROI)
|
||||||
assert not freqtrade.execute_sell(trade=trade, limit=ticker_sell_up()['bid'],
|
assert not freqtrade.execute_exit(trade=trade, limit=ticker_sell_up()['bid'],
|
||||||
sell_reason=sell_reason)
|
sell_reason=sell_reason, side="sell")
|
||||||
assert mock_insuf.call_count == 1
|
assert mock_insuf.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
@ -3207,7 +3212,7 @@ def test_sell_not_enough_balance(default_conf, limit_buy_order, limit_buy_order_
|
|||||||
assert trade.amount != amnt
|
assert trade.amount != amnt
|
||||||
|
|
||||||
|
|
||||||
def test__safe_sell_amount(default_conf, fee, caplog, mocker):
|
def test__safe_exit_amount(default_conf, fee, caplog, mocker):
|
||||||
patch_RPCManager(mocker)
|
patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
amount = 95.33
|
amount = 95.33
|
||||||
@ -3227,17 +3232,17 @@ def test__safe_sell_amount(default_conf, fee, caplog, mocker):
|
|||||||
patch_get_signal(freqtrade)
|
patch_get_signal(freqtrade)
|
||||||
|
|
||||||
wallet_update.reset_mock()
|
wallet_update.reset_mock()
|
||||||
assert freqtrade._safe_sell_amount(trade.pair, trade.amount) == amount_wallet
|
assert freqtrade._safe_exit_amount(trade.pair, trade.amount) == amount_wallet
|
||||||
assert log_has_re(r'.*Falling back to wallet-amount.', caplog)
|
assert log_has_re(r'.*Falling back to wallet-amount.', caplog)
|
||||||
assert wallet_update.call_count == 1
|
assert wallet_update.call_count == 1
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
wallet_update.reset_mock()
|
wallet_update.reset_mock()
|
||||||
assert freqtrade._safe_sell_amount(trade.pair, amount_wallet) == amount_wallet
|
assert freqtrade._safe_exit_amount(trade.pair, amount_wallet) == amount_wallet
|
||||||
assert not log_has_re(r'.*Falling back to wallet-amount.', caplog)
|
assert not log_has_re(r'.*Falling back to wallet-amount.', caplog)
|
||||||
assert wallet_update.call_count == 1
|
assert wallet_update.call_count == 1
|
||||||
|
|
||||||
|
|
||||||
def test__safe_sell_amount_error(default_conf, fee, caplog, mocker):
|
def test__safe_exit_amount_error(default_conf, fee, caplog, mocker):
|
||||||
patch_RPCManager(mocker)
|
patch_RPCManager(mocker)
|
||||||
patch_exchange(mocker)
|
patch_exchange(mocker)
|
||||||
amount = 95.33
|
amount = 95.33
|
||||||
@ -3254,8 +3259,8 @@ def test__safe_sell_amount_error(default_conf, fee, caplog, mocker):
|
|||||||
)
|
)
|
||||||
freqtrade = FreqtradeBot(default_conf)
|
freqtrade = FreqtradeBot(default_conf)
|
||||||
patch_get_signal(freqtrade)
|
patch_get_signal(freqtrade)
|
||||||
with pytest.raises(DependencyException, match=r"Not enough amount to sell."):
|
with pytest.raises(DependencyException, match=r"Not enough amount to exit trade."):
|
||||||
assert freqtrade._safe_sell_amount(trade.pair, trade.amount)
|
assert freqtrade._safe_exit_amount(trade.pair, trade.amount)
|
||||||
|
|
||||||
|
|
||||||
def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplog) -> None:
|
def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplog) -> None:
|
||||||
@ -3281,7 +3286,7 @@ def test_locked_pairs(default_conf, ticker, fee, ticker_sell_down, mocker, caplo
|
|||||||
fetch_ticker=ticker_sell_down
|
fetch_ticker=ticker_sell_down
|
||||||
)
|
)
|
||||||
|
|
||||||
freqtrade.execute_sell(trade=trade, limit=ticker_sell_down()['bid'],
|
freqtrade.execute_exit(trade=trade, limit=ticker_sell_down()['bid'], side="sell",
|
||||||
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS))
|
sell_reason=SellCheckTuple(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)
|
||||||
@ -4221,7 +4226,7 @@ def test_cancel_all_open_orders(mocker, default_conf, fee, limit_buy_order, limi
|
|||||||
side_effect=[
|
side_effect=[
|
||||||
ExchangeError(), limit_sell_order, limit_buy_order, limit_sell_order])
|
ExchangeError(), limit_sell_order, limit_buy_order, limit_sell_order])
|
||||||
buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_enter')
|
buy_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_enter')
|
||||||
sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_sell')
|
sell_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_cancel_exit')
|
||||||
|
|
||||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||||
create_mock_trades(fee)
|
create_mock_trades(fee)
|
||||||
|
@ -9,7 +9,7 @@ from freqtrade.strategy.interface import SellCheckTuple
|
|||||||
from tests.conftest import get_patched_freqtradebot, patch_get_signal
|
from tests.conftest import get_patched_freqtradebot, patch_get_signal
|
||||||
|
|
||||||
|
|
||||||
def test_may_execute_sell_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
|
||||||
limit_buy_order, mocker) -> None:
|
limit_buy_order, mocker) -> None:
|
||||||
"""
|
"""
|
||||||
Tests workflow of selling stoploss_on_exchange.
|
Tests workflow of selling stoploss_on_exchange.
|
||||||
|
Loading…
Reference in New Issue
Block a user