This commit is contained in:
Kavinkumar 2022-02-23 12:39:58 +00:00 committed by GitHub
commit 8870126739
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 175 additions and 32 deletions

View File

@ -478,9 +478,8 @@ class FreqtradeBot(LoggingMixin):
if stake_amount is not None and stake_amount < 0.0: if stake_amount is not None and stake_amount < 0.0:
# We should decrease our position # We should decrease our position
# TODO: Selling part of the trade not implemented yet. self.execute_trade_exit(trade, current_rate, sell_reason=SellCheckTuple(
logger.error(f"Unable to decrease trade position / sell partially" sell_type=SellType.CUSTOM_SELL), sub_trade_amt=-stake_amount)
f" for pair {trade.pair}, feature not implemented.")
def _check_depth_of_market_buy(self, pair: str, conf: Dict) -> bool: def _check_depth_of_market_buy(self, pair: str, conf: Dict) -> bool:
""" """
@ -620,7 +619,7 @@ class FreqtradeBot(LoggingMixin):
# Updating wallets # Updating wallets
self.wallets.update() self.wallets.update()
self._notify_enter(trade, order, order_type) self._notify_enter(trade, order, order_type, sub_trade=pos_adjust)
if pos_adjust: if pos_adjust:
if order_status == 'closed': if order_status == 'closed':
@ -677,7 +676,7 @@ class FreqtradeBot(LoggingMixin):
return enter_limit_requested, stake_amount return enter_limit_requested, stake_amount
def _notify_enter(self, trade: Trade, order: Dict, order_type: Optional[str] = None, def _notify_enter(self, trade: Trade, order: Dict, order_type: Optional[str] = None,
fill: bool = False) -> None: fill: bool = False, sub_trade: bool = False) -> None:
""" """
Sends rpc notification when a buy occurred. Sends rpc notification when a buy occurred.
""" """
@ -693,7 +692,7 @@ class FreqtradeBot(LoggingMixin):
'trade_id': trade.id, 'trade_id': trade.id,
'type': RPCMessageType.BUY_FILL if fill else RPCMessageType.BUY, 'type': RPCMessageType.BUY_FILL if fill else RPCMessageType.BUY,
'buy_tag': trade.buy_tag, 'buy_tag': trade.buy_tag,
'exchange': self.exchange.name.capitalize(), 'exchange': trade.exchange.capitalize(),
'pair': trade.pair, 'pair': trade.pair,
'limit': open_rate, # Deprecated (?) 'limit': open_rate, # Deprecated (?)
'open_rate': open_rate, 'open_rate': open_rate,
@ -701,15 +700,17 @@ class FreqtradeBot(LoggingMixin):
'stake_amount': trade.stake_amount, 'stake_amount': trade.stake_amount,
'stake_currency': self.config['stake_currency'], 'stake_currency': self.config['stake_currency'],
'fiat_currency': self.config.get('fiat_display_currency', None), 'fiat_currency': self.config.get('fiat_display_currency', None),
'amount': safe_value_fallback(order, 'filled', 'amount') or trade.amount, 'amount': order.get('filled') or order.get('amount'),
'open_date': trade.open_date or datetime.utcnow(), 'open_date': trade.open_date or datetime.utcnow(),
'current_rate': current_rate, 'current_rate': current_rate,
'sub_trade': sub_trade,
} }
# Send the message # Send the message
self.rpc.send_msg(msg) self.rpc.send_msg(msg)
def _notify_enter_cancel(self, trade: Trade, order_type: str, reason: str) -> None: def _notify_enter_cancel(self, trade: Trade, order_type: str, reason: str,
sub_trade: bool = False) -> None:
""" """
Sends rpc notification when a buy cancel occurred. Sends rpc notification when a buy cancel occurred.
""" """
@ -730,6 +731,7 @@ class FreqtradeBot(LoggingMixin):
'open_date': trade.open_date, 'open_date': trade.open_date,
'current_rate': current_rate, 'current_rate': current_rate,
'reason': reason, 'reason': reason,
'sub_trade': sub_trade,
} }
# Send the message # Send the message
@ -1153,6 +1155,7 @@ class FreqtradeBot(LoggingMixin):
*, *,
exit_tag: Optional[str] = None, exit_tag: Optional[str] = None,
ordertype: Optional[str] = None, ordertype: Optional[str] = None,
sub_trade_amt: float = None,
) -> bool: ) -> bool:
""" """
Executes a trade exit for the given trade and limit Executes a trade exit for the given trade and limit
@ -1190,7 +1193,7 @@ class FreqtradeBot(LoggingMixin):
# 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")
amount = self._safe_exit_amount(trade.pair, trade.amount) amount = sub_trade_amt or 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']
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)(
@ -1225,15 +1228,17 @@ class FreqtradeBot(LoggingMixin):
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')
self._notify_exit(trade, order_type) self._notify_exit(trade, order_type, sub_trade=bool(sub_trade_amt))
# In case of market sell orders the order can be closed immediately # In case of market sell orders the order can be closed immediately
if order.get('status', 'unknown') in ('closed', 'expired'): if order.get('status', 'unknown') in ('closed', 'expired'):
self.update_trade_state(trade, trade.open_order_id, order) self.update_trade_state(trade, trade.open_order_id, order,
sub_trade=bool(sub_trade_amt))
Trade.commit() Trade.commit()
return True return True
def _notify_exit(self, trade: Trade, order_type: str, fill: bool = False) -> None: def _notify_exit(self, trade: Trade, order_type: str, fill: bool = False,
sub_trade: bool = False) -> None:
""" """
Sends rpc notification when a sell occurred. Sends rpc notification when a sell occurred.
""" """
@ -1264,9 +1269,11 @@ class FreqtradeBot(LoggingMixin):
'sell_reason': trade.sell_reason, 'sell_reason': trade.sell_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_amount': trade.stake_amount,
'stake_currency': self.config['stake_currency'], 'stake_currency': self.config['stake_currency'],
'fiat_currency': self.config.get('fiat_display_currency', None), 'fiat_currency': self.config.get('fiat_display_currency', None),
} 'sub_trade': sub_trade,
}
if 'fiat_display_currency' in self.config: if 'fiat_display_currency' in self.config:
msg.update({ msg.update({
@ -1276,7 +1283,8 @@ class FreqtradeBot(LoggingMixin):
# Send the message # Send the message
self.rpc.send_msg(msg) self.rpc.send_msg(msg)
def _notify_exit_cancel(self, trade: Trade, order_type: str, reason: str) -> None: def _notify_exit_cancel(self, trade: Trade, order_type: str, reason: str,
sub_trade: bool = False) -> None:
""" """
Sends rpc notification when a sell cancel occurred. Sends rpc notification when a sell cancel occurred.
""" """
@ -1311,6 +1319,8 @@ class FreqtradeBot(LoggingMixin):
'stake_currency': self.config['stake_currency'], 'stake_currency': self.config['stake_currency'],
'fiat_currency': self.config.get('fiat_display_currency', None), 'fiat_currency': self.config.get('fiat_display_currency', None),
'reason': reason, 'reason': reason,
'sub_trade': sub_trade,
'stake_amount': trade.stake_amount,
} }
if 'fiat_display_currency' in self.config: if 'fiat_display_currency' in self.config:
@ -1326,7 +1336,8 @@ class FreqtradeBot(LoggingMixin):
# #
def update_trade_state(self, trade: Trade, order_id: str, action_order: Dict[str, Any] = None, def update_trade_state(self, trade: Trade, order_id: str, action_order: Dict[str, Any] = None,
stoploss_order: bool = False, send_msg: bool = True) -> bool: stoploss_order: bool = False, send_msg: bool = True,
sub_trade: bool = False) -> bool:
""" """
Checks trades with open orders and updates the amount if necessary Checks trades with open orders and updates the amount if necessary
Handles closing both buy and sell orders. Handles closing both buy and sell orders.
@ -1359,7 +1370,7 @@ class FreqtradeBot(LoggingMixin):
order = self.handle_order_fee(trade, order) order = self.handle_order_fee(trade, order)
trade.update(order) trade.update(order, sub_trade=sub_trade)
trade.recalc_trade_from_orders() trade.recalc_trade_from_orders()
Trade.commit() Trade.commit()
@ -1372,11 +1383,11 @@ class FreqtradeBot(LoggingMixin):
if not trade.is_open: if not trade.is_open:
if send_msg and not stoploss_order and not trade.open_order_id: if send_msg and not stoploss_order and not trade.open_order_id:
self._notify_exit(trade, '', True) self._notify_exit(trade, '', True, sub_trade=sub_trade)
self.handle_protections(trade.pair) self.handle_protections(trade.pair)
elif send_msg and not trade.open_order_id: elif send_msg and not trade.open_order_id:
# Buy fill # Buy fill
self._notify_enter(trade, order, fill=True) self._notify_enter(trade, order, fill=True, sub_trade=sub_trade)
return False return False

View File

@ -119,6 +119,7 @@ class Order(_DECL_BASE):
ft_order_side = Column(String(25), nullable=False) ft_order_side = Column(String(25), nullable=False)
ft_pair = Column(String(25), nullable=False) ft_pair = Column(String(25), nullable=False)
ft_is_open = Column(Boolean, nullable=False, default=True, index=True) ft_is_open = Column(Boolean, nullable=False, default=True, index=True)
is_fully_realized = Column(Boolean, nullable=True, default=False)
order_id = Column(String(255), nullable=False, index=True) order_id = Column(String(255), nullable=False, index=True)
status = Column(String(255), nullable=True) status = Column(String(255), nullable=True)
@ -455,7 +456,7 @@ class LocalTrade():
f"Trailing stoploss saved us: " f"Trailing stoploss saved us: "
f"{float(self.stop_loss) - float(self.initial_stop_loss):.8f}.") f"{float(self.stop_loss) - float(self.initial_stop_loss):.8f}.")
def update(self, order: Dict) -> None: def update(self, order: Dict, sub_trade: bool = False) -> None:
""" """
Updates this entity with amount and actual open/close rates. Updates this entity with amount and actual open/close rates.
:param order: order retrieved by exchange.fetch_order() :param order: order retrieved by exchange.fetch_order()
@ -479,7 +480,13 @@ class LocalTrade():
elif order_type in ('market', 'limit') and order['side'] == 'sell': elif order_type in ('market', 'limit') and order['side'] == 'sell':
if self.is_open: if self.is_open:
logger.info(f'{order_type.upper()}_SELL has been fulfilled for {self}.') logger.info(f'{order_type.upper()}_SELL has been fulfilled for {self}.')
self.close(safe_value_fallback(order, 'average', 'price')) self.open_order_id = None
if sub_trade or 1:
logger.info(f'debug1:{sub_trade}')
self.process_sell_sub_trade(order)
return
# else:
# self.close(safe_value_fallback(order, 'average', 'price'))
elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'): elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'):
self.stoploss_order_id = None self.stoploss_order_id = None
self.close_rate_requested = self.stop_loss self.close_rate_requested = self.stop_loss
@ -491,6 +498,44 @@ class LocalTrade():
raise ValueError(f'Unknown order type: {order_type}') raise ValueError(f'Unknown order type: {order_type}')
Trade.commit() Trade.commit()
def process_sell_sub_trade(self, order: Dict) -> None:
orders = (self.select_filled_orders('buy'))
sell_rate = float(safe_value_fallback(order, 'average', 'price'))
sell_amount = float(safe_value_fallback(order, 'filled', 'amount'))
profit = 0.0
idx = -1
while sell_amount:
b_order = orders[idx]
buy_amount = b_order.filled or b_order.amount
buy_rate = b_order.average or b_order.price
if sell_amount < buy_amount:
amount = sell_amount
b_order.filled -= amount
else:
if sell_amount == self.amount:
self.close(safe_value_fallback(order, 'average', 'price'))
Trade.commit()
return
b_order.is_fully_realized = True
self.update_order(b_order)
idx -= 1
amount = buy_amount
sell_amount -= amount
profit += self.calc_profit2(buy_rate, sell_rate, amount)
b_order2 = orders[idx]
amount2 = b_order2.filled or b_order2.amount
b_order2.average = (b_order2.average * amount2 - profit) / amount2
self.update_order(b_order2)
Order.query.session.commit()
self.recalc_trade_from_orders()
Trade.commit()
def calc_profit2(self, open_rate: float, close_rate: float,
amount: float) -> float:
return float(Decimal(amount) *
(Decimal(1 - self.fee_close) * Decimal(close_rate) -
Decimal(1 + self.fee_open) * Decimal(open_rate)))
def close(self, rate: float, *, show_msg: bool = True) -> None: def close(self, rate: float, *, show_msg: bool = True) -> None:
""" """
Sets close_rate to the given rate, calculates total profit Sets close_rate to the given rate, calculates total profit
@ -628,6 +673,7 @@ class LocalTrade():
for o in self.orders: for o in self.orders:
if (o.ft_is_open or if (o.ft_is_open or
(o.ft_order_side != 'buy') or (o.ft_order_side != 'buy') or
o.is_fully_realized or
(o.status not in NON_OPEN_EXCHANGE_STATES)): (o.status not in NON_OPEN_EXCHANGE_STATES)):
continue continue
@ -685,6 +731,7 @@ class LocalTrade():
return [o for o in self.orders if ((o.ft_order_side == order_side) or (order_side is None)) return [o for o in self.orders if ((o.ft_order_side == order_side) or (order_side is None))
and o.ft_is_open is False and and o.ft_is_open is False and
(o.filled or 0) > 0 and (o.filled or 0) > 0 and
not o.is_fully_realized and
o.status in NON_OPEN_EXCHANGE_STATES] o.status in NON_OPEN_EXCHANGE_STATES]
@property @property

