2
0
forked from kodorvan/stcs

The entire database has been changed to PostgresSQL. The entire code has been updated.

This commit is contained in:
algizn97
2025-10-01 15:23:21 +05:00
parent e5a3de4ed8
commit 97662081ce
49 changed files with 7916 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
__all__ = "router"
from aiogram import Router
from app.telegram.handlers.add_bybit_api import router_add_bybit_api
from app.telegram.handlers.changing_the_symbol import router_changing_the_symbol
from app.telegram.handlers.close_orders import router_close_orders
from app.telegram.handlers.common import router_common
from app.telegram.handlers.get_positions_handlers import router_get_positions_handlers
from app.telegram.handlers.handlers_main import router_handlers_main
from app.telegram.handlers.main_settings import router_main_settings
from app.telegram.handlers.settings import router_settings
from app.telegram.handlers.start_trading import router_start_trading
from app.telegram.handlers.stop_trading import router_stop_trading
from app.telegram.handlers.tp_sl_handlers import router_tp_sl_handlers
router = Router(name=__name__)
router.include_router(router_handlers_main)
router.include_router(router_add_bybit_api)
router.include_router(router_settings)
router.include_router(router_main_settings)
router.include_router(router_changing_the_symbol)
router.include_router(router_get_positions_handlers)
router.include_router(router_start_trading)
router.include_router(router_stop_trading)
router.include_router(router_close_orders)
router.include_router(router_tp_sl_handlers)
# Do not add anything below this router
router.include_router(router_common)

View File

@@ -0,0 +1,150 @@
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 app.telegram.keyboards.reply as kbr
import database.request as rq
from app.bybit.profile_bybit import user_profile_bybit
from app.telegram.states.states import AddBybitApiState
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("add_bybit_api")
router_add_bybit_api = Router(name="add_bybit_api")
@router_add_bybit_api.callback_query(F.data == "connect_platform")
async def connect_platform(callback: CallbackQuery, state: FSMContext) -> None:
"""
Handles the callback query to initiate Bybit platform connection.
Sends instructions on how to create and provide API keys to the bot.
:param callback: CallbackQuery object triggered by user interaction.
:param state: FSMContext object to manage state data.
:return: None
"""
try:
await state.clear()
await callback.answer()
user = await rq.get_user(tg_id=callback.from_user.id)
if user:
await callback.message.answer(
text=(
"Подключение Bybit аккаунта \n\n"
"1. Зарегистрируйтесь или войдите в свой аккаунт на Bybit по ссылке: "
"[Перейти на Bybit](https://www.bybit.com/invite?ref=YME83OJ).\n"
"2. В личном кабинете выберите раздел API. \n"
"3. Создание нового API ключа\n"
" - Нажмите кнопку Create New Key (Создать новый ключ).\n"
" - Выберите системно-сгенерированный ключ.\n"
" - Укажите название API ключа (любое). \n"
" - Выберите права доступа для торговли (Trade). \n"
" - Можно ограничить доступ по IP для безопасности.\n"
"4. Подтверждение создания\n"
" - Подтвердите создание ключа.\n"
" - Отправьте чат-роботу.\n\n"
"Важно: сохраните отдельно API Key и Secret Key в надежном месте. Secret ключ отображается только один раз."
),
parse_mode="Markdown",
reply_markup=kbi.add_bybit_api,
disable_web_page_preview=True,
)
else:
await rq.create_user(
tg_id=callback.from_user.id, username=callback.from_user.username
)
await rq.set_user_symbol(tg_id=callback.from_user.id, symbol="BTCUSDT")
await rq.create_user_additional_settings(tg_id=callback.from_user.id)
await rq.create_user_risk_management(tg_id=callback.from_user.id)
await rq.create_user_conditional_settings(tg_id=callback.from_user.id)
await connect_platform(callback=callback, state=state)
except Exception as e:
logger.error("Error adding bybit API for user %s: %s", callback.from_user.id, e)
await callback.message.answer(
text="Произошла ошибка. Пожалуйста, попробуйте позже."
)
@router_add_bybit_api.callback_query(F.data == "add_bybit_api")
async def process_api_key(callback: CallbackQuery, state: FSMContext) -> None:
"""
Starts the FSM flow to add Bybit API keys.
Sets the FSM state to prompt user to enter API Key.
:param callback: CallbackQuery object.
:param state: FSMContext for managing user state.
"""
try:
await state.clear()
await state.set_state(AddBybitApiState.api_key_state)
await callback.answer()
await callback.message.answer(text="Введите API Key:")
except Exception as e:
logger.error("Error adding bybit API for user %s: %s", callback.from_user.id, e)
await callback.message.answer(
text="Произошла ошибка. Пожалуйста, попробуйте позже."
)
@router_add_bybit_api.message(AddBybitApiState.api_key_state)
async def process_secret_key(message: Message, state: FSMContext) -> None:
"""
Receives the API Key input from the user, stores it in FSM context,
then sets state to collect Secret Key.
:param message: Message object with user's input.
:param state: FSMContext for managing user state.
"""
try:
api_key = message.text
await state.update_data(api_key=api_key)
await state.set_state(AddBybitApiState.api_secret_state)
await message.answer(text="Введите Secret Key:")
except Exception as e:
logger.error("Error adding bybit API for user %s: %s", message.from_user.id, e)
await message.answer(text="Произошла ошибка. Пожалуйста, попробуйте позже.")
@router_add_bybit_api.message(AddBybitApiState.api_secret_state)
async def add_bybit_api(message: Message, state: FSMContext) -> None:
"""
Receives the Secret Key input, stores it, saves both API keys in the database,
clears FSM state and confirms success to the user.
:param message: Message object with user's input.
:param state: FSMContext for managing user state.
"""
try:
api_secret = message.text
api_key = (await state.get_data()).get("api_key")
await state.update_data(api_secret=api_secret)
if not api_key or not api_secret:
await message.answer("Введите корректные данные.")
return
result = await rq.set_user_api(
tg_id=message.from_user.id, api_key=api_key, api_secret=api_secret
)
if result:
await message.answer(text="Данные добавлены.", reply_markup=kbr.profile)
await user_profile_bybit(
tg_id=message.from_user.id, message=message, state=state
)
logger.debug(
"Bybit API added successfully for user: %s", message.from_user.id
)
else:
await message.answer(text="Произошла ошибка. Пожалуйста, попробуйте позже.")
logger.error(
"Error adding bybit API for user %s: %s", message.from_user.id, result
)
await state.clear()
except Exception as e:
logger.error("Error adding bybit API for user %s: %s", message.from_user.id, e)
await message.answer(text="Произошла ошибка. Пожалуйста, попробуйте позже.")

View File

