1
0
forked from kodorvan/stcs

Added the ability to get a list of open trades and limit orders, as well as their closures (previously it was possible only for the selected pair)

This commit is contained in:
algizn97
2025-08-28 11:25:48 +05:00
parent 704249d0af
commit e05b214a8a
4 changed files with 147 additions and 74 deletions

View File

@@ -14,6 +14,8 @@ import app.telegram.Keyboards.inline_keyboards as inline_markup
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("futures")
processed_trade_ids = set()
def safe_float(val) -> float:
"""
@@ -100,6 +102,13 @@ async def handle_execution_message(message, msg: dict) -> None:
"""
# logger.info(f"Исполнена сделка:\n{json.dumps(msg, indent=4, ensure_ascii=False)}")
trade_id = msg.get('data', [{}])[0].get('orderId')
if trade_id in processed_trade_ids:
logger.info(f"Уже обработана сделка {trade_id}, дублирующее уведомление игнорируется")
return
processed_trade_ids.add(trade_id)
pnl = parse_pnl_from_msg(msg)
tg_id = message.from_user.id
@@ -475,7 +484,7 @@ async def set_take_profit_stop_loss(tg_id: int, message, take_profit_price: floa
async def cancel_all_tp_sl_orders(tg_id, symbol):
"""
Отменяет все открытые ордера TP/SL для указанного символа.
Отменяет лимитные ордера для указанного символа.
"""
api_key = await rq.get_bybit_api_key(tg_id)
secret_key = await rq.get_bybit_secret_key(tg_id)
@@ -487,26 +496,49 @@ async def cancel_all_tp_sl_orders(tg_id, symbol):
for order in orders:
order_id = order.get('orderId')
order_symbol = order.get('symbol')
cancel_resp = client.cancel_order(category='linear', symbol=symbol, orderId=order_id)
last_response = cancel_resp
if cancel_resp.get('retCode') != 0:
logger.warning(f"Не удалось отменить ордер {order_id}: {cancel_resp.get('retMsg')}")
else:
last_response = order_symbol
except Exception as e:
logger.error(f"Ошибка при отмене ордеров TP/SL: {e}")
logger.error(f"Ошибка при отмене ордера: {e}")
return last_response
async def get_active_positions_by_symbol(tg_id, message):
async def get_active_positions(tg_id, message):
"""
Показывает активные позиции пользователя.
"""
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)
active_positions = client.get_positions(category='linear', settleCoin='USDT')
positions = active_positions.get('result', {}).get('list', [])
active_symbols = [pos.get('symbol') for pos in positions if float(pos.get('size', 0)) > 0]
if active_symbols:
await message.answer("📈 Ваши активные позиции:",
reply_markup=inline_markup.create_trades_inline_keyboard(active_symbols))
else:
await message.answer("❗️ У вас нет активных позиций.", reply_markup=inline_markup.back_to_main)
return
async def get_active_positions_by_symbol(tg_id, symbol, message):
"""
Показывает активные позиции пользователя по символу.
"""
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)
symbol = await rq.get_symbol(tg_id)
active_positions = client.get_positions(category='linear', symbol=symbol)
positions = active_positions.get('result', {}).get('list', [])
pos = positions[0] if positions else None
@@ -526,14 +558,33 @@ async def get_active_positions_by_symbol(tg_id, message):
await message.answer(text, reply_markup=inline_markup.create_close_deal_markup(symbol))
async def get_active_orders(tg_id, message):
"""
Показывает активные лимитные ордера пользователя.
"""
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)
async def get_active_orders_by_symbol(tg_id, message):
response = client.get_open_orders(category='linear', settleCoin='USDT', orderType='Limit')
orders = response.get('result', {}).get('list', [])
limit_orders = [order for order in orders if order.get('orderType') == 'Limit']
if limit_orders:
symbols = [order['symbol'] for order in limit_orders]
await message.answer("📈 Ваши активные лимитные ордера:",
reply_markup=inline_markup.create_trades_inline_keyboard_limits(symbols))
else:
await message.answer("❗️ У вас нет активных лимитных ордеров.", reply_markup=inline_markup.back_to_main)
return
async def get_active_orders_by_symbol(tg_id, symbol, message):
"""
Показывает активные лимитные ордера пользователя по символу.
"""
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)
client = HTTP(api_key=api_key, api_secret=secret_key)
active_orders = client.get_open_orders(category='linear', symbol=symbol)
@@ -561,7 +612,7 @@ async def get_active_orders_by_symbol(tg_id, message):
)
texts.append(text)
await message.answer("\n\n".join(texts), reply_markup=inline_markup.create_close_deal_markup(symbol))
await message.answer("\n\n".join(texts), reply_markup=inline_markup.create_close_limit_markup(symbol))
async def close_user_trade(tg_id: int, symbol: str, message):
@@ -574,7 +625,6 @@ async def close_user_trade(tg_id: int, symbol: str, message):
secret_key = await rq.get_bybit_secret_key(tg_id)
data_risk_stgs = await rq.get_user_risk_management_settings(tg_id)
limit_price = await rq.get_limit_price(tg_id)
include_fee = data_risk_stgs.get('commission_fee', 'Нет') == 'Да'
client = HTTP(api_key=api_key, api_secret=secret_key)
@@ -589,66 +639,29 @@ async def close_user_trade(tg_id: int, symbol: str, message):
position = positions_list[0]
qty = abs(safe_float(position.get('size')))
side = position.get('side')
entry_price = safe_float(position.get('avgPrice'))
if qty == 0:
return False
orders = client.get_open_orders(category='linear', symbol=symbol)
cancel_resp = await cancel_all_tp_sl_orders(tg_id, symbol)
open_orders_list = orders.get('result', {}).get('list', [])
order_id = open_orders_list[0].get('orderId') if open_orders_list else None
close_side = "Sell" if side == "Buy" else "Buy"
ticker_resp = client.get_tickers(category="linear", symbol=symbol)
current_price = 0.0
if ticker_resp.get('retCode') == 0:
result = ticker_resp.get('result', {})
ticker_list = []
if isinstance(result, dict):
ticker_list = result.get('list', [])
elif isinstance(result, list):
ticker_list = result
if ticker_list:
current_price = float(ticker_list[0].get('lastPrice', 0.0))
place_resp = client.place_order(
category="linear",
symbol=symbol,
side=close_side,
orderType="Market",
qty=str(qty),
timeInForce="GTC",
reduceOnly=True
)
if place_resp.get('retCode', -1) == 0:
trade_fee = 0
try:
trades_resp = client.get_closed_pnl(category="linear", symbol=symbol)
if trades_resp.get('retCode') == 0:
trades = trades_resp.get('result', {}).get('list', [])
for trade in trades:
if trade.get('orderId') == order_id:
trade_fee += float(trade.get('execFee', 0))
except Exception as e:
logger.error(f"Ошибка при получении сделок: {e}")
trade_fee = 0
pnl = (current_price - entry_price) * qty if side == "Buy" else (entry_price - current_price) * qty
if include_fee:
pnl -= trade_fee
pnl_percent = (pnl / (entry_price * qty)) * 100 if entry_price * qty > 0 else 0
return True
else:
if message:
await message.answer(f"Ошибка закрытия сделки {symbol}.",
reply_markup=inline_markup.back_to_main)
return False
place_resp = client.place_order(
category="linear",
symbol=symbol,
side=close_side,
orderType="Market",
qty=str(qty),
timeInForce="GTC",
reduceOnly=True
)
if place_resp.get('retCode') == 0:
await message.answer(f"Сделка {symbol} успешно закрыта.", reply_markup=inline_markup.back_to_main)
return True
else:
await message.answer(f"Ошибка закрытия сделки {symbol}.", reply_markup=inline_markup.back_to_main)
return False
except Exception as e:
logger.error(f"Ошибка закрытия сделки {symbol} для пользователя {tg_id}: {e}", exc_info=True)
if message:
await message.answer("Произошла ошибка при закрытии сделки.", reply_markup=inline_markup.back_to_main)
await message.answer("Произошла ошибка при закрытии сделки.", reply_markup=inline_markup.back_to_main)
return False