@@ -1,15 +1,14 @@
import asyncio
import json
import asyncio
import logging . config
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 . services . Bybit . functions . price_symbol as price_symbol
import app . telegram . database . requests as rq
import app . telegram . Keyboards . inline_keyboards as inline_markup
from logger_helper . logger_helper import LOGGING_CONFIG
from pybit import exceptions
from pybit . unified_trading import HTTP
logging . config . dictConfig ( LOGGING_CONFIG )
logger = logging . getLogger ( " futures " )
@@ -35,11 +34,11 @@ def safe_float(val) -> float:
Возвращает 0.0, если значение None, пустое или некорректное.
"""
try :
if val is None or val == ' ' :
if val is None or val == " " :
return 0.0
return float ( val )
except ( ValueError , TypeError ) :
logger . error ( " Некорректное значение для преобразования в float " )
logger . error ( " Некорректное значение для преобразования в float " , exc_info = True )
return 0.0
@@ -47,25 +46,25 @@ def format_trade_details_position(data, commission_fee):
"""
Форматирует информацию о сделке в виде строки.
"""
msg = data . get ( ' data' , [ { } ] ) [ 0 ]
msg = data . get ( " data" , [ { } ] ) [ 0 ]
closed_size = safe_float ( msg . get ( ' closedSize' , 0 ) )
symbol = msg . get ( ' symbol' , ' N/A' )
entry_price = safe_float ( msg . get ( ' execPrice' , 0 ) )
qty = safe_float ( msg . get ( ' execQty' , 0 ) )
order_type = msg . get ( ' orderType' , ' N/A' )
side = msg . get ( ' side' , ' ' )
commission = safe_float ( msg . get ( ' execFee' , 0 ) )
pnl = safe_float ( msg . get ( ' execPnl' , 0 ) )
closed_size = safe_float ( msg . get ( " closedSize" , 0 ) )
symbol = msg . get ( " symbol" , " N/A" )
entry_price = safe_float ( msg . get ( " execPrice" , 0 ) )
qty = safe_float ( msg . get ( " execQty" , 0 ) )
order_type = msg . get ( " orderType" , " N/A" )
side = msg . get ( " side" , " " )
commission = safe_float ( msg . get ( " execFee" , 0 ) )
pnl = safe_float ( msg . get ( " execPnl" , 0 ) )
if commission_fee == " Да " :
pnl - = commission
movement = ' '
if side . lower ( ) == ' buy' :
movement = ' Покупка'
elif side . lower ( ) == ' sell' :
movement = ' Продажа'
movement = " "
if side . lower ( ) == " buy" :
movement = " Покупка"
elif side . lower ( ) == " sell" :
movement = " Продажа"
else :
movement = side
@@ -81,7 +80,7 @@ def format_trade_details_position(data, commission_fee):
f " Комиссия за сделку: { commission : .6f } \n "
f " Реализованная прибыль: { pnl : .6f } USDT "
)
if order_type == ' Market' :
if order_type == " Market" :
return (
f " Сделка открыта: \n "
f " Торговая пара: { symbol } \n "
@@ -98,27 +97,27 @@ def format_order_details_position(data):
"""
Форматирует информацию о б ордере в виде строки.
"""
msg = data . get ( ' data' , [ { } ] ) [ 0 ]
price = safe_float ( msg . get ( ' price' , 0 ) )
qty = safe_float ( msg . get ( ' qty' , 0 ) )
cum_exec_qty = safe_float ( msg . get ( ' cumExecQty' , 0 ) )
cum_exec_fee = safe_float ( msg . get ( ' cumExecFee' , 0 ) )
take_profit = safe_float ( msg . get ( ' takeProfit' , 0 ) )
stop_loss = safe_float ( msg . get ( ' stopLoss' , 0 ) )
order_status = msg . get ( ' orderStatus' , ' N/A' )
symbol = msg . get ( ' symbol' , ' N/A' )
order_type = msg . get ( ' orderType' , ' N/A' )
side = msg . get ( ' side' , ' ' )
msg = data . get ( " data" , [ { } ] ) [ 0 ]
price = safe_float ( msg . get ( " price" , 0 ) )
qty = safe_float ( msg . get ( " qty" , 0 ) )
cum_exec_qty = safe_float ( msg . get ( " cumExecQty" , 0 ) )
cum_exec_fee = safe_float ( msg . get ( " cumExecFee" , 0 ) )
take_profit = safe_float ( msg . get ( " takeProfit" , 0 ) )
stop_loss = safe_float ( msg . get ( " stopLoss" , 0 ) )
order_status = msg . get ( " orderStatus" , " N/A" )
symbol = msg . get ( " symbol" , " N/A" )
order_type = msg . get ( " orderType" , " N/A" )
side = msg . get ( " side" , " " )
movement = ' '
if side . lower ( ) == ' buy' :
movement = ' Покупка'
elif side . lower ( ) == ' sell' :
movement = ' Продажа'
movement = " "
if side . lower ( ) == " buy" :
movement = " Покупка"
elif side . lower ( ) == " sell" :
movement = " Продажа"
else :
movement = side
if order_status . lower ( ) == ' filled' and order_type . lower ( ) == ' limit' :
if order_status . lower ( ) == " filled" and order_type . lower ( ) == " limit" :
text = (
f " Ордер исполнен: \n "
f " Торговая пара: { symbol } \n "
@@ -131,10 +130,9 @@ def format_order_details_position(data):
f " Стоп-лосс: { stop_loss : .6f } \n "
f " Комиссия за сделку: { cum_exec_fee : .6f } \n "
)
logger . info ( text )
return text
elif order_status . lower ( ) == ' new' :
elif order_status . lower ( ) == " new" :
text = (
f " Ордер создан: \n "
f " Торговая пара: { symbol } \n "
@@ -145,10 +143,9 @@ def format_order_details_position(data):
f " Тейк-профит: { take_profit : .6f } \n "
f " Стоп-лосс: { stop_loss : .6f } \n "
)
logger . info ( text )
return text
elif order_status . lower ( ) == ' cancelled' :
elif order_status . lower ( ) == " cancelled" :
text = (
f " Ордер отменен: \n "
f " Торговая пара: { symbol } \n "
@@ -159,7 +156,6 @@ def format_order_details_position(data):
f " Тейк-профит: { take_profit : .6f } \n "
f " Стоп-лосс: { stop_loss : .6f } \n "
)
logger . info ( text )
return text
return None
@@ -169,66 +165,117 @@ def parse_pnl_from_msg(msg) -> float:
Извлекает реализованную прибыль/убыток из сообщения.
"""
try :
data = msg . get ( ' data' , [ { } ] ) [ 0 ]
return float ( data . get ( ' execPnl' , 0 ) )
data = msg . get ( " data" , [ { } ] ) [ 0 ]
return float ( data . get ( " execPnl" , 0 ) )
except Exception as e :
logger . error ( f " Ошибка при извлечении реализованной прибыли: { e } " )
logger . error ( " Ошибка при извлечении реализованной прибыли: %s " , e )
return 0.0
async def calculate_total_budget ( starting_quantity , martingale_factor , max_steps , commission_fee_percent , leverage , current_price ) :
"""
Вычисляет общий бюджет серии ставок с учётом цены пары, комиссии и кредитного плеча.
Параметры:
- starting_quantity_usdt: стартовый размер ставки в долларах (USD)
- martingale_factor: множитель увеличения ставки при каждом проигрыше
- max_steps: максимальное количество шагов удвоения ставки
- commission_fee_percent: процент комиссии на одну операцию (открытие или закрытие)
- leverage: кредитное плечо
- current_price: текущая цена актива (например BTCUSDT)
Возвращает:
- общий бюджет в долларах, который необходимо иметь на счету
"""
total = 0
for step in range ( max_steps ) :
quantity = starting_quantity * ( martingale_factor * * step ) # размер ставки на текущем шаге в USDT
# Переводим ставку из USDT в количество актива по текущей цене
quantity_in_asset = quantity / current_price
# Учитываем комиссию за вход и выход (умножаем на 2)
quantity_with_fee = quantity * ( 1 + 2 * commission_fee_percent / 100 )
# Учитываем кредитное плечо - реальные собственные вложения меньше
effective_quantity = quantity_with_fee / leverage
total + = effective_quantity
# Возвращаем бюджет в USDT
total_usdt = total * current_price
return total_usdt
async def handle_execution_message ( message , msg ) :
"""
Обработчик сообщений о б исполнении сделки.
Логирует событие и проверяет условия для мартингейла и TP.
"""
# logger.info(f"Исполнена сделка:\n{json.dumps(msg, indent=4, ensure_ascii=False)}")
tg_id = message . from_user . id
data = msg . get ( ' data' , [ { } ] ) [ 0 ]
data = msg . get ( " data" , [ { } ] ) [ 0 ]
data_main_risk_stgs = await rq . get_user_risk_management_settings ( tg_id )
commission_fee = data_main_risk_stgs . get ( ' commission_fee' , " ДА " )
commission_fee = data_main_risk_stgs . get ( " commission_fee" , " ДА " )
pnl = parse_pnl_from_msg ( msg )
data_main_stgs = await rq . get_user_main_settings ( tg_id )
symbol = data . get ( ' symbol' )
switch _mode = data_main_stgs . get ( ' switch _mode' , ' Включено ' )
trading_mode = data_main_stgs . get ( ' trading_mode ' , ' Long ' )
symbol = data . get ( " symbol" )
trading _mode = data_main_stgs . get ( " trading _mode" , " Long " )
trigger = await rq . get_for_registration_trigger ( tg_id )
margin_mode = data_main_stgs . get ( ' margin_type' , ' Isolated' )
starting_quantity = safe_float ( data_main_stgs . get ( ' starting_quantity' ) )
margin_mode = data_main_stgs . get ( " margin_type" , " Isolated" )
starting_quantity = safe_float ( data_main_stgs . get ( " starting_quantity" ) )
martingale_factor = safe_float ( data_main_stgs . get ( " martingale_factor " ) )
closed_size = safe_float ( data . get ( " closedSize " , 0 ) )
commission = safe_float ( data . get ( " execFee " , 0 ) )
trade_info = format_trade_details_position ( data = msg , commission_fee = commission_fee )
if commission_fee == " Да " :
pnl - = commission
trade_info = format_trade_details_position (
data = msg ,
commission_fee = commission_fee
)
if trade_info :
await message . answer ( f " { trade_info } " , reply_markup = inline_markup . back_to_main )
sid e = None
if switch_mo de == ' Включено ' :
switch_state = data_main_stgs . get ( ' switch_state ' , ' Long ' )
side = ' Buy ' if switch_state == ' Long ' else ' Sell '
else :
if trading_mode == ' Long ' :
side = ' Buy '
elif trading_mode == ' Short ' :
side = ' Sell '
else :
side = ' Buy '
if closed_ siz e == 0 :
si de = data . get ( " side " , " " )
if trigger == " Автоматический " :
if side . lower ( ) == " buy " :
await rq . set_last_series_info ( tg_id , last_side = " Buy " )
elif side . lower ( ) == " sell " :
await rq . set_last_series_info ( tg_id , last_side = " Sell " )
if trigger == " Автоматический " and closed_size > 0 :
if pnl < 0 :
martingale_factor = safe_float ( data_main_stgs . get ( ' martingale_factor ' ) )
if trading_mode == ' Switch ' :
side = data_main_stgs . get ( " last_side " )
else :
side = " Buy " if trading_mode == " Long " else " Sell "
current_martingale = await rq . get_martingale_step ( tg_id )
current_martingale_step = int ( current_martingale )
current_martingale + = 1
next_quantity = float ( starting_quantity ) * ( float ( martingale_factor ) * * current_martingale_step )
next_quantity = float ( starting_quantity ) * (
float ( martingale_factor ) * * current_martingale_step
)
await rq . update_martingale_step ( tg_id , current_martingale )
await open_position ( tg_id , message , side = side , margin_mode = margin_mode , symbol = symbol ,
quantity = next_quantity )
await open_position (
tg_id ,
message ,
side = side ,
margin_mode = margin_mode ,
symbol = symbol ,
quantity = next_quantity ,
)
elif pnl > 0 :
await rq . update_martingale_step ( tg_id , 0 )
await message . answer ( " ❗️ Прибыль достигнута, шаг мартингейла сброшен. "
" Начинаем новую серию ставок " )
await open_position ( tg_id , message , side = side , margin_mode = margin_mode , symbol = symbol ,
quantity = starting_quantity )
await message . answer (
" ❗️ Прибыль достигнута, шаг мартингейла сброшен. "
)
async def handle_order_message ( message , msg : dict ) - > None :
@@ -248,21 +295,29 @@ async def error_max_step(message) -> None:
"""
Сообщение о б ошибке превышения максимального количества шагов мартингейла.
"""
logger . error ( ' Сделка не была совершена, превышен лимит максимального количества ставок в серии. ' )
await message . answer ( ' Сделка не была совершена, превышен лимит максимального количества ставок в серии.' ,
reply_markup = inline_markup . back_to_main )
logger . error (
" Сделка не была совершена, превышен лимит максимального количества ставок в серии. "
)
await message . answer (
" Сделка не была совершена, превышен лимит максимального количества ставок в серии. " ,
reply_markup = inline_markup . back_to_main ,
)
async def error_max_risk ( message ) - > None :
"""
Сообщение о б ошибке превышения риск-лимита сделки.
"""
logger . error ( ' Сделка не была совершена, риск убытка превышает допустимый лимит.' )
await message . answer ( ' Сделка не была совершена, риск убытка превышает допустимый лимит. ' ,
reply_markup = inline_markup . back_to_main )
logger . error ( " Сделка не была совершена, риск убытка превышает допустимый лимит." )
await message . answer (
" Сделка не была совершена, риск убытка превышает допустимый лимит. " ,
reply_markup = inline_markup . back_to_main ,
)
async def open_position ( tg_id , message , side : str , margin_mode : str , symbol , quantity , tpsl_mode = ' Full ' ) :
async def open_position (
tg_id , message , side : str , margin_mode : str , symbol , quantity , tpsl_mode = " Full "
) :
"""
Открывает позицию на Bybit с учётом настроек пользователя, маржи, размера лота, платформы и риска.
@@ -271,67 +326,106 @@ async def open_position(tg_id, message, side: str, margin_mode: str, symbol, qua
try :
client = await get_bybit_client ( tg_id )
data_main_stgs = await rq . get_user_main_settings ( tg_id )
order_type = data_main_stgs . get ( ' entry_order_type' )
bybit_margin_mode = ' ISOLATED_MARGIN ' if margin_mode == ' Isolated ' else ' REGULAR_MARGIN '
order_type = data_main_stgs . get ( " entry_order_type" )
bybit_margin_mode = (
" ISOLATED_MARGIN " if margin_mode == " Isolated " else " REGULAR_MARGIN "
)
limit_price = None
if order_type == ' Limit' :
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 )
balance = await balance_g . get_balance ( tg_id , message )
price = await price_symbol . get_price ( tg_id , symbol = symbol )
entry_price = safe_float ( price )
leverage = safe_float ( data_main_stgs . get ( " size_leverage " , 1 ) )
max_martingale_steps = int ( data_main_stgs . get ( ' maximal_quantity' , 0 ) )
max_martingale_steps = int ( data_main_stgs . get ( " maximal_quantity" , 0 ) )
current_martingale = await rq . get_martingale_step ( tg_id )
max_risk_percent = safe_float ( data_risk_stgs . get ( ' max_risk_deal' ) )
loss_profit = safe_float ( data_risk_stgs . get ( ' price_loss' ) )
max_risk_percent = safe_float ( data_risk_stgs . get ( " max_risk_deal" ) )
loss_profit = safe_float ( data_risk_stgs . get ( " price_loss" ) )
commission_fee = data_risk_stgs . get ( " commission_fee " )
starting_quantity = safe_float ( data_main_stgs . get ( ' starting_quantity ' ) )
martingale_factor = safe_float ( data_main_stgs . get ( ' martingale_factor ' ) )
fee_info = client . get_fee_rates ( category = ' linear ' , symbol = symbol )
instruments_resp = client . get_instruments_info ( category = " linear " , symbol = symbol )
instrument = instruments_resp . get ( " result " , { } ) . get ( " list " , [ ] )
if order_type == ' Limit ' and limit_price :
if commission_fee == " Да " :
commission_fee_percent = safe_float ( fee_info [ ' result ' ] [ ' list ' ] [ 0 ] [ ' takerFeeRate ' ] )
else :
commission_fee_percent = 0.0
total_budget = await calculate_total_budget (
starting_quantity = starting_quantity ,
martingale_factor = martingale_factor ,
max_steps = max_martingale_steps ,
commission_fee_percent = commission_fee_percent ,
leverage = leverage ,
current_price = entry_price ,
)
balance = await balance_g . get_balance ( tg_id , message )
if safe_float ( balance ) < total_budget :
await message . answer (
f " Недостаточно средств для серии из { max_martingale_steps } шагов с текущими параметрами. "
f " Требуемый бюджет: { total_budget : .2f } USDT, доступно: { balance } USDT. " ,
reply_markup = inline_markup . back_to_main ,
)
return
if order_type == " Limit " and limit_price :
price_for_calc = limit_price
else :
price_for_calc = entry_price
potential_loss = safe_float ( quantity ) * price_for_calc * ( loss_profit / 100 )
adjusted_loss = potential_loss / leverage
allowed_loss = safe_float ( balance ) * ( max_risk_percent / 100 )
if adjusted_loss > allowed_loss :
await error_max_risk ( message )
return
if max_martingale_steps == current_martingale :
await error_max_step ( message )
return
if potential_loss > allowed_loss :
await error_max_risk ( message )
return
client . set_margin_mode ( setMarginMode = bybit_margin_mode )
max_leverage = safe_float ( instrument [ 0 ] . get ( " leverageFilter " , { } ) . get ( " maxLeverage " , 0 ) )
leverage = int ( data_main_stgs . get ( ' size_leverage ' , 1 ) )
try :
resp = client . set_leverage (
category = ' linear ' ,
symbol = symbol ,
buyLeverage = str ( leverage ) ,
sellLeverage = str ( leverage )
if safe_float ( leverage ) > max_leverage :
await message . answer (
f " Запрошенное кредитное плечо { leverage } превышает максимальное { max_leverage } для { symbol } . "
f " Устанавливаю максимальное. " ,
reply_markup = inline_markup . back_to_main ,
)
logger . info (
f " Запрошенное кредитное плечо { leverage } превышает максимальное { max_leverage } для { symbol } . Устанавливаю максимальное. " )
leverage_to_set = max_leverage
else :
leverage_to_set = safe_float ( leverage )
try :
client . set_leverage (
category = " linear " ,
symbol = symbol ,
buyLeverage = str ( leverage_to_set ) ,
sellLeverage = str ( leverage_to_set ) ,
)
logger . info ( f " Set leverage to { leverage_to_set } for { symbol } " )
except exceptions . InvalidRequestError as e :
if " 110043 " in str ( e ) :
logger . info ( f " Leverage already set to { leverage } for { symbol } " )
else :
raise e
instruments_resp = client . get_instruments_info ( category = ' linear ' , symbol = symbol )
if instruments_resp . get ( ' retCode ' ) == 0 :
instrument_info = instruments_resp . get ( ' result ' , { } ) . get ( ' list ' , [ ] )
if instruments_resp . get ( " retCode " ) == 0 :
instrument_info = instruments_resp . get ( " result " , { } ) . get ( " list " , [ ] )
if instrument_info :
instrument = instrument_info [ 0 ]
min_order_qty = float ( instrument . get ( ' minOrderQty ' , 0 ) )
min_order_value_api = float ( instrument . get ( ' minOrderValue ' , 0 ) )
if min_order_value_api == 0 :
min_order_value_api = 5.0
min_order_value_calc = min_order_qty * price_for_calc if min_order_qty > 0 else 0
min_order_value = max ( min_order_value_calc , min_order_value_api )
instrument_info = instrument_info [ 0 ]
min_notional_value = float ( instrument_info . get ( " lotSizeFilter " , { } ) . get ( " minNotionalValue " , 0 ) )
min_order_value = min_notional_value
else :
min_order_value = 5.0
@@ -340,34 +434,40 @@ async def open_position(tg_id, message, side: str, margin_mode: str, symbol, qua
await message . answer (
f " Сумма ордера слишком мала: { order_value : .2f } USDT. "
f " Минимум для торговли — { min_order_value } USDT. "
f " Пожалуйста, увеличьте количество позиций. " , reply_markup = inline_markup . back_to_main )
f " Пожалуйста, увеличьте количество позиций. " ,
reply_markup = inline_markup . back_to_main ,
)
return False
if bybit_margin_mode == ' ISOLATED_MARGIN' :
if bybit_margin_mode == " ISOLATED_MARGIN" :
# Открываем позицию
response = client . place_order (
category = ' linear' ,
category = " linear" ,
symbol = symbol ,
side = side ,
orderType = order_type ,
qty = str ( quantity ) ,
price = str ( limit_price ) if order_type == ' Limit ' and limit_price else None ,
timeInForc e=' GTC ' ,
orderLinkId = f " deal_ { symbol } _ { int ( time . time ( ) ) } "
price = (
str ( limit_price ) if order_typ e == " Limit " and limit_price else None
) ,
timeInForce = " GTC " ,
orderLinkId = f " deal_ { symbol } _ { int ( time . time ( ) ) } " ,
)
if response . get ( ' retCode' , - 1 ) != 0 :
if response . get ( " retCode" , - 1 ) != 0 :
logger . error ( f " Ошибка открытия ордера: { response } " )
await message . answer ( f " Ошибка открытия ордера " , reply_markup = inline_markup . back_to_main )
await message . answer (
f " Ошибка открытия ордера " , reply_markup = inline_markup . back_to_main
)
return False
# Получаем цену ликвидации
positions = client . get_positions ( category = ' linear' , symbol = symbol )
pos = positions . get ( ' result' , { } ) . get ( ' list' , [ { } ] ) [ 0 ]
avg_price = float ( pos . get ( ' avgPrice' , 0 ) )
liq_price = safe_float ( pos . get ( ' liqPrice' , 0 ) )
positions = client . get_positions ( category = " linear" , symbol = symbol )
pos = positions . get ( " result" , { } ) . get ( " list" , [ { } ] ) [ 0 ]
avg_price = float ( pos . get ( " avgPrice" , 0 ) )
liq_price = safe_float ( pos . get ( " liqPrice" , 0 ) )
if liq_price > 0 and avg_price > 0 :
if side . lower ( ) == ' buy' :
if side . lower ( ) == " buy" :
take_profit_price = avg_price + ( avg_price - liq_price )
else :
take_profit_price = avg_price - ( liq_price - avg_price )
@@ -376,45 +476,49 @@ async def open_position(tg_id, message, side: str, margin_mode: str, symbol, qua
try :
try :
client . set_tp_sl_mode ( symbol = symbol , category = ' linear ' , tpSlMode = ' Full ' )
client . set_tp_sl_mode (
symbol = symbol , category = " linear " , tpSlMode = " Full "
)
except exceptions . InvalidRequestError as e :
if ' same tp sl mode' in str ( e ) :
if " same tp sl mode" in str ( e ) :
logger . info ( " Режим TP/SL уже установлен - пропускаем " )
else :
raise
resp = client . set_trading_stop (
category = ' linear' ,
category = " linear" ,
symbol = symbol ,
takeProfit = str ( round ( take_profit_price , 5 ) ) ,
tpTriggerBy = ' LastPrice' ,
slTriggerBy = ' LastPrice' ,
tpTriggerBy = " LastPrice" ,
slTriggerBy = " LastPrice" ,
positionIdx = 0 ,
reduceOnly = False ,
tpslMode = tpsl_mode
tpslMode = tpsl_mode ,
)
except Exception as e :
logger . error ( f " Ошибка установки TP/SL: { e } " )
await message . answer ( ' Ошибка при установке Take Profit и Stop Loss. ' ,
reply_markup = inline_markup . back_to_main )
await message . answer (
" Ошибка при установке Take Profit и Stop Loss. " ,
reply_markup = inline_markup . back_to_main ,
)
return False
else :
logger . warning ( " Н е удалось получить цену ликвидации для позиции" )
else : # REGULAR_MARGIN
try :
client . set_tp_sl_mode ( symbol = symbol , category = ' linear' , tpSlMode = ' Full' )
client . set_tp_sl_mode ( symbol = symbol , category = " linear" , tpSlMode = " Full" )
except exceptions . InvalidRequestError as e :
if ' same tp sl mode' in str ( e ) :
if " same tp sl mode" in str ( e ) :
logger . info ( " Режим TP/SL уже установлен - пропускаем " )
else :
raise
if order_type == ' Market' :
if order_type == " Market" :
base_price = entry_price
else :
base_price = limit_price
if side . lower ( ) == ' buy' :
if side . lower ( ) == " buy" :
take_profit_price = base_price * ( 1 + loss_profit / 100 )
stop_loss_price = base_price * ( 1 - loss_profit / 100 )
else :
@@ -424,24 +528,26 @@ async def open_position(tg_id, message, side: str, margin_mode: str, symbol, qua
take_profit_price = max ( take_profit_price , 0 )
stop_loss_price = max ( stop_loss_price , 0 )
if tpsl_mode == ' Full' :
tp_order_type = ' Market'
sl_order_type = ' Market'
if tpsl_mode == " Full" :
tp_order_type = " Market"
sl_order_type = " Market"
tp_limit_price = None
sl_limit_price = None
else : # Partial
tp_order_type = ' Limit'
sl_order_type = ' Limit'
tp_order_type = " Limit"
sl_order_type = " Limit"
tp_limit_price = take_profit_price
sl_limit_price = stop_loss_price
response = client . place_order (
category = ' linear' ,
category = " linear" ,
symbol = symbol ,
side = side ,
orderType = order_type ,
qty = str ( quantity ) ,
price = str ( limit_price ) if order_type == ' Limit ' and limit_price else None ,
price = (
str ( limit_price ) if order_type == " Limit " and limit_price else None
) ,
takeProfit = str ( take_profit_price ) ,
tpOrderType = tp_order_type ,
tpLimitPrice = str ( tp_limit_price ) if tp_limit_price else None ,
@@ -449,25 +555,42 @@ async def open_position(tg_id, message, side: str, margin_mode: str, symbol, qua
slOrderType = sl_order_type ,
slLimitPrice = str ( sl_limit_price ) if sl_limit_price else None ,
tpslMode = tpsl_mode ,
timeInForce = ' GTC' ,
orderLinkId = f " deal_ { symbol } _ { int ( time . time ( ) ) } "
timeInForce = " GTC" ,
orderLinkId = f " deal_ { symbol } _ { int ( time . time ( ) ) } " ,
)
if response . get ( ' retCode' , - 1 ) == 0 :
if response . get ( " retCode" , - 1 ) == 0 :
return True
else :
logger . error ( f " Ошибка открытия ордера: { response } " )
await message . answer ( f " Ошибка открытия ордера " , reply_markup = inline_markup . back_to_main )
await message . answer (
f " Ошибка открытия ордера " , reply_markup = inline_markup . back_to_main
)
return False
return None
except exceptions . InvalidRequestError as e :
logger . error ( f " InvalidRequestError: { e } " , exc_info = True )
await message . answer ( ' Недостаточно средств для размещения нового ордера с заданным количеством и плечом. ' ,
reply_markup = inline_markup . back_to_main )
logger . error ( " InvalidRequestError: %s " , e )
error_text = str ( e )
if " estimated will trigger liq " in error_text :
await message . answer (
" Лимитный ордер может вызвать мгновенную ликвидацию. Проверьте параметры ордера. " ,
reply_markup = inline_markup . back_to_main ,
)
elif " ab not enough for new order " in error_text :
await message . answer ( " Недостаточно средств для нового ордера " ,
reply_markup = inline_markup . back_to_main )
else :
await message . answer (
" Недостаточно средств для размещения нового ордера с заданным количеством и плечом. " ,
reply_markup = inline_markup . back_to_main ,
)
except Exception as e :
logger . error ( f " Ошибка при совершении сделки: { e } " , exc_info = True )
await message . answer ( ' Возникла ошибка при попытке открыть позицию. ' , reply_markup = inline_markup . back_to_main )
logger . error ( " Ошибка при совершении сделки: %s " , e )
await message . answer (
" Возникла ошибка при попытке открыть позицию. " ,
reply_markup = inline_markup . back_to_main ,
)
async def trading_cycle ( tg_id , message , side , margin_mode , symbol , starting_quantity ) :
@@ -478,7 +601,7 @@ async def trading_cycle(tg_id, message, side, margin_mode, symbol, starting_quan
timer_data = await rq . get_user_timer ( tg_id )
timer_min = 0
if isinstance ( timer_data , dict ) :
timer_min = timer_data . get ( ' timer_minutes' ) or timer_data . get ( ' timer' ) or 0
timer_min = timer_data . get ( " timer_minutes" ) or timer_data . get ( " timer" ) or 0
else :
timer_min = timer_data or 0
@@ -487,66 +610,72 @@ async def trading_cycle(tg_id, message, side, margin_mode, symbol, starting_quan
if timer_sec > 0 :
await asyncio . sleep ( timer_sec )
await open_position ( tg_id , message , side = side , margin_mode = margin_mode , symbol = symbol ,
quantity = starting_quantity )
await open_position (
tg_id ,
message ,
side = side ,
margin_mode = margin_mode ,
symbol = symbol ,
quantity = starting_quantity ,
)
except asyncio . CancelledError :
logger . info ( f " Торговый цикл для пользователя { tg_id } был отменён. " , exc_info = True )
logger . info (
f " Торговый цикл для пользователя { tg_id } был отменён. " , exc_info = True
)
async def set_take_profit_stop_loss ( tg_id : int , message , take_profit_price : float , stop_loss_price : float ,
tpsl_mode = ' Full ' ) :
async def set_take_profit_stop_loss (
tg_id : int ,
message ,
take_profit_price : float ,
stop_loss_price : float ,
tpsl_mode = " Full " ,
) :
"""
Устанавливает уровни Take Profit и Stop Loss для открытой позиции.
"""
symbol = await rq . get_symbol ( tg_id )
data_main_stgs = await rq . get_user_main_settings ( tg_id )
trading_mode = data_main_stgs . get ( ' trading_mode ' )
side = None
if trading_mode == ' Long ' :
side = ' Buy '
elif trading_mode == ' Short ' :
side = ' Sell '
if side is None :
await message . answer ( " Н е удалось определить сторону сделки." )
return
client = await get_bybit_client ( tg_id )
await cancel_all_tp_sl_orders ( tg_id , symbol )
try :
try :
client . set_tp_sl_mode ( symbol = symbol , category = ' linear' , tpSlMode = tpsl_mode )
client . set_tp_sl_mode ( symbol = symbol , category = " linear" , tpSlMode = tpsl_mode )
except exceptions . InvalidRequestError as e :
if ' same tp sl mode' in str ( e ) . lower ( ) :
if " same tp sl mode" in str ( e ) . lower ( ) :
logger . info ( f " Режим TP/SL уже установлен для { symbol } " )
else :
raise
resp = client . set_trading_stop (
category = ' linear' ,
category = " linear" ,
symbol = symbol ,
takeProfit = str ( round ( take_profit_price , 5 ) ) ,
stopLoss = str ( round ( stop_loss_price , 5 ) ) ,
tpTriggerBy = ' LastPrice' ,
slTriggerBy = ' LastPrice' ,
tpTriggerBy = " LastPrice" ,
slTriggerBy = " LastPrice" ,
positionIdx = 0 ,
reduceOnly = False ,
tpslMode = tpsl_mode
tpslMode = tpsl_mode ,
)
if resp . get ( ' retCode' ) != 0 :
await message . answer ( f " Ошибка обновления TP/SL: { resp . get ( ' retMsg ' ) } " ,
reply_marku p = inline_markup . back_to_main )
if resp . get ( " retCode" ) != 0 :
await message . answer (
f " Ошибка обновления TP/SL: { res p. get ( ' retMsg ' ) } " ,
reply_markup = inline_markup . back_to_main ,
)
return
await message . answer (
f " ТП и СЛ успешно установлены: \n Тейк-профит: { take_profit_price : .5f } \n Стоп-лосс: { stop_loss_price : .5f } " ,
reply_markup = inline_markup . back_to_main )
reply_markup = inline_markup . back_to_main ,
)
except Exception as e :
logger . error ( f " Ошибка установки TP/SL для { symbol } : { e } " , exc_info = True )
await message . answer ( " Произошла ошибка при установке TP и SL. " , reply_markup = inline_markup . back_to_main )
await message . answer (
" Произошла ошибка при установке TP и SL. " ,
reply_markup = inline_markup . back_to_main ,
)
async def cancel_all_tp_sl_orders ( tg_id , symbol ) :
@@ -556,15 +685,19 @@ async def cancel_all_tp_sl_orders(tg_id, symbol):
client = await get_bybit_client ( tg_id )
last_response = None
try :
orders_resp = client . get_open_orders ( category = ' linear' , symbol = symbol )
orders = orders_resp . get ( ' result' , { } ) . get ( ' list' , [ ] )
orders_resp = client . get_open_orders ( category = " linear" , symbol = symbol )
orders = orders_resp . get ( " result" , { } ) . get ( " list" , [ ] )
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 )
if cancel_resp . get ( ' retCode ' ) != 0 :
logger . warning ( f " Н е удалось отменить ордер { order_id } : { cancel_resp . get ( ' retMsg ' ) } " )
order_id = order . get ( " orderId" )
order_symbol = order . get ( " symbol" )
cancel_resp = client . cancel_order (
category = " linear " , symbol = symbol , orderId = order_id
)
if cancel_resp . get ( " retCode " ) != 0 :
logger . warning (
f " Н е удалось отменить ордер { order_id } : { cancel_resp . get ( ' retMsg ' ) } "
)
else :
last_response = order_symbol
except Exception as e :
@@ -578,15 +711,21 @@ async def get_active_positions(tg_id, message):
Показывает активные позиции пользователя.
"""
client = await get_bybit_client ( tg_id )
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 ]
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 ) )
await message . answer (
" 📈 Ваши активные позиции: " ,
reply_markup = inline_markup . create_trades_inline_keyboard ( active_symbols ) ,
)
else :
await message . answer ( " ❗️ У вас нет активных позиций. " , reply_markup = inline_markup . back_to_main )
await message . answer (
" ❗️ У вас нет активных позиций. " , reply_markup = inline_markup . back_to_main
)
return
@@ -595,12 +734,14 @@ async def get_active_positions_by_symbol(tg_id, symbol, message):
Показывает активные позиции пользователя по символу.
"""
client = await get_bybit_client ( tg_id )
active_positions = client . get_positions ( category = ' linear' , symbol = symbol )
positions = active_positions . get ( ' result' , { } ) . get ( ' list' , [ ] )
active_positions = client . get_positions ( category = " linear" , symbol = symbol )
positions = active_positions . get ( " result" , { } ) . get ( " list" , [ ] )
pos = positions [ 0 ] if positions else None
if float ( pos . get ( ' size' , 0 ) ) == 0 :
await message . answer ( " ❗️ У вас нет активных позиций. " , reply_markup = inline_markup . back_to_main )
if float ( pos . get ( " size" , 0 ) ) == 0 :
await message . answer (
" ❗️ У вас нет активных позиций. " , reply_markup = inline_markup . back_to_main
)
return
text = (
@@ -613,7 +754,9 @@ async def get_active_positions_by_symbol(tg_id, symbol, message):
f " Стоп-лосс: { pos . get ( ' stopLoss ' ) } \n "
)
await message . answer ( text , reply_markup = inline_markup . create_close_deal_markup ( symbol ) )
await message . answer (
text , reply_markup = inline_markup . create_close_deal_markup ( symbol )
)
async def get_active_orders ( tg_id , message ) :
@@ -621,16 +764,23 @@ async def get_active_orders(tg_id, message):
Показывает активные лимитные ордера пользователя.
"""
client = await get_bybit_client ( tg_id )
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 ' ]
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 ) )
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 )
await message . answer (
" ❗️ У вас нет активных лимитных ордеров. " ,
reply_markup = inline_markup . back_to_main ,
)
return
@@ -639,15 +789,18 @@ async def get_active_orders_by_symbol(tg_id, symbol, message):
Показывает активные лимитные ордера пользователя по символу.
"""
client = await get_bybit_client ( tg_id )
active_orders = client . get_open_orders ( category = ' linear' , symbol = symbol )
active_orders = client . get_open_orders ( category = " linear" , symbol = symbol )
limit_orders = [
order for order in active_orders . get ( ' result ' , { } ) . get ( ' list ' , [ ] )
i f order . get ( ' orderType ' ) == ' Limit '
order
for order in active_orders . get ( " result " , { } ) . get ( " list " , [ ] )
if order . get ( " orderType " ) == " Limit "
]
if not limit_orders :
await message . answer ( " Нет активных лимитных ордеров по данной торговой паре. " ,
reply_markup = inline_markup . back_to_main )
await message . answer (
" Нет активных лимитных ордеров по данной торговой паре. " ,
reply_markup = inline_markup . back_to_main ,
)
return
texts = [ ]
@@ -663,7 +816,9 @@ async def get_active_orders_by_symbol(tg_id, symbol, message):
)
texts . append ( text )
await message . answer ( " \n \n " . join ( texts ) , reply_markup = inline_markup . create_close_limit_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 ) :
@@ -675,15 +830,15 @@ async def close_user_trade(tg_id: int, symbol: str):
client = await get_bybit_client ( tg_id )
positions_resp = client . get_positions ( category = " linear " , symbol = symbol )
if positions_resp . get ( ' retCode' ) != 0 :
if positions_resp . get ( " retCode" ) != 0 :
return False
positions_list = positions_resp . get ( ' result' , { } ) . get ( ' list' , [ ] )
positions_list = positions_resp . get ( " result" , { } ) . get ( " list" , [ ] )
if not positions_list :
return False
position = positions_list [ 0 ]
qty = abs ( safe_float ( position . get ( ' size' ) ) )
side = position . get ( ' side' )
qty = abs ( safe_float ( position . get ( " size" ) ) )
side = position . get ( " side" )
if qty == 0 :
return False
@@ -696,15 +851,18 @@ async def close_user_trade(tg_id: int, symbol: str):
orderType = " Market " ,
qty = str ( qty ) ,
timeInForce = " GTC " ,
reduceOnly = True
reduceOnly = True ,
)
if place_resp . get ( ' retCode' ) == 0 :
if place_resp . get ( " retCode" ) == 0 :
return True
else :
return False
except Exception as e :
logger . error ( f " Ошибка закрытия сделки { symbol } для пользователя { tg_id } : { e } " , exc_info = True )
logger . error (
f " Ошибка закрытия сделки { symbol } для пользователя { tg_id } : { e } " ,
exc_info = True ,
)
return False
@@ -716,13 +874,19 @@ async def close_trade_after_delay(tg_id: int, message, symbol: str, delay_sec: i
await asyncio . sleep ( delay_sec )
result = await close_user_trade ( tg_id , symbol )
if result :
await message . answer ( f " Сделка { symbol } успешно закрыта по таймеру. " ,
reply_markup = inline_markup . back_to_main )
await message . answer (
f " Сделка { symbol } успешно закрыта по таймеру. "
)
logger . info ( f " Сделка { symbol } успешно закрыта по таймеру. " )
else :
await message . answer ( f " Н е удалось закрыть сделку { symbol } по таймеру. " ,
reply_markup = inline_markup . back_to_main )
await message . answer (
f " Н е удалось закрыть сделку { symbol } по таймеру. " ,
reply_markup = inline_markup . back_to_main ,
)
logger . error ( f " Н е удалось закрыть сделку { symbol } по таймеру. " )
except asyncio . CancelledError :
await message . answer ( f " Закрытие сделки { symbol } по таймеру отменено. " , reply_markup = inline_markup . back_to_main )
await message . answer (
f " Закрытие сделки { symbol } по таймеру отменено. " ,
reply_markup = inline_markup . back_to_main ,
)
logger . info ( f " Закрытие сделки { symbol } по таймеру отменено. " )