From 6ec99dc9a7d93dc478f6acfbbad78a290b4ee0d7 Mon Sep 17 00:00:00 2001 From: algizn97 Date: Fri, 22 Aug 2025 16:47:33 +0500 Subject: [PATCH] Update functions --- app/services/Bybit/functions/Futures.py | 506 +++++++++------------- app/services/Bybit/functions/functions.py | 272 +++++++----- app/telegram/functions/functions.py | 4 +- 3 files changed, 379 insertions(+), 403 deletions(-) diff --git a/app/services/Bybit/functions/Futures.py b/app/services/Bybit/functions/Futures.py index afd28a0..360c7ca 100644 --- a/app/services/Bybit/functions/Futures.py +++ b/app/services/Bybit/functions/Futures.py @@ -1,325 +1,239 @@ -import time - -from typing import Optional -from asyncio import Handle - -from annotated_types import T +import asyncio +import time +import logging from pybit import exceptions from pybit.unified_trading import HTTP -from pybit.unified_trading import WebSocket -from app.services.Bybit.functions import price_symbol +import app.services.Bybit.functions.price_symbol as price_symbol import app.services.Bybit.functions.balance as balance_g import app.telegram.database.requests as rq -import logging -logging.basicConfig(level=logging.DEBUG) - -def handle_message(message): - print(message) async def info_access_open_deal(message, symbol, trade_mode, margin_mode, leverage, qty): - match margin_mode: - case 'ISOLATED_MARGIN': - margin_mode = 'Isolated' - case 'REGULAR_MARGIN': - margin_mode = 'Cross' - + human_margin_mode = 'Isolated' if margin_mode == 'ISOLATED_MARGIN' else 'Cross' text = f'''Позиция была успешна открыта! - Торговая пара: {symbol} - Движение: {trade_mode} - Тип-маржи: {margin_mode} - Кредитное плечо: {leverage} - Количество: {qty} - ''' - +Торговая пара: {symbol} +Движение: {trade_mode} +Тип-маржи: {human_margin_mode} +Кредитное плечо: {leverage} +Количество: {qty} +''' await message.answer(text=text, parse_mode='html') + async def error_max_step(message): - await message.answer('Сделка не была совершена, превышен лимит максимального количества ставок') + await message.answer('Сделка не была совершена, превышен лимит максимального количества ставок в серии.') + async def error_max_risk(message): - await message.answer('Сделка не была совершена, слишком высокий риск') + await message.answer('Сделка не была совершена, риск убытка превышает допустимый лимит.') + + +async def open_position(tg_id, message, side: str, margin_mode: str): + """ + Открытие позиции (торговля с мартингейлом и управлением рисками) + + :param tg_id: Telegram ID пользователя + :param message: объект сообщения Telegram для ответов + :param side: 'Buy' для Long, 'Sell' для Short + :param margin_mode: 'Isolated' или 'Cross' + """ -async def contract_long(tg_id, message, margin_mode): 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) - - data_main_stgs = await rq.get_user_main_settings(tg_id) - data_risk_management_stgs = await rq.get_user_risk_management_settings(tg_id) - - match margin_mode: - case 'Isolated': - margin_mode = 'ISOLATED_MARGIN' - case 'Cross': - margin_mode = 'REGULAR_MARGIN' - - client = HTTP( - api_key=api_key, - api_secret=secret_key - ) - - try: - balance = 0 - price = 0 - - balance = await balance_g.get_balance(tg_id) - price = await price_symbol.get_price(tg_id, message) - - client.set_margin_mode( - setMarginMode=margin_mode # margin_type - ) - - martingale_factor = float(data_main_stgs['martingale_factor']) # Исправлено: было maximal_quantity - max_martingale_steps = int(data_main_stgs['maximal_quantity']) - starting_quantity = float(data_main_stgs['starting_quantity']) - max_risk_percent = float(data_risk_management_stgs['max_risk_deal']) - loss_profit = float(data_risk_management_stgs['price_loss']) - takeprofit= float(data_risk_management_stgs['price_profit']) - commission_fee = float(data_risk_management_stgs.get('commission_fee', 0)) - takeProfit_raw = takeprofit - takeProfit = takeProfit_raw - commission_fee # уменьшаем TP на комиссию - - if takeProfit < 0: - takeProfit = 0 - - # Инициализация переменных - next_quantity = starting_quantity - last_quantity = starting_quantity - realised_pnl = 0.0 - - current_martingale_step = 0 # Текущая ставка в серии - - next_quantity = 0 - realised_pnl = 0 - - last_quantity = starting_quantity - - # Пример расчёта следующего размера позиции - try: - position_info = client.get_positions(category='linear', symbol=SYMBOL) - position = position_info['result']['list'][0] # или другой нужный индекс - - realised_pnl = float(position['unrealisedPnl']) - - if realised_pnl > 0: - starting_quantity = next_quantity - current_martingale_step = 0 - elif not realised_pnl: - next_quantity = starting_quantity - current_martingale_step += 1 - else: - current_martingale_step += 1 - next_quantity = last_quantity * martingale_factor - starting_quantity = next_quantity - except Exception as e: - print("Не получены позиции") - next_quantity = starting_quantity - - potential_loss = (next_quantity * float(price)) * (loss_profit / 100) - allowed_loss = float(balance) * (max_risk_percent / 100) - - if current_martingale_step >= max_martingale_steps: - print("Достигнут максимум ставок в серии (8)!") - print("Торговля не продолжится") - - await error_max_step(message) - else: - if potential_loss > allowed_loss: - print(f"ОШИБКА: Риск превышен!") - print(f"Ручной qty = {next_quantity} → Убыток = {potential_loss} USDT") - print(f"Разрешено = {allowed_loss} USDT (1% от баланса)") - - await error_max_risk(message) - else: - print(f"Риск в допустимых пределах. Qty = {next_quantity}") - - r = client.place_order( - category='linear', - symbol=SYMBOL, - side='Buy', - orderType="Market", - leverage=int(data_main_stgs['size_leverage']), - qty=next_quantity, - takeProfit=takeProfit, # TP - закрывает позицию, когда цена достигает нужного уровня - stopProfit=float(data_risk_management_stgs['price_loss']), # SL - закрывает позицию, когда убыток достигает нужного уровня - orderLinkId=f"deal_{SYMBOL}_{time.time()}" - ) - - await info_access_open_deal(message, SYMBOL, data_main_stgs['trading_mode'], margin_mode, data_main_stgs['size_leverage'], next_quantity) - - except exceptions.InvalidRequestError as e: - logging.error(f"Неверно указана торговая пара: {e}") - await message.answer('Недостаточно баланса') - except Exception as e: - logging.error(f"Ошибка при совершении сделки: {e}") - await message.answer('⚠️ Ошибка при совершении сделки') - -async def contract_short(tg_id, message, margin_mode): - 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) - - data_main_stgs = await rq.get_user_main_settings(tg_id) - data_risk_management_stgs = await rq.get_user_risk_management_settings(tg_id) - - match margin_mode: - case 'Isolated': - margin_mode = 'ISOLATED_MARGIN' - case 'Cross': - margin_mode = 'REGULAR_MARGIN' - - client = HTTP( - api_key=api_key, - api_secret=secret_key - ) - - try: - balance = 0 - price = 0 - - balance = await balance_g.get_balance(tg_id) - price = await price_symbol.get_price(tg_id, message) - - client.set_margin_mode( - setMarginMode=margin_mode # margin_type - ) - - martingale_factor = float(data_main_stgs['martingale_factor']) # Исправлено: было maximal_quantity - max_martingale_steps = int(data_main_stgs['maximal_quantity']) - starting_quantity = float(data_main_stgs['starting_quantity']) - max_risk_percent = float(data_risk_management_stgs['max_risk_deal']) - loss_profit = float(data_risk_management_stgs['price_loss']) - takeprofit = float(data_risk_management_stgs['price_profit']) - commission_fee = float(data_risk_management_stgs.get('commission_fee', 0)) - takeProfit_raw = takeprofit - takeProfit = takeProfit_raw - commission_fee # уменьшаем TP на комиссию - - if takeProfit < 0: - takeProfit = 0 - - # Инициализация переменных - next_quantity = starting_quantity - last_quantity = starting_quantity - realised_pnl = 0.0 - - current_martingale_step = 0 # Текущая ставка в серии - - next_quantity = 0 - realised_pnl = 0 - - last_quantity = starting_quantity - - # Пример расчёта следующего размера позиции - try: - position_info = client.get_positions(category='linear', symbol=SYMBOL) - position = position_info['result']['list'][0] # или другой нужный индекс - - realised_pnl = float(position['unrealisedPnl']) - - if realised_pnl > 0: - starting_quantity = next_quantity - current_martingale_step = 0 - elif not realised_pnl: - next_quantity = starting_quantity - current_martingale_step += 1 - else: - current_martingale_step += 1 - next_quantity = last_quantity * martingale_factor - starting_quantity = next_quantity - except Exception as e: - print("Не получены позиции") - next_quantity = starting_quantity - - potential_loss = (next_quantity * float(price)) * (loss_profit / 100) - allowed_loss = float(balance) * (max_risk_percent / 100) - - if current_martingale_step >= max_martingale_steps: - print("Достигнут максимум ставок в серии (8)!") - print("Торговля не продолжится") - - await error_max_step(message) - else: - if potential_loss > allowed_loss: - print(f"ОШИБКА: Риск превышен!") - print(f"Ручной qty = {next_quantity} → Убыток = {potential_loss} USDT") - print(f"Разрешено = {allowed_loss} USDT (1% от баланса)") - - await error_max_risk(message) - else: - print(f"Риск в допустимых пределах. Qty = {next_quantity}") - - r = client.place_order( - category='linear', - symbol=SYMBOL, - side='Sell', - orderType="Market", - leverage=int(data_main_stgs['size_leverage']), - qty=next_quantity, - orderLinkId=f"deal_{SYMBOL}_{time.time()}" - ) - - await info_access_open_deal(message, SYMBOL, data_main_stgs['trading_mode'], margin_mode, data_main_stgs['size_leverage'], next_quantity) - - except exceptions.InvalidRequestError as e: - logging.error(f"Error in open_deal: {e}") - await message.answer('Недостаточно баланса') - except Exception as e: - logging.error(f"Error in open_deal: {e}") - await message.answer('⚠️ Ошибка при совершении сделки') - - - -async def open_market_order(tg_id, message, api_key, secret_key): - data_main_stgs = await rq.get_user_main_settings(tg_id) - trading_mode = data_main_stgs['trading_mode'] - margin_mode = data_main_stgs.get('margin_type') - - if trading_mode == 'Long': - await contract_long(tg_id, message, margin_mode) - elif trading_mode == 'Short': - await contract_short(tg_id, message, margin_mode) - elif trading_mode == 'Smart': - await message.answer("Режим Smart пока недоступен") - elif trading_mode == 'Switch': - await message.answer("Режим Switch пока недоступен") - - -async def open_limit_order(tg_id, message, price, api_key, secret_key): - data_main_stgs = await rq.get_user_main_settings(tg_id) - trading_mode = data_main_stgs['trading_mode'] - margin_mode = data_main_stgs.get('margin_type') - order_type = await rq.get_entry_order_type(tg_id) - - - client = HTTP( - api_key=api_key, - api_secret=secret_key - ) - symbol = await rq.get_symbol(tg_id) - qty = float(data_main_stgs['starting_quantity']) - side = 'Buy' if trading_mode == 'Long' else 'Short' + data_main_stgs = await rq.get_user_main_settings(tg_id) + data_risk_stgs = await rq.get_user_risk_management_settings(tg_id) + + bybit_margin_mode = 'ISOLATED_MARGIN' if margin_mode == 'Isolated' else 'REGULAR_MARGIN' + + client = HTTP(api_key=api_key, api_secret=secret_key) try: + balance = await balance_g.get_balance(tg_id) + price = await price_symbol.get_price(tg_id, message) + + # Установка маржинального режима + client.set_margin_mode(setMarginMode=bybit_margin_mode) + + martingale_factor = float(data_main_stgs['martingale_factor']) + max_martingale_steps = int(data_main_stgs['maximal_quantity']) + starting_quantity = float(data_main_stgs['starting_quantity']) + max_risk_percent = float(data_risk_stgs['max_risk_deal']) + loss_profit = float(data_risk_stgs['price_loss']) + takeprofit = float(data_risk_stgs['price_profit']) + commission_fee = float(data_risk_stgs.get('commission_fee', 0)) + + takeProfit = max(takeprofit - commission_fee, 0) + + current_martingale_step = 0 + next_quantity = starting_quantity + last_quantity = starting_quantity + realised_pnl = 0.0 + + # Получаем текущие открытые позиции по символу + positions_resp = client.get_positions(category='linear', symbol=symbol) + positions_list = positions_resp.get('result', {}).get('list', []) + current_martingale_step = await rq.get_martingale_step(tg_id) + if positions_list: + position = positions_list[0] + realised_pnl = float(position.get('unrealisedPnl', 0.0)) + + + if realised_pnl > 0: + current_martingale_step = 0 + next_quantity = starting_quantity + + else: + current_martingale_step += 1 + if current_martingale_step > max_martingale_steps: + await error_max_step(message) + return + next_quantity = starting_quantity * (martingale_factor ** current_martingale_step) + else: + # Позиция не открыта — начинаем с начальной ставки + next_quantity = starting_quantity + current_martingale_step = 0 + + # Проверяем риск убытка + potential_loss = next_quantity * price * (loss_profit / 100) + allowed_loss = balance * (max_risk_percent / 100) + + if potential_loss > allowed_loss: + await error_max_risk(message) + return + + # Отправляем запрос на открытие ордера response = client.place_order( category='linear', symbol=symbol, side=side, - orderType='Limit', - qty=qty, - price=price, - timeInForce='GTC', - orderLinkId=f"order_{int(time.time())}" + orderType="Market", + qty=next_quantity, + leverage=int(data_main_stgs['size_leverage']), + takeProfit=takeProfit, + stopLoss=loss_profit, + orderLinkId=f"deal_{symbol}_{int(time.time())}" ) - if response.get('retCode') == 0: - await message.answer(f"Limit ордер открыт: {side} {qty} {symbol} по цене {price}") - await rq.update_user_trades(tg_id, symbol=symbol, side=order_type) + if response.get('ret_code', -1) == 0: + await info_access_open_deal(message, symbol, data_main_stgs['trading_mode'], bybit_margin_mode, + data_main_stgs['size_leverage'], next_quantity) + await rq.update_martingale_step(tg_id, current_martingale_step) else: - await message.answer(f"Ошибка открытия ордера: {response.get('retMsg')}") + await message.answer(f"Ошибка открытия ордера: {response.get('ret_msg', 'неизвестная ошибка')}") + + except exceptions.InvalidRequestError as e: + logging.error(f"InvalidRequestError: {e}") + await message.answer('Ошибка: неверно указана торговая пара или параметры.') except Exception as e: - logging.error(f"Ошибка при открытии лимитного ордера: {e}") - await message.answer("Ошибка при открытии ордера") \ No newline at end of file + logging.error(f"Ошибка при совершении сделки: {e}") + await message.answer('⚠️ Ошибка при совершении сделки') + + +async def trading_cycle(tg_id, message): + start_time = time.time() + timer_min = await rq.get_user_timer(tg_id) + timer_sec = timer_min * 60 if timer_min else 0 + + while True: + elapsed = time.time() - start_time + if timer_sec > 0 and elapsed > timer_sec: + await message.answer("Время работы по таймеру истекло. Торговля остановлена.") + await rq.update_martingale_step(tg_id, 0) + break + + # Проверяем позиции + data_main_stgs = await rq.get_user_main_settings(tg_id) + side = 'Buy' if data_main_stgs['trading_mode'] == 'Long' else 'Sell' + margin_mode = data_main_stgs.get('margin_type', 'Isolated') + + # Можно добавлять логику по PNL, стоп-лоссам, тейк-профитам + + await open_position(tg_id, message, side=side, margin_mode=margin_mode) + + await asyncio.sleep(10) + + + +async def get_active_positions(message, api_key, secret_key, symbol): + + client = HTTP( + api_key=api_key, + api_secret=secret_key + ) + instruments_resp = client.get_instruments_info(category='linear') + if instruments_resp.get('ret_code') != 0: + return [] + symbols = [item['symbol'] for item in instruments_resp.get('result', {}).get('list', [])] + + active_positions = [] + + async def fetch_positions(symbol): + try: + resp = client.get_positions(category='linear', symbol=symbol) + if resp.get('ret_code') == 0: + positions = resp.get('result', {}).get('list', []) + for pos in positions: + if pos.get('size') and float(pos['size']) > 0: + active_positions.append(pos) + except Exception as e: + logging.error(f"Ошибка при получении позиций: {e}") + await message.answer('⚠️ Ошибка при получении позиций') + + for sym in symbols: + await fetch_positions(sym) + + return active_positions + + +async def close_user_trade(tg_id: int, symbol: str) -> bool: + 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) + + # Получаем текущие открытые позиции по символу (пример для linear фьючерсов) + positions_resp = client.get_positions(category="linear", symbol=symbol) + + ret_code = positions_resp.get('ret_code') + result = positions_resp.get('result') + + if ret_code != 0 or not result or not result.get('list'): + return False + + positions_list = result['list'] + if not positions_list: + return False + + position = positions_list[0] + qty = abs(float(position['size'])) + side = position['side'] + + if qty == 0: + return False + + # Определяем сторону закрытия — противоположная открытой позиции + close_side = "Sell" if side == "Buy" else "Buy" + + try: + response = client.place_order( + category="linear", + symbol=symbol, + side=close_side, + orderType="Market", + qty=str(qty), + timeInForce="GoodTillCancel", + reduceOnly=True + ) + return response['ret_code'] == 0 + except Exception as e: + logging.error(f"Ошибка закрытия сделки {symbol} для пользователя {tg_id}: {e}") + + return False + + +def get_positive_percent(negative_percent: float, manual_positive_percent: float | None) -> float: + if manual_positive_percent and manual_positive_percent > 0: + return manual_positive_percent + return abs(negative_percent) \ No newline at end of file diff --git a/app/services/Bybit/functions/functions.py b/app/services/Bybit/functions/functions.py index ac8b186..ef87d6e 100644 --- a/app/services/Bybit/functions/functions.py +++ b/app/services/Bybit/functions/functions.py @@ -1,10 +1,13 @@ -from aiogram import F, Router +import asyncio + +from aiogram import F, Router from app.services.Bybit.functions import Futures, func_min_qty -from app.services.Bybit.functions.Futures import open_market_order, open_limit_order +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 @@ -14,13 +17,21 @@ from aiogram.fsm.context import FSMContext router_functions_bybit_trade = Router() + class state_update_symbol(StatesGroup): symbol = State() + class state_update_entry_type(StatesGroup): entry_type = State() -@router_functions_bybit_trade.callback_query(F.data.in_(['clb_start_trading', 'clb_back_to_main'])) + +class TradeSetup(StatesGroup): + waiting_for_timer = State() + waiting_for_positive_percent = 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) @@ -30,7 +41,7 @@ async def clb_start_bybit_trade_message(callback: CallbackQuery, state: FSMConte symbol = await rq.get_symbol(callback.from_user.id) text = f'''💎 Торговля на Bybit - + ⚖️ Ваш баланс (USDT): {balance} 📊 Текущая торговая пара: {symbol} @@ -41,16 +52,17 @@ async def clb_start_bybit_trade_message(callback: CallbackQuery, state: FSMConte ''' 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: + if balance: symbol = await rq.get_symbol(message.from_user.id) text = f'''💎 Торговля на Bybit - + ⚖️ Ваш баланс (USDT): {balance} 📊 Текущая торговая пара: {symbol} @@ -62,15 +74,18 @@ async def start_bybit_trade_message(message, state): 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): ') + await callback.message.answer( + text='Укажите торговую пару заглавными буквами без пробелов и лишних символов (пример: BTCUSDT): ') + @router_functions_bybit_trade.message(state_update_symbol.symbol) async def update_symbol_for_trade(message: Message, state: FSMContext): - await state.update_data(symbol = message.text) + await state.update_data(symbol=message.text) data = await state.get_data() @@ -96,116 +111,161 @@ async def entry_order_type_callback(callback: CallbackQuery, state: FSMContext): await callback.answer("Ошибка выбора", show_alert=True) return - await state.update_data(entry_order_type=order_type) - await rq.update_entry_order_type(callback.from_user.id, order_type) - - if order_type == 'Limit': - await callback.answer("Вы выбрали Limit. Введите цену для лимитного ордера.") - await callback.message.answer("Введите цену лимитного ордера:") - await state.update_data(awaiting_limit_price=True) - else: - await callback.answer("Вы выбрали Market. Нажмите кнопку ниже, чтобы открыть сделку.") - await callback.message.answer("Нажмите кнопку, чтобы открыть сделку.", - reply_markup=inline_markup.open_deal_markup) - await callback.message.delete() - - -@router_functions_bybit_trade.message() -async def process_limit_price_message(message: Message, state: FSMContext): - data = await state.get_data() - if not data.get('awaiting_limit_price'): - return - try: - price = float(message.text) - if price <= 0: - raise ValueError() - except ValueError: - await message.answer("Ошибка: введите корректное положительное число для цены.") - return - - await state.update_data(limit_price=price, awaiting_limit_price=False) - - await message.answer(f"Цена лимитного ордера установлена: {price}. Нажмите кнопку ниже, чтобы открыть сделку.", - reply_markup=inline_markup.open_deal_markup) - - -@router_functions_bybit_trade.callback_query(F.data == "clb_open_deal") -async def open_deal(callback: CallbackQuery, state: FSMContext): - data_main_stgs = await rq.get_user_main_settings(callback.from_user.id) - data = await state.get_data() - order_type = await rq.get_entry_order_type(callback.from_user.id) - api = await rq.get_bybit_api_key(callback.from_user.id) - secret = await rq.get_bybit_secret_key(callback.from_user.id) - qty = data_main_stgs['starting_quantity'] - qty_min = await func_min_qty.get_min_qty(callback.from_user.id, callback.message) - - if qty < qty_min: - await callback.message.edit_text(f"Количество вашей ставки ({qty}) меньше минимального количества ({qty_min}) для данной торговой пары") - await callback.answer() - return - - if order_type == 'Market': - await open_market_order(callback.from_user.id, callback.message, api_key=api, secret_key=secret) - await rq.update_user_trades(callback.from_user.id, symbol=data.get('symbol'), side=order_type) - elif order_type == 'Limit': - price = data.get('limit_price') - if not price: - await callback.answer("Цена для лимитного ордера не задана. Введите сначала цену.") - return - await open_limit_order(callback.from_user.id, callback.message, price, api_key=api, secret_key=secret) - else: - await callback.answer("Неизвестный тип ордера.") - - await callback.message.edit_reply_markup() + await state.update_data(entry_order_type=order_type) + await rq.update_entry_order_type(callback.from_user.id, order_type) + await callback.answer("Тип входа в позицию был успешно обновлен") + except Exception as e: + await callback.message.answer("Произошла ошибка при обновлении типа входа в позицию") 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 - trades = await rq.get_user_trades(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) + + trades = await get_active_positions(callback.message, api_key, secret_key, symbol) if not trades: - await callback.message.answer("У вас ещё нет сделок.") + await callback.message.answer("Нет активных позиций.") await callback.answer() return - grouped = {} - for trade in trades: - symbol = trade['symbol'] if isinstance(trade, dict) else trade.symbol - grouped.setdefault(symbol, []).append(trade) + keyboard = inline_markup.create_trades_inline_keyboard(trades) - text_response = "Ваши сделки по валютным парам:\n\n" - for symbol, trade_list in grouped.items(): - text_response += f"{symbol}\n" - for t in trade_list: - side = t['side'] if isinstance(t, dict) else t.side - text_response += f" - {side}\n" - text_response += "\n" - - await callback.message.answer(text_response, parse_mode='html') + await callback.message.answer( + "Выберите сделку из списка:", + reply_markup=keyboard + ) await callback.answer() -# @router_functions_bybit_trade.callback_query(F.data == 'clb_open_deal') -# async def make_deal_bybit (callback: CallbackQuery): -# data_main_stgs = await rq.get_user_main_settings(callback.from_user.id) -# -# trade_mode = data_main_stgs['trading_mode'] -# qty = data_main_stgs['starting_quantity'] -# margin_mode = data_main_stgs['margin_type'] - qty_min = await func_min_qty.get_min_qty(callback.from_user.id, callback.message) -# -# if qty < qty_min: -# await callback.message.edit_text(f"Количество вашей ставки ({qty}) меньше минимального количества ({qty_min}) для данной торговой пары") -# else: -# match trade_mode: -# case 'Long': -# await Futures.contract_long(callback.from_user.id, callback.message, margin_mode) -# case 'Short': -# await Futures.contract_short(callback.from_user.id, callback.message, margin_mode) -# case 'Switch': -# await callback.message.edit_text('Режим Switch пока недоступен') -# case 'Smart': -# await callback.message.edit_text('Режим Smart пока недоступен') \ No newline at end of file +@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: + 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) + await open_position(tg_id, message, side=side, margin_mode=margin_mode) + + + # Проверяем таймер и информируем пользователя + timer_minutes = await rq.get_user_timer(tg_id) + 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() + diff --git a/app/telegram/functions/functions.py b/app/telegram/functions/functions.py index 28e9134..0a1b5fe 100644 --- a/app/telegram/functions/functions.py +++ b/app/telegram/functions/functions.py @@ -27,4 +27,6 @@ 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 + await message.edit_text("Выберите что настроить", reply_markup=inline_markup.special_settings_markup) + +