diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index fce85baa3..71daf741c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -478,9 +478,9 @@ class FreqtradeBot(LoggingMixin): if stake_amount is not None and stake_amount < 0.0: # We should decrease our position - # TODO: Selling part of the trade not implemented yet. - logger.error(f"Unable to decrease trade position / sell partially" - f" for pair {trade.pair}, feature not implemented.") + proposed_exit_rate = self.exchange.get_rate(pair, refresh=True, side="buy") + self.execute_trade_exit(trade, proposed_exit_rate, sell_reason=SellCheckTuple( + sell_type=SellType.CUSTOM_SELL), partial=stake_amount) def _check_depth_of_market_buy(self, pair: str, conf: Dict) -> bool: """ @@ -620,7 +620,7 @@ class FreqtradeBot(LoggingMixin): # Updating wallets self.wallets.update() - self._notify_enter(trade, order, order_type) + self._notify_enter(trade, order, order_type, partial=pos_adjust) if pos_adjust: if order_status == 'closed': @@ -677,7 +677,7 @@ class FreqtradeBot(LoggingMixin): return enter_limit_requested, stake_amount def _notify_enter(self, trade: Trade, order: Dict, order_type: Optional[str] = None, - fill: bool = False) -> None: + fill: bool = False, partial: bool = False) -> None: """ Sends rpc notification when a buy occurred. """ @@ -693,7 +693,7 @@ class FreqtradeBot(LoggingMixin): 'trade_id': trade.id, 'type': RPCMessageType.BUY_FILL if fill else RPCMessageType.BUY, 'buy_tag': trade.buy_tag, - 'exchange': self.exchange.name.capitalize(), + 'exchange': trade.exchange.capitalize(), 'pair': trade.pair, 'limit': open_rate, # Deprecated (?) 'open_rate': open_rate, @@ -704,6 +704,7 @@ class FreqtradeBot(LoggingMixin): 'amount': safe_value_fallback(order, 'filled', 'amount') or trade.amount, 'open_date': trade.open_date or datetime.utcnow(), 'current_rate': current_rate, + 'partial': partial, } # Send the message @@ -1153,6 +1154,7 @@ class FreqtradeBot(LoggingMixin): *, exit_tag: Optional[str] = None, ordertype: Optional[str] = None, + partial: float = None, ) -> bool: """ Executes a trade exit for the given trade and limit @@ -1225,7 +1227,7 @@ class FreqtradeBot(LoggingMixin): self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc), reason='Auto lock') - self._notify_exit(trade, order_type) + self._notify_exit(trade, order_type, partial=bool(partial)) # In case of market sell orders the order can be closed immediately if order.get('status', 'unknown') in ('closed', 'expired'): self.update_trade_state(trade, trade.open_order_id, order) @@ -1233,7 +1235,7 @@ class FreqtradeBot(LoggingMixin): 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, partial: bool = False) -> None: """ Sends rpc notification when a sell occurred. """ @@ -1264,8 +1266,10 @@ class FreqtradeBot(LoggingMixin): 'sell_reason': trade.sell_reason, 'open_date': trade.open_date, 'close_date': trade.close_date or datetime.utcnow(), + 'stake_amount': trade.stake_amount, 'stake_currency': self.config['stake_currency'], 'fiat_currency': self.config.get('fiat_display_currency', None), + 'partial': partial, } if 'fiat_display_currency' in self.config: diff --git a/freqtrade/persistence/models.py b/freqtrade/persistence/models.py index 5f2db1050..61f60301f 100644 --- a/freqtrade/persistence/models.py +++ b/freqtrade/persistence/models.py @@ -476,7 +476,18 @@ class LocalTrade(): elif order_type in ('market', 'limit') and order['side'] == 'sell': if self.is_open: 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 partial: + orders=(self.select_filled_orders('buy')) + lbuy=orders[-2] + lamount = (lbuy.filled or lbuy.amount) + lbuy.average=(lbuy.average * lamount - self.calc_profit2(orders[-1].rate, order.rate, order.filled or order.amount))/lamount + Order.query.session.commit() + self.orders.remove(orders[-1]) + self.recalc_trade_from_orders() + Trade.commit() + else: + self.close(safe_value_fallback(order, 'average', 'price')) elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'): self.stoploss_order_id = None self.close_rate_requested = self.stop_loss @@ -488,6 +499,8 @@ class LocalTrade(): raise ValueError(f'Unknown order type: {order_type}') Trade.commit() + def calc_profit2(self, open_rate: float, close_rate: float, amount: Float) ->float: + return Decimal(self.amount) *( (1-self.fee_close)* Decimal(close_rate) -(1+self.fee_open)* Decimal(open_rate) ) def close(self, rate: float, *, show_msg: bool = True) -> None: """ Sets close_rate to the given rate, calculates total profit diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 0a634ffae..fa2d2aecd 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -235,17 +235,30 @@ class Telegram(RPCHandler): if msg['type'] == RPCMessageType.BUY_FILL: message += f"*Open Rate:* `{msg['open_rate']:.8f}`\n" + total = msg['amount'] * msg['open_rate'] elif msg['type'] == RPCMessageType.BUY: message += f"*Open Rate:* `{msg['limit']:.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(msg['stake_amount'], msg['stake_currency'])}" if msg.get('fiat_currency', None): - message += f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}" + message += f", {round_coin_value(total, msg['fiat_currency'])}" - message += ")`" + message += ")`\n" + if msg['partial']: + message += f"*Balance:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}" + + if msg.get('fiat_currency', None): + message += f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}" + + message += ")`" return message def _format_sell_msg(self, msg: Dict[str, Any]) -> str: @@ -287,7 +300,18 @@ class Telegram(RPCHandler): elif msg['type'] == RPCMessageType.SELL_FILL: message += f"*Close Rate:* `{msg['close_rate']:.8f}`" + 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 + if msg['partial']: + message += f"*Balance:* `({round_coin_value(msg['stake_amount'], msg['stake_currency'])}" + if msg.get('fiat_currency', None): + message += f", {round_coin_value(msg['stake_amount_fiat'], msg['fiat_currency'])}" + + message += ")`" return message def compose_message(self, msg: Dict[str, Any], msg_type: RPCMessageType) -> str: