1
0
forked from kodorvan/stcs

Compare commits

...

14 Commits

Author SHA1 Message Date
algizn97
058ba09c03 Fixed 2025-09-03 21:33:08 +05:00
algizn97
dd53e5a14a Fixed 2025-09-03 21:28:12 +05:00
algizn97
3bd6b7363c Fixed 2025-08-31 11:47:13 +05:00
algizn97
2ee8c9916f Fixed 2025-08-30 16:29:56 +05:00
algizn97
3462078a47 Fixed 2025-08-29 11:44:24 +05:00
algizn97
8715b32139 Fixed 2025-08-29 11:43:52 +05:00
algizn97
4245e165bf Updated 2025-08-29 11:43:11 +05:00
algizn97
f4ff128236 Added trigger function 2025-08-29 11:42:55 +05:00
algizn97
f09fe1d70b Added new request 2025-08-29 11:42:03 +05:00
algizn97
4f774160b3 Updated States.py 2025-08-29 11:41:37 +05:00
algizn97
f6130c0b8c Deleted tasks.py 2025-08-29 11:41:18 +05:00
algizn97
e05b214a8a Added the ability to get a list of open trades and limit orders, as well as their closures (previously it was possible only for the selected pair) 2025-08-28 11:25:48 +05:00
algizn97
704249d0af Fixed and updated tasks 2025-08-27 14:28:53 +05:00
algizn97
bf44b481e9 Added new handlers for tasks 2025-08-27 14:28:23 +05:00
19 changed files with 940 additions and 741 deletions

View File

@@ -1,8 +1,8 @@
import asyncio import asyncio
import logging.config import logging.config
from aiogram import Bot, Dispatcher from aiogram import Bot, Dispatcher
import app.services.Bybit.functions.bybit_ws as bybit_ws
import app.telegram.database.requests as rq from app.services.Bybit.functions.bybit_ws import get_or_create_event_loop, set_event_loop
from app.telegram.database.models import async_main from app.telegram.database.models import async_main
from app.telegram.handlers.handlers import router from app.telegram.handlers.handlers import router
from app.telegram.functions.main_settings.settings import router_main_settings from app.telegram.functions.main_settings.settings import router_main_settings
@@ -21,37 +21,12 @@ bot = Bot(token=TOKEN_TG_BOT_1)
dp = Dispatcher() dp = Dispatcher()
def get_or_create_event_loop() -> asyncio.AbstractEventLoop:
"""
Возвращает текущий активный цикл событий asyncio или создает новый, если его нет.
"""
try:
return asyncio.get_running_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
return loop
loop = get_or_create_event_loop()
bybit_ws.set_event_loop(loop)
async def run_ws_for_user(tg_id, message) -> None:
"""
Запускает WebSocket Bybit для пользователя с указанным tg_id.
"""
api_key = await rq.get_bybit_api_key(tg_id)
api_secret = await rq.get_bybit_secret_key(tg_id)
await bybit_ws.start_execution_ws(api_key, api_secret, message)
async def main() -> None: async def main() -> None:
""" """
Основная асинхронная функция запуска бота: Основная асинхронная функция запуска бота:
""" """
loop = get_or_create_event_loop()
set_event_loop(loop)
await async_main() await async_main()

View File

