import asyncio import logging.config from aiogram import F, Router from aiogram.fsm.context import FSMContext from aiogram.types import CallbackQuery import app.telegram.keyboards.inline as kbi import database.request as rq from app.bybit.get_functions.get_positions import get_active_positions_by_symbol 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, ) from logger_helper.logger_helper import LOGGING_CONFIG logging.config.dictConfig(LOGGING_CONFIG) logger = logging.getLogger("start_trading") router_start_trading = Router(name="start_trading") @router_start_trading.callback_query(F.data == "start_trading") async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> None: """ Handles the "start_trading" callback query. Clears the FSM state and sends a message to the user to select the trading mode. :param callback_query: Message :param state: FSMContext :return: None """ 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": 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="Торговля уже запущена в одностороннем режиме для данного инструмента" ) 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_merged(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_merged, ) 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, ) res = await start_trading_cycle( tg_id=callback_query.from_user.id, side=side, switch_side_mode=False, ) 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, ) 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_merged(user_id=callback_query.from_user.id, task=task) except Exception as e: await callback_query.answer(text="Произошла ошибка при запуске торговли") logger.error( "Error processing command long for user %s: %s", callback_query.from_user.id, e, ) except asyncio.CancelledError: 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" ) async def cancel_start_trading( callback_query: CallbackQuery, state: FSMContext ) -> None: """ Handles the "cancel_timer" callback query. Clears the FSM state and sends a message to the user to cancel the start trading process. :param callback_query: Message :param state: FSMContext :return: None """ try: 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 ) except Exception as e: await callback_query.answer("Произошла ошибка при отмене запуска торговли") logger.error( "Error processing command cancel_timer for user %s: %s", callback_query.from_user.id, e, )