@@ -0,0 +1,164 @@
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_tickers import get_tickers
from app.bybit.profile_bybit import user_profile_bybit
from app.bybit.set_functions.set_leverage import (
set_leverage,
set_leverage_to_buy_and_sell,
)
from app.bybit.set_functions.set_margin_mode import set_margin_mode
from app.bybit.set_functions.set_switch_position_mode import set_switch_position_mode
from app.telegram.states.states import ChangingTheSymbolState
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("changing_the_symbol")
router_changing_the_symbol = Router(name="changing_the_symbol")
@router_changing_the_symbol.callback_query(F.data == "change_symbol")
async def change_symbol(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handler for the "change_symbol" command.
Sends a message with available symbols to choose from.
"""
try:
await state.clear()
await state.set_state(ChangingTheSymbolState.symbol_state)
msg = await callback_query.message.edit_text(
text="Выберите название инструмента без лишних символов (например: BTCUSDT):",
reply_markup=kbi.symbol,
)
await state.update_data(prompt_message_id=msg.message_id)
logger.debug(
"Command change_symbol processed successfully for user: %s",
callback_query.from_user.id,
)
except Exception as e:
await callback_query.answer(
text="Произошла ошибка. Пожалуйста, попробуйте позже."
)
logger.error(
"Error processing command change_symbol for user %s: %s",
callback_query.from_user.id,
e,
)
@router_changing_the_symbol.message(ChangingTheSymbolState.symbol_state)
async def set_symbol(message: Message, state: FSMContext) -> None:
"""
Handler for user input for setting the symbol.
Updates FSM context with the selected symbol 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 symbol.
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
symbol = message.text.upper()
additional_settings = await rq.get_user_additional_settings(
tg_id=message.from_user.id
)
if not additional_settings:
await rq.create_user_additional_settings(tg_id=message.from_user.id)
return
trade_mode = additional_settings.trade_mode or "Merged_Single"
mode = 0 if trade_mode == "Merged_Single" else 3
margin_type = additional_settings.margin_type or "ISOLATED_MARGIN"
leverage = "10"
leverage_to_buy = "10"
leverage_to_sell = "10"
ticker = await get_tickers(tg_id=message.from_user.id, symbol=symbol)
if ticker is None:
await message.answer(
text=f"Инструмент {symbol} не найден.", reply_markup=kbi.symbol
)
return
req = await rq.set_user_symbol(tg_id=message.from_user.id, symbol=symbol)
if not req:
await message.answer(
text="Произошла ошибка при установке инструмента.",
reply_markup=kbi.symbol,
)
return
await user_profile_bybit(
tg_id=message.from_user.id, message=message, state=state
)
res = await set_switch_position_mode(
tg_id=message.from_user.id, symbol=symbol, mode=mode
)
if res == "You have an existing position, so position mode cannot be switched":
if mode == 0:
mode = 3
else:
mode = 0
await set_switch_position_mode(
tg_id=message.from_user.id, symbol=symbol, mode=mode
)
if trade_mode == "Merged_Single":
trade_mode = "Both_Sides"
else:
trade_mode = "Merged_Single"
await rq.set_trade_mode(tg_id=message.from_user.id, trade_mode=trade_mode)
await set_margin_mode(tg_id=message.from_user.id, margin_mode=margin_type)
if margin_type == "ISOLATED_MARGIN":
await set_leverage_to_buy_and_sell(
tg_id=message.from_user.id,
symbol=symbol,
leverage_to_buy=str(leverage_to_buy),
leverage_to_sell=str(leverage_to_sell),
)
else:
await set_leverage(
tg_id=message.from_user.id, symbol=symbol, leverage=str(leverage)
)
await rq.set_leverage(tg_id=message.from_user.id, leverage=str(leverage))
await rq.set_leverage_to_buy_and_sell(
tg_id=message.from_user.id,
leverage_to_buy=str(leverage_to_buy),
leverage_to_sell=str(leverage_to_sell),
)
await rq.set_limit_price(tg_id=message.from_user.id, limit_price=0)
await rq.set_trigger_price(tg_id=message.from_user.id, trigger_price=0)
await state.clear()
except Exception as e:
await message.answer(text="Произошла ошибка. Пожалуйста, попробуйте позже.")
logger.error("Error setting symbol for user %s: %s", message.from_user.id, e)

View File

@@ -0,0 +1,120 @@
import logging.config
from aiogram import F, Router
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery
from app.bybit.close_positions import cancel_all_orders, cancel_order, close_position
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("close_orders")
router_close_orders = Router(name="close_orders")
@router_close_orders.callback_query(
lambda c: c.data and c.data.startswith("close_position_")
)
async def close_position_handler(
callback_query: CallbackQuery, state: FSMContext
) -> None:
"""
Close a position.
:param callback_query: Incoming callback query from Telegram inline keyboard.
:param state: Finite State Machine context for the current user session.
:return: None
"""
try:
symbol = callback_query.data.split("_", 2)[2]
res = await close_position(tg_id=callback_query.from_user.id, symbol=symbol)
if not res:
await callback_query.answer(text="Произошла ошибка при закрытии позиции.")
return
await callback_query.answer(text="Позиция успешно закрыта.")
logger.debug(
"Command close_position processed successfully for user: %s",
callback_query.from_user.id,
)
except Exception as e:
await callback_query.answer(text="Произошла ошибка при закрытии позиции.")
logger.error(
"Error processing command close_position for user %s: %s",
callback_query.from_user.id,
e,
)
finally:
await state.clear()
@router_close_orders.callback_query(
lambda c: c.data and c.data.startswith("close_order_")
)
async def cancel_order_handler(
callback_query: CallbackQuery, state: FSMContext
) -> None:
"""
Cancel an order.
:param callback_query: Incoming callback query from Telegram inline keyboard.
:param state: Finite State Machine context for the current user session.
:return: None
"""
try:
symbol = callback_query.data.split("_", 2)[2]
res = await cancel_order(tg_id=callback_query.from_user.id, symbol=symbol)
if not res:
await callback_query.answer(text="Произошла ошибка при закрытии ордера.")
return
await callback_query.answer(text="Ордер успешно закрыт.")
logger.debug(
"Command close_order processed successfully for user: %s",
callback_query.from_user.id,
)
except Exception as e:
await callback_query.answer(text="Произошла ошибка при закрытии ордера.")
logger.error(
"Error processing command close_order for user %s: %s",
callback_query.from_user.id,
e,
)
finally:
await state.clear()
@router_close_orders.callback_query(F.data == "cancel_all_orders")
async def cancel_all_orders_handler(
callback_query: CallbackQuery, state: FSMContext
) -> None:
"""
Close all open positions and orders.
:param callback_query: Incoming callback query from Telegram inline keyboard.
:param state: Finite State Machine context for the current user session.
:return: None
"""
try:
res = await cancel_all_orders(tg_id=callback_query.from_user.id)
if not res:
await callback_query.answer(
text="Произошла ошибка при закрытии всех ордеров."
)
return
await callback_query.answer(text="Все ордера успешно закрыты.")
logger.debug(
"Command close_all_orders processed successfully for user: %s",
callback_query.from_user.id,
)
except Exception as e:
await callback_query.answer(text="Произошла ошибка при закрытии всех ордеров.")
logger.error(
"Error processing command close_all_orders for user %s: %s",
callback_query.from_user.id,
e,
)
finally:
await state.clear()

View File

@@ -0,0 +1,50 @@
import logging.config
from aiogram import Router
from aiogram.fsm.context import FSMContext
from aiogram.types import Message
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("common")
router_common = Router(name="common")
@router_common.message()
async def unknown_message(message: Message, state: FSMContext) -> None:
"""
Handle unexpected or unrecognized messages.
Clears FSM state and informs the user about available commands.
Args:
message (types.Message): Incoming message object.
state (FSMContext): Current FSM context.
Returns:
None
"""
try:
await message.answer(
text="Извините, я вас не понял. "
"Пожалуйста, используйте одну из следующих команд:\n"
"/start - Запустить бота\n"
"/profile - Профиль\n"
"/bybit - Панель Bybit\n"
"/help - Получить помощь\n"
)
logger.debug(
"Received unknown message from user %s: %s",
message.from_user.id,
message.text,
)
except Exception as e:
logger.error(
"Error handling unknown message for user %s: %s", message.from_user.id, e
)
await message.answer(
text="Произошла ошибка при обработке вашего сообщения. Пожалуйста, попробуйте позже."
)
finally:
await state.clear()

View File

@@ -0,0 +1,214 @@
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_orders,
get_active_orders_by_symbol,
get_active_positions,
get_active_positions_by_symbol,
)
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("get_positions_handlers")
router_get_positions_handlers = Router(name="get_positions_handlers")
@router_get_positions_handlers.callback_query(F.data == "my_deals")
async def get_positions_handlers(
callback_query: CallbackQuery, state: FSMContext
) -> None:
"""
Gets the user's active positions.
:param callback_query: CallbackQuery object.
:param state: FSMContext
:return: None
"""
try:
await state.clear()
await callback_query.message.edit_text(
text="Выберите какие сделки вы хотите посмотреть:",
reply_markup=kbi.change_position,
)
except Exception as e:
logger.error("Error in get_positions_handler: %s", e)
await callback_query.answer(text="Произошла ошибка при получении сделок.")
@router_get_positions_handlers.callback_query(F.data == "change_position")
async def get_positions_handler(
callback_query: CallbackQuery, state: FSMContext
) -> None:
"""
Gets the user's active positions.
:param callback_query: CallbackQuery object.
:param state: FSMContext
:return: None
"""
try:
res = await get_active_positions(tg_id=callback_query.from_user.id)
if res is None:
await callback_query.answer(
text="Произошла ошибка при получении активных позиций."
)
return
if res == ["No active positions found"]:
await callback_query.answer(text="Нет активных позиций.")
return
await callback_query.message.edit_text(
text="Ваши активные позиции:",
reply_markup=kbi.create_active_positions_keyboard(res),
)
except Exception as e:
logger.error("Error in get_positions_handler: %s", e)
await callback_query.answer(
text="Произошла ошибка при получении активных позиций."
)
finally:
await state.clear()
@router_get_positions_handlers.callback_query(
lambda c: c.data.startswith("get_position_")
)
async def get_position_handler(callback_query: CallbackQuery, state: FSMContext):
try:
symbol = callback_query.data.split("_", 2)[2]
res = await get_active_positions_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol
)
if res is None:
await callback_query.answer(
text="Произошла ошибка при получении активных позиций."
)
return
symbol = res.get("symbol") or "Нет данных"
avg_price = res.get("avgPrice") or "Нет данных"
size = res.get("size") or "Нет данных"
side = res.get("side") or ""
side_rus = (
"Покупка"
if side == "Buy"
else "Продажа" if side == "Sell" else "Нет данных"
)
take_profit = res.get("takeProfit") or "Нет данных"
stop_loss = res.get("stopLoss") or "Нет данных"
await callback_query.message.edit_text(
text=f"Торговая пара: {symbol}\n"
f"Цена входа: {avg_price}\n"
f"Количество: {size}\n"
f"Движение: {side_rus}\n"
f"Тейк-профит: {take_profit}\n"
f"Стоп-лосс: {stop_loss}\n",
reply_markup=kbi.make_close_position_keyboard(symbol_pos=symbol),
)
except Exception as e:
logger.error("Error in get_position_handler: %s", e)
await callback_query.answer(
text="Произошла ошибка при получении активных позиций."
)
finally:
await state.clear()
@router_get_positions_handlers.callback_query(F.data == "open_orders")
async def get_open_orders_handler(
callback_query: CallbackQuery, state: FSMContext
) -> None:
"""
Gets the user's open orders.
:param callback_query: CallbackQuery object.
:param state: FSMContext
:return: None
"""
try:
res = await get_active_orders(tg_id=callback_query.from_user.id)
if res is None:
await callback_query.answer(
text="Произошла ошибка при получении активных ордеров."
)
return
if res == ["No active orders found"]:
await callback_query.answer(text="Нет активных ордеров.")
return
await callback_query.message.edit_text(
text="Ваши активные ордера:",
reply_markup=kbi.create_active_orders_keyboard(res),
)
except Exception as e:
logger.error("Error in get_open_orders_handler: %s", e)
await callback_query.answer(
text="Произошла ошибка при получении активных ордеров."
)
finally:
await state.clear()
@router_get_positions_handlers.callback_query(lambda c: c.data.startswith("get_order_"))
async def get_order_handler(callback_query: CallbackQuery, state: FSMContext):
try:
symbol = callback_query.data.split("_", 2)[2]
res = await get_active_orders_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol
)
if res is None:
await callback_query.answer(
text="Произошла ошибка при получении активных ордеров."
)
return
symbol = res.get("symbol") or "Нет данных"
price = res.get("price") or "Нет данных"
qty = res.get("qty") or "Нет данных"
side = res.get("side") or ""
side_rus = (
"Покупка"
if side == "Buy"
else "Продажа" if side == "Sell" else "Нет данных"
)
order_type = res.get("orderType") or ""
order_type_rus = (
"Рыночный"
if order_type == "Market"
else "Лимитный" if order_type == "Limit" else "Нет данных"
)
trigger_price = res.get("triggerPrice") or "Нет данных"
take_profit = res.get("takeProfit") or "Нет данных"
stop_loss = res.get("stopLoss") or "Нет данных"
await callback_query.message.edit_text(
text=f"Торговая пара: {symbol}\n"
f"Цена: {price}\n"
f"Количество: {qty}\n"
f"Движение: {side_rus}\n"
f"Тип ордера: {order_type_rus}\n"
f"Триггер цена: {trigger_price}\n"
f"Тейк-профит: {take_profit}\n"
f"Стоп-лосс: {stop_loss}\n",
reply_markup=kbi.make_close_orders_keyboard(symbol_order=symbol),
)
await rq.set_user_symbol(tg_id=callback_query.from_user.id, symbol=symbol)
except Exception as e:
logger.error("Error in get_order_handler: %s", e)
await callback_query.answer(
text="Произошла ошибка при получении активных ордеров."
)
finally:
await state.clear()