@@ -14,6 +14,20 @@ 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")
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:
""" """
@@ -35,19 +49,16 @@ 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
else:
pnl -= commission pnl -= commission
movement = '' movement = ''
@@ -70,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"
@@ -80,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:
@@ -87,63 +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)}")
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)
if trade_info:
await message.answer(f"{trade_info}", reply_markup=inline_markup.back_to_main) await message.answer(f"{trade_info}", reply_markup=inline_markup.back_to_main)
liquidation_threshold = -100 side = None
if switch_mode == 'Включено':
switch_state = data_main_stgs.get('switch_state', 'Long')
side = 'Buy' if switch_state == 'Long' else 'Sell'
else:
if trading_mode == 'Long':
side = 'Buy'
elif trading_mode == 'Short':
side = 'Sell'
else:
side = 'Buy'
if pnl <= liquidation_threshold: if trigger == "Автоматический":
current_step = int(await rq.get_martingale_step(tg_id)) if pnl < 0:
current_step += 1 martingale_factor = safe_float(data_main_stgs.get('martingale_factor'))
await rq.update_martingale_step(tg_id, current_step) 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)
side = 'Buy' if position and position.get('side', '').lower() == 'long' else 'Sell' elif pnl > 0:
margin_mode = data_main_stgs.get('margin_type', 'Isolated')
await open_position(tg_id, message, side=side, margin_mode=margin_mode)
elif position:
entry_price = safe_float(position.get('avgPrice'))
side = position.get('side', '')
current_price = float(position.get('markPrice', 0))
if side.lower() == 'long':
take_profit_trigger_price = entry_price * (1 + take_profit_percent / 100)
if current_price >= take_profit_trigger_price:
await close_user_trade(tg_id, symbol, message)
await rq.update_martingale_step(tg_id, 0)
elif side.lower() == 'short':
take_profit_trigger_price = entry_price * (1 - take_profit_percent / 100)
if current_price <= take_profit_trigger_price:
await close_user_trade(tg_id, symbol, message)
await rq.update_martingale_step(tg_id, 0) await rq.update_martingale_step(tg_id, 0)
await message.answer("❗️ Прибыль достигнута, шаг мартингейла сброшен. "
"Начинаем новую серию ставок")
await open_position(tg_id, message, side=side, margin_mode=margin_mode, symbol=symbol,
quantity=starting_quantity)
async def handle_order_message(message, msg: dict) -> None:
"""
Обработчик сообщений об исполнении ордера.
Логирует событие и проверяет условия для мартингейла и TP.
"""
# logger.info(f"Исполнен ордер:\n{json.dumps(msg, indent=4, ensure_ascii=False)}")
trade_info = format_order_details_position(msg)
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:
@@ -164,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) try:
secret_key = await rq.get_bybit_secret_key(tg_id) client = await get_bybit_client(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') order_type = data_main_stgs.get('entry_order_type')
bybit_margin_mode = 'ISOLATED_MARGIN' if margin_mode == 'Isolated' else 'REGULAR_MARGIN'
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)
data_risk_stgs = await rq.get_user_risk_management_settings(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:
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:
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: else:
entry_price = price price_for_calc = entry_price
else:
entry_price = price
if order_type == 'Market': potential_loss = safe_float(quantity) * price_for_calc * (loss_profit / 100)
base_price = entry_price allowed_loss = safe_float(balance) * (max_risk_percent / 100)
else:
base_price = limit_price
if side.lower() == 'buy': if max_martingale_steps == current_martingale:
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) await error_max_step(message)
return 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)
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:
@@ -304,6 +320,110 @@ async def open_position(tg_id, message, side: str, margin_mode: str, tpsl_mode='
else: else:
raise e raise e
instruments_resp = client.get_instruments_info(category='linear', symbol=symbol)
if instruments_resp.get('retCode') == 0:
instrument_info = instruments_resp.get('result', {}).get('list', [])
if instrument_info:
instrument = instrument_info[0]
min_order_qty = float(instrument.get('minOrderQty', 0))
min_order_value_api = float(instrument.get('minOrderValue', 0))
if min_order_value_api == 0:
min_order_value_api = 5.0
min_order_value_calc = min_order_qty * price_for_calc if min_order_qty > 0 else 0
min_order_value = max(min_order_value_calc, min_order_value_api)
else:
min_order_value = 5.0
order_value = float(quantity) * price_for_calc
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
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': if tpsl_mode == 'Full':
tp_order_type = 'Market' tp_order_type = 'Market'
sl_order_type = 'Market' sl_order_type = 'Market'
@@ -320,7 +440,7 @@ async def open_position(tg_id, message, side: str, margin_mode: str, tpsl_mode='
symbol=symbol, symbol=symbol,
side=side, side=side,
orderType=order_type, orderType=order_type,
qty=str(next_quantity), qty=str(quantity),
price=str(limit_price) if order_type == 'Limit' and limit_price else None, price=str(limit_price) if order_type == 'Limit' and limit_price else None,
takeProfit=str(take_profit_price), takeProfit=str(take_profit_price),
tpOrderType=tp_order_type, tpOrderType=tp_order_type,
@@ -334,23 +454,23 @@ async def open_position(tg_id, message, side: str, margin_mode: str, tpsl_mode='
) )
if response.get('retCode', -1) == 0: if response.get('retCode', -1) == 0:
await rq.update_martingale_step(tg_id, current_martingale_step)
return True return True
else: else:
logger.error(f"Ошибка открытия ордера: {response}") logger.error(f"Ошибка открытия ордера: {response}")
await message.answer(f"Ошибка открытия ордера", reply_markup=inline_markup.back_to_main) await message.answer(f"Ошибка открытия ордера", reply_markup=inline_markup.back_to_main)
return False return 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):
""" """
Цикл торговой логики с учётом таймера пользователя. Цикл торговой логики с учётом таймера пользователя.
""" """
@@ -367,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,
@@ -381,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
@@ -406,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:
@@ -418,38 +524,6 @@ 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)
positions = positions_resp.get('result', {}).get('list', [])
if not positions or abs(float(positions[0].get('size', 0))) == 0:
params = dict(
category='linear',
symbol=symbol,
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( resp = client.set_trading_stop(
category='linear', category='linear',
symbol=symbol, symbol=symbol,
@@ -457,7 +531,9 @@ async def set_take_profit_stop_loss(tg_id: int, message, take_profit_price: floa
stopLoss=str(round(stop_loss_price, 5)), stopLoss=str(round(stop_loss_price, 5)),
tpTriggerBy='LastPrice', tpTriggerBy='LastPrice',
slTriggerBy='LastPrice', slTriggerBy='LastPrice',
reduceOnly=False positionIdx=0,
reduceOnly=False,
tpslMode=tpsl_mode
) )
if resp.get('retCode') != 0: if resp.get('retCode') != 0:
@@ -475,11 +551,9 @@ async def set_take_profit_stop_loss(tg_id: int, message, take_profit_price: floa
async def cancel_all_tp_sl_orders(tg_id, symbol): async def cancel_all_tp_sl_orders(tg_id, symbol):
""" """
Отменяет все открытые ордера TP/SL для указанного символа. Отменяет лимитные ордера для указанного символа.
""" """
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)
@@ -487,25 +561,40 @@ async def cancel_all_tp_sl_orders(tg_id, symbol):
for order in orders: for order in orders:
order_id = order.get('orderId') order_id = order.get('orderId')
order_symbol = order.get('symbol')
cancel_resp = client.cancel_order(category='linear', symbol=symbol, orderId=order_id) cancel_resp = client.cancel_order(category='linear', symbol=symbol, orderId=order_id)
last_response = cancel_resp
if cancel_resp.get('retCode') != 0: if cancel_resp.get('retCode') != 0:
logger.warning(f"Не удалось отменить ордер {order_id}: {cancel_resp.get('retMsg')}") logger.warning(f"Не удалось отменить ордер {order_id}: {cancel_resp.get('retMsg')}")
else:
last_response = order_symbol
except Exception as e: except Exception as e:
logger.error(f"Ошибка при отмене ордеров TP/SL: {e}") logger.error(f"Ошибка при отмене ордера: {e}")
return last_response return last_response
async def get_active_positions_by_symbol(tg_id, message): async def get_active_positions(tg_id, message):
"""
Показывает активные позиции пользователя.
"""
client = await get_bybit_client(tg_id)
active_positions = client.get_positions(category='linear', settleCoin='USDT')
positions = active_positions.get('result', {}).get('list', [])
active_symbols = [pos.get('symbol') for pos in positions if float(pos.get('size', 0)) > 0]
if active_symbols:
await message.answer("📈 Ваши активные позиции:",
reply_markup=inline_markup.create_trades_inline_keyboard(active_symbols))
else:
await message.answer("❗️ У вас нет активных позиций.", reply_markup=inline_markup.back_to_main)
return
async def get_active_positions_by_symbol(tg_id, symbol, message):
""" """
Показывает активные позиции пользователя по символу. Показывает активные позиции пользователя по символу.
""" """
api_key = await rq.get_bybit_api_key(tg_id) 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)
symbol = await rq.get_symbol(tg_id)
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
@@ -527,15 +616,29 @@ async def get_active_positions_by_symbol(tg_id, 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_by_symbol(tg_id, message): async def get_active_orders(tg_id, message):
"""
Показывает активные лимитные ордера пользователя.
"""
client = await get_bybit_client(tg_id)
response = client.get_open_orders(category='linear', settleCoin='USDT', orderType='Limit')
orders = response.get('result', {}).get('list', [])
limit_orders = [order for order in orders if order.get('orderType') == 'Limit']
if limit_orders:
symbols = [order['symbol'] for order in limit_orders]
await message.answer("📈 Ваши активные лимитные ордера:",
reply_markup=inline_markup.create_trades_inline_keyboard_limits(symbols))
else:
await message.answer("❗️ У вас нет активных лимитных ордеров.", reply_markup=inline_markup.back_to_main)
return
async def get_active_orders_by_symbol(tg_id, symbol, message):
""" """
Показывает активные лимитные ордера пользователя по символу. Показывает активные лимитные ордера пользователя по символу.
""" """
api_key = await rq.get_bybit_api_key(tg_id) client = await get_bybit_client(tg_id)
secret_key = await rq.get_bybit_secret_key(tg_id)
symbol = await rq.get_symbol(tg_id)
client = HTTP(api_key=api_key, api_secret=secret_key)
active_orders = client.get_open_orders(category='linear', symbol=symbol) 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', [])
@@ -557,27 +660,19 @@ async def get_active_orders_by_symbol(tg_id, 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_deal_markup(symbol)) await message.answer("\n\n".join(texts), reply_markup=inline_markup.create_close_limit_markup(symbol))
async def close_user_trade(tg_id: int, symbol: str, message): 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)
limit_price = await rq.get_limit_price(tg_id)
include_fee = data_risk_stgs.get('commission_fee', 'Нет') == 'Да'
client = HTTP(api_key=api_key, api_secret=secret_key)
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:
@@ -589,26 +684,10 @@ async def close_user_trade(tg_id: int, symbol: str, message):
position = positions_list[0] position = positions_list[0]
qty = abs(safe_float(position.get('size'))) qty = abs(safe_float(position.get('size')))
side = position.get('side') side = position.get('side')
entry_price = safe_float(position.get('avgPrice'))
if qty == 0: if qty == 0:
return False return False
orders = client.get_open_orders(category='linear', symbol=symbol)
cancel_resp = await cancel_all_tp_sl_orders(tg_id, symbol)
open_orders_list = orders.get('result', {}).get('list', [])
order_id = open_orders_list[0].get('orderId') if open_orders_list else None
close_side = "Sell" if side == "Buy" else "Buy" close_side = "Sell" if side == "Buy" else "Buy"
ticker_resp = client.get_tickers(category="linear", symbol=symbol)
current_price = 0.0
if ticker_resp.get('retCode') == 0:
result = ticker_resp.get('result', {})
ticker_list = []
if isinstance(result, dict):
ticker_list = result.get('list', [])
elif isinstance(result, list):
ticker_list = result
if ticker_list:
current_price = float(ticker_list[0].get('lastPrice', 0.0))
place_resp = client.place_order( place_resp = client.place_order(
category="linear", category="linear",
@@ -620,35 +699,12 @@ async def close_user_trade(tg_id: int, symbol: str, message):
reduceOnly=True reduceOnly=True
) )
if place_resp.get('retCode', -1) == 0: if place_resp.get('retCode') == 0:
trade_fee = 0
try:
trades_resp = client.get_closed_pnl(category="linear", symbol=symbol)
if trades_resp.get('retCode') == 0:
trades = trades_resp.get('result', {}).get('list', [])
for trade in trades:
if trade.get('orderId') == order_id:
trade_fee += float(trade.get('execFee', 0))
except Exception as e:
logger.error(f"Ошибка при получении сделок: {e}")
trade_fee = 0
pnl = (current_price - entry_price) * qty if side == "Buy" else (entry_price - current_price) * qty
if include_fee:
pnl -= trade_fee
pnl_percent = (pnl / (entry_price * qty)) * 100 if entry_price * qty > 0 else 0
return True return True
else: else:
if message:
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)
if message:
await message.answer("Произошла ошибка при закрытии сделки.", reply_markup=inline_markup.back_to_main)
return False return False
@@ -658,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

