1
0
forked from kodorvan/stcs
This commit is contained in:
algizn97
2025-08-30 16:29:56 +05:00
parent 3462078a47
commit 2ee8c9916f
16 changed files with 523 additions and 459 deletions

View File

@@ -17,6 +17,18 @@ logger = logging.getLogger("futures")
processed_trade_ids = set() processed_trade_ids = set()
async def get_bybit_client(tg_id):
"""
Асинхронно получает экземпляр клиента Bybit.
:param tg_id: int - ID пользователя Telegram
:return: HTTP - экземпляр клиента Bybit
"""
api_key = await rq.get_bybit_api_key(tg_id)
secret_key = await rq.get_bybit_secret_key(tg_id)
return HTTP(api_key=api_key, api_secret=secret_key)
def safe_float(val) -> float: def safe_float(val) -> float:
""" """
Безопасное преобразование значения в float. Безопасное преобразование значения в float.
@@ -37,20 +49,17 @@ def format_trade_details_position(data, commission_fee):
""" """
msg = data.get('data', [{}])[0] msg = data.get('data', [{}])[0]
closed_size = float(msg.get('closedSize', 0)) closed_size = safe_float(msg.get('closedSize', 0))
symbol = msg.get('symbol', 'N/A') symbol = msg.get('symbol', 'N/A')
entry_price = float(msg.get('execPrice', 0)) entry_price = safe_float(msg.get('execPrice', 0))
qty = float(msg.get('execQty', 0)) qty = safe_float(msg.get('execQty', 0))
order_type = msg.get('orderType', 'N/A') order_type = msg.get('orderType', 'N/A')
side = msg.get('side', '') side = msg.get('side', '')
commission = float(msg.get('execFee', 0)) commission = safe_float(msg.get('execFee', 0))
pnl = float(msg.get('execPnl', 0)) pnl = safe_float(msg.get('execPnl', 0))
if commission_fee == "Да": if commission_fee == "Да":
if pnl >= 0: pnl -= commission
pnl -= commission
else:
pnl -= commission
movement = '' movement = ''
if side.lower() == 'buy': if side.lower() == 'buy':
@@ -72,7 +81,7 @@ def format_trade_details_position(data, commission_fee):
f"Комиссия за сделку: {commission:.6f}\n" f"Комиссия за сделку: {commission:.6f}\n"
f"Реализованная прибыль: {pnl:.6f} USDT" f"Реализованная прибыль: {pnl:.6f} USDT"
) )
else: if order_type == 'Market':
return ( return (
f"Сделка открыта:\n" f"Сделка открыта:\n"
f"Торговая пара: {symbol}\n" f"Торговая пара: {symbol}\n"
@@ -82,6 +91,77 @@ def format_trade_details_position(data, commission_fee):
f"Движение: {movement}\n" f"Движение: {movement}\n"
f"Комиссия за сделку: {commission:.6f}" f"Комиссия за сделку: {commission:.6f}"
) )
return None
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', '')
movement = ''
if side.lower() == 'buy':
movement = 'Покупка'
elif side.lower() == 'sell':
movement = 'Продажа'
else:
movement = side
if order_status.lower() == 'filled' and order_type.lower() == 'limit':
text = (
f"Ордер исполнен:\n"
f"Торговая пара: {symbol}\n"
f"Цена исполнения: {price:.6f}\n"
f"Количество: {qty}\n"
f"Исполнено позиций: {cum_exec_qty}\n"
f"Тип ордера: {order_type}\n"
f"Движение: {movement}\n"
f"Тейк-профит: {take_profit:.6f}\n"
f"Стоп-лосс: {stop_loss:.6f}\n"
f"Комиссия за сделку: {cum_exec_fee:.6f}\n"
)
logger.info(text)
return text
elif order_status.lower() == 'new':
text = (
f"Ордер создан:\n"
f"Торговая пара: {symbol}\n"
f"Цена: {price:.6f}\n"
f"Количество: {qty}\n"
f"Тип ордера: {order_type}\n"
f"Движение: {movement}\n"
f"Тейк-профит: {take_profit:.6f}\n"
f"Стоп-лосс: {stop_loss:.6f}\n"
)
logger.info(text)
return text
elif order_status.lower() == 'cancelled':
text = (
f"Ордер отменен:\n"
f"Торговая пара: {symbol}\n"
f"Цена: {price:.6f}\n"
f"Количество: {qty}\n"
f"Тип ордера: {order_type}\n"
f"Движение: {movement}\n"
f"Тейк-профит: {take_profit:.6f}\n"
f"Стоп-лосс: {stop_loss:.6f}\n"
)
logger.info(text)
return text
return None
def parse_pnl_from_msg(msg) -> float: def parse_pnl_from_msg(msg) -> float:
@@ -89,70 +169,79 @@ def parse_pnl_from_msg(msg) -> float:
Извлекает реализованную прибыль/убыток из сообщения. Извлекает реализованную прибыль/убыток из сообщения.
""" """
try: try:
return float(msg.get('realisedPnl', 0)) data = msg.get('data', [{}])[0]
return float(data.get('execPnl', 0))
except Exception as e: except Exception as e:
logger.error(f"Ошибка при извлечении реализованной прибыли: {e}") logger.error(f"Ошибка при извлечении реализованной прибыли: {e}")
return 0.0 return 0.0
async def handle_execution_message(message, msg: dict) -> None: async def handle_execution_message(message, msg):
""" """
Обработчик сообщений об исполнении сделки. Обработчик сообщений об исполнении сделки.
Логирует событие и проверяет условия для мартингейла и TP. Логирует событие и проверяет условия для мартингейла и TP.
""" """
# logger.info(f"Исполнена сделка:\n{json.dumps(msg, indent=4, ensure_ascii=False)}") # 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 tg_id = message.from_user.id
data = msg.get('data', [{}])[0]
data_main_stgs = await rq.get_user_main_settings(tg_id)
data_main_risk_stgs = await rq.get_user_risk_management_settings(tg_id) data_main_risk_stgs = await rq.get_user_risk_management_settings(tg_id)
take_profit_percent = safe_float(data_main_stgs.get('take_profit_percent', 2))
commission_fee = data_main_risk_stgs.get('commission_fee', "ДА") commission_fee = data_main_risk_stgs.get('commission_fee', "ДА")
symbol = await rq.get_symbol(tg_id) pnl = parse_pnl_from_msg(msg)
api_key = await rq.get_bybit_api_key(tg_id) data_main_stgs = await rq.get_user_main_settings(tg_id)
api_secret = await rq.get_bybit_secret_key(tg_id) symbol = data.get('symbol')
client = HTTP(api_key=api_key, api_secret=api_secret) switch_mode = data_main_stgs.get('switch_mode', 'Включено')
positions_resp = client.get_positions(category='linear', symbol=symbol) trading_mode = data_main_stgs.get('trading_mode', 'Long')
positions_list = positions_resp.get('result', {}).get('list', []) trigger = await rq.get_for_registration_trigger(tg_id)
position = positions_list[0] if positions_list else None margin_mode = data_main_stgs.get('margin_type', 'Isolated')
starting_quantity = safe_float(data_main_stgs.get('starting_quantity'))
trade_info = format_trade_details_position(msg, commission_fee=commission_fee) trade_info = format_trade_details_position(data=msg, commission_fee=commission_fee)
await message.answer(f"{trade_info}", reply_markup=inline_markup.back_to_main)
liquidation_threshold = -100 if trade_info:
await message.answer(f"{trade_info}", reply_markup=inline_markup.back_to_main)
if pnl <= liquidation_threshold: side = None
current_step = int(await rq.get_martingale_step(tg_id)) if switch_mode == 'Включено':
current_step += 1 switch_state = data_main_stgs.get('switch_state', 'Long')
await rq.update_martingale_step(tg_id, current_step) side = 'Buy' if switch_state == 'Long' else 'Sell'
else:
if trading_mode == 'Long':
side = 'Buy'
elif trading_mode == 'Short':
side = 'Sell'
else:
side = 'Buy'
side = 'Buy' if position and position.get('side', '').lower() == 'long' else 'Sell' if trigger == "Автоматический":
margin_mode = data_main_stgs.get('margin_type', 'Isolated') if pnl < 0:
await open_position(tg_id, message, side=side, margin_mode=margin_mode) martingale_factor = safe_float(data_main_stgs.get('martingale_factor'))
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)
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)
elif position: elif pnl > 0:
entry_price = safe_float(position.get('avgPrice')) await rq.update_martingale_step(tg_id, 0)
side = position.get('side', '') await message.answer("❗️ Прибыль достигнута, шаг мартингейла сброшен. "
current_price = float(position.get('markPrice', 0)) "Начинаем новую серию ставок")
await open_position(tg_id, message, side=side, margin_mode=margin_mode, symbol=symbol,
quantity=starting_quantity)
if side.lower() == 'long':
take_profit_trigger_price = entry_price * (1 + take_profit_percent / 100) async def handle_order_message(message, msg: dict) -> None:
if current_price >= take_profit_trigger_price: """
await close_user_trade(tg_id, symbol, message) Обработчик сообщений об исполнении ордера.
await rq.update_martingale_step(tg_id, 0) Логирует событие и проверяет условия для мартингейла и TP.
elif side.lower() == 'short': """
take_profit_trigger_price = entry_price * (1 - take_profit_percent / 100) # logger.info(f"Исполнен ордер:\n{json.dumps(msg, indent=4, ensure_ascii=False)}")
if current_price <= take_profit_trigger_price:
await close_user_trade(tg_id, symbol, message) trade_info = format_order_details_position(msg)
await rq.update_martingale_step(tg_id, 0)
if trade_info:
await message.answer(f"{trade_info}", reply_markup=inline_markup.back_to_main)
async def error_max_step(message) -> None: async def error_max_step(message) -> None:
@@ -173,131 +262,49 @@ async def error_max_risk(message) -> None:
reply_markup=inline_markup.back_to_main) reply_markup=inline_markup.back_to_main)
async def open_position(tg_id, message, side: str, margin_mode: str, tpsl_mode='Full'): async def open_position(tg_id, message, side: str, margin_mode: str, symbol, quantity, tpsl_mode='Full'):
""" """
Открывает позицию на Bybit с учётом настроек пользователя, маржи, размера лота, платформы и риска. Открывает позицию на Bybit с учётом настроек пользователя, маржи, размера лота, платформы и риска.
Возвращает True при успехе, False при ошибках открытия ордера, None при исключениях. Возвращает True при успехе, False при ошибках открытия ордера, None при исключениях.
""" """
api_key = await rq.get_bybit_api_key(tg_id)
secret_key = await rq.get_bybit_secret_key(tg_id)
symbol = await rq.get_symbol(tg_id)
data_main_stgs = await rq.get_user_main_settings(tg_id)
order_type = data_main_stgs.get('entry_order_type')
limit_price = None
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)
bybit_margin_mode = 'ISOLATED_MARGIN' if margin_mode == 'Isolated' else 'REGULAR_MARGIN'
client = HTTP(api_key=api_key, api_secret=secret_key)
try: try:
client.set_tp_sl_mode(symbol=symbol, category='linear', tpSlMode='Full') client = await get_bybit_client(tg_id)
except exceptions.InvalidRequestError as e: data_main_stgs = await rq.get_user_main_settings(tg_id)
if 'same tp sl mode' in str(e): order_type = data_main_stgs.get('entry_order_type')
logger.info("Режим TP/SL уже установлен - пропускаем") bybit_margin_mode = 'ISOLATED_MARGIN' if margin_mode == 'Isolated' else 'REGULAR_MARGIN'
else:
raise limit_price = None
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)
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, symbol=symbol)
entry_price = safe_float(price)
client.set_margin_mode(setMarginMode=bybit_margin_mode)
martingale_factor = safe_float(data_main_stgs.get('martingale_factor'))
max_martingale_steps = int(data_main_stgs.get('maximal_quantity', 0)) max_martingale_steps = int(data_main_stgs.get('maximal_quantity', 0))
starting_quantity = safe_float(data_main_stgs.get('starting_quantity')) current_martingale = await rq.get_martingale_step(tg_id)
max_risk_percent = safe_float(data_risk_stgs.get('max_risk_deal')) max_risk_percent = safe_float(data_risk_stgs.get('max_risk_deal'))
loss_profit = safe_float(data_risk_stgs.get('price_loss')) loss_profit = safe_float(data_risk_stgs.get('price_loss'))
take_profit = safe_float(data_risk_stgs.get('price_profit'))
positions_resp = client.get_positions(category='linear', symbol=symbol)
positions_list = positions_resp.get('result', {}).get('list', [])
if positions_list: if order_type == 'Limit' and limit_price:
position = positions_list[0] price_for_calc = limit_price
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: else:
entry_price = price price_for_calc = entry_price
if order_type == 'Market': potential_loss = safe_float(quantity) * price_for_calc * (loss_profit / 100)
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
next_quantity = starting_quantity
realised_pnl = 0.0
current_martingale = await rq.get_martingale_step(tg_id)
current_martingale_step = int(current_martingale)
if positions_list:
if realised_pnl > 0:
current_martingale_step = 0
next_quantity = starting_quantity
else:
current_martingale_step += 1
if current_martingale_step > max_martingale_steps:
await error_max_step(message)
return
next_quantity = float(starting_quantity) * (float(martingale_factor) ** current_martingale_step)
else:
next_quantity = starting_quantity
current_martingale_step = 0
potential_loss = safe_float(next_quantity) * safe_float(price) * (loss_profit / 100)
allowed_loss = safe_float(balance) * (max_risk_percent / 100) allowed_loss = safe_float(balance) * (max_risk_percent / 100)
if max_martingale_steps == current_martingale:
await error_max_step(message)
return
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) client.set_margin_mode(setMarginMode=bybit_margin_mode)
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"Пожалуйста, увеличьте количество позиций.", reply_markup=inline_markup.back_to_main)
return False
leverage = int(data_main_stgs.get('size_leverage', 1)) leverage = int(data_main_stgs.get('size_leverage', 1))
try: try:
@@ -313,53 +320,157 @@ async def open_position(tg_id, message, side: str, margin_mode: str, tpsl_mode='
else: else:
raise e raise e
if tpsl_mode == 'Full': instruments_resp = client.get_instruments_info(category='linear', symbol=symbol)
tp_order_type = 'Market' if instruments_resp.get('retCode') == 0:
sl_order_type = 'Market' instrument_info = instruments_resp.get('result', {}).get('list', [])
tp_limit_price = None if instrument_info:
sl_limit_price = None instrument = instrument_info[0]
else: # Partial min_order_qty = float(instrument.get('minOrderQty', 0))
tp_order_type = 'Limit' min_order_value_api = float(instrument.get('minOrderValue', 0))
sl_order_type = 'Limit'
tp_limit_price = take_profit_price
sl_limit_price = stop_loss_price
response = client.place_order( if min_order_value_api == 0:
category='linear', min_order_value_api = 5.0
symbol=symbol, min_order_value_calc = min_order_qty * price_for_calc if min_order_qty > 0 else 0
side=side, min_order_value = max(min_order_value_calc, min_order_value_api)
orderType=order_type, else:
qty=str(next_quantity), min_order_value = 5.0
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,
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())}"
)
if response.get('retCode', -1) == 0: order_value = float(quantity) * price_for_calc
await rq.update_martingale_step(tg_id, current_martingale_step) if order_value < min_order_value:
return True await message.answer(
else: f"Сумма ордера слишком мала: {order_value:.2f} USDT. "
logger.error(f"Ошибка открытия ордера: {response}") f"Минимум для торговли — {min_order_value} USDT. "
await message.answer(f"Ошибка открытия ордера", reply_markup=inline_markup.back_to_main) f"Пожалуйста, увеличьте количество позиций.", reply_markup=inline_markup.back_to_main)
return False return False
if bybit_margin_mode == 'ISOLATED_MARGIN':
# Открываем позицию
response = client.place_order(
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,
timeInForce='GTC',
orderLinkId=f"deal_{symbol}_{int(time.time())}"
)
if response.get('retCode', -1) != 0:
logger.error(f"Ошибка открытия ордера: {response}")
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))
if liq_price > 0 and avg_price > 0:
if side.lower() == 'buy':
take_profit_price = avg_price + (avg_price - liq_price)
else:
take_profit_price = avg_price - (liq_price - avg_price)
take_profit_price = max(take_profit_price, 0)
try:
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
resp = client.set_trading_stop(
category='linear',
symbol=symbol,
takeProfit=str(round(take_profit_price, 5)),
tpTriggerBy='LastPrice',
slTriggerBy='LastPrice',
positionIdx=0,
reduceOnly=False,
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)
return False
else:
logger.warning("Не удалось получить цену ликвидации для позиции")
else: # REGULAR_MARGIN
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
if order_type == 'Market':
base_price = entry_price
else:
base_price = limit_price
if side.lower() == 'buy':
take_profit_price = base_price * (1 + loss_profit / 100)
stop_loss_price = base_price * (1 - loss_profit / 100)
else:
take_profit_price = base_price * (1 - loss_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)
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(
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,
takeProfit=str(take_profit_price),
tpOrderType=tp_order_type,
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())}"
)
if response.get('retCode', -1) == 0:
return True
else:
logger.error(f"Ошибка открытия ордера: {response}")
await message.answer(f"Ошибка открытия ордера", reply_markup=inline_markup.back_to_main)
return False
return None
except exceptions.InvalidRequestError as e: except exceptions.InvalidRequestError as e:
logger.error(f"InvalidRequestError: {e}") logger.error(f"InvalidRequestError: {e}", exc_info=True)
await message.answer('Недостаточно средств для размещения нового ордера с заданным количеством и плечом.', await message.answer('Недостаточно средств для размещения нового ордера с заданным количеством и плечом.',
reply_markup=inline_markup.back_to_main) reply_markup=inline_markup.back_to_main)
except Exception as e: except Exception as e:
logger.error(f"Ошибка при совершении сделки: {e}") logger.error(f"Ошибка при совершении сделки: {e}", exc_info=True)
await message.answer('Возникла ошибка при попытке открыть позицию.', reply_markup=inline_markup.back_to_main) await message.answer('Возникла ошибка при попытке открыть позицию.', reply_markup=inline_markup.back_to_main)
async def trading_cycle(tg_id, message): async def trading_cycle(tg_id, message, side, margin_mode, symbol, starting_quantity):
""" """
Цикл торговой логики с учётом таймера пользователя. Цикл торговой логики с учётом таймера пользователя.
""" """
@@ -376,13 +487,10 @@ async def trading_cycle(tg_id, message):
if timer_sec > 0: if timer_sec > 0:
await asyncio.sleep(timer_sec) await asyncio.sleep(timer_sec)
data_main_stgs = await rq.get_user_main_settings(tg_id) await open_position(tg_id, message, side=side, margin_mode=margin_mode, symbol=symbol,
side = 'Buy' if data_main_stgs.get('trading_mode', '') == 'Long' else 'Sell' quantity=starting_quantity)
margin_mode = data_main_stgs.get('margin_type', 'Isolated')
await open_position(tg_id, message, side=side, margin_mode=margin_mode)
except asyncio.CancelledError: except asyncio.CancelledError:
logger.info(f"Торговый цикл для пользователя {tg_id} был отменён.") 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, async def set_take_profit_stop_loss(tg_id: int, message, take_profit_price: float, stop_loss_price: float,
@@ -390,19 +498,8 @@ async def set_take_profit_stop_loss(tg_id: int, message, take_profit_price: floa
""" """
Устанавливает уровни Take Profit и Stop Loss для открытой позиции. Устанавливает уровни Take Profit и Stop Loss для открытой позиции.
""" """
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) 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')
starting_quantity = safe_float(data_main_stgs.get('starting_quantity'))
limit_price = None
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)
trading_mode = data_main_stgs.get('trading_mode') trading_mode = data_main_stgs.get('trading_mode')
side = None side = None
@@ -415,7 +512,7 @@ async def set_take_profit_stop_loss(tg_id: int, message, take_profit_price: floa
await message.answer("Не удалось определить сторону сделки.") await message.answer("Не удалось определить сторону сделки.")
return return
client = HTTP(api_key=api_key, api_secret=secret_key) client = await get_bybit_client(tg_id)
await cancel_all_tp_sl_orders(tg_id, symbol) await cancel_all_tp_sl_orders(tg_id, symbol)
try: try:
@@ -427,52 +524,22 @@ async def set_take_profit_stop_loss(tg_id: int, message, take_profit_price: floa
else: else:
raise raise
positions_resp = client.get_positions(category='linear', symbol=symbol) resp = client.set_trading_stop(
positions = positions_resp.get('result', {}).get('list', []) category='linear',
symbol=symbol,
takeProfit=str(round(take_profit_price, 5)),
stopLoss=str(round(stop_loss_price, 5)),
tpTriggerBy='LastPrice',
slTriggerBy='LastPrice',
positionIdx=0,
reduceOnly=False,
tpslMode=tpsl_mode
)
if not positions or abs(float(positions[0].get('size', 0))) == 0: if resp.get('retCode') != 0:
params = dict( await message.answer(f"Ошибка обновления TP/SL: {resp.get('retMsg')}",
category='linear', reply_markup=inline_markup.back_to_main)
symbol=symbol, return
side=side,
orderType=order_type,
qty=str(starting_quantity),
timeInForce='GTC',
orderLinkId=f"deal_{symbol}_{int(time.time())}",
takeProfit=str(take_profit_price),
stopLoss=str(stop_loss_price),
tpOrderType='Limit' if tpsl_mode == 'Partial' else 'Market',
slOrderType='Limit' if tpsl_mode == 'Partial' else 'Market',
tpslMode=tpsl_mode
)
if order_type == 'Limit' and limit_price is not None:
params['price'] = str(limit_price)
if tpsl_mode == 'Partial':
params['tpLimitPrice'] = str(take_profit_price)
params['slLimitPrice'] = str(stop_loss_price)
response = client.place_order(**params)
if response.get('retCode') != 0:
await message.answer(f"Ошибка создания ордера с TP/SL: {response.get('retMsg')}",
reply_markup=inline_markup.back_to_main)
return
else:
resp = client.set_trading_stop(
category='linear',
symbol=symbol,
takeProfit=str(round(take_profit_price, 5)),
stopLoss=str(round(stop_loss_price, 5)),
tpTriggerBy='LastPrice',
slTriggerBy='LastPrice',
reduceOnly=False
)
if resp.get('retCode') != 0:
await message.answer(f"Ошибка обновления TP/SL: {resp.get('retMsg')}",
reply_markup=inline_markup.back_to_main)
return
await message.answer( await message.answer(
f"ТП и СЛ успешно установлены:\nТейк-профит: {take_profit_price:.5f}\nСтоп-лосс: {stop_loss_price:.5f}", f"ТП и СЛ успешно установлены:\nТейк-профит: {take_profit_price:.5f}\nСтоп-лосс: {stop_loss_price:.5f}",
@@ -486,9 +553,7 @@ async def cancel_all_tp_sl_orders(tg_id, symbol):
""" """
Отменяет лимитные ордера для указанного символа. Отменяет лимитные ордера для указанного символа.
""" """
api_key = await rq.get_bybit_api_key(tg_id) client = await get_bybit_client(tg_id)
secret_key = await rq.get_bybit_secret_key(tg_id)
client = HTTP(api_key=api_key, api_secret=secret_key)
last_response = None last_response = None
try: try:
orders_resp = client.get_open_orders(category='linear', symbol=symbol) orders_resp = client.get_open_orders(category='linear', symbol=symbol)
@@ -512,12 +577,8 @@ async def get_active_positions(tg_id, message):
""" """
Показывает активные позиции пользователя. Показывает активные позиции пользователя.
""" """
api_key = await rq.get_bybit_api_key(tg_id) client = await get_bybit_client(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') active_positions = client.get_positions(category='linear', settleCoin='USDT')
positions = active_positions.get('result', {}).get('list', []) positions = active_positions.get('result', {}).get('list', [])
active_symbols = [pos.get('symbol') for pos in positions if float(pos.get('size', 0)) > 0] active_symbols = [pos.get('symbol') for pos in positions if float(pos.get('size', 0)) > 0]
@@ -533,12 +594,8 @@ async def get_active_positions_by_symbol(tg_id, symbol, message):
""" """
Показывает активные позиции пользователя по символу. Показывает активные позиции пользователя по символу.
""" """
api_key = await rq.get_bybit_api_key(tg_id) client = await get_bybit_client(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', symbol=symbol) active_positions = client.get_positions(category='linear', symbol=symbol)
positions = active_positions.get('result', {}).get('list', []) positions = active_positions.get('result', {}).get('list', [])
pos = positions[0] if positions else None pos = positions[0] if positions else None
@@ -558,14 +615,12 @@ async def get_active_positions_by_symbol(tg_id, symbol, message):
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): async def get_active_orders(tg_id, message):
""" """
Показывает активные лимитные ордера пользователя. Показывает активные лимитные ордера пользователя.
""" """
api_key = await rq.get_bybit_api_key(tg_id) client = await get_bybit_client(tg_id)
secret_key = await rq.get_bybit_secret_key(tg_id)
client = HTTP(api_key=api_key, api_secret=secret_key)
response = client.get_open_orders(category='linear', settleCoin='USDT', orderType='Limit') response = client.get_open_orders(category='linear', settleCoin='USDT', orderType='Limit')
orders = response.get('result', {}).get('list', []) orders = response.get('result', {}).get('list', [])
limit_orders = [order for order in orders if order.get('orderType') == 'Limit'] limit_orders = [order for order in orders if order.get('orderType') == 'Limit']
@@ -583,10 +638,7 @@ async def get_active_orders_by_symbol(tg_id, symbol, message):
""" """
Показывает активные лимитные ордера пользователя по символу. Показывает активные лимитные ордера пользователя по символу.
""" """
api_key = await rq.get_bybit_api_key(tg_id) client = await get_bybit_client(tg_id)
secret_key = await rq.get_bybit_secret_key(tg_id)
client = HTTP(api_key=api_key, api_secret=secret_key)
active_orders = client.get_open_orders(category='linear', symbol=symbol) active_orders = client.get_open_orders(category='linear', symbol=symbol)
limit_orders = [ limit_orders = [
order for order in active_orders.get('result', {}).get('list', []) order for order in active_orders.get('result', {}).get('list', [])
@@ -608,26 +660,19 @@ async def get_active_orders_by_symbol(tg_id, symbol, message):
f"Количество: {order.get('qty')}\n" f"Количество: {order.get('qty')}\n"
f"Тейк-профит: {order.get('takeProfit')}\n" f"Тейк-профит: {order.get('takeProfit')}\n"
f"Стоп-лосс: {order.get('stopLoss')}\n" f"Стоп-лосс: {order.get('stopLoss')}\n"
f"Кредитное плечо: {order.get('leverage')}\n"
) )
texts.append(text) 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, message): async def close_user_trade(tg_id: int, symbol: str):
""" """
Закрывает открытые позиции пользователя по символу рыночным ордером. Закрывает открытые позиции пользователя по символу рыночным ордером.
Возвращает True при успехе, False при ошибках. Возвращает True при успехе, False при ошибках.
""" """
try: try:
api_key = await rq.get_bybit_api_key(tg_id) client = await get_bybit_client(tg_id)
secret_key = await rq.get_bybit_secret_key(tg_id)
data_risk_stgs = await rq.get_user_risk_management_settings(tg_id)
include_fee = data_risk_stgs.get('commission_fee', 'Нет') == 'Да'
client = HTTP(api_key=api_key, api_secret=secret_key)
positions_resp = client.get_positions(category="linear", symbol=symbol) positions_resp = client.get_positions(category="linear", symbol=symbol)
if positions_resp.get('retCode') != 0: if positions_resp.get('retCode') != 0:
@@ -643,6 +688,7 @@ async def close_user_trade(tg_id: int, symbol: str, message):
return False return False
close_side = "Sell" if side == "Buy" else "Buy" close_side = "Sell" if side == "Buy" else "Buy"
place_resp = client.place_order( place_resp = client.place_order(
category="linear", category="linear",
symbol=symbol, symbol=symbol,
@@ -654,14 +700,11 @@ async def close_user_trade(tg_id: int, symbol: str, message):
) )
if place_resp.get('retCode') == 0: if place_resp.get('retCode') == 0:
await message.answer(f"Сделка {symbol} успешно закрыта.", reply_markup=inline_markup.back_to_main)
return True return True
else: else:
await message.answer(f"Ошибка закрытия сделки {symbol}.", reply_markup=inline_markup.back_to_main)
return False return False
except Exception as e: except Exception as e:
logger.error(f"Ошибка закрытия сделки {symbol} для пользователя {tg_id}: {e}", exc_info=True) logger.error(f"Ошибка закрытия сделки {symbol} для пользователя {tg_id}: {e}", exc_info=True)
await message.answer("Произошла ошибка при закрытии сделки.", reply_markup=inline_markup.back_to_main)
return False return False
@@ -671,12 +714,15 @@ async def close_trade_after_delay(tg_id: int, message, symbol: str, delay_sec: i
""" """
try: try:
await asyncio.sleep(delay_sec) await asyncio.sleep(delay_sec)
result = await close_user_trade(tg_id, symbol, message) result = await close_user_trade(tg_id, symbol)
if result: if result:
await message.answer(f"Сделка {symbol} успешно закрыта по таймеру.", await message.answer(f"Сделка {symbol} успешно закрыта по таймеру.",
reply_markup=inline_markup.back_to_main) reply_markup=inline_markup.back_to_main)
logger.info(f"Сделка {symbol} успешно закрыта по таймеру.")
else: else:
await message.answer(f"Не удалось закрыть сделку {symbol} по таймеру.", await message.answer(f"Не удалось закрыть сделку {symbol} по таймеру.",
reply_markup=inline_markup.back_to_main) reply_markup=inline_markup.back_to_main)
logger.error(f"Не удалось закрыть сделку {symbol} по таймеру.")
except asyncio.CancelledError: 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} по таймеру отменено.")

View File

@@ -9,6 +9,7 @@ logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("bybit_ws") logger = logging.getLogger("bybit_ws")
event_loop = None # Сюда нужно будет установить event loop из основного приложения event_loop = None # Сюда нужно будет установить event loop из основного приложения
active_ws_tasks = {}
def get_or_create_event_loop() -> asyncio.AbstractEventLoop: def get_or_create_event_loop() -> asyncio.AbstractEventLoop:
""" """
@@ -31,11 +32,14 @@ async def run_ws_for_user(tg_id, message) -> None:
""" """
Запускает WebSocket Bybit для пользователя с указанным tg_id. Запускает WebSocket Bybit для пользователя с указанным tg_id.
""" """
if tg_id not in active_ws_tasks or active_ws_tasks[tg_id].done():
api_key = await rq.get_bybit_api_key(tg_id) api_key = await rq.get_bybit_api_key(tg_id)
api_secret = await rq.get_bybit_secret_key(tg_id) api_secret = await rq.get_bybit_secret_key(tg_id)
# Запускаем WebSocket как асинхронную задачу
await start_execution_ws(api_key, api_secret, message) active_ws_tasks[tg_id] = asyncio.create_task(
start_execution_ws(api_key, api_secret, message)
)
logger.info(f"WebSocket для пользователя {tg_id} запущен.")
def on_order_callback(message, msg): def on_order_callback(message, msg):

View File

@@ -2,17 +2,17 @@
import logging.config import logging.config
from aiogram import F, Router from aiogram import F, Router
from app.telegram.functions.main_settings.settings import main_settings_message
from logger_helper.logger_helper import LOGGING_CONFIG from logger_helper.logger_helper import LOGGING_CONFIG
from app.services.Bybit.functions.Futures import (close_user_trade, set_take_profit_stop_loss, \ from app.services.Bybit.functions.Futures import (close_user_trade, set_take_profit_stop_loss, \
get_active_positions_by_symbol, get_active_orders_by_symbol, get_active_positions_by_symbol, get_active_orders_by_symbol,
get_active_positions, get_active_orders, cancel_all_tp_sl_orders, get_active_positions, get_active_orders, cancel_all_tp_sl_orders,
trading_cycle, open_position, close_trade_after_delay, trading_cycle, open_position, close_trade_after_delay, safe_float,
) )
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
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 from app.services.Bybit.functions.price_symbol import get_price
@@ -37,10 +37,10 @@ async def clb_start_bybit_trade_message(callback: CallbackQuery) -> None:
""" """
user_id = callback.from_user.id user_id = callback.from_user.id
balance = await get_balance(user_id, callback.message) balance = await get_balance(user_id, callback.message)
price = await get_price(user_id)
if balance: if balance:
symbol = await rq.get_symbol(user_id) symbol = await rq.get_symbol(user_id)
price = await get_price(user_id, symbol=symbol)
text = ( text = (
f"💎 Торговля на Bybit\n\n" f"💎 Торговля на Bybit\n\n"
@@ -61,10 +61,10 @@ async def start_bybit_trade_message(message: Message) -> None:
вместе с инструкциями по началу торговли. вместе с инструкциями по началу торговли.
""" """
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)
price = await get_price(message.from_user.id, symbol=symbol)
text = ( text = (
f"💎 Торговля на Bybit\n\n" f"💎 Торговля на Bybit\n\n"
@@ -124,7 +124,6 @@ async def update_entry_type_message(callback: CallbackQuery, state: FSMContext)
await callback.answer() await callback.answer()
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith('entry_order_type:')) @router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith('entry_order_type:'))
async def entry_order_type_callback(callback: CallbackQuery, state: FSMContext) -> None: async def entry_order_type_callback(callback: CallbackQuery, state: FSMContext) -> None:
""" """
@@ -192,43 +191,11 @@ async def start_trading_process(callback: CallbackQuery) -> None:
message = callback.message message = callback.message
data_main_stgs = await rq.get_user_main_settings(tg_id) data_main_stgs = await rq.get_user_main_settings(tg_id)
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) symbol = await rq.get_symbol(tg_id)
margin_mode = data_main_stgs.get('margin_type', 'Isolated') margin_mode = data_main_stgs.get('margin_type', 'Isolated')
trading_mode = data_main_stgs.get('trading_mode') trading_mode = data_main_stgs.get('trading_mode')
switch_mode = data_main_stgs.get('switch_mode_enabled') switch_mode = data_main_stgs.get('switch_mode_enabled')
starting_quantity = safe_float(data_main_stgs.get('starting_quantity'))
if not api_key or not secret_key:
await message.answer("❗️ У вас не настроены API ключи для Bybit.")
await callback.answer()
return
if trading_mode not in ['Long', 'Short', 'Smart']:
await message.answer(f"❗️ Некорректный торговый режим: {trading_mode}")
await callback.answer()
return
if margin_mode not in ['Isolated', 'Cross']:
margin_mode = 'Isolated'
client = HTTP(api_key=api_key, api_secret=secret_key)
try:
positions_resp = client.get_positions(category='linear', symbol=symbol)
positions = positions_resp.get('result', {}).get('list', [])
except Exception as e:
logger.error(f"Ошибка при получении позиций: {e}")
positions = []
for pos in positions:
size = pos.get('size')
existing_margin_mode = pos.get('margin_mode')
if size and float(size) > 0 and existing_margin_mode and existing_margin_mode != margin_mode:
await callback.answer(
f"⚠️ Маржинальный режим нельзя менять при открытой позиции "
f"(текущий режим: {existing_margin_mode})", show_alert=True)
return
side = None side = None
if switch_mode == 'Включено': if switch_mode == 'Включено':
@@ -247,7 +214,6 @@ async def start_trading_process(callback: CallbackQuery) -> None:
await message.answer("Начинаю торговлю с использованием текущих настроек...") await message.answer("Начинаю торговлю с использованием текущих настроек...")
timer_data = await rq.get_user_timer(tg_id) timer_data = await rq.get_user_timer(tg_id)
if isinstance(timer_data, dict): if isinstance(timer_data, dict):
timer_minute = timer_data.get('timer_minutes', 0) timer_minute = timer_data.get('timer_minutes', 0)
@@ -255,11 +221,12 @@ async def start_trading_process(callback: CallbackQuery) -> None:
timer_minute = timer_data or 0 timer_minute = timer_data or 0
if timer_minute > 0: if timer_minute > 0:
await trading_cycle(tg_id, message) await trading_cycle(tg_id, message, side=side, margin_mode=margin_mode, symbol=symbol,
starting_quantity=starting_quantity)
await message.answer(f"Торговля начнётся через {timer_minute} мин.") await message.answer(f"Торговля начнётся через {timer_minute} мин.")
await rq.update_user_timer(tg_id, minutes=0) await rq.update_user_timer(tg_id, minutes=0)
else: else:
await open_position(tg_id, message, side, margin_mode) await open_position(tg_id, message, side, margin_mode, symbol=symbol, quantity=starting_quantity)
await callback.answer() await callback.answer()
@@ -290,6 +257,7 @@ async def show_my_trades_callback(callback: CallbackQuery):
logger.error(f"Произошла ошибка при выборе сделки: {e}") logger.error(f"Произошла ошибка при выборе сделки: {e}")
await callback.message.answer("Произошла ошибка при выборе сделки", reply_markup=inline_markup.back_to_main) await callback.message.answer("Произошла ошибка при выборе сделки", reply_markup=inline_markup.back_to_main)
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("show_deal_")) @router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("show_deal_"))
async def show_deal_callback(callback_query: CallbackQuery) -> None: async def show_deal_callback(callback_query: CallbackQuery) -> None:
""" """
@@ -303,7 +271,8 @@ async def show_deal_callback(callback_query: CallbackQuery) -> None:
await get_active_positions_by_symbol(tg_id, symbol, message=callback_query.message) await get_active_positions_by_symbol(tg_id, symbol, message=callback_query.message)
except Exception as e: except Exception as e:
logger.error(f"Произошла ошибка при выборе сделки: {e}") logger.error(f"Произошла ошибка при выборе сделки: {e}")
await callback_query.message.answer("Произошла ошибка при выборе сделки", reply_markup=inline_markup.back_to_main) await callback_query.message.answer("Произошла ошибка при выборе сделки",
reply_markup=inline_markup.back_to_main)
@router_functions_bybit_trade.callback_query(F.data == "clb_open_orders") @router_functions_bybit_trade.callback_query(F.data == "clb_open_orders")
@@ -333,7 +302,8 @@ async def show_limit_callback(callback_query: CallbackQuery) -> None:
await get_active_orders_by_symbol(tg_id, symbol, message=callback_query.message) await get_active_orders_by_symbol(tg_id, symbol, message=callback_query.message)
except Exception as e: except Exception as e:
logger.error(f"Произошла ошибка при выборе сделки: {e}") logger.error(f"Произошла ошибка при выборе сделки: {e}")
await callback_query.message.answer("Произошла ошибка при выборе сделки", reply_markup=inline_markup.back_to_main) await callback_query.message.answer("Произошла ошибка при выборе сделки",
reply_markup=inline_markup.back_to_main)
@router_functions_bybit_trade.callback_query(F.data == "clb_set_tp_sl") @router_functions_bybit_trade.callback_query(F.data == "clb_set_tp_sl")
@@ -407,7 +377,7 @@ async def close_trade_callback(callback: CallbackQuery) -> None:
symbol = callback.data.split(':')[1] symbol = callback.data.split(':')[1]
tg_id = callback.from_user.id tg_id = callback.from_user.id
result = await close_user_trade(tg_id, symbol, message=callback.message) result = await close_user_trade(tg_id, symbol)
if result: if result:
logger.info(f"Сделка {symbol} успешно закрыта.") logger.info(f"Сделка {symbol} успешно закрыта.")
@@ -480,6 +450,7 @@ async def reset_martingale(callback: CallbackQuery) -> None:
tg_id = callback.from_user.id tg_id = callback.from_user.id
await rq.update_martingale_step(tg_id, 0) await rq.update_martingale_step(tg_id, 0)
await callback.answer("Сброс шагов выполнен.") await callback.answer("Сброс шагов выполнен.")
await main_settings_message(tg_id, callback.message)
@router_functions_bybit_trade.callback_query(F.data == "clb_stop_trading") @router_functions_bybit_trade.callback_query(F.data == "clb_stop_trading")
@@ -492,6 +463,7 @@ async def confirm_stop_trading(callback: CallbackQuery):
) )
await callback.answer() await callback.answer()
@router_functions_bybit_trade.callback_query(F.data == "stop_immediately") @router_functions_bybit_trade.callback_query(F.data == "stop_immediately")
async def stop_immediately(callback: CallbackQuery): async def stop_immediately(callback: CallbackQuery):
""" """
@@ -503,6 +475,7 @@ async def stop_immediately(callback: CallbackQuery):
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()
@router_functions_bybit_trade.callback_query(F.data == "stop_with_timer") @router_functions_bybit_trade.callback_query(F.data == "stop_with_timer")
async def stop_with_timer_start(callback: CallbackQuery, state: FSMContext): async def stop_with_timer_start(callback: CallbackQuery, state: FSMContext):
""" """
@@ -510,9 +483,11 @@ async def stop_with_timer_start(callback: CallbackQuery, state: FSMContext):
""" """
await state.set_state(CloseTradeTimerState.waiting_for_delay) await state.set_state(CloseTradeTimerState.waiting_for_delay)
await callback.message.answer("Введите задержку в минутах перед остановкой торговли:", reply_markup=inline_markup.cancel) await callback.message.answer("Введите задержку в минутах перед остановкой торговли:",
reply_markup=inline_markup.cancel)
await callback.answer() await callback.answer()
@router_functions_bybit_trade.message(CloseTradeTimerState.waiting_for_delay) @router_functions_bybit_trade.message(CloseTradeTimerState.waiting_for_delay)
async def process_stop_delay(message: Message, state: FSMContext): async def process_stop_delay(message: Message, state: FSMContext):
""" """