View File

@@ -0,0 +1,319 @@
import logging.config
from aiogram import F, Router
from aiogram.filters import Command
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, Message
import app.telegram.keyboards.inline as kbi
import app.telegram.keyboards.reply as kbr
import database.request as rq
from app.bybit.profile_bybit import user_profile_bybit
from app.telegram.functions.profile_tg import user_profile_tg
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("handlers_main")
router_handlers_main = Router(name="handlers_main")
@router_handlers_main.message(Command("start", "hello"))
@router_handlers_main.message(F.text.lower() == "привет")
async def cmd_start(message: Message, state: FSMContext) -> None:
"""
Handle the /start or /hello commands and the text message "привет".
Checks if the user exists in the database and sends a user profile or creates a new user
with default settings and greeting message.
Args:
message (Message): Incoming Telegram message object.
state (FSMContext): FSMContext for managing user state.
Raises:
None: Exceptions are caught and logged internally.
"""
tg_id = message.from_user.id
username = message.from_user.username
full_name = message.from_user.full_name
user = await rq.get_user(tg_id)
try:
if user:
await user_profile_tg(tg_id=message.from_user.id, message=message)
logger.debug(
"Command start processed successfully for user: %s",
message.from_user.id,
)
else:
await rq.create_user(tg_id=tg_id, username=username)
await rq.set_user_symbol(tg_id=tg_id, symbol="BTCUSDT")
await rq.create_user_additional_settings(tg_id=tg_id)
await rq.create_user_risk_management(tg_id=tg_id)
await rq.create_user_conditional_settings(tg_id=tg_id)
await message.answer(
text=f"Добро пожаловать, {full_name}!\n\n"
"PHANTOM TRADING - ваш надежный помощник для автоматизации трейдинга😉",
reply_markup=kbi.connect_the_platform,
)
logger.debug(
"Command start processed successfully for user: %s",
message.from_user.id,
)
except Exception as e:
logger.error(
"Error processing command start for user %s: %s", message.from_user.id, e
)
await message.answer(text="Произошла ошибка. Пожалуйста, попробуйте позже.")
finally:
await state.clear()
@router_handlers_main.message(Command("profile"))
@router_handlers_main.message(F.text == "Профиль")
async def cmd_to_main(message: Message, state: FSMContext) -> None:
"""
Handle the /profile command or text "Профиль".
Clears the current FSM state and sends the Telegram user profile.
Args:
message (Message): Incoming Telegram message object.
state (FSMContext): FSM state context.
Raises:
None: Exceptions are caught and logged internally.
"""
try:
await user_profile_tg(tg_id=message.from_user.id, message=message)
logger.debug(
"Command to_profile_tg processed successfully for user: %s",
message.from_user.id,
)
except Exception as e:
logger.error(
"Error processing command to_profile_tg for user %s: %s",
message.from_user.id,
e,
)
finally:
await state.clear()
@router_handlers_main.message(Command("bybit"))
@router_handlers_main.message(F.text == "Панель Bybit")
async def profile_bybit(message: Message, state: FSMContext) -> None:
"""
Handle the /bybit command or text "Панель Bybit".
Clears FSM state and sends Bybit trading panel profile.
Args:
message (Message): Incoming Telegram message object.
state (FSMContext): FSM state context.
Raises:
None: Exceptions are caught and logged internally.
"""
try:
await state.clear()
await user_profile_bybit(
tg_id=message.from_user.id, message=message, state=state
)
logger.debug(
"Command to_profile_bybit processed successfully for user: %s",
message.from_user.id,
)
except Exception as e:
logger.error(
"Error processing command to_profile_bybit for user %s: %s",
message.from_user.id,
e,
)
@router_handlers_main.callback_query(F.data == "profile_bybit")
async def profile_bybit_callback(
callback_query: CallbackQuery, state: FSMContext
) -> None:
"""
Handle callback query with data "profile_bybit".
Clears FSM state and sends the Bybit profile in response.
Args:
callback_query (CallbackQuery): Callback query object from Telegram.
state (FSMContext): FSM state context.
Raises:
None: Exceptions are caught and logged internally.
"""
try:
await state.clear()
await user_profile_bybit(
tg_id=callback_query.from_user.id,
message=callback_query.message,
state=state,
)
logger.debug(
"Callback profile_bybit processed successfully for user: %s",
callback_query.from_user.id,
)
await callback_query.answer()
except Exception as e:
logger.error(
"Error processing callback profile_bybit for user %s: %s",
callback_query.from_user.id,
e,
)
@router_handlers_main.callback_query(F.data == "main_settings")
async def settings(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handle callback query with data "main_settings".
Clears FSM state and edits the message to show main settings options.
Args:
callback_query (CallbackQuery): Callback query object.
state (FSMContext): FSM state context.
Raises:
None: Exceptions are caught and logged internally.
"""
try:
await state.clear()
msg = await callback_query.message.edit_text(
text="Выберите, что вы хотите настроить:", reply_markup=kbi.main_settings
)
await state.update_data(prompt_message_id=msg.message_id)
logger.debug(
"Command settings processed successfully for user: %s",
callback_query.from_user.id,
)
except Exception as e:
logger.error(
"Error processing command settings for user %s: %s",
callback_query.from_user.id,
e,
)
@router_handlers_main.message(Command("help"))
async def cmd_help(message: Message, state: FSMContext) -> None:
"""
Handle the /help command.
Clears FSM state and sends a help message with available commands and reply keyboard.
Args:
message (Message): Incoming Telegram message object.
state (FSMContext): FSM state context.
Raises:
None: Exceptions are caught and logged internally.
"""
try:
await state.clear()
await message.answer(
text="Используйте одну из следующих команд:\n"
"/start - Запустить бота\n"
"/profile - Профиль\n"
"/bybit - Панель Bybit\n",
reply_markup=kbr.profile,
)
logger.debug(
"Command help processed successfully for user: %s",
message.from_user.id,
)
except Exception as e:
logger.error(
"Error processing command help for user %s: %s", message.from_user.id, e
)
await message.answer(
text="Произошла ошибка. Пожалуйста, попробуйте позже.",
reply_markup=kbr.profile,
)
@router_handlers_main.message(Command("cancel"))
@router_handlers_main.message(
lambda message: message.text.casefold() in ["cancel", "отмена"]
)
async def cmd_cancel_handler(message: Message, state: FSMContext) -> None:
"""
Handle /cancel command or text 'cancel'/'отмена'.
If there is an active FSM state, clears it and informs the user.
Otherwise, informs that no operation was in progress.
Args:
message (Message): Incoming Telegram message object.
state (FSMContext): FSM state context.
Raises:
None: Exceptions are caught and logged internally.
"""
current_state = await state.get_state()
if current_state is None:
await message.reply(
text="Хорошо, но ничего не происходило.", reply_markup=kbr.profile
)
logger.debug(
"Cancel command received but no active state for user %s.",
message.from_user.id,
)
return
try:
await state.clear()
await message.reply(text="Команда отменена.", reply_markup=kbr.profile)
logger.debug(
"Command cancel executed successfully. State cleared for user %s.",
message.from_user.id,
)
except Exception as e:
logger.error(
"Error while cancelling command for user %s: %s", message.from_user.id, e
)
await message.answer(
text="Произошла ошибка при отмене. Пожалуйста, попробуйте позже.",
reply_markup=kbr.profile,
)
@router_handlers_main.callback_query(F.data == "cancel")
async def cmd_cancel(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handle callback query with data "cancel".
Clears the FSM state and sends a cancellation message.
Args:
callback_query (CallbackQuery): Callback query object.
state (FSMContext): FSM state context.
Raises:
None: Exceptions are caught and logged internally.
"""
try:
await callback_query.message.delete()
await user_profile_bybit(
tg_id=callback_query.from_user.id,
message=callback_query.message,
state=state,
)
logger.debug(
"Command cancel processed successfully for user: %s",
callback_query.from_user.id,
)
except Exception as e:
logger.error(
"Error processing command cancel for user %s: %s",
callback_query.from_user.id,
e,
)
finally:
await state.clear()

View File

@@ -0,0 +1,17 @@
__all__ = "router"
from aiogram import Router
from app.telegram.handlers.main_settings.additional_settings import (
router_additional_settings,
)
from app.telegram.handlers.main_settings.conditional_settings import (
router_conditional_settings,
)
from app.telegram.handlers.main_settings.risk_management import router_risk_management
router_main_settings = Router(name=__name__)
router_main_settings.include_router(router_additional_settings)
router_main_settings.include_router(router_risk_management)
router_main_settings.include_router(router_conditional_settings)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,174 @@
import logging.config
from aiogram import 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.helper_functions import is_int_for_timer
from app.telegram.states.states import ConditionalSettingsState
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("conditional_settings")
router_conditional_settings = Router(name="conditional_settings")
@router_conditional_settings.callback_query(
lambda c: c.data == "start_timer" or c.data == "stop_timer"
)
async def timer(callback_query: CallbackQuery, state: FSMContext):
"""
Handles callback queries starting with 'start_timer' or 'stop_timer'.
"""
try:
await state.clear()
if callback_query.data == "start_timer":
await state.set_state(ConditionalSettingsState.start_timer_state)
msg = await callback_query.message.edit_text(
"Введите время в минутах для старта торговли:",
reply_markup=kbi.back_to_conditions,
)
await state.update_data(prompt_message_id=msg.message_id)
elif callback_query.data == "stop_timer":
await state.set_state(ConditionalSettingsState.stop_timer_state)
msg = await callback_query.message.edit_text(
"Введите время в минутах для остановки торговли:",
reply_markup=kbi.back_to_conditions,
)
await state.update_data(prompt_message_id=msg.message_id)
else:
await callback_query.answer(
text="Произошла ошибка. Пожалуйста, попробуйте позже."
)
except Exception as e:
await callback_query.answer(
text="Произошла ошибка. Пожалуйста, попробуйте позже."
)
logger.error(
"Error processing command timer for user %s: %s",
callback_query.from_user.id,
e,
)
@router_conditional_settings.message(ConditionalSettingsState.start_timer_state)
async def start_timer(message: Message, state: FSMContext):
"""
Handles the start_timer state of the Finite State Machine.
"""
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_start_timer = message.text
value = is_int_for_timer(get_start_timer)
if value is False:
await message.answer(
"Ошибка: введите валидное число.",
reply_markup=kbi.back_to_conditions,
)
logger.debug(
"User %s input invalid (not an valid number): %s",
message.from_user.id,
get_start_timer,
)
return
req = await rq.set_start_timer(
tg_id=message.from_user.id, timer_start=int(get_start_timer)
)
if req:
await message.answer(
"Таймер успешно установлен.",
reply_markup=kbi.back_to_conditions,
)
else:
await message.answer(
"Произошла ошибка. Пожалуйста, попробуйте позже.",
reply_markup=kbi.back_to_conditions,
)
await state.clear()
except Exception as e:
await message.answer(text="Произошла ошибка. Пожалуйста, попробуйте позже.")
logger.error(
"Error processing command start_timer for user %s: %s",
message.from_user.id,
e,
)
@router_conditional_settings.message(ConditionalSettingsState.stop_timer_state)
async def stop_timer(message: Message, state: FSMContext):
"""
Handles the stop_timer state of the Finite State Machine.
"""
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_stop_timer = message.text
value = is_int_for_timer(get_stop_timer)
if value is False:
await message.answer(
"Ошибка: введите валидное число.",
reply_markup=kbi.back_to_conditions,
)
logger.debug(
"User %s input invalid (not an valid number): %s",
message.from_user.id,
get_stop_timer,
)
return
req = await rq.set_stop_timer(
tg_id=message.from_user.id, timer_end=int(get_stop_timer)
)
if req:
await message.answer(
"Таймер успешно установлен.",
reply_markup=kbi.back_to_conditions,
)
else:
await message.answer(
"Произошла ошибка. Пожалуйста, попробуйте позже.",
reply_markup=kbi.back_to_conditions,
)
await state.clear()
except Exception as e:
await message.answer(text="Произошла ошибка. Пожалуйста, попробуйте позже.")
logger.error(
"Error processing command stop_timer for user %s: %s",
message.from_user.id,
e,
)

View File

@@ -0,0 +1,467 @@
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.helper_functions import is_int
from app.telegram.states.states import RiskManagementState
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("risk_management")
router_risk_management = Router(name="risk_management")
@router_risk_management.callback_query(F.data == "take_profit_percent")
async def take_profit_percent(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handles the 'profit_price_change' callback query.
Clears the current FSM state, edits the message text to display the take profit percent 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(RiskManagementState.take_profit_percent_state)
msg = await callback_query.message.edit_text(
text="Введите процент изменения цены для фиксации прибыли: ",
reply_markup=kbi.back_to_risk_management,
)
await state.update_data(prompt_message_id=msg.message_id)
logger.debug(
"Command profit_price_change processed successfully for user: %s",
callback_query.from_user.id,
)
except Exception as e:
await callback_query.answer(
text="Произошла ошибка. Пожалуйста, попробуйте позже."
)
logger.error(
"Error processing command profit_price_change for user %s: %s",
callback_query.from_user.id,
e,
)
@router_risk_management.message(RiskManagementState.take_profit_percent_state)
async def set_take_profit_percent(message: Message, state: FSMContext) -> None:
"""
Handles user input for setting the take profit percentage.
Updates FSM context with the selected percentage 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 take profit percentage.
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
take_profit_percent_value = message.text
if not is_int(take_profit_percent_value):
await message.answer(
text="Ошибка: введите валидное число.",
reply_markup=kbi.back_to_risk_management,
)
logger.debug(
"User %s input invalid (not an valid number): %s",
message.from_user.id,
take_profit_percent_value,
)
return
if int(take_profit_percent_value) < 1 or int(take_profit_percent_value) > 100:
await message.answer(
text="Ошибка: введите число от 1 до 100.",
reply_markup=kbi.back_to_risk_management,
)
logger.debug(
"User %s input invalid (not an valid number): %s",
message.from_user.id,
take_profit_percent_value,
)
return
req = await rq.set_take_profit_percent(
tg_id=message.from_user.id,
take_profit_percent=int(take_profit_percent_value),
)
if req:
await message.answer(
text=f"Процент изменения цены для фиксации прибыли "
f"установлен на {take_profit_percent_value}%.",
reply_markup=kbi.back_to_risk_management,
)
else:
await message.answer(
text="Произошла ошибка при установке процента изменения цены для фиксации прибыли. "
"Пожалуйста, попробуйте позже.",
reply_markup=kbi.back_to_risk_management,
)
await state.clear()
except Exception as e:
logger.error(
"Error processing command profit_price_change for user %s: %s",
message.from_user.id,
e,
)
@router_risk_management.callback_query(F.data == "stop_loss_percent")
async def stop_loss_percent(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handles the 'stop_loss_percent' callback query.
Clears the current FSM state, edits the message text to display the stop loss percentage 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(RiskManagementState.stop_loss_percent_state)
msg = await callback_query.message.edit_text(
text="Введите процент изменения цены для фиксации убытка: ",
reply_markup=kbi.back_to_risk_management,
)
await state.update_data(prompt_message_id=msg.message_id)
logger.debug(
"Command stop_loss_percent processed successfully for user: %s",
callback_query.from_user.id,
)
except Exception as e:
await callback_query.answer(
text="Произошла ошибка. Пожалуйста, попробуйте позже."
)
logger.error(
"Error processing command stop_loss_percent for user %s: %s",
callback_query.from_user.id,
e,
)
@router_risk_management.message(RiskManagementState.stop_loss_percent_state)
async def set_stop_loss_percent(message: Message, state: FSMContext) -> None:
"""
Handles user input for setting the stop loss percentage.
Updates FSM context with the selected percentage 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 stop loss percentage.
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
stop_loss_percent_value = message.text
if not is_int(stop_loss_percent_value):
await message.answer(
text="Ошибка: введите валидное число.",
reply_markup=kbi.back_to_risk_management,
)
logger.debug(
"User %s input invalid (not an valid number): %s",
message.from_user.id,
stop_loss_percent_value,
)
return
if int(stop_loss_percent_value) < 1 or int(stop_loss_percent_value) > 100:
await message.answer(
text="Ошибка: введите число от 1 до 100.",
reply_markup=kbi.back_to_risk_management,
)
logger.debug(
"User %s input invalid (not an valid number): %s",
message.from_user.id,
stop_loss_percent_value,
)
return
req = await rq.set_stop_loss_percent(
tg_id=message.from_user.id, stop_loss_percent=int(stop_loss_percent_value)
)
if req:
await message.answer(
text=f"Процент изменения цены для фиксации убытка "
f"установлен на {stop_loss_percent_value}%.",
reply_markup=kbi.back_to_risk_management,
)
else:
await message.answer(
text="Произошла ошибка при установке процента изменения цены для фиксации убытка. "
"Пожалуйста, попробуйте позже.",
reply_markup=kbi.back_to_risk_management,
)
await state.clear()
except Exception as e:
await message.answer(
text="Произошла ошибка при установке процента изменения цены для фиксации убытка. "
"Пожалуйста, попробуйте позже.",
reply_markup=kbi.back_to_risk_management,
)
logger.error(
"Error processing command stop_loss_percent for user %s: %s",
message.from_user.id,
e,
)
@router_risk_management.callback_query(F.data == "max_risk_percent")
async def max_risk_percent(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handles the 'max_risk_percent' callback query.
Clears the current FSM state, edits the message text to display the maximum risk percentage 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(RiskManagementState.max_risk_percent_state)
msg = await callback_query.message.edit_text(
text="Введите максимальный процент риска: ",
reply_markup=kbi.back_to_risk_management,
)
await state.update_data(prompt_message_id=msg.message_id)
logger.debug(
"Command max_risk_percent 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_risk_percent for user %s: %s",
callback_query.from_user.id,
e,
)
@router_risk_management.message(RiskManagementState.max_risk_percent_state)
async def set_max_risk_percent(message: Message, state: FSMContext) -> None:
"""
Handles user input for setting the maximum risk percentage.
Updates FSM context with the selected percentage 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 maximum risk percentage.
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_risk_percent_value = message.text
if not is_int(max_risk_percent_value):
await message.answer(
text="Ошибка: введите валидное число.",
reply_markup=kbi.back_to_risk_management,
)
logger.debug(
"User %s input invalid (not an valid number): %s",
message.from_user.id,
max_risk_percent_value,
)
return
if int(max_risk_percent_value) < 1 or int(max_risk_percent_value) > 100:
await message.answer(
text="Ошибка: введите число от 1 до 100.",
reply_markup=kbi.back_to_risk_management,
)
logger.debug(
"User %s input invalid (not an valid number): %s",
message.from_user.id,
max_risk_percent_value,
)
return
req = await rq.set_max_risk_percent(
tg_id=message.from_user.id, max_risk_percent=int(max_risk_percent_value)
)
if req:
await message.answer(
text=f"Максимальный процент риска установлен на {max_risk_percent_value}%.",
reply_markup=kbi.back_to_risk_management,
)
else:
await message.answer(
text="Произошла ошибка при установке максимального процента риска. "
"Пожалуйста, попробуйте позже.",
reply_markup=kbi.back_to_risk_management,
)
await state.clear()
except Exception as e:
await message.answer(
text="Произошла ошибка при установке максимального процента риска. "
"Пожалуйста, попробуйте позже.",
reply_markup=kbi.back_to_risk_management,
)
logger.error(
"Error processing command max_risk_percent for user %s: %s",
message.from_user.id,
e,
)
@router_risk_management.callback_query(F.data == "commission_fee")
async def commission_fee(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handles the 'commission_fee' callback query.
Clears the current FSM state, edits the message text to display the commission fee 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(RiskManagementState.commission_fee_state)
msg = await callback_query.message.edit_text(
text="Учитывать комиссию биржи для расчета прибыли?: ",
reply_markup=kbi.commission_fee,
)
await state.update_data(prompt_message_id=msg.message_id)
logger.debug(
"Command commission_fee processed successfully for user: %s",
callback_query.from_user.id,
)
except Exception as e:
await callback_query.answer(
text="Произошла ошибка. Пожалуйста, попробуйте позже."
)
logger.error(
"Error processing command commission_fee for user %s: %s",
callback_query.from_user.id,
e,
)
@router_risk_management.callback_query(
lambda c: c.data in ["Yes_commission_fee", "No_commission_fee"]
)
async def set_commission_fee(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handles user input for setting the commission fee.
Updates FSM context with the selected option and persists the choice in database.
Sends an acknowledgement to user and clears FSM state afterward.
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:
req = await rq.set_commission_fee(
tg_id=callback_query.from_user.id, commission_fee=callback_query.data
)
if not req:
await callback_query.answer(
text="Произошла ошибка при установке комиссии биржи. Пожалуйста, попробуйте позже."
)
return
if callback_query.data == "Yes_commission_fee":
await callback_query.answer(text="Комиссия биржи учитывается.")
else:
await callback_query.answer(text="Комиссия биржи не учитывается.")
except Exception as e:
logger.error(
"Error processing command commission_fee for user %s: %s",
callback_query.from_user.id,
e,
)
finally:
await state.clear()

View File

@@ -0,0 +1,278 @@
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 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 logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("settings")
router_settings = Router(name="settings")
@router_settings.callback_query(F.data == "additional_settings")
async def additional_settings(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handler for the "additional_settings" command.
Sends a message with additional settings options.
"""
try:
await state.clear()
tg_id = callback_query.from_user.id
symbol = await rq.get_user_symbol(tg_id=tg_id)
additional_data = await rq.get_user_additional_settings(tg_id=tg_id)
if not additional_data:
await rq.create_user(
tg_id=tg_id, username=callback_query.from_user.username
)
await rq.create_user_additional_settings(tg_id=tg_id)
await rq.create_user_risk_management(tg_id=tg_id)
await rq.create_user_conditional_settings(tg_id=tg_id)
await additional_settings(callback_query=callback_query, state=state)
return
trade_mode_map = {
"Merged_Single": "Односторонний режим",
"Both_Sides": "Хеджирование",
}
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, "Условный")
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)
fee_info = client.get_fee_rates(category="linear", symbol=symbol)
if commission_fee == "Yes_commission_fee":
commission_fee_percent = safe_float(
fee_info["result"]["list"][0]["takerFeeRate"]
)
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
durability_buy = quantity * bid_price
durability_sell = quantity * ask_price
quantity_price = quantity * entry_price
total_commission = quantity_price * commission_fee_percent
total_budget = await calculate_total_budget(
quantity=durability_buy,
martingale_factor=martingale,
max_steps=max_bets,
commission_fee_percent=total_commission,
)
text = (
f"Основные настройки:\n\n"
f"- Режим позиции: {trade_mode_rus}\n"
f"- Тип маржи: {margin_type_rus}\n"
f"- Размер кредитного плеча: {leverage_str}\n"
f"- Тип ордера: {order_type_rus}\n"
f"- Количество ордера: {quantity} {sym}\n"
f"- Коэффициент мартингейла: {martingale:.2f}\n"
f"{conditional_order_type_text}"
f"{trigger_price_text}"
f"{limit_price_text}"
f"- Максимальное кол-во ставок в серии: {max_bets}\n\n"
f"- Стоимость: {durability_buy:.2f}/{durability_sell:.2f} USDT\n"
f"- Рекомендуемый бюджет: {total_budget:.4f} USDT\n"
)
keyboard = kbi.get_additional_settings_keyboard(
current_order_type=order_type, conditional_order=conditional_order_type
)
await callback_query.message.edit_text(text=text, reply_markup=keyboard)
logger.debug(
"Command additional_settings processed successfully for user: %s", tg_id
)
except Exception as e:
await callback_query.message.edit_text(
text="Произошла ошибка. Пожалуйста, попробуйте ещё раз.",
reply_markup=kbi.profile_bybit,
)
logger.error(
"Error processing command additional_settings for user %s: %s",
callback_query.from_user.id,
e,
)
@router_settings.callback_query(F.data == "risk_management")
async def risk_management(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handler for the "risk_management" command.
Sends a message with risk management options.
"""
try:
await state.clear()
risk_management_data = await rq.get_user_risk_management(
tg_id=callback_query.from_user.id
)
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 "Нет"
)
await callback_query.message.edit_text(
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,
)
logger.debug(
"Command main_settings processed successfully for user: %s",
callback_query.from_user.id,
)
else:
await rq.create_user(
tg_id=callback_query.from_user.id,
username=callback_query.from_user.username,
)
await rq.create_user_additional_settings(tg_id=callback_query.from_user.id)
await rq.create_user_risk_management(tg_id=callback_query.from_user.id)
await rq.create_user_conditional_settings(tg_id=callback_query.from_user.id)
await risk_management(callback_query=callback_query, state=state)
except Exception as e:
logger.error(
"Error processing command main_settings for user %s: %s",
callback_query.from_user.id,
e,
)
@router_settings.callback_query(F.data == "conditional_settings")
async def conditions(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handler for the "conditions" command.
Sends a message with trading conditions options.
"""
try:
await state.clear()
conditional_settings_data = await rq.get_user_conditional_settings(
tg_id=callback_query.from_user.id
)
if conditional_settings_data:
start_timer = conditional_settings_data.timer_start or 0
stop_timer = conditional_settings_data.timer_end or 0
await callback_query.message.edit_text(
text="Условия торговли:\n\n"
f"- Таймер для старта: {start_timer} мин.\n"
f"- Таймер для остановки: {stop_timer} мин.\n",
reply_markup=kbi.conditions,
)
logger.debug(
"Command main_settings processed successfully for user: %s",
callback_query.from_user.id,
)
else:
await rq.create_user(
tg_id=callback_query.from_user.id,
username=callback_query.from_user.username,
)
await rq.create_user_additional_settings(tg_id=callback_query.from_user.id)
await rq.create_user_risk_management(tg_id=callback_query.from_user.id)
await rq.create_user_conditional_settings(tg_id=callback_query.from_user.id)
await conditions(callback_query=callback_query, state=state)
except Exception as e:
logger.error(
"Error processing command main_settings for user %s: %s",
callback_query.from_user.id,
e,
)

View File

@@ -0,0 +1,363 @@
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 logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("start_trading")
router_start_trading = Router(name="start_trading")
user_trade_tasks = {}
@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
)
size = deals.get("size") or 0
position_idx = deals.get("positionIdx")
if position_idx != 0 and int(size) > 0 and trade_mode == "Merged_Single":
await callback_query.answer(
text="У вас есть активная позиция в режиме хеджирования. "
"Открытие сделки в одностороннем режиме невозможно.",
)
return
if position_idx == 0 and int(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
)
size = deals.get("size") or 0
position_idx = deals.get("positionIdx")
if position_idx == 0 and int(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
if callback_query.from_user.id in user_trade_tasks:
task = user_trade_tasks[callback_query.from_user.id]
if not task.done():
task.cancel()
del user_trade_tasks[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,
)
await asyncio.sleep(timer_start * 60)
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": "Сумма ордера не sufficientдля запуска торговли",
"position idx not match position mode": "Торговля уже запущена в режиме хеджирования на продажу для данного инструмента",
"Qty invalid": "Некорректное значение ордера для данного инструмента",
}
if res == "OK":
await rq.set_start_timer(
tg_id=callback_query.from_user.id, timer_start=0
)
await rq.set_auto_trading(
tg_id=callback_query.from_user.id, symbol=symbol, auto_trading=True
)
await callback_query.answer(text="Торговля запущена")
await state.clear()
else:
# Получаем сообщение из таблицы, или дефолтный текст
text = error_messages.get(res, "Произошла ошибка при запуске торговли")
await callback_query.answer(text=text)
task = asyncio.create_task(delay_start())
user_trade_tasks[callback_query.from_user.id] = 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
)
size = deals.get("size") or 0
position_idx = deals.get("positionIdx")
if position_idx == 1 and int(size) > 0 and side == "Buy":
await callback_query.answer(
text="Торговля уже запущена в режиме хеджирования на покупку для данного инструмента"
)
return
if position_idx == 2 and int(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
if callback_query.from_user.id in user_trade_tasks:
task = user_trade_tasks[callback_query.from_user.id]
if not task.done():
task.cancel()
del user_trade_tasks[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,
)
await asyncio.sleep(timer_start * 60)
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": "Сумма ордера не sufficientдля запуска торговли",
"position idx not match position mode": "Торговля уже запущена в режиме хеджирования на продажу для данного инструмента",
"Qty invalid": "Некорректное значение ордера для данного инструмента",
}
if res == "OK":
await rq.set_start_timer(
tg_id=callback_query.from_user.id, timer_start=0
)
await rq.set_auto_trading(
tg_id=callback_query.from_user.id, symbol=symbol, auto_trading=True
)
await callback_query.answer(text="Торговля запущена")
await state.clear()
else:
text = error_messages.get(res, "Произошла ошибка при запуске торговли")
await callback_query.answer(text=text)
task = asyncio.create_task(delay_start())
user_trade_tasks[callback_query.from_user.id] = 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(F.data == "cancel_timer")
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:
task = user_trade_tasks.get(callback_query.from_user.id)
if task and not task.done():
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
user_trade_tasks.pop(callback_query.from_user.id, None)
await callback_query.message.edit_text(
"Таймер отменён.", reply_markup=kbi.profile_bybit
)
else:
await callback_query.message.edit_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,
)
finally:
await state.clear()

View File

@@ -0,0 +1,73 @@
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.close_positions import cancel_order, close_position
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("stop_trading")
router_stop_trading = Router(name="stop_trading")
user_trade_tasks = {}
@router_stop_trading.callback_query(F.data == "stop_trading")
async def stop_trading(callback_query: CallbackQuery, state: FSMContext):
try:
await state.clear()
if callback_query.from_user.id in user_trade_tasks:
task = user_trade_tasks[callback_query.from_user.id]
if not task.done():
task.cancel()
del user_trade_tasks[callback_query.from_user.id]
conditional_data = await rq.get_user_conditional_settings(
tg_id=callback_query.from_user.id
)
timer_end = conditional_data.timer_end
symbols = await rq.get_all_symbols(tg_id=callback_query.from_user.id)
async def delay_start():
if timer_end > 0:
await callback_query.message.edit_text(
text=f"Торговля будет остановлена с задержкой {timer_end} мин.",
reply_markup=kbi.cancel_timer,
)
await asyncio.sleep(timer_end * 60)
for symbol in symbols:
auto_trading_data = await rq.get_user_auto_trading(
tg_id=callback_query.from_user.id, symbol=symbol
)
if auto_trading_data is not None and auto_trading_data.auto_trading:
await close_position(tg_id=callback_query.from_user.id, symbol=symbol)
await cancel_order(tg_id=callback_query.from_user.id, symbol=symbol)
await rq.set_auto_trading(
tg_id=callback_query.from_user.id, symbol=symbol, auto_trading=False
)
await callback_query.answer(text="Торговля остановлена")
await rq.set_stop_timer(tg_id=callback_query.from_user.id, timer_end=0)
task = asyncio.create_task(delay_start())
user_trade_tasks[callback_query.from_user.id] = task
logger.debug(
"Command stop_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 stop_trading for user %s: %s",
callback_query.from_user.id,
e,
)

