1030 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1030 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import logging.config
 | 
						||
 | 
						||
from aiogram import F, Router
 | 
						||
from aiogram.fsm.context import FSMContext
 | 
						||
from aiogram.types import CallbackQuery, Message
 | 
						||
 | 
						||
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.get_functions.get_positions import get_active_positions_by_symbol, get_active_orders_by_symbol
 | 
						||
from app.bybit.set_functions.set_leverage import set_leverage
 | 
						||
from app.bybit.set_functions.set_margin_mode import set_margin_mode
 | 
						||
from app.helper_functions import is_int, is_number, safe_float
 | 
						||
from app.telegram.states.states import AdditionalSettingsState
 | 
						||
from logger_helper.logger_helper import LOGGING_CONFIG
 | 
						||
 | 
						||
logging.config.dictConfig(LOGGING_CONFIG)
 | 
						||
logger = logging.getLogger("additional_settings")
 | 
						||
 | 
						||
router_additional_settings = Router(name="additional_settings")
 | 
						||
 | 
						||
 | 
						||
@router_additional_settings.callback_query(F.data == "trade_mode")
 | 
						||
async def settings_for_trade_mode(
 | 
						||
        callback_query: CallbackQuery, state: FSMContext
 | 
						||
) -> None:
 | 
						||
    """
 | 
						||
    Handles the 'trade_mode' callback query.
 | 
						||
 | 
						||
    Clears the current FSM state, edits the message text to display trade mode options
 | 
						||
    with explanation for 'Long' and 'Short' modes, and shows an inline keyboard for selection.
 | 
						||
 | 
						||
    Args:
 | 
						||
        callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard.
 | 
						||
        state (FSMContext): Finite State Machine context for the current user session.
 | 
						||
 | 
						||
    Logs:
 | 
						||
        Success or error messages with user identification.
 | 
						||
    """
 | 
						||
    try:
 | 
						||
        await state.clear()
 | 
						||
        await callback_query.message.edit_text(
 | 
						||
            text="Выберите режим торговли:\n\n"
 | 
						||
                 "Лонг - все сделки серии открываются на покупку.\n"
 | 
						||
                 "Шорт - все сделки серии открываются на продажу.\n"
 | 
						||
                 "Свитч - направление каждой сделки в рамках серии меняется попеременно.\n",
 | 
						||
            reply_markup=kbi.trade_mode,
 | 
						||
        )
 | 
						||
        logger.debug(
 | 
						||
            "Command trade_mode processed successfully for user: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
        )
 | 
						||
 | 
						||
    except Exception as e:
 | 
						||
        await callback_query.answer(
 | 
						||
            text="Произошла ошибка. Пожалуйста, попробуйте позже."
 | 
						||
        )
 | 
						||
        logger.error(
 | 
						||
            "Error processing command trade_mode for user %s: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
            e,
 | 
						||
        )
 | 
						||
 | 
						||
 | 
						||
@router_additional_settings.callback_query(
 | 
						||
    lambda c: c.data == "Long" or c.data == "Short" or c.data == "Switch"
 | 
						||
)
 | 
						||