View File

@@ -28,7 +28,7 @@ async def get_min_qty(tg_id: int) -> float:
client = HTTP(api_key=api_key, api_secret=secret_key) client = HTTP(api_key=api_key, api_secret=secret_key)
price = await get_price(tg_id) price = await get_price(tg_id, symbol=symbol)
response = client.get_instruments_info(symbol=symbol, category='linear') response = client.get_instruments_info(symbol=symbol, category='linear')

View File

@@ -8,7 +8,7 @@ logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("price_symbol") logger = logging.getLogger("price_symbol")
async def get_price(tg_id: int) -> float: async def get_price(tg_id: int, symbol: str) -> float:
""" """
Асинхронно получает текущую цену символа пользователя на Bybit. Асинхронно получает текущую цену символа пользователя на Bybit.
@@ -17,7 +17,6 @@ async def get_price(tg_id: int) -> float:
""" """
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)
client = HTTP( client = HTTP(
api_key=api_key, api_key=api_key,

View File

@@ -55,3 +55,14 @@ class condition_settings(StatesGroup):
volume = State() volume = State()
integration = State() integration = State()
use_tv_signal = State() use_tv_signal = State()
class update_main_settings(StatesGroup):
"""FSM состояние для обновления основных настройок."""
trading_mode = State()
size_leverage = State()
margin_type = State()
martingale_factor = State()
starting_quantity = State()
maximal_quantity = State()
switch_mode_enabled = State()

View File

@@ -25,6 +25,7 @@ special_settings_markup = InlineKeyboardMarkup(inline_keyboard=[
InlineKeyboardButton(text="Дополнительные параметры", callback_data='clb_change_additional_settings')], InlineKeyboardButton(text="Дополнительные параметры", callback_data='clb_change_additional_settings')],
[InlineKeyboardButton(text="Подключить Bybit", callback_data='clb_new_user_connect_bybit_api_message')], [InlineKeyboardButton(text="Подключить Bybit", callback_data='clb_new_user_connect_bybit_api_message')],
back_btn_to_main
]) ])
connect_bybit_api_markup = InlineKeyboardMarkup(inline_keyboard=[ connect_bybit_api_markup = InlineKeyboardMarkup(inline_keyboard=[
@@ -93,7 +94,7 @@ risk_management_settings_markup = InlineKeyboardMarkup(inline_keyboard=[
]) ])
condition_settings_markup = InlineKeyboardMarkup(inline_keyboard=[ condition_settings_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='Триггер', callback_data='clb_change_trigger'), [InlineKeyboardButton(text='Режим торговли', callback_data='clb_change_mode'),
InlineKeyboardButton(text='Таймер', callback_data='clb_change_timer')], InlineKeyboardButton(text='Таймер', callback_data='clb_change_timer')],
[InlineKeyboardButton(text='Фильтр волатильности', callback_data='clb_change_filter_volatility'), [InlineKeyboardButton(text='Фильтр волатильности', callback_data='clb_change_filter_volatility'),

View File

@@ -1,6 +1,6 @@
from datetime import datetime from datetime import datetime
import logging.config import logging.config
from sqlalchemy.sql.sqltypes import DateTime from sqlalchemy.sql.sqltypes import DateTime, Numeric
from sqlalchemy import BigInteger, Boolean, Integer, String, ForeignKey from sqlalchemy import BigInteger, Boolean, Integer, String, ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
@@ -120,17 +120,16 @@ class Margin_type(Base):
class Trigger(Base): class Trigger(Base):
""" """
Справочник видов триггеров для сделок. Справочник триггеров для сделок.
Атрибуты: Атрибуты:
id (int): Первичный ключ. id (int): Первичный ключ..
trigger (str): Название триггера (например, 'Ручной', 'Автоматический').
""" """
__tablename__ = 'triggers' __tablename__ = 'triggers'
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
trigger = mapped_column(String(15), unique=True) trigger_price = mapped_column(Integer(), default=0)
class User_Main_Settings(Base): class User_Main_Settings(Base):
@@ -163,10 +162,10 @@ class User_Main_Settings(Base):
size_leverage = mapped_column(Integer(), default=1) size_leverage = mapped_column(Integer(), default=1)
starting_quantity = mapped_column(Integer(), default=1) starting_quantity = mapped_column(Integer(), default=1)
martingale_factor = mapped_column(Integer(), default=1) martingale_factor = mapped_column(Integer(), default=1)
martingale_step = mapped_column(Integer(), default=1) martingale_step = mapped_column(Integer(), default=0)
maximal_quantity = mapped_column(Integer(), default=10) maximal_quantity = mapped_column(Integer(), default=10)
entry_order_type = mapped_column(String(10), default='Market') entry_order_type = mapped_column(String(10), default='Market')
limit_order_price = mapped_column(String(20), nullable=True) limit_order_price = mapped_column(Numeric(18, 15), nullable=True)
class User_Risk_Management_Settings(Base): class User_Risk_Management_Settings(Base):
@@ -297,7 +296,7 @@ async def async_main():
await conn.run_sync(Base.metadata.create_all) await conn.run_sync(Base.metadata.create_all)
# Заполнение таблиц # Заполнение таблиц
modes = ['Long', 'Short', 'Switch', 'Smart'] modes = ['Long', 'Short', 'Smart']
for mode in modes: for mode in modes:
result = await conn.execute(select(Trading_Mode).where(Trading_Mode.mode == mode)) result = await conn.execute(select(Trading_Mode).where(Trading_Mode.mode == mode))
if not result.first(): if not result.first():
@@ -310,10 +309,3 @@ async def async_main():
if not result.first(): if not result.first():
logger.info("Заполение таблицы типов маржи") logger.info("Заполение таблицы типов маржи")
await conn.execute(Margin_type.__table__.insert().values(type=type)) await conn.execute(Margin_type.__table__.insert().values(type=type))
triggers = ['Ручной', 'Автоматический']
for trigger in triggers:
result = await conn.execute(select(Trigger).where(Trigger.trigger == trigger))
if not result.first():
logger.info("Заполение таблицы триггеров")
await conn.execute(Trigger.__table__.insert().values(trigger=trigger))

View File

@@ -1,4 +1,5 @@
import logging.config import logging.config
from logger_helper.logger_helper import LOGGING_CONFIG from logger_helper.logger_helper import LOGGING_CONFIG
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any from typing import Any

View File

@@ -7,7 +7,7 @@ async def reg_new_user_default_additional_settings(id, message):
await rq.set_new_user_default_additional_settings(tg_id) await rq.set_new_user_default_additional_settings(tg_id)
async def main_settings_message(id, message, state): async def main_settings_message(id, message):
text = '''<b>Дополнительные параметры</b> text = '''<b>Дополнительные параметры</b>
<b>- Сохранить как шаблон стратегии:</b> да / нет <b>- Сохранить как шаблон стратегии:</b> да / нет

View File

@@ -28,7 +28,7 @@ async def main_settings_message(id, message):
trigger = await rq.get_for_registration_trigger(tg_id) trigger = await rq.get_for_registration_trigger(tg_id)
text = f""" <b>Условия запуска</b> text = f""" <b>Условия запуска</b>
<b>- Триггер:</b> {trigger} <b>- Режим торговли:</b> {trigger}
<b>- Таймер: </b> установить таймер / остановить таймер <b>- Таймер: </b> установить таймер / остановить таймер
<b>- Фильтр волатильности / объёма: </b> включить/отключить <b>- Фильтр волатильности / объёма: </b> включить/отключить
<b>- Интеграции и внешние сигналы: </b> <b>- Интеграции и внешние сигналы: </b>
@@ -53,7 +53,6 @@ async def trigger_message(id, message, state: FSMContext):
async def trigger_manual_callback(callback: CallbackQuery, state: FSMContext): async def trigger_manual_callback(callback: CallbackQuery, state: FSMContext):
await state.set_state(condition_settings.trigger) await state.set_state(condition_settings.trigger)
await rq.update_trigger(tg_id=callback.from_user.id, trigger="Ручной") await rq.update_trigger(tg_id=callback.from_user.id, trigger="Ручной")
await callback.message.answer("Триггер установлен в ручной режим.")
await main_settings_message(callback.from_user.id, callback.message) await main_settings_message(callback.from_user.id, callback.message)
await callback.answer() await callback.answer()
@@ -62,7 +61,6 @@ async def trigger_manual_callback(callback: CallbackQuery, state: FSMContext):
async def trigger_manual_callback(callback: CallbackQuery, state: FSMContext): async def trigger_manual_callback(callback: CallbackQuery, state: FSMContext):
await state.set_state(condition_settings.trigger) await state.set_state(condition_settings.trigger)
await rq.update_trigger(tg_id=callback.from_user.id, trigger="Автоматический") await rq.update_trigger(tg_id=callback.from_user.id, trigger="Автоматический")
await callback.message.answer("Триггер установлен в автоматический режим.")
await main_settings_message(callback.from_user.id, callback.message) await main_settings_message(callback.from_user.id, callback.message)
await callback.answer() await callback.answer()

View File

@@ -10,7 +10,7 @@ async def start_message(message):
username = message.from_user.first_name username = message.from_user.first_name
else: else:
username = f'{message.from_user.first_name} {message.from_user.last_name}' username = f'{message.from_user.first_name} {message.from_user.last_name}'
await message.answer(f""" Привет <b>{username}</b>! 👋""", parse_mode='html') await message.answer(f""" Привет <b>{username}</b>! 👋""", parse_mode='html', reply_markup=reply_markup.base_buttons_markup)
await message.answer("Добро пожаловать в чат-робот для автоматизации трейдинга — вашего надежного помощника для анализа рынка и принятия взвешенных решений.", await message.answer("Добро пожаловать в чат-робот для автоматизации трейдинга — вашего надежного помощника для анализа рынка и принятия взвешенных решений.",
parse_mode='html', reply_markup=inline_markup.start_markup) parse_mode='html', reply_markup=inline_markup.start_markup)

View File

@@ -1,24 +1,18 @@
from aiogram import Router, F from aiogram import Router
import logging.config
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
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.states.States import update_main_settings
from logger_helper.logger_helper import LOGGING_CONFIG
# FSM - Механизм состояния logging.config.dictConfig(LOGGING_CONFIG)
from aiogram.fsm.state import State, StatesGroup logger = logging.getLogger("main_settings")
router_main_settings = Router() router_main_settings = Router()
class update_main_settings(StatesGroup):
trading_mode = State()
size_leverage = State()
margin_type = State()
martingale_factor = State()
starting_quantity = State()
maximal_quantity = State()
switch_mode_enabled = State()
async def reg_new_user_default_main_settings(id, message): async def reg_new_user_default_main_settings(id, message):
tg_id = id tg_id = id
@@ -27,12 +21,12 @@ async def reg_new_user_default_main_settings(id, message):
margin_type = await rq.get_for_registration_margin_type() margin_type = await rq.get_for_registration_margin_type()
await rq.set_new_user_default_main_settings(tg_id, trading_mode, margin_type) await rq.set_new_user_default_main_settings(tg_id, trading_mode, margin_type)
async def main_settings_message(id, message, state):
data = await rq.get_user_main_settings(id)
await message.answer(f"""<b>Основные настройки</b> async def main_settings_message(id, message):
data = await rq.get_user_main_settings(id)
await message.answer(f"""<b>Основные настройки</b>
<b>- Режим торговли:</b> {data['trading_mode']} <b>- Режим торговли:</b> {data['trading_mode']}
<b>- Режим свитч:</b> {data['switch_mode_enabled']} <b>- Режим свитч:</b> {data['switch_mode_enabled']}
@@ -45,6 +39,7 @@ async def main_settings_message(id, message, state):
<b>- Максимальное количество ставок в серии:</b> {data['maximal_quantity']} <b>- Максимальное количество ставок в серии:</b> {data['maximal_quantity']}
""", parse_mode='html', reply_markup=inline_markup.main_settings_markup) """, parse_mode='html', reply_markup=inline_markup.main_settings_markup)
async def trading_mode_message(message, state): async def trading_mode_message(message, state):
await state.set_state(update_main_settings.trading_mode) await state.set_state(update_main_settings.trading_mode)
@@ -59,36 +54,37 @@ async def trading_mode_message(message, state):
<em>Выберите ниже для изменений:</em> <em>Выберите ниже для изменений:</em>
""", parse_mode='html', reply_markup=inline_markup.trading_mode_markup) """, parse_mode='html', reply_markup=inline_markup.trading_mode_markup)
@router_main_settings.callback_query(update_main_settings.trading_mode) @router_main_settings.callback_query(update_main_settings.trading_mode)
async def state_trading_mode(callback: CallbackQuery, state): async def state_trading_mode(callback: CallbackQuery, state):
await callback.answer() await callback.answer()
id = callback.from_user.id id = callback.from_user.id
data_settings = await rq.get_user_main_settings(id) data_settings = await rq.get_user_main_settings(id)
try: try:
match callback.data: match callback.data:
case 'trade_mode_long': case 'trade_mode_long':
await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Long") await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Long")
await rq.update_trade_mode_user(id, 'Long') await rq.update_trade_mode_user(id, 'Long')
await main_settings_message(id, callback.message, state) await main_settings_message(id, callback.message)
await state.clear() await state.clear()
case 'trade_mode_short': case 'trade_mode_short':
await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Short") await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Short")
await rq.update_trade_mode_user(id, 'Short') await rq.update_trade_mode_user(id, 'Short')
await main_settings_message(id, callback.message, state) await main_settings_message(id, callback.message)
await state.clear() await state.clear()
case 'trade_mode_smart': case 'trade_mode_smart':
await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Smart") await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Smart")
await rq.update_trade_mode_user(id, 'Smart') await rq.update_trade_mode_user(id, 'Smart')
await main_settings_message(id, callback.message, state) await main_settings_message(id, callback.message)
await state.clear() await state.clear()
except Exception as e: except Exception as e:
print(f"error: {e}") logger.error(e)
async def switch_mode_enabled_message(message, state): async def switch_mode_enabled_message(message, state):
@@ -97,9 +93,8 @@ async def switch_mode_enabled_message(message, state):
await message.edit_text( await message.edit_text(
"""<b>Свитч</b> — динамическое переключение между торговыми режимами для максимизации эффективности. """<b>Свитч</b> — динамическое переключение между торговыми режимами для максимизации эффективности.
<em>Выберите ниже для изменений:</em>""", parse_mode='html', reply_markup=inline_markup.buttons_on_off_markup_for_switch) <em>Выберите ниже для изменений:</em>""", parse_mode='html',
reply_markup=inline_markup.buttons_on_off_markup_for_switch)
@router_main_settings.callback_query(lambda c: c.data in ["clb_on_switch", "clb_off_switch"]) @router_main_settings.callback_query(lambda c: c.data in ["clb_on_switch", "clb_off_switch"])
@@ -109,12 +104,12 @@ async def state_switch_mode_enabled(callback: CallbackQuery, state):
val = "Включить" if callback.data == "clb_on_switch" else "Выключить" val = "Включить" if callback.data == "clb_on_switch" else "Выключить"
if val == "Включить": if val == "Включить":
await rq.update_switch_mode_enabled(tg_id, "Включено") await rq.update_switch_mode_enabled(tg_id, "Включено")
await callback.message.answer(f"Включено") await callback.answer(f"Включено")
await main_settings_message(tg_id, callback.message, state) await main_settings_message(tg_id, callback.message)
else: else:
await rq.update_switch_mode_enabled(tg_id, "Выключено") await rq.update_switch_mode_enabled(tg_id, "Выключено")
await callback.message.answer(f"Выключено") await callback.answer(f"Выключено")
await main_settings_message(tg_id, callback.message, state) await main_settings_message(tg_id, callback.message)
await state.clear() await state.clear()
@@ -132,24 +127,24 @@ async def state_switch_mode_enabled(callback: CallbackQuery, state):
if val == "Long": if val == "Long":
await rq.update_switch_state(tg_id, "Long") await rq.update_switch_state(tg_id, "Long")
await callback.message.answer(f"Состояние свитча: {val}") await callback.message.answer(f"Состояние свитча: {val}")
await main_settings_message(tg_id, callback.message, state) await main_settings_message(tg_id, callback.message)
else: else:
await rq.update_switch_state(tg_id, "Short") await rq.update_switch_state(tg_id, "Short")
await callback.message.answer(f"Состояние свитча: {val}") await callback.message.answer(f"Состояние свитча: {val}")
await main_settings_message(tg_id, callback.message, state) await main_settings_message(tg_id, callback.message)
await state.clear() await state.clear()
async def size_leverage_message(message, state):
async def size_leverage_message (message, state):
await state.set_state(update_main_settings.size_leverage) await state.set_state(update_main_settings.size_leverage)
await message.edit_text("Введите размер <b>кредитного плеча</b> (от 1 до 100): ", parse_mode='html', reply_markup=inline_markup.back_btn_list_settings_markup) await message.edit_text("Введите размер <b>кредитного плеча</b> (от 1 до 100): ", parse_mode='html',
reply_markup=inline_markup.back_btn_list_settings_markup)
@router_main_settings.message(update_main_settings.size_leverage) @router_main_settings.message(update_main_settings.size_leverage)
async def state_size_leverage(message: Message, state): async def state_size_leverage(message: Message, state):
await state.update_data(size_leverage = message.text) await state.update_data(size_leverage=message.text)
data = await state.get_data() data = await state.get_data()
data_settings = await rq.get_user_main_settings(message.from_user.id) data_settings = await rq.get_user_main_settings(message.from_user.id)
@@ -158,22 +153,26 @@ async def state_size_leverage(message: Message, state):
await message.answer(f"✅ Изменено: {data_settings['size_leverage']}{data['size_leverage']}") await message.answer(f"✅ Изменено: {data_settings['size_leverage']}{data['size_leverage']}")
await rq.update_size_leverange(message.from_user.id, data['size_leverage']) await rq.update_size_leverange(message.from_user.id, data['size_leverage'])
await main_settings_message(message.from_user.id, message, state) await main_settings_message(message.from_user.id, message)
await state.clear() await state.clear()
else: else:
await message.answer(f'⛔️ Ошибка: ваше значение ({data['size_leverage']}) или выше лимита (100) или вы вводите неверные символы') await message.answer(
f'⛔️ Ошибка: ваше значение ({data['size_leverage']}) или выше лимита (100) или вы вводите неверные символы')
await main_settings_message(message.from_user.id, message)
await main_settings_message(message.from_user.id, message, state)
async def martingale_factor_message(message, state): async def martingale_factor_message(message, state):
await state.set_state(update_main_settings.martingale_factor) await state.set_state(update_main_settings.martingale_factor)
await message.edit_text("Введите <b>коэффициент Мартингейла:</b>", parse_mode='html', reply_markup=inline_markup.back_btn_list_settings_markup) await message.edit_text("Введите <b>коэффициент Мартингейла:</b>", parse_mode='html',
reply_markup=inline_markup.back_btn_list_settings_markup)
@router_main_settings.message(update_main_settings.martingale_factor) @router_main_settings.message(update_main_settings.martingale_factor)
async def state_martingale_factor(message: Message, state): async def state_martingale_factor(message: Message, state):
await state.update_data(martingale_factor = message.text) await state.update_data(martingale_factor=message.text)
data = await state.get_data() data = await state.get_data()
data_settings = await rq.get_user_main_settings(message.from_user.id) data_settings = await rq.get_user_main_settings(message.from_user.id)
@@ -182,14 +181,16 @@ async def state_martingale_factor(message: Message, state):
await message.answer(f"✅ Изменено: {data_settings['martingale_factor']}{data['martingale_factor']}") await message.answer(f"✅ Изменено: {data_settings['martingale_factor']}{data['martingale_factor']}")
await rq.update_martingale_factor(message.from_user.id, data['martingale_factor']) await rq.update_martingale_factor(message.from_user.id, data['martingale_factor'])
await main_settings_message(message.from_user.id, message, state) await main_settings_message(message.from_user.id, message)
await state.clear() await state.clear()
else: else:
await message.answer(f'⛔️ Ошибка: ваше значение ({data['martingale_factor']}) или выше лимита (100) или вы вводите неверные символы') await message.answer(
f'⛔️ Ошибка: ваше значение ({data['martingale_factor']}) или выше лимита (100) или вы вводите неверные символы')
await main_settings_message(message.from_user.id, message)
await main_settings_message(message.from_user.id, message, state)
async def margin_type_message(message, state): async def margin_type_message(message, state):
await state.set_state(update_main_settings.margin_type) await state.set_state(update_main_settings.margin_type)
@@ -209,40 +210,60 @@ async def margin_type_message(message, state):
<em>Выберите ниже для изменений:</em> <em>Выберите ниже для изменений:</em>
""", parse_mode='html', reply_markup=inline_markup.margin_type_markup) """, parse_mode='html', reply_markup=inline_markup.margin_type_markup)
@router_main_settings.callback_query(update_main_settings.margin_type) @router_main_settings.callback_query(update_main_settings.margin_type)
async def state_margin_type(callback: CallbackQuery, state): async def state_margin_type(callback: CallbackQuery, state):
await callback.answer() 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)
data_settings = await rq.get_user_main_settings(tg_id)
client = HTTP(api_key=api_key, api_secret=secret_key)
try:
active_positions = client.get_positions(category='linear', settleCoin='USDT')
id = callback.from_user.id positions = active_positions.get('result', {}).get('list', [])
data_settings = await rq.get_user_main_settings(id) except Exception as e:
logger.error(f"error: {e}")
positions = []
try: for pos in positions:
match callback.data: size = pos.get('size')
case 'margin_type_isolated': if float(size) > 0:
await callback.answer(
"⚠️ Маржинальный режим нельзя менять при открытой позиции",
show_alert=True
)
return
try:
match callback.data:
case 'margin_type_isolated':
await callback.message.answer(f"✅ Изменено: {data_settings['margin_type']} → Isolated") await callback.message.answer(f"✅ Изменено: {data_settings['margin_type']} → Isolated")
await rq.update_margin_type(id, 'Isolated') await rq.update_margin_type(tg_id, 'Isolated')
await main_settings_message(id, callback.message, state) await main_settings_message(tg_id, callback.message)
await state.clear() await state.clear()
case 'margin_type_cross': case 'margin_type_cross':
await callback.message.answer(f"✅ Изменено: {data_settings['margin_type']} → Cross") await callback.message.answer(f"✅ Изменено: {data_settings['margin_type']} → Cross")
await rq.update_margin_type(id, 'Cross') await rq.update_margin_type(tg_id, 'Cross')
await main_settings_message(id, callback.message, state) await main_settings_message(tg_id, callback.message)
await state.clear() await state.clear()
except Exception as e: except Exception as e:
print(f"error: {e}") logger.error(f"error: {e}")
async def starting_quantity_message (message, state):
async def starting_quantity_message(message, state):
await state.set_state(update_main_settings.starting_quantity) await state.set_state(update_main_settings.starting_quantity)
await message.edit_text("Введите <b>начальную ставку:</b>", parse_mode='html', reply_markup=inline_markup.back_btn_list_settings_markup) await message.edit_text("Введите <b>начальную ставку:</b>", parse_mode='html',
reply_markup=inline_markup.back_btn_list_settings_markup)
@router_main_settings.message(update_main_settings.starting_quantity) @router_main_settings.message(update_main_settings.starting_quantity)
async def state_starting_quantity(message: Message, state): async def state_starting_quantity(message: Message, state):
await state.update_data(starting_quantity = message.text) await state.update_data(starting_quantity=message.text)
data = await state.get_data() data = await state.get_data()
data_settings = await rq.get_user_main_settings(message.from_user.id) data_settings = await rq.get_user_main_settings(message.from_user.id)
@@ -251,22 +272,25 @@ async def state_starting_quantity(message: Message, state):
await message.answer(f"✅ Изменено: {data_settings['starting_quantity']}{data['starting_quantity']}") await message.answer(f"✅ Изменено: {data_settings['starting_quantity']}{data['starting_quantity']}")
await rq.update_starting_quantity(message.from_user.id, data['starting_quantity']) await rq.update_starting_quantity(message.from_user.id, data['starting_quantity'])
await main_settings_message(message.from_user.id, message, state) await main_settings_message(message.from_user.id, message)
await state.clear() await state.clear()
else: else:
await message.answer(f'⛔️ Ошибка: вы вводите неверные символы') await message.answer(f'⛔️ Ошибка: вы вводите неверные символы')
await main_settings_message(message.from_user.id, message, state) await main_settings_message(message.from_user.id, message)
async def maximum_quantity_message(message, state): async def maximum_quantity_message(message, state):
await state.set_state(update_main_settings.maximal_quantity) await state.set_state(update_main_settings.maximal_quantity)
await message.edit_text("Введите <b>максимальное количество серии ставок:</b>", parse_mode='html', reply_markup=inline_markup.back_btn_list_settings_markup) await message.edit_text("Введите <b>максимальное количество серии ставок:</b>", parse_mode='html',
reply_markup=inline_markup.back_btn_list_settings_markup)
@router_main_settings.message(update_main_settings.maximal_quantity) @router_main_settings.message(update_main_settings.maximal_quantity)
async def state_maximal_quantity(message: Message, state): async def state_maximal_quantity(message: Message, state):
await state.update_data(maximal_quantity = message.text) await state.update_data(maximal_quantity=message.text)
data = await state.get_data() data = await state.get_data()
data_settings = await rq.get_user_main_settings(message.from_user.id) data_settings = await rq.get_user_main_settings(message.from_user.id)
@@ -275,10 +299,11 @@ async def state_maximal_quantity(message: Message, state):
await message.answer(f"✅ Изменено: {data_settings['maximal_quantity']}{data['maximal_quantity']}") await message.answer(f"✅ Изменено: {data_settings['maximal_quantity']}{data['maximal_quantity']}")
await rq.update_maximal_quantity(message.from_user.id, data['maximal_quantity']) await rq.update_maximal_quantity(message.from_user.id, data['maximal_quantity'])
await main_settings_message(message.from_user.id, message, state) await main_settings_message(message.from_user.id, message)
await state.clear() await state.clear()
else: else:
await message.answer(f'⛔️ Ошибка: ваше значение ({data['maximal_quantity']}) или выше лимита (100) или вы вводите неверные символы') await message.answer(
f'⛔️ Ошибка: ваше значение ({data['maximal_quantity']}) или выше лимита (100) или вы вводите неверные символы')
await main_settings_message(message.from_user.id, message, state)
await main_settings_message(message.from_user.id, message)

