Files
stcs/app/services/Bybit/functions/Futures.py
2025-08-22 16:47:33 +05:00

239 lines
9.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import asyncio
import time
import logging
from pybit import exceptions
from pybit.unified_trading import HTTP
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
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)
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="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('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:
logging.error(f"InvalidRequestError: {e}")
await message.answer('Ошибка: неверно указана торговая пара или параметры.')
except Exception as e:
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)