async def trade_mode(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
						||
    """
 | 
						||
    Handles callback queries related to trade mode selection.
 | 
						||
 | 
						||
    Updates FSM context with selected trade mode and persists the choice in database.
 | 
						||
    Sends an acknowledgement to user and clears FSM state afterward.
 | 
						||
 | 
						||
    Args:
 | 
						||
        callback_query (CallbackQuery): Incoming callback query indicating selected trade mode.
 | 
						||
        state (FSMContext): Finite State Machine context for the current user session.
 | 
						||
 | 
						||
    Logs:
 | 
						||
        Success or error messages with user identification.
 | 
						||
    """
 | 
						||
    try:
 | 
						||
        req = await rq.set_trade_mode(
 | 
						||
            tg_id=callback_query.from_user.id, trade_mode=callback_query.data
 | 
						||
        )
 | 
						||
 | 
						||
        if not req:
 | 
						||
            await callback_query.answer(
 | 
						||
                text="Произошла ошибка при установке режима торговли"
 | 
						||
            )
 | 
						||
            return
 | 
						||
 | 
						||
        await callback_query.answer(text="Режим торговли успешно изменен")
 | 
						||
        logger.debug(
 | 
						||
            "Trade mode changed successfully for user: %s", callback_query.from_user.id
 | 
						||
        )
 | 
						||
    except Exception as e:
 | 
						||
        await callback_query.answer(text="Произошла ошибка при смене режима позиции.")
 | 
						||
        logger.error(
 | 
						||
            "Error processing set trade_mode for user %s: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
            e,
 | 
						||
        )
 | 
						||
    finally:
 | 
						||
        await state.clear()
 | 
						||
 | 
						||
 | 
						||
@router_additional_settings.callback_query(F.data == "switch_side_second")
 | 
						||
async def switch_side_second(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
						||
    """
 | 
						||
    Handles the 'switch_side_second' callback query.
 | 
						||
 | 
						||
    Clears the current FSM state, edits the message text to display the switch side start message,
 | 
						||
    and shows an inline keyboard for selection.
 | 
						||
 | 
						||
    Args:
 | 
						||
        callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard.
 | 
						||
        state (FSMContext): Finite State Machine context for the current user session.
 | 
						||
 | 
						||
    Logs:
 | 
						||
        Success or error messages with user identification.
 | 
						||
    """
 | 
						||
    try:
 | 
						||
        await state.clear()
 | 
						||
        await callback_query.message.edit_text(
 | 
						||
            text="Выберите направление первой сделки последующих серии:\n\n"
 | 
						||
                 "По направлению - сделка открывается в направлении последней сделки предыдущей серии.\n"
 | 
						||
                 "Противоположно - сделка открывается в противоположном направлении последней сделки предыдущей серии.\n",
 | 
						||
            reply_markup=kbi.switch_side,
 | 
						||
        )
 | 
						||
        logger.debug(
 | 
						||
            "Command switch_side_second processed successfully for user: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
        )
 | 
						||
    except Exception as e:
 | 
						||
        await callback_query.answer(
 | 
						||
            text="Произошла ошибка. Пожалуйста, попробуйте позже."
 | 
						||
        )
 | 
						||
        logger.error(
 | 
						||
            "Error processing command switch_side_second for user %s: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
            e,
 | 
						||
        )
 | 
						||
 | 
						||
 | 
						||
@router_additional_settings.callback_query(lambda c: c.data == "switch_direction" or c.data == "switch_opposite")
 | 
						||
async def switch_side_handler(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
						||
    """
 | 
						||
    Handles callback queries related to switch side selection.
 | 
						||
 | 
						||
    Updates FSM context with selected switch side and persists the choice in database.
 | 
						||
    Sends an acknowledgement to user and clears FSM state afterward.
 | 
						||
 | 
						||
    Args:
 | 
						||
        callback_query (CallbackQuery): Incoming callback query indicating selected switch side.
 | 
						||
        state (FSMContext): Finite State Machine context for the current user session.
 | 
						||
 | 
						||
    Logs:
 | 
						||
        Success or error messages with user identification.
 | 
						||
    """
 | 
						||
    try:
 | 
						||
        if callback_query.data == "switch_direction":
 | 
						||
            switch_side = "По направлению"
 | 
						||
        elif callback_query.data == "switch_opposite":
 | 
						||
            switch_side = "Противоположно"
 | 
						||
        else:
 | 
						||
            switch_side = None
 | 
						||
 | 
						||
        req = await rq.set_switch_side(
 | 
						||
            tg_id=callback_query.from_user.id, switch_side=switch_side
 | 
						||
        )
 | 
						||
 | 
						||
        if not req:
 | 
						||
            await callback_query.answer(
 | 
						||
                text="Произошла ошибка при установке направления переключения"
 | 
						||
            )
 | 
						||
            return
 | 
						||
 | 
						||
        await callback_query.answer(text=f"Выбрано: {switch_side}")
 | 
						||
        logger.debug(
 | 
						||
            "Switch side changed successfully for user: %s", callback_query.from_user.id
 | 
						||
        )
 | 
						||
    except Exception as e:
 | 
						||
        await callback_query.answer(
 | 
						||
            text="Произошла ошибка при смене направления переключения"
 | 
						||
        )
 | 
						||
        logger.error(
 | 
						||
            "Error processing set switch_side for user %s: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
            e,
 | 
						||
        )
 | 
						||
    finally:
 | 
						||
        await state.clear()
 | 
						||
 | 
						||
 | 
						||
@router_additional_settings.callback_query(F.data == "switch_side_start")
 | 
						||
async def switch_side_start(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
						||
    """
 | 
						||
    Handles the 'switch_side_start' callback query.
 | 
						||
 | 
						||
    Clears the current FSM state, edits the message text to display the switch side second message,
 | 
						||
    and shows an inline keyboard for selection.
 | 
						||
 | 
						||
    Args:
 | 
						||
        callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard.
 | 
						||
        state (FSMContext): Finite State Machine context for the current user session.
 | 
						||
 | 
						||
    Logs:
 | 
						||
        Success or error messages with user identification.
 | 
						||
    """
 | 
						||
    try:
 | 
						||
        await state.clear()
 | 
						||
        await callback_query.message.edit_text(
 | 
						||
            text="Выберите направление первой сделки:\n\n", reply_markup=kbi.side_for_switch
 | 
						||
        )
 | 
						||
        logger.debug(
 | 
						||
            "Command switch_side_start processed successfully for user: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
        )
 | 
						||
    except Exception as e:
 | 
						||
        await callback_query.answer(
 | 
						||
            text="Произошла ошибка. Пожалуйста, попробуйте позже."
 | 
						||
        )
 | 
						||
        logger.error(
 | 
						||
            "Error processing command switch_side_start for user %s: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
            e,
 | 
						||
        )
 | 
						||
 | 
						||
 | 
						||
@router_additional_settings.callback_query(lambda c: c.data == "buy_switch" or c.data == "sell_switch")
 | 
						||
async def switch_side_handler_2(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
						||
    """
 | 
						||
    Handles callback queries related to switch side selection.
 | 
						||
 | 
						||
    Updates FSM context with selected switch side and persists the choice in database.
 | 
						||
    Sends an acknowledgement to user and clears FSM state afterward.
 | 
						||
 | 
						||
    Args:
 | 
						||
        callback_query (CallbackQuery): Incoming callback query indicating selected switch side.
 | 
						||
        state (FSMContext): Finite State Machine context for the current user session.
 | 
						||
 | 
						||
    Logs:
 | 
						||
        Success or error messages with user identification.
 | 
						||
    """
 | 
						||
    try:
 | 
						||
        if callback_query.data == "sell_switch":
 | 
						||
            side = "Sell"
 | 
						||
            side_rus = "Продажа"
 | 
						||
        else:
 | 
						||
            side = "Buy"
 | 
						||
            side_rus = "Покупка"
 | 
						||
 | 
						||
        req = await rq.set_side(
 | 
						||
            tg_id=callback_query.from_user.id, side=side
 | 
						||
        )
 | 
						||
 | 
						||
        if not req:
 | 
						||
            await callback_query.answer(
 | 
						||
                text="Произошла ошибка при смене направления. Пожалуйста, попробуйте позже."
 | 
						||
            )
 | 
						||
            return
 | 
						||
 | 
						||
        await callback_query.answer(text=f"Выбрано: {side_rus}")
 | 
						||
        logger.debug(
 | 
						||
            "Switch side changed successfully for user: %s", callback_query.from_user.id
 | 
						||
        )
 | 
						||
    except Exception as e:
 | 
						||
        await callback_query.answer(text="Произошла ошибка при смене направления.")
 | 
						||
        logger.error(
 | 
						||
            "Error processing set switch_side for user %s: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
            e,
 | 
						||
        )
 | 
						||
    finally:
 | 
						||
        await state.clear()
 | 
						||
 | 
						||
 | 
						||
@router_additional_settings.callback_query(F.data == "margin_type")
 | 
						||
async def settings_for_margin_type(
 | 
						||
        callback_query: CallbackQuery, state: FSMContext
 | 
						||
) -> None:
 | 
						||
    """
 | 
						||
    Handles the 'margin_type' callback query.
 | 
						||
 | 
						||
    Clears the current FSM state, edits the message text to display margin type options,
 | 
						||
    and shows an inline keyboard for selection.
 | 
						||
 | 
						||
    Args:
 | 
						||
        callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard.
 | 
						||
        state (FSMContext): Finite State Machine context for the current user session.
 | 
						||
 | 
						||
    Logs:
 | 
						||
        Success or error messages with user identification.
 | 
						||
    """
 | 
						||
    try:
 | 
						||
        await state.clear()
 | 
						||
        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)
 | 
						||
        else:
 | 
						||
            size = 0
 | 
						||
 | 
						||
        if safe_float(size) > 0:
 | 
						||
            await callback_query.answer(
 | 
						||
                text="У вас есть активная позиция по текущей паре",
 | 
						||
            )
 | 
						||
            return
 | 
						||
 | 
						||
        orders = await get_active_orders_by_symbol(
 | 
						||
            tg_id=callback_query.from_user.id, symbol=symbol)
 | 
						||
 | 
						||
        if orders is not None:
 | 
						||
            await callback_query.answer(
 | 
						||
                text="У вас есть активный ордер по текущей паре",
 | 
						||
            )
 | 
						||
            return
 | 
						||
        await callback_query.message.edit_text(
 | 
						||
            text="Выберите тип маржи:\n\n"
 | 
						||
                 "Примечание: Если у вас есть открытые позиции, то маржа примениться ко всем позициям",
 | 
						||
            reply_markup=kbi.margin_type
 | 
						||
        )
 | 
						||
        logger.debug(
 | 
						||
            "Command margin_type processed successfully for user: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
        )
 | 
						||
    except Exception as e:
 | 
						||
        await callback_query.answer(
 | 
						||
            text="Произошла ошибка. Пожалуйста, попробуйте позже."
 | 
						||
        )
 | 
						||
        logger.error(
 | 
						||
            "Error processing command margin_type for user %s: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
            e,
 | 
						||
        )
 | 
						||
 | 
						||
 | 
						||
@router_additional_settings.callback_query(
 | 
						||
    lambda c: c.data == "ISOLATED_MARGIN" or c.data == "REGULAR_MARGIN"
 | 
						||
)
 | 
						||
async def set_margin_type(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
						||
    """
 | 
						||
    Handles callback queries starting with 'Isolated' or 'Cross'.
 | 
						||
 | 
						||
    Updates FSM context with selected margin type and persists the choice in database.
 | 
						||
    Sends an acknowledgement to user and clears FSM state afterward.
 | 
						||
 | 
						||
    Args:
 | 
						||
        callback_query (CallbackQuery): Incoming callback query indicating selected margin type.
 | 
						||
        state (FSMContext): Finite State Machine context for the current user session.
 | 
						||
 | 
						||
    Logs:
 | 
						||
        Success or error messages with user identification.
 | 
						||
    """
 | 
						||
    try:
 | 
						||
        symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id)
 | 
						||
        additional_settings = await rq.get_user_additional_settings(
 | 
						||
            tg_id=callback_query.from_user.id
 | 
						||
        )
 | 
						||
        get_leverage = additional_settings.leverage or "10"
 | 
						||
 | 
						||
        leverage_to_float = safe_float(get_leverage)
 | 
						||
        bybit_margin_mode = callback_query.data
 | 
						||
        response = await set_margin_mode(
 | 
						||
            tg_id=callback_query.from_user.id, margin_mode=bybit_margin_mode
 | 
						||
        )
 | 
						||
 | 
						||
        if not response:
 | 
						||
            await callback_query.answer(
 | 
						||
                text="Произошла ошибка при установке типа маржи"
 | 
						||
            )
 | 
						||
            return
 | 
						||
 | 
						||
        req = await rq.set_margin_type(
 | 
						||
            tg_id=callback_query.from_user.id, margin_type=callback_query.data
 | 
						||
        )
 | 
						||
 | 
						||
        if not req:
 | 
						||
            await callback_query.answer(
 | 
						||
                text="Произошла ошибка при установке типа маржи"
 | 
						||
            )
 | 
						||
            return
 | 
						||
 | 
						||
        await set_leverage(
 | 
						||
            tg_id=callback_query.from_user.id,
 | 
						||
            symbol=symbol,
 | 
						||
            leverage=str(leverage_to_float),
 | 
						||
        )
 | 
						||
 | 
						||
        if callback_query.data.startswith("ISOLATED_MARGIN"):
 | 
						||
            await callback_query.answer(text="Выбран тип маржи: Изолированная")
 | 
						||
        elif callback_query.data.startswith("REGULAR_MARGIN"):
 | 
						||
            await callback_query.answer(text="Выбран тип маржи: Кросс")
 | 
						||
        else:
 | 
						||
            await callback_query.answer(
 | 
						||
                text="Произошла ошибка при установке типа маржи"
 | 
						||
            )
 | 
						||
 | 
						||
    except Exception as e:
 | 
						||
        await callback_query.answer(text="Произошла ошибка при установке типа маржи")
 | 
						||
        logger.error(
 | 
						||
            "Error processing command margin_type for user %s: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
            e,
 | 
						||
        )
 | 
						||
    finally:
 | 
						||
        await state.clear()
 | 
						||
 | 
						||
 | 
						||
@router_additional_settings.callback_query(lambda c: c.data == "trigger_price")
 | 
						||
async def trigger_price(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
						||
    """
 | 
						||
    Handles the 'trigger_price' callback query.
 | 
						||
 | 
						||
    Clears the current FSM state, edits the message text to prompt for the trigger price,
 | 
						||
    and shows an inline keyboard for input.
 | 
						||
 | 
						||
    Args:
 | 
						||
        callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard.
 | 
						||
        state (FSMContext): Finite State Machine context for the current user session.
 | 
						||
 | 
						||
    Logs:
 | 
						||
        Success or error messages with user identification.
 | 
						||
    """
 | 
						||
    try:
 | 
						||
        await state.clear()
 | 
						||
        await state.set_state(AdditionalSettingsState.trigger_price_state)
 | 
						||
        await callback_query.answer()
 | 
						||
        await state.update_data(prompt_message_id=callback_query.message.message_id)
 | 
						||
        msg = await callback_query.message.edit_text(
 | 
						||
            text="Введите цену:", reply_markup=kbi.back_to_additional_settings
 | 
						||
        )
 | 
						||
        await state.update_data(prompt_message_id=msg.message_id)
 | 
						||
        logger.debug(
 | 
						||
            "Command trigger_price processed successfully for user: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
        )
 | 
						||
    except Exception as e:
 | 
						||
        await callback_query.answer(
 | 
						||
            text="Произошла ошибка. Пожалуйста, попробуйте позже."
 | 
						||
        )
 | 
						||
        logger.error(
 | 
						||
            "Error processing command trigger_price for user %s: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
            e,
 | 
						||
        )
 | 
						||
 | 
						||
 | 
						||
@router_additional_settings.message(AdditionalSettingsState.trigger_price_state)
 | 
						||
async def set_trigger_price(message: Message, state: FSMContext) -> None:
 | 
						||
    """
 | 
						||
    Handles user input for setting the trigger price.
 | 
						||
 | 
						||
    Updates FSM context with the selected trigger price and persists the choice in database.
 | 
						||
    Sends an acknowledgement to user and clears FSM state afterward.
 | 
						||
 | 
						||
    Args:
 | 
						||
        message (Message): Incoming message from user containing the selected trigger price.
 | 
						||
        state (FSMContext): Finite State Machine context for the current user session.
 | 
						||
 | 
						||
    Logs:
 | 
						||
        Success or error messages with user identification.
 | 
						||
    """
 | 
						||
    try:
 | 
						||
        try:
 | 
						||
            data = await state.get_data()
 | 
						||
            if "prompt_message_id" in data:
 | 
						||
                prompt_message_id = data["prompt_message_id"]
 | 
						||
                await message.bot.delete_message(
 | 
						||
                    chat_id=message.chat.id, message_id=prompt_message_id
 | 
						||
                )
 | 
						||
            await message.delete()
 | 
						||
        except Exception as e:
 | 
						||
            if "message to delete not found" in str(e).lower():
 | 
						||
                pass  # Ignore this error
 | 
						||
            else:
 | 
						||
                raise e
 | 
						||
 | 
						||
        trigger_price_value = message.text
 | 
						||
 | 
						||
        if not is_number(trigger_price_value):
 | 
						||
            await message.answer(
 | 
						||
                "Ошибка: введите валидное число.",
 | 
						||
                reply_markup=kbi.back_to_additional_settings,
 | 
						||
            )
 | 
						||
            logger.debug(
 | 
						||
                "User %s input invalid (not an valid number): %s",
 | 
						||
                message.from_user.id,
 | 
						||
                trigger_price_value,
 | 
						||
            )
 | 
						||
            return
 | 
						||
 | 
						||
        req = await rq.set_trigger_price(
 | 
						||
            tg_id=message.from_user.id, trigger_price=safe_float(trigger_price_value)
 | 
						||
        )
 | 
						||
        if req:
 | 
						||
            await message.answer(
 | 
						||
                text=f"Цена триггера установлена на: {trigger_price_value}",
 | 
						||
                reply_markup=kbi.back_to_additional_settings,
 | 
						||
            )
 | 
						||
        else:
 | 
						||
            await message.answer(
 | 
						||
                text="Произошла ошибка при установке цены триггера.",
 | 
						||
                reply_markup=kbi.back_to_additional_settings,
 | 
						||
            )
 | 
						||
 | 
						||
        await state.clear()
 | 
						||
    except Exception as e:
 | 
						||
        await message.answer(
 | 
						||
            text="Произошла ошибка при установке цены триггера.",
 | 
						||
            reply_markup=kbi.back_to_additional_settings,
 | 
						||
        )
 | 
						||
        logger.error(
 | 
						||
            "Error processing set_trigger_price for user %s: %s",
 | 
						||
            message.from_user.id,
 | 
						||
            e,
 | 
						||
        )
 | 
						||
 | 
						||
 | 
						||
@router_additional_settings.callback_query(F.data == "leverage")
 | 
						||
async def leverage_handler(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
						||
    """
 | 
						||
    Handles the 'leverage' callback query.
 | 
						||
 | 
						||
    Clears the current FSM state, edits the message text to display the leverage options,
 | 
						||
    and shows an inline keyboard for selection.
 | 
						||
 | 
						||
    Args:
 | 
						||
        callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard.
 | 
						||
        state (FSMContext): Finite State Machine context for the current user session.
 | 
						||
 | 
						||
    Logs:
 | 
						||
        Success or error messages with user identification.
 | 
						||
    """
 | 
						||
    try:
 | 
						||
        await state.clear()
 | 
						||
        await callback_query.answer()
 | 
						||
        await state.set_state(AdditionalSettingsState.leverage_state)
 | 
						||
        msg = await callback_query.message.edit_text(
 | 
						||
            text="Введите размер кредитного плеча:",
 | 
						||
            reply_markup=kbi.back_to_additional_settings,
 | 
						||
        )
 | 
						||
        await state.update_data(prompt_message_id=msg.message_id)
 | 
						||
        logger.debug(
 | 
						||
            "Command leverage processed successfully for user: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
        )
 | 
						||
    except Exception as e:
 | 
						||
        await callback_query.answer(
 | 
						||
            text="Произошла ошибка. Пожалуйста, попробуйте позже."
 | 
						||
        )
 | 
						||
        logger.error(
 | 
						||
            "Error processing command leverage for user %s: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
            e,
 | 
						||
        )
 | 
						||
 | 
						||
 | 
						||
@router_additional_settings.message(AdditionalSettingsState.leverage_state)
 | 
						||
async def set_leverage_handler(message: Message, state: FSMContext) -> None:
 | 
						||
    """
 | 
						||
    Handles user input for setting the leverage.
 | 
						||
 | 
						||
    Updates FSM context with the selected leverage and persists the choice in database.
 | 
						||
    Sends an acknowledgement to user and clears FSM state afterward.
 | 
						||
 | 
						||
    Args:
 | 
						||
        message (Message): Incoming message from user containing the selected leverage.
 | 
						||
        state (FSMContext): Finite State Machine context for the current user session.
 | 
						||
 | 
						||
    Logs:
 | 
						||
        Success or error messages with user identification.
 | 
						||
    """
 | 
						||
    try:
 | 
						||
        try:
 | 
						||
            data = await state.get_data()
 | 
						||
            if "prompt_message_id" in data:
 | 
						||
                prompt_message_id = data["prompt_message_id"]
 | 
						||
                await message.bot.delete_message(
 | 
						||
                    chat_id=message.chat.id, message_id=prompt_message_id
 | 
						||
                )
 | 
						||
            await message.delete()
 | 
						||
        except Exception as e:
 | 
						||
            if "message to delete not found" in str(e).lower():
 | 
						||
                pass  # Ignore this error
 | 
						||
            else:
 | 
						||
                raise e
 | 
						||
 | 
						||
        get_leverage = message.text
 | 
						||
        tg_id = message.from_user.id
 | 
						||
        if not is_number(get_leverage):
 | 
						||
            await message.answer(
 | 
						||
                "Ошибка: введите валидное число.",
 | 
						||
                reply_markup=kbi.back_to_additional_settings,
 | 
						||
            )
 | 
						||
            logger.debug(
 | 
						||
                "User %s input invalid (not an valid number): %s",
 | 
						||
                message.from_user.id,
 | 
						||
                get_leverage,
 | 
						||
            )
 | 
						||
            return
 | 
						||
 | 
						||
        leverage_float = safe_float(get_leverage)
 | 
						||
 | 
						||
        symbol = await rq.get_user_symbol(tg_id=tg_id)
 | 
						||
        instruments_info = await get_instruments_info(tg_id=tg_id, symbol=symbol)
 | 
						||
 | 
						||
        if instruments_info is not None:
 | 
						||
            min_leverage = (
 | 
						||
                    safe_float(instruments_info.get("leverageFilter").get("minLeverage"))
 | 
						||
                    or 1
 | 
						||
            )
 | 
						||
            max_leverage = (
 | 
						||
                    safe_float(instruments_info.get("leverageFilter").get("maxLeverage"))
 | 
						||
                    or 100
 | 
						||
            )
 | 
						||
 | 
						||
            if leverage_float > max_leverage or leverage_float < min_leverage:
 | 
						||
                await message.answer(
 | 
						||
                    text=f"Кредитное плечо должно быть от {min_leverage} до {max_leverage}",
 | 
						||
                    reply_markup=kbi.back_to_additional_settings,
 | 
						||
                )
 | 
						||
                logger.info(
 | 
						||
                    "User %s input invalid (out of range): %s, %s, %s: %s",
 | 
						||
                    message.from_user.id,
 | 
						||
                    symbol,
 | 
						||
                    min_leverage,
 | 
						||
                    max_leverage,
 | 
						||
                    leverage_float,
 | 
						||
                )
 | 
						||
                return
 | 
						||
        else:
 | 
						||
            await message.answer(
 | 
						||
                text="Произошла ошибка. Пожалуйста, попробуйте позже.",
 | 
						||
                reply_markup=kbi.back_to_additional_settings,
 | 
						||
            )
 | 
						||
 | 
						||
        response = await set_leverage(
 | 
						||
            tg_id=message.from_user.id, symbol=symbol, leverage=str(leverage_float)
 | 
						||
        )
 | 
						||
 | 
						||
        if not response:
 | 
						||
            await message.answer(
 | 
						||
                text="Невозможно установить кредитное плечо для текущего режима торговли.",
 | 
						||
                reply_markup=kbi.back_to_additional_settings,
 | 
						||
            )
 | 
						||
            return
 | 
						||
 | 
						||
        req_leverage = await rq.set_leverage(
 | 
						||
            tg_id=message.from_user.id, leverage=str(leverage_float)
 | 
						||
        )
 | 
						||
 | 
						||
        if req_leverage:
 | 
						||
            await message.answer(
 | 
						||
                text=f"Кредитное плечо успешно установлено на {leverage_float}",
 | 
						||
                reply_markup=kbi.back_to_additional_settings,
 | 
						||
            )
 | 
						||
            risk_percent = 10 / safe_float(leverage_float)
 | 
						||
            await rq.set_stop_loss_percent(
 | 
						||
                tg_id=message.from_user.id, stop_loss_percent=risk_percent)
 | 
						||
            await rq.set_take_profit_percent(
 | 
						||
                tg_id=message.from_user.id, take_profit_percent=risk_percent)
 | 
						||
            logger.info(
 | 
						||
                "User %s set leverage: %s", message.from_user.id, leverage_float
 | 
						||
            )
 | 
						||
        else:
 | 
						||
            await message.answer(
 | 
						||
                text="Произошла ошибка при установке кредитного плеча. Пожалуйста, попробуйте позже.",
 | 
						||
                reply_markup=kbi.back_to_additional_settings,
 | 
						||
            )
 | 
						||
 | 
						||
        await state.clear()
 | 
						||
    except Exception as e:
 | 
						||
        await message.answer(
 | 
						||
            text="Произошла ошибка при установке кредитного плеча. Пожалуйста, попробуйте позже.",
 | 
						||
            reply_markup=kbi.back_to_additional_settings,
 | 
						||
        )
 | 
						||
        logger.error(
 | 
						||
            "Error processing command leverage for user %s: %s", message.from_user.id, e
 | 
						||
        )
 | 
						||
 | 
						||
 | 
						||
@router_additional_settings.callback_query(F.data == "order_quantity")
 | 
						||
async def order_quantity(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
						||
    """
 | 
						||
    Handles the 'order_quantity' callback query.
 | 
						||
 | 
						||
    Clears the current FSM state, edits the message text to display the order quantity options,
 | 
						||
    and shows an inline keyboard for selection.
 | 
						||
 | 
						||
    Args:
 | 
						||
        callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard.
 | 
						||
        state (FSMContext): Finite State Machine context for the current user session.
 | 
						||
 | 
						||
    Logs:
 | 
						||
        Success or error messages with user identification.
 | 
						||
    """
 | 
						||
    try:
 | 
						||
        await state.clear()
 | 
						||
        await state.set_state(AdditionalSettingsState.quantity_state)
 | 
						||
        msg = await callback_query.message.edit_text(
 | 
						||
            text=f"Введите базовую ставку в USDT:",
 | 
						||
            reply_markup=kbi.back_to_additional_settings,
 | 
						||
        )
 | 
						||
        await state.update_data(prompt_message_id=msg.message_id)
 | 
						||
        logger.debug(
 | 
						||
            "Command order_quantity processed successfully for user: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
        )
 | 
						||
    except Exception as e:
 | 
						||
        await callback_query.answer(
 | 
						||
            text="Произошла ошибка. Пожалуйста, попробуйте позже."
 | 
						||
        )
 | 
						||
        logger.error(
 | 
						||
            "Error processing command order_quantity for user %s: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
            e,
 | 
						||
        )
 | 
						||
 | 
						||
 | 
						||
@router_additional_settings.message(AdditionalSettingsState.quantity_state)
 | 
						||
async def set_order_quantity(message: Message, state: FSMContext) -> None:
 | 
						||
    """
 | 
						||
    Handles user input for setting the order quantity.
 | 
						||
 | 
						||
    Updates FSM context with the selected order quantity and persists the choice in database.
 | 
						||
    Sends an acknowledgement to user and clears FSM state afterward.
 | 
						||
 | 
						||
    Args:
 | 
						||
        message (Message): Incoming message from user containing the selected order quantity.
 | 
						||
        state (FSMContext): Finite State Machine context for the current user session.
 | 
						||
 | 
						||
    Logs:
 | 
						||
        Success or error messages with user identification.
 | 
						||
    """
 | 
						||
    try:
 | 
						||
        try:
 | 
						||
            data = await state.get_data()
 | 
						||
            if "prompt_message_id" in data:
 | 
						||
                prompt_message_id = data["prompt_message_id"]
 | 
						||
                await message.bot.delete_message(
 | 
						||
                    chat_id=message.chat.id, message_id=prompt_message_id
 | 
						||
                )
 | 
						||
            await message.delete()
 | 
						||
        except Exception as e:
 | 
						||
            if "message to delete not found" in str(e).lower():
 | 
						||
                pass  # Ignore this error
 | 
						||
            else:
 | 
						||
                raise e
 | 
						||
 | 
						||
        order_quantity_value = message.text
 | 
						||
 | 
						||
        if not is_number(order_quantity_value):
 | 
						||
            await message.answer(
 | 
						||
                "Ошибка: введите валидное число.",
 | 
						||
                reply_markup=kbi.back_to_additional_settings,
 | 
						||
            )
 | 
						||
            logger.debug(
 | 
						||
                "User %s input invalid (not an valid number): %s",
 | 
						||
                message.from_user.id,
 | 
						||
                order_quantity_value,
 | 
						||
            )
 | 
						||
            return
 | 
						||
 | 
						||
        quantity = safe_float(order_quantity_value)
 | 
						||
 | 
						||
        req = await rq.set_order_quantity(
 | 
						||
            tg_id=message.from_user.id, order_quantity=quantity
 | 
						||
        )
 | 
						||
 | 
						||
        if req:
 | 
						||
            await message.answer(
 | 
						||
                text=f"Базовая ставка установлена на {message.text} USDT",
 | 
						||
                reply_markup=kbi.back_to_additional_settings,
 | 
						||
            )
 | 
						||
        else:
 | 
						||
            await message.answer(
 | 
						||
                text="Произошла ошибка при установке кол-ва ордера. Пожалуйста, попробуйте позже.",
 | 
						||
                reply_markup=kbi.back_to_additional_settings,
 | 
						||
            )
 | 
						||
 | 
						||
        await state.clear()
 | 
						||
    except Exception as e:
 | 
						||
        await message.answer(
 | 
						||
            text="Произошла ошибка при установке базовой ставки. Пожалуйста, попробуйте позже.",
 | 
						||
            reply_markup=kbi.back_to_additional_settings,
 | 
						||
        )
 | 
						||
        logger.error("Error processing command set_order_quantity: %s", e)
 | 
						||
 | 
						||
 | 
						||
@router_additional_settings.callback_query(F.data == "martingale_factor")
 | 
						||
async def martingale_factor(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
						||
    """
 | 
						||
    Handles the 'martingale_factor' callback query.
 | 
						||
 | 
						||
    Clears the current FSM state, edits the message text to display the martingale factor options,
 | 
						||
    and shows an inline keyboard for selection.
 | 
						||
 | 
						||
    Args:
 | 
						||
        callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard.
 | 
						||
        state (FSMContext): Finite State Machine context for the current user session.
 | 
						||
 | 
						||
    Logs:
 | 
						||
        Success or error messages with user identification.
 | 
						||
    """
 | 
						||
    try:
 | 
						||
        await state.clear()
 | 
						||
        await state.set_state(AdditionalSettingsState.martingale_factor_state)
 | 
						||
        msg = await callback_query.message.edit_text(
 | 
						||
            text="Введите коэффициент мартингейла:",
 | 
						||
            reply_markup=kbi.back_to_additional_settings,
 | 
						||
        )
 | 
						||
        await state.update_data(prompt_message_id=msg.message_id)
 | 
						||
        logger.debug(
 | 
						||
            "Command martingale_factor processed successfully for user: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
        )
 | 
						||
    except Exception as e:
 | 
						||
        await callback_query.answer(
 | 
						||
            text="Произошла ошибка. Пожалуйста, попробуйте позже."
 | 
						||
        )
 | 
						||
        logger.error(
 | 
						||
            "Error processing command martingale_factor for user %s: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
            e,
 | 
						||
        )
 | 
						||
 | 
						||
 | 
						||
@router_additional_settings.message(AdditionalSettingsState.martingale_factor_state)
 | 
						||
async def set_martingale_factor(message: Message, state: FSMContext) -> None:
 | 
						||
    """
 | 
						||
    Handles user input for setting the martingale factor.
 | 
						||
 | 
						||
    Updates FSM context with the selected martingale factor and persists the choice in database.
 | 
						||
    Sends an acknowledgement to user and clears FSM state afterward.
 | 
						||
 | 
						||
    Args:
 | 
						||
        message (Message): Incoming message from user containing the selected martingale factor.
 | 
						||
        state (FSMContext): Finite State Machine context for the current user session.
 | 
						||
 | 
						||
    Logs:
 | 
						||
        Success or error messages with user identification.
 | 
						||
    """
 | 
						||
    try:
 | 
						||
        try:
 | 
						||
            data = await state.get_data()
 | 
						||
            if "prompt_message_id" in data:
 | 
						||
                prompt_message_id = data["prompt_message_id"]
 | 
						||
                await message.bot.delete_message(
 | 
						||
                    chat_id=message.chat.id, message_id=prompt_message_id
 | 
						||
                )
 | 
						||
            await message.delete()
 | 
						||
        except Exception as e:
 | 
						||
            if "message to delete not found" in str(e).lower():
 | 
						||
                pass  # Ignore this error
 | 
						||
            else:
 | 
						||
                raise e
 | 
						||
 | 
						||
        martingale_factor_value = message.text
 | 
						||
 | 
						||
        if not is_number(martingale_factor_value):
 | 
						||
            await message.answer(
 | 
						||
                "Ошибка: введите валидное число.",
 | 
						||
                reply_markup=kbi.back_to_additional_settings,
 | 
						||
            )
 | 
						||
            logger.debug(
 | 
						||
                "User %s input invalid (not an valid number): %s",
 | 
						||
                message.from_user.id,
 | 
						||
                martingale_factor_value,
 | 
						||
            )
 | 
						||
            return
 | 
						||
 | 
						||
        martingale_factor_value_float = safe_float(martingale_factor_value)
 | 
						||
 | 
						||
        if martingale_factor_value_float < 0.1 or martingale_factor_value_float > 10:
 | 
						||
            await message.answer(text="Ошибка: коэффициент мартингейла должен быть в диапазоне от 0.1 до 10")
 | 
						||
            logger.debug("User %s input invalid (not in range 0.1 to 10): %s", message.from_user.id,
 | 
						||
                         martingale_factor_value_float)
 | 
						||
            return
 | 
						||
 | 
						||
        req = await rq.set_martingale_factor(
 | 
						||
            tg_id=message.from_user.id, martingale_factor=martingale_factor_value_float
 | 
						||
        )
 | 
						||
 | 
						||
        if req:
 | 
						||
            await message.answer(
 | 
						||
                text=f"Коэффициент мартингейла установлен на {message.text}",
 | 
						||
                reply_markup=kbi.back_to_additional_settings,
 | 
						||
            )
 | 
						||
        else:
 | 
						||
            await message.answer(
 | 
						||
                text="Произошла ошибка при установке коэффициента мартингейла. Пожалуйста, попробуйте позже.",
 | 
						||
                reply_markup=kbi.back_to_additional_settings,
 | 
						||
            )
 | 
						||
 | 
						||
        await state.clear()
 | 
						||
    except Exception as e:
 | 
						||
        await message.answer(
 | 
						||
            text="Произошла ошибка при установке коэффициента мартингейла. Пожалуйста, попробуйте позже.",
 | 
						||
            reply_markup=kbi.back_to_additional_settings,
 | 
						||
        )
 | 
						||
        logger.error("Error processing command set_martingale_factor: %s", e)
 | 
						||
 | 
						||
 | 
						||
@router_additional_settings.callback_query(F.data == "max_bets_in_series")
 | 
						||
async def max_bets_in_series(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
						||
    """
 | 
						||
    Handles the 'max_bets_in_series' callback query.
 | 
						||
 | 
						||
    Clears the current FSM state, edits the message text to display the max bets in series options,
 | 
						||
    and shows an inline keyboard for selection.
 | 
						||
 | 
						||
    Args:
 | 
						||
        callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard.
 | 
						||
        state (FSMContext): Finite State Machine context for the current user session.
 | 
						||
 | 
						||
    Logs:
 | 
						||
        Success or error messages with user identification.
 | 
						||
    """
 | 
						||
    try:
 | 
						||
        await state.clear()
 | 
						||
        await state.set_state(AdditionalSettingsState.max_bets_in_series_state)
 | 
						||
        msg = await callback_query.message.edit_text(
 | 
						||
            text="Введите максимальное количество ставок в серии:",
 | 
						||
            reply_markup=kbi.back_to_additional_settings,
 | 
						||
        )
 | 
						||
        await state.update_data(prompt_message_id=msg.message_id)
 | 
						||
        logger.debug(
 | 
						||
            "Command max_bets_in_series processed successfully for user: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
        )
 | 
						||
    except Exception as e:
 | 
						||
        await callback_query.answer(
 | 
						||
            text="Произошла ошибка. Пожалуйста, попробуйте позже."
 | 
						||
        )
 | 
						||
        logger.error(
 | 
						||
            "Error processing command max_bets_in_series for user %s: %s",
 | 
						||
            callback_query.from_user.id,
 | 
						||
            e,
 | 
						||
        )
 | 
						||
 | 
						||
 | 
						||
@router_additional_settings.message(AdditionalSettingsState.max_bets_in_series_state)
 | 
						||
async def set_max_bets_in_series(message: Message, state: FSMContext) -> None:
 | 
						||
    """
 | 
						||
    Handles user input for setting the max bets in series.
 | 
						||
 | 
						||
    Updates FSM context with the selected max steps and persists the choice in database.
 | 
						||
    Sends an acknowledgement to user and clears FSM state afterward.
 | 
						||
 | 
						||
    Args:
 | 
						||
        message (Message): Incoming message from user containing the selected max bets in series.
 | 
						||
        state (FSMContext): Finite State Machine context for the current user session.
 | 
						||
 | 
						||
    Logs:
 | 
						||
        Success or error messages with user identification.
 | 
						||
    """
 | 
						||
    try:
 | 
						||
        try:
 | 
						||
            data = await state.get_data()
 | 
						||
            if "prompt_message_id" in data:
 | 
						||
                prompt_message_id = data["prompt_message_id"]
 | 
						||
                await message.bot.delete_message(
 | 
						||
                    chat_id=message.chat.id, message_id=prompt_message_id
 | 
						||
                )
 | 
						||
            await message.delete()
 | 
						||
        except Exception as e:
 | 
						||
            if "message to delete not found" in str(e).lower():
 | 
						||
                pass  # Ignore this error
 | 
						||
            else:
 | 
						||
                raise e
 | 
						||
 | 
						||
        max_bets_in_series_value = message.text
 | 
						||
 | 
						||
        if not is_int(max_bets_in_series_value):
 | 
						||
            await message.answer(
 | 
						||
                "Ошибка: введите валидное число.",
 | 
						||
                reply_markup=kbi.back_to_additional_settings,
 | 
						||
            )
 | 
						||
            logger.debug(
 | 
						||
                "User %s input invalid (not an valid number): %s",
 | 
						||
                message.from_user.id,
 | 
						||
                max_bets_in_series_value,
 | 
						||
            )
 | 
						||
            return
 | 
						||
 | 
						||
        if safe_float(max_bets_in_series_value) < 1 or safe_float(max_bets_in_series_value) > 100:
 | 
						||
            await message.answer(
 | 
						||
                "Ошибка: число должно быть в диапазоне от 1 до 100.",
 | 
						||
                reply_markup=kbi.back_to_additional_settings,
 | 
						||
            )
 | 
						||
            logger.debug(
 | 
						||
                "User %s input invalid (not in range 1 to 100): %s",
 | 
						||
                message.from_user.id,
 | 
						||
                max_bets_in_series_value,
 | 
						||
            )
 | 
						||
            return
 | 
						||
 | 
						||
        req = await rq.set_max_bets_in_series(
 | 
						||
            tg_id=message.from_user.id, max_bets_in_series=int(max_bets_in_series_value)
 | 
						||
        )
 | 
						||
 | 
						||
        if req:
 | 
						||
            await message.answer(
 | 
						||
                text=f"Максимальное количество шагов установлено на {message.text}",
 | 
						||
                reply_markup=kbi.back_to_additional_settings,
 | 
						||
            )
 | 
						||
        else:
 | 
						||
            await message.answer(
 | 
						||
                text="Произошла ошибка при установке максимального количества шагов. Пожалуйста, попробуйте позже.",
 | 
						||
                reply_markup=kbi.back_to_additional_settings,
 | 
						||
            )
 | 
						||
 | 
						||
        await state.clear()
 | 
						||
    except Exception as e:
 | 
						||
        await message.answer(
 | 
						||
            text="Произошла ошибка при установке максимального количества шагов. Пожалуйста, попробуйте позже.",
 | 
						||
            reply_markup=kbi.back_to_additional_settings,
 | 
						||
        )
 | 
						||
        logger.error(
 | 
						||
            "Error processing command set_max_bets_in_series for user %s: %s",
 | 
						||
            message.from_user.id,
 | 
						||
            e,
 | 
						||
        )
 |