@@ -34,7 +34,7 @@ async def get_balance(tg_id: int, message) -> float:
if api_key == 'None' or secret_key == 'None': if api_key == 'None' or secret_key == 'None':
await message.answer('⚠️ Подключите платформу для торговли', await message.answer('⚠️ Подключите платформу для торговли',
reply_markup=inline_markup.connect_bybit_api_markup) reply_markup=inline_markup.connect_bybit_api_message)
return 0 return 0
try: try:

View File

@@ -3,11 +3,24 @@ import logging.config
from pybit.unified_trading import WebSocket from pybit.unified_trading import WebSocket
from websocket import WebSocketConnectionClosedException from websocket import WebSocketConnectionClosedException
from logger_helper.logger_helper import LOGGING_CONFIG from logger_helper.logger_helper import LOGGING_CONFIG
import app.telegram.database.requests as rq
logging.config.dictConfig(LOGGING_CONFIG) 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:
"""
Возвращает текущий активный цикл событий asyncio или создает новый, если его нет.
"""
try:
return asyncio.get_running_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
return loop
def set_event_loop(loop: asyncio.AbstractEventLoop): def set_event_loop(loop: asyncio.AbstractEventLoop):
@@ -15,14 +28,32 @@ def set_event_loop(loop: asyncio.AbstractEventLoop):
event_loop = loop event_loop = loop
def on_execution_callback(message, msg): async def run_ws_for_user(tg_id, message) -> None:
""" """
Callback на событие исполнения сделки. Запускает WebSocket Bybit для пользователя с указанным tg_id.
Безопасно запускает асинхронный обработчик из sync callback.
""" """
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_secret = await rq.get_bybit_secret_key(tg_id)
# Запускаем WebSocket как асинхронную задачу
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):
if event_loop is not None: if event_loop is not None:
from app.services.Bybit.functions.Futures import handle_execution_message # Импорт внутри, чтобы избежать циклических импортов from app.services.Bybit.functions.Futures import handle_order_message
asyncio.run_coroutine_threadsafe(handle_execution_message(message, msg), event_loop) asyncio.run_coroutine_threadsafe(handle_order_message(message, msg), event_loop)
else:
logger.error("Event loop не установлен, callback пропущен.")
def on_execution_callback(message, ws_msg):
if event_loop is not None:
from app.services.Bybit.functions.Futures import handle_execution_message
asyncio.run_coroutine_threadsafe(handle_execution_message(message, ws_msg), event_loop)
else: else:
logger.error("Event loop не установлен, callback пропущен.") logger.error("Event loop не установлен, callback пропущен.")
@@ -35,8 +66,14 @@ async def start_execution_ws(api_key: str, api_secret: str, message):
reconnect_delay = 5 reconnect_delay = 5
while True: while True:
try: try:
if not api_key or not api_secret:
logger.error("API_KEY и API_SECRET должны быть указаны для подключения к приватным каналам.")
await asyncio.sleep(reconnect_delay)
continue
ws = WebSocket(api_key=api_key, api_secret=api_secret, testnet=False, channel_type="private") ws = WebSocket(api_key=api_key, api_secret=api_secret, testnet=False, channel_type="private")
ws.execution_stream(lambda msg: on_execution_callback(message, msg))
ws.subscribe("order", lambda ws_msg: on_order_callback(message, ws_msg))
ws.subscribe("execution", lambda ws_msg: on_execution_callback(message, ws_msg))
while True: while True:
await asyncio.sleep(1) # Поддержание активности await asyncio.sleep(1) # Поддержание активности
except WebSocketConnectionClosedException: except WebSocketConnectionClosedException:

View File

