From e5a3de4ed84ed7599c5fa46864fd4e5cb6a78069 Mon Sep 17 00:00:00 2001 From: algizn97 Date: Wed, 1 Oct 2025 15:20:18 +0500 Subject: [PATCH] Deleted --- .env.sample | 3 - BybitBot_API.py | 51 - BybitBot_API.pyproj | 68 -- BybitBot_API.sln | 23 - app/services/Bybit/functions/Add_Bybit_API.py | 111 --- app/services/Bybit/functions/Futures.py | 893 ------------------ app/services/Bybit/functions/balance.py | 52 - app/services/Bybit/functions/bybit_ws.py | 115 --- app/services/Bybit/functions/functions.py | 540 ----------- .../Bybit/functions/get_valid_symbol.py | 40 - app/services/Bybit/functions/min_qty.py | 52 - app/services/Bybit/functions/price_symbol.py | 32 - app/states/States.py | 72 -- app/telegram/Keyboards/inline_keyboards.py | 224 ----- app/telegram/Keyboards/reply_keyboards.py | 6 - app/telegram/database/models.py | 316 ------- app/telegram/database/requests.py | 621 ------------ .../functions/additional_settings/settings.py | 38 - .../functions/condition_settings/settings.py | 208 ---- app/telegram/functions/functions.py | 29 - .../functions/main_settings/settings.py | 375 -------- .../risk_management_settings/settings.py | 160 ---- app/telegram/handlers/handlers.py | 316 ------- config.py | 9 - data/__init__.py | 0 requirements.txt | 51 - 26 files changed, 4405 deletions(-) delete mode 100644 .env.sample delete mode 100644 BybitBot_API.py delete mode 100644 BybitBot_API.pyproj delete mode 100644 BybitBot_API.sln delete mode 100644 app/services/Bybit/functions/Add_Bybit_API.py delete mode 100644 app/services/Bybit/functions/Futures.py delete mode 100644 app/services/Bybit/functions/balance.py delete mode 100644 app/services/Bybit/functions/bybit_ws.py delete mode 100644 app/services/Bybit/functions/functions.py delete mode 100644 app/services/Bybit/functions/get_valid_symbol.py delete mode 100644 app/services/Bybit/functions/min_qty.py delete mode 100644 app/services/Bybit/functions/price_symbol.py delete mode 100644 app/states/States.py delete mode 100644 app/telegram/Keyboards/inline_keyboards.py delete mode 100644 app/telegram/Keyboards/reply_keyboards.py delete mode 100644 app/telegram/database/models.py delete mode 100644 app/telegram/database/requests.py delete mode 100644 app/telegram/functions/additional_settings/settings.py delete mode 100644 app/telegram/functions/condition_settings/settings.py delete mode 100644 app/telegram/functions/functions.py delete mode 100644 app/telegram/functions/main_settings/settings.py delete mode 100644 app/telegram/functions/risk_management_settings/settings.py delete mode 100644 app/telegram/handlers/handlers.py delete mode 100644 config.py delete mode 100644 data/__init__.py delete mode 100644 requirements.txt diff --git a/.env.sample b/.env.sample deleted file mode 100644 index 2637330..0000000 --- a/.env.sample +++ /dev/null @@ -1,3 +0,0 @@ -TOKEN_TELEGRAM_BOT_1= -TOKEN_TELEGRAM_BOT_2= -TOKEN_TELEGRAM_BOT_3= diff --git a/BybitBot_API.py b/BybitBot_API.py deleted file mode 100644 index 0f844e5..0000000 --- a/BybitBot_API.py +++ /dev/null @@ -1,51 +0,0 @@ -import asyncio -import logging.config -from aiogram import Bot, Dispatcher - -from app.services.Bybit.functions.bybit_ws import get_or_create_event_loop, set_event_loop -from app.telegram.database.models import async_main -from app.telegram.handlers.handlers import router -from app.telegram.functions.main_settings.settings import router_main_settings -from app.telegram.functions.risk_management_settings.settings import router_risk_management_settings -from app.telegram.functions.condition_settings.settings import condition_settings_router -from app.services.Bybit.functions.Add_Bybit_API import router_register_bybit_api -from app.services.Bybit.functions.functions import router_functions_bybit_trade - -from logger_helper.logger_helper import LOGGING_CONFIG -from config import TOKEN_TG_BOT_1 - -logging.config.dictConfig(LOGGING_CONFIG) -logger = logging.getLogger("main") - -bot = Bot(token=TOKEN_TG_BOT_1) -dp = Dispatcher() - - -async def main() -> None: - """ - Основная асинхронная функция запуска бота: - """ - loop = get_or_create_event_loop() - set_event_loop(loop) - - await async_main() - - dp.include_router(router) - dp.include_router(router_main_settings) - dp.include_router(router_risk_management_settings) - dp.include_router(condition_settings_router) - dp.include_router(router_register_bybit_api) - dp.include_router(router_functions_bybit_trade) - - try: - await dp.start_polling(bot) - except asyncio.CancelledError: - logger.info("Bot is off") - - -if __name__ == '__main__': - try: - logger.info("Bot is on") - asyncio.run(main()) - except KeyboardInterrupt: - logger.info("Bot is off") diff --git a/BybitBot_API.pyproj b/BybitBot_API.pyproj deleted file mode 100644 index 595308b..0000000 --- a/BybitBot_API.pyproj +++ /dev/null @@ -1,68 +0,0 @@ - - - Debug - 2.0 - bc1d7460-d8ca-4977-a249-0f6d6cc2375a - . - BibytBot_API.py - - - . - . - BibytBot_API - BibytBot_API - - - true - false - - - true - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/BybitBot_API.sln b/BybitBot_API.sln deleted file mode 100644 index e90ff6b..0000000 --- a/BybitBot_API.sln +++ /dev/null @@ -1,23 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.13.35825.156 d17.13 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "BibytBot_API", "BibytBot_API.pyproj", "{BC1D7460-D8CA-4977-A249-0F6D6CC2375A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {BC1D7460-D8CA-4977-A249-0F6D6CC2375A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BC1D7460-D8CA-4977-A249-0F6D6CC2375A}.Release|Any CPU.ActiveCfg = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {9AF00E9A-19FB-4146-96C0-B86C8B1E02C0} - EndGlobalSection -EndGlobal diff --git a/app/services/Bybit/functions/Add_Bybit_API.py b/app/services/Bybit/functions/Add_Bybit_API.py deleted file mode 100644 index e425446..0000000 --- a/app/services/Bybit/functions/Add_Bybit_API.py +++ /dev/null @@ -1,111 +0,0 @@ -from aiogram import F, Router -import logging.config - -from app.services.Bybit.functions.functions import start_bybit_trade_message -from logger_helper.logger_helper import LOGGING_CONFIG -import app.telegram.Keyboards.inline_keyboards as inline_markup -import app.telegram.Keyboards.reply_keyboards as reply_markup - -import app.telegram.functions.main_settings.settings as func_main_settings -import app.telegram.functions.risk_management_settings.settings as func_rmanagement_settings -import app.telegram.functions.condition_settings.settings as func_condition_settings -import app.telegram.functions.additional_settings.settings as func_additional_settings - -import app.telegram.database.requests as rq -from aiogram.types import Message, CallbackQuery - -from app.states.States import state_reg_bybit_api -from aiogram.fsm.context import FSMContext - -logging.config.dictConfig(LOGGING_CONFIG) -logger = logging.getLogger("add_bybit_api") - -router_register_bybit_api = Router() - - -@router_register_bybit_api.callback_query(F.data == 'clb_new_user_connect_bybit_api_message') -async def info_for_bybit_api_message(callback: CallbackQuery) -> None: - """ - Отвечает пользователю подробной инструкцией по подключению аккаунта Bybit. - Показывает как создать API ключ и передать его чат-боту. - """ - text = '''Подключение Bybit аккаунта - -1. Зарегистрируйтесь или войдите в свой аккаунт на Bybit (https://www.bybit.com/). -2. В личном кабинете выберите раздел API. -3. Создание нового API ключа - - Нажмите кнопку Create New Key (Создать новый ключ). - - Выберите системно-сгенерированный ключ. - - Укажите название API ключа (любое). - - Выберите права доступа для торговли (Trade). - - Можно ограничить доступ по IP для безопасности. -4. Подтверждение создания - - Подтвердите создание ключа. - - Отправьте чат-роботу. - -Важно: сохраните отдельно API Key и Secret Key в надежном месте. Secret ключ отображается только один раз. - ''' - - await callback.message.answer(text=text, parse_mode='html', reply_markup=inline_markup.connect_bybit_api_markup) - - await callback.answer() - - -@router_register_bybit_api.callback_query(F.data == 'clb_new_user_connect_bybit_api') -async def add_api_key_message(callback: CallbackQuery, state: FSMContext) -> None: - """ - Инициирует процесс добавления API ключа. - Переводит пользователя в состояние ожидания ввода API Key. - """ - await state.set_state(state_reg_bybit_api.api_key) - - text = 'Отправьте KEY_API ниже: ' - - await callback.message.answer(text=text) - - -@router_register_bybit_api.message(state_reg_bybit_api.api_key) -async def add_api_key_and_message_for_secret_key(message: Message, state: FSMContext) -> None: - """ - Сохраняет API Key во временное состояние FSM, - затем запрашивает у пользователя ввод Secret Key. - """ - await state.update_data(api_key=message.text) - - text = 'Отправьте SECRET_KEY ниже' - - await message.answer(text=text) - - await state.set_state(state_reg_bybit_api.secret_key) - - -@router_register_bybit_api.message(state_reg_bybit_api.secret_key) -async def add_secret_key(message: Message, state: FSMContext) -> None: - """ - Сохраняет Secret Key и финализирует регистрацию, - обновляет базу данных, устанавливает символ пользователя и очищает состояние. - """ - await state.update_data(secret_key=message.text) - - data = await state.get_data() - user = await rq.check_user(message.from_user.id) - - await rq.upsert_api_keys(message.from_user.id, data['api_key'], data['secret_key']) - await rq.set_new_user_symbol(message.from_user.id) - - await state.clear() - - await message.answer('Данные добавлены.', - reply_markup=reply_markup.base_buttons_markup) - - if user: - await start_bybit_trade_message(message) - else: - await rq.save_tg_id_new_user(message.from_user.id) - - await func_main_settings.reg_new_user_default_main_settings(message.from_user.id, message) - await func_rmanagement_settings.reg_new_user_default_risk_management_settings(message.from_user.id, - message) - await func_condition_settings.reg_new_user_default_condition_settings(message.from_user.id) - await func_additional_settings.reg_new_user_default_additional_settings(message.from_user.id, message) - await start_bybit_trade_message(message) \ No newline at end of file diff --git a/app/services/Bybit/functions/Futures.py b/app/services/Bybit/functions/Futures.py deleted file mode 100644 index d82e6e6..0000000 --- a/app/services/Bybit/functions/Futures.py +++ /dev/null @@ -1,893 +0,0 @@ -import asyncio -import logging.config -import time -import math -import app.services.Bybit.functions.balance as balance_g -import app.services.Bybit.functions.price_symbol as price_symbol -import app.telegram.database.requests as rq -import app.telegram.Keyboards.inline_keyboards as inline_markup -from logger_helper.logger_helper import LOGGING_CONFIG -from pybit import exceptions -from pybit.unified_trading import HTTP - -logging.config.dictConfig(LOGGING_CONFIG) -logger = logging.getLogger("futures") - -processed_trade_ids = set() - - -async def get_bybit_client(tg_id): - """ - Асинхронно получает экземпляр клиента Bybit. - - :param tg_id: int - ID пользователя Telegram - :return: HTTP - экземпляр клиента Bybit - """ - api_key = await rq.get_bybit_api_key(tg_id) - secret_key = await rq.get_bybit_secret_key(tg_id) - return HTTP(api_key=api_key, api_secret=secret_key) - - -def safe_float(val) -> float: - """ - Безопасное преобразование значения в float. - Возвращает 0.0, если значение None, пустое или некорректное. - """ - try: - if val is None or val == "": - return 0.0 - return float(val) - except (ValueError, TypeError): - logger.error("Некорректное значение для преобразования в float", exc_info=True) - return 0.0 - - -def format_trade_details_position(data): - """ - Форматирует информацию о сделке в виде строки. - """ - msg = data.get("data", [{}])[0] - closed_size = safe_float(msg.get("closedSize", 0)) - symbol = msg.get("symbol", "N/A") - entry_price = safe_float(msg.get("execPrice", 0)) - qty = safe_float(msg.get("orderQty", 0)) - order_type = msg.get("orderType", "N/A") - side = msg.get("side", "") - commission = safe_float(msg.get("execFee", 0)) - pnl = safe_float(msg.get("execPnl", 0)) - - movement = "" - if side.lower() == "buy": - movement = "Покупка" - elif side.lower() == "sell": - movement = "Продажа" - else: - movement = side - - if closed_size > 0: - text = ( - f"Сделка закрыта:\n" - f"Торговая пара: {symbol}\n" - f"Цена исполнения: {entry_price:.6f}\n" - f"Количество: {qty}\n" - f"Закрыто позиций: {closed_size}\n" - f"Тип ордера: {order_type}\n" - f"Движение: {movement}\n" - f"Комиссия за сделку: {commission:.6f}\n" - f"Реализованная прибыль: {pnl:.6f} USDT" - ) - return text - if order_type == "Market": - text = ( - f"Сделка открыта:\n" - f"Торговая пара: {symbol}\n" - f"Цена исполнения: {entry_price:.6f}\n" - f"Количество: {qty}\n" - f"Тип ордера: {order_type}\n" - f"Движение: {movement}\n" - f"Комиссия за сделку: {commission:.6f}" - ) - return text - return None - - -def format_order_details_position(data): - """ - Форматирует информацию об ордере в виде строки. - """ - msg = data.get("data", [{}])[0] - price = safe_float(msg.get("price", 0)) - qty = safe_float(msg.get("qty", 0)) - cum_exec_qty = safe_float(msg.get("cumExecQty", 0)) - cum_exec_fee = safe_float(msg.get("cumExecFee", 0)) - order_status = msg.get("orderStatus", "N/A") - symbol = msg.get("symbol", "N/A") - order_type = msg.get("orderType", "N/A") - side = msg.get("side", "") - trigger_price = msg.get("triggerPrice", "N/A") - - movement = "" - if side.lower() == "buy": - movement = "Покупка" - elif side.lower() == "sell": - movement = "Продажа" - else: - movement = side - - if order_status.lower() == "filled" and order_type.lower() == "limit": - text = ( - f"Ордер исполнен:\n" - f"Торговая пара: {symbol}\n" - f"Цена исполнения: {price:.6f}\n" - f"Количество: {qty}\n" - f"Исполнено позиций: {cum_exec_qty}\n" - f"Тип ордера: {order_type}\n" - f"Движение: {movement}\n" - f"Комиссия за сделку: {cum_exec_fee:.6f}\n" - ) - return text - - elif order_status.lower() == "new": - text = ( - f"Ордер создан:\n" - f"Торговая пара: {symbol}\n" - f"Цена: {price:.6f}\n" - f"Количество: {qty}\n" - f"Тип ордера: {order_type}\n" - f"Движение: {movement}\n" - ) - return text - - elif order_status.lower() == "cancelled": - text = ( - f"Ордер отменен:\n" - f"Торговая пара: {symbol}\n" - f"Цена: {price:.6f}\n" - f"Количество: {qty}\n" - f"Тип ордера: {order_type}\n" - f"Движение: {movement}\n" - ) - return text - elif order_status.lower() == "untriggered": - text = ( - f"Условный ордер создан:\n" - f"Торговая пара: {symbol}\n" - f"Цена: {price:.6f}\n" - f"Триггер цена: {trigger_price}\n" - f"Количество: {qty}\n" - f"Тип ордера: {order_type}\n" - f"Движение: {movement}\n" - ) - return text - elif order_status.lower() == "deactivated": - text = ( - f"Условный ордер отменен:\n" - f"Торговая пара: {symbol}\n" - f"Цена: {price:.6f}\n" - f"Триггер цена: {trigger_price}\n" - f"Количество: {qty}\n" - f"Тип ордера: {order_type}\n" - f"Движение: {movement}\n" - ) - return text - return None - - -def parse_pnl_from_msg(msg) -> float: - """ - Извлекает реализованную прибыль/убыток из сообщения. - """ - try: - data = msg.get("data", [{}])[0] - return float(data.get("execPnl", 0)) - except Exception as e: - logger.error("Ошибка при извлечении реализованной прибыли: %s", e) - return 0.0 - - -async def calculate_total_budget(starting_quantity, martingale_factor, max_steps, commission_fee_percent): - """ - Вычисляет общий бюджет серии ставок с учётом цены пары, комиссии и кредитного плеча. - - Параметры: - - starting_quantity_usdt: стартовый размер ставки в долларах (USD) - - martingale_factor: множитель увеличения ставки при каждом проигрыше - - max_steps: максимальное количество шагов удвоения ставки - - commission_fee_percent: процент комиссии на одну операцию (открытие или закрытие) - - leverage: кредитное плечо - - current_price: текущая цена актива (например BTCUSDT) - - Возвращает: - - общий бюджет в долларах, который необходимо иметь на счету - """ - total = 0 - for step in range(max_steps): - base_quantity = starting_quantity * (martingale_factor ** step) - if commission_fee_percent == 0: - # Комиссия уже включена в сумму ставки, поэтому реальный размер позиции меньше - quantity = base_quantity / (1 + commission_fee_percent) - else: - # Комиссию добавляем сверху - quantity = base_quantity * (1 + commission_fee_percent) - - total += quantity - return total - - -async def handle_execution_message(message, msg): - """ - Обработчик сообщений об исполнении сделки. - Логирует событие и проверяет условия для мартингейла и TP. - """ - tg_id = message.from_user.id - data = msg.get("data", [{}])[0] - pnl = parse_pnl_from_msg(msg) - data_main_stgs = await rq.get_user_main_settings(tg_id) - symbol = data.get("symbol") - trading_mode = data_main_stgs.get("trading_mode", "Long") - trigger = await rq.get_for_registration_trigger(tg_id) - margin_mode = data_main_stgs.get("margin_type", "Isolated") - starting_quantity = safe_float(data_main_stgs.get("starting_quantity")) - martingale_factor = safe_float(data_main_stgs.get("martingale_factor")) - closed_size = safe_float(data.get("closedSize", 0)) - - trade_info = format_trade_details_position(data=msg) - - if trade_info: - await message.answer(f"{trade_info}", reply_markup=inline_markup.back_to_main) - - if data is not None: - await rq.update_trigger_price(tg_id, 0.0) - - if closed_size == 0: - side = data.get("side", "") - - if side.lower() == "buy": - await rq.set_last_series_info(tg_id, last_side="Buy") - elif side.lower() == "sell": - await rq.set_last_series_info(tg_id, last_side="Sell") - - if trigger == "Автоматический" and closed_size > 0: - if trading_mode == 'Switch': - side = data_main_stgs.get("last_side") - else: - side = "Buy" if trading_mode == "Long" else "Sell" - - if pnl < 0: - - current_martingale = await rq.get_martingale_step(tg_id) - current_martingale_step = int(current_martingale) - current_martingale += 1 - next_quantity = float(starting_quantity) * ( - float(martingale_factor) ** current_martingale_step - ) - await rq.update_martingale_step(tg_id, current_martingale) - await rq.update_starting_quantity(tg_id=tg_id, num=next_quantity) - await message.answer( - f"❗️ Сделка закрылась в минус, открываю новую сделку с увеличенной ставкой.\n" - ) - await open_position( - tg_id, - message, - side=side, - margin_mode=margin_mode, - symbol=symbol, - quantity=next_quantity, - ) - - elif pnl > 0: - await rq.update_martingale_step(tg_id, 1) - num = data_main_stgs.get("base_quantity") - await rq.update_starting_quantity(tg_id=tg_id, num=num) - await message.answer( - "❗️ Прибыль достигнута, шаг мартингейла сброшен. " - "Возврат к начальной ставке." - ) - - -async def handle_order_message(message, msg: dict) -> None: - """ - Обработчик сообщений об исполнении ордера. - Логирует событие и проверяет условия для мартингейла и TP. - """ - # logger.info(f"Исполнен ордер:\n{json.dumps(msg, indent=4, ensure_ascii=False)}") - data = msg.get("data", [{}])[0] - tg_id = message.from_user.id - if data is not None: - await rq.update_trigger_price(tg_id, 0.0) - trade_info = format_order_details_position(msg) - - if trade_info: - await message.answer(f"{trade_info}", reply_markup=inline_markup.back_to_main) - - -async def error_max_step(message) -> None: - """ - Сообщение об ошибке превышения максимального количества шагов мартингейла. - """ - logger.error( - "Сделка не была совершена, превышен лимит максимального количества ставок в серии." - ) - await message.answer( - "Сделка не была совершена, превышен лимит максимального количества ставок в серии.", - reply_markup=inline_markup.back_to_main, - ) - - -async def error_max_risk(message) -> None: - """ - Сообщение об ошибке превышения риск-лимита сделки. - """ - logger.error("Сделка не была совершена, риск убытка превышает допустимый лимит.") - await message.answer( - "Сделка не была совершена, риск убытка превышает допустимый лимит.", - reply_markup=inline_markup.back_to_main, - ) - - -async def open_position( - tg_id, message, side: str, margin_mode: str, symbol, quantity, tpsl_mode="Full" -): - """ - Открывает позицию на Bybit с учётом настроек пользователя, маржи, размера лота, платформы и риска. - - Возвращает True при успехе, False при ошибках открытия ордера, None при исключениях. - """ - try: - client = await get_bybit_client(tg_id) - data_main_stgs = await rq.get_user_main_settings(tg_id) - order_type = data_main_stgs.get("entry_order_type") - bybit_margin_mode = ( - "ISOLATED_MARGIN" if margin_mode == "Isolated" else "REGULAR_MARGIN" - ) - - positions_get = client.get_positions(category="linear", symbol=symbol) - positions = positions_get.get("result", {}).get("list", []) - logger.info(f"Позиции: {positions}") - - trigger_price = await rq.get_trigger_price(tg_id) - limit_price = None - if order_type == "Limit": - limit_price = await rq.get_limit_price(tg_id) - data_risk_stgs = await rq.get_user_risk_management_settings(tg_id) - - price = await price_symbol.get_price(tg_id, symbol=symbol) - entry_price = safe_float(price) - leverage = safe_float(data_main_stgs.get("size_leverage", 1)) - - max_martingale_steps = int(data_main_stgs.get("maximal_quantity", 0)) - current_martingale = await rq.get_martingale_step(tg_id) - max_risk_percent = safe_float(data_risk_stgs.get("max_risk_deal")) - loss_profit = safe_float(data_risk_stgs.get("price_loss")) - commission_fee = data_risk_stgs.get("commission_fee") - fee_info = client.get_fee_rates(category='linear', symbol=symbol) - instruments_resp = client.get_instruments_info(category="linear", symbol=symbol) - instrument = instruments_resp.get("result", {}).get("list", []) - - if commission_fee == "Да": - commission_fee_percent = safe_float(fee_info['result']['list'][0]['takerFeeRate']) - else: - commission_fee_percent = 0.0 - - if commission_fee_percent > 0: - # Добавляем к тейк-профиту процент комиссии - tp_multiplier = 1 + (loss_profit / 100) + commission_fee_percent - else: - tp_multiplier = 1 + (loss_profit / 100) - - - if order_type == "Limit" and limit_price: - price_for_calc = limit_price - else: - price_for_calc = entry_price - - balance = await balance_g.get_balance(tg_id, message) - potential_loss = safe_float(quantity) * price_for_calc * (loss_profit / 100) - adjusted_loss = potential_loss / leverage - allowed_loss = safe_float(balance) * (max_risk_percent / 100) - - if adjusted_loss > allowed_loss: - await error_max_risk(message) - return - - if max_martingale_steps < current_martingale: - await error_max_step(message) - return - - max_leverage = safe_float(instrument[0].get("leverageFilter", {}).get("maxLeverage", 0)) - - if safe_float(leverage) > max_leverage: - await message.answer( - f"Запрошенное кредитное плечо {leverage} превышает максимальное {max_leverage} для {symbol}. " - f"Устанавливаю максимальное.", - reply_markup=inline_markup.back_to_main, - ) - logger.info( - f"Запрошенное кредитное плечо {leverage} превышает максимальное {max_leverage} для {symbol}. Устанавливаю максимальное.") - leverage_to_set = max_leverage - else: - leverage_to_set = safe_float(leverage) - - try: - client.set_leverage( - category="linear", - symbol=symbol, - buyLeverage=str(leverage_to_set), - sellLeverage=str(leverage_to_set), - ) - logger.info(f"Set leverage to {leverage_to_set} for {symbol}") - except exceptions.InvalidRequestError as e: - if "110043" in str(e): - logger.info(f"Leverage already set to {leverage_to_set} for {symbol}") - else: - raise e - - if instruments_resp.get("retCode") == 0: - instrument_info = instruments_resp.get("result", {}).get("list", []) - if instrument_info: - instrument_info = instrument_info[0] - min_notional_value = float(instrument_info.get("lotSizeFilter", {}).get("minNotionalValue", 0)) - min_order_value = min_notional_value - else: - min_order_value = 5.0 - - qty_step = float(instrument[0].get("lotSizeFilter", {}).get("qtyStep","1")) - quantity_in_usdt = float(quantity) - min_qty_lots = min_order_value / price_for_calc - adjusted_min_lots = math.ceil(min_qty_lots / qty_step) * qty_step - lots = quantity_in_usdt / price_for_calc - requested_lots = int(lots) - - if requested_lots < adjusted_min_lots: - logger.error( - f"Сумма ордера слишком мала. " - f"Пожалуйста, увеличьте сумму позиций." - ) - await message.answer( - f"Сумма ордера слишком мала. " - f"Пожалуйста, увеличьте сумму позиций.", - reply_markup=inline_markup.back_to_main, - ) - return False - - if bybit_margin_mode == "ISOLATED_MARGIN": - # Открываем позицию - if trigger_price and float(trigger_price) > 0: - response = client.place_order( - category="linear", - symbol=symbol, - side=side, - orderType="Stop" if order_type == "Conditional" else order_type, - qty=str(requested_lots), - price=(str(limit_price) if order_type == "Limit" and limit_price else None), - triggerPrice=str(trigger_price), - triggerBy="LastPrice", - triggerDirection=2 if side == "Buy" else 1, - timeInForce="GTC", - orderLinkId=f"deal_{symbol}_{int(time.time())}", - ) - else: - # Обычный ордер, без триггера - response = client.place_order( - category="linear", - symbol=symbol, - side=side, - orderType=order_type, - qty=str(requested_lots), - price=(str(limit_price) if order_type == "Limit" and limit_price else None), - timeInForce="GTC", - orderLinkId=f"deal_{symbol}_{int(time.time())}", - ) - - if response.get("retCode", -1) == 0: - return True - if response.get("retCode", -1) != 0: - logger.error(f"Ошибка открытия ордера: {response}") - await message.answer( - f"Ошибка открытия ордера", reply_markup=inline_markup.back_to_main - ) - return False - - # Получаем цену ликвидации - positions = client.get_positions(category="linear", symbol=symbol) - pos = positions.get("result", {}).get("list", [{}])[0] - avg_price = float(pos.get("avgPrice", 0)) - liq_price = safe_float(pos.get("liqPrice", 0)) - - if liq_price > 0 and avg_price > 0: - if side.lower() == "buy": - base_tp = avg_price + (avg_price - liq_price) - take_profit_price = base_tp * (1 + commission_fee_percent) - else: - base_tp = avg_price - (liq_price - avg_price) - take_profit_price = base_tp * (1 - commission_fee_percent) - - take_profit_price = max(take_profit_price, 0) - - try: - try: - client.set_tp_sl_mode( - symbol=symbol, category="linear", tpSlMode="Full" - ) - except exceptions.InvalidRequestError as e: - if "same tp sl mode" in str(e): - logger.info("Режим TP/SL уже установлен - пропускаем") - else: - raise - client.set_trading_stop( - category="linear", - symbol=symbol, - takeProfit=str(round(take_profit_price, 5)), - tpTriggerBy="LastPrice", - slTriggerBy="LastPrice", - positionIdx=0, - reduceOnly=False, - tpslMode=tpsl_mode, - ) - except Exception as e: - logger.error(f"Ошибка установки TP/SL: {e}") - await message.answer( - "Ошибка при установке Take Profit и Stop Loss.", - reply_markup=inline_markup.back_to_main, - ) - return False - else: - logger.warning("Не удалось получить цену ликвидации для позиции") - - else: # REGULAR_MARGIN - try: - client.set_tp_sl_mode(symbol=symbol, category="linear", tpSlMode="Full") - except exceptions.InvalidRequestError as e: - if "same tp sl mode" in str(e): - logger.info("Режим TP/SL уже установлен - пропускаем") - else: - raise - - if order_type == "Market": - base_price = entry_price - else: - base_price = limit_price - - if side.lower() == "buy": - take_profit_price = base_price * tp_multiplier - stop_loss_price = base_price * (1 - loss_profit / 100) - else: - take_profit_price = base_price * (1 - (loss_profit / 100) - (tp_multiplier - 1)) - stop_loss_price = base_price * (1 + loss_profit / 100) - - take_profit_price = max(take_profit_price, 0) - stop_loss_price = max(stop_loss_price, 0) - - if tpsl_mode == "Full": - tp_order_type = "Market" - sl_order_type = "Market" - tp_limit_price = None - sl_limit_price = None - else: # Partial - tp_order_type = "Limit" - sl_order_type = "Limit" - tp_limit_price = take_profit_price - sl_limit_price = stop_loss_price - - response = client.place_order( - category="linear", - symbol=symbol, - side=side, - orderType=order_type, - qty=str(requested_lots), - price=( - str(limit_price) if order_type == "Limit" and limit_price else None - ), - takeProfit=str(take_profit_price), - tpOrderType=tp_order_type, - tpLimitPrice=str(tp_limit_price) if tp_limit_price else None, - stopLoss=str(stop_loss_price), - slOrderType=sl_order_type, - slLimitPrice=str(sl_limit_price) if sl_limit_price else None, - tpslMode=tpsl_mode, - triggerPrice=str(trigger_price) if trigger_price and float(trigger_price) > 0 else None, - triggerBy="LastPrice" if trigger_price and float(trigger_price) > 0 else None, - triggerDirection=2 if side == "Buy" else 1 if trigger_price and float(trigger_price) > 0 else None, - timeInForce="GTC", - orderLinkId=f"deal_{symbol}_{int(time.time())}", - ) - - if response.get("retCode", -1) == 0: - return True - else: - logger.error(f"Ошибка открытия ордера: {response}") - await message.answer( - f"Ошибка открытия ордера", reply_markup=inline_markup.back_to_main - ) - return False - - return None - except exceptions.InvalidRequestError as e: - logger.error("InvalidRequestError: %s", e) - error_text = str(e) - if "estimated will trigger liq" in error_text: - await message.answer( - "Лимитный ордер может вызвать мгновенную ликвидацию. Проверьте параметры ордера.", - reply_markup=inline_markup.back_to_main, - ) - elif "ab not enough for new order" in error_text: - await message.answer("Недостаточно средств для нового ордера", - reply_markup=inline_markup.back_to_main) - else: - logger.error("Ошибка при совершении сделки: %s", e) - await message.answer( - "Недостаточно средств для размещения нового ордера с заданным количеством и плечом.", - reply_markup=inline_markup.back_to_main, - ) - except Exception as e: - logger.error("Ошибка при совершении сделки: %s", e) - await message.answer( - "Возникла ошибка при попытке открыть позицию.", - reply_markup=inline_markup.back_to_main, - ) - - -async def set_take_profit_stop_loss( - tg_id: int, - message, - take_profit_price: float, - stop_loss_price: float, - tpsl_mode="Full", -): - """ - Устанавливает уровни Take Profit и Stop Loss для открытой позиции. - """ - symbol = await rq.get_symbol(tg_id) - client = await get_bybit_client(tg_id) - await cancel_all_tp_sl_orders(tg_id, symbol) - - try: - try: - client.set_tp_sl_mode(symbol=symbol, category="linear", tpSlMode=tpsl_mode) - except exceptions.InvalidRequestError as e: - if "same tp sl mode" in str(e).lower(): - logger.info("Режим TP/SL уже установлен для %s - пропускаем", symbol) - else: - raise - - resp = client.set_trading_stop( - category="linear", - symbol=symbol, - takeProfit=str(round(take_profit_price, 5)), - stopLoss=str(round(stop_loss_price, 5)), - tpTriggerBy="LastPrice", - slTriggerBy="LastPrice", - positionIdx=0, - reduceOnly=False, - tpslMode=tpsl_mode, - ) - - if resp.get("retCode") != 0: - await message.answer( - f"Ошибка обновления TP/SL: {resp.get('retMsg')}", - reply_markup=inline_markup.back_to_main, - ) - return - - await message.answer( - f"ТП и СЛ успешно установлены:\nТейк-профит: {take_profit_price:.5f}\nСтоп-лосс: {stop_loss_price:.5f}", - reply_markup=inline_markup.back_to_main, - ) - except Exception as e: - logger.error(f"Ошибка установки TP/SL для {symbol}: {e}", exc_info=True) - await message.answer( - "Произошла ошибка при установке TP и SL.", - reply_markup=inline_markup.back_to_main, - ) - - -async def cancel_all_tp_sl_orders(tg_id, symbol): - """ - Отменяет лимитные ордера для указанного символа. - """ - client = await get_bybit_client(tg_id) - last_response = None - try: - orders_resp = client.get_open_orders(category="linear", symbol=symbol) - orders = orders_resp.get("result", {}).get("list", []) - - for order in orders: - order_id = order.get("orderId") - order_symbol = order.get("symbol") - cancel_resp = client.cancel_order( - category="linear", symbol=symbol, orderId=order_id - ) - if cancel_resp.get("retCode") != 0: - logger.warning( - f"Не удалось отменить ордер {order_id}: {cancel_resp.get('retMsg')}" - ) - else: - last_response = order_symbol - except Exception as e: - logger.error(f"Ошибка при отмене ордера: {e}") - - return last_response - - -async def get_active_positions(tg_id, message): - """ - Показывает активные позиции пользователя. - """ - client = await get_bybit_client(tg_id) - active_positions = client.get_positions(category="linear", settleCoin="USDT") - positions = active_positions.get("result", {}).get("list", []) - active_symbols = [ - pos.get("symbol") for pos in positions if float(pos.get("size", 0)) > 0 - ] - - if active_symbols: - await message.answer( - "📈 Ваши активные позиции:", - reply_markup=inline_markup.create_trades_inline_keyboard(active_symbols), - ) - else: - await message.answer( - "❗️ У вас нет активных позиций.", reply_markup=inline_markup.back_to_main - ) - return - - -async def get_active_positions_by_symbol(tg_id, symbol, message): - """ - Показывает активные позиции пользователя по символу. - """ - client = await get_bybit_client(tg_id) - active_positions = client.get_positions(category="linear", symbol=symbol) - positions = active_positions.get("result", {}).get("list", []) - pos = positions[0] if positions else None - - if float(pos.get("size", 0)) == 0: - await message.answer( - "❗️ У вас нет активных позиций.", reply_markup=inline_markup.back_to_main - ) - return - - text = ( - f"Торговая пара: {pos.get('symbol')}\n" - f"Цена входа: {pos.get('avgPrice')}\n" - f"Движение: {pos.get('side')}\n" - f"Кредитное плечо: {pos.get('leverage')}x\n" - f"Количество: {pos.get('size')}\n" - f"Тейк-профит: {pos.get('takeProfit')}\n" - f"Стоп-лосс: {pos.get('stopLoss')}\n" - ) - - await message.answer( - text, reply_markup=inline_markup.create_close_deal_markup(symbol) - ) - - -async def get_active_orders(tg_id, message): - """ - Показывает активные лимитные ордера пользователя. - """ - client = await get_bybit_client(tg_id) - response = client.get_open_orders( - category="linear", settleCoin="USDT", orderType="Limit" and "Market" - ) - orders = response.get("result", {}).get("list", []) - limit_orders = [order for order in orders] - - if limit_orders: - symbols = [order["symbol"] for order in limit_orders] - await message.answer( - "📈 Ваши активные ордера:", - reply_markup=inline_markup.create_trades_inline_keyboard_limits(symbols), - ) - else: - await message.answer( - "❗️ У вас нет активных ордеров.", - reply_markup=inline_markup.back_to_main, - ) - return - - -async def get_active_orders_by_symbol(tg_id, symbol, message): - """ - Показывает активные лимитные ордера пользователя по символу. - """ - client = await get_bybit_client(tg_id) - active_orders = client.get_open_orders(category="linear", symbol=symbol) - limit_orders = [ - order - for order in active_orders.get("result", {}).get("list", []) - ] - - if not limit_orders: - await message.answer( - "Нет активных ордеров по данной торговой паре.", - reply_markup=inline_markup.back_to_main, - ) - return - - texts = [] - for order in limit_orders: - text = ( - f"Торговая пара: {order.get('symbol')}\n" - f"Тип ордера: {order.get('orderType')}\n" - f"Сторона: {order.get('side')}\n" - f"Цена: {order.get('price')}\n" - f"Триггер цена: {order.get('triggerPrice')}\n" - f"Количество: {order.get('qty')}\n" - ) - texts.append(text) - - await message.answer( - "\n\n".join(texts), reply_markup=inline_markup.create_close_limit_markup(symbol) - ) - - -async def close_user_trade(tg_id: int, symbol: str): - """ - Закрывает открытые позиции пользователя по символу рыночным ордером. - Возвращает True при успехе, False при ошибках. - """ - try: - client = await get_bybit_client(tg_id) - positions_resp = client.get_positions(category="linear", symbol=symbol) - - if positions_resp.get("retCode") != 0: - return False - positions_list = positions_resp.get("result", {}).get("list", []) - if not positions_list: - return False - - position = positions_list[0] - qty = abs(safe_float(position.get("size"))) - side = position.get("side") - if qty == 0: - return False - - close_side = "Sell" if side == "Buy" else "Buy" - - place_resp = client.place_order( - category="linear", - symbol=symbol, - side=close_side, - orderType="Market", - qty=str(qty), - timeInForce="GTC", - reduceOnly=True, - ) - - if place_resp.get("retCode") == 0: - return True - else: - return False - except Exception as e: - logger.error( - f"Ошибка закрытия сделки {symbol} для пользователя {tg_id}: {e}", - exc_info=True, - ) - return False - - -async def close_trade_after_delay(tg_id: int, message, symbol: str, delay_sec: int): - """ - Закрывает сделку пользователя после задержки delay_sec секунд. - """ - try: - await asyncio.sleep(delay_sec) - result = await close_user_trade(tg_id, symbol) - if result: - await message.answer( - f"Сделка {symbol} успешно закрыта по таймеру." - ) - logger.info(f"Сделка {symbol} успешно закрыта по таймеру.") - else: - await message.answer( - f"Не удалось закрыть сделку {symbol} по таймеру.", - reply_markup=inline_markup.back_to_main, - ) - logger.error(f"Не удалось закрыть сделку {symbol} по таймеру.") - except asyncio.CancelledError: - await message.answer( - f"Закрытие сделки {symbol} по таймеру отменено.", - reply_markup=inline_markup.back_to_main, - ) - logger.info(f"Закрытие сделки {symbol} по таймеру отменено.") diff --git a/app/services/Bybit/functions/balance.py b/app/services/Bybit/functions/balance.py deleted file mode 100644 index 35093d4..0000000 --- a/app/services/Bybit/functions/balance.py +++ /dev/null @@ -1,52 +0,0 @@ -import app.telegram.database.requests as rq -import app.telegram.Keyboards.inline_keyboards as inline_markup -import logging.config -from logger_helper.logger_helper import LOGGING_CONFIG -from pybit.unified_trading import HTTP - -logging.config.dictConfig(LOGGING_CONFIG) -logger = logging.getLogger("balance") - - -async def get_balance(tg_id: int, message) -> float: - """ - Асинхронно получает общий баланс пользователя на Bybit. - - Процедура: - - Получает API ключ и секрет пользователя из базы данных. - - Если ключи не заданы, отправляет пользователю сообщение с предложением подключить платформу. - - Создает клиент Bybit с ключами. - - Запрашивает общий баланс по типу аккаунта UNIFIED. - - Если ответ успешен, возвращает баланс в виде float. - - При ошибках API или исключениях логирует ошибку и уведомляет пользователя. - - :param tg_id: int - идентификатор пользователя Telegram - :param message: объект сообщения для отправки ответов пользователю - :return: float - общий баланс пользователя; 0 при ошибке или отсутствии ключей - """ - api_key = await rq.get_bybit_api_key(tg_id) - secret_key = await rq.get_bybit_secret_key(tg_id) - - client = HTTP( - api_key=api_key, - api_secret=secret_key - ) - - if api_key is None or secret_key is None: - await message.answer('⚠️ Подключите платформу для торговли', - reply_markup=inline_markup.connect_bybit_api_message) - return 0 - - try: - response = client.get_wallet_balance(accountType='UNIFIED') - if response['retCode'] == 0: - total_balance = response['result']['list'][0].get('totalWalletBalance', '0') - return total_balance - else: - logger.error(f"Ошибка API: {response.get('retMsg')}") - await message.answer(f"⚠️ Ошибка API: {response.get('retMsg')}") - return 0 - except Exception as e: - logger.error(f"Ошибка при получении общего баланса: {e}") - await message.answer('Ошибка при подключении, повторите попытку', reply_markup=inline_markup.connect_bybit_api_message) - return 0 diff --git a/app/services/Bybit/functions/bybit_ws.py b/app/services/Bybit/functions/bybit_ws.py deleted file mode 100644 index 5eea159..0000000 --- a/app/services/Bybit/functions/bybit_ws.py +++ /dev/null @@ -1,115 +0,0 @@ -import asyncio -import logging.config - -from pybit.unified_trading import WebSocket -from websocket import WebSocketConnectionClosedException -from logger_helper.logger_helper import LOGGING_CONFIG -import app.telegram.database.requests as rq - -logging.config.dictConfig(LOGGING_CONFIG) -logger = logging.getLogger("bybit_ws") - -event_loop = None # Сюда нужно будет установить event loop из основного приложения -active_ws_tasks = {} - - -def on_ws_error(ws, error): - logger.error(f"WebSocket internal error: {error}") - # Запланировать переподключение через event loop - if event_loop: - asyncio.run_coroutine_threadsafe(reconnect_ws(ws), event_loop) - - -def on_ws_close(ws, close_status_code, close_msg): - logger.warning(f"WebSocket closed: {close_status_code} - {close_msg}") - # Запланировать переподключение через event loop - if event_loop: - asyncio.run_coroutine_threadsafe(reconnect_ws(ws), event_loop) - - -async def reconnect_ws(ws): - logger.info("Запускаем переподключение WebSocket...") - await asyncio.sleep(5) - try: - await ws.run_forever() - except WebSocketConnectionClosedException: - logger.info("WebSocket переподключение успешно завершено.") - - -def get_or_create_event_loop() -> asyncio.AbstractEventLoop: - """ - Возвращает текущий активный цикл событий asyncio или создает новый, если его нет. - """ - try: - return asyncio.get_running_loop() - except RuntimeError: - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - return loop - - -def set_event_loop(loop: asyncio.AbstractEventLoop): - global event_loop - event_loop = loop - - -async def run_ws_for_user(tg_id, message) -> None: - """ - Запускает WebSocket Bybit для пользователя с указанным tg_id. - """ - if tg_id not in active_ws_tasks or active_ws_tasks[tg_id].done(): - api_key = await rq.get_bybit_api_key(tg_id) - api_secret = await rq.get_bybit_secret_key(tg_id) - # Запускаем WebSocket как асинхронную задачу - active_ws_tasks[tg_id] = asyncio.create_task( - start_execution_ws(api_key, api_secret, message) - ) - logger.info(f"WebSocket для пользователя {tg_id} запущен.") - - -def on_order_callback(message, msg): - if event_loop is not None: - from app.services.Bybit.functions.Futures import handle_order_message - asyncio.run_coroutine_threadsafe(handle_order_message(message, msg), event_loop) - logger.info("Callback для ордера выполнен.") - else: - logger.error("Event loop не установлен, callback пропущен.") - - -def on_execution_callback(message, ws_msg): - if event_loop is not None: - from app.services.Bybit.functions.Futures import handle_execution_message - asyncio.run_coroutine_threadsafe(handle_execution_message(message, ws_msg), event_loop) - logger.info("Callback для маркета выполнен.") - else: - logger.error("Event loop не установлен, callback пропущен.") - - -async def start_execution_ws(api_key: str, api_secret: str, message): - """ - Запускает и поддерживает WebSocket подключение для исполнения сделок. - Реконнект при потерях соединения. - """ - reconnect_delay = 5 - while True: - try: - if not api_key or not api_secret: - logger.error("API_KEY и API_SECRET должны быть указаны для подключения к приватным каналам.") - await asyncio.sleep(reconnect_delay) - continue - ws = WebSocket(api_key=api_key, api_secret=api_secret, testnet=False, channel_type="private") - - ws.on_error = on_ws_error - ws.on_close = on_ws_close - - ws.subscribe("order", lambda ws_msg: on_order_callback(message, ws_msg)) - ws.subscribe("execution", lambda ws_msg: on_execution_callback(message, ws_msg)) - - while True: - await asyncio.sleep(1) # Поддержание активности - except WebSocketConnectionClosedException: - logger.warning("WebSocket закрыт, переподключение через 5 секунд...") - await asyncio.sleep(reconnect_delay) - except Exception as e: - logger.error(f"Ошибка WebSocket: {e}") - await asyncio.sleep(reconnect_delay) diff --git a/app/services/Bybit/functions/functions.py b/app/services/Bybit/functions/functions.py deleted file mode 100644 index 6f6b817..0000000 --- a/app/services/Bybit/functions/functions.py +++ /dev/null @@ -1,540 +0,0 @@ -import asyncio -import logging.config -from aiogram import F, Router - -from app.services.Bybit.functions.bybit_ws import run_ws_for_user -from app.telegram.functions.main_settings.settings import main_settings_message -from logger_helper.logger_helper import LOGGING_CONFIG - -from app.services.Bybit.functions.Futures import (close_user_trade, set_take_profit_stop_loss, \ - get_active_positions_by_symbol, get_active_orders_by_symbol, - get_active_positions, get_active_orders, cancel_all_tp_sl_orders, - open_position, close_trade_after_delay, safe_float, - ) -from app.services.Bybit.functions.balance import get_balance -import app.telegram.Keyboards.inline_keyboards as inline_markup - -import app.telegram.database.requests as rq -from aiogram.types import Message, CallbackQuery -from app.services.Bybit.functions.price_symbol import get_price -# import app.services.Bybit.functions.balance as balance_g - -from app.states.States import (state_update_symbol, - SetTP_SL_State, CloseTradeTimerState) -from aiogram.fsm.context import FSMContext - -from app.services.Bybit.functions.get_valid_symbol import get_valid_symbols - -logging.config.dictConfig(LOGGING_CONFIG) -logger = logging.getLogger("functions") - -router_functions_bybit_trade = Router() - -user_trade_tasks = {} - - -@router_functions_bybit_trade.callback_query(F.data.in_(['clb_start_trading', 'clb_back_to_main', 'back_to_main'])) -async def clb_start_bybit_trade_message(callback: CallbackQuery) -> None: - """ - Обработка нажатия кнопок запуска торговли или возврата в главное меню. - Отправляет информацию о балансе, символе, цене и инструкциях по торговле. - """ - user_id = callback.from_user.id - balance = await get_balance(user_id, callback.message) - - if balance: - symbol = await rq.get_symbol(user_id) - price = await get_price(user_id, symbol=symbol) - - text = ( - f"💎 Торговля на Bybit\n\n" - f"⚖️ Ваш баланс (USDT): {float(balance):.2f}\n" - f"📊 Текущая торговая пара: {symbol}\n" - f"$$$ Цена: {price}\n\n" - "Как начать торговлю?\n\n" - "1️⃣ Проверьте и тщательно настройте все параметры в вашем профиле.\n" - "2️⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару, без лишних символов (например: BTCUSDT).\n" - "3️⃣ Нажмите кнопку 'Начать торговать'.\n" - ) - await callback.message.edit_text(text=text, parse_mode='html', reply_markup=inline_markup.trading_markup) - - -async def start_bybit_trade_message(message: Message) -> None: - """ - Отправляет пользователю информацию о балансе, символе и текущей цене, - вместе с инструкциями по началу торговли. - """ - balance = await get_balance(message.from_user.id, message) - tg_id = message.from_user.id - - if balance: - await run_ws_for_user(tg_id, message) - symbol = await rq.get_symbol(message.from_user.id) - price = await get_price(message.from_user.id, symbol=symbol) - - text = ( - f"💎 Торговля на Bybit\n\n" - f"⚖️ Ваш баланс (USDT): {float(balance):.2f}\n" - f"📊 Текущая торговая пара: {symbol}\n" - f"$$$ Цена: {price}\n\n" - "Как начать торговлю?\n\n" - "1️⃣ Проверьте и тщательно настройте все параметры в вашем профиле.\n" - "2️⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару, без лишних символов (например: BTCUSDT).\n" - "3️⃣ Нажмите кнопку 'Начать торговать'.\n" - ) - - await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.trading_markup) - - -@router_functions_bybit_trade.callback_query(F.data == 'clb_update_trading_pair') -async def update_symbol_for_trade_message(callback: CallbackQuery, state: FSMContext) -> None: - """ - Начинает процедуру обновления торговой пары, переводит пользователя в состояние ожидания пары. - """ - await state.set_state(state_update_symbol.symbol) - await callback.answer() - - await callback.message.answer( - text='Укажите торговую пару заглавными буквами без пробелов и лишних символов (пример: BTCUSDT): ', - reply_markup=inline_markup.cancel) - - -@router_functions_bybit_trade.message(state_update_symbol.symbol) -async def update_symbol_for_trade(message: Message, state: FSMContext) -> None: - """ - Обрабатывает ввод торговой пары пользователем и проверяет её валидность. - При успешном обновлении сохранит пару и отправит обновлённую информацию. - """ - user_input = message.text.strip().upper() - exists = await get_valid_symbols(message.from_user.id, user_input) - - if not exists: - await message.answer("Введена некорректная торговая пара или такой пары нет в списке. Попробуйте снова.") - return - - await state.update_data(symbol=message.text) - await message.answer('Пара была успешно обновлена') - await rq.update_symbol(message.from_user.id, user_input) - await start_bybit_trade_message(message) - - await state.clear() - - -@router_functions_bybit_trade.callback_query(F.data == "clb_start_chatbot_trading") -async def start_trading_process(callback: CallbackQuery) -> None: - """ - Запускает торговый цикл в выбранном режиме Long/Short. - Проверяет API-ключи, режим торговли, маржинальный режим и открытые позиции, - затем запускает торговый цикл с задержкой или без неё. - """ - await callback.answer() - tg_id = callback.from_user.id - message = callback.message - data_main_stgs = await rq.get_user_main_settings(tg_id) - # data_risk_stgs = await rq.get_user_risk_management_settings(tg_id) - # client = await get_bybit_client(tg_id) - symbol = await rq.get_symbol(tg_id) - margin_mode = data_main_stgs.get('margin_type', 'Isolated') - trading_mode = data_main_stgs.get('trading_mode') - starting_quantity = safe_float(data_main_stgs.get('starting_quantity')) - switch_state = data_main_stgs.get("switch_state", "По направлению") - # martingale_factor = safe_float(data_main_stgs.get('martingale_factor')) - # max_martingale_steps = int(data_main_stgs.get("maximal_quantity", 0)) - # commission_fee = data_risk_stgs.get("commission_fee") - # fee_info = client.get_fee_rates(category='linear', symbol=symbol) - - - if trading_mode == 'Switch': - if switch_state == "По направлению": - side = data_main_stgs.get("last_side") - else: - side = data_main_stgs.get("last_side") - if side.lower() == "buy": - side = "Sell" - else: - side = "Buy" - else: - if trading_mode == 'Long': - side = 'Buy' - elif trading_mode == 'Short': - side = 'Sell' - else: - await message.answer(f"Режим торговли '{trading_mode}' пока не поддерживается.", - reply_markup=inline_markup.back_to_main) - return - - # if commission_fee == "Да": - # commission_fee_percent = safe_float(fee_info['result']['list'][0]['takerFeeRate']) - # else: - # commission_fee_percent = 0.0 - - # total_budget = await calculate_total_budget( - # starting_quantity=starting_quantity, - # martingale_factor=martingale_factor, - # max_steps=max_martingale_steps, - # commission_fee_percent=commission_fee_percent, - # ) - - # balance = await balance_g.get_balance(tg_id, message) - # if safe_float(balance) < total_budget: - # logger.error( - # f"Недостаточно средств для серии из {max_martingale_steps} шагов с текущими параметрами. " - # f"Требуемый бюджет: {total_budget:.2f} USDT, доступно: {balance} USDT." - # ) - # await message.answer( - # f"Недостаточно средств для серии из {max_martingale_steps} шагов с текущими параметрами. " - # f"Требуемый бюджет: {total_budget:.2f} USDT, доступно: {balance} USDT.", - # reply_markup=inline_markup.back_to_main, - # ) - # return - - await message.answer("Начинаю торговлю с использованием текущих настроек...") - await rq.update_trigger(tg_id=tg_id, trigger="Автоматический") - - timer_data = await rq.get_user_timer(tg_id) - if isinstance(timer_data, dict): - timer_minute = timer_data.get('timer_minutes', 0) - else: - timer_minute = timer_data or 0 - - if timer_minute > 0: - await message.answer(f"Торговля начнётся через {timer_minute} мин.", reply_markup=inline_markup.cancel_start) - - async def delay_start(): - try: - await asyncio.sleep(timer_minute * 60) - await open_position(tg_id, message, side, margin_mode, symbol=symbol, quantity=starting_quantity) - await rq.update_user_timer(tg_id, minutes=0) - except asyncio.exceptions.CancelledError: - logger.exception(f"Торговый цикл для пользователя {tg_id} был отменён.") - raise - - task = asyncio.create_task(delay_start()) - user_trade_tasks[tg_id] = task - else: - await open_position(tg_id, message, side, margin_mode, symbol=symbol, quantity=starting_quantity) - - -@router_functions_bybit_trade.callback_query(F.data == "clb_cancel_start") -async def cancel_start_trading(callback: CallbackQuery): - tg_id = callback.from_user.id - task = user_trade_tasks.get(tg_id) - if task and not task.done(): - task.cancel() - try: - await task - except asyncio.CancelledError: - pass - user_trade_tasks.pop(tg_id, None) - await rq.update_user_timer(tg_id, minutes=0) - await rq.update_trigger(tg_id, "Ручной") - await callback.message.answer("Запуск торговли отменён.", reply_markup=inline_markup.back_to_main) - await callback.message.edit_reply_markup(reply_markup=None) - else: - await callback.answer("Нет запланированной задачи запуска.", show_alert=True) - - -@router_functions_bybit_trade.callback_query(F.data == "clb_my_deals") -async def show_my_trades(callback: CallbackQuery) -> None: - """ - Отображает пользователю выбор типа сделки по текущей торговой паре. - """ - await callback.answer() - try: - await callback.message.answer("Выберите тип сделки:", - reply_markup=inline_markup.my_deals_select_markup) - except Exception as e: - logger.error("Произошла ошибка при выборе типа сделки: %s", e) - - -@router_functions_bybit_trade.callback_query(F.data == "clb_open_deals") -async def show_my_trades_callback(callback: CallbackQuery): - """ - Показывает открытые позиции пользователя. - """ - await callback.answer() - - try: - await get_active_positions(callback.from_user.id, message=callback.message) - except Exception as e: - logger.error("Произошла ошибка при выборе сделки: %s", e) - await callback.message.answer("Произошла ошибка при выборе сделки", reply_markup=inline_markup.back_to_main) - - -@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("show_deal_")) -async def show_deal_callback(callback_query: CallbackQuery) -> None: - """ - Показывает сделку пользователя по символу. - """ - await callback_query.answer() - try: - symbol = callback_query.data[len("show_deal_"):] - await rq.update_symbol(callback_query.from_user.id, symbol) - tg_id = callback_query.from_user.id - await get_active_positions_by_symbol(tg_id, symbol, message=callback_query.message) - except Exception as e: - logger.error("Произошла ошибка при выборе сделки: %s", e) - await callback_query.message.answer("Произошла ошибка при выборе сделки", - reply_markup=inline_markup.back_to_main) - - -@router_functions_bybit_trade.callback_query(F.data == "clb_open_orders") -async def show_my_orders_callback(callback: CallbackQuery) -> None: - """ - Показывает открытые позиции пользователя по символу. - """ - await callback.answer() - - try: - await get_active_orders(callback.from_user.id, message=callback.message) - except Exception as e: - logger.error("Произошла ошибка при выборе ордера: %s", e) - await callback.message.answer("Произошла ошибка при выборе ордера", reply_markup=inline_markup.back_to_main) - - -@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("show_limit_")) -async def show_limit_callback(callback_query: CallbackQuery) -> None: - """ - Показывает сделку пользователя по символу. - """ - await callback_query.answer() - try: - symbol = callback_query.data[len("show_limit_"):] - await rq.update_symbol(callback_query.from_user.id, symbol) - tg_id = callback_query.from_user.id - await get_active_orders_by_symbol(tg_id, symbol, message=callback_query.message) - except Exception as e: - logger.error("Произошла ошибка при выборе сделки: %s", e) - await callback_query.message.answer("Произошла ошибка при выборе сделки", - reply_markup=inline_markup.back_to_main) - - -@router_functions_bybit_trade.callback_query(F.data == "clb_set_tp_sl") -async def set_tp_sl(callback: CallbackQuery, state: FSMContext) -> None: - """ - Запускает процесс установки Take Profit и Stop Loss. - """ - await callback.answer() - await state.set_state(SetTP_SL_State.waiting_for_take_profit) - await callback.message.answer("Введите значение Take Profit (в цене, например 26000.5):", - reply_markup=inline_markup.cancel) - - -@router_functions_bybit_trade.message(SetTP_SL_State.waiting_for_take_profit) -async def process_take_profit(message: Message, state: FSMContext) -> None: - """ - Обрабатывает ввод значения Take Profit и запрашивает Stop Loss. - """ - try: - tp = float(message.text.strip()) - if tp <= 0: - await message.answer("Значение Take Profit должно быть положительным числом. Попробуйте снова.", - reply_markup=inline_markup.cancel) - return - except ValueError: - await message.answer("Некорректный ввод. Пожалуйста, введите число для Take Profit.", - reply_markup=inline_markup.cancel) - return - - await state.update_data(take_profit=tp) - await state.set_state(SetTP_SL_State.waiting_for_stop_loss) - await message.answer("Введите значение Stop Loss (в цене, например 24500.3):", reply_markup=inline_markup.cancel) - - -@router_functions_bybit_trade.message(SetTP_SL_State.waiting_for_stop_loss) -async def process_stop_loss(message: Message, state: FSMContext) -> None: - """ - Обрабатывает ввод значения Stop Loss и завершает процесс установки TP/SL. - """ - try: - sl = float(message.text.strip()) - if sl <= 0: - await message.answer("Значение Stop Loss должно быть положительным числом. Попробуйте снова.", - reply_markup=inline_markup.cancel) - return - except ValueError: - await message.answer("Некорректный ввод. Пожалуйста, введите число для Stop Loss.", - reply_markup=inline_markup.cancel) - return - - data = await state.get_data() - tp = data.get("take_profit") - - if tp is None: - await message.answer("Ошибка, не найдено значение Take Profit. Попробуйте снова.") - await state.clear() - return - - tg_id = message.from_user.id - - await set_take_profit_stop_loss(tg_id, message, take_profit_price=tp, stop_loss_price=sl) - - await state.clear() - - -@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_deal:")) -async def close_trade_callback(callback: CallbackQuery) -> None: - """ - Закрывает сделку пользователя по символу. - """ - symbol = callback.data.split(':')[1] - tg_id = callback.from_user.id - - result = await close_user_trade(tg_id, symbol) - - if result: - logger.info(f"Сделка {symbol} успешно закрыта.") - else: - logger.error(f"Не удалось закрыть сделку {symbol}.") - await callback.message.answer(f"Не удалось закрыть сделку {symbol}.") - - await callback.answer() - - -@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_limit:")) -async def close_trade_callback(callback: CallbackQuery) -> None: - """ - Закрывает ордера пользователя по символу. - """ - symbol = callback.data.split(':')[1] - tg_id = callback.from_user.id - - result = await cancel_all_tp_sl_orders(tg_id, symbol) - - if result: - logger.info(f"Ордер {result} успешно закрыт.") - else: - await callback.message.answer(f"Не удалось закрыть ордер {result}.") - - await callback.answer() - - -@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_deal_by_timer:")) -async def ask_close_delay(callback: CallbackQuery, state: FSMContext) -> None: - """ - Запускает диалог с пользователем для задания задержки перед закрытием сделки. - """ - symbol = callback.data.split(":")[1] - await state.update_data(symbol=symbol) - await state.set_state(CloseTradeTimerState.waiting_for_delay) - await callback.message.answer("Введите задержку в минутах до закрытия сделки:", - reply_markup=inline_markup.cancel) - await callback.answer() - - -@router_functions_bybit_trade.message(CloseTradeTimerState.waiting_for_delay) -async def process_close_delay(message: Message, state: FSMContext) -> None: - """ - Обрабатывает ввод закрытия сделки с задержкой. - """ - try: - delay_minutes = int(message.text.strip()) - if delay_minutes <= 0: - await message.answer("Введите положительное число.") - return - except ValueError: - await message.answer("Некорректный ввод. Введите число в минутах.") - return - - data = await state.get_data() - symbol = data.get("symbol") - - delay = delay_minutes * 60 - await message.answer(f"Закрытие сделки {symbol} запланировано через {delay_minutes} мин.", - reply_markup=inline_markup.back_to_main) - await close_trade_after_delay(message.from_user.id, message, symbol, delay) - await state.clear() - - -@router_functions_bybit_trade.callback_query(F.data == "clb_change_martingale_reset") -async def reset_martingale(callback: CallbackQuery) -> None: - """ - Сбрасывает шаги мартингейла пользователя. - """ - tg_id = callback.from_user.id - await rq.update_martingale_step(tg_id, 1) - await callback.answer("Сброс шагов выполнен.") - await main_settings_message(tg_id, callback.message) - - -@router_functions_bybit_trade.callback_query(F.data == "clb_stop_trading") -async def confirm_stop_trading(callback: CallbackQuery): - """ - Предлагает пользователю выбрать вариант подтверждение остановки торговли. - """ - await callback.message.answer( - "Выберите вариант остановки торговли:", reply_markup=inline_markup.stop_choice_markup - ) - await callback.answer() - - -@router_functions_bybit_trade.callback_query(F.data == "stop_immediately") -async def stop_immediately(callback: CallbackQuery): - """ - Останавливает торговлю немедленно. - """ - tg_id = callback.from_user.id - symbol = await rq.get_symbol(tg_id) - - await close_user_trade(tg_id, symbol) - await rq.update_trigger(tg_id, "Ручной") - await rq.update_martingale_step(tg_id, 1) - - await callback.message.answer("Торговля остановлена.", reply_markup=inline_markup.back_to_main) - await callback.answer() - - -@router_functions_bybit_trade.callback_query(F.data == "stop_with_timer") -async def stop_with_timer_start(callback: CallbackQuery, state: FSMContext): - """ - Запускает диалог с пользователем для задания задержки до остановки торговли. - """ - - await state.set_state(CloseTradeTimerState.waiting_for_trade) - await callback.message.answer("Введите задержку в минутах до остановки торговли:", - reply_markup=inline_markup.cancel) - await callback.answer() - - -@router_functions_bybit_trade.message(CloseTradeTimerState.waiting_for_trade) -async def process_stop_delay(message: Message, state: FSMContext): - """ - Обрабатывает ввод задержки и запускает задачу остановки торговли с задержкой. - """ - try: - delay_minutes = int(message.text.strip()) - if delay_minutes <= 0: - await message.answer("Введите положительное число минут.") - return - except ValueError: - await message.answer("Некорректный формат. Введите число в минутах.") - return - - tg_id = message.from_user.id - delay_seconds = delay_minutes * 60 - - await message.answer(f"Торговля будет остановлена через {delay_minutes} минут.", - reply_markup=inline_markup.back_to_main) - await asyncio.sleep(delay_seconds) - - symbol = await rq.get_symbol(tg_id) - - await close_user_trade(tg_id, symbol) - await rq.update_trigger(tg_id, "Ручной") - await rq.update_martingale_step(tg_id, 1) - await message.answer("Торговля остановлена.", reply_markup=inline_markup.back_to_main) - - await state.clear() - - -@router_functions_bybit_trade.callback_query(F.data == "clb_cancel") -async def cancel(callback: CallbackQuery, state: FSMContext) -> None: - """ - Отменяет текущее состояние FSM и сообщает пользователю об отмене. - """ - try: - await state.clear() - await callback.message.answer("Отменено!", reply_markup=inline_markup.back_to_main) - await callback.answer() - except Exception as e: - logger.error("Ошибка при обработке отмены: %s", e) diff --git a/app/services/Bybit/functions/get_valid_symbol.py b/app/services/Bybit/functions/get_valid_symbol.py deleted file mode 100644 index 1a8d830..0000000 --- a/app/services/Bybit/functions/get_valid_symbol.py +++ /dev/null @@ -1,40 +0,0 @@ -import logging.config -from pybit.unified_trading import HTTP -import app.telegram.database.requests as rq -from logger_helper.logger_helper import LOGGING_CONFIG - -logging.config.dictConfig(LOGGING_CONFIG) -logger = logging.getLogger("get_valid_symbol") - - -async def get_valid_symbols(user_id: int, symbol: str) -> bool: - """ - Проверяет существование торговой пары на Bybit в категории 'linear'. - - Эта функция получает API-ключи пользователя из базы данных и - с помощью Bybit API проверяет наличие данного символа в списке - торговых инструментов категории 'linear'. - - Args: - user_id (int): Идентификатор пользователя Telegram. - symbol (str): Торговый символ (валютная пара), например "BTCUSDT". - - Returns: - bool: Возвращает True, если торговая пара существует, иначе False. - - Raises: - Исключения подавляются и вызывается False, если произошла ошибка запроса к API. - """ - api_key = await rq.get_bybit_api_key(user_id) - secret_key = await rq.get_bybit_secret_key(user_id) - client = HTTP(api_key=api_key, api_secret=secret_key) - - try: - resp = client.get_instruments_info(category='linear', symbol=symbol) - # Проверка наличия результата и непустого списка инструментов - if resp.get('retCode') == 0 and resp.get('result') and resp['result'].get('list'): - return len(resp['result']['list']) > 0 - return False - except Exception as e: - logging.error(f"Ошибка при получении списка инструментов: {e}") - return False diff --git a/app/services/Bybit/functions/min_qty.py b/app/services/Bybit/functions/min_qty.py deleted file mode 100644 index 51faa53..0000000 --- a/app/services/Bybit/functions/min_qty.py +++ /dev/null @@ -1,52 +0,0 @@ -import math -import logging.config -from app.services.Bybit.functions.price_symbol import get_price -import app.telegram.database.requests as rq -from logger_helper.logger_helper import LOGGING_CONFIG -from pybit.unified_trading import HTTP - -logging.config.dictConfig(LOGGING_CONFIG) -logger = logging.getLogger("min_qty") - -def round_up_qty(value: float, step: float) -> float: - """ - Округление value вверх до ближайшего кратного step. - """ - return math.ceil(value / step) * step - -async def get_min_qty(tg_id: int) -> float: - """ - Получает минимальный объем (количество) ордера для символа пользователя на Bybit, - округленное с учетом шага количества qtyStep. - - :param tg_id: int - идентификатор пользователя Telegram - :return: float - минимальное количество лота для ордера - """ - api_key = await rq.get_bybit_api_key(tg_id) - secret_key = await rq.get_bybit_secret_key(tg_id) - symbol = await rq.get_symbol(tg_id) - - client = HTTP(api_key=api_key, api_secret=secret_key) - - price = await get_price(tg_id, symbol=symbol) - - response = client.get_instruments_info(symbol=symbol, category='linear') - - instrument = response['result'][0] - lot_size_filter = instrument.get('lotSizeFilter', {}) - - min_order_qty = float(lot_size_filter.get('minOrderQty', 0)) - min_notional_value = float(lot_size_filter.get('minNotionalValue', 0)) - qty_step = float(lot_size_filter.get('qtyStep', 1)) - - calculated_qty = (5 / price) * 1.1 - - min_qty = max(min_order_qty, calculated_qty) - - min_qty_rounded = round_up_qty(min_qty, qty_step) - - logger.debug(f"tg_id={tg_id}: price={price}, min_order_qty={min_order_qty}, " - f"min_notional_value={min_notional_value}, qty_step={qty_step}, " - f"calculated_qty={calculated_qty}, min_qty_rounded={min_qty_rounded}") - - return min_qty_rounded diff --git a/app/services/Bybit/functions/price_symbol.py b/app/services/Bybit/functions/price_symbol.py deleted file mode 100644 index d86737f..0000000 --- a/app/services/Bybit/functions/price_symbol.py +++ /dev/null @@ -1,32 +0,0 @@ -import app.telegram.database.requests as rq -import logging.config -from logger_helper.logger_helper import LOGGING_CONFIG -from pybit import exceptions -from pybit.unified_trading import HTTP - -logging.config.dictConfig(LOGGING_CONFIG) -logger = logging.getLogger("price_symbol") - - -async def get_price(tg_id: int, symbol: str) -> float: - """ - Асинхронно получает текущую цену символа пользователя на Bybit. - - :param tg_id: int - ID пользователя Telegram - :return: float - текущая цена символа - """ - api_key = await rq.get_bybit_api_key(tg_id) - secret_key = await rq.get_bybit_secret_key(tg_id) - - client = HTTP( - api_key=api_key, - api_secret=secret_key - ) - - try: - price = float( - client.get_tickers(category='linear', symbol=symbol).get('result').get('list')[0].get('ask1Price')) - return price - except exceptions.InvalidRequestError as e: - logger.error(f"Ошибка при получении цены: {e}") - return 1.0 diff --git a/app/states/States.py b/app/states/States.py deleted file mode 100644 index ba211fa..0000000 --- a/app/states/States.py +++ /dev/null @@ -1,72 +0,0 @@ -from aiogram.fsm.state import State, StatesGroup - - -class state_update_symbol(StatesGroup): - """FSM состояние для обновления торгового символа.""" - symbol = State() - - -class state_update_entry_type(StatesGroup): - """FSM состояние для обновления типа входа.""" - entry_type = State() - - -class TradeSetup(StatesGroup): - """FSM состояния для настройки торговли с таймером и процентом.""" - waiting_for_timer = State() - waiting_for_positive_percent = State() - - -class state_limit_price(StatesGroup): - """FSM состояние для установки лимита.""" - price = State() - -class state_trigger_price(StatesGroup): - """FSM состояние для установки лимита.""" - price = State() - -class CloseTradeTimerState(StatesGroup): - """FSM состояние ожидания задержки перед закрытием сделки.""" - waiting_for_delay = State() - waiting_for_trade = State() - - -class SetTP_SL_State(StatesGroup): - """FSM состояние для установки TP и SL.""" - waiting_for_take_profit = State() - waiting_for_stop_loss = State() - - -class update_risk_management_settings(StatesGroup): - """FSM состояние для обновления настроек управления рисками.""" - price_profit = State() - price_loss = State() - max_risk_deal = State() - commission_fee = State() - - -class state_reg_bybit_api(StatesGroup): - """FSM состояние для регистрации API Bybit.""" - api_key = State() - secret_key = State() - - -class condition_settings(StatesGroup): - """FSM состояние для настройки условий трейдинга.""" - trigger = State() - timer = State() - volatilty = State() - volume = State() - integration = State() - use_tv_signal = State() - - -class update_main_settings(StatesGroup): - """FSM состояние для обновления основных настройок.""" - trading_mode = State() - size_leverage = State() - margin_type = State() - martingale_factor = State() - starting_quantity = State() - maximal_quantity = State() - switch_mode_enabled = State() \ No newline at end of file diff --git a/app/telegram/Keyboards/inline_keyboards.py b/app/telegram/Keyboards/inline_keyboards.py deleted file mode 100644 index 023c36a..0000000 --- a/app/telegram/Keyboards/inline_keyboards.py +++ /dev/null @@ -1,224 +0,0 @@ -from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup -from aiogram.utils.keyboard import InlineKeyboardBuilder - -start_markup = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="🔥 Начать торговлю", callback_data="clb_start_chatbot_message")] -]) - -settings_markup = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Запуск", callback_data='clb_start_trading')] -]) - -cancel_start = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Отменить запуск", callback_data="clb_cancel_start")] -]) - -back_btn_list_settings = [InlineKeyboardButton(text="Назад", - callback_data='clb_back_to_special_settings_message')] # Кнопка для возврата к списку каталога настроек -back_btn_list_settings_markup = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Назад", - callback_data='clb_back_to_special_settings_message')]]) # Клавиатура для возврата к списку каталога настроек -back_btn_to_main = [InlineKeyboardButton(text="На главную", callback_data='clb_back_to_main')] - -back_btn_profile = [InlineKeyboardButton(text="Назад", callback_data='clb_start_chatbot_message')] - -connect_bybit_api_message = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Подключить Bybit", callback_data='clb_new_user_connect_bybit_api_message')] -]) - -special_settings_markup = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Основные настройки", callback_data='clb_change_main_settings'), - InlineKeyboardButton(text="Риск-менеджмент", callback_data='clb_change_risk_management_settings')], - - [InlineKeyboardButton(text="Условия запуска", callback_data='clb_change_condition_settings')], - # InlineKeyboardButton(text="Дополнительные параметры", callback_data='clb_change_additional_settings')], - [InlineKeyboardButton(text="Подключить Bybit", callback_data='clb_new_user_connect_bybit_api_message')], - back_btn_to_main -]) - -connect_bybit_api_markup = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Подключить Bybit", callback_data='clb_new_user_connect_bybit_api')] -]) - -trading_markup = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Настройки", callback_data='clb_settings_message')], - [InlineKeyboardButton(text="Мои сделки", callback_data='clb_my_deals')], - [InlineKeyboardButton(text="Указать торговую пару", callback_data='clb_update_trading_pair')], - [InlineKeyboardButton(text="Начать торговать", callback_data='clb_start_chatbot_trading')], - [InlineKeyboardButton(text="Остановить торговлю", callback_data='clb_stop_trading')], -]) - -start_trading_markup = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Начать торговлю", callback_data="clb_start_chatbot_trading")], - [InlineKeyboardButton(text="На главную", callback_data='back_to_main')], -]) - -cancel = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Отменить", callback_data="clb_cancel")] -]) - -entry_order_type_markup = InlineKeyboardMarkup( - inline_keyboard=[ - [ - InlineKeyboardButton(text="Маркет", callback_data="entry_order_type:Market"), - InlineKeyboardButton(text="Лимит", callback_data="entry_order_type:Limit"), - ], back_btn_to_main - ] -) - -back_to_main = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="На главную", callback_data='back_to_main')], -]) - -main_settings_markup = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='Режим торговли', callback_data='clb_change_trading_mode'), - InlineKeyboardButton(text='Состояние свитча', callback_data='clb_change_switch_state'), - InlineKeyboardButton(text='Тип маржи', callback_data='clb_change_margin_type')], - - [InlineKeyboardButton(text='Размер кредитного плеча', callback_data='clb_change_size_leverage'), - InlineKeyboardButton(text='Ставка', callback_data='clb_change_starting_quantity')], - - [InlineKeyboardButton(text='Коэффициент Мартингейла', callback_data='clb_change_martingale_factor'), - InlineKeyboardButton(text='Сбросить шаги Мартингейла', callback_data='clb_change_martingale_reset')], - [InlineKeyboardButton(text='Максимальное кол-во ставок', callback_data='clb_change_maximum_quantity')], - - back_btn_list_settings, - back_btn_to_main -]) - -risk_management_settings_markup = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='Изм. цены прибыли', callback_data='clb_change_price_profit'), - InlineKeyboardButton(text='Изм. цены убытков', callback_data='clb_change_price_loss')], - - [InlineKeyboardButton(text='Макс. риск на сделку', callback_data='clb_change_max_risk_deal')], - [InlineKeyboardButton(text='Учитывать комиссию биржи (Да/Нет)', callback_data='commission_fee')], - - back_btn_list_settings, - back_btn_to_main -]) - -condition_settings_markup = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='Таймер', callback_data='clb_change_timer'), - InlineKeyboardButton(text='Тип позиции', callback_data='clb_update_entry_type')], - [InlineKeyboardButton(text='Триггер цена', callback_data='clb_change_trigger_price'), - InlineKeyboardButton(text='Лимит цена', callback_data='clb_change_limit_price')], - # - # [InlineKeyboardButton(text='Фильтр волатильности', callback_data='clb_change_filter_volatility'), - # InlineKeyboardButton(text='Внешние сигналы', callback_data='clb_change_external_cues')], - # - # [InlineKeyboardButton(text='Сигналы TradingView', callback_data='clb_change_tradingview_cues'), - # InlineKeyboardButton(text='Webhook URL', callback_data='clb_change_webhook')], - # - # [InlineKeyboardButton(text='AI - аналитика', callback_data='clb_change_ai_analytics')], - - back_btn_list_settings, - back_btn_to_main -]) - -back_to_condition_settings = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='Назад', callback_data='clb_change_condition_settings')], - back_btn_to_main -]) - -additional_settings_markup = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='Сохранить шаблон', callback_data='clb_change_save_pattern'), - InlineKeyboardButton(text='Автозапуск', callback_data='clb_change_auto_start')], - - [InlineKeyboardButton(text='Уведомления', callback_data='clb_change_notifications')], - - back_btn_list_settings, - back_btn_to_main -]) - -trading_mode_markup = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Лонг", callback_data="trade_mode_long"), - InlineKeyboardButton(text="Шорт", callback_data="trade_mode_short"), - InlineKeyboardButton(text="Свитч", callback_data="trade_mode_switch")], - # InlineKeyboardButton(text="Смарт", callback_data="trade_mode_smart")], - - back_btn_list_settings, - back_btn_to_main -]) - -margin_type_markup = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Изолированный", callback_data="margin_type_isolated"), - InlineKeyboardButton(text="Кросс", callback_data="margin_type_cross")], - - back_btn_list_settings -]) - -trigger_markup = InlineKeyboardMarkup(inline_keyboard=[ # ИЗМЕНИТЬ НА INLINE - [InlineKeyboardButton(text='Ручной', callback_data="clb_trigger_manual")], - # [InlineKeyboardButton(text='TradingView', callback_data="clb_trigger_tradingview")], - [InlineKeyboardButton(text="Автоматический", callback_data="clb_trigger_auto")], - back_btn_list_settings, - back_btn_to_main -]) - -buttons_yes_no_markup = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='Да', callback_data="clb_yes"), - InlineKeyboardButton(text='Нет', callback_data="clb_no")], -]) - -buttons_on_off_markup = InlineKeyboardMarkup(inline_keyboard=[ # ИЗМЕНИТЬ НА INLINE - [InlineKeyboardButton(text='Включить', callback_data="clb_on"), - InlineKeyboardButton(text='Выключить', callback_data="clb_off")] -]) - -my_deals_select_markup = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='Позиции', callback_data="clb_open_deals"), - InlineKeyboardButton(text='Ордера', callback_data="clb_open_orders")], - back_btn_to_main -]) - - -def create_trades_inline_keyboard(trades): - builder = InlineKeyboardBuilder() - for trade in trades: - builder.button(text=trade, callback_data=f"show_deal_{trade}") - builder.adjust(2) - return builder.as_markup() - - -def create_trades_inline_keyboard_limits(trades): - builder = InlineKeyboardBuilder() - for trade in trades: - builder.button(text=trade, callback_data=f"show_limit_{trade}") - builder.adjust(2) - return builder.as_markup() - - -def create_close_deal_markup(symbol: str) -> InlineKeyboardMarkup: - return InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Закрыть сделку", callback_data=f"close_deal:{symbol}")], - [InlineKeyboardButton(text="Закрыть по таймеру", callback_data=f"close_deal_by_timer:{symbol}")], - [InlineKeyboardButton(text="Установить TP/SL", callback_data="clb_set_tp_sl")], - back_btn_to_main - ]) - - -def create_close_limit_markup(symbol: str) -> InlineKeyboardMarkup: - return InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Закрыть ордер", callback_data=f"close_limit:{symbol}")], - back_btn_to_main - ]) - - -timer_markup = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Установить таймер", callback_data="clb_set_timer")], - [InlineKeyboardButton(text="Удалить таймер", callback_data="clb_delete_timer")], - back_btn_to_main -]) - -stop_choice_markup = InlineKeyboardMarkup( - inline_keyboard=[ - [ - InlineKeyboardButton(text="Остановить сразу", callback_data="stop_immediately"), - InlineKeyboardButton(text="Остановить по таймеру", callback_data="stop_with_timer"), - ] - ] -) - -switch_state_markup = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='По направлению', callback_data="clb_long_switch"), - InlineKeyboardButton(text='Против направления', callback_data="clb_short_switch")], -]) diff --git a/app/telegram/Keyboards/reply_keyboards.py b/app/telegram/Keyboards/reply_keyboards.py deleted file mode 100644 index df7c56c..0000000 --- a/app/telegram/Keyboards/reply_keyboards.py +++ /dev/null @@ -1,6 +0,0 @@ -from aiogram.types import ReplyKeyboardMarkup, KeyboardButton - -base_buttons_markup = ReplyKeyboardMarkup(keyboard=[ - [KeyboardButton(text="👤 Профиль")], - # [KeyboardButton(text="Настройки")] -], resize_keyboard=True, one_time_keyboard=False) \ No newline at end of file diff --git a/app/telegram/database/models.py b/app/telegram/database/models.py deleted file mode 100644 index 73c56cd..0000000 --- a/app/telegram/database/models.py +++ /dev/null @@ -1,316 +0,0 @@ -from datetime import datetime -import logging.config -from sqlalchemy.sql.sqltypes import DateTime, Numeric - -from sqlalchemy import BigInteger, Boolean, Integer, String, ForeignKey -from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column -from sqlalchemy.ext.asyncio import AsyncAttrs, async_sessionmaker, create_async_engine -from logger_helper.logger_helper import LOGGING_CONFIG -from sqlalchemy import select, insert - -logging.config.dictConfig(LOGGING_CONFIG) -logger = logging.getLogger("models") - -engine = create_async_engine(url='sqlite+aiosqlite:///data/db.sqlite3') - -async_session = async_sessionmaker(engine) - - -class Base(AsyncAttrs, DeclarativeBase): - """Базовый класс для declarative моделей SQLAlchemy с поддержкой async.""" - pass - - -class User_Telegram_Id(Base): - """ - Модель таблицы user_telegram_id. - - Хранит идентификаторы Telegram пользователей. - - Атрибуты: - id (int): Внутренний первичный ключ записи. - tg_id (int): Уникальный идентификатор пользователя Telegram. - """ - __tablename__ = 'user_telegram_id' - - id: Mapped[int] = mapped_column(primary_key=True) - - tg_id = mapped_column(BigInteger) - - -class User_Bybit_API(Base): - """ - Модель таблицы user_bybit_api. - - Хранит API ключи и секреты Bybit для каждого Telegram пользователя. - - Атрибуты: - id (int): Внутренний первичный ключ записи. - tg_id (int): Внешний ключ на Telegram пользователя (user_telegram_id.tg_id). - api_key (str): API ключ Bybit (уникальный для пользователя). - secret_key (str): Секретный ключ Bybit (уникальный для пользователя). - """ - __tablename__ = 'user_bybit_api' - - id: Mapped[int] = mapped_column(primary_key=True) - - tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True) - - api_key = mapped_column(String(18), unique=True, nullable=True) - secret_key = mapped_column(String(36), unique=True, nullable=True) - - -class User_Symbol(Base): - """ - Модель таблицы user_main_settings. - - Хранит основные настройки торговли для пользователя. - """ - __tablename__ = 'user_symbols' - - id: Mapped[int] = mapped_column(primary_key=True) - - tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True) - - symbol = mapped_column(String(18), default='PENGUUSDT') - - -class Trading_Mode(Base): - """ - Справочник доступных режимов торговли. - - Атрибуты: - id (int): Первичный ключ. - mode (str): Уникальный режим (например, 'Long', 'Short', 'Switch). - """ - __tablename__ = 'trading_modes' - - id: Mapped[int] = mapped_column(primary_key=True) - - mode = mapped_column(String(10), unique=True) - - -class Margin_type(Base): - """ - Справочник типов маржинальной торговли. - - Атрибуты: - id (int): Первичный ключ. - type (str): Тип маржи (например, 'Isolated', 'Cross'). - """ - __tablename__ = 'margin_types' - - id: Mapped[int] = mapped_column(primary_key=True) - - type = mapped_column(String(15), unique=True) - - -class Trigger(Base): - """ - Справочник триггеров для сделок. - - Атрибуты: - id (int): Первичный ключ. - """ - __tablename__ = 'triggers' - - id: Mapped[int] = mapped_column(primary_key=True) - - trigger_price = mapped_column(Integer(), default=0) - - -class User_Main_Settings(Base): - """ - Основные настройки пользователя для торговли. - - Атрибуты: - id (int): Первичный ключ. - tg_id (int): Внешний ключ на Telegram пользователя. - trading_mode (str): Режим торговли, FK на trading_modes.mode. - margin_type (str): Тип маржи, FK на margin_types.type. - size_leverage (int): Кредитное плечо. - starting_quantity (int): Начальный объем позиции. - martingale_factor (int): Коэффициент мартингейла. - martingale_step (int): Текущий шаг мартингейла. - maximal_quantity (int): Максимальное число шагов мартингейла. - entry_order_type (str): Тип ордера входа (Market/Limit). - limit_order_price (Optional[str]): Цена лимитного ордера, если есть. - last_side (str): Последняя сторона ордера. - """ - __tablename__ = 'user_main_settings' - - id: Mapped[int] = mapped_column(primary_key=True) - - tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True) - - trading_mode = mapped_column(ForeignKey("trading_modes.mode")) - margin_type = mapped_column(ForeignKey("margin_types.type")) - switch_state = mapped_column(String(10), default='По направлению') - size_leverage = mapped_column(Integer(), default=1) - starting_quantity = mapped_column(Integer(), default=1) - base_quantity = mapped_column(Integer(), default=1) - martingale_factor = mapped_column(Integer(), default=1) - martingale_step = mapped_column(Integer(), default=1) - maximal_quantity = mapped_column(Integer(), default=10) - entry_order_type = mapped_column(String(10), default='Market') - limit_order_price = mapped_column(Numeric(18, 15), nullable=True, default=0) - trigger_price = mapped_column(Numeric(18, 15), nullable=True, default=0) - last_side = mapped_column(String(10), default='Buy') - trading_start_stop = mapped_column(Integer(), default=0) - - -class User_Risk_Management_Settings(Base): - """ - Настройки управления рисками пользователя. - - Атрибуты: - id (int): Первичный ключ. - tg_id (int): Внешний ключ на Telegram пользователя. - price_profit (int): Процент прибыли для трейда. - price_loss (int): Процент убытка для трейда. - max_risk_deal (int): Максимально допустимый риск по сделке в процентах. - commission_fee (str): Учитывать ли комиссию в расчетах ("Да"/"Нет"). - """ - __tablename__ = 'user_risk_management_settings' - - id: Mapped[int] = mapped_column(primary_key=True) - - tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True) - - price_profit = mapped_column(Integer(), default=1) - price_loss = mapped_column(Integer(), default=1) - max_risk_deal = mapped_column(Integer(), default=100) - commission_fee = mapped_column(String(), default="Да") - - -class User_Condition_Settings(Base): - """ - Дополнительные пользовательские условия для торговли. - - Атрибуты: - id (int): Первичный ключ. - tg_id (int): Внешний ключ на Telegram пользователя. - trigger (str): Тип триггера, FK на triggers.trigger. - filter_time (str): Временной фильтр. - filter_volatility (bool): Фильтр по волатильности. - external_cues (bool): Внешние сигналы. - tradingview_cues (bool): Сигналы TradingView. - webhook (str): URL webhook. - ai_analytics (bool): Использование AI для аналитики. - """ - __tablename__ = 'user_condition_settings' - - id: Mapped[int] = mapped_column(primary_key=True) - - tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True) - - trigger = mapped_column(String(15), default='Автоматический') - filter_time = mapped_column(String(25), default='???') - filter_volatility = mapped_column(Boolean, default=False) - external_cues = mapped_column(Boolean, default=False) - tradingview_cues = mapped_column(Boolean, default=False) - webhook = mapped_column(String(40), default='') - ai_analytics = mapped_column(Boolean, default=False) - - -class User_Additional_Settings(Base): - """ - Прочие дополнительные настройки пользователя. - - Атрибуты: - id (int): Первичный ключ. - tg_id (int): Внешний ключ на Telegram пользователя. - pattern_save (bool): Сохранять ли шаблоны. - autostart (bool): Автоматический запуск. - notifications (bool): Получение уведомлений. - """ - __tablename__ = 'user_additional_settings' - - id: Mapped[int] = mapped_column(primary_key=True) - - tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True) - - pattern_save = mapped_column(Boolean, default=False) - autostart = mapped_column(Boolean, default=False) - notifications = mapped_column(Boolean, default=False) - - -class USER_DEALS(Base): - """ - Таблица сделок пользователя. - - Атрибуты: - id (int): Первичный ключ. - tg_id (int): Внешний ключ на Telegram пользователя. - symbol (str): Торговая пара. - side (str): Направление сделки (Buy/Sell). - open_price (int): Цена открытия. - positive_percent (int): Процент доходности. - """ - __tablename__ = 'user_deals' - - id: Mapped[int] = mapped_column(primary_key=True) - - tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True) - - symbol = mapped_column(String(18), default='PENGUUSDT') - side = mapped_column(String(10), nullable=False) - open_price = mapped_column(Integer(), nullable=False) - positive_percent = mapped_column(Integer(), nullable=False) - - -class UserTimer(Base): - """ - Таймер пользователя для отсроченного запуска сделок. - - Атрибуты: - id (int): Первичный ключ. - tg_id (int): Внешний ключ на Telegram пользователя. - timer_minutes (int): Количество минут таймера. - timer_start (datetime): Время начала таймера. - timer_end (Optional[datetime]): Время окончания таймера (если установлено). - """ - __tablename__ = 'user_timers' - - id: Mapped[int] = mapped_column(primary_key=True) - tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True) - timer_minutes = mapped_column(Integer, nullable=False, default=0) - timer_start = mapped_column(DateTime, default=datetime.utcnow) - timer_end = mapped_column(DateTime, nullable=True) - - -async def async_main(): - """ - Асинхронное создание всех таблиц и заполнение справочников начальными данными. - """ - async with engine.begin() as conn: - await conn.run_sync(Base.metadata.create_all) - - # Заполнение таблиц - modes = ['Long', 'Short', 'Switch', 'Smart'] - for mode in modes: - result = await conn.execute(select(Trading_Mode).where(Trading_Mode.mode == mode)) - if not result.first(): - logger.info("Заполение таблицы режима торговли") - await conn.execute(Trading_Mode.__table__.insert().values(mode=mode)) - - types = ['Isolated', 'Cross'] - for type in types: - result = await conn.execute(select(Margin_type).where(Margin_type.type == type)) - if not result.first(): - logger.info("Заполение таблицы типов маржи") - await conn.execute(Margin_type.__table__.insert().values(type=type)) - - last_side = ['Buy', 'Sell'] - for side in last_side: - result = await conn.execute(select(User_Main_Settings).where(User_Main_Settings.last_side == side)) - if not result.first(): - logger.info("Заполение таблицы последнего направления") - await conn.execute(User_Main_Settings.__table__.insert().values(last_side=side)) - - order_type = ['Limit', 'Market'] - for typ in order_type: - result = await conn.execute(select(User_Main_Settings).where(User_Main_Settings.entry_order_type == typ)) - if not result.first(): - logger.info("Заполение таблицы типов ордеров") - await conn.execute(User_Main_Settings.__table__.insert().values(entry_order_type=typ)) diff --git a/app/telegram/database/requests.py b/app/telegram/database/requests.py deleted file mode 100644 index f796e1a..0000000 --- a/app/telegram/database/requests.py +++ /dev/null @@ -1,621 +0,0 @@ -import logging.config - -from logger_helper.logger_helper import LOGGING_CONFIG -from datetime import datetime, timedelta -from typing import Any - -from app.telegram.database.models import ( - async_session, - User_Telegram_Id as UTi, - User_Main_Settings as UMS, - User_Bybit_API as UBA, - User_Symbol, - User_Risk_Management_Settings as URMS, - User_Condition_Settings as UCS, - User_Additional_Settings as UAS, - Trading_Mode, - Margin_type, - Trigger, - USER_DEALS, - UserTimer, -) - -from sqlalchemy import select, update - -logging.config.dictConfig(LOGGING_CONFIG) -logger = logging.getLogger("requests") - - -# --- Функции сохранения в БД --- - -async def save_tg_id_new_user(tg_id) -> None: - """ - Сохраняет Telegram ID нового пользователя в базу, если такого ещё нет. - - Args: - tg_id (int): Telegram ID пользователя. - """ - async with async_session() as session: - user = await session.scalar(select(UTi).where(UTi.tg_id == tg_id)) - - if not user: - session.add(UTi(tg_id=tg_id)) - - logger.info("Новый пользователь был добавлен в бд %s", tg_id) - - await session.commit() - - -async def set_new_user_bybit_api(tg_id) -> None: - """ - Создаёт запись API пользователя Bybit, если её ещё нет. - - Args: - tg_id (int): Telegram ID пользователя. - """ - async with async_session() as session: - user = await session.scalar(select(UBA).where(UBA.tg_id == tg_id)) - - if not user: - session.add(UBA(tg_id=tg_id)) - await session.commit() - - -async def set_new_user_symbol(tg_id) -> None: - """ - Создаёт запись торгового символа пользователя, если её нет. - - Args: - tg_id (int): Telegram ID пользователя. - """ - async with async_session() as session: - user = await session.scalar(select(User_Symbol).where(User_Symbol.tg_id == tg_id)) - - if not user: - session.add(User_Symbol(tg_id=tg_id)) - - logger.info(f"Symbol был успешно добавлен %s", tg_id) - - await session.commit() - - -async def set_new_user_default_main_settings(tg_id, trading_mode, margin_type) -> None: - """ - Создаёт основные настройки пользователя по умолчанию. - - Args: - tg_id (int): Telegram ID пользователя. - trading_mode (str): Режим торговли. - margin_type (str): Тип маржи. - """ - async with async_session() as session: - settings = await session.scalar(select(UMS).where(UMS.tg_id == tg_id)) - - if not settings: - session.add(UMS( - tg_id=tg_id, - trading_mode=trading_mode, - margin_type=margin_type, - )) - - logger.info("Основные настройки нового пользователя были заполнены%s", tg_id) - - await session.commit() - - -async def set_new_user_default_risk_management_settings(tg_id) -> None: - """ - Создаёт настройки риск-менеджмента по умолчанию. - - Args: - tg_id (int): Telegram ID пользователя. - """ - async with async_session() as session: - settings = await session.scalar(select(URMS).where(URMS.tg_id == tg_id)) - - if not settings: - session.add(URMS( - tg_id=tg_id - )) - - logger.info("Риск-Менеджмент настройки нового пользователя были заполнены %s", tg_id) - - await session.commit() - - -async def set_new_user_default_condition_settings(tg_id, trigger) -> None: - """ - Создаёт условные настройки по умолчанию. - - Args: - tg_id (int): Telegram ID пользователя. - trigger (Any): Значение триггера по умолчанию. - """ - async with async_session() as session: - settings = await session.scalar(select(UCS).where(UCS.tg_id == tg_id)) - - if not settings: - session.add(UCS( - tg_id=tg_id, - trigger=trigger - )) - - logger.info("Условные настройки нового пользователя были заполнены %s", tg_id) - - await session.commit() - - -async def set_new_user_default_additional_settings(tg_id) -> None: - """ - Создаёт дополнительные настройки по умолчанию. - - Args: - tg_id (int): Telegram ID пользователя. - """ - async with async_session() as session: - settings = await session.scalar(select(UAS).where(UAS.tg_id == tg_id)) - - if not settings: - session.add(UAS( - tg_id=tg_id, - )) - - logger.info("Дополнительные настройки нового пользователя были заполнены %s", tg_id) - - await session.commit() - - -# --- Функции получения данных из БД --- - -async def check_user(tg_id): - """ - Проверяет наличие пользователя в базе. - - Args: - tg_id (int): Telegram ID пользователя. - - Returns: - Optional[UTi]: Пользователь или None. - """ - async with async_session() as session: - user = await session.scalar(select(UTi).where(UTi.tg_id == tg_id)) - return user - - -async def get_bybit_api_key(tg_id): - """Получить API ключ Bybit пользователя.""" - async with async_session() as session: - api_key = await session.scalar(select(UBA.api_key).where(UBA.tg_id == tg_id)) - return api_key - - -async def get_bybit_secret_key(tg_id): - """Получить секретный ключ Bybit пользователя.""" - async with async_session() as session: - secret_key = await session.scalar(select(UBA.secret_key).where(UBA.tg_id == tg_id)) - return secret_key - - -async def get_symbol(tg_id): - """Получить символ пользователя.""" - async with async_session() as session: - symbol = await session.scalar(select(User_Symbol.symbol).where(User_Symbol.tg_id == tg_id)) - return symbol - - -async def get_user_trades(tg_id): - """Получить сделки пользователя.""" - async with async_session() as session: - query = select(USER_DEALS.symbol, USER_DEALS.side).where(USER_DEALS.tg_id == tg_id) - result = await session.execute(query) - trades = result.all() - return trades - - -async def get_entry_order_type(tg_id: object) -> str | None | Any: - """Получить тип входного ордера пользователя.""" - async with async_session() as session: - order_type = await session.scalar( - select(UMS.entry_order_type).where(UMS.tg_id == tg_id) - ) - # Если в базе не установлен тип — возвращаем значение по умолчанию - return order_type or 'Market' - - -# --- Функции обновления данных --- - -async def update_user_trades(tg_id, **kwargs): - """Обновить сделки пользователя.""" - async with async_session() as session: - query = update(USER_DEALS).where(USER_DEALS.tg_id == tg_id).values(**kwargs) - await session.execute(query) - await session.commit() - - -async def update_symbol(tg_id: int, symbol: str) -> None: - """Обновить торговый символ пользователя.""" - async with async_session() as session: - await session.execute(update(User_Symbol).where(User_Symbol.tg_id == tg_id).values(symbol=symbol)) - await session.commit() - - -async def upsert_api_keys(tg_id: int, api_key: str, secret_key: str) -> None: - """Обновить API ключ пользователя.""" - async with async_session() as session: - result = await session.execute(select(UBA).where(UBA.tg_id == tg_id)) - user = result.scalars().first() - if user: - if api_key is not None: - user.api_key = api_key - if secret_key is not None: - user.secret_key = secret_key - logger.info(f"Обновлены ключи для пользователя {tg_id}") - else: - new_user = UBA(tg_id=tg_id, api_key=api_key, secret_key=secret_key) - session.add(new_user) - logger.info(f"Добавлен новый пользователь {tg_id} с ключами") - await session.commit() - - -# --- Более мелкие обновления и запросы по настройкам --- - -async def update_trade_mode_user(tg_id, trading_mode) -> None: - """Обновить режим торговли пользователя.""" - async with async_session() as session: - mode = await session.scalar(select(Trading_Mode.mode).where(Trading_Mode.mode == trading_mode)) - - if mode: - logger.info("Изменён торговый режим для пользователя %s", tg_id) - await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(trading_mode=mode)) - - await session.commit() - - -async def delete_user_trade(tg_id: int, symbol: str): - """Удалить сделку пользователя.""" - async with async_session() as session: - await session.execute( - USER_DEALS.__table__.delete().where( - (USER_DEALS.tg_id == tg_id) & (USER_DEALS.symbol == symbol) - ) - ) - await session.commit() - - -async def get_for_registration_trading_mode(): - """Получить режим торговли по умолчанию.""" - async with async_session() as session: - mode = await session.scalar(select(Trading_Mode.mode).where(Trading_Mode.id == 1)) - return mode - - -async def get_for_registration_margin_type(): - """Получить тип маржи по умолчанию.""" - async with async_session() as session: - type = await session.scalar(select(Margin_type.type).where(Margin_type.id == 1)) - return type - - -async def get_for_registration_trigger(tg_id): - """Получить триггер по умолчанию.""" - async with async_session() as session: - trigger = await session.scalar(select(UCS.trigger).where(tg_id == tg_id)) - return trigger - - -async def get_user_main_settings(tg_id): - """Получить основные настройки пользователя.""" - async with async_session() as session: - user = await session.scalar(select(UMS).where(UMS.tg_id == tg_id)) - if user: - data = { - 'trading_mode': user.trading_mode, - 'margin_type': user.margin_type, - 'switch_state': user.switch_state, - 'size_leverage': user.size_leverage, - 'starting_quantity': user.starting_quantity, - 'martingale_factor': user.martingale_factor, - 'maximal_quantity': user.maximal_quantity, - 'entry_order_type': user.entry_order_type, - 'limit_order_price': user.limit_order_price, - 'trigger_price': user.trigger_price, - 'martingale_step': user.martingale_step, - 'last_side': user.last_side, - 'trading_start_stop': user.trading_start_stop, - 'base_quantity': user.base_quantity, - } - return data - - -async def get_user_risk_management_settings(tg_id): - """Получить риск-менеджмента настройки пользователя.""" - async with async_session() as session: - user = await session.scalar(select(URMS).where(URMS.tg_id == tg_id)) - - if user: - logger.info("Получение риск-менеджмента настроек пользователя %s", tg_id) - - price_profit = await session.scalar(select(URMS.price_profit).where(URMS.tg_id == tg_id)) - price_loss = await session.scalar(select(URMS.price_loss).where(URMS.tg_id == tg_id)) - max_risk_deal = await session.scalar(select(URMS.max_risk_deal).where(URMS.tg_id == tg_id)) - commission_fee = await session.scalar(select(URMS.commission_fee).where(URMS.tg_id == tg_id)) - - data = { - 'price_profit': price_profit, - 'price_loss': price_loss, - 'max_risk_deal': max_risk_deal, - 'commission_fee': commission_fee, - } - - return data - - -async def update_margin_type(tg_id, margin_type) -> None: - """Обновить тип маржи пользователя.""" - async with async_session() as session: - type = await session.scalar(select(Margin_type.type).where(Margin_type.type == margin_type)) - - if type: - logger.info("Изменен тип маржи %s", tg_id) - await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(margin_type=type)) - - await session.commit() - - -async def update_size_leverange(tg_id, num): - """Обновить размер левеража пользователя.""" - async with async_session() as session: - await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(size_leverage=num)) - - await session.commit() - - -async def update_starting_quantity(tg_id, num): - """Обновить размер начальной ставки пользователя.""" - async with async_session() as session: - await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(starting_quantity=num)) - - await session.commit() - - -async def update_base_quantity(tg_id, num): - """Обновить размер следующей ставки пользователя.""" - async with async_session() as session: - await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(base_quantity=num)) - - await session.commit() - - -async def update_martingale_factor(tg_id, num): - """Обновить шаг мартингейла пользователя.""" - async with async_session() as session: - await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(martingale_factor=num)) - - await session.commit() - - -async def update_maximal_quantity(tg_id, num): - """Обновить размер максимальной ставки пользователя.""" - async with async_session() as session: - await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(maximal_quantity=num)) - - await session.commit() - - -# ОБНОВЛЕНИЕ НАСТРОЕК РИСК-МЕНЕДЖМЕНТА - -async def update_price_profit(tg_id, num): - """Обновить цену тейк-профита (прибыль) пользователя.""" - async with async_session() as session: - await session.execute(update(URMS).where(URMS.tg_id == tg_id).values(price_profit=num)) - - await session.commit() - - -async def update_price_loss(tg_id, num): - """Обновить цену тейк-лосса (убыток) пользователя.""" - async with async_session() as session: - await session.execute(update(URMS).where(URMS.tg_id == tg_id).values(price_loss=num)) - - await session.commit() - - -async def update_max_risk_deal(tg_id, num): - """Обновить максимальную сумму риска пользователя.""" - async with async_session() as session: - await session.execute(update(URMS).where(URMS.tg_id == tg_id).values(max_risk_deal=num)) - - await session.commit() - - -async def update_entry_order_type(tg_id, order_type): - """Обновить тип входного ордера пользователя.""" - async with async_session() as session: - await session.execute( - update(UMS) - .where(UMS.tg_id == tg_id) - .values(entry_order_type=order_type) - ) - await session.commit() - - -async def update_trigger_price(tg_id, price): - """Обновить условную цену пользователя.""" - async with async_session() as session: - await session.execute( - update(UMS) - .where(UMS.tg_id == tg_id) - .values(trigger_price=str(price)) - ) - await session.commit() - -async def get_trigger_price(tg_id): - """Получить условную цену пользователя как float, либо None.""" - async with async_session() as session: - result = await session.execute( - select(UMS.trigger_price) - .where(UMS.tg_id == tg_id) - ) - price = result.scalar_one_or_none() - if price: - try: - return float(price) - except ValueError: - return None - return None - -async def get_limit_price(tg_id): - """Получить лимитную цену пользователя как float, либо None.""" - async with async_session() as session: - result = await session.execute( - select(UMS.limit_order_price) - .where(UMS.tg_id == tg_id) - ) - price = result.scalar_one_or_none() - if price: - try: - return float(price) - except ValueError: - return None - return None - - -async def update_limit_price(tg_id, price): - """Обновить лимитную цену пользователя.""" - async with async_session() as session: - await session.execute( - update(UMS) - .where(UMS.tg_id == tg_id) - .values(limit_order_price=str(price)) - ) - await session.commit() - - -async def update_commission_fee(tg_id, num): - """Обновить комиссию пользователя.""" - async with async_session() as session: - await session.execute(update(URMS).where(URMS.tg_id == tg_id).values(commission_fee=num)) - - await session.commit() - - -async def get_user_timer(tg_id): - """Получить данные о таймере пользователя.""" - async with async_session() as session: - result = await session.execute(select(UserTimer).where(UserTimer.tg_id == tg_id)) - user_timer = result.scalars().first() - - if not user_timer: - logging.info(f"No timer found for user {tg_id}") - return None - - timer_minutes = user_timer.timer_minutes - timer_start = user_timer.timer_start - timer_end = user_timer.timer_end - - logging.info(f"Timer data for tg_id={tg_id}: " - f"timer_minutes={timer_minutes}, " - f"timer_start={timer_start}, " - f"timer_end={timer_end}") - - remaining = None - if timer_end: - remaining = max(0, int((timer_end - datetime.utcnow()).total_seconds() // 60)) - - return { - "timer_minutes": timer_minutes, - "timer_start": timer_start, - "timer_end": timer_end, - "remaining_minutes": remaining - } - - -async def update_user_timer(tg_id, minutes: int): - """Обновить данные о таймере пользователя.""" - async with async_session() as session: - try: - timer_start = None - timer_end = None - - if minutes > 0: - timer_start = datetime.utcnow() - timer_end = timer_start + timedelta(minutes=minutes) - - result = await session.execute(select(UserTimer).where(UserTimer.tg_id == tg_id)) - user_timer = result.scalars().first() - - if user_timer: - user_timer.timer_minutes = minutes - user_timer.timer_start = timer_start - user_timer.timer_end = timer_end - else: - user_timer = UserTimer( - tg_id=tg_id, - timer_minutes=minutes, - timer_start=timer_start, - timer_end=timer_end - ) - session.add(user_timer) - - await session.commit() - except Exception as e: - logging.error(f"Ошибка обновления таймера пользователя {tg_id}: {e}") - - -async def get_martingale_step(tg_id): - """Получить шаг мартингейла пользователя.""" - async with async_session() as session: - result = await session.execute(select(UMS).where(UMS.tg_id == tg_id)) - user_settings = result.scalars().first() - return user_settings.martingale_step - - -async def update_martingale_step(tg_id, step): - """Обновить шаг мартингейла пользователя.""" - async with async_session() as session: - await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(martingale_step=step)) - - await session.commit() - - -async def update_switch_mode_enabled(tg_id, switch_mode): - """Обновить режим переключения пользователя.""" - async with async_session() as session: - await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(switch_mode_enabled=switch_mode)) - - await session.commit() - - -async def update_switch_state(tg_id, switch_state): - """Обновить состояние переключения пользователя.""" - async with async_session() as session: - await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(switch_state=switch_state)) - - await session.commit() - - -async def update_trigger(tg_id, trigger): - """Обновить триггер пользователя.""" - async with async_session() as session: - await session.execute(update(UCS).where(UCS.tg_id == tg_id).values(trigger=trigger)) - - await session.commit() - - -async def set_last_series_info(tg_id: int, last_side: str): - async with async_session() as session: - async with session.begin(): - # Обновляем запись - result = await session.execute( - update(UMS) - .where(UMS.tg_id == tg_id) - .values(last_side=last_side) - ) - if result.rowcount == 0: - # Если запись не существует, создаём новую - new_entry = UMS( - tg_id=tg_id, - last_side=last_side, - ) - session.add(new_entry) - await session.commit() diff --git a/app/telegram/functions/additional_settings/settings.py b/app/telegram/functions/additional_settings/settings.py deleted file mode 100644 index 5090d0b..0000000 --- a/app/telegram/functions/additional_settings/settings.py +++ /dev/null @@ -1,38 +0,0 @@ -import app.telegram.Keyboards.inline_keyboards as inline_markup - -import app.telegram.database.requests as rq - -async def reg_new_user_default_additional_settings(id, message): - tg_id = id - - await rq.set_new_user_default_additional_settings(tg_id) - -async def main_settings_message(id, message): - text = '''Дополнительные параметры - -- Сохранить как шаблон стратегии: да / нет -- Автозапуск после сохранения: да / нет -- Уведомления в Telegram: включено / отключено ''' - - await message.edit_text(text=text, parse_mode='html', reply_markup=inline_markup.additional_settings_markup) - -async def save_pattern_message(message, state): - text = '''Сохранение шаблона - - Описание... ''' - - await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.buttons_yes_no_markup) - -async def auto_start_message(message, state): - text = '''Автозапуск - - Описание... ''' - - await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.buttons_yes_no_markup) - -async def notifications_message(message, state): - text = '''Уведомления - - Описание... ''' - - await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.buttons_on_off_markup) \ No newline at end of file diff --git a/app/telegram/functions/condition_settings/settings.py b/app/telegram/functions/condition_settings/settings.py deleted file mode 100644 index bfc9eec..0000000 --- a/app/telegram/functions/condition_settings/settings.py +++ /dev/null @@ -1,208 +0,0 @@ -import logging.config -import app.telegram.Keyboards.inline_keyboards as inline_markup -from aiogram import Router, F -from aiogram.types import Message, CallbackQuery -from aiogram.fsm.context import FSMContext -import app.telegram.database.requests as rq -from app.states.States import condition_settings -from app.states.States import state_limit_price, state_update_entry_type, state_trigger_price -from logger_helper.logger_helper import LOGGING_CONFIG - -logging.config.dictConfig(LOGGING_CONFIG) -logger = logging.getLogger("condition_settings") - -condition_settings_router = Router() - - -async def reg_new_user_default_condition_settings(id): - tg_id = id - - trigger = await rq.get_for_registration_trigger(tg_id) - - await rq.set_new_user_default_condition_settings(tg_id, trigger) - - -async def main_settings_message(id, message): - data = await rq.get_user_main_settings(id) - entry_order_type = data['entry_order_type'] - - if entry_order_type == "Market": - entry_order_type_rus = "Маркет" - elif entry_order_type == "Limit": - entry_order_type_rus = "Лимит" - else: - entry_order_type_rus = "Условный" - - trigger_price = data['trigger_price'] or 0.0 - limit_price = data['limit_order_price'] or 0.0 - - text = f""" Условия запуска -- Таймер: установить таймер / удалить таймер -- Тип позиции: {entry_order_type_rus} -- Триггер цена: {trigger_price:,.4f} -- Лимит цена: {limit_price:,.4f} -""" - await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.condition_settings_markup) - - -async def timer_message(id, message: Message, state: FSMContext): - await state.set_state(condition_settings.timer) - - timer_info = await rq.get_user_timer(id) - if timer_info is None: - await message.answer("Таймер не установлен.", reply_markup=inline_markup.timer_markup) - return - - await message.answer( - f"Таймер установлен на: {timer_info['timer_minutes']} мин\n", - reply_markup=inline_markup.timer_markup - ) - - -@condition_settings_router.callback_query(F.data == "clb_set_timer") -async def set_timer_callback(callback: CallbackQuery, state: FSMContext): - await state.set_state(condition_settings.timer) # состояние для ввода времени - await callback.message.answer("Введите время работы в минутах (например, 60):", reply_markup=inline_markup.cancel) - await callback.answer() - - -@condition_settings_router.message(condition_settings.timer) -async def process_timer_input(message: Message, state: FSMContext): - try: - minutes = int(message.text) - if minutes <= 0: - await message.reply("Введите число больше нуля.") - return - - await rq.update_user_timer(message.from_user.id, minutes) - logger.info("Timer set for user %s: %s minutes", message.from_user.id, minutes) - await timer_message(message.from_user.id, message, state) - await state.clear() - except ValueError: - await message.reply("Пожалуйста, введите корректное число.") - - -@condition_settings_router.callback_query(F.data == "clb_delete_timer") -async def delete_timer_callback(callback: CallbackQuery, state: FSMContext): - await state.clear() - await rq.update_user_timer(callback.from_user.id, 0) - logger.info("Timer deleted for user %s", callback.from_user.id) - await timer_message(callback.from_user.id, callback.message, state) - await callback.answer() - - -@condition_settings_router.callback_query(F.data == 'clb_update_entry_type') -async def update_entry_type_message(callback: CallbackQuery, state: FSMContext) -> None: - """ - Запрашивает у пользователя тип входа в позицию (Market или Limit). - """ - await state.set_state(state_update_entry_type.entry_type) - await callback.message.answer("Выберите тип входа в позицию:", reply_markup=inline_markup.entry_order_type_markup) - await callback.answer() - - -@condition_settings_router.callback_query(lambda c: c.data and c.data.startswith('entry_order_type:')) -async def entry_order_type_callback(callback: CallbackQuery, state: FSMContext) -> None: - """ - Обработка выбора типа входа в позицию. - Если Limit, запрашивает цену лимитного ордера. - Если Market — обновляет настройки. - """ - order_type = callback.data.split(':')[1] - - if order_type not in ['Market', 'Limit']: - await callback.answer("Ошибка выбора", show_alert=True) - return - - if order_type == 'Limit': - order_type_rus = 'Лимит' - else: - order_type_rus = 'Маркет' - try: - await state.update_data(entry_order_type=order_type) - await rq.update_entry_order_type(callback.from_user.id, order_type) - await callback.message.answer(f"Выбран тип входа {order_type_rus}", - reply_markup=inline_markup.back_to_condition_settings) - await callback.answer() - except Exception as e: - logger.error("Произошла ошибка при обновлении типа входа в позицию: %s", e) - await callback.message.answer("Произошла ошибка при обновлении типа входа в позицию", - reply_markup=inline_markup.back_to_condition_settings) - await state.clear() - - -@condition_settings_router.callback_query(F.data == 'clb_change_limit_price') -async def set_limit_price_callback(callback: CallbackQuery, state: FSMContext) -> None: - await state.set_state(state_limit_price.price) - await callback.message.answer("Введите цену лимитного ордера:", reply_markup=inline_markup.cancel) - await callback.answer() - - -@condition_settings_router.message(state_limit_price.price) -async def process_limit_price_input(message: Message, state: FSMContext) -> None: - try: - price = float(message.text) - await state.update_data(price=price) - await rq.update_limit_price(tg_id=message.from_user.id, price=price) - await message.answer(f"Цена лимитного ордера установлена: {price}", reply_markup=inline_markup.back_to_condition_settings) - await state.clear() - except ValueError: - await message.reply("Пожалуйста, введите корректную цену.") - - -@condition_settings_router.callback_query(F.data == 'clb_change_trigger_price') -async def change_trigger_price_callback(callback: CallbackQuery, state: FSMContext) -> None: - await state.set_state(state_trigger_price.price) - await callback.message.answer("Введите цену триггера:", reply_markup=inline_markup.cancel) - await callback.answer() - - -@condition_settings_router.message(state_trigger_price.price) -async def process_trigger_price_input(message: Message, state: FSMContext) -> None: - try: - price = float(message.text) - await state.update_data(price=price) - await rq.update_trigger_price(tg_id=message.from_user.id, price=price) - await message.answer(f"Цена триггера установлена: {price}", reply_markup=inline_markup.back_to_condition_settings) - await state.clear() - except ValueError: - await message.reply("Пожалуйста, введите корректную цену.") - - - -async def filter_volatility_message(message, state): - text = '''Фильтр волатильности - - Описание... ''' - - await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.buttons_on_off_markup) - - -async def external_cues_message(message, state): - text = '''Внешние сигналы - - Описание... ''' - - await message.answer(text=text, parse_mode='html', reply_markup=None) - - -async def trading_cues_message(message, state): - text = '''Использование сигналов - - Описание... ''' - - await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.buttons_yes_no_markup) - - -async def webhook_message(message, state): - text = '''Скиньте ссылку на webhook (если есть trading view): ''' - - await message.answer(text=text, parse_mode='html') - - -async def ai_analytics_message(message, state): - text = '''ИИ - Аналитика - - Описание... ''' - - await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.buttons_yes_no_markup) diff --git a/app/telegram/functions/functions.py b/app/telegram/functions/functions.py deleted file mode 100644 index 19ab77b..0000000 --- a/app/telegram/functions/functions.py +++ /dev/null @@ -1,29 +0,0 @@ -import app.telegram.Keyboards.inline_keyboards as inline_markup -import app.telegram.Keyboards.reply_keyboards as reply_markup - -async def start_message(message): - username = '' - - if message.from_user.first_name == None: - username = message.from_user.last_name - elif message.from_user.last_name == None: - username = message.from_user.first_name - else: - username = f'{message.from_user.first_name} {message.from_user.last_name}' - await message.answer(f""" Привет {username}! 👋""", parse_mode='html') - await message.answer("Добро пожаловать в чат-робот для автоматизации трейдинга — вашего надежного помощника для анализа рынка и принятия взвешенных решений.", - parse_mode='html', reply_markup=inline_markup.start_markup) - -async def profile_message(username, message): - await message.answer(f""" @{username} - -Баланс -⭐️ 0 - -""", parse_mode='html', reply_markup=inline_markup.settings_markup) - -async def check_profile_message(message, username): - await message.answer(f'С возвращением, {username}!', reply_markup=reply_markup.base_buttons_markup) - -async def settings_message(message): - await message.edit_text("Выберите что настроить", reply_markup=inline_markup.special_settings_markup) \ No newline at end of file diff --git a/app/telegram/functions/main_settings/settings.py b/app/telegram/functions/main_settings/settings.py deleted file mode 100644 index df7e2c3..0000000 --- a/app/telegram/functions/main_settings/settings.py +++ /dev/null @@ -1,375 +0,0 @@ -from aiogram import Router -import logging.config -import app.telegram.Keyboards.inline_keyboards as inline_markup - -import app.telegram.database.requests as rq -from aiogram.types import Message, CallbackQuery - -from app.services.Bybit.functions.Futures import safe_float, calculate_total_budget, get_bybit_client -from app.states.States import update_main_settings -from logger_helper.logger_helper import LOGGING_CONFIG - -logging.config.dictConfig(LOGGING_CONFIG) -logger = logging.getLogger("main_settings") - -router_main_settings = Router() - - -def is_number(value: str) -> bool: - """ - Checks if a given string represents a number. - - Args: - value (str): The string to check. - - Returns: - bool: True if the string represents a number, False otherwise. - """ - try: - # Convert the string to a float - num = float(value) - # Check if the number is positive - if num <= 0: - return False - # Check if the string contains "+" or "-" - if "+" in value or "-" in value: - return False - # Check if the string contains only digits - allowed_chars = set("0123456789.") - if not all(ch in allowed_chars for ch in value): - return False - return True - except ValueError: - return False - - -async def reg_new_user_default_main_settings(id, message): - tg_id = id - - trading_mode = await rq.get_for_registration_trading_mode() - margin_type = await rq.get_for_registration_margin_type() - - await rq.set_new_user_default_main_settings(tg_id, trading_mode, margin_type) - - -async def main_settings_message(id, message): - try: - data = await rq.get_user_main_settings(id) - tg_id = id - - data_main_stgs = await rq.get_user_main_settings(id) - data_risk_stgs = await rq.get_user_risk_management_settings(id) - client = await get_bybit_client(tg_id) - symbol = await rq.get_symbol(tg_id) - max_martingale_steps = (data_main_stgs or {}).get('maximal_quantity', 0) - commission_fee = (data_risk_stgs or {}).get('commission_fee') - starting_quantity = safe_float((data_main_stgs or {}).get('starting_quantity')) - martingale_factor = safe_float((data_main_stgs or {}).get('martingale_factor')) - fee_info = client.get_fee_rates(category='linear', symbol=symbol) - - if commission_fee == "Да": - commission_fee_percent = safe_float(fee_info['result']['list'][0]['takerFeeRate']) - else: - commission_fee_percent = 0.0 - - total_budget = await calculate_total_budget( - starting_quantity=starting_quantity, - martingale_factor=martingale_factor, - max_steps=max_martingale_steps, - commission_fee_percent=commission_fee_percent, - ) - - await message.answer(f"""Основные настройки - - - Режим торговли: {data['trading_mode']} - - Состояние свитча: {data['switch_state']} - - Направление последней сделки: {data['last_side']} - - Тип маржи: {data['margin_type']} - - Размер кредитного плеча: х{data['size_leverage']} - - Ставка: {data['starting_quantity']} - - Коэффициент мартингейла: {data['martingale_factor']} - - Текущий шаг: {data['martingale_step']} - - Максимальное количество ставок в серии: {data['maximal_quantity']} - - - Требуемый бюджет: {total_budget:.2f} USDT - """, parse_mode='html', reply_markup=inline_markup.main_settings_markup) - except PermissionError as e: - logger.error("Authenticated endpoints require keys: %s", e) - await message.answer("Вы не авторизованы.", reply_markup=inline_markup.connect_bybit_api_message) - - -async def trading_mode_message(message, state): - await state.set_state(update_main_settings.trading_mode) - - await message.edit_text("""Режим торговли - -Лонг — стратегия, ориентированная на покупку актива с целью заработать на повышении его стоимости. - -Шорт — метод продажи активов, взятых в кредит, чтобы получить прибыль от снижения цены. - -Свитч — динамическое переключение между торговыми режимами для максимизации эффективности. - -Выберите ниже для изменений: -""", parse_mode='html', reply_markup=inline_markup.trading_mode_markup) - - -@router_main_settings.callback_query(update_main_settings.trading_mode) -async def state_trading_mode(callback: CallbackQuery, state): - await callback.answer() - - id = callback.from_user.id - data_settings = await rq.get_user_main_settings(id) - - try: - match callback.data: - case 'trade_mode_long': - await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Long") - await rq.update_trade_mode_user(id, 'Long') - await main_settings_message(id, callback.message) - - await state.clear() - case 'trade_mode_short': - await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Short") - await rq.update_trade_mode_user(id, 'Short') - await main_settings_message(id, callback.message) - - await state.clear() - - case 'trade_mode_switch': - await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Switch") - await rq.update_trade_mode_user(id, 'Switch') - await main_settings_message(id, callback.message) - - await state.clear() - - case 'trade_mode_smart': - await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Smart") - await rq.update_trade_mode_user(id, 'Smart') - await main_settings_message(id, callback.message) - - await state.clear() - except Exception as e: - logger.error("Ошибка при обновлении режима торговли: %s", e) - - -async def switch_mode_enabled_message(message, state): - await state.set_state(update_main_settings.switch_mode_enabled) - - await message.edit_text( - f""" Состояние свитча - - По направлению - по направлению последней сделки предыдущей серии - Против направления - против направления последней сделки предыдущей серии - - По умолчанию при первом запуске бота, направление сделки установлено на "Buy". - Выберите ниже для изменений:""", parse_mode='html', - reply_markup=inline_markup.switch_state_markup) - - -@router_main_settings.callback_query(lambda c: c.data in ["clb_long_switch", "clb_short_switch"]) -async def state_switch_mode_enabled(callback: CallbackQuery, state): - await callback.answer() - tg_id = callback.from_user.id - val = "По направлению" if callback.data == "clb_long_switch" else "Против направления" - if val == "По направлению": - await rq.update_switch_state(tg_id, "По направлению") - await callback.message.answer(f"Состояние свитча: {val}") - await main_settings_message(tg_id, callback.message) - else: - await rq.update_switch_state(tg_id, "Против направления") - await callback.message.answer(f"Состояние свитча: {val}") - await main_settings_message(tg_id, callback.message) - await state.clear() - - -async def size_leverage_message(message, state): - await state.set_state(update_main_settings.size_leverage) - - await message.edit_text("Введите размер кредитного плеча (от 1 до 100): ", parse_mode='html', - reply_markup=inline_markup.back_btn_list_settings_markup) - - -@router_main_settings.message(update_main_settings.size_leverage) -async def state_size_leverage(message: Message, state): - try: - leverage = float(message.text) - if leverage <= 0: - raise ValueError("Неверное значение") - except ValueError: - await message.answer( - "Ошибка: пожалуйста, введите положительное число для кредитного плеча." - "\nПопробуйте снова." - ) - return - - await state.update_data(size_leverage=message.text) - - data = await state.get_data() - tg_id = message.from_user.id - symbol = await rq.get_symbol(tg_id) - leverage = data['size_leverage'] - client = await get_bybit_client(tg_id) - - instruments_resp = client.get_instruments_info(category="linear", symbol=symbol) - info = instruments_resp.get("result", {}).get("list", []) - - max_leverage = safe_float(info[0].get("leverageFilter", {}).get("maxLeverage", 0)) - - if safe_float(leverage) > max_leverage: - await message.answer( - f"Запрошенное кредитное плечо {leverage} превышает максимальное {max_leverage} для {symbol}. " - f"Устанавливаю максимальное.", - reply_markup=inline_markup.back_to_main, - ) - logger.info( - f"Запрошенное кредитное плечо {leverage} превышает максимальное {max_leverage} для {symbol}. Устанавливаю максимальное.") - - await rq.update_size_leverange(message.from_user.id, max_leverage) - await main_settings_message(message.from_user.id, message) - await state.clear() - else: - await message.answer(f"✅ Изменено: {leverage}") - await rq.update_size_leverange(message.from_user.id, safe_float(leverage)) - await main_settings_message(message.from_user.id, message) - await state.clear() - - -async def martingale_factor_message(message, state): - await state.set_state(update_main_settings.martingale_factor) - - await message.edit_text("Введите коэффициент Мартингейла:", parse_mode='html', - reply_markup=inline_markup.back_btn_list_settings_markup) - - -@router_main_settings.message(update_main_settings.martingale_factor) -async def state_martingale_factor(message: Message, state): - await state.update_data(martingale_factor=message.text) - - data = await state.get_data() - data_settings = await rq.get_user_main_settings(message.from_user.id) - - if data['martingale_factor'].isdigit() and int(data['martingale_factor']) <= 100: - await message.answer(f"✅ Изменено: {data_settings['martingale_factor']} → {data['martingale_factor']}") - - await rq.update_martingale_factor(message.from_user.id, data['martingale_factor']) - await main_settings_message(message.from_user.id, message) - - await state.clear() - else: - val = data['martingale_factor'] - await message.answer( - f"⛔️ Ошибка: ваше значение ({val}) или выше лимита (100) или вы вводите неверные символы") - - await main_settings_message(message.from_user.id, message) - - -async def margin_type_message(message, state): - await state.set_state(update_main_settings.margin_type) - - await message.edit_text("""Тип маржи - -Изолированная маржа -Этот тип маржи позволяет ограничить риск конкретной позиции. -При использовании изолированной маржи вы выделяете определённую сумму средств только для одной позиции. -Если позиция начинает приносить убытки, ваши потери ограничиваются этой суммой, -и остальные средства на счёте не затрагиваются. - -Кросс-маржа -Кросс-маржа объединяет весь маржинальный баланс на счёте и использует все доступные средства для поддержания открытых позиций. -В случае убытков средства с других позиций или баланса автоматически покрывают дефицит, -снижая риск ликвидации, но увеличивая общий риск потери капитала. - -Выберите ниже для изменений: -""", parse_mode='html', reply_markup=inline_markup.margin_type_markup) - - -@router_main_settings.callback_query(update_main_settings.margin_type) -async def state_margin_type(callback: CallbackQuery, state): - callback_data = callback.data - if callback_data in ['margin_type_isolated', 'margin_type_cross']: - tg_id = callback.from_user.id - data_settings = await rq.get_user_main_settings(tg_id) - - try: - match callback.data: - case 'margin_type_isolated': - await callback.answer() - await callback.message.answer(f"✅ Изменено: {data_settings['margin_type']} → Isolated") - - await rq.update_margin_type(tg_id, 'Isolated') - await main_settings_message(tg_id, callback.message) - - await state.clear() - case 'margin_type_cross': - await callback.answer() - await callback.message.answer(f"✅ Изменено: {data_settings['margin_type']} → Cross") - - await rq.update_margin_type(tg_id, 'Cross') - await main_settings_message(tg_id, callback.message) - - await state.clear() - except Exception as e: - logger.error("Ошибка при изменении типа маржи: %s", e) - else: - await callback.answer() - await main_settings_message(callback.from_user.id, callback.message) - - await state.clear() - - -async def starting_quantity_message(message, state): - await state.set_state(update_main_settings.starting_quantity) - - await message.edit_text("Введите ставку:", parse_mode='html', - reply_markup=inline_markup.back_btn_list_settings_markup) - - -@router_main_settings.message(update_main_settings.starting_quantity) -async def state_starting_quantity(message: Message, state): - await state.update_data(starting_quantity=message.text) - - data = await state.get_data() - data_settings = await rq.get_user_main_settings(message.from_user.id) - - if is_number(data['starting_quantity']): - await message.answer(f"✅ Изменено: {data_settings['starting_quantity']} → {data['starting_quantity']}") - - await rq.update_starting_quantity(message.from_user.id, data['starting_quantity']) - await rq.update_base_quantity(tg_id=message.from_user.id, num=data['starting_quantity']) - await main_settings_message(message.from_user.id, message) - - await state.clear() - else: - await message.answer("⛔️ Ошибка: вы вводите неверные символы") - - await main_settings_message(message.from_user.id, message) - - -async def maximum_quantity_message(message, state): - await state.set_state(update_main_settings.maximal_quantity) - - await message.edit_text("Введите максимальное количество серии ставок:", - reply_markup=inline_markup.back_btn_list_settings_markup) - - -@router_main_settings.message(update_main_settings.maximal_quantity) -async def state_maximal_quantity(message: Message, state): - await state.update_data(maximal_quantity=message.text) - - data = await state.get_data() - data_settings = await rq.get_user_main_settings(message.from_user.id) - - if data['maximal_quantity'].isdigit() and int(data['maximal_quantity']) <= 100: - await message.answer(f"✅ Изменено: {data_settings['maximal_quantity']} → {data['maximal_quantity']}") - - await rq.update_maximal_quantity(message.from_user.id, data['maximal_quantity']) - await main_settings_message(message.from_user.id, message) - - await state.clear() - else: - val = data['maximal_quantity'] - await message.answer( - f'⛔️ Ошибка: ваше значение ({val}) или выше лимита (100) или вы вводите неверные символы') - logger.error(f'⛔️ Ошибка: ваше значение ({val}) или выше лимита (100) или вы вводите неверные символы') - - await main_settings_message(message.from_user.id, message) diff --git a/app/telegram/functions/risk_management_settings/settings.py b/app/telegram/functions/risk_management_settings/settings.py deleted file mode 100644 index 6594e37..0000000 --- a/app/telegram/functions/risk_management_settings/settings.py +++ /dev/null @@ -1,160 +0,0 @@ -from aiogram import Router -import app.telegram.Keyboards.inline_keyboards as inline_markup -import logging.config -import app.telegram.database.requests as rq -from aiogram.types import Message, CallbackQuery - -from app.states.States import update_risk_management_settings - -from logger_helper.logger_helper import LOGGING_CONFIG - -logging.config.dictConfig(LOGGING_CONFIG) -logger = logging.getLogger("risk_management_settings") - -router_risk_management_settings = Router() - - -async def reg_new_user_default_risk_management_settings(id, message): - tg_id = id - - await rq.set_new_user_default_risk_management_settings(tg_id) - - -async def main_settings_message(id, message): - data = await rq.get_user_risk_management_settings(id) - - text = f"""Риск менеджмент, - - - Процент изменения цены для фиксации прибыли: {data.get('price_profit', 0)}% - - Процент изменения цены для фиксации убытков: {data.get('price_loss', 0)}% - - Максимальный риск на сделку (в % от баланса): {data.get('max_risk_deal', 0)}% - - Комиссия биржи для расчета прибыли: {data.get('commission_fee', "Да")} - """ - await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.risk_management_settings_markup) - - -async def price_profit_message(message, state): - await state.set_state(update_risk_management_settings.price_profit) - - text = 'Введите число изменения цены для фиксации прибыли: ' - - await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.cancel) - - -@router_risk_management_settings.message(update_risk_management_settings.price_profit) -async def state_price_profit(message: Message, state): - await state.update_data(price_profit=message.text) - - data = await state.get_data() - data_settings = await rq.get_user_risk_management_settings(message.from_user.id) - - if data['price_profit'].isdigit() and int(data['price_profit']) <= 100: - await message.answer(f"✅ Изменено: {data_settings['price_profit']}% → {data['price_profit']}%") - - await rq.update_price_profit(message.from_user.id, data['price_profit']) - await main_settings_message(message.from_user.id, message) - - await state.clear() - else: - val = data['price_profit'] - await message.answer( - f'⛔️ Ошибка: ваше значение ({val}%) или выше лимита (100) или вы вводите неверные символы') - - await main_settings_message(message.from_user.id, message) - - -async def price_loss_message(message, state): - await state.set_state(update_risk_management_settings.price_loss) - - text = 'Введите число изменения цены для фиксации убытков: ' - - await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.cancel) - - -@router_risk_management_settings.message(update_risk_management_settings.price_loss) -async def state_price_loss(message: Message, state): - await state.update_data(price_loss=message.text) - - data = await state.get_data() - data_settings = await rq.get_user_risk_management_settings(message.from_user.id) - - if data['price_loss'].isdigit() and int(data['price_loss']) <= 100: - new_price_loss = int(data['price_loss']) - old_price_loss = int(data_settings.get('price_loss', 0)) - - current_price_profit = data_settings.get('price_profit') - # Пробуем перевести price_profit в число, если это возможно - try: - current_price_profit_num = int(current_price_profit) - except Exception as e: - logger.error(e) - current_price_profit_num = 0 - - # Флаг, если price_profit изначально равен 0 или совпадает со старым стоп-лоссом - should_update_profit = (current_price_profit_num == 0) or (current_price_profit_num == abs(old_price_loss)) - - # Обновляем стоп-лосс - await rq.update_price_loss(message.from_user.id, new_price_loss) - - # Если нужно, меняем тейк-профит - if should_update_profit: - new_price_profit = abs(new_price_loss) - await rq.update_price_profit(message.from_user.id, new_price_profit) - await message.answer(f"✅ Стоп-лосс изменён: {old_price_loss}% → {new_price_loss}%\n" - f"Тейк-профит автоматически установлен в: {new_price_profit}%") - else: - await message.answer(f"✅ Стоп-лосс изменён: {old_price_loss}% → {new_price_loss}%") - - await main_settings_message(message.from_user.id, message) - await state.clear() - else: - val = data['price_loss'] - await message.answer( - f'⛔️ Ошибка: ваше значение ({val}%) выше лимита (100) или содержит неверные символы') - await main_settings_message(message.from_user.id, message) - - -async def max_risk_deal_message(message, state): - await state.set_state(update_risk_management_settings.max_risk_deal) - - text = 'Введите число (процент от баланса) для изменения максимального риска на сделку: ' - - await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.cancel) - - -@router_risk_management_settings.message(update_risk_management_settings.max_risk_deal) -async def state_max_risk_deal(message: Message, state): - await state.update_data(max_risk_deal=message.text) - - data = await state.get_data() - data_settings = await rq.get_user_risk_management_settings(message.from_user.id) - - if data['max_risk_deal'].isdigit() and int(data['max_risk_deal']) <= 100: - await message.answer(f"✅ Изменено: {data_settings['max_risk_deal']}% → {data['max_risk_deal']}%") - - await rq.update_max_risk_deal(message.from_user.id, data['max_risk_deal']) - await main_settings_message(message.from_user.id, message) - - await state.clear() - else: - val = data['max_risk_deal'] - await message.answer( - f'⛔️ Ошибка: ваше значение ({val}%) или выше лимита (100) или вы вводите неверные символы') - - await main_settings_message(message.from_user.id, message) - - -async def commission_fee_message(message, state): - await state.set_state(update_risk_management_settings.commission_fee) - await message.answer(text="Хотите учитывать комиссию биржи:", parse_mode='html', - reply_markup=inline_markup.buttons_yes_no_markup) - - -@router_risk_management_settings.callback_query(lambda c: c.data in ["clb_yes", "clb_no"]) -async def process_commission_fee_callback(callback: CallbackQuery, state): - val = "Да" if callback.data == "clb_yes" else "Нет" - await rq.update_commission_fee(callback.from_user.id, val) - await callback.message.answer(f"✅ Изменено: {val}") - await callback.answer() - await main_settings_message(callback.from_user.id, callback.message) - await state.clear() \ No newline at end of file diff --git a/app/telegram/handlers/handlers.py b/app/telegram/handlers/handlers.py deleted file mode 100644 index f2de4e5..0000000 --- a/app/telegram/handlers/handlers.py +++ /dev/null @@ -1,316 +0,0 @@ -import logging.config - -from aiogram import F, Router -from aiogram.filters import CommandStart, Command -from aiogram.types import Message, CallbackQuery -from aiogram.fsm.context import FSMContext - -import app.telegram.functions.functions as func -import app.telegram.functions.main_settings.settings as func_main_settings -import app.telegram.functions.risk_management_settings.settings as func_rmanagement_settings -import app.telegram.functions.condition_settings.settings as func_condition_settings -import app.telegram.functions.additional_settings.settings as func_additional_settings - -import app.telegram.database.requests as rq - -from app.services.Bybit.functions.balance import get_balance -from app.services.Bybit.functions.bybit_ws import run_ws_for_user - -from logger_helper.logger_helper import LOGGING_CONFIG - -logging.config.dictConfig(LOGGING_CONFIG) -logger = logging.getLogger("handlers") - -router = Router() - -@router.message(Command("start")) -@router.message(CommandStart()) -async def start_message(message: Message) -> None: - """ - Обработчик команды /start. - Инициализирует нового пользователя в БД. - - Args: - message (Message): Входящее сообщение с командой /start. - """ - await rq.set_new_user_bybit_api(message.from_user.id) - await func.start_message(message) - - -@router.message(Command("profile")) -@router.message(F.text == "👤 Профиль") -async def profile_message(message: Message) -> None: - """ - Обработчик кнопки 'Профиль'. - Проверяет существование пользователя и отображает профиль. - - Args: - message (Message): Сообщение с текстом кнопки. - """ - user = await rq.check_user(message.from_user.id) - tg_id = message.from_user.id - balance = await get_balance(message.from_user.id, message) - if user and balance: - await run_ws_for_user(tg_id, message) - await func.profile_message(message.from_user.username, message) - else: - await rq.save_tg_id_new_user(message.from_user.id) - await func_main_settings.reg_new_user_default_main_settings(message.from_user.id, message) - await func_rmanagement_settings.reg_new_user_default_risk_management_settings(message.from_user.id, message) - await func_condition_settings.reg_new_user_default_condition_settings(message.from_user.id) - await func_additional_settings.reg_new_user_default_additional_settings(message.from_user.id, message) - - -@router.callback_query(F.data == "clb_start_chatbot_message") -async def clb_profile_msg(callback: CallbackQuery) -> None: - """ - Обработчик колбэка 'clb_start_chatbot_message'. - Если пользователь есть в БД — показывает профиль, - иначе регистрирует нового пользователя и инициализирует настройки. - - Args: - callback (CallbackQuery): Полученный колбэк. - """ - tg_id = callback.from_user.id - message = callback.message - user = await rq.check_user(callback.from_user.id) - balance = await get_balance(callback.from_user.id, callback.message) - first_name = callback.from_user.first_name or "" - last_name = callback.from_user.last_name or "" - username = f"{first_name} {last_name}".strip() or callback.from_user.username or "Пользователь" - - if user and balance: - await run_ws_for_user(tg_id, message) - await func.profile_message(callback.from_user.username, callback.message) - else: - await rq.save_tg_id_new_user(callback.from_user.id) - - await func_main_settings.reg_new_user_default_main_settings(callback.from_user.id, callback.message) - await func_rmanagement_settings.reg_new_user_default_risk_management_settings(callback.from_user.id, - callback.message) - await func_condition_settings.reg_new_user_default_condition_settings(callback.from_user.id) - await func_additional_settings.reg_new_user_default_additional_settings(callback.from_user.id, callback.message) - - await callback.answer() - - -@router.callback_query(F.data == "clb_settings_message") -async def clb_settings_msg(callback: CallbackQuery) -> None: - """ - Показать главное меню настроек. - - Args: - callback (CallbackQuery): полученный колбэк. - """ - await func.settings_message(callback.message) - - await callback.answer() - - -@router.callback_query(F.data == "clb_back_to_special_settings_message") -async def clb_back_to_settings_msg(callback: CallbackQuery) -> None: - """ - Вернуть пользователя к меню специальных настроек. - - Args: - callback (CallbackQuery): полученный колбэк. - """ - await func.settings_message(callback.message) - - await callback.answer() - - -@router.callback_query(F.data == "clb_change_main_settings") -async def clb_change_main_settings_message(callback: CallbackQuery) -> None: - """ - Открыть меню изменения главных настроек. - - Args: - callback (CallbackQuery): полученный колбэк. - """ - await func_main_settings.main_settings_message(callback.from_user.id, callback.message) - - await callback.answer() - - -@router.callback_query(F.data == "clb_change_risk_management_settings") -async def clb_change_risk_management_message(callback: CallbackQuery) -> None: - """ - Открыть меню изменения настроек управления рисками. - - Args: - callback (CallbackQuery): полученный колбэк. - """ - await func_rmanagement_settings.main_settings_message(callback.from_user.id, callback.message) - - await callback.answer() - - -@router.callback_query(F.data == "clb_change_condition_settings") -async def clb_change_condition_message(callback: CallbackQuery) -> None: - """ - Открыть меню изменения настроек условий. - - Args: - callback (CallbackQuery): полученный колбэк. - """ - await func_condition_settings.main_settings_message(callback.from_user.id, callback.message) - - await callback.answer() - - -@router.callback_query(F.data == "clb_change_additional_settings") -async def clb_change_additional_message(callback: CallbackQuery) -> None: - """ - Открыть меню изменения дополнительных настроек. - - Args: - callback (CallbackQuery): полученный колбэк. - """ - await func_additional_settings.main_settings_message(callback.from_user.id, callback.message) - - await callback.answer() - - -# Конкретные настройки каталогов -list_main_settings = ['clb_change_trading_mode', - 'clb_change_switch_state', - 'clb_change_margin_type', - 'clb_change_size_leverage', - 'clb_change_starting_quantity', - 'clb_change_martingale_factor', - 'clb_change_maximum_quantity' - ] - - -@router.callback_query(F.data.in_(list_main_settings)) -async def clb_main_settings_msg(callback: CallbackQuery, state: FSMContext) -> None: - """ - Обработчик колбэков изменения главных настроек с dispatch через match-case. - - Args: - callback (CallbackQuery): полученный колбэк. - state (FSMContext): текущее состояние FSM. - """ - await callback.answer() - - try: - match callback.data: - case 'clb_change_trading_mode': - await func_main_settings.trading_mode_message(callback.message, state) - case 'clb_change_switch_state': - await func_main_settings.switch_mode_enabled_message(callback.message, state) - case 'clb_change_margin_type': - await func_main_settings.margin_type_message(callback.message, state) - case 'clb_change_size_leverage': - await func_main_settings.size_leverage_message(callback.message, state) - case 'clb_change_starting_quantity': - await func_main_settings.starting_quantity_message(callback.message, state) - case 'clb_change_martingale_factor': - await func_main_settings.martingale_factor_message(callback.message, state) - case 'clb_change_maximum_quantity': - await func_main_settings.maximum_quantity_message(callback.message, state) - except Exception as e: - logger.error("Error callback in main_settings match-case: %s", e) - - -list_risk_management_settings = ['clb_change_price_profit', - 'clb_change_price_loss', - 'clb_change_max_risk_deal', - 'commission_fee', - ] - - -@router.callback_query(F.data.in_(list_risk_management_settings)) -async def clb_risk_management_settings_msg(callback: CallbackQuery, state: FSMContext) -> None: - """ - Обработчик изменений настроек управления рисками. - - Args: - callback (CallbackQuery): полученный колбэк. - state (FSMContext): текущее состояние FSM. - """ - await callback.answer() - - try: - match callback.data: - case 'clb_change_price_profit': - await func_rmanagement_settings.price_profit_message(callback.message, state) - case 'clb_change_price_loss': - await func_rmanagement_settings.price_loss_message(callback.message, state) - case 'clb_change_max_risk_deal': - await func_rmanagement_settings.max_risk_deal_message(callback.message, state) - case 'commission_fee': - await func_rmanagement_settings.commission_fee_message(callback.message, state) - except Exception as e: - logger.error("Error callback in risk_management match-case: %s", e) - - -list_condition_settings = ['clb_change_mode', - 'clb_change_timer', - 'clb_change_filter_volatility', - 'clb_change_external_cues', - 'clb_change_tradingview_cues', - 'clb_change_webhook', - 'clb_change_ai_analytics' - ] - - -@router.callback_query(F.data.in_(list_condition_settings)) -async def clb_condition_settings_msg(callback: CallbackQuery, state: FSMContext) -> None: - """ - Обработчик изменений настроек условий трейдинга. - - Args: - callback (CallbackQuery): полученный колбэк. - state (FSMContext): текущее состояние FSM. - """ - await callback.answer() - - try: - match callback.data: - case 'clb_change_mode': - await func_condition_settings.trigger_message(callback.from_user.id, callback.message, state) - case 'clb_change_timer': - await func_condition_settings.timer_message(callback.from_user.id, callback.message, state) - case 'clb_change_filter_volatility': - await func_condition_settings.filter_volatility_message(callback.message, state) - case 'clb_change_external_cues': - await func_condition_settings.external_cues_message(callback.message, state) - case 'clb_change_tradingview_cues': - await func_condition_settings.trading_cues_message(callback.message, state) - case 'clb_change_webhook': - await func_condition_settings.webhook_message(callback.message, state) - case 'clb_change_ai_analytics': - await func_condition_settings.ai_analytics_message(callback.message, state) - except Exception as e: - logger.error("Error callback in main_settings match-case: %s", e) - - -list_additional_settings = ['clb_change_save_pattern', - 'clb_change_auto_start', - 'clb_change_notifications', - ] - - -@router.callback_query(F.data.in_(list_additional_settings)) -async def clb_additional_settings_msg(callback: CallbackQuery, state: FSMContext) -> None: - """ - Обработчик дополнительных настроек бота. - - Args: - callback (CallbackQuery): полученный колбэк. - state (FSMContext): текущее состояние FSM. - """ - await callback.answer() - - try: - match callback.data: - case 'clb_change_save_pattern': - await func_additional_settings.save_pattern_message(callback.message, state) - case 'clb_change_auto_start': - await func_additional_settings.auto_start_message(callback.message, state) - case 'clb_change_notifications': - await func_additional_settings.notifications_message(callback.message, state) - except Exception as e: - logger.error("Error callback in additional_settings match-case: %s", e) \ No newline at end of file diff --git a/config.py b/config.py deleted file mode 100644 index a55e59e..0000000 --- a/config.py +++ /dev/null @@ -1,9 +0,0 @@ -from dotenv import load_dotenv, find_dotenv -import os - -env_file = find_dotenv() -load_dotenv(env_file) - -TOKEN_TG_BOT_1 = os.getenv('TOKEN_TELEGRAM_BOT_1') -TOKEN_TG_BOT_2 = os.getenv('TOKEN_TELEGRAM_BOT_2') -TOKEN_TG_BOT_3 = os.getenv('TOKEN_TELEGRAM_BOT_3') \ No newline at end of file diff --git a/data/__init__.py b/data/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index e3ab834..0000000 --- a/requirements.txt +++ /dev/null @@ -1,51 +0,0 @@ -aiofiles==24.1.0 -aiogram==3.22.0 -aiohappyeyeballs==2.6.1 -aiohttp==3.12.15 -aiosignal==1.4.0 -aiosqlite==0.21.0 -alembic==1.16.5 -annotated-types==0.7.0 -attrs==25.3.0 -black==25.1.0 -certifi==2025.8.3 -charset-normalizer==3.4.3 -click==8.2.1 -colorama==0.4.6 -dotenv==0.9.9 -flake8==7.3.0 -flake8-bugbear==24.12.12 -flake8-pie==0.16.0 -frozenlist==1.7.0 -greenlet==3.2.4 -idna==3.10 -isort==6.0.1 -magic-filter==1.0.12 -Mako==1.3.10 -mando==0.7.1 -MarkupSafe==3.0.2 -mccabe==0.7.0 -multidict==6.6.4 -mypy_extensions==1.1.0 -nest-asyncio==1.6.0 -packaging==25.0 -pathspec==0.12.1 -platformdirs==4.4.0 -propcache==0.3.2 -pybit==5.11.0 -pycodestyle==2.14.0 -pycryptodome==3.23.0 -pydantic==2.11.7 -pydantic_core==2.33.2 -pyflakes==3.4.0 -python-dotenv==1.1.1 -radon==6.0.1 -redis==6.4.0 -requests==2.32.5 -six==1.17.0 -SQLAlchemy==2.0.43 -typing-inspection==0.4.1 -typing_extensions==4.14.1 -urllib3==2.5.0 -websocket-client==1.8.0 -yarl==1.20.1