View File

@@ -0,0 +1,161 @@
import logging.config
from aiogram import Router
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery, Message
import app.telegram.keyboards.inline as kbi
from app.bybit.set_functions.set_tp_sl import set_tp_sl_for_position
from app.helper_functions import is_number
from app.telegram.states.states import SetTradingStopState
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("tp_sl_handlers")
router_tp_sl_handlers = Router(name="tp_sl_handlers")
@router_tp_sl_handlers.callback_query(lambda c: c.data.startswith("pos_tp_sl_"))
async def set_tp_sl_handler(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handles the 'pos_tp_sl' callback query.
Clears the current FSM state, sets the state to 'take_profit', and prompts the user to enter the take-profit.
Args:
callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard.
state (FSMContext): Finite State Machine context for the current user session.
"""
try:
await state.clear()
symbol = callback_query.data.split("_", 3)[3]
await state.set_state(SetTradingStopState.take_profit_state)
await state.update_data(symbol=symbol)
msg = await callback_query.message.answer(
text="Введите тейк-профит:", reply_markup=kbi.cancel
)
await state.update_data(prompt_message_id=msg.message_id)
except Exception as e:
logger.error("Error in set_tp_sl_handler: %s", e)
await callback_query.answer(text="Произошла ошибка, попробуйте позже")
@router_tp_sl_handlers.message(SetTradingStopState.take_profit_state)
async def set_take_profit_handler(message: Message, state: FSMContext) -> None:
"""
Handles the 'take_profit' state.
Clears the current FSM state, sets the state to 'stop_loss', and prompts the user to enter the stop-loss.
Args:
message (Message): Incoming message from Telegram.
state (FSMContext): Finite State Machine context for the current user session.
Returns:
None
"""
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
take_profit = message.text
if not is_number(take_profit):
await message.answer(
"Ошибка: введите валидное число.",
reply_markup=kbi.profile_bybit,
)
logger.debug(
"User %s input invalid (not an valid number): %s",
message.from_user.id,
take_profit,
)
return
await state.update_data(take_profit=take_profit)
await state.set_state(SetTradingStopState.stop_loss_state)
msg = await message.answer(text="Введите стоп-лосс:", reply_markup=kbi.cancel)
await state.update_data(prompt_message_id=msg.message_id)
except Exception as e:
logger.error("Error in set_take_profit_handler: %s", e)
await message.answer(
text="Произошла ошибка, попробуйте позже", reply_markup=kbi.profile_bybit
)
@router_tp_sl_handlers.message(SetTradingStopState.stop_loss_state)
async def set_stop_loss_handler(message: Message, state: FSMContext) -> None:
"""
Handles the 'stop_loss' state.
Clears the current FSM state, sets the state to 'take_profit', and prompts the user to enter the take-profit.
Args:
message (Message): Incoming message from Telegram.
state (FSMContext): Finite State Machine context for the current user session.
Returns:
None
"""
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
stop_loss = message.text
if not is_number(stop_loss):
await message.answer(
"Ошибка: введите валидное число.",
reply_markup=kbi.profile_bybit,
)
logger.debug(
"User %s input invalid (not an valid number): %s",
message.from_user.id,
stop_loss,
)
return
await state.update_data(stop_loss=stop_loss)
data = await state.get_data()
symbol = data["symbol"]
take_profit = data["take_profit"]
res = await set_tp_sl_for_position(
tg_id=message.from_user.id,
symbol=symbol,
take_profit_price=float(take_profit),
stop_loss_price=float(stop_loss),
)
if res:
await message.answer(text="Тейк-профит и стоп-лосс установлены.")
else:
await message.answer(text="Тейк-профит и стоп-лосс не установлены.")
await state.clear()
except Exception as e:
await message.answer(
text="Произошла ошибка, попробуйте позже", reply_markup=kbi.profile_bybit
)
logger.error("Error in set_stop_loss_handler: %s", e)