@@ -1,16 +1,18 @@
import logging.config import asyncio
import logging.config
from aiogram import F, Router from aiogram import F, Router
from app.tasks.tasks import handle_stop_close_trade, handle_start_close_trade, handle_stop_trading, handle_start_trading 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, open_position, 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,
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
@@ -35,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"
@@ -48,7 +50,7 @@ async def clb_start_bybit_trade_message(callback: CallbackQuery) -> None:
"Как начать торговлю?\n\n" "Как начать торговлю?\n\n"
"1⃣ Проверьте и тщательно настройте все параметры в вашем профиле.\n" "1⃣ Проверьте и тщательно настройте все параметры в вашем профиле.\n"
"2⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару, без лишних символов (например: BTCUSDT).\n" "2⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару, без лишних символов (например: BTCUSDT).\n"
"3⃣ Нажмите кнопку 'Выбрать тип входа' и после нажмите начать торговлю.\n" "3⃣ Нажмите кнопку 'Начать торговать'.\n"
) )
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)
@@ -59,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"
@@ -72,7 +74,7 @@ async def start_bybit_trade_message(message: Message) -> None:
"Как начать торговлю?\n\n" "Как начать торговлю?\n\n"
"1⃣ Проверьте и тщательно настройте все параметры в вашем профиле.\n" "1⃣ Проверьте и тщательно настройте все параметры в вашем профиле.\n"
"2⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару, без лишних символов (например: BTCUSDT).\n" "2⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару, без лишних символов (например: BTCUSDT).\n"
"3⃣ Нажмите кнопку 'Выбрать тип входа' и после нажмите начать торговлю.\n" "3⃣ Нажмите кнопку 'Начать торговать'.\n"
) )
await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.trading_markup) await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.trading_markup)
@@ -189,43 +191,17 @@ 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')
starting_quantity = safe_float(data_main_stgs.get('starting_quantity'))
if not api_key or not secret_key: side = None
await message.answer("❗️ У вас не настроены API ключи для Bybit.") if switch_mode == 'Включено':
await callback.answer() switch_state = data_main_stgs.get('switch_state', 'Long')
return side = 'Buy' if switch_state == 'Long' else 'Sell'
else:
if trading_mode not in ['Long', 'Short', 'Smart', 'Switch']:
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
if trading_mode == 'Long': if trading_mode == 'Long':
side = 'Buy' side = 'Buy'
elif trading_mode == 'Short': elif trading_mode == 'Short':
@@ -238,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)
@@ -246,12 +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 handle_start_trading(tg_id, message) await trading_cycle(tg_id, message, side=side, margin_mode=margin_mode, symbol=symbol,
await message.answer(f"Торговля начнётся через {timer_minute} мин. Для отмены нажмите кнопку ниже.", starting_quantity=starting_quantity)
reply_markup=inline_markup.cancel_start_markup) 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=side, margin_mode=margin_mode) await open_position(tg_id, message, side, margin_mode, symbol=symbol, quantity=starting_quantity)
await callback.answer() await callback.answer()
@@ -263,8 +238,7 @@ async def show_my_trades(callback: CallbackQuery) -> None:
""" """
await callback.answer() await callback.answer()
try: try:
symbol = await rq.get_symbol(callback.from_user.id) await callback.message.answer(f"Выберите тип сделки:",
await callback.message.answer(f"Выберите тип сделки для пары {symbol}:",
reply_markup=inline_markup.my_deals_select_markup) reply_markup=inline_markup.my_deals_select_markup)
except Exception as e: except Exception as e:
logger.error(f"Произошла ошибка при выборе типа сделки: {e}") logger.error(f"Произошла ошибка при выборе типа сделки: {e}")
@@ -273,17 +247,34 @@ async def show_my_trades(callback: CallbackQuery) -> None:
@router_functions_bybit_trade.callback_query(F.data == "clb_open_deals") @router_functions_bybit_trade.callback_query(F.data == "clb_open_deals")
async def show_my_trades_callback(callback: CallbackQuery): async def show_my_trades_callback(callback: CallbackQuery):
""" """
Показывает открытые позиции пользователя по символу. Показывает открытые позиции пользователя.
""" """
await callback.answer() await callback.answer()
try: try:
await get_active_positions_by_symbol(callback.from_user.id, message=callback.message) await get_active_positions(callback.from_user.id, message=callback.message)
except Exception as e: except Exception as e:
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_"))
async def show_deal_callback(callback_query: CallbackQuery) -> None:
"""
Показывает сделку пользователя по символу.
"""
await callback_query.answer()
try:
symbol = callback_query.data[len("show_deal_"):]
await rq.update_symbol(callback_query.from_user.id, symbol)
tg_id = callback_query.from_user.id
await get_active_positions_by_symbol(tg_id, symbol, message=callback_query.message)
except Exception as e:
logger.error(f"Произошла ошибка при выборе сделки: {e}")
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")
async def show_my_orders_callback(callback: CallbackQuery) -> None: async def show_my_orders_callback(callback: CallbackQuery) -> None:
""" """
@@ -292,16 +283,33 @@ async def show_my_orders_callback(callback: CallbackQuery) -> None:
await callback.answer() await callback.answer()
try: try:
await get_active_orders_by_symbol(callback.from_user.id, message=callback.message) await get_active_orders(callback.from_user.id, message=callback.message)
except Exception as e: except Exception as e:
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_limit_"))
async def show_limit_callback(callback_query: CallbackQuery) -> None:
"""
Показывает сделку пользователя по символу.
"""
await callback_query.answer()
try:
symbol = callback_query.data[len("show_limit_"):]
await rq.update_symbol(callback_query.from_user.id, symbol)
tg_id = callback_query.from_user.id
await get_active_orders_by_symbol(tg_id, symbol, message=callback_query.message)
except Exception as e:
logger.error(f"Произошла ошибка при выборе сделки: {e}")
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")
async def set_tp_sl(callback: CallbackQuery, state: FSMContext) -> None: async def set_tp_sl(callback: CallbackQuery, state: FSMContext) -> None:
""" """
Показывает активные ордера пользователя. Запускает процесс установки Take Profit и Stop Loss.
""" """
await callback.answer() await callback.answer()
await state.set_state(SetTP_SL_State.waiting_for_take_profit) await state.set_state(SetTP_SL_State.waiting_for_take_profit)
@@ -361,18 +369,6 @@ async def process_stop_loss(message: Message, state: FSMContext) -> None:
await state.clear() await state.clear()
@router_functions_bybit_trade.callback_query(F.data == "clb_stop_timer")
async def cancel_start_callback(callback: CallbackQuery) -> None:
"""
Отменяет задачу старта торговли по таймеру, если она активна.
"""
tg_id = callback.from_user.id
await handle_stop_close_trade(tg_id)
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:")) @router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_deal:"))
async def close_trade_callback(callback: CallbackQuery) -> None: async def close_trade_callback(callback: CallbackQuery) -> None:
""" """
@@ -381,10 +377,9 @@ 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:
await handle_stop_trading(tg_id)
logger.info(f"Сделка {symbol} успешно закрыта.") logger.info(f"Сделка {symbol} успешно закрыта.")
else: else:
logger.error(f"Не удалось закрыть сделку {symbol}.") logger.error(f"Не удалось закрыть сделку {symbol}.")
@@ -393,6 +388,24 @@ async def close_trade_callback(callback: CallbackQuery) -> None:
await callback.answer() await callback.answer()
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_limit:"))
async def close_trade_callback(callback: CallbackQuery) -> None:
"""
Закрывает ордера пользователя по символу.
"""
symbol = callback.data.split(':')[1]
tg_id = callback.from_user.id
result = await cancel_all_tp_sl_orders(tg_id, symbol)
if result:
logger.info(f"Ордер {result} успешно закрыт.")
else:
await callback.message.answer(f"Не удалось закрыть ордер {result}.")
await callback.answer()
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_deal_by_timer:")) @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) -> None: async def ask_close_delay(callback: CallbackQuery, state: FSMContext) -> None:
""" """
@@ -401,7 +414,7 @@ async def ask_close_delay(callback: CallbackQuery, state: FSMContext) -> None:
symbol = callback.data.split(":")[1] symbol = callback.data.split(":")[1]
await state.update_data(symbol=symbol) await state.update_data(symbol=symbol)
await state.set_state(CloseTradeTimerState.waiting_for_delay) await state.set_state(CloseTradeTimerState.waiting_for_delay)
await callback.message.answer("Введите задержку в минутах до закрытия сделки (например, 60):", await callback.message.answer("Введите задержку в минутах до закрытия сделки:",
reply_markup=inline_markup.cancel) reply_markup=inline_markup.cancel)
await callback.answer() await callback.answer()
@@ -409,7 +422,7 @@ async def ask_close_delay(callback: CallbackQuery, state: FSMContext) -> None:
@router_functions_bybit_trade.message(CloseTradeTimerState.waiting_for_delay) @router_functions_bybit_trade.message(CloseTradeTimerState.waiting_for_delay)
async def process_close_delay(message: Message, state: FSMContext) -> None: async def process_close_delay(message: Message, state: FSMContext) -> None:
""" """
Обрабатывает ввод задержки и запускает задачу закрытия сделки с задержкой. Обрабатывает ввод закрытия сделки с задержкой.
""" """
try: try:
delay_minutes = int(message.text.strip()) delay_minutes = int(message.text.strip())
@@ -422,14 +435,10 @@ async def process_close_delay(message: Message, state: FSMContext) -> None:
data = await state.get_data() data = await state.get_data()
symbol = data.get("symbol") symbol = data.get("symbol")
tg_id = message.from_user.id
delay = delay_minutes * 60 delay = delay_minutes * 60
await message.answer(f"Закрытие сделки {symbol} запланировано через {delay_minutes} мин.")
await handle_start_close_trade(tg_id, message, symbol, delay) await close_trade_after_delay(message.from_user.id, message, symbol, delay)
await message.answer(f"Закрытие сделки {symbol} запланировано через {delay} секунд.",
reply_markup=inline_markup.cancel_start_markup)
await state.clear() await state.clear()
@@ -441,6 +450,67 @@ 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")
async def confirm_stop_trading(callback: CallbackQuery):
"""
Предлагает пользователю выбрать вариант подтверждение остановки торговли.
"""
await callback.message.answer(
"Выберите вариант остановки торговли:", reply_markup=inline_markup.stop_choice_markup
)
await callback.answer()
@router_functions_bybit_trade.callback_query(F.data == "stop_immediately")
async def stop_immediately(callback: CallbackQuery):
"""
Останавливает торговлю немедленно.
"""
tg_id = callback.from_user.id
await rq.update_trigger(tg_id, "Ручной")
await callback.message.answer("Автоматическая торговля остановлена.", reply_markup=inline_markup.back_to_main)
await callback.answer()
@router_functions_bybit_trade.callback_query(F.data == "stop_with_timer")
async def stop_with_timer_start(callback: CallbackQuery, state: FSMContext):
"""
Запускает диалог с пользователем для задания задержки перед остановкой торговли.
"""
await state.set_state(CloseTradeTimerState.waiting_for_trade)
await callback.message.answer("Введите задержку в минутах перед остановкой торговли:",
reply_markup=inline_markup.cancel)
await callback.answer()
@router_functions_bybit_trade.message(CloseTradeTimerState.waiting_for_trade)
async def process_stop_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
tg_id = message.from_user.id
delay_seconds = delay_minutes * 60
await message.answer(f"Торговля будет остановлена через {delay_minutes} минут.")
await asyncio.sleep(delay_seconds)
await rq.update_trigger(tg_id, "Ручной")
await message.answer("Автоматическая торговля остановлена.", reply_markup=inline_markup.back_to_main)
await state.clear()
@router_functions_bybit_trade.callback_query(F.data == "clb_cancel") @router_functions_bybit_trade.callback_query(F.data == "clb_cancel")

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

@@ -25,6 +25,7 @@ class state_limit_price(StatesGroup):
class CloseTradeTimerState(StatesGroup): class CloseTradeTimerState(StatesGroup):
"""FSM состояние ожидания задержки перед закрытием сделки.""" """FSM состояние ожидания задержки перед закрытием сделки."""
waiting_for_delay = State() waiting_for_delay = State()
waiting_for_trade = State()
class SetTP_SL_State(StatesGroup): class SetTP_SL_State(StatesGroup):
@@ -45,3 +46,24 @@ class state_reg_bybit_api(StatesGroup):
"""FSM состояние для регистрации API Bybit.""" """FSM состояние для регистрации API Bybit."""
api_key = State() api_key = State()
secret_key = State() secret_key = State()
class condition_settings(StatesGroup):
"""FSM состояние для настройки условий трейдинга."""
trigger = State()
timer = State()
volatilty = State()
volume = State()
integration = 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