View File

@@ -1,11 +1,16 @@
from aiogram import Router from aiogram import Router
import app.telegram.Keyboards.inline_keyboards as inline_markup import app.telegram.Keyboards.inline_keyboards as inline_markup
import logging.config
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.states.States import update_risk_management_settings from app.states.States import update_risk_management_settings
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("risk_management_settings")
router_risk_management_settings = Router() router_risk_management_settings = Router()
@@ -80,7 +85,8 @@ async def state_price_loss(message: Message, state):
# Пробуем перевести price_profit в число, если это возможно # Пробуем перевести price_profit в число, если это возможно
try: try:
current_price_profit_num = int(current_price_profit) current_price_profit_num = int(current_price_profit)
except Exception: except Exception as e:
logger.error(e)
current_price_profit_num = 0 current_price_profit_num = 0
# Флаг, если price_profit изначально равен 0 или совпадает со старым стоп-лоссом # Флаг, если price_profit изначально равен 0 или совпадает со старым стоп-лоссом

View File

@@ -1,5 +1,5 @@
import logging.config import logging.config
import asyncio
from aiogram import F, Router from aiogram import F, Router
from aiogram.filters import CommandStart from aiogram.filters import CommandStart
from aiogram.types import Message, CallbackQuery from aiogram.types import Message, CallbackQuery
@@ -50,8 +50,7 @@ async def profile_message(message: Message) -> None:
tg_id = message.from_user.id tg_id = message.from_user.id
balance = await get_balance(message.from_user.id, message) balance = await get_balance(message.from_user.id, message)
if user and balance: if user and balance:
asyncio.create_task(run_ws_for_user(tg_id, message)) await run_ws_for_user(tg_id, message)
logger.info(f"Получение event loop")
await func.profile_message(message.from_user.username, message) await func.profile_message(message.from_user.username, message)
@@ -112,15 +111,14 @@ async def clb_back_to_settings_msg(callback: CallbackQuery) -> None:
@router.callback_query(F.data == "clb_change_main_settings") @router.callback_query(F.data == "clb_change_main_settings")
async def clb_change_main_settings_message(callback: CallbackQuery, state: FSMContext) -> None: async def clb_change_main_settings_message(callback: CallbackQuery) -> None:
""" """
Открыть меню изменения главных настроек. Открыть меню изменения главных настроек.
Args: Args:
callback (CallbackQuery): полученный колбэк. callback (CallbackQuery): полученный колбэк.
state (FSMContext): текущее состояние FSM.
""" """
await func_main_settings.main_settings_message(callback.from_user.id, callback.message, state) await func_main_settings.main_settings_message(callback.from_user.id, callback.message)
await callback.answer() await callback.answer()
@@ -139,13 +137,12 @@ async def clb_change_risk_management_message(callback: CallbackQuery) -> None:
@router.callback_query(F.data == "clb_change_condition_settings") @router.callback_query(F.data == "clb_change_condition_settings")
async def clb_change_condition_message(callback: CallbackQuery, state: FSMContext) -> None: async def clb_change_condition_message(callback: CallbackQuery) -> None:
""" """
Открыть меню изменения настроек условий. Открыть меню изменения настроек условий.
Args: Args:
callback (CallbackQuery): полученный колбэк. callback (CallbackQuery): полученный колбэк.
state (FSMContext): текущее состояние FSM.
""" """
await func_condition_settings.main_settings_message(callback.from_user.id, callback.message) await func_condition_settings.main_settings_message(callback.from_user.id, callback.message)
@@ -153,15 +150,14 @@ async def clb_change_condition_message(callback: CallbackQuery, state: FSMContex
@router.callback_query(F.data == "clb_change_additional_settings") @router.callback_query(F.data == "clb_change_additional_settings")
async def clb_change_additional_message(callback: CallbackQuery, state: FSMContext) -> None: async def clb_change_additional_message(callback: CallbackQuery) -> None:
""" """
Открыть меню изменения дополнительных настроек. Открыть меню изменения дополнительных настроек.
Args: Args:
callback (CallbackQuery): полученный колбэк. callback (CallbackQuery): полученный колбэк.
state (FSMContext): текущее состояние FSM.
""" """
await func_additional_settings.main_settings_message(callback.from_user.id, callback.message, state) await func_additional_settings.main_settings_message(callback.from_user.id, callback.message)
await callback.answer() await callback.answer()
@@ -240,7 +236,7 @@ async def clb_risk_management_settings_msg(callback: CallbackQuery, state: FSMCo
logger.error(f"Error callback in risk_management match-case: {e}") logger.error(f"Error callback in risk_management match-case: {e}")
list_condition_settings = ['clb_change_trigger', list_condition_settings = ['clb_change_mode',
'clb_change_timer', 'clb_change_timer',
'clb_change_filter_volatility', 'clb_change_filter_volatility',
'clb_change_external_cues', 'clb_change_external_cues',
@@ -263,7 +259,7 @@ async def clb_condition_settings_msg(callback: CallbackQuery, state: FSMContext)
try: try:
match callback.data: match callback.data:
case 'clb_change_trigger': case 'clb_change_mode':
await func_condition_settings.trigger_message(callback.from_user.id, callback.message, state) await func_condition_settings.trigger_message(callback.from_user.id, callback.message, state)
case 'clb_change_timer': case 'clb_change_timer':
await func_condition_settings.timer_message(callback.from_user.id, callback.message, state) await func_condition_settings.timer_message(callback.from_user.id, callback.message, state)

View File

@@ -85,6 +85,16 @@ LOGGING_CONFIG = {
"level": "DEBUG", "level": "DEBUG",
"propagate": False, "propagate": False,
}, },
"main_settings": {
"handlers": ["console", "timed_rotating_file"],
"level": "DEBUG",
"propagate": False,
},
"risk_management_settings": {
"handlers": ["console", "timed_rotating_file"],
"level": "DEBUG",
"propagate": False,
},
"models": { "models": {
"handlers": ["console", "timed_rotating_file"], "handlers": ["console", "timed_rotating_file"],
"level": "DEBUG", "level": "DEBUG",