forked from kodorvan/stcs
Added documentation, added websocket and tasks
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
import asyncio
|
||||
import nest_asyncio
|
||||
|
||||
import time
|
||||
import json
|
||||
import logging.config
|
||||
from pybit import exceptions
|
||||
from pybit.unified_trading import HTTP, WebSocket
|
||||
from websocket import WebSocketConnectionClosedException
|
||||
|
||||
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
|
||||
@@ -16,8 +13,6 @@ import app.telegram.Keyboards.inline_keyboards as inline_markup
|
||||
logging.config.dictConfig(LOGGING_CONFIG)
|
||||
logger = logging.getLogger("futures")
|
||||
|
||||
nest_asyncio.apply()
|
||||
|
||||
|
||||
def safe_float(val) -> float:
|
||||
"""
|
||||
@@ -33,6 +28,53 @@ def safe_float(val) -> float:
|
||||
return 0.0
|
||||
|
||||
|
||||
def format_trade_details_position(data):
|
||||
"""
|
||||
Форматирует информацию о сделке в виде строки.
|
||||
"""
|
||||
msg = data.get('data', [{}])[0]
|
||||
|
||||
closed_size = float(msg.get('closedSize', 0))
|
||||
symbol = msg.get('symbol', 'N/A')
|
||||
entry_price = float(msg.get('execPrice', 0))
|
||||
qty = float(msg.get('execQty', 0))
|
||||
order_type = msg.get('orderType', 'N/A')
|
||||
side = msg.get('side', '')
|
||||
commission_fee = float(msg.get('execFee', 0))
|
||||
pnl = float(msg.get('execPnl', 0))
|
||||
|
||||
movement = ''
|
||||
if side.lower() == 'buy':
|
||||
movement = 'Покупка'
|
||||
elif side.lower() == 'sell':
|
||||
movement = 'Продажа'
|
||||
else:
|
||||
movement = side
|
||||
|
||||
if closed_size > 0:
|
||||
return (
|
||||
f"Сделка закрыта:\n"
|
||||
f"Торговая пара: {symbol}\n"
|
||||
f"Цена исполнения: {entry_price:.6f}\n"
|
||||
f"Количество: {qty}\n"
|
||||
f"Закрыто позиций: {closed_size}\n"
|
||||
f"Тип ордера: {order_type}\n"
|
||||
f"Движение: {movement}\n"
|
||||
f"Комиссия за сделку: {commission_fee:.6f}\n"
|
||||
f"Реализованная прибыль: {pnl:.6f} USDT"
|
||||
)
|
||||
else:
|
||||
return (
|
||||
f"Сделка открыта:\n"
|
||||
f"Торговая пара: {symbol}\n"
|
||||
f"Цена исполнения: {entry_price:.6f}\n"
|
||||
f"Количество: {qty}\n"
|
||||
f"Тип ордера: {order_type}\n"
|
||||
f"Движение: {movement}\n"
|
||||
f"Комиссия за сделку: {commission_fee:.6f}"
|
||||
)
|
||||
|
||||
|
||||
def parse_pnl_from_msg(msg) -> float:
|
||||
"""
|
||||
Извлекает реализованную прибыль/убыток из сообщения.
|
||||
@@ -49,8 +91,6 @@ async def handle_execution_message(message, msg: dict) -> None:
|
||||
Обработчик сообщений об исполнении сделки.
|
||||
Логирует событие и проверяет условия для мартингейла и TP.
|
||||
"""
|
||||
logger.info(f"Исполнена сделка: {msg}")
|
||||
await message.answer(f"Исполнена сделка: {msg}")
|
||||
|
||||
pnl = parse_pnl_from_msg(msg)
|
||||
tg_id = message.from_user.id
|
||||
@@ -66,6 +106,9 @@ async def handle_execution_message(message, msg: dict) -> None:
|
||||
positions_list = positions_resp.get('result', {}).get('list', [])
|
||||
position = positions_list[0] if positions_list else None
|
||||
|
||||
trade_info = format_trade_details_position(msg)
|
||||
await message.answer(f"{trade_info}", reply_markup=inline_markup.back_to_main)
|
||||
|
||||
liquidation_threshold = -100
|
||||
|
||||
if pnl <= liquidation_threshold:
|
||||
@@ -94,64 +137,6 @@ async def handle_execution_message(message, msg: dict) -> None:
|
||||
await rq.update_martingale_step(tg_id, 0)
|
||||
|
||||
|
||||
async def start_execution_ws(tg_id, message) -> None:
|
||||
"""
|
||||
Запускает WebSocket для отслеживания исполнения сделок в режиме реального времени.
|
||||
Переподключается при ошибках.
|
||||
"""
|
||||
api_key = await rq.get_bybit_api_key(tg_id)
|
||||
api_secret = await rq.get_bybit_secret_key(tg_id)
|
||||
|
||||
reconnect_delay = 5
|
||||
|
||||
while True:
|
||||
try:
|
||||
ws = WebSocket(api_key=api_key,
|
||||
api_secret=api_secret,
|
||||
testnet=False,
|
||||
channel_type="private")
|
||||
|
||||
async def on_execution(msg):
|
||||
await handle_execution_message(message, msg)
|
||||
|
||||
def on_execution_sync(msg):
|
||||
asyncio.create_task(on_execution(msg))
|
||||
|
||||
ws.execution_stream(on_execution_sync)
|
||||
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
|
||||
except WebSocketConnectionClosedException:
|
||||
logging.warning("WebSocket закрыт, переподключение через 5 секунд...")
|
||||
await asyncio.sleep(reconnect_delay)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Ошибка WebSocket: {e}")
|
||||
await asyncio.sleep(reconnect_delay)
|
||||
|
||||
|
||||
async def info_access_open_deal(message, symbol, trade_mode, margin_mode, leverage, qty, tp, sl, entry_price,
|
||||
limit_price, order_type) -> None:
|
||||
"""
|
||||
Отправляет сообщение об успешном открытии позиции или выставлении лимитного ордера.
|
||||
"""
|
||||
human_margin_mode = 'Isolated' if margin_mode == 'ISOLATED_MARGIN' else 'Cross'
|
||||
text = (
|
||||
f"{'Позиция была успешна открыта' if order_type == 'Market' else 'Лимитный ордер установлен'}!\n"
|
||||
f"Торговая пара: {symbol}\n"
|
||||
f"Цена входа: {entry_price if order_type == 'Market' else round(limit_price, 5)}\n"
|
||||
f"Движение: {trade_mode}\n"
|
||||
f"Тип-маржи: {human_margin_mode}\n"
|
||||
f"Кредитное плечо: {leverage}x\n"
|
||||
f"Количество: {qty}\n"
|
||||
f"Тейк-профит: {round(tp, 5)}\n"
|
||||
f"Стоп-лосс: {round(sl, 5)}\n"
|
||||
)
|
||||
|
||||
await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.create_close_deal_markup(symbol))
|
||||
|
||||
|
||||
async def error_max_step(message) -> None:
|
||||
"""
|
||||
Сообщение об ошибке превышения максимального количества шагов мартингейла.
|
||||
@@ -340,10 +325,6 @@ async def open_position(tg_id, message, side: str, margin_mode: str, tpsl_mode='
|
||||
)
|
||||
|
||||
if response.get('retCode', -1) == 0:
|
||||
await info_access_open_deal(message, symbol, data_main_stgs.get('trading_mode', ''),
|
||||
bybit_margin_mode,
|
||||
data_main_stgs.get('size_leverage', 1), next_quantity, take_profit_price,
|
||||
stop_loss_price, entry_price, limit_price, order_type=order_type)
|
||||
await rq.update_martingale_step(tg_id, current_martingale_step)
|
||||
return True
|
||||
else:
|
||||
@@ -417,7 +398,7 @@ async def set_take_profit_stop_loss(tg_id: int, message, take_profit_price: floa
|
||||
return
|
||||
|
||||
client = HTTP(api_key=api_key, api_secret=secret_key)
|
||||
await cancel_all_tp_sl_orders(client, symbol)
|
||||
await cancel_all_tp_sl_orders(tg_id, symbol)
|
||||
|
||||
try:
|
||||
try:
|
||||
@@ -648,15 +629,6 @@ async def close_user_trade(tg_id: int, symbol: str, message):
|
||||
if include_fee:
|
||||
pnl -= trade_fee
|
||||
pnl_percent = (pnl / (entry_price * qty)) * 100 if entry_price * qty > 0 else 0
|
||||
|
||||
text = (
|
||||
f"Сделка {symbol} успешно закрыта.\n"
|
||||
f"Цена входа: {entry_price if entry_price else limit_price}\n"
|
||||
f"Цена закрытия: {current_price}\n"
|
||||
f"Прибыль: {pnl:.4f} USDT ({pnl_percent:.2f}%)\n"
|
||||
f"{'Включая комиссию биржи' if include_fee else 'Без учета комиссии'}"
|
||||
)
|
||||
await message.answer(text)
|
||||
return True
|
||||
else:
|
||||
if message:
|
||||
|
Reference in New Issue
Block a user