@@ -1,98 +0,0 @@
import asyncio
import logging.config
from typing import Optional
from app.services.Bybit.functions.Futures import close_trade_after_delay, trading_cycle
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("tasks")
active_start_tasks = {}
active_close_tasks = {}
lock_start_tasks = asyncio.Lock()
lock_close_tasks = asyncio.Lock()
def start_trading_cycle(tg_id, message) -> None:
"""
Запускает асинхронную задачу торгового цикла для пользователя с указанным tg_id.
"""
task = asyncio.create_task(trading_cycle(tg_id, message))
active_start_tasks[tg_id] = task
def stop_trading_cycle(tg_id) -> None:
"""
Останавливает (отменяет) задачу торгового цикла для пользователя с указанным tg_id.
"""
task: Optional[asyncio.Task] = active_start_tasks.pop(tg_id, None)
if task:
task.cancel()
def start_close_trade_task(tg_id, message, symbol, delay_sec) -> None:
"""
Запускает асинхронную задачу автоматического закрытия сделки после задержки.
"""
task = asyncio.create_task(close_trade_after_delay(tg_id, message, symbol, delay_sec))
active_close_tasks[tg_id] = task
def stop_close_trade_task(tg_id) -> None:
"""
Останавливает (отменяет) задачу автоматического закрытия сделки для пользователя.
"""
task: Optional[asyncio.Task] = active_close_tasks.pop(tg_id, None)
if task:
task.cancel()
async def handle_start_trading(tg_id: int, message):
"""
Запускает торговый цикл. Если уже есть запущенная задача, отменяет её.
"""
async with lock_start_tasks:
old_task = active_start_tasks.get(tg_id)
if old_task and not old_task.done():
old_task.cancel()
try:
await old_task
except asyncio.CancelledError:
logger.info(f"Старая задача торговли для пользователя {tg_id} отменена")
start_trading_cycle(tg_id, message)
logger.info(f"Новая задача торговли запущена для пользователя {tg_id}")
async def handle_stop_trading(tg_id: int):
"""
Останавливает торговую задачу пользователя, если она активна.
"""
async with lock_start_tasks:
stop_trading_cycle(tg_id)
logger.info(f"Задача торговли остановлена для пользователя {tg_id}")
async def handle_start_close_trade(tg_id: int, message, symbol: str, delay_sec: int):
"""
Запускает задачу закрытия сделки с задержкой. Отменяет старую задачу, если есть.
"""
async with lock_close_tasks:
old_task = active_close_tasks.get(tg_id)
if old_task and not old_task.done():
old_task.cancel()
try:
await old_task
except asyncio.CancelledError:
logger.info(f"Старая задача закрытия сделки пользователя {tg_id} отменена")
start_close_trade_task(tg_id, message, symbol, delay_sec)
logger.info(f"Задача закрытия сделки для {symbol} запущена с задержкой {delay_sec}s для пользователя {tg_id}")
async def handle_stop_close_trade(tg_id: int):
"""
Отменяет задачу закрытия сделки пользователя, если она есть.
"""
async with lock_close_tasks:
stop_close_trade_task(tg_id)
logger.info(f"Задача закрытия сделки отменена для пользователя {tg_id}")

View File

