From 7c85c03d1033d2b0872bf0b1c49a29afb1ca5c01 Mon Sep 17 00:00:00 2001 From: algizn97 Date: Sun, 9 Nov 2025 13:10:13 +0500 Subject: [PATCH] The logic of the trading cycle has been changed, fixed errors when setting TP and SL --- app/bybit/open_positions.py | 7 +- app/bybit/set_functions/set_tp_sl.py | 22 +- app/bybit/telegram_message_handler.py | 303 +++++++++++++------------- app/telegram/handlers/stop_trading.py | 4 +- 4 files changed, 178 insertions(+), 158 deletions(-) diff --git a/app/bybit/open_positions.py b/app/bybit/open_positions.py index 2c14636..680a76f 100644 --- a/app/bybit/open_positions.py +++ b/app/bybit/open_positions.py @@ -75,6 +75,12 @@ async def start_trading_cycle( commission_place=commission_place, pnl_series=0 ) + await rq.set_total_fee_user_auto_trading( + tg_id=tg_id, symbol=symbol, total_fee=0 + ) + await rq.set_fee_user_auto_trading( + tg_id=tg_id, symbol=symbol, fee=0 + ) res = await open_positions( tg_id=tg_id, @@ -343,7 +349,6 @@ async def open_positions( "triggerBy": "LastPrice", "timeInForce": "GTC", "positionIdx": 0, - "tpslMode": "Full", } response = client.place_order(**order_params) diff --git a/app/bybit/set_functions/set_tp_sl.py b/app/bybit/set_functions/set_tp_sl.py index 7c95a60..76dd2bc 100644 --- a/app/bybit/set_functions/set_tp_sl.py +++ b/app/bybit/set_functions/set_tp_sl.py @@ -13,7 +13,7 @@ async def set_tp_sl_for_position( take_profit_price: float, stop_loss_price: float, position_idx: int, -) -> bool: +) -> bool | str: """ Set take profit and stop loss for a symbol. :param tg_id: Telegram user ID @@ -21,7 +21,7 @@ async def set_tp_sl_for_position( :param take_profit_price: Take profit price :param stop_loss_price: Stop loss price :param position_idx: Position index - :return: bool + :return: bool or str """ try: client = await get_bybit_client(tg_id) @@ -40,8 +40,18 @@ async def set_tp_sl_for_position( logger.info("TP/SL for %s has been set", symbol) return True else: - logger.error("Error setting TP/SL for %s: %s", symbol, resp.get("retMsg")) - return False + error_msg = resp.get("retMsg") + if "not modified" in error_msg.lower(): + logger.info("TP/SL for %s not modified: %s", symbol, error_msg) + return "not modified" + else: + logger.error("Error setting TP/SL for %s: %s", symbol, error_msg) + return False except Exception as e: - logger.error("Error setting TP/SL for %s: %s", symbol, e) - return False + error_msg = str(e) + if "not modified" in error_msg.lower(): + logger.info("TP/SL for %s not modified: %s", symbol, error_msg) + return "not modified" + else: + logger.error("Error set TP/SL for %s: %s", symbol, e) + return False diff --git a/app/bybit/telegram_message_handler.py b/app/bybit/telegram_message_handler.py index ed20cc5..f2a3407 100644 --- a/app/bybit/telegram_message_handler.py +++ b/app/bybit/telegram_message_handler.py @@ -16,10 +16,13 @@ logger = logging.getLogger("telegram_message_handler") class TelegramMessageHandler: def __init__(self, telegram_bot): + """Initialize the TelegramMessageHandler class.""" self.telegram_bot = telegram_bot async def format_order_update(self, message, tg_id): + """Handle order updates.""" try: + # logger.info("Order update: %s", json.dumps(message)) user_additional_data = await rq.get_user_additional_settings(tg_id=tg_id) trigger_price = safe_float(user_additional_data.trigger_price) if trigger_price > 0: @@ -56,6 +59,7 @@ class TelegramMessageHandler: logger.error("Error in format_order_update: %s", e) async def format_execution_update(self, message, tg_id): + """Handle execution updates without duplicate processing.""" try: # logger.info("Execution update: %s", json.dumps(message)) execution = message.get("data", [{}])[0] @@ -68,6 +72,9 @@ class TelegramMessageHandler: exec_fees = format_value(execution.get("execFee")) fee_rate = format_value(execution.get("feeRate")) side = format_value(execution.get("side")) + exec_pnl = format_value(execution.get("execPnl")) + stop_order_type = format_value(execution.get("stopOrderType")) + create_type = format_value(execution.get("createType")) user_auto_trading = await rq.get_user_auto_trading( tg_id=tg_id, symbol=symbol @@ -76,175 +83,154 @@ class TelegramMessageHandler: user_auto_trading.auto_trading if user_auto_trading else False ) - side_rus = ( - "Покупка" - if side == "Buy" - else "Продажа" if side == "Sell" else "Нет данных" - ) - if safe_float(exec_fees) == 0: - exec_fee = safe_float(exec_price) * safe_float(exec_qty) * safe_float( - fee_rate + if auto_trading: + side_rus = ( + "Покупка" + if side == "Buy" + else "Продажа" if side == "Sell" else "Нет данных" ) - else: - exec_fee = safe_float(exec_fees) - - if safe_float(closed_size) == 0: - await rq.set_fee_user_auto_trading( - tg_id=tg_id, symbol=symbol, fee=safe_float(exec_fee) - ) - - get_total_fee = 0 - - if user_auto_trading is not None: - get_total_fee = user_auto_trading.total_fee - - total_fee = safe_float(exec_fee) + safe_float(get_total_fee) - - exec_pnl = format_value(execution.get("execPnl")) - ex_pnl = safe_float(exec_pnl) - pnl = safe_float(exec_pnl) - - header = ( - "Сделка закрыта:" if safe_float(closed_size) > 0 else "Сделка открыта:" - ) - text = f"{header}\n" f"Торговая пара: {symbol}\n" - user_deals_data = await rq.get_user_deal_by_symbol( - tg_id=tg_id, symbol=symbol - ) - if user_deals_data is None: - commission_fee = "Yes_commission_fee" - commission_place = "Commission_for_qty" - - else: - commission_fee = user_deals_data.commission_fee or "Yes_commission_fee" - commission_place = user_deals_data.commission_place or "Commission_for_qty" - - current_series = user_deals_data.current_series - current_step = user_deals_data.current_step - order_quantity = user_deals_data.order_quantity - pnl_series = user_deals_data.pnl_series - margin_type = user_deals_data.margin_type - take_profit_percent = user_deals_data.take_profit_percent - stop_loss_percent = user_deals_data.stop_loss_percent - fee = safe_float(user_auto_trading.fee) - total_pnl = safe_float(exec_pnl) - safe_float(exec_fee) - fee - leverage = safe_float(user_deals_data.leverage) - - if commission_fee == "Yes_commission_fee": - if commission_place == "Commission_for_qty": - total_quantity = safe_float(order_quantity) + safe_float( - total_fee - ) * 2 + if safe_float(exec_fees) == 0: + exec_fee = safe_float(exec_price) * safe_float(exec_qty) * safe_float( + fee_rate + ) else: - total_quantity = safe_float(order_quantity) - else: - total_quantity = safe_float(order_quantity) + exec_fee = safe_float(exec_fees) - if user_deals_data is not None and auto_trading and safe_float(closed_size) == 0: - await rq.set_total_fee_user_auto_trading( - tg_id=tg_id, symbol=symbol, total_fee=total_fee + if safe_float(closed_size) == 0: + await rq.set_fee_user_auto_trading( + tg_id=tg_id, symbol=symbol, fee=safe_float(exec_fee) + ) + + get_total_fee = 0 + + if user_auto_trading is not None: + get_total_fee = user_auto_trading.total_fee + + total_fee = safe_float(exec_fee) + safe_float(get_total_fee) + ex_pnl = safe_float(exec_pnl) + + header = ( + "Сделка закрыта:" if safe_float(closed_size) > 0 else "Сделка открыта:" + ) + text = f"{header}\n" f"Торговая пара: {symbol}\n" + user_deals_data = await rq.get_user_deal_by_symbol( + tg_id=tg_id, symbol=symbol ) - text += f"Текущая ставка: {total_quantity:.2f} USDT\n" - text += f"Серия №: {current_series}\n" - text += f"Сделка №: {current_step}\n" - text += ( - f"Цена исполнения: {exec_price}\n" - f"Комиссия: {exec_fee:.8f}\n" - ) - - if safe_float(closed_size) == 0: - instruments_info = await get_instruments_info(tg_id=tg_id, symbol=symbol) - qty_step_str = instruments_info.get("lotSizeFilter").get("qtyStep") - qty_step = safe_float(qty_step_str) - qty = (safe_float(order_quantity) * safe_float(leverage)) / safe_float(exec_price) - decimals = abs(int(round(math.log10(qty_step)))) - qty_format = math.floor(qty / qty_step) * qty_step - qty_formatted = round(qty_format, decimals) - total_commission = 0 + commission_fee = user_deals_data.commission_fee + commission_place = user_deals_data.commission_place + current_series = user_deals_data.current_series + current_step = user_deals_data.current_step + order_quantity = user_deals_data.order_quantity + pnl_series = user_deals_data.pnl_series + margin_type = user_deals_data.margin_type + take_profit_percent = user_deals_data.take_profit_percent + stop_loss_percent = user_deals_data.stop_loss_percent + leverage = safe_float(user_deals_data.leverage) + fee = safe_float(user_auto_trading.fee) + total_pnl = safe_float(exec_pnl) - safe_float(exec_fee) - fee if commission_fee == "Yes_commission_fee": - if commission_place == "Commission_for_tp": - total_commission = safe_float(total_fee) / qty_formatted - - if margin_type == "ISOLATED_MARGIN": - if side == "Buy": - take_profit_price = safe_float(exec_price) * ( - 1 + take_profit_percent / 100) + total_commission - stop_loss_price = None + if commission_place == "Commission_for_qty": + total_quantity = safe_float(order_quantity) + safe_float( + total_fee + ) * 2 else: - take_profit_price = safe_float(exec_price) * ( - 1 - take_profit_percent / 100) - total_commission - stop_loss_price = None + total_quantity = safe_float(order_quantity) else: - if side == "Buy": - take_profit_price = safe_float(exec_price) * ( - 1 + take_profit_percent / 100) + total_commission - stop_loss_price = safe_float(exec_price) * (1 - stop_loss_percent / 100) + total_quantity = safe_float(order_quantity) + + if user_deals_data is not None and safe_float(closed_size) == 0: + await rq.set_total_fee_user_auto_trading( + tg_id=tg_id, symbol=symbol, total_fee=total_fee + ) + text += f"Текущая ставка: {total_quantity:.2f} USDT\n" + text += f"Серия №: {current_series}\n" + text += f"Сделка №: {current_step}\n" + + text += ( + f"Цена исполнения: {exec_price}\n" + f"Комиссия: {exec_fee:.8f}\n" + ) + + if safe_float(closed_size) == 0: + instruments_info = await get_instruments_info(tg_id=tg_id, symbol=symbol) + qty_step_str = instruments_info.get("lotSizeFilter").get("qtyStep") + qty_step = safe_float(qty_step_str) + qty = (safe_float(order_quantity) * safe_float(leverage)) / safe_float(exec_price) + decimals = abs(int(round(math.log10(qty_step)))) + qty_format = math.floor(qty / qty_step) * qty_step + qty_formatted = round(qty_format, decimals) + total_commission = 0 + + if commission_fee == "Yes_commission_fee": + if commission_place == "Commission_for_tp": + total_commission = safe_float(total_fee) / qty_formatted + + if margin_type == "ISOLATED_MARGIN": + if side == "Buy": + take_profit_price = safe_float(exec_price) * ( + 1 + take_profit_percent / 100) + total_commission + stop_loss_price = None + else: + take_profit_price = safe_float(exec_price) * ( + 1 - take_profit_percent / 100) - total_commission + stop_loss_price = None else: - take_profit_price = safe_float(exec_price) * ( - 1 - take_profit_percent / 100) - total_commission - stop_loss_price = safe_float(exec_price) * (1 + stop_loss_percent / 100) + if side == "Buy": + take_profit_price = safe_float(exec_price) * ( + 1 + take_profit_percent / 100) + total_commission + stop_loss_price = safe_float(exec_price) * (1 - stop_loss_percent / 100) + else: + take_profit_price = safe_float(exec_price) * ( + 1 - take_profit_percent / 100) - total_commission + stop_loss_price = safe_float(exec_price) * (1 + stop_loss_percent / 100) + + ress = await set_tp_sl_for_position(tg_id=tg_id, + symbol=symbol, + take_profit_price=take_profit_price, + stop_loss_price=stop_loss_price, + position_idx=0) + if ress or ress == "not modified": + take_profit_truncated = await truncate_float(take_profit_price, 6) + text += (f"Движение: {side_rus}\n" + f"Тейк-профит: {take_profit_truncated}\n" + ) + if stop_loss_price is not None: + stop_loss_truncated = await truncate_float(stop_loss_price, 6) + else: + stop_loss_truncated = None + + if stop_loss_truncated is not None: + text += f"Стоп-лосс: {stop_loss_truncated}\n" + else: + deals = await get_active_positions_by_symbol( + tg_id=tg_id, symbol=symbol + ) + position = next((d for d in deals if d.get("symbol") == symbol), None) + + if position: + liq_price = position.get("liqPrice", 0) + text += f"Цена ликвидации: {liq_price}\n" - take_profit_price = max(take_profit_price, 0) - stop_loss_price = max(stop_loss_price, 0) - ress = await set_tp_sl_for_position(tg_id=tg_id, - symbol=symbol, - take_profit_price=take_profit_price, - stop_loss_price=stop_loss_price, - position_idx=0) - if ress: - take_profit_truncated = await truncate_float(take_profit_price, 6) - text += (f"Движение: {side_rus}\n" - f"Тейк-профит: {take_profit_truncated}\n" - ) - if stop_loss_price is not None: - stop_loss_truncated = await truncate_float(stop_loss_price, 6) else: - stop_loss_truncated = None + text += (f"Движение: {side_rus}\n" + "Не удалось установить ТП и СЛ\n") - if stop_loss_truncated is not None: - text += f"Стоп-лосс: {stop_loss_truncated}\n" - else: - deals = await get_active_positions_by_symbol( - tg_id=tg_id, symbol=symbol - ) - position = next((d for d in deals if d.get("symbol") == symbol), None) - - if position: - liq_price = position.get("liqPrice", 0) - text += f"Цена ликвидации: {liq_price}\n" - - - else: - text += (f"Движение: {side_rus}\n" - "Не удалось установить ТП и СЛ\n") - - else: - if auto_trading: + elif safe_float(closed_size) > 0 and auto_trading: new_pnl = safe_float(pnl_series) + total_pnl await rq.set_pnl_series_by_symbol( tg_id=tg_id, symbol=symbol, pnl_series=new_pnl) text += f"\nПрибыль без комиссии: {ex_pnl:.4f}\n" text += f"Реализованная прибыль: {total_pnl:.4f}\n" text += f"Прибыль серии: {safe_float(new_pnl):.4f}\n" - else: - text += f"\nПрибыль без комиссии: {ex_pnl:.4f}\n" - text += f"Реализованная прибыль: {total_pnl:.4f}\n" - await self.telegram_bot.send_message( - chat_id=tg_id, text=text, reply_markup=kbi.profile_bybit - ) + await self.telegram_bot.send_message( + chat_id=tg_id, text=text, reply_markup=kbi.profile_bybit + ) - user_symbols = user_auto_trading.symbol if user_auto_trading else None - - if ( - auto_trading - and safe_float(closed_size) > 0 - and user_symbols is not None - ): - if safe_float(pnl) > 0: + if stop_order_type == "TakeProfit": profit_text = "📈 Начинаю новую серию с базовой ставки\n" await self.telegram_bot.send_message( chat_id=tg_id, text=profit_text, reply_markup=kbi.profile_bybit @@ -297,7 +283,8 @@ class TelegramMessageHandler: text=error_text, reply_markup=kbi.profile_bybit, ) - else: + + elif stop_order_type == "StopLoss" or exec_type == "BustTrade": open_order_text = "\n❗️ Открываю новую сделку с увеличенной ставкой.\n" await self.telegram_bot.send_message( chat_id=tg_id, text=open_order_text @@ -321,7 +308,7 @@ class TelegramMessageHandler: "ab not enough for new order": "❗️ Недостаточно средств для продолжения торговли", "InvalidRequestError": "❗️ Недостаточно средств для размещения нового ордера с заданным количеством и плечом.", "The number of contracts exceeds maximum limit allowed": "❗️ Превышен максимальный лимит ставки", - "Order placement failed as your position may exceed the max": "❗️ Превышен максимальный лимит ставки", + "Order placement failed as your position may exceed the max": "❗️ Превышен максимальный лимит ставки с текущим плечом", } error_text = errors.get( res, "❗️ Не удалось открыть новую сделку" @@ -341,5 +328,23 @@ class TelegramMessageHandler: text=error_text, reply_markup=kbi.profile_bybit, ) + elif create_type == "CreateByClosing": + await self.telegram_bot.send_message( + chat_id=tg_id, + text=f"❗️ Торговля для {symbol} остановлена", + reply_markup=kbi.profile_bybit, + ) + await rq.set_auto_trading( + tg_id=tg_id, symbol=symbol, auto_trading=False + ) + + await rq.set_total_fee_user_auto_trading( + tg_id=tg_id, symbol=symbol, total_fee=0 + ) + await rq.set_fee_user_auto_trading( + tg_id=tg_id, symbol=symbol, fee=0 + ) + logger.info("Stop trading for symbol: %s, create_type: %s, stop_order_type: %s: %s", + symbol, create_type, stop_order_type, tg_id) except Exception as e: logger.error("Error in telegram_message_handler: %s", e, exc_info=True) diff --git a/app/telegram/handlers/stop_trading.py b/app/telegram/handlers/stop_trading.py index 3ccd377..dc96da2 100644 --- a/app/telegram/handlers/stop_trading.py +++ b/app/telegram/handlers/stop_trading.py @@ -39,13 +39,13 @@ async def stop_all_trading(callback_query: CallbackQuery, state: FSMContext): await rq.set_stop_timer(tg_id=callback_query.from_user.id, timer_end=0) await asyncio.sleep(timer_end * 60) + await close_position_by_symbol( + tg_id=callback_query.from_user.id, symbol=symbol) await rq.set_auto_trading( tg_id=callback_query.from_user.id, symbol=symbol, auto_trading=False, ) - await close_position_by_symbol( - tg_id=callback_query.from_user.id, symbol=symbol) await callback_query.message.edit_text(text=f"Торговля для {symbol} остановлена", reply_markup=kbi.profile_bybit)