diff --git a/BybitBot_API.py b/BybitBot_API.py index 1eff3a4..7ead8b1 100644 --- a/BybitBot_API.py +++ b/BybitBot_API.py @@ -1,7 +1,7 @@ import asyncio import logging.config from aiogram import Bot, Dispatcher - +from aiogram.fsm.storage.redis import RedisStorage 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 @@ -17,8 +17,9 @@ from config import TOKEN_TG_BOT_1 logging.config.dictConfig(LOGGING_CONFIG) logger = logging.getLogger("main") +storage = RedisStorage.from_url("redis://localhost:6379/0") bot = Bot(token=TOKEN_TG_BOT_1) -dp = Dispatcher() +dp = Dispatcher(storage=storage) async def main() -> None: @@ -37,7 +38,10 @@ async def main() -> None: dp.include_router(router_register_bybit_api) dp.include_router(router_functions_bybit_trade) - await dp.start_polling(bot) + try: + await dp.start_polling(bot) + except asyncio.CancelledError: + logger.info("Bot is off") if __name__ == '__main__': diff --git a/app/services/Bybit/functions/Futures.py b/app/services/Bybit/functions/Futures.py index cd505d1..911c22f 100644 --- a/app/services/Bybit/functions/Futures.py +++ b/app/services/Bybit/functions/Futures.py @@ -367,6 +367,10 @@ async def open_position( 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.", @@ -431,6 +435,11 @@ async def open_position( order_value = float(quantity) * price_for_calc if order_value < min_order_value: + logger.error( + f"Сумма ордера слишком мала: {order_value:.2f} USDT. " + f"Минимум для торговли — {min_order_value} USDT. " + f"Пожалуйста, увеличьте количество позиций." + ) await message.answer( f"Сумма ордера слишком мала: {order_value:.2f} USDT. " f"Минимум для торговли — {min_order_value} USDT. " @@ -581,6 +590,7 @@ async def open_position( 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, @@ -593,37 +603,6 @@ async def open_position( ) -async def trading_cycle(tg_id, message, side, margin_mode, symbol, starting_quantity): - """ - Цикл торговой логики с учётом таймера пользователя. - """ - try: - timer_data = await rq.get_user_timer(tg_id) - timer_min = 0 - if isinstance(timer_data, dict): - timer_min = timer_data.get("timer_minutes") or timer_data.get("timer") or 0 - else: - timer_min = timer_data or 0 - - timer_sec = timer_min * 60 - - if timer_sec > 0: - await asyncio.sleep(timer_sec) - - await open_position( - tg_id, - message, - side=side, - margin_mode=margin_mode, - symbol=symbol, - quantity=starting_quantity, - ) - except asyncio.CancelledError: - logger.info( - f"Торговый цикл для пользователя {tg_id} был отменён.", exc_info=True - ) - - async def set_take_profit_stop_loss( tg_id: int, message, @@ -643,7 +622,7 @@ async def set_take_profit_stop_loss( 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(f"Режим TP/SL уже установлен для {symbol}") + logger.info("Режим TP/SL уже установлен для %s - пропускаем", symbol) else: raise diff --git a/app/services/Bybit/functions/functions.py b/app/services/Bybit/functions/functions.py index b639aed..5129e60 100644 --- a/app/services/Bybit/functions/functions.py +++ b/app/services/Bybit/functions/functions.py @@ -9,7 +9,7 @@ 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, - trading_cycle, open_position, close_trade_after_delay, safe_float, + 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 @@ -29,6 +29,7 @@ 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: @@ -63,7 +64,6 @@ async def start_bybit_trade_message(message: Message) -> None: """ balance = await get_balance(message.from_user.id, message) tg_id = message.from_user.id - await run_ws_for_user(tg_id, message) if balance: await run_ws_for_user(tg_id, message) @@ -229,14 +229,40 @@ async def start_trading_process(callback: CallbackQuery) -> None: timer_minute = timer_data or 0 if timer_minute > 0: - await message.answer(f"Торговля начнётся через {timer_minute} мин.") - await trading_cycle(tg_id, message, side=side, margin_mode=margin_mode, symbol=symbol, - starting_quantity=starting_quantity) - await rq.update_user_timer(tg_id, minutes=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.CancelledError: + logger.exception(f"Торговый цикл для пользователя {tg_id} был отменён.", exc_info=True) + 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 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: """ diff --git a/app/telegram/Keyboards/inline_keyboards.py b/app/telegram/Keyboards/inline_keyboards.py index 993984d..8efe82f 100644 --- a/app/telegram/Keyboards/inline_keyboards.py +++ b/app/telegram/Keyboards/inline_keyboards.py @@ -9,6 +9,10 @@ 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="Назад", @@ -46,10 +50,8 @@ trading_markup = InlineKeyboardMarkup(inline_keyboard=[ ]) start_trading_markup = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="На главную", callback_data='back_to_main')], [InlineKeyboardButton(text="Начать торговлю", callback_data="clb_start_chatbot_trading")], - [InlineKeyboardButton(text="Установить таймер", callback_data="clb_set_timer")], - + [InlineKeyboardButton(text="На главную", callback_data='back_to_main')], ]) cancel = InlineKeyboardMarkup(inline_keyboard=[ diff --git a/app/telegram/functions/condition_settings/settings.py b/app/telegram/functions/condition_settings/settings.py index 126eb36..9958269 100644 --- a/app/telegram/functions/condition_settings/settings.py +++ b/app/telegram/functions/condition_settings/settings.py @@ -90,10 +90,7 @@ async def process_timer_input(message: Message, state: FSMContext): 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 message.answer(f"Таймер установлен на {minutes} минут.\nНажмите кнопку 'Начать торговлю' для запуска.", - reply_markup=inline_markup.start_trading_markup) - - + await timer_message(message.from_user.id, message, state) await state.clear() except ValueError: await message.reply("Пожалуйста, введите корректное число.")