@@ -6,24 +6,33 @@ start_markup = InlineKeyboardMarkup(inline_keyboard=[
]) ])
settings_markup = InlineKeyboardMarkup(inline_keyboard=[ settings_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Настройки", callback_data='clb_settings_message')],
[InlineKeyboardButton(text="Запуск", callback_data='clb_start_trading')] [InlineKeyboardButton(text="Запуск", callback_data='clb_start_trading')]
]) ])
back_btn_list_settings = [InlineKeyboardButton(text="Назад",
callback_data='clb_back_to_special_settings_message')] # Кнопка для возврата к списку каталога настроек
back_btn_list_settings_markup = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Назад",
callback_data='clb_back_to_special_settings_message')]]) # Клавиатура для возврата к списку каталога настроек
back_btn_to_main = [InlineKeyboardButton(text="На главную", callback_data='clb_back_to_main')]
back_btn_profile = [InlineKeyboardButton(text="Назад", callback_data='clb_start_chatbot_message')] back_btn_profile = [InlineKeyboardButton(text="Назад", callback_data='clb_start_chatbot_message')]
connect_bybit_api_message = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Подключить Bybit", callback_data='clb_new_user_connect_bybit_api_message')]
])
special_settings_markup = InlineKeyboardMarkup(inline_keyboard=[ special_settings_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Основные настройки", callback_data='clb_change_main_settings'), [InlineKeyboardButton(text="Основные настройки", callback_data='clb_change_main_settings'),
InlineKeyboardButton(text="Риск-менеджмент", callback_data='clb_change_risk_management_settings')], InlineKeyboardButton(text="Риск-менеджмент", callback_data='clb_change_risk_management_settings')],
[InlineKeyboardButton(text="Условия запуска", callback_data='clb_change_condition_settings'), [InlineKeyboardButton(text="Условия запуска", callback_data='clb_change_condition_settings'),
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
back_btn_profile
]) ])
connect_bybit_api_markup = InlineKeyboardMarkup(inline_keyboard=[ connect_bybit_api_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Подключить Bybit", callback_data='clb_new_user_connect_bybit_api')] [InlineKeyboardButton(text="Подключить Bybit", callback_data='clb_new_user_connect_bybit_api')]
]) ])
@@ -32,7 +41,8 @@ trading_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Настройки", callback_data='clb_settings_message')], [InlineKeyboardButton(text="Настройки", callback_data='clb_settings_message')],
[InlineKeyboardButton(text="Мои сделки", callback_data='clb_my_deals')], [InlineKeyboardButton(text="Мои сделки", callback_data='clb_my_deals')],
[InlineKeyboardButton(text="Указать торговую пару", callback_data='clb_update_trading_pair')], [InlineKeyboardButton(text="Указать торговую пару", callback_data='clb_update_trading_pair')],
[InlineKeyboardButton(text="Выбрать тип входа", callback_data='clb_update_entry_type')], [InlineKeyboardButton(text="Начать торговать", callback_data='clb_update_entry_type')],
[InlineKeyboardButton(text="Остановить торговлю", callback_data='clb_stop_trading')],
]) ])
start_trading_markup = InlineKeyboardMarkup(inline_keyboard=[ start_trading_markup = InlineKeyboardMarkup(inline_keyboard=[
@@ -51,15 +61,11 @@ entry_order_type_markup = InlineKeyboardMarkup(
[ [
InlineKeyboardButton(text="Market (текущая цена)", callback_data="entry_order_type:Market"), InlineKeyboardButton(text="Market (текущая цена)", callback_data="entry_order_type:Market"),
InlineKeyboardButton(text="Limit (фиксированная цена)", callback_data="entry_order_type:Limit"), InlineKeyboardButton(text="Limit (фиксированная цена)", callback_data="entry_order_type:Limit"),
], ], back_btn_to_main
] ]
) )
back_btn_list_settings = [InlineKeyboardButton(text="Назад",
callback_data='clb_back_to_special_settings_message')] # Кнопка для возврата к списку каталога настроек
back_btn_list_settings_markup = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Назад",
callback_data='clb_back_to_special_settings_message')]]) # Клавиатура для возврата к списку каталога настроек
back_btn_to_main = [InlineKeyboardButton(text="На главную", callback_data='clb_back_to_main')]
back_to_main = InlineKeyboardMarkup(inline_keyboard=[ back_to_main = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="На главную", callback_data='back_to_main')], [InlineKeyboardButton(text="На главную", callback_data='back_to_main')],
@@ -67,6 +73,7 @@ back_to_main = InlineKeyboardMarkup(inline_keyboard=[
main_settings_markup = InlineKeyboardMarkup(inline_keyboard=[ main_settings_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='Режим торговли', callback_data='clb_change_trading_mode'), [InlineKeyboardButton(text='Режим торговли', callback_data='clb_change_trading_mode'),
InlineKeyboardButton(text='Режим свитч', callback_data='clb_change_switch_mode'),
InlineKeyboardButton(text='Тип маржи', callback_data='clb_change_margin_type')], InlineKeyboardButton(text='Тип маржи', callback_data='clb_change_margin_type')],
[InlineKeyboardButton(text='Размер кредитного плеча', callback_data='clb_change_size_leverage'), [InlineKeyboardButton(text='Размер кредитного плеча', callback_data='clb_change_size_leverage'),
@@ -92,7 +99,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'),
@@ -119,9 +126,7 @@ additional_settings_markup = InlineKeyboardMarkup(inline_keyboard=[
trading_mode_markup = InlineKeyboardMarkup(inline_keyboard=[ trading_mode_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Лонг", callback_data="trade_mode_long"), [InlineKeyboardButton(text="Лонг", callback_data="trade_mode_long"),
InlineKeyboardButton(text="Шорт", callback_data="trade_mode_short")], InlineKeyboardButton(text="Шорт", callback_data="trade_mode_short"),
[InlineKeyboardButton(text="Свитч", callback_data="trade_mode_switch"),
InlineKeyboardButton(text="Смарт", callback_data="trade_mode_smart")], InlineKeyboardButton(text="Смарт", callback_data="trade_mode_smart")],
back_btn_list_settings, back_btn_list_settings,
@@ -137,9 +142,11 @@ margin_type_markup = InlineKeyboardMarkup(inline_keyboard=[
]) ])
trigger_markup = InlineKeyboardMarkup(inline_keyboard=[ # ИЗМЕНИТЬ НА INLINE trigger_markup = InlineKeyboardMarkup(inline_keyboard=[ # ИЗМЕНИТЬ НА INLINE
[InlineKeyboardButton(text='Ручной', callback_data="clb_trigger_ruchnoy"), [InlineKeyboardButton(text='Ручной', callback_data="clb_trigger_manual")],
InlineKeyboardButton(text='TradingView', callback_data="clb_trigger_tradingview")], # [InlineKeyboardButton(text='TradingView', callback_data="clb_trigger_tradingview")],
[InlineKeyboardButton(text="Автоматический", callback_data="clb_trigger_auto")] [InlineKeyboardButton(text="Автоматический", callback_data="clb_trigger_auto")],
back_btn_list_settings,
back_btn_to_main
]) ])
buttons_yes_no_markup = InlineKeyboardMarkup(inline_keyboard=[ buttons_yes_no_markup = InlineKeyboardMarkup(inline_keyboard=[
@@ -154,15 +161,21 @@ buttons_on_off_markup = InlineKeyboardMarkup(inline_keyboard=[ # ИЗМЕНИТ
my_deals_select_markup = InlineKeyboardMarkup(inline_keyboard=[ my_deals_select_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='Открытые сделки', callback_data="clb_open_deals"), [InlineKeyboardButton(text='Открытые сделки', callback_data="clb_open_deals"),
InlineKeyboardButton(text='Открытые ордера', callback_data="clb_open_orders")], InlineKeyboardButton(text='Лимитные ордера', callback_data="clb_open_orders")],
back_btn_to_main back_btn_to_main
]) ])
def create_trades_inline_keyboard(trades): def create_trades_inline_keyboard(trades):
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
for trade in trades: for trade in trades:
symbol = trade['symbol'] if isinstance(trade, dict) else trade.symbol builder.button(text=trade, callback_data=f"show_deal_{trade}")
builder.button(text=symbol, callback_data=f"show_deal_{symbol}") builder.adjust(2)
return builder.as_markup()
def create_trades_inline_keyboard_limits(trades):
builder = InlineKeyboardBuilder()
for trade in trades:
builder.button(text=trade, callback_data=f"show_limit_{trade}")
builder.adjust(2) builder.adjust(2)
return builder.as_markup() return builder.as_markup()
@@ -175,12 +188,34 @@ def create_close_deal_markup(symbol: str) -> InlineKeyboardMarkup:
back_btn_to_main back_btn_to_main
]) ])
def create_close_limit_markup(symbol: str) -> InlineKeyboardMarkup:
return InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Закрыть лимитный ордер", callback_data=f"close_limit:{symbol}")],
back_btn_to_main
])
timer_markup = InlineKeyboardMarkup(inline_keyboard=[ timer_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Установить таймер", callback_data="clb_set_timer")], [InlineKeyboardButton(text="Установить таймер", callback_data="clb_set_timer")],
back_btn_to_main back_btn_to_main
]) ])
cancel_start_markup = InlineKeyboardMarkup(inline_keyboard=[ stop_choice_markup = InlineKeyboardMarkup(
[InlineKeyboardButton(text="Отменить таймер", callback_data="clb_stop_timer")] inline_keyboard=[
[
InlineKeyboardButton(text="Остановить сразу", callback_data="stop_immediately"),
InlineKeyboardButton(text="Остановить по таймеру", callback_data="stop_with_timer"),
]
]
)
buttons_on_off_markup_for_switch = InlineKeyboardMarkup(inline_keyboard=[ # ИЗМЕНИТЬ НА INLINE
[InlineKeyboardButton(text='Включить', callback_data="clb_on_switch"),
InlineKeyboardButton(text='Выключить', callback_data="clb_off_switch")],
[InlineKeyboardButton(text="Изменить состояние", callback_data="clb_switch_state")],
back_btn_to_main
])
switch_state_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='Long', callback_data="clb_long_switch"),
InlineKeyboardButton(text='Short', callback_data="clb_short_switch")],
]) ])

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):
@@ -158,13 +157,15 @@ class User_Main_Settings(Base):
trading_mode = mapped_column(ForeignKey("trading_modes.mode")) trading_mode = mapped_column(ForeignKey("trading_modes.mode"))
margin_type = mapped_column(ForeignKey("margin_types.type")) margin_type = mapped_column(ForeignKey("margin_types.type"))
switch_mode_enabled = mapped_column(String(15), default="Выключен")
switch_state = mapped_column(String(10), default='Long')
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):
@@ -212,7 +213,7 @@ class User_Condition_Settings(Base):
tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id")) tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"))
trigger = mapped_column(ForeignKey("triggers.trigger")) trigger = mapped_column(String(15), default='Ручной')
filter_time = mapped_column(String(25), default='???') filter_time = mapped_column(String(25), default='???')
filter_volatility = mapped_column(Boolean, default=False) filter_volatility = mapped_column(Boolean, default=False)
external_cues = mapped_column(Boolean, default=False) external_cues = mapped_column(Boolean, default=False)
@@ -295,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():
@@ -308,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 = ['Ручной', 'Автоматический', 'TradingView']
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
@@ -291,10 +292,10 @@ async def get_for_registration_margin_type():
return type return type
async def get_for_registration_trigger(): async def get_for_registration_trigger(tg_id):
"""Получить триггер по умолчанию.""" """Получить триггер по умолчанию."""
async with async_session() as session: async with async_session() as session:
trigger = await session.scalar(select(Trigger.trigger).where(Trigger.id == 1)) trigger = await session.scalar(select(UCS.trigger).where(tg_id == tg_id))
return trigger return trigger
@@ -308,6 +309,8 @@ async def get_user_main_settings(tg_id):
trading_mode = await session.scalar(select(UMS.trading_mode).where(UMS.tg_id == tg_id)) trading_mode = await session.scalar(select(UMS.trading_mode).where(UMS.tg_id == tg_id))
margin_mode = await session.scalar(select(UMS.margin_type).where(UMS.tg_id == tg_id)) margin_mode = await session.scalar(select(UMS.margin_type).where(UMS.tg_id == tg_id))
switch_mode_enabled = await session.scalar(select(UMS.switch_mode_enabled).where(UMS.tg_id == tg_id))
switch_state = await session.scalar(select(UMS.switch_state).where(UMS.tg_id == tg_id))
size_leverage = await session.scalar(select(UMS.size_leverage).where(UMS.tg_id == tg_id)) size_leverage = await session.scalar(select(UMS.size_leverage).where(UMS.tg_id == tg_id))
starting_quantity = await session.scalar(select(UMS.starting_quantity).where(UMS.tg_id == tg_id)) starting_quantity = await session.scalar(select(UMS.starting_quantity).where(UMS.tg_id == tg_id))
martingale_factor = await session.scalar(select(UMS.martingale_factor).where(UMS.tg_id == tg_id)) martingale_factor = await session.scalar(select(UMS.martingale_factor).where(UMS.tg_id == tg_id))
@@ -319,6 +322,8 @@ async def get_user_main_settings(tg_id):
data = { data = {
'trading_mode': trading_mode, 'trading_mode': trading_mode,
'margin_type': margin_mode, 'margin_type': margin_mode,
'switch_mode_enabled': switch_mode_enabled,
'switch_state': switch_state,
'size_leverage': size_leverage, 'size_leverage': size_leverage,
'starting_quantity': starting_quantity, 'starting_quantity': starting_quantity,
'martingale_factor': martingale_factor, 'martingale_factor': martingale_factor,
@@ -547,3 +552,27 @@ async def update_martingale_step(tg_id, step):
await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(martingale_step=step)) await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(martingale_step=step))
await session.commit() await session.commit()
async def update_switch_mode_enabled(tg_id, switch_mode):
"""Обновить режим переключения пользователя."""
async with async_session() as session:
await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(switch_mode_enabled=switch_mode))
await session.commit()
async def update_switch_state(tg_id, switch_state):
"""Обновить состояние переключения пользователя."""
async with async_session() as session:
await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(switch_state=switch_state))
await session.commit()
async def update_trigger(tg_id, trigger):
"""Обновить триггер пользователя."""
async with async_session() as session:
await session.execute(update(UCS).where(UCS.tg_id == tg_id).values(trigger=trigger))
await session.commit()

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

