import asyncio import logging.config from aiogram import F, Router from logger_helper.logger_helper import LOGGING_CONFIG from app.services.Bybit.functions import Futures, min_qty from app.services.Bybit.functions.Futures import open_position, close_user_trade, get_active_positions, trading_cycle from app.services.Bybit.functions.balance import get_balance import app.telegram.Keyboards.inline_keyboards as inline_markup import app.telegram.Keyboards.reply_keyboards as reply_markup from pybit.unified_trading import HTTP import app.telegram.database.requests as rq from aiogram.types import Message, CallbackQuery # FSM - Механизм состояния from aiogram.fsm.state import State, StatesGroup 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() class state_update_symbol(StatesGroup): symbol = State() class state_update_entry_type(StatesGroup): entry_type = State() class TradeSetup(StatesGroup): waiting_for_timer = State() waiting_for_positive_percent = State() class state_limit_price(StatesGroup): price = State() @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, state: FSMContext): api = await rq.get_bybit_api_key(callback.from_user.id) secret = await rq.get_bybit_secret_key(callback.from_user.id) balance = await get_balance(callback.from_user.id, callback.message) if balance: symbol = await rq.get_symbol(callback.from_user.id) text = f'''💎 Торговля на Bybit ⚖️ Ваш баланс (USDT): {balance} 📊 Текущая торговая пара: {symbol} Как начать торговлю? 1️⃣ Проверьте и тщательно настройте все параметры в вашем профиле. 2️⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару, без лишних символов (например: BTCUSDT). 3️⃣ Нажмите кнопку 'Выбрать тип входа' и после нажмите начать торговлю. ''' await callback.message.edit_text(text=text, parse_mode='html', reply_markup=inline_markup.trading_markup) async def start_bybit_trade_message(message, state): api = await rq.get_bybit_api_key(message.from_user.id) secret = await rq.get_bybit_secret_key(message.from_user.id) balance = await get_balance(message.from_user.id, message) if balance: symbol = await rq.get_symbol(message.from_user.id) text = f'''💎 Торговля на Bybit ⚖️ Ваш баланс (USDT): {balance} 📊 Текущая торговая пара: {symbol} Как начать торговлю? 1️⃣ Проверьте и тщательно настройте все параметры в вашем профиле. 2️⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару, без лишних символов (например: BTCUSDT). 3️⃣ Нажмите кнопку 'Выбрать тип входа' и после нажмите начать торговлю. ''' 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): await state.set_state(state_update_symbol.symbol) 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): 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, state) await state.clear() @router_functions_bybit_trade.callback_query(F.data == 'clb_update_entry_type') async def update_entry_type_message(callback: CallbackQuery, state: FSMContext): 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() @router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith('entry_order_type:')) async def entry_order_type_callback(callback: CallbackQuery, state: FSMContext): order_type = callback.data.split(':')[1] if order_type not in ['Market', 'Limit']: await callback.answer("Ошибка выбора", show_alert=True) return if order_type == 'Limit': await state.set_state(state_limit_price.price) await callback.message.answer("Введите цену лимитного ордера:", reply_markup=inline_markup.cancel) await callback.answer() return 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}", reply_markup=inline_markup.start_trading_markup) await callback.answer() except Exception as e: logger.error(f"Произошла ошибка при обновлении типа входа в позицию: {e}") await callback.message.answer("Произошла ошибка при обновлении типа входа в позицию", reply_markup=inline_markup.back_to_main) await state.clear() @router_functions_bybit_trade.message(state_limit_price.price) async def set_limit_price(message: Message, state: FSMContext): try: price = float(message.text) if price <= 0: await message.answer("Цена должна быть положительным числом. Попробуйте снова.", reply_markup=inline_markup.cancel) return except ValueError: await message.answer("Некорректный формат цены. Введите число.", reply_markup=inline_markup.cancel) return await state.update_data(entry_order_type='Limit', limit_price=price) data = await state.get_data() await rq.update_entry_order_type(message.from_user.id, 'Limit') await rq.update_limit_price(message.from_user.id, price) await message.answer(f"Цена лимитного ордера установлена: {price}", reply_markup=inline_markup.start_trading_markup) await state.clear() @router_functions_bybit_trade.callback_query(F.data == "clb_my_deals") async def show_my_trades_callback(callback: CallbackQuery): tg_id = callback.from_user.id 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) trades = await get_active_positions(callback.message, api_key, secret_key, symbol) if not trades: await callback.message.answer("Нет активных позиций.") await callback.answer() return keyboard = inline_markup.create_trades_inline_keyboard(trades) await callback.message.answer( "Выберите сделку из списка:", reply_markup=keyboard ) await callback.answer() @router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith('select_trade:')) async def on_trade_selected(callback: CallbackQuery): symbol = callback.data.split(':')[1] tg_id = callback.from_user.id api_key = await rq.get_bybit_api_key(tg_id) secret_key = await rq.get_bybit_secret_key(tg_id) positions = await get_active_positions(callback.message, api_key, secret_key, symbol) # Если несколько позиций по символу, можно выбрать нужную или взять первую if not positions: await callback.message.answer("Позиция не найдена") await callback.answer() return pos = positions[0] symbol = pos.get('symbol') side = pos.get('side') entry_price = pos.get('entryPrice') # Цена открытия позиции current_price = pos.get('price') # Текущая цена (если есть) text = (f"Информация по позиции:\n" f"Название: {symbol}\n" f"Направление: {side}\n" f"Цена покупки: {entry_price}\n" f"Текущая цена: {current_price if current_price else 'N/A'}") keyboard = inline_markup.create_close_deal_markup(symbol) await callback.message.answer(text, reply_markup=keyboard) await callback.answer() @router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_deal:")) async def close_trade_callback(callback: CallbackQuery): symbol = callback.data.split(':')[1] tg_id = callback.from_user.id result = await close_user_trade(tg_id, symbol) if result: await callback.message.answer(f"Сделка {symbol} успешно закрыта.") else: await callback.message.answer(f"Не удалось закрыть сделку {symbol}.") await callback.answer() @router_functions_bybit_trade.callback_query(F.data == "clb_start_chatbot_trading") async def start_trading_process(callback: CallbackQuery, state: FSMContext): tg_id = callback.from_user.id message = callback.message # Получаем настройки пользователя data_main_stgs = await rq.get_user_main_settings(tg_id) 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) margin_mode = data_main_stgs.get('margin_type', 'Isolated') trading_mode = data_main_stgs.get('trading_mode') # Проверка API ключей if not api_key or not secret_key: await message.answer("❗️ У вас не настроены API ключи для Bybit.") await callback.answer() return # Проверка режима торговли if trading_mode not in ['Long', 'Short', 'Smart', 'Switch']: await message.answer(f"❗️ Некорректный торговый режим: {trading_mode}") await callback.answer() return # Проверка допустимости маржинального режима if margin_mode not in ['Isolated', 'Cross']: margin_mode = 'Isolated' # Проверяем открытые позиции и маржинальный режим client = HTTP(api_key=api_key, api_secret=secret_key) try: positions_resp = client.get_positions(category='linear', symbol=symbol) positions = positions_resp.get('result', {}).get('list', []) except Exception as e: logger.error(f"Ошибка при получении позиций: {e}") positions = [] for pos in positions: size = pos.get('size') existing_margin_mode = pos.get('margin_mode') if size and float(size) > 0 and existing_margin_mode and existing_margin_mode != margin_mode: await callback.answer( f"⚠️ Маржинальный режим нельзя менять при открытой позиции " f"(текущий режим: {existing_margin_mode})", show_alert=True) return # Определяем сторону для открытия позиции if trading_mode == 'Long': side = 'Buy' elif trading_mode == 'Short': side = 'Sell' else: await message.answer(f"Режим торговли '{trading_mode}' пока не поддерживается.") await callback.answer() return # Сообщаем о начале торговли await message.answer("Начинаю торговлю с использованием текущих настроек...") # Открываем позицию (вызывает Futures.open_position) success = await open_position(tg_id, message, side=side, margin_mode=margin_mode) if not success: await message.answer('⚠️ Ошибка при совершении сделки', reply_markup=inline_markup.back_to_main) return # Проверяем таймер и информируем пользователя timer_data = await rq.get_user_timer(tg_id) timer_minutes = timer_data.get('timer') if isinstance(timer_data, dict) else timer_data if timer_minutes and timer_minutes > 0: await message.answer(f"Торговля будет работать по таймеру: {timer_minutes} мин.") asyncio.create_task(trading_cycle(tg_id, message)) else: await message.answer( "Торговля начата без ограничения по времени. Для остановки нажмите кнопку 'Закрыть сделку'.", reply_markup=inline_markup.create_close_deal_markup(symbol) ) await callback.answer() @router_functions_bybit_trade.callback_query(F.data == "clb_cancel") async def cancel(callback: CallbackQuery, state: FSMContext): await state.clear() await callback.message.answer("Отменено!", reply_markup=inline_markup.back_to_main) await callback.answer()