Merge 06d14b81dd
into afd2be06d8
This commit is contained in:
commit
8870126739
@ -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,8 +1269,10 @@ 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:
|
||||||
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -235,12 +235,26 @@ 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']
|
||||||
|
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'])}"
|
||||||
|
|
||||||
message += f"*Total:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}"
|
if msg.get('fiat_currency', None):
|
||||||
|
message += f", {round_coin_value(total_fiat, msg['fiat_currency'])}"
|
||||||
|
|
||||||
|
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):
|
if msg.get('fiat_currency', None):
|
||||||
message += f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}"
|
message += f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}"
|
||||||
@ -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:
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -2622,6 +2622,8 @@ 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,
|
||||||
|
'sub_trade': False,
|
||||||
|
'stake_amount': 60.0,
|
||||||
} == last_msg
|
} == last_msg
|
||||||
|
|
||||||
|
|
||||||
@ -2676,6 +2678,8 @@ 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,
|
||||||
|
'sub_trade': False,
|
||||||
|
'stake_amount': 60.0,
|
||||||
} == last_msg
|
} == last_msg
|
||||||
|
|
||||||
|
|
||||||
@ -2744,6 +2748,8 @@ 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,
|
||||||
|
'sub_trade': False,
|
||||||
|
'stake_amount': 60.0,
|
||||||
} == last_msg
|
} == last_msg
|
||||||
|
|
||||||
|
|
||||||
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user