@@ -1,13 +1,10 @@
import asyncio import logging.config
import logging.config
import app.telegram.Keyboards.inline_keyboards as inline_markup import app.telegram.Keyboards.inline_keyboards as inline_markup
from aiogram import Router, F from aiogram import Router, F
from aiogram.types import Message, CallbackQuery from aiogram.types import Message, CallbackQuery
from aiogram.fsm.context import FSMContext from aiogram.fsm.context import FSMContext
import app.telegram.database.requests as rq import app.telegram.database.requests as rq
from aiogram.fsm.state import State, StatesGroup from app.states.States import condition_settings
from app.services.Bybit.functions.Futures import trading_cycle
from logger_helper.logger_helper import LOGGING_CONFIG from logger_helper.logger_helper import LOGGING_CONFIG
@@ -17,27 +14,21 @@ logger = logging.getLogger("condition_settings")
condition_settings_router = Router() condition_settings_router = Router()
class condition_settings(StatesGroup): async def reg_new_user_default_condition_settings(id):
trigger = State()
timer = State()
volatilty = State()
volume = State()
integration = State()
use_tv_signal = State()
async def reg_new_user_default_condition_settings(id, message):
tg_id = id tg_id = id
trigger = await rq.get_for_registration_trigger() trigger = await rq.get_for_registration_trigger(tg_id)
await rq.set_new_user_default_condition_settings(tg_id, trigger) await rq.set_new_user_default_condition_settings(tg_id, trigger)
async def main_settings_message(id, message, state): async def main_settings_message(id, message):
text = """ <b>Условия запуска</b>
<b>- Триггер:</b> Ручной запуск / Сигнал TradingView / Полностью автоматический tg_id = id
trigger = await rq.get_for_registration_trigger(tg_id)
text = f""" <b>Условия запуска</b>
<b>- Режим торговли:</b> {trigger}
<b>- Таймер: </b> установить таймер / остановить таймер <b>- Таймер: </b> установить таймер / остановить таймер
<b>- Фильтр волатильности / объёма: </b> включить/отключить <b>- Фильтр волатильности / объёма: </b> включить/отключить
<b>- Интеграции и внешние сигналы: </b> <b>- Интеграции и внешние сигналы: </b>
@@ -48,14 +39,31 @@ async def main_settings_message(id, message, state):
await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.condition_settings_markup) await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.condition_settings_markup)
async def trigger_message(message, state): async def trigger_message(id, message, state: FSMContext):
text = '''Триггер await state.set_state(condition_settings.trigger)
text = '''
Описание ручного запуска, сигналов, автоматического режима ''' <b>- Автоматический:</b> торговля будет продолжаться до условии остановки.
<b>- Ручной:</b> торговля будет происходить только в ручном режиме.
<em>- Выберите тип триггера:</em>'''
await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.trigger_markup) await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.trigger_markup)
@condition_settings_router.callback_query(F.data == "clb_trigger_manual")
async def trigger_manual_callback(callback: CallbackQuery, state: FSMContext):
await state.set_state(condition_settings.trigger)
await rq.update_trigger(tg_id=callback.from_user.id, trigger="Ручной")
await main_settings_message(callback.from_user.id, callback.message)
await callback.answer()
@condition_settings_router.callback_query(F.data == "clb_trigger_auto")
async def trigger_manual_callback(callback: CallbackQuery, state: FSMContext):
await state.set_state(condition_settings.trigger)
await rq.update_trigger(tg_id=callback.from_user.id, trigger="Автоматический")
await main_settings_message(callback.from_user.id, callback.message)
await callback.answer()
async def timer_message(id, message: Message, state: FSMContext): async def timer_message(id, message: Message, state: FSMContext):
await state.set_state(condition_settings.timer) await state.set_state(condition_settings.timer)

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', reply_markup=reply_markup.base_buttons_markup) await message.answer(f""" Привет <b>{username}</b>! 👋""", parse_mode='html')
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,23 +1,18 @@
from aiogram import Router 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()
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
@@ -28,12 +23,14 @@ async def reg_new_user_default_main_settings(id, message):
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): async def main_settings_message(id, message):
data = await rq.get_user_main_settings(id) data = await rq.get_user_main_settings(id)
await message.answer(f"""<b>Основные настройки</b> 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_state']}
<b>- Тип маржи:</b> {data['margin_type']} <b>- Тип маржи:</b> {data['margin_type']}
<b>- Размер кредитного плеча:</b> х{data['size_leverage']} <b>- Размер кредитного плеча:</b> х{data['size_leverage']}
<b>- Начальная ставка:</b> {data['starting_quantity']} <b>- Начальная ставка:</b> {data['starting_quantity']}
@@ -42,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)
@@ -53,11 +51,10 @@ async def trading_mode_message(message, state):
<b>Смарт</b> — автоматизированный режим, который подбирает оптимальную стратегию в зависимости от текущих рыночных условий. <b>Смарт</b> — автоматизированный режим, который подбирает оптимальную стратегию в зависимости от текущих рыночных условий.
<b>Свитч</b> — динамическое переключение между торговыми режимами для максимизации эффективности.
<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()
@@ -70,38 +67,84 @@ async def state_trading_mode(callback: CallbackQuery, state):
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_switch':
await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Switch")
await rq.update_trade_mode_user(id, 'Switch')
await main_settings_message(id, callback.message, state)
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 size_leverage_message (message, state):
async def switch_mode_enabled_message(message, state):
await state.set_state(update_main_settings.switch_mode_enabled)
await message.edit_text(
"""<b>Свитч</b> — динамическое переключение между торговыми режимами для максимизации эффективности.
<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"])
async def state_switch_mode_enabled(callback: CallbackQuery, state):
await callback.answer()
tg_id = callback.from_user.id
val = "Включить" if callback.data == "clb_on_switch" else "Выключить"
if val == "Включить":
await rq.update_switch_mode_enabled(tg_id, "Включено")
await callback.answer(f"Включено")
await main_settings_message(tg_id, callback.message)
else:
await rq.update_switch_mode_enabled(tg_id, "Выключено")
await callback.answer(f"Выключено")
await main_settings_message(tg_id, callback.message)
await state.clear()
@router_main_settings.callback_query(lambda c: c.data in ["clb_switch_state"])
async def state_switch_mode_enabled(callback: CallbackQuery):
await callback.answer()
await callback.message.answer("Выберите состояние свитча:", reply_markup=inline_markup.switch_state_markup)
@router_main_settings.callback_query(lambda c: c.data in ["clb_long_switch", "clb_short_switch"])
async def state_switch_mode_enabled(callback: CallbackQuery, state):
await callback.answer()
tg_id = callback.from_user.id
val = "Long" if callback.data == "clb_long_switch" else "Short"
if val == "Long":
await rq.update_switch_state(tg_id, "Long")
await callback.message.answer(f"Состояние свитча: {val}")
await main_settings_message(tg_id, callback.message)
else:
await rq.update_switch_state(tg_id, "Short")
await callback.message.answer(f"Состояние свитча: {val}")
await main_settings_message(tg_id, callback.message)
await state.clear()
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)
@@ -110,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)
@@ -134,13 +181,15 @@ 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)
@@ -161,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 = []
for pos in positions:
size = pos.get('size')
if float(size) > 0:
await callback.answer(
"⚠️ Маржинальный режим нельзя менять при открытой позиции",
show_alert=True
)
return
try: try:
match callback.data: match callback.data:
case 'margin_type_isolated': 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)
@@ -203,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)
@@ -227,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()
@@ -33,7 +38,7 @@ async def price_profit_message(message, state):
text = 'Введите число изменения цены для фиксации прибыли: ' text = 'Введите число изменения цены для фиксации прибыли: '
await message.answer(text=text, parse_mode='html', reply_markup=None) await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.cancel)
@router_risk_management_settings.message(update_risk_management_settings.price_profit) @router_risk_management_settings.message(update_risk_management_settings.price_profit)
@@ -62,7 +67,7 @@ async def price_loss_message(message, state):
text = 'Введите число изменения цены для фиксации убытков: ' text = 'Введите число изменения цены для фиксации убытков: '
await message.answer(text=text, parse_mode='html', reply_markup=None) await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.cancel)
@router_risk_management_settings.message(update_risk_management_settings.price_loss) @router_risk_management_settings.message(update_risk_management_settings.price_loss)
@@ -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 или совпадает со старым стоп-лоссом
@@ -111,7 +117,7 @@ async def max_risk_deal_message(message, state):
text = 'Введите число (процент от баланса) для изменения максимального риска на сделку: ' text = 'Введите число (процент от баланса) для изменения максимального риска на сделку: '
await message.answer(text=text, parse_mode='html', reply_markup=None) await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.cancel)
@router_risk_management_settings.message(update_risk_management_settings.max_risk_deal) @router_risk_management_settings.message(update_risk_management_settings.max_risk_deal)

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
@@ -12,7 +12,9 @@ import app.telegram.functions.condition_settings.settings as func_condition_sett
import app.telegram.functions.additional_settings.settings as func_additional_settings import app.telegram.functions.additional_settings.settings as func_additional_settings
import app.telegram.database.requests as rq import app.telegram.database.requests as rq
import app.telegram.Keyboards.reply_keyboards as reply_markup
from app.services.Bybit.functions.balance import get_balance
from app.services.Bybit.functions.bybit_ws import run_ws_for_user
from logger_helper.logger_helper import LOGGING_CONFIG from logger_helper.logger_helper import LOGGING_CONFIG
@@ -26,16 +28,11 @@ router = Router()
async def start_message(message: Message) -> None: async def start_message(message: Message) -> None:
""" """
Обработчик команды /start. Обработчик команды /start.
Запускает WebSocket для пользователя и инициализирует нового пользователя в БД. Инициализирует нового пользователя в БД.
Args: Args:
message (Message): Входящее сообщение с командой /start. message (Message): Входящее сообщение с командой /start.
""" """
from BybitBot_API import run_ws_for_user
tg_id = message.from_user.id
asyncio.create_task(run_ws_for_user(tg_id, message))
logger.info(f"Получение event loop")
await rq.set_new_user_bybit_api(message.from_user.id) await rq.set_new_user_bybit_api(message.from_user.id)
await func.start_message(message) await func.start_message(message)
@@ -50,26 +47,13 @@ async def profile_message(message: Message) -> None:
message (Message): Сообщение с текстом кнопки. message (Message): Сообщение с текстом кнопки.
""" """
user = await rq.check_user(message.from_user.id) user = await rq.check_user(message.from_user.id)
tg_id = message.from_user.id
if user: balance = await get_balance(message.from_user.id, message)
if user and balance:
await run_ws_for_user(tg_id, message)
await func.profile_message(message.from_user.username, message) await func.profile_message(message.from_user.username, message)
@router.message(F.text == "Настройки")
async def settings_msg(message: Message) -> None:
"""
Обработчик кнопки 'Настройки'.
Проверяет пользователя и отображает меню настроек.
Args:
message (Message): Сообщение с текстом кнопки.
"""
user = await rq.check_user(message.from_user.id)
if user:
await func.settings_message(message)
@router.callback_query(F.data == "clb_start_chatbot_message") @router.callback_query(F.data == "clb_start_chatbot_message")
async def clb_profile_msg(callback: CallbackQuery) -> None: async def clb_profile_msg(callback: CallbackQuery) -> None:
""" """
@@ -81,12 +65,12 @@ async def clb_profile_msg(callback: CallbackQuery) -> None:
callback (CallbackQuery): Полученный колбэк. callback (CallbackQuery): Полученный колбэк.
""" """
user = await rq.check_user(callback.from_user.id) user = await rq.check_user(callback.from_user.id)
balance = await get_balance(callback.from_user.id, callback.message)
first_name = callback.from_user.first_name or "" first_name = callback.from_user.first_name or ""
last_name = callback.from_user.last_name or "" last_name = callback.from_user.last_name or ""
username = f"{first_name} {last_name}".strip() or callback.from_user.username or "Пользователь" username = f"{first_name} {last_name}".strip() or callback.from_user.username or "Пользователь"
if user: if user and balance:
await func.profile_message(callback.from_user.username, callback.message) await func.profile_message(callback.from_user.username, callback.message)
else: else:
await rq.save_tg_id_new_user(callback.from_user.id) await rq.save_tg_id_new_user(callback.from_user.id)
@@ -94,13 +78,9 @@ async def clb_profile_msg(callback: CallbackQuery) -> None:
await func_main_settings.reg_new_user_default_main_settings(callback.from_user.id, callback.message) await func_main_settings.reg_new_user_default_main_settings(callback.from_user.id, callback.message)
await func_rmanagement_settings.reg_new_user_default_risk_management_settings(callback.from_user.id, await func_rmanagement_settings.reg_new_user_default_risk_management_settings(callback.from_user.id,
callback.message) callback.message)
await func_condition_settings.reg_new_user_default_condition_settings(callback.from_user.id, callback.message) await func_condition_settings.reg_new_user_default_condition_settings(callback.from_user.id)
await func_additional_settings.reg_new_user_default_additional_settings(callback.from_user.id, callback.message) await func_additional_settings.reg_new_user_default_additional_settings(callback.from_user.id, callback.message)
await callback.message.answer(f'Здравствуйте, {username}!', reply_markup=reply_markup.base_buttons_markup)
await func.profile_message(username, callback.message)
await callback.answer() await callback.answer()
@@ -131,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()
@@ -158,35 +137,34 @@ 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, state) await func_condition_settings.main_settings_message(callback.from_user.id, callback.message)
await callback.answer() await callback.answer()
@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()
# Конкретные настройки каталогов # Конкретные настройки каталогов
list_main_settings = ['clb_change_trading_mode', list_main_settings = ['clb_change_trading_mode',
'clb_change_switch_mode',
'clb_change_margin_type', 'clb_change_margin_type',
'clb_change_size_leverage', 'clb_change_size_leverage',
'clb_change_starting_quantity', 'clb_change_starting_quantity',
@@ -210,6 +188,8 @@ async def clb_main_settings_msg(callback: CallbackQuery, state: FSMContext) -> N
match callback.data: match callback.data:
case 'clb_change_trading_mode': case 'clb_change_trading_mode':
await func_main_settings.trading_mode_message(callback.message, state) await func_main_settings.trading_mode_message(callback.message, state)
case 'clb_change_switch_mode':
await func_main_settings.switch_mode_enabled_message(callback.message, state)
case 'clb_change_margin_type': case 'clb_change_margin_type':
await func_main_settings.margin_type_message(callback.message, state) await func_main_settings.margin_type_message(callback.message, state)
case 'clb_change_size_leverage': case 'clb_change_size_leverage':
@@ -256,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',
@@ -279,8 +259,8 @@ 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.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)
case 'clb_change_filter_volatility': case 'clb_change_filter_volatility':

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",