import asyncio import time import logging.config from pybit import exceptions from pybit.unified_trading import HTTP from logger_helper.logger_helper import LOGGING_CONFIG 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 logging.config.dictConfig(LOGGING_CONFIG) logger = logging.getLogger("futures") async def info_access_open_deal(message, symbol, trade_mode, margin_mode, leverage, qty): human_margin_mode = 'Isolated' if margin_mode == 'ISOLATED_MARGIN' else 'Cross' text = f'''Позиция была успешна открыта! Торговая пара: {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('Сделка не была совершена, превышен лимит максимального количества ставок в серии.') async def error_max_risk(message): 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' """ 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) order_type = data_main_stgs.get('entry_order_type', 'Market') 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) 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, message) price = await price_symbol.get_price(tg_id) # Установка маржинального режима 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=order_type, qty=next_quantity, leverage=int(data_main_stgs['size_leverage']), price=limit_price if order_type == 'Limit' else None, takeProfit=takeProfit, stopLoss=loss_profit, orderLinkId=f"deal_{symbol}_{int(time.time())}" ) 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('ret_msg', 'неизвестная ошибка')}") except exceptions.InvalidRequestError as e: logger.error(f"InvalidRequestError: {e}") await message.answer('Ошибка: неверно указана торговая пара или параметры.') except Exception as e: logger.error(f"Ошибка при совершении сделки: {e}") 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: logger.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: logger.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)