Updated timer functions
This commit is contained in:
@@ -1,52 +1,67 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import functools
|
||||||
import time
|
import time
|
||||||
import logging.config
|
import logging.config
|
||||||
from pybit import exceptions
|
from pybit import exceptions
|
||||||
from pybit.unified_trading import HTTP
|
from pybit.unified_trading import HTTP
|
||||||
|
|
||||||
from logger_helper.logger_helper import LOGGING_CONFIG
|
from logger_helper.logger_helper import LOGGING_CONFIG
|
||||||
import app.services.Bybit.functions.price_symbol as price_symbol
|
import app.services.Bybit.functions.price_symbol as price_symbol
|
||||||
import app.services.Bybit.functions.balance as balance_g
|
import app.services.Bybit.functions.balance as balance_g
|
||||||
import app.telegram.database.requests as rq
|
import app.telegram.database.requests as rq
|
||||||
|
import app.telegram.Keyboards.inline_keyboards as inline_markup
|
||||||
|
|
||||||
logging.config.dictConfig(LOGGING_CONFIG)
|
logging.config.dictConfig(LOGGING_CONFIG)
|
||||||
logger = logging.getLogger("futures")
|
logger = logging.getLogger("futures")
|
||||||
|
|
||||||
async def info_access_open_deal(message, symbol, trade_mode, margin_mode, leverage, qty):
|
active_start_tasks = {}
|
||||||
|
active_close_tasks = {}
|
||||||
|
|
||||||
|
|
||||||
|
def safe_float(val):
|
||||||
|
try:
|
||||||
|
if val is None or val == '':
|
||||||
|
return 0.0
|
||||||
|
return float(val)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
|
||||||
|
async def info_access_open_deal(message, symbol, trade_mode, margin_mode, leverage, qty, tp, sl, entry_price, limit_price, order_type):
|
||||||
human_margin_mode = 'Isolated' if margin_mode == 'ISOLATED_MARGIN' else 'Cross'
|
human_margin_mode = 'Isolated' if margin_mode == 'ISOLATED_MARGIN' else 'Cross'
|
||||||
text = f'''Позиция была успешна открыта!
|
text = f'''{'Позиция была успешна открыта' if order_type == 'Market' else 'Лимитный ордер установлен'}!
|
||||||
Торговая пара: {symbol}
|
Торговая пара: {symbol}
|
||||||
|
Цена входа: {entry_price if order_type == 'Market' else round(limit_price, 5)}
|
||||||
Движение: {trade_mode}
|
Движение: {trade_mode}
|
||||||
Тип-маржи: {human_margin_mode}
|
Тип-маржи: {human_margin_mode}
|
||||||
Кредитное плечо: {leverage}
|
Кредитное плечо: {leverage}x
|
||||||
Количество: {qty}
|
Количество: {qty}
|
||||||
|
Тейк-профит: {round(tp, 5)}
|
||||||
|
Стоп-лосс: {round(sl, 5)}
|
||||||
'''
|
'''
|
||||||
await message.answer(text=text, parse_mode='html')
|
await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.create_close_deal_markup(symbol))
|
||||||
|
|
||||||
|
|
||||||
async def error_max_step(message):
|
async def error_max_step(message):
|
||||||
await message.answer('Сделка не была совершена, превышен лимит максимального количества ставок в серии.')
|
logger.error('Сделка не была совершена, превышен лимит максимального количества ставок в серии.')
|
||||||
|
await message.answer('Сделка не была совершена, превышен лимит максимального количества ставок в серии.',
|
||||||
|
reply_markup=inline_markup.back_to_main)
|
||||||
|
|
||||||
|
|
||||||
async def error_max_risk(message):
|
async def error_max_risk(message):
|
||||||
await message.answer('Сделка не была совершена, риск убытка превышает допустимый лимит.')
|
logger.error('Сделка не была совершена, риск убытка превышает допустимый лимит.')
|
||||||
|
await message.answer('Сделка не была совершена, риск убытка превышает допустимый лимит.',
|
||||||
|
reply_markup=inline_markup.back_to_main)
|
||||||
|
|
||||||
|
|
||||||
async def open_position(tg_id, message, side: str, margin_mode: str):
|
async def open_position(tg_id, message, side: str, margin_mode: str, tpsl_mode='Full'):
|
||||||
"""
|
|
||||||
Открытие позиции (торговля с мартингейлом и управлением рисками)
|
|
||||||
|
|
||||||
: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)
|
api_key = await rq.get_bybit_api_key(tg_id)
|
||||||
secret_key = await rq.get_bybit_secret_key(tg_id)
|
secret_key = await rq.get_bybit_secret_key(tg_id)
|
||||||
symbol = await rq.get_symbol(tg_id)
|
symbol = await rq.get_symbol(tg_id)
|
||||||
|
|
||||||
data_main_stgs = await rq.get_user_main_settings(tg_id)
|
data_main_stgs = await rq.get_user_main_settings(tg_id)
|
||||||
order_type = data_main_stgs.get('entry_order_type', 'Market')
|
order_type = data_main_stgs.get('entry_order_type')
|
||||||
|
|
||||||
limit_price = None
|
limit_price = None
|
||||||
if order_type == 'Limit':
|
if order_type == 'Limit':
|
||||||
limit_price = await rq.get_limit_price(tg_id)
|
limit_price = await rq.get_limit_price(tg_id)
|
||||||
@@ -55,192 +70,346 @@ async def open_position(tg_id, message, side: str, margin_mode: str):
|
|||||||
bybit_margin_mode = 'ISOLATED_MARGIN' if margin_mode == 'Isolated' else 'REGULAR_MARGIN'
|
bybit_margin_mode = 'ISOLATED_MARGIN' if margin_mode == 'Isolated' else 'REGULAR_MARGIN'
|
||||||
|
|
||||||
client = HTTP(api_key=api_key, api_secret=secret_key)
|
client = HTTP(api_key=api_key, api_secret=secret_key)
|
||||||
|
try:
|
||||||
|
client.set_tp_sl_mode(symbol=symbol, category='linear', tpSlMode='Full')
|
||||||
|
except exceptions.InvalidRequestError as e:
|
||||||
|
if 'same tp sl mode' in str(e):
|
||||||
|
logger.info("Режим TP/SL уже установлен - пропускаем")
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
try:
|
try:
|
||||||
balance = await balance_g.get_balance(tg_id, message)
|
balance = await balance_g.get_balance(tg_id, message)
|
||||||
price = await price_symbol.get_price(tg_id)
|
price = await price_symbol.get_price(tg_id)
|
||||||
|
|
||||||
# Установка маржинального режима
|
|
||||||
client.set_margin_mode(setMarginMode=bybit_margin_mode)
|
client.set_margin_mode(setMarginMode=bybit_margin_mode)
|
||||||
|
|
||||||
martingale_factor = float(data_main_stgs['martingale_factor'])
|
martingale_factor = safe_float(data_main_stgs.get('martingale_factor'))
|
||||||
max_martingale_steps = int(data_main_stgs['maximal_quantity'])
|
max_martingale_steps = int(data_main_stgs.get('maximal_quantity', 0))
|
||||||
starting_quantity = float(data_main_stgs['starting_quantity'])
|
starting_quantity = safe_float(data_main_stgs.get('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)
|
max_risk_percent = safe_float(data_risk_stgs.get('max_risk_deal'))
|
||||||
|
loss_profit = safe_float(data_risk_stgs.get('price_loss'))
|
||||||
|
take_profit = safe_float(data_risk_stgs.get('price_profit'))
|
||||||
|
commission_fee = safe_float(data_risk_stgs.get('commission_fee', 0))
|
||||||
|
positions_resp = client.get_positions(category='linear', symbol=symbol)
|
||||||
|
positions_list = positions_resp.get('result', {}).get('list', [])
|
||||||
|
|
||||||
|
if positions_list:
|
||||||
|
position = positions_list[0]
|
||||||
|
size = safe_float(position.get('size', 0))
|
||||||
|
side_pos = position.get('side', '')
|
||||||
|
if size > 0 and side_pos:
|
||||||
|
entry_price = safe_float(position.get('avgPrice', price))
|
||||||
|
else:
|
||||||
|
entry_price = price
|
||||||
|
else:
|
||||||
|
entry_price = price
|
||||||
|
|
||||||
|
if order_type == 'Market':
|
||||||
|
base_price = entry_price
|
||||||
|
else:
|
||||||
|
base_price = limit_price
|
||||||
|
|
||||||
|
if side.lower() == 'buy':
|
||||||
|
take_profit_price = base_price * (1 + take_profit / 100)
|
||||||
|
stop_loss_price = base_price * (1 - loss_profit / 100)
|
||||||
|
else:
|
||||||
|
take_profit_price = base_price * (1 - take_profit / 100)
|
||||||
|
stop_loss_price = base_price * (1 + loss_profit / 100)
|
||||||
|
|
||||||
|
take_profit_price = max(take_profit_price, 0)
|
||||||
|
stop_loss_price = max(stop_loss_price, 0)
|
||||||
|
|
||||||
current_martingale_step = 0
|
current_martingale_step = 0
|
||||||
next_quantity = starting_quantity
|
next_quantity = starting_quantity
|
||||||
last_quantity = starting_quantity
|
|
||||||
realised_pnl = 0.0
|
realised_pnl = 0.0
|
||||||
|
|
||||||
# Получаем текущие открытые позиции по символу
|
current_martingale = await rq.get_martingale_step(tg_id)
|
||||||
positions_resp = client.get_positions(category='linear', symbol=symbol)
|
current_martingale_step = int(current_martingale)
|
||||||
positions_list = positions_resp.get('result', {}).get('list', [])
|
|
||||||
current_martingale_step = await rq.get_martingale_step(tg_id)
|
|
||||||
if positions_list:
|
if positions_list:
|
||||||
position = positions_list[0]
|
|
||||||
realised_pnl = float(position.get('unrealisedPnl', 0.0))
|
|
||||||
|
|
||||||
|
|
||||||
if realised_pnl > 0:
|
if realised_pnl > 0:
|
||||||
current_martingale_step = 0
|
current_martingale_step = 0
|
||||||
next_quantity = starting_quantity
|
next_quantity = starting_quantity
|
||||||
|
|
||||||
else:
|
else:
|
||||||
current_martingale_step += 1
|
current_martingale_step += 1
|
||||||
if current_martingale_step > max_martingale_steps:
|
if current_martingale_step > max_martingale_steps:
|
||||||
await error_max_step(message)
|
await error_max_step(message)
|
||||||
return
|
return
|
||||||
next_quantity = starting_quantity * (martingale_factor ** current_martingale_step)
|
next_quantity = float(starting_quantity) * (float(martingale_factor) ** current_martingale_step)
|
||||||
else:
|
else:
|
||||||
# Позиция не открыта — начинаем с начальной ставки
|
|
||||||
next_quantity = starting_quantity
|
next_quantity = starting_quantity
|
||||||
current_martingale_step = 0
|
current_martingale_step = 0
|
||||||
|
|
||||||
# Проверяем риск убытка
|
potential_loss = safe_float(next_quantity) * safe_float(price) * (loss_profit / 100)
|
||||||
potential_loss = next_quantity * price * (loss_profit / 100)
|
allowed_loss = safe_float(balance) * (max_risk_percent / 100)
|
||||||
allowed_loss = balance * (max_risk_percent / 100)
|
|
||||||
|
|
||||||
if potential_loss > allowed_loss:
|
if potential_loss > allowed_loss:
|
||||||
await error_max_risk(message)
|
await error_max_risk(message)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Отправляем запрос на открытие ордера
|
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 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 if min_order_qty > 0 else 0
|
||||||
|
# Минимальное значение из значений параметров на бирже
|
||||||
|
min_order_value = max(min_order_value_calc, min_order_value_api)
|
||||||
|
else:
|
||||||
|
min_order_value = 5.0
|
||||||
|
|
||||||
|
order_value = float(next_quantity) * float(price)
|
||||||
|
|
||||||
|
if order_value < min_order_value:
|
||||||
|
await message.answer(
|
||||||
|
f"Сумма ордера слишком мала: {order_value:.2f} USDT. "
|
||||||
|
f"Минимум для торговли — {min_order_value} USDT. "
|
||||||
|
f"Пожалуйста, увеличьте количество позиций.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
leverage = int(data_main_stgs.get('size_leverage', 1))
|
||||||
|
try:
|
||||||
|
resp = client.set_leverage(
|
||||||
|
category='linear',
|
||||||
|
symbol=symbol,
|
||||||
|
buyLeverage=str(leverage),
|
||||||
|
sellLeverage=str(leverage)
|
||||||
|
)
|
||||||
|
except exceptions.InvalidRequestError as e:
|
||||||
|
if "110043" in str(e):
|
||||||
|
# Плечо уже установлено с таким значением, можем игнорировать
|
||||||
|
logger.info(f"Leverage already set to {leverage} for {symbol}")
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
|
|
||||||
|
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_limit_price = take_profit_price
|
||||||
|
sl_limit_price = stop_loss_price
|
||||||
|
|
||||||
response = client.place_order(
|
response = client.place_order(
|
||||||
category='linear',
|
category='linear',
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
side=side,
|
side=side,
|
||||||
orderType=order_type,
|
orderType=order_type, # Market или Limit
|
||||||
qty=next_quantity,
|
qty=str(next_quantity),
|
||||||
leverage=int(data_main_stgs['size_leverage']),
|
price=str(limit_price) if order_type == 'Limit' and limit_price else None,
|
||||||
price=limit_price if order_type == 'Limit' else None,
|
takeProfit=str(take_profit_price),
|
||||||
takeProfit=takeProfit,
|
tpOrderType=tp_order_type,
|
||||||
stopLoss=loss_profit,
|
tpLimitPrice=str(tp_limit_price) if tp_limit_price else None,
|
||||||
|
stopLoss=str(stop_loss_price),
|
||||||
|
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())}"
|
orderLinkId=f"deal_{symbol}_{int(time.time())}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if response.get('ret_code', -1) == 0:
|
if response.get('retCode', -1) == 0:
|
||||||
await info_access_open_deal(message, symbol, data_main_stgs['trading_mode'], bybit_margin_mode,
|
await info_access_open_deal(message, symbol, data_main_stgs.get('trading_mode', ''),
|
||||||
data_main_stgs['size_leverage'], next_quantity)
|
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)
|
await rq.update_martingale_step(tg_id, current_martingale_step)
|
||||||
|
return True
|
||||||
else:
|
else:
|
||||||
await message.answer(f"Ошибка открытия ордера: {response.get('ret_msg', 'неизвестная ошибка')}")
|
logger.error(f"Ошибка открытия ордера: {response}")
|
||||||
|
await message.answer(f"Ошибка открытия ордера", reply_markup=inline_markup.back_to_main)
|
||||||
|
return False
|
||||||
|
|
||||||
except exceptions.InvalidRequestError as e:
|
except exceptions.InvalidRequestError as e:
|
||||||
logger.error(f"InvalidRequestError: {e}")
|
logger.error(f"InvalidRequestError: {e}")
|
||||||
await message.answer('Ошибка: неверно указана торговая пара или параметры.')
|
await message.answer('Недостаточно средств для размещения нового ордера с заданным количеством и плечом.',
|
||||||
|
reply_markup=inline_markup.back_to_main)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при совершении сделки: {e}")
|
logger.error(f"Ошибка при совершении сделки: {e}")
|
||||||
|
await message.answer('Возникла ошибка при попытке открыть позицию.', reply_markup=inline_markup.back_to_main)
|
||||||
|
|
||||||
|
|
||||||
async def trading_cycle(tg_id, message):
|
async def trading_cycle(tg_id, message):
|
||||||
start_time = time.time()
|
try:
|
||||||
timer_min = await rq.get_user_timer(tg_id)
|
timer_data = await rq.get_user_timer(tg_id)
|
||||||
timer_sec = timer_min * 60 if timer_min else 0
|
timer_min = 0
|
||||||
|
if isinstance(timer_data, dict):
|
||||||
|
timer_min = timer_data.get('timer_minutes') or timer_data.get('timer') or 0
|
||||||
|
else:
|
||||||
|
timer_min = timer_data or 0
|
||||||
|
|
||||||
while True:
|
timer_sec = timer_min * 60
|
||||||
elapsed = time.time() - start_time
|
|
||||||
if timer_sec > 0 and elapsed > timer_sec:
|
if timer_sec > 0:
|
||||||
await message.answer("Время работы по таймеру истекло. Торговля остановлена.")
|
await asyncio.sleep(timer_sec)
|
||||||
await rq.update_martingale_step(tg_id, 0)
|
|
||||||
break
|
|
||||||
|
|
||||||
# Проверяем позиции
|
|
||||||
data_main_stgs = await rq.get_user_main_settings(tg_id)
|
data_main_stgs = await rq.get_user_main_settings(tg_id)
|
||||||
side = 'Buy' if data_main_stgs['trading_mode'] == 'Long' else 'Sell'
|
side = 'Buy' if data_main_stgs.get('trading_mode', '') == 'Long' else 'Sell'
|
||||||
margin_mode = data_main_stgs.get('margin_type', 'Isolated')
|
margin_mode = data_main_stgs.get('margin_type', 'Isolated')
|
||||||
|
|
||||||
# Можно добавлять логику по PNL, стоп-лоссам, тейк-профитам
|
|
||||||
|
|
||||||
await open_position(tg_id, message, side=side, margin_mode=margin_mode)
|
await open_position(tg_id, message, side=side, margin_mode=margin_mode)
|
||||||
|
except asyncio.CancelledError:
|
||||||
await asyncio.sleep(10)
|
logger.info(f"Торговый цикл для пользователя {tg_id} был отменён.")
|
||||||
|
|
||||||
|
|
||||||
|
async def fetch_positions_async(client, symbol):
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
# запускаем блокирующий вызов get_positions в отдельном потоке
|
||||||
|
return await loop.run_in_executor(None, functools.partial(client.get_positions, category='linear', symbol=symbol))
|
||||||
|
|
||||||
async def get_active_positions(message, api_key, secret_key, symbol):
|
|
||||||
|
|
||||||
|
async def get_active_positions(message, api_key, secret_key):
|
||||||
client = HTTP(
|
client = HTTP(
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
api_secret=secret_key
|
api_secret=secret_key
|
||||||
)
|
)
|
||||||
instruments_resp = client.get_instruments_info(category='linear')
|
instruments_resp = client.get_instruments_info(category='linear')
|
||||||
if instruments_resp.get('ret_code') != 0:
|
if instruments_resp.get('retCode') != 0:
|
||||||
return []
|
return []
|
||||||
symbols = [item['symbol'] for item in instruments_resp.get('result', {}).get('list', [])]
|
symbols = [item['symbol'] for item in instruments_resp.get('result', {}).get('list', [])]
|
||||||
|
|
||||||
active_positions = []
|
active_positions = []
|
||||||
|
|
||||||
async def fetch_positions(symbol):
|
for sym in symbols:
|
||||||
try:
|
try:
|
||||||
resp = client.get_positions(category='linear', symbol=symbol)
|
resp = await fetch_positions_async(client, sym)
|
||||||
if resp.get('ret_code') == 0:
|
if resp.get('retCode') == 0:
|
||||||
positions = resp.get('result', {}).get('list', [])
|
positions = resp.get('result', {}).get('list', [])
|
||||||
for pos in positions:
|
for pos in positions:
|
||||||
if pos.get('size') and float(pos['size']) > 0:
|
if pos.get('size') and safe_float(pos['size']) > 0:
|
||||||
active_positions.append(pos)
|
active_positions.append(pos)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при получении позиций: {e}")
|
logger.error(f"Ошибка при получении позиций: {e}")
|
||||||
await message.answer('⚠️ Ошибка при получении позиций')
|
await message.answer('⚠️ Ошибка при получении позиций', reply_markup=inline_markup.back_to_main)
|
||||||
|
|
||||||
for sym in symbols:
|
|
||||||
await fetch_positions(sym)
|
|
||||||
|
|
||||||
return active_positions
|
return active_positions
|
||||||
|
|
||||||
|
|
||||||
async def close_user_trade(tg_id: int, symbol: str) -> bool:
|
async def close_user_trade(tg_id: int, symbol: str, message=None) -> 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:
|
try:
|
||||||
response = client.place_order(
|
api_key = await rq.get_bybit_api_key(tg_id)
|
||||||
category="linear",
|
secret_key = await rq.get_bybit_secret_key(tg_id)
|
||||||
symbol=symbol,
|
client = HTTP(api_key=api_key, api_secret=secret_key)
|
||||||
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}")
|
|
||||||
|
|
||||||
|
# Получаем текущие открытые позиции
|
||||||
|
positions_resp = client.get_positions(category="linear", symbol=symbol)
|
||||||
|
if positions_resp.get('retCode') != 0:
|
||||||
|
return False
|
||||||
|
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')
|
||||||
|
entry_price = safe_float(position.get('avgPrice'))
|
||||||
|
if qty == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Получаем настройки пользователя
|
||||||
|
data_main_stgs = await rq.get_user_main_settings(tg_id)
|
||||||
|
order_type = data_main_stgs.get('entry_order_type')
|
||||||
|
limit_price = await rq.get_limit_price(tg_id)
|
||||||
|
|
||||||
|
# Получаем открытые ордера
|
||||||
|
orders = client.get_open_orders(category='linear', symbol=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', {})
|
||||||
|
# поддержать оба варианта: result это dict с key 'list', или list
|
||||||
|
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))
|
||||||
|
|
||||||
|
if order_type == 'Limit':
|
||||||
|
# Если есть открытый лимитный ордер – отменяем его
|
||||||
|
if order_id:
|
||||||
|
cancel_resp = client.cancel_order(category='linear', symbol=symbol, orderId=order_id)
|
||||||
|
if cancel_resp.get('retCode') != 0:
|
||||||
|
if message:
|
||||||
|
await message.answer("Ошибка при отмене лимитного ордера.",
|
||||||
|
reply_markup=inline_markup.back_to_main)
|
||||||
|
return False
|
||||||
|
# Можно здесь добавить логику выставления лимитного ордера на закрытие, если нужно
|
||||||
|
# В текущем коде отсутствует
|
||||||
|
if message:
|
||||||
|
await message.answer(f"Лимитный ордер отменён, позиция не закрыта автоматически.",
|
||||||
|
reply_markup=inline_markup.back_to_main)
|
||||||
|
return False
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Рыночный ордер для закрытия позиции
|
||||||
|
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:
|
||||||
|
if message:
|
||||||
|
pnl = (current_price - entry_price) * qty if side == "Buy" else (entry_price - current_price) * qty
|
||||||
|
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}%)")
|
||||||
|
await message.answer(text, reply_markup=inline_markup.back_to_main)
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
if message:
|
||||||
|
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)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def close_trade_after_delay(tg_id: int, message, symbol: str, delay_sec: int):
|
||||||
|
try:
|
||||||
|
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)
|
||||||
|
else:
|
||||||
|
await message.answer(f"Не удалось закрыть сделку {symbol} по таймеру.",
|
||||||
|
reply_markup=inline_markup.back_to_main)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
await message.answer(f"Закрытие сделки {symbol} по таймеру отменено.", reply_markup=inline_markup.back_to_main)
|
||||||
|
finally:
|
||||||
|
active_close_tasks.pop(tg_id, None)
|
||||||
|
|
||||||
|
|
||||||
def get_positive_percent(negative_percent: float, manual_positive_percent: float | None) -> float:
|
def get_positive_percent(negative_percent: float, manual_positive_percent: float | None) -> float:
|
||||||
if manual_positive_percent and manual_positive_percent > 0:
|
if manual_positive_percent and manual_positive_percent > 0:
|
||||||
return manual_positive_percent
|
return manual_positive_percent
|
||||||
return abs(negative_percent)
|
return abs(negative_percent)
|
||||||
|
@@ -1,15 +1,18 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging.config
|
import logging.config
|
||||||
from aiogram import F, Router
|
from aiogram import F, Router
|
||||||
|
|
||||||
from logger_helper.logger_helper import LOGGING_CONFIG
|
from logger_helper.logger_helper import LOGGING_CONFIG
|
||||||
from app.services.Bybit.functions import Futures, min_qty
|
|
||||||
from app.services.Bybit.functions.Futures import open_position, close_user_trade, get_active_positions, trading_cycle
|
from app.services.Bybit.functions.Futures import close_user_trade, get_active_positions, close_trade_after_delay, \
|
||||||
|
active_close_tasks, active_start_tasks, trading_cycle, open_position
|
||||||
from app.services.Bybit.functions.balance import get_balance
|
from app.services.Bybit.functions.balance import get_balance
|
||||||
import app.telegram.Keyboards.inline_keyboards as inline_markup
|
import app.telegram.Keyboards.inline_keyboards as inline_markup
|
||||||
import app.telegram.Keyboards.reply_keyboards as reply_markup
|
|
||||||
from pybit.unified_trading import HTTP
|
from pybit.unified_trading import HTTP
|
||||||
import app.telegram.database.requests as rq
|
import app.telegram.database.requests as rq
|
||||||
from aiogram.types import Message, CallbackQuery
|
from aiogram.types import Message, CallbackQuery
|
||||||
|
from app.services.Bybit.functions.price_symbol import get_price
|
||||||
|
|
||||||
# FSM - Механизм состояния
|
# FSM - Механизм состояния
|
||||||
from aiogram.fsm.state import State, StatesGroup
|
from aiogram.fsm.state import State, StatesGroup
|
||||||
@@ -35,16 +38,19 @@ class TradeSetup(StatesGroup):
|
|||||||
waiting_for_timer = State()
|
waiting_for_timer = State()
|
||||||
waiting_for_positive_percent = State()
|
waiting_for_positive_percent = State()
|
||||||
|
|
||||||
|
|
||||||
class state_limit_price(StatesGroup):
|
class state_limit_price(StatesGroup):
|
||||||
price = State()
|
price = State()
|
||||||
|
|
||||||
|
|
||||||
|
class CloseTradeTimerState(StatesGroup):
|
||||||
|
waiting_for_delay = State()
|
||||||
|
|
||||||
|
|
||||||
@router_functions_bybit_trade.callback_query(F.data.in_(['clb_start_trading', 'clb_back_to_main', 'back_to_main']))
|
@router_functions_bybit_trade.callback_query(F.data.in_(['clb_start_trading', 'clb_back_to_main', 'back_to_main']))
|
||||||
async def clb_start_bybit_trade_message(callback: CallbackQuery, state: FSMContext):
|
async def clb_start_bybit_trade_message(callback: CallbackQuery, state: FSMContext):
|
||||||
api = await rq.get_bybit_api_key(callback.from_user.id)
|
|
||||||
secret = await rq.get_bybit_secret_key(callback.from_user.id)
|
|
||||||
balance = await get_balance(callback.from_user.id, callback.message)
|
balance = await get_balance(callback.from_user.id, callback.message)
|
||||||
|
price = await get_price(callback.from_user.id)
|
||||||
|
|
||||||
if balance:
|
if balance:
|
||||||
symbol = await rq.get_symbol(callback.from_user.id)
|
symbol = await rq.get_symbol(callback.from_user.id)
|
||||||
@@ -53,6 +59,7 @@ async def clb_start_bybit_trade_message(callback: CallbackQuery, state: FSMConte
|
|||||||
|
|
||||||
⚖️ Ваш баланс (USDT): {balance}
|
⚖️ Ваш баланс (USDT): {balance}
|
||||||
📊 Текущая торговая пара: {symbol}
|
📊 Текущая торговая пара: {symbol}
|
||||||
|
$$$ Цена: {price}
|
||||||
|
|
||||||
Как начать торговлю?
|
Как начать торговлю?
|
||||||
|
|
||||||
@@ -63,10 +70,9 @@ async def clb_start_bybit_trade_message(callback: CallbackQuery, state: FSMConte
|
|||||||
await callback.message.edit_text(text=text, parse_mode='html', reply_markup=inline_markup.trading_markup)
|
await callback.message.edit_text(text=text, parse_mode='html', reply_markup=inline_markup.trading_markup)
|
||||||
|
|
||||||
|
|
||||||
async def start_bybit_trade_message(message, state):
|
async def start_bybit_trade_message(message: Message, state: FSMContext):
|
||||||
api = await rq.get_bybit_api_key(message.from_user.id)
|
|
||||||
secret = await rq.get_bybit_secret_key(message.from_user.id)
|
|
||||||
balance = await get_balance(message.from_user.id, message)
|
balance = await get_balance(message.from_user.id, message)
|
||||||
|
price = await get_price(message.from_user.id)
|
||||||
|
|
||||||
if balance:
|
if balance:
|
||||||
symbol = await rq.get_symbol(message.from_user.id)
|
symbol = await rq.get_symbol(message.from_user.id)
|
||||||
@@ -75,6 +81,7 @@ async def start_bybit_trade_message(message, state):
|
|||||||
|
|
||||||
⚖️ Ваш баланс (USDT): {balance}
|
⚖️ Ваш баланс (USDT): {balance}
|
||||||
📊 Текущая торговая пара: {symbol}
|
📊 Текущая торговая пара: {symbol}
|
||||||
|
$$$ Цена: {price}
|
||||||
|
|
||||||
Как начать торговлю?
|
Как начать торговлю?
|
||||||
|
|
||||||
@@ -138,7 +145,7 @@ async def entry_order_type_callback(callback: CallbackQuery, state: FSMContext):
|
|||||||
await state.update_data(entry_order_type=order_type)
|
await state.update_data(entry_order_type=order_type)
|
||||||
await rq.update_entry_order_type(callback.from_user.id, order_type)
|
await rq.update_entry_order_type(callback.from_user.id, order_type)
|
||||||
await callback.message.answer(f"Выбран тип входа в позицию: {order_type}",
|
await callback.message.answer(f"Выбран тип входа в позицию: {order_type}",
|
||||||
reply_markup=inline_markup.start_trading_markup)
|
reply_markup=inline_markup.start_trading_markup)
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Произошла ошибка при обновлении типа входа в позицию: {e}")
|
logger.error(f"Произошла ошибка при обновлении типа входа в позицию: {e}")
|
||||||
@@ -152,14 +159,14 @@ async def set_limit_price(message: Message, state: FSMContext):
|
|||||||
try:
|
try:
|
||||||
price = float(message.text)
|
price = float(message.text)
|
||||||
if price <= 0:
|
if price <= 0:
|
||||||
await message.answer("Цена должна быть положительным числом. Попробуйте снова.", reply_markup=inline_markup.cancel)
|
await message.answer("Цена должна быть положительным числом. Попробуйте снова.",
|
||||||
|
reply_markup=inline_markup.cancel)
|
||||||
return
|
return
|
||||||
except ValueError:
|
except ValueError:
|
||||||
await message.answer("Некорректный формат цены. Введите число.", reply_markup=inline_markup.cancel)
|
await message.answer("Некорректный формат цены. Введите число.", reply_markup=inline_markup.cancel)
|
||||||
return
|
return
|
||||||
|
|
||||||
await state.update_data(entry_order_type='Limit', limit_price=price)
|
await state.update_data(entry_order_type='Limit', limit_price=price)
|
||||||
data = await state.get_data()
|
|
||||||
|
|
||||||
await rq.update_entry_order_type(message.from_user.id, 'Limit')
|
await rq.update_entry_order_type(message.from_user.id, 'Limit')
|
||||||
await rq.update_limit_price(message.from_user.id, price)
|
await rq.update_limit_price(message.from_user.id, price)
|
||||||
@@ -168,82 +175,61 @@ async def set_limit_price(message: Message, state: FSMContext):
|
|||||||
await state.clear()
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@router_functions_bybit_trade.callback_query(F.data == "clb_my_deals")
|
@router_functions_bybit_trade.callback_query(F.data == "clb_my_deals")
|
||||||
async def show_my_trades_callback(callback: CallbackQuery):
|
async def show_my_trades_callback(callback: CallbackQuery):
|
||||||
tg_id = callback.from_user.id
|
await callback.answer() # сразу отвечаем Telegram, освобождаем callback
|
||||||
|
|
||||||
api_key = await rq.get_bybit_api_key(tg_id)
|
async def process():
|
||||||
secret_key = await rq.get_bybit_secret_key(tg_id)
|
tg_id = callback.from_user.id
|
||||||
symbol = await rq.get_symbol(tg_id)
|
api_key = await rq.get_bybit_api_key(tg_id)
|
||||||
|
secret_key = await rq.get_bybit_secret_key(tg_id)
|
||||||
|
|
||||||
trades = await get_active_positions(callback.message, api_key, secret_key, symbol)
|
trades = await get_active_positions(callback.message, api_key, secret_key)
|
||||||
|
|
||||||
if not trades:
|
if not trades:
|
||||||
await callback.message.answer("Нет активных позиций.")
|
await callback.message.answer("Нет активных позиций.")
|
||||||
await callback.answer()
|
return
|
||||||
return
|
|
||||||
|
|
||||||
keyboard = inline_markup.create_trades_inline_keyboard(trades)
|
keyboard = inline_markup.create_trades_inline_keyboard(trades)
|
||||||
|
|
||||||
await callback.message.answer(
|
await callback.message.answer(
|
||||||
"Выберите сделку из списка:",
|
"Выберите сделку из списка:",
|
||||||
reply_markup=keyboard
|
reply_markup=keyboard
|
||||||
)
|
)
|
||||||
await callback.answer()
|
|
||||||
|
asyncio.create_task(process())
|
||||||
|
|
||||||
|
|
||||||
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith('select_trade:'))
|
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith('select_trade:'))
|
||||||
async def on_trade_selected(callback: CallbackQuery):
|
async def on_trade_selected(callback: CallbackQuery):
|
||||||
symbol = callback.data.split(':')[1]
|
|
||||||
|
|
||||||
tg_id = callback.from_user.id
|
|
||||||
api_key = await rq.get_bybit_api_key(tg_id)
|
|
||||||
secret_key = await rq.get_bybit_secret_key(tg_id)
|
|
||||||
|
|
||||||
positions = await get_active_positions(callback.message, api_key, secret_key, symbol)
|
|
||||||
|
|
||||||
# Если несколько позиций по символу, можно выбрать нужную или взять первую
|
|
||||||
if not positions:
|
|
||||||
await callback.message.answer("Позиция не найдена")
|
|
||||||
await callback.answer()
|
|
||||||
return
|
|
||||||
|
|
||||||
pos = positions[0]
|
|
||||||
symbol = pos.get('symbol')
|
|
||||||
side = pos.get('side')
|
|
||||||
entry_price = pos.get('entryPrice') # Цена открытия позиции
|
|
||||||
current_price = pos.get('price') # Текущая цена (если есть)
|
|
||||||
|
|
||||||
text = (f"Информация по позиции:\n"
|
|
||||||
f"Название: {symbol}\n"
|
|
||||||
f"Направление: {side}\n"
|
|
||||||
f"Цена покупки: {entry_price}\n"
|
|
||||||
f"Текущая цена: {current_price if current_price else 'N/A'}")
|
|
||||||
|
|
||||||
keyboard = inline_markup.create_close_deal_markup(symbol)
|
|
||||||
|
|
||||||
await callback.message.answer(text, reply_markup=keyboard)
|
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
|
|
||||||
|
async def process():
|
||||||
|
tg_id = callback.from_user.id
|
||||||
|
api_key = await rq.get_bybit_api_key(tg_id)
|
||||||
|
secret_key = await rq.get_bybit_secret_key(tg_id)
|
||||||
|
|
||||||
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_deal:"))
|
positions = await get_active_positions(callback.message, api_key, secret_key)
|
||||||
async def close_trade_callback(callback: CallbackQuery):
|
|
||||||
symbol = callback.data.split(':')[1]
|
|
||||||
tg_id = callback.from_user.id
|
|
||||||
|
|
||||||
result = await close_user_trade(tg_id, symbol)
|
if not positions:
|
||||||
|
await callback.message.answer("Позиция не найдена")
|
||||||
|
return
|
||||||
|
|
||||||
if result:
|
pos = positions[0]
|
||||||
await callback.message.answer(f"Сделка {symbol} успешно закрыта.")
|
text = (f"Информация по позиции:\n"
|
||||||
else:
|
f"Название: {pos.get('symbol')}\n"
|
||||||
await callback.message.answer(f"Не удалось закрыть сделку {symbol}.")
|
f"Направление: {pos.get('side')}\n"
|
||||||
|
f"Цена покупки: {pos.get('entryPrice')}\n"
|
||||||
|
f"Текущая цена: {pos.get('price', 'N/A')}")
|
||||||
|
|
||||||
await callback.answer()
|
keyboard = inline_markup.create_close_deal_markup(pos.get('symbol'))
|
||||||
|
await callback.message.answer(text, reply_markup=keyboard)
|
||||||
|
|
||||||
|
asyncio.create_task(process())
|
||||||
|
|
||||||
|
|
||||||
@router_functions_bybit_trade.callback_query(F.data == "clb_start_chatbot_trading")
|
@router_functions_bybit_trade.callback_query(F.data == "clb_start_chatbot_trading")
|
||||||
async def start_trading_process(callback: CallbackQuery, state: FSMContext):
|
async def start_trading_process(callback: CallbackQuery):
|
||||||
tg_id = callback.from_user.id
|
tg_id = callback.from_user.id
|
||||||
message = callback.message
|
message = callback.message
|
||||||
|
|
||||||
@@ -302,31 +288,122 @@ async def start_trading_process(callback: CallbackQuery, state: FSMContext):
|
|||||||
# Сообщаем о начале торговли
|
# Сообщаем о начале торговли
|
||||||
await message.answer("Начинаю торговлю с использованием текущих настроек...")
|
await message.answer("Начинаю торговлю с использованием текущих настроек...")
|
||||||
|
|
||||||
# Открываем позицию (вызывает Futures.open_position)
|
|
||||||
success = await open_position(tg_id, message, side=side, margin_mode=margin_mode)
|
|
||||||
if not success:
|
|
||||||
await message.answer('⚠️ Ошибка при совершении сделки', reply_markup=inline_markup.back_to_main)
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
# Проверяем таймер и информируем пользователя
|
|
||||||
|
|
||||||
timer_data = await rq.get_user_timer(tg_id)
|
timer_data = await rq.get_user_timer(tg_id)
|
||||||
timer_minutes = timer_data.get('timer') if isinstance(timer_data, dict) else timer_data
|
if isinstance(timer_data, dict):
|
||||||
if timer_minutes and timer_minutes > 0:
|
timer_minute = timer_data.get('timer_minutes', 0)
|
||||||
await message.answer(f"Торговля будет работать по таймеру: {timer_minutes} мин.")
|
|
||||||
asyncio.create_task(trading_cycle(tg_id, message))
|
|
||||||
else:
|
else:
|
||||||
await message.answer(
|
timer_minute = timer_data or 0
|
||||||
"Торговля начата без ограничения по времени. Для остановки нажмите кнопку 'Закрыть сделку'.",
|
|
||||||
reply_markup=inline_markup.create_close_deal_markup(symbol)
|
logger.info(f"Timer minutes for user {tg_id}: {timer_minute}")
|
||||||
)
|
|
||||||
|
symbol = await rq.get_symbol(tg_id)
|
||||||
|
|
||||||
|
if timer_minute > 0:
|
||||||
|
old_task = active_start_tasks.get(tg_id)
|
||||||
|
if old_task:
|
||||||
|
old_task.cancel()
|
||||||
|
# можно ждать завершения старой задачи, если в async функции
|
||||||
|
task = asyncio.create_task(trading_cycle(tg_id, message, symbol))
|
||||||
|
active_start_tasks[tg_id] = task
|
||||||
|
await message.answer(f"Торговля начнётся через {timer_minute} мин. Для отмены нажмите кнопку ниже.",
|
||||||
|
reply_markup=inline_markup.cancel_start_markup)
|
||||||
|
await rq.update_user_timer(tg_id, minutes=0)
|
||||||
|
else:
|
||||||
|
await open_position(tg_id, message, side=side, margin_mode=margin_mode)
|
||||||
|
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router_functions_bybit_trade.callback_query(F.data == "clb_stop_timer")
|
||||||
|
async def cancel_start_callback(callback: CallbackQuery):
|
||||||
|
tg_id = callback.from_user.id
|
||||||
|
task = active_start_tasks.get(tg_id)
|
||||||
|
if task:
|
||||||
|
task.cancel()
|
||||||
|
del active_start_tasks[tg_id]
|
||||||
|
await callback.message.answer("Торговля по таймеру отменена.", reply_markup=inline_markup.back_to_main)
|
||||||
|
else:
|
||||||
|
await callback.message.answer("Нет активности для отмены.", reply_markup=inline_markup.back_to_main)
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router_functions_bybit_trade.callback_query(F.data == "clb_stop_timer")
|
||||||
|
async def cancel_start_callback(callback: CallbackQuery):
|
||||||
|
tg_id = callback.from_user.id
|
||||||
|
task = active_close_tasks.get(tg_id)
|
||||||
|
if task:
|
||||||
|
task.cancel()
|
||||||
|
del active_close_tasks[tg_id]
|
||||||
|
await callback.message.answer("Таймер отменен.", reply_markup=inline_markup.back_to_main)
|
||||||
|
else:
|
||||||
|
await callback.message.answer("Нет активности для отмены.", reply_markup=inline_markup.back_to_main)
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_deal:"))
|
||||||
|
async def close_trade_callback(callback: CallbackQuery):
|
||||||
|
symbol = callback.data.split(':')[1]
|
||||||
|
tg_id = callback.from_user.id
|
||||||
|
|
||||||
|
result = await close_user_trade(tg_id, symbol)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
await callback.message.answer(f"Сделка {symbol} успешно закрыта.", reply_markup=inline_markup.back_to_main)
|
||||||
|
else:
|
||||||
|
await callback.message.answer(f"Не удалось закрыть сделку {symbol}.")
|
||||||
|
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_deal_by_timer:"))
|
||||||
|
async def ask_close_delay(callback: CallbackQuery, state: FSMContext):
|
||||||
|
symbol = callback.data.split(":")[1]
|
||||||
|
await state.update_data(symbol=symbol)
|
||||||
|
await state.set_state(CloseTradeTimerState.waiting_for_delay)
|
||||||
|
await callback.message.answer("Введите задержку в минутах до закрытия сделки (например, 60):")
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
|
||||||
|
@router_functions_bybit_trade.message(CloseTradeTimerState.waiting_for_delay)
|
||||||
|
async def process_close_delay(message: Message, state: FSMContext):
|
||||||
|
try:
|
||||||
|
delay_minutes = int(message.text.strip())
|
||||||
|
if delay_minutes <= 0:
|
||||||
|
await message.answer("Введите положительное число.")
|
||||||
|
return
|
||||||
|
except ValueError:
|
||||||
|
await message.answer("Некорректный ввод. Введите число в минутах.")
|
||||||
|
return
|
||||||
|
|
||||||
|
data = await state.get_data()
|
||||||
|
symbol = data.get("symbol")
|
||||||
|
tg_id = message.from_user.id
|
||||||
|
|
||||||
|
delay = delay_minutes * 60
|
||||||
|
|
||||||
|
# Отменяем предыдущую задачу, если есть
|
||||||
|
if tg_id in active_close_tasks:
|
||||||
|
active_close_tasks[tg_id].cancel()
|
||||||
|
|
||||||
|
task = asyncio.create_task(close_trade_after_delay(tg_id, message, symbol, delay))
|
||||||
|
active_close_tasks[tg_id] = task
|
||||||
|
|
||||||
|
await message.answer(f"Закрытие сделки {symbol} запланировано через {delay} секунд.",
|
||||||
|
reply_markup=inline_markup.cancel_start_markup)
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
|
@router_functions_bybit_trade.callback_query(F.data == "clb_change_martingale_reset")
|
||||||
|
async def reset_martingale(callback: CallbackQuery):
|
||||||
|
await callback.answer()
|
||||||
|
tg_id = callback.from_user.id
|
||||||
|
await rq.update_martingale_step(tg_id, 0)
|
||||||
|
await callback.message.answer("Сброс шагов мартингейла выполнен. Торговля начнется заново с начального объема.",
|
||||||
|
reply_markup=inline_markup.back_to_main)
|
||||||
|
|
||||||
|
|
||||||
@router_functions_bybit_trade.callback_query(F.data == "clb_cancel")
|
@router_functions_bybit_trade.callback_query(F.data == "clb_cancel")
|
||||||
async def cancel(callback: CallbackQuery, state: FSMContext):
|
async def cancel(callback: CallbackQuery, state: FSMContext):
|
||||||
await state.clear()
|
await state.clear()
|
||||||
await callback.message.answer("Отменено!", reply_markup=inline_markup.back_to_main)
|
await callback.message.answer("Отменено!", reply_markup=inline_markup.back_to_main)
|
||||||
await callback.answer()
|
await callback.answer()
|
||||||
|
Reference in New Issue
Block a user