View File

@ -235,17 +235,31 @@ class Telegram(RPCHandler):
if msg['type'] == RPCMessageType.BUY_FILL: if msg['type'] == RPCMessageType.BUY_FILL:
message += f"*Open Rate:* `{msg['open_rate']:.8f}`\n" message += f"*Open Rate:* `{msg['open_rate']:.8f}`\n"
total = msg['amount'] * msg['open_rate']
elif msg['type'] == RPCMessageType.BUY: elif msg['type'] == RPCMessageType.BUY:
message += f"*Open Rate:* `{msg['limit']:.8f}`\n"\ message += f"*Open Rate:* `{msg['limit']:.8f}`\n"\
f"*Current Rate:* `{msg['current_rate']:.8f}`\n" f"*Current Rate:* `{msg['current_rate']:.8f}`\n"
total = msg['amount'] * msg['limit']
message += f"*Total:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}" if self._rpc._fiat_converter:
total_fiat = self._rpc._fiat_converter.convert_amount(
total, msg['stake_currency'], msg['fiat_currency'])
else:
total_fiat = 0
message += f"*Total:* `({round_coin_value(total, msg['stake_currency'])}"
if msg.get('fiat_currency', None): if msg.get('fiat_currency', None):
message += f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}" message += f", {round_coin_value(total_fiat, msg['fiat_currency'])}"
message += ")`" message += ")`"
if msg.get('sub_trade'):
bal = round_coin_value(msg['stake_amount'], msg['stake_currency'])
message += f"\n*Balance:* `({bal}"
if msg.get('fiat_currency', None):
message += f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}"
message += ")`"
return message return message
def _format_sell_msg(self, msg: Dict[str, Any]) -> str: def _format_sell_msg(self, msg: Dict[str, Any]) -> str:
@ -287,7 +301,19 @@ class Telegram(RPCHandler):
elif msg['type'] == RPCMessageType.SELL_FILL: elif msg['type'] == RPCMessageType.SELL_FILL:
message += f"*Close Rate:* `{msg['close_rate']:.8f}`" message += f"*Close Rate:* `{msg['close_rate']:.8f}`"
if msg.get('sub_trade'):
if self._rpc._fiat_converter:
msg['stake_amount_fiat'] = self._rpc._fiat_converter.convert_amount(
msg['stake_amount'], msg['stake_currency'], msg['fiat_currency'])
else:
msg['stake_amount_fiat'] = 0
bal = round_coin_value(msg['stake_amount'], msg['stake_currency'])
message += f"\n*Balance:* `({bal}"
if msg.get('fiat_currency', None):
message += f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}"
message += ")`"
return message return message
def compose_message(self, msg: Dict[str, Any], msg_type: RPCMessageType) -> str: def compose_message(self, msg: Dict[str, Any], msg_type: RPCMessageType) -> str:
@ -388,12 +414,14 @@ class Telegram(RPCHandler):
else: else:
sumA = 0 sumA = 0
sumB = 0 sumB = 0
first_order_price = filled_orders[0]["average"] or filled_orders[0]["price"]
for y in range(x): for y in range(x):
sumA += (filled_orders[y]["amount"] * filled_orders[y]["average"]) sumA += (filled_orders[y]["amount"] * (filled_orders[y]["average"]
or filled_orders[y]["price"]))
sumB += filled_orders[y]["amount"] sumB += filled_orders[y]["amount"]
prev_avg_price = sumA/sumB prev_avg_price = sumA/sumB
price_to_1st_entry = ((cur_entry_average - filled_orders[0]["average"]) price_to_1st_entry = ((cur_entry_average - first_order_price)
/ filled_orders[0]["average"]) / first_order_price)
minus_on_entry = (cur_entry_average - prev_avg_price)/prev_avg_price minus_on_entry = (cur_entry_average - prev_avg_price)/prev_avg_price
dur_entry = cur_entry_datetime - arrow.get(filled_orders[x-1]["order_filled_date"]) dur_entry = cur_entry_datetime - arrow.get(filled_orders[x-1]["order_filled_date"])
days = dur_entry.days days = dur_entry.days
@ -535,7 +563,9 @@ class Telegram(RPCHandler):
reload_able=True, callback_path="update_status_table", reload_able=True, callback_path="update_status_table",
query=update.callback_query) query=update.callback_query)
except RPCException as e: except RPCException as e:
self._send_msg(str(e)) self._send_msg(str(e), reload_able=True,
callback_path="update_status_table",
query=update.callback_query)
@authorized_only @authorized_only
def _daily(self, update: Update, context: CallbackContext) -> None: def _daily(self, update: Update, context: CallbackContext) -> None:

View File

@ -1037,6 +1037,8 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
'stake_amount': 0.0009999999999054,
'sub_trade': False
} == last_msg } == last_msg
@ -1102,6 +1104,8 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
'stake_amount': 0.0009999999999054,
'sub_trade': False
} == last_msg } == last_msg
@ -1157,6 +1161,8 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
'stake_amount': 0.0009999999999054,
'sub_trade': False
} == msg } == msg

View File

@ -2622,7 +2622,9 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
} == last_msg 'sub_trade': False,
'stake_amount': 60.0,
} == last_msg
def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down, def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usdt_sell_down,
@ -2676,7 +2678,9 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
} == last_msg 'sub_trade': False,
'stake_amount': 60.0,
} == last_msg
def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fee, def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fee,
@ -2744,7 +2748,9 @@ def test_execute_trade_exit_custom_exit_price(default_conf_usdt, ticker_usdt, fe
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
} == last_msg 'sub_trade': False,
'stake_amount': 60.0,
} == last_msg
def test_execute_trade_exit_down_stoploss_on_exchange_dry_run( def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
@ -2804,6 +2810,8 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
'sub_trade': False,
'stake_amount': 60.0,
} == last_msg } == last_msg
@ -3022,6 +3030,8 @@ def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee,
'open_date': ANY, 'open_date': ANY,
'close_date': ANY, 'close_date': ANY,
'close_rate': ANY, 'close_rate': ANY,
'sub_trade': False,
'stake_amount': 60.0,
} == last_msg } == last_msg
@ -4312,7 +4322,6 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
get_fee=fee, get_fee=fee,
) )
pair = 'ETH/USDT' pair = 'ETH/USDT'
# Initial buy # Initial buy
closed_successful_buy_order = { closed_successful_buy_order = {
'pair': pair, 'pair': pair,
@ -4377,7 +4386,7 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
'status': None, 'status': None,
'price': 9, 'price': 9,
'amount': 12, 'amount': 12,
'cost': 100, 'cost': 108,
'ft_is_open': True, 'ft_is_open': True,
'id': '651', 'id': '651',
'order_id': '651' 'order_id': '651'
@ -4524,6 +4533,46 @@ def test_position_adjust(mocker, default_conf_usdt, fee) -> None:
# Make sure the closed order is found as the second order. # Make sure the closed order is found as the second order.
order = trade.select_order('buy', False) order = trade.select_order('buy', False)
assert order.order_id == '652' assert order.order_id == '652'
closed_sell_dca_order_1 = {
'ft_pair': pair,
'status': 'closed',
'ft_order_side': 'sell',
'side': 'sell',
'type': 'limit',
'price': 8,
'average': 8,
'amount': 15,
'filled': 15,
'cost': 120,
'ft_is_open': False,
'id': '653',
'order_id': '653'
}
mocker.patch('freqtrade.exchange.Exchange.create_order',
MagicMock(return_value=closed_sell_dca_order_1))
mocker.patch('freqtrade.exchange.Exchange.fetch_order',
MagicMock(return_value=closed_sell_dca_order_1))
mocker.patch('freqtrade.exchange.Exchange.fetch_order_or_stoploss_order',
MagicMock(return_value=closed_sell_dca_order_1))
assert freqtrade.execute_trade_exit(trade=trade, limit=8,
sell_reason=SellCheckTuple(sell_type=SellType.STOP_LOSS),
sub_trade_amt=15)
# Assert trade is as expected (averaged dca)
trade = Trade.query.first()
assert trade
assert trade.open_order_id is None
assert trade.amount == 22
assert trade.stake_amount == 203.5625
assert pytest.approx(trade.open_rate) == 9.252840909090908
orders = Order.query.all()
assert orders
assert len(orders) == 4
# Make sure the closed order is found as the second order.
order = trade.select_order('sell', False)
assert order.order_id == '653'
def test_process_open_trade_positions_exception(mocker, default_conf_usdt, fee, caplog) -> None: def test_process_open_trade_positions_exception(mocker, default_conf_usdt, fee, caplog) -> None: