diff --git a/app/telegram/handlers/settings.py b/app/telegram/handlers/settings.py index b687e0a..ed9a914 100644 --- a/app/telegram/handlers/settings.py +++ b/app/telegram/handlers/settings.py @@ -7,8 +7,8 @@ from aiogram.types import CallbackQuery import app.telegram.keyboards.inline as kbi import database.request as rq from app.bybit import get_bybit_client -from app.bybit.get_functions.get_tickers import get_tickers -from app.helper_functions import calculate_total_budget, get_base_currency, safe_float + +from app.helper_functions import calculate_total_budget, safe_float from logger_helper.logger_helper import LOGGING_CONFIG logging.config.dictConfig(LOGGING_CONFIG) @@ -40,77 +40,30 @@ async def additional_settings(callback_query: CallbackQuery, state: FSMContext) return trade_mode_map = { - "Merged_Single": "Односторонний режим", - "Both_Sides": "Хеджирование", + "Long": "Лонг", + "Short": "Шорт", + "Switch": "Свитч", } margin_type_map = { "ISOLATED_MARGIN": "Изолированная", "REGULAR_MARGIN": "Кросс", } - order_type_map = {"Market": "Рыночный", "Limit": "Лимитный"} trade_mode = additional_data.trade_mode or "" margin_type = additional_data.margin_type or "" - order_type = additional_data.order_type or "" trade_mode_rus = trade_mode_map.get(trade_mode, trade_mode) margin_type_rus = margin_type_map.get(margin_type, margin_type) - order_type_rus = order_type_map.get(order_type, "Условный") + switch_side = additional_data.switch_side def f(x): return safe_float(x) leverage = f(additional_data.leverage) - leverage_to_buy = f(additional_data.leverage_to_buy) - leverage_to_sell = f(additional_data.leverage_to_sell) martingale = f(additional_data.martingale_factor) max_bets = additional_data.max_bets_in_series quantity = f(additional_data.order_quantity) - limit_price = f(additional_data.limit_price) trigger_price = f(additional_data.trigger_price) or 0 - - tickers = await get_tickers(tg_id=tg_id, symbol=symbol) - price_symbol = safe_float(tickers.get("lastPrice")) or 0 - bid = f(tickers.get("bid1Price")) or 0 - ask = f(tickers.get("ask1Price")) or 0 - - sym = get_base_currency(symbol) - - if trade_mode == "Merged_Single": - leverage_str = f"{leverage:.2f}x" - else: - if margin_type == "ISOLATED_MARGIN": - leverage_str = f"{leverage_to_buy:.2f}x:{leverage_to_sell:.2f}x" - else: - leverage_str = f"{leverage:.2f}x" - - conditional_order_type = additional_data.conditional_order_type or "" - conditional_order_type_rus = ( - "Лимитный" - if conditional_order_type == "Limit" - else ( - "Рыночный" - if conditional_order_type == "Market" - else conditional_order_type - ) - ) - - conditional_order_type_text = ( - f"- Тип условного ордера: {conditional_order_type_rus}\n" - if order_type == "Conditional" - else "" - ) - - limit_price_text = "" - trigger_price_text = "" - - if order_type == "Limit": - limit_price_text = f"- Цена лимитного ордера: {limit_price:.4f} USDT\n" - elif order_type == "Conditional": - if conditional_order_type == "Limit": - limit_price_text = f"- Цена лимитного ордера: {limit_price:.4f} USDT\n" - trigger_price_text = f"- Триггер цена: {trigger_price:.4f} USDT\n" - risk_management_data = await rq.get_user_risk_management(tg_id=tg_id) commission_fee = risk_management_data.commission_fee client = await get_bybit_client(tg_id=tg_id) @@ -124,54 +77,32 @@ async def additional_settings(callback_query: CallbackQuery, state: FSMContext) else: commission_fee_percent = 0.0 - if order_type == "Conditional": - if conditional_order_type == "Limit": - entry_price = limit_price - ask_price = limit_price - bid_price = limit_price - else: - ask_price = trigger_price - bid_price = trigger_price - entry_price = trigger_price - else: - if order_type == "Limit": - entry_price = limit_price - ask_price = limit_price - bid_price = limit_price - else: - entry_price = price_symbol - ask_price = ask - bid_price = bid + switch_side_mode = "" + if trade_mode == "Switch": + switch_side_mode = f"- Направление первой сделки: {switch_side}\n" - durability_buy = quantity * bid_price - durability_sell = quantity * ask_price - quantity_price = quantity * entry_price + quantity_price = quantity * trigger_price total_commission = quantity_price * commission_fee_percent total_budget = await calculate_total_budget( - quantity=durability_buy, + quantity=quantity, martingale_factor=martingale, max_steps=max_bets, commission_fee_percent=total_commission, ) text = ( f"Основные настройки:\n\n" - f"- Режим позиции: {trade_mode_rus}\n" + f"- Режим торговли: {trade_mode_rus}\n" + f"{switch_side_mode}" f"- Тип маржи: {margin_type_rus}\n" - f"- Размер кредитного плеча: {leverage_str}\n" - f"- Тип ордера: {order_type_rus}\n" - f"- Количество ордера: {quantity} {sym}\n" + f"- Размер кредитного плеча: {leverage:.2f}\n" + f"- Базовая ставка: {quantity} USDT\n" f"- Коэффициент мартингейла: {martingale:.2f}\n" - f"{conditional_order_type_text}" - f"{trigger_price_text}" - f"{limit_price_text}" + f"- Триггер цена: {trigger_price:.4f} USDT\n" f"- Максимальное кол-во ставок в серии: {max_bets}\n\n" - f"- Стоимость: {durability_buy:.2f}/{durability_sell:.2f} USDT\n" - f"- Рекомендуемый бюджет: {total_budget:.4f} USDT\n" + f"- Бюджет серии: {total_budget:.4f} USDT\n" ) - keyboard = kbi.get_additional_settings_keyboard( - current_order_type=order_type, conditional_order=conditional_order_type - ) + keyboard = kbi.get_additional_settings_keyboard(mode=trade_mode) await callback_query.message.edit_text(text=text, reply_markup=keyboard) logger.debug( "Command additional_settings processed successfully for user: %s", tg_id @@ -202,7 +133,6 @@ async def risk_management(callback_query: CallbackQuery, state: FSMContext) -> N if risk_management_data: take_profit_percent = risk_management_data.take_profit_percent or "" stop_loss_percent = risk_management_data.stop_loss_percent or "" - max_risk_percent = risk_management_data.max_risk_percent or "" commission_fee = risk_management_data.commission_fee or "" commission_fee_rus = ( "Да" if commission_fee == "Yes_commission_fee" else "Нет" @@ -212,7 +142,6 @@ async def risk_management(callback_query: CallbackQuery, state: FSMContext) -> N text=f"Риск-менеджмент:\n\n" f"- Процент изменения цены для фиксации прибыли: {take_profit_percent}%\n" f"- Процент изменения цены для фиксации убытка: {stop_loss_percent}%\n\n" - f"- Максимальный риск на сделку (в % от баланса): {max_risk_percent}%\n\n" f"- Комиссия биржи для расчета прибыли: {commission_fee_rus}\n\n", reply_markup=kbi.risk_management, ) diff --git a/app/telegram/handlers/start_trading.py b/app/telegram/handlers/start_trading.py index 351bb33..36c8801 100644 --- a/app/telegram/handlers/start_trading.py +++ b/app/telegram/handlers/start_trading.py @@ -12,9 +12,7 @@ from app.bybit.open_positions import start_trading_cycle from app.helper_functions import safe_float from app.telegram.tasks.tasks import ( add_start_task_merged, - add_start_task_switch, - cancel_start_task_merged, - cancel_start_task_switch, + cancel_start_task_merged ) from logger_helper.logger_helper import LOGGING_CONFIG @@ -35,97 +33,20 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non """ try: await state.clear() - additional_data = await rq.get_user_additional_settings( - tg_id=callback_query.from_user.id - ) - trade_mode = additional_data.trade_mode symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id) deals = await get_active_positions_by_symbol( tg_id=callback_query.from_user.id, symbol=symbol ) position = next((d for d in deals if d.get("symbol") == symbol), None) + if position: size = position.get("size", 0) - position_idx = position.get("positionIdx") else: size = 0 - position_idx = None - if position_idx != 0 and safe_float(size) > 0 and trade_mode == "Merged_Single": + if safe_float(size) > 0: await callback_query.answer( - text="У вас есть активная позиция в режиме хеджирования. " - "Открытие сделки в одностороннем режиме невозможно.", - ) - return - - if position_idx == 0 and safe_float(size) > 0 and trade_mode == "Both_Sides": - await callback_query.answer( - text="У вас есть активная позиция в одностороннем режиме. " - "Открытие сделки в режиме хеджирования невозможно.", - ) - return - - if trade_mode == "Merged_Single": - await callback_query.message.edit_text( - text="Выберите режим торговли:\n\n" - "Лонг - все сделки серии открываются на покупку.\n" - "Шорт - все сделки серии открываются на продажу.\n" - "Свитч - направление каждой сделки серии меняется по переменно.\n", - reply_markup=kbi.merged_start_trading, - ) - else: # trade_mode == "Both_Sides": - await callback_query.message.edit_text( - text="Выберите режим торговли:\n\n" - "Лонг - все сделки открываются на покупку.\n" - "Шорт - все сделки открываются на продажу.\n", - reply_markup=kbi.both_start_trading, - ) - logger.debug( - "Command start_trading processed successfully for user: %s", - callback_query.from_user.id, - ) - except Exception as e: - await callback_query.answer(text="Произошла ошибка при запуске торговли") - logger.error( - "Error processing command start_trading for user %s: %s", - callback_query.from_user.id, - e, - ) - - -@router_start_trading.callback_query(lambda c: c.data == "long" or c.data == "short") -async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) -> None: - """ - Handles the "long" or "short" callback query. - Clears the FSM state and starts the trading cycle. - :param callback_query: Message - :param state: FSMContext - :return: None - """ - try: - if callback_query.data == "long": - side = "Buy" - elif callback_query.data == "short": - side = "Sell" - else: - await callback_query.answer(text="Произошла ошибка при запуске торговли") - return - - symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id) - deals = await get_active_positions_by_symbol( - tg_id=callback_query.from_user.id, symbol=symbol - ) - position = next((d for d in deals if d.get("symbol") == symbol), None) - if position: - size = position.get("size", 0) - position_idx = position.get("positionIdx") - else: - size = 0 - position_idx = None - - if position_idx == 0 and safe_float(size) > 0: - await callback_query.answer( - text="Торговля уже запущена в одностороннем режиме для данного инструмента" + text="У вас есть активная позиция", ) return @@ -151,12 +72,9 @@ async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) - tg_id=callback_query.from_user.id, symbol=symbol, auto_trading=True, - side=side, ) res = await start_trading_cycle( tg_id=callback_query.from_user.id, - side=side, - switch_side_mode=False, ) error_messages = { @@ -167,9 +85,10 @@ async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) - "ab not enough for new order": "Недостаточно средств для создания нового ордера", "InvalidRequestError": "Произошла ошибка при запуске торговли.", "Order does not meet minimum order value": "Сумма ордера не достаточна для запуска торговли", - "position idx not match position mode": "Торговля уже запущена в режиме хеджирования на продажу для данного инструмента", + "position idx not match position mode": "Ошибка режима позиции для данного инструмента", "Qty invalid": "Некорректное значение ордера для данного инструмента", "The number of contracts exceeds maximum limit allowed": "️️Количество контрактов превышает допустимое максимальное количество контрактов", + "The number of contracts exceeds minimum limit allowed": "️️Количество контрактов превышает допустимое минимальное количество контрактов", } if res == "OK": @@ -180,7 +99,6 @@ async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) - tg_id=callback_query.from_user.id, symbol=symbol, auto_trading=False, - side=side, ) text = error_messages.get(res, "Произошла ошибка при запуске торговли") await callback_query.message.edit_text( @@ -202,189 +120,8 @@ async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) - logger.error("Cancelled timer for user %s", callback_query.from_user.id) -@router_start_trading.callback_query(lambda c: c.data == "switch") -async def start_trading_switch( - callback_query: CallbackQuery, state: FSMContext -) -> None: - """ - Handles the "switch" callback query. - Clears the FSM state and sends a message to the user to select the switch side. - :param callback_query: Message - :param state: FSMContext - :return: None - """ - try: - await state.clear() - await callback_query.message.edit_text( - text="Выберите направление первой сделки серии:\n\n" - "Лонг - открывается первая сделка на покупку.\n" - "Шорт - открывается первая сделка на продажу.\n" - "По направлению - сделка открывается в направлении последней сделки предыдущей серии.\n" - "Противоположно - сделка открывается в противоположном направлении последней сделки предыдущей серии.\n", - reply_markup=kbi.switch_side, - ) - except Exception as e: - await callback_query.answer(text="Произошла ошибка при запуске торговли") - logger.error( - "Error processing command start trading switch for user %s: %s", - callback_query.from_user.id, - e, - ) - - @router_start_trading.callback_query( - lambda c: c.data - in {"switch_long", "switch_short", "switch_direction", "switch_opposite"} -) -async def start_switch(callback_query: CallbackQuery, state: FSMContext) -> None: - """ - Starts the trading cycle with the selected side. - :param callback_query: - :param state: - :return: - """ - try: - symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id) - user_deals_data = await rq.get_user_deal_by_symbol( - tg_id=callback_query.from_user.id, symbol=symbol - ) - - get_side = "Buy" - - if user_deals_data: - get_side = user_deals_data.last_side or "Buy" - - if callback_query.data == "switch_long": - side = "Buy" - elif callback_query.data == "switch_short": - side = "Sell" - elif callback_query.data == "switch_direction": - side = get_side - elif callback_query.data == "switch_opposite": - if get_side == "Buy": - side = "Sell" - else: - side = "Buy" - else: - await callback_query.answer(text="Произошла ошибка при запуске торговли") - return - - deals = await get_active_positions_by_symbol( - tg_id=callback_query.from_user.id, symbol=symbol - ) - position = next((d for d in deals if d.get("symbol") == symbol), None) - if position: - size = position.get("size", 0) - position_idx = position.get("positionIdx") - else: - size = 0 - position_idx = None - - if position_idx == 1 and safe_float(size) > 0 and side == "Buy": - await callback_query.answer( - text="Торговля уже запущена в режиме хеджирования на покупку для данного инструмента" - ) - return - - if position_idx == 2 and safe_float(size) > 0 and side == "Sell": - await callback_query.answer( - text="Торговля уже запущена в режиме хеджирования на продажу для данного инструмента" - ) - return - - conditional_data = await rq.get_user_conditional_settings( - tg_id=callback_query.from_user.id - ) - timer_start = conditional_data.timer_start - - cancel_start_task_switch(user_id=callback_query.from_user.id) - - async def delay_start(): - if timer_start > 0: - await callback_query.message.edit_text( - text=f"Торговля будет запущена с задержкой {timer_start} мин.", - reply_markup=kbi.cancel_timer_switch, - ) - await rq.set_start_timer( - tg_id=callback_query.from_user.id, timer_start=0 - ) - await asyncio.sleep(timer_start * 60) - await rq.set_auto_trading( - tg_id=callback_query.from_user.id, - symbol=symbol, - auto_trading=True, - side=side, - ) - if side == "Buy": - r_side = "Sell" - else: - r_side = "Buy" - await rq.set_auto_trading( - tg_id=callback_query.from_user.id, - symbol=symbol, - auto_trading=True, - side=r_side, - ) - res = await start_trading_cycle( - tg_id=callback_query.from_user.id, - side=side, - switch_side_mode=True, - ) - - error_messages = { - "Limit price is out min price": "Цена лимитного ордера меньше минимального", - "Limit price is out max price": "Цена лимитного ордера больше максимального", - "Risk is too high for this trade": "Риск сделки превышает допустимый убыток", - "estimated will trigger liq": "Лимитный ордер может вызвать мгновенную ликвидацию. Проверьте параметры ордера.", - "ab not enough for new order": "Недостаточно средств для создания нового ордера", - "InvalidRequestError": "Произошла ошибка при запуске торговли.", - "Order does not meet minimum order value": "Сумма ордера не достаточна для запуска торговли", - "position idx not match position mode": "Торговля уже запущена в режиме хеджирования на продажу для данного инструмента", - "Qty invalid": "Некорректное значение ордера для данного инструмента", - "The number of contracts exceeds maximum limit allowed": "️ ️️Количество контрактов превышает допустимое максимальное количество контрактов", - } - - if res == "OK": - await callback_query.message.edit_text(text="Торговля запущена") - await state.clear() - else: - await rq.set_auto_trading( - tg_id=callback_query.from_user.id, - symbol=symbol, - auto_trading=False, - side=side, - ) - if side == "Buy": - r_side = "Sell" - else: - r_side = "Buy" - await rq.set_auto_trading( - tg_id=callback_query.from_user.id, - symbol=symbol, - auto_trading=False, - side=r_side, - ) - text = error_messages.get(res, "Произошла ошибка при запуске торговли") - await callback_query.message.edit_text( - text=text, reply_markup=kbi.profile_bybit - ) - - await callback_query.message.edit_text("Запуск торговли...") - task = asyncio.create_task(delay_start()) - await add_start_task_switch(user_id=callback_query.from_user.id, task=task) - except asyncio.CancelledError: - logger.error("Cancelled timer for user %s", callback_query.from_user.id) - except Exception as e: - await callback_query.answer(text="Произошла ошибка при запуске торговли") - logger.error( - "Error processing command start switch for user %s: %s", - callback_query.from_user.id, - e, - ) - - -@router_start_trading.callback_query( - lambda c: c.data == "cancel_timer_merged" or c.data == "cancel_timer_switch" + lambda c: c.data == "cancel_timer_merged" ) async def cancel_start_trading( callback_query: CallbackQuery, state: FSMContext @@ -400,8 +137,6 @@ async def cancel_start_trading( await state.clear() if callback_query.data == "cancel_timer_merged": cancel_start_task_merged(user_id=callback_query.from_user.id) - elif callback_query.data == "cancel_timer_switch": - cancel_start_task_switch(user_id=callback_query.from_user.id) await callback_query.message.edit_text( text="Запуск торговли отменен", reply_markup=kbi.profile_bybit )