diff --git a/alembic/versions/e5d612e44563_added_column_side_for_additional_.py b/alembic/versions/e5d612e44563_added_column_side_for_additional_.py new file mode 100644 index 0000000..1e6555d --- /dev/null +++ b/alembic/versions/e5d612e44563_added_column_side_for_additional_.py @@ -0,0 +1,34 @@ +"""Added column side for additional_setiings + +Revision ID: e5d612e44563 +Revises: fbf4e3658310 +Create Date: 2025-10-25 18:25:52.746250 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'e5d612e44563' +down_revision: Union[str, Sequence[str], None] = 'fbf4e3658310' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('user_additional_settings', + sa.Column('side', sa.String(), nullable=False, server_default='') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('user_additional_settings', 'side') + # ### end Alembic commands ### diff --git a/app/bybit/open_positions.py b/app/bybit/open_positions.py index 9c98ee6..8d426c6 100644 --- a/app/bybit/open_positions.py +++ b/app/bybit/open_positions.py @@ -28,11 +28,9 @@ async def start_trading_cycle( symbol = await rq.get_user_symbol(tg_id=tg_id) additional_data = await rq.get_user_additional_settings(tg_id=tg_id) risk_management_data = await rq.get_user_risk_management(tg_id=tg_id) - user_deals_data = await rq.get_user_deal_by_symbol( - tg_id=tg_id, symbol=symbol - ) trade_mode = additional_data.trade_mode switch_side = additional_data.switch_side + side= additional_data.side margin_type = additional_data.margin_type leverage = additional_data.leverage order_quantity = additional_data.order_quantity @@ -43,19 +41,8 @@ async def start_trading_cycle( stop_loss_percent = risk_management_data.stop_loss_percent total_commission = 0 - get_side = "Buy" - - if user_deals_data: - get_side = user_deals_data.last_side or "Buy" - if trade_mode == "Switch": - if switch_side == "По направлению": - side = get_side - else: - if get_side == "Buy": - side = "Sell" - else: - side = "Buy" + side = side else: if trade_mode == "Long": side = "Buy" diff --git a/app/telegram/handlers/main_settings/additional_settings.py b/app/telegram/handlers/main_settings/additional_settings.py index 894e3ac..c792555 100644 --- a/app/telegram/handlers/main_settings/additional_settings.py +++ b/app/telegram/handlers/main_settings/additional_settings.py @@ -105,10 +105,10 @@ async def trade_mode(callback_query: CallbackQuery, state: FSMContext) -> None: await state.clear() -@router_additional_settings.callback_query(F.data == "switch_side_start") -async def switch_side_start(callback_query: CallbackQuery, state: FSMContext) -> None: +@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_start' callback query. + 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. @@ -123,13 +123,13 @@ async def switch_side_start(callback_query: CallbackQuery, state: FSMContext) -> try: await state.clear() await callback_query.message.edit_text( - text="Выберите направление первой сделки серии:\n\n" + text="Выберите направление первой сделки последующих серии:\n\n" "По направлению - сделка открывается в направлении последней сделки предыдущей серии.\n" "Противоположно - сделка открывается в противоположном направлении последней сделки предыдущей серии.\n", reply_markup=kbi.switch_side, ) logger.debug( - "Command switch_side_start processed successfully for user: %s", + "Command switch_side_second processed successfully for user: %s", callback_query.from_user.id, ) except Exception as e: @@ -137,7 +137,7 @@ async def switch_side_start(callback_query: CallbackQuery, state: FSMContext) -> text="Произошла ошибка. Пожалуйста, попробуйте позже." ) logger.error( - "Error processing command switch_side_start for user %s: %s", + "Error processing command switch_side_second for user %s: %s", callback_query.from_user.id, e, ) @@ -193,6 +193,89 @@ async def switch_side_handler(callback_query: CallbackQuery, state: FSMContext) 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 diff --git a/app/telegram/handlers/settings.py b/app/telegram/handlers/settings.py index 592e063..831768d 100644 --- a/app/telegram/handlers/settings.py +++ b/app/telegram/handlers/settings.py @@ -62,10 +62,19 @@ async def additional_settings(callback_query: CallbackQuery, state: FSMContext) max_bets = additional_data.max_bets_in_series quantity = f(additional_data.order_quantity) trigger_price = f(additional_data.trigger_price) or 0 + side = additional_data.side + + side_map = { + "Buy": "Лонг", + "Sell": "Шорт", + } + side_rus = side_map.get(side, side) switch_side_mode = "" + side = "" if trade_mode == "Switch": - switch_side_mode = f"- Направление первой сделки: {switch_side}\n" + side = f"- Направление первой сделки: {side_rus}\n" + switch_side_mode = f"- Направление первой сделки последующих серии: {switch_side}\n" total_budget = await calculate_total_budget( quantity=quantity, @@ -75,6 +84,7 @@ async def additional_settings(callback_query: CallbackQuery, state: FSMContext) text = ( f"Основные настройки:\n\n" f"- Режим торговли: {trade_mode_rus}\n" + f"{side}" f"{switch_side_mode}" f"- Тип маржи: {margin_type_rus}\n" f"- Размер кредитного плеча: {leverage:.2f}\n" diff --git a/app/telegram/keyboards/inline.py b/app/telegram/keyboards/inline.py index 9f70820..8d7e172 100644 --- a/app/telegram/keyboards/inline.py +++ b/app/telegram/keyboards/inline.py @@ -94,7 +94,10 @@ def get_additional_settings_keyboard(mode: str if mode == "Switch": buttons.append( - [InlineKeyboardButton(text="Направление первой сделки", callback_data="switch_side_start")] + [InlineKeyboardButton(text="Направление первой сделки первой серии", callback_data="switch_side_start")] + ) + buttons.append( + [InlineKeyboardButton(text="Направление первой сделки последующих серии", callback_data="switch_side_second")] ) buttons.append( @@ -148,6 +151,19 @@ switch_side = InlineKeyboardMarkup( ] ) +side_for_switch = InlineKeyboardMarkup( + inline_keyboard=[ + [ + InlineKeyboardButton(text="Лонг", callback_data="buy_switch"), + InlineKeyboardButton(text="Шорт", callback_data="sell_switch"), + ], + [ + InlineKeyboardButton(text="Назад", callback_data="additional_settings"), + InlineKeyboardButton(text="На главную", callback_data="profile_bybit"), + ], + ] +) + margin_type = InlineKeyboardMarkup( inline_keyboard=[ [ diff --git a/database/models.py b/database/models.py index 6dd8865..f08f49e 100644 --- a/database/models.py +++ b/database/models.py @@ -92,6 +92,7 @@ class UserAdditionalSettings(Base): nullable=False, unique=True) trade_mode = Column(String, nullable=False, default="Merged_Single") switch_side = Column(String, nullable=False, default="По направлению") + side = Column(String, nullable=False, default="Buy") trigger_price = Column(Float, nullable=False, default=0.0) margin_type = Column(String, nullable=False, default="ISOLATED_MARGIN") leverage = Column(String, nullable=False, default="10") diff --git a/database/request.py b/database/request.py index 39a6b4b..ab48782 100644 --- a/database/request.py +++ b/database/request.py @@ -358,6 +358,45 @@ async def set_switch_side(tg_id: int, switch_side: str) -> bool: return False +async def set_side(tg_id: int, side: str) -> bool: + """ + Set side for a user in the database. + :param tg_id: Telegram user ID + :param side: "BUY" or "SELL" + :return: True if successful, False otherwise + """ + try: + async with async_session() as session: + result = await session.execute( + select(User) + .options(joinedload(User.user_additional_settings)) + .filter_by(tg_id=tg_id) + ) + user = result.scalars().first() + + if user: + if user.user_additional_settings: + # Updating existing record + user.user_additional_settings.side = side + else: + # Creating new record + user_additional_settings = UserAdditionalSettings( + side=side, + user=user, + ) + session.add(user_additional_settings) + + await session.commit() + logger.info("User side updated for user: %s", tg_id) + return True + else: + logger.error("User not found with tg_id: %s", tg_id) + return False + except Exception as e: + logger.error("Error adding/updating user side for user %s: %s", tg_id, e) + return False + + async def set_leverage(tg_id: int, leverage: str) -> bool: """ Set leverage for a user in the database.