From e043a2429fc236a4376a1afe51757e6178f626d6 Mon Sep 17 00:00:00 2001 From: algizn97 Date: Wed, 29 Oct 2025 20:58:04 +0500 Subject: [PATCH] The logic of setting take profit and stop loss has been changed, added data for the end user --- app/bybit/open_positions.py | 69 ++--------- app/bybit/telegram_message_handler.py | 167 ++++++++++++++++---------- app/bybit/web_socket.py | 23 ++-- 3 files changed, 127 insertions(+), 132 deletions(-) diff --git a/app/bybit/open_positions.py b/app/bybit/open_positions.py index ed233d6..9ccb188 100644 --- a/app/bybit/open_positions.py +++ b/app/bybit/open_positions.py @@ -78,7 +78,8 @@ async def start_trading_cycle( stop_loss_percent=stop_loss_percent, base_quantity=order_quantity, commission_fee=commission_fee, - commission_place=commission_place + commission_place=commission_place, + pnl_series=0 ) res = await open_positions( @@ -87,10 +88,7 @@ async def start_trading_cycle( side=side, order_quantity=order_quantity, trigger_price=trigger_price, - margin_type=margin_type, - leverage=leverage, - take_profit_percent=take_profit_percent, - stop_loss_percent=stop_loss_percent, + leverage=leverage ) if res == "OK": @@ -171,7 +169,8 @@ async def trading_cycle_profit( stop_loss_percent=stop_loss_percent, base_quantity=base_quantity, commission_fee=commission_fee, - commission_place=commission_place + commission_place=commission_place, + pnl_series=0 ) res = await open_positions( @@ -180,10 +179,7 @@ async def trading_cycle_profit( side=s_side, order_quantity=base_quantity, trigger_price=trigger_price, - margin_type=margin_type, - leverage=leverage, - take_profit_percent=take_profit_percent, - stop_loss_percent=stop_loss_percent, + leverage=leverage ) if res == "OK": @@ -227,6 +223,7 @@ async def trading_cycle( base_quantity = user_deals_data.base_quantity side_mode = user_deals_data.side_mode current_series = user_deals_data.current_series + pnl_series = user_deals_data.pnl_series next_quantity = safe_float(order_quantity) * ( safe_float(martingale_factor) @@ -272,7 +269,8 @@ async def trading_cycle( stop_loss_percent=stop_loss_percent, base_quantity=base_quantity, commission_fee=commission_fee, - commission_place=commission_place + commission_place=commission_place, + pnl_series=pnl_series ) res = await open_positions( @@ -281,10 +279,7 @@ async def trading_cycle( side=r_side, order_quantity=total_quantity, trigger_price=trigger_price, - margin_type=margin_type, - leverage=leverage, - take_profit_percent=take_profit_percent, - stop_loss_percent=stop_loss_percent, + leverage=leverage ) if res == "OK": @@ -313,18 +308,10 @@ async def open_positions( symbol: str, order_quantity: float, trigger_price: float, - margin_type: str, - leverage: str, - take_profit_percent: float, - stop_loss_percent: float + leverage: str ) -> str | None: try: client = await get_bybit_client(tg_id=tg_id) - user_deals_data = await rq.get_user_deal_by_symbol(tg_id=tg_id, symbol=symbol) - commission_fee = user_deals_data.commission_fee - commission_place = user_deals_data.commission_place - user_auto_trading_data = await rq.get_user_auto_trading(tg_id=tg_id, symbol=symbol) - total_fee = user_auto_trading_data.total_fee get_ticker = await get_tickers(tg_id, symbol=symbol) price_symbol = safe_float(get_ticker.get("lastPrice")) or 0 instruments_info = await get_instruments_info(tg_id=tg_id, symbol=symbol) @@ -342,38 +329,6 @@ async def open_positions( po_trigger_price = None trigger_direction = None - price_for_cals = trigger_price if po_trigger_price is not None else price_symbol - - if qty_formatted <= 0: - return "Order does not meet minimum order value" - - 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 = price_for_cals * ( - 1 + take_profit_percent / 100) + total_commission - stop_loss_price = None - else: - take_profit_price = price_for_cals * ( - 1 - take_profit_percent / 100) - total_commission - stop_loss_price = None - else: - if side == "Buy": - take_profit_price = price_for_cals * ( - 1 + take_profit_percent / 100) + total_commission - stop_loss_price = price_for_cals * (1 - stop_loss_percent / 100) - else: - take_profit_price = price_for_cals * ( - 1 - take_profit_percent / 100) - total_commission - stop_loss_price = price_for_cals * (1 + stop_loss_percent / 100) - - take_profit_price = max(take_profit_price, 0) - stop_loss_price = max(stop_loss_price, 0) - # Place order order_params = { "category": "linear", @@ -387,8 +342,6 @@ async def open_positions( "timeInForce": "GTC", "positionIdx": 0, "tpslMode": "Full", - "takeProfit": str(take_profit_price) if take_profit_price else None, - "stopLoss": str(stop_loss_price) if stop_loss_price else None, } response = client.place_order(**order_params) diff --git a/app/bybit/telegram_message_handler.py b/app/bybit/telegram_message_handler.py index 94f84d4..b5d874b 100644 --- a/app/bybit/telegram_message_handler.py +++ b/app/bybit/telegram_message_handler.py @@ -1,10 +1,13 @@ import logging.config +import math import app.telegram.keyboards.inline as kbi import database.request as rq +from app.bybit.get_functions.get_instruments_info import get_instruments_info from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG from app.bybit.open_positions import trading_cycle, trading_cycle_profit -from app.helper_functions import format_value, safe_float +from app.bybit.set_functions.set_tp_sl import set_tp_sl_for_position +from app.helper_functions import format_value, safe_float, truncate_float logging.config.dictConfig(LOGGING_CONFIG) logger = logging.getLogger("telegram_message_handler") @@ -14,65 +17,40 @@ class TelegramMessageHandler: def __init__(self, telegram_bot): self.telegram_bot = telegram_bot - async def format_position_update(self, message): - pass - async def format_order_update(self, message, tg_id): try: - order_data = message.get("data", [{}])[0] - symbol = format_value(order_data.get("symbol")) - qty = format_value(order_data.get("qty")) - side = format_value(order_data.get("side")) - side_rus = ( - "Покупка" - if side == "Buy" - else "Продажа" if side == "Sell" else "Нет данных" - ) - order_status = format_value(order_data.get("orderStatus")) - price = format_value(order_data.get("price")) - trigger_price = format_value(order_data.get("triggerPrice")) - take_profit = format_value(order_data.get("takeProfit")) - stop_loss = format_value(order_data.get("stopLoss")) + 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: + order_data = message.get("data", [{}])[0] + symbol = format_value(order_data.get("symbol")) + side = format_value(order_data.get("side")) + side_rus = ( + "Покупка" + if side == "Buy" + else "Продажа" if side == "Sell" else "Нет данных" + ) + order_status = format_value(order_data.get("orderStatus")) + tr_price = format_value(order_data.get("triggerPrice")) - status_map = { - "Untriggered": "Условный ордер выставлен", - } + status_map = { + "Untriggered": "Условный ордер выставлен", + } - if order_status == "Filled" or order_status not in status_map: - return None + if order_status == "Filled" or order_status not in status_map: + return None - user_auto_trading = await rq.get_user_auto_trading( - tg_id=tg_id, symbol=symbol - ) - auto_trading = ( - user_auto_trading.auto_trading if user_auto_trading else False - ) - user_deals_data = await rq.get_user_deal_by_symbol( - tg_id=tg_id, symbol=symbol - ) + text = ( + f"Торговая пара: {symbol}\n" + f"Движение: {side_rus}\n" + ) + if tr_price and tr_price != "Нет данных": + text += f"Триггер цена: {tr_price}\n" - text = ( - f"Торговая пара: {symbol}\n" - f"Движение: {side_rus}\n" - ) - - if user_deals_data is not None and auto_trading: - text += f"Текущая ставка: {user_deals_data.order_quantity} USDT\n" - else: - text += f"Количество: {qty}\n" - - if price and price != "0": - text += f"Цена: {price}\n" - if take_profit and take_profit != "Нет данных": - text += f"Тейк-профит: {take_profit}\n" - if stop_loss and stop_loss != "Нет данных": - text += f"Стоп-лосс: {stop_loss}\n" - if trigger_price and trigger_price != "Нет данных": - text += f"Триггер цена: {trigger_price}\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 + ) + await rq.set_trigger_price(tg_id=tg_id, trigger_price=0) except Exception as e: logger.error("Error in format_order_update: %s", e) @@ -111,6 +89,7 @@ class TelegramMessageHandler: 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 = ( @@ -127,24 +106,33 @@ class TelegramMessageHandler: 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 + 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(user_deals_data.order_quantity) + safe_float( + total_quantity = safe_float(order_quantity) + safe_float( total_fee - ) + ) * 2 else: - total_quantity = safe_float(user_deals_data.order_quantity) + total_quantity = safe_float(order_quantity) else: - total_quantity = safe_float(user_deals_data.order_quantity) + total_quantity = safe_float(order_quantity) 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 ) text += f"Текущая ставка: {total_quantity:.2f} USDT\n" - text += f"Серия №: {user_deals_data.current_series}\n" - text += f"Сделка №: {user_deals_data.current_step}\n" + text += f"Серия №: {current_series}\n" + text += f"Сделка №: {current_step}\n" text += ( f"Цена исполнения: {exec_price}\n" @@ -152,9 +140,61 @@ class TelegramMessageHandler: ) if safe_float(closed_size) == 0: - text += f"Движение: {side_rus}\n" + 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: + 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) + + take_profit_price = max(take_profit_price, 0) + stop_loss_price = max(stop_loss_price, 0) + + 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) + + take_profit_truncated = await truncate_float(take_profit_price, 4) + stop_loss_truncated = await truncate_float(stop_loss_price, 4) + + text += (f"Движение: {side_rus}\n" + f"Тейк-профит: {take_profit_truncated}\n" + f"Стоп-лосс: {stop_loss_truncated}\n" + ) else: - text += f"\nПрибыль: {pnl:.7f}\n" + 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"Реализованный PNL: {total_pnl:.4f}\n" + text += f"Прибыль серии: {safe_float(new_pnl):.4f}\n" await self.telegram_bot.send_message( chat_id=tg_id, text=text, reply_markup=kbi.profile_bybit @@ -186,6 +226,7 @@ class TelegramMessageHandler: await rq.set_fee_user_auto_trading( tg_id=tg_id, symbol=symbol, fee=0 ) + await rq.set_pnl_series_by_symbol(tg_id=tg_id, symbol=symbol, pnl_series=0) res = await trading_cycle_profit( tg_id=tg_id, symbol=symbol, side=r_side @@ -263,4 +304,4 @@ class TelegramMessageHandler: ) except Exception as e: - logger.error("Error in telegram_message_handler: %s", e) + logger.error("Error in telegram_message_handler: %s", e, exc_info=True) diff --git a/app/bybit/web_socket.py b/app/bybit/web_socket.py index c937510..2690d78 100644 --- a/app/bybit/web_socket.py +++ b/app/bybit/web_socket.py @@ -25,6 +25,7 @@ class WebSocketBot: self.user_keys = {} self.loop = None self.message_handler = TelegramMessageHandler(telegram_bot) + self.last_execution_seq = {} async def run_user_check_loop(self): """Run a loop to check for users and connect them to the WebSocket.""" @@ -83,12 +84,6 @@ class WebSocketBot: self.user_sockets[tg_id] = self.ws_private # Connect to the WebSocket private channel - # Handle position updates - self.ws_private.position_stream( - lambda msg: self.loop.call_soon_threadsafe( - asyncio.create_task, self.handle_position_update(msg) - ) - ) # Handle order updates self.ws_private.order_stream( lambda msg: self.loop.call_soon_threadsafe( @@ -106,17 +101,23 @@ class WebSocketBot: logger.error("Error connecting user %s: %s", tg_id, e) return False - async def handle_position_update(self, message): - """Handle position updates.""" - await self.message_handler.format_position_update(message) - async def handle_order_update(self, message, tg_id): """Handle order updates.""" await self.message_handler.format_order_update(message, tg_id) async def handle_execution_update(self, message, tg_id): """Handle execution updates.""" - await self.message_handler.format_execution_update(message, tg_id) + data_items = message.get('data', []) + if not data_items: + return + for exec_data in data_items: + seq = exec_data.get('seq') + if tg_id not in self.last_execution_seq: + self.last_execution_seq[tg_id] = -1 + if seq <= self.last_execution_seq[tg_id]: + continue + self.last_execution_seq[tg_id] = seq + await self.message_handler.format_execution_update(message, tg_id) @staticmethod async def get_users_from_db():