2
0
forked from kodorvan/stcs

The entire database has been changed to PostgresSQL. The entire code has been updated.

This commit is contained in:
algizn97
2025-10-01 15:23:21 +05:00
parent e5a3de4ed8
commit 97662081ce
49 changed files with 7916 additions and 0 deletions

View File

@@ -0,0 +1,363 @@
import asyncio
import logging.config
from aiogram import F, Router
from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery
import app.telegram.keyboards.inline as kbi
import database.request as rq
from app.bybit.get_functions.get_positions import get_active_positions_by_symbol
from app.bybit.open_positions import start_trading_cycle
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("start_trading")
router_start_trading = Router(name="start_trading")
user_trade_tasks = {}
@router_start_trading.callback_query(F.data == "start_trading")
async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handles the "start_trading" callback query.
Clears the FSM state and sends a message to the user to select the trading mode.
:param callback_query: Message
:param state: FSMContext
:return: None
"""
try:
await state.clear()
additional_data = await rq.get_user_additional_settings(
tg_id=callback_query.from_user.id
)
trade_mode = additional_data.trade_mode
symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id)
deals = await get_active_positions_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol
)
size = deals.get("size") or 0
position_idx = deals.get("positionIdx")
if position_idx != 0 and int(size) > 0 and trade_mode == "Merged_Single":
await callback_query.answer(
text="У вас есть активная позиция в режиме хеджирования. "
"Открытие сделки в одностороннем режиме невозможно.",
)
return
if position_idx == 0 and int(size) > 0 and trade_mode == "Both_Sides":
await callback_query.answer(
text="У вас есть активная позиция в одностороннем режиме. "
"Открытие сделки в режиме хеджирования невозможно.",
)
return
if trade_mode == "Merged_Single":
await callback_query.message.edit_text(
text="Выберите режим торговли:\n\n"
"Лонг - все сделки серии открываются на покупку.\n"
"Шорт - все сделки серии открываются на продажу.\n"
"Свитч - направление каждой сделки серии меняется по переменно.\n",
reply_markup=kbi.merged_start_trading,
)
else: # trade_mode == "Both_Sides":
await callback_query.message.edit_text(
text="Выберите режим торговли:\n\n"
"Лонг - все сделки открываются на покупку.\n"
"Шорт - все сделки открываются на продажу.\n",
reply_markup=kbi.both_start_trading,
)
logger.debug(
"Command start_trading processed successfully for user: %s",
callback_query.from_user.id,
)
except Exception as e:
await callback_query.answer(text="Произошла ошибка при запуске торговли")
logger.error(
"Error processing command start_trading for user %s: %s",
callback_query.from_user.id,
e,
)
@router_start_trading.callback_query(lambda c: c.data == "long" or c.data == "short")
async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handles the "long" or "short" callback query.
Clears the FSM state and starts the trading cycle.
:param callback_query: Message
:param state: FSMContext
:return: None
"""
try:
if callback_query.data == "long":
side = "Buy"
elif callback_query.data == "short":
side = "Sell"
else:
await callback_query.answer(text="Произошла ошибка при запуске торговли")
return
symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id)
deals = await get_active_positions_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol
)
size = deals.get("size") or 0
position_idx = deals.get("positionIdx")
if position_idx == 0 and int(size) > 0:
await callback_query.answer(
text="Торговля уже запущена в одностороннем режиме для данного инструмента"
)
return
conditional_data = await rq.get_user_conditional_settings(
tg_id=callback_query.from_user.id
)
timer_start = conditional_data.timer_start
if callback_query.from_user.id in user_trade_tasks:
task = user_trade_tasks[callback_query.from_user.id]
if not task.done():
task.cancel()
del user_trade_tasks[callback_query.from_user.id]
async def delay_start():
if timer_start > 0:
await callback_query.message.edit_text(
text=f"Торговля будет запущена с задержкой {timer_start} мин.",
reply_markup=kbi.cancel_timer,
)
await asyncio.sleep(timer_start * 60)
res = await start_trading_cycle(
tg_id=callback_query.from_user.id,
side=side,
switch_side_mode=False,
)
error_messages = {
"Limit price is out min price": "Цена лимитного ордера меньше минимального",
"Limit price is out max price": "Цена лимитного ордера больше максимального",
"Risk is too high for this trade": "Риск сделки превышает допустимый убыток",
"estimated will trigger liq": "Лимитный ордер может вызвать мгновенную ликвидацию. Проверьте параметры ордера.",
"ab not enough for new order": "Недостаточно средств для создания нового ордера",
"InvalidRequestError": "Произошла ошибка при запуске торговли.",
"Order does not meet minimum order value": "Сумма ордера не sufficientдля запуска торговли",
"position idx not match position mode": "Торговля уже запущена в режиме хеджирования на продажу для данного инструмента",
"Qty invalid": "Некорректное значение ордера для данного инструмента",
}
if res == "OK":
await rq.set_start_timer(
tg_id=callback_query.from_user.id, timer_start=0
)
await rq.set_auto_trading(
tg_id=callback_query.from_user.id, symbol=symbol, auto_trading=True
)
await callback_query.answer(text="Торговля запущена")
await state.clear()
else:
# Получаем сообщение из таблицы, или дефолтный текст
text = error_messages.get(res, "Произошла ошибка при запуске торговли")
await callback_query.answer(text=text)
task = asyncio.create_task(delay_start())
user_trade_tasks[callback_query.from_user.id] = task
except Exception as e:
await callback_query.answer(text="Произошла ошибка при запуске торговли")
logger.error(
"Error processing command long for user %s: %s",
callback_query.from_user.id,
e,
)
except asyncio.CancelledError:
logger.error("Cancelled timer for user %s", callback_query.from_user.id)
@router_start_trading.callback_query(lambda c: c.data == "switch")
async def start_trading_switch(
callback_query: CallbackQuery, state: FSMContext
) -> None:
"""
Handles the "switch" callback query.
Clears the FSM state and sends a message to the user to select the switch side.
:param callback_query: Message
:param state: FSMContext
:return: None
"""
try:
await state.clear()
await callback_query.message.edit_text(
text="Выберите направление первой сделки серии:\n\n"
"Лонг - открывается первая сделка на покупку.\n"
"Шорт - открывается первая сделка на продажу.\n"
"По направлению - сделка открывается в направлении последней сделки предыдущей серии.\n"
"Противоположно - сделка открывается в противоположном направлении последней сделки предыдущей серии.\n",
reply_markup=kbi.switch_side,
)
except Exception as e:
await callback_query.answer(text="Произошла ошибка при запуске торговли")
logger.error(
"Error processing command start trading switch for user %s: %s",
callback_query.from_user.id,
e,
)
@router_start_trading.callback_query(
lambda c: c.data
in {"switch_long", "switch_short", "switch_direction", "switch_opposite"}
)
async def start_switch(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Starts the trading cycle with the selected side.
:param callback_query:
:param state:
:return:
"""
try:
symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id)
user_deals_data = await rq.get_user_deal_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol
)
get_side = "Buy"
if user_deals_data:
get_side = user_deals_data.last_side or "Buy"
if callback_query.data == "switch_long":
side = "Buy"
elif callback_query.data == "switch_short":
side = "Sell"
elif callback_query.data == "switch_direction":
side = get_side
elif callback_query.data == "switch_opposite":
if get_side == "Buy":
side = "Sell"
else:
side = "Buy"
else:
await callback_query.answer(text="Произошла ошибка при запуске торговли")
return
deals = await get_active_positions_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol
)
size = deals.get("size") or 0
position_idx = deals.get("positionIdx")
if position_idx == 1 and int(size) > 0 and side == "Buy":
await callback_query.answer(
text="Торговля уже запущена в режиме хеджирования на покупку для данного инструмента"
)
return
if position_idx == 2 and int(size) > 0 and side == "Sell":
await callback_query.answer(
text="Торговля уже запущена в режиме хеджирования на продажу для данного инструмента"
)
return
conditional_data = await rq.get_user_conditional_settings(
tg_id=callback_query.from_user.id
)
timer_start = conditional_data.timer_start
if callback_query.from_user.id in user_trade_tasks:
task = user_trade_tasks[callback_query.from_user.id]
if not task.done():
task.cancel()
del user_trade_tasks[callback_query.from_user.id]
async def delay_start():
if timer_start > 0:
await callback_query.message.edit_text(
text=f"Торговля будет запущена с задержкой {timer_start} мин.",
reply_markup=kbi.cancel_timer,
)
await asyncio.sleep(timer_start * 60)
res = await start_trading_cycle(
tg_id=callback_query.from_user.id,
side=side,
switch_side_mode=True,
)
error_messages = {
"Limit price is out min price": "Цена лимитного ордера меньше минимального",
"Limit price is out max price": "Цена лимитного ордера больше максимального",
"Risk is too high for this trade": "Риск сделки превышает допустимый убыток",
"estimated will trigger liq": "Лимитный ордер может вызвать мгновенную ликвидацию. Проверьте параметры ордера.",
"ab not enough for new order": "Недостаточно средств для создания нового ордера",
"InvalidRequestError": "Произошла ошибка при запуске торговли.",
"Order does not meet minimum order value": "Сумма ордера не sufficientдля запуска торговли",
"position idx not match position mode": "Торговля уже запущена в режиме хеджирования на продажу для данного инструмента",
"Qty invalid": "Некорректное значение ордера для данного инструмента",
}
if res == "OK":
await rq.set_start_timer(
tg_id=callback_query.from_user.id, timer_start=0
)
await rq.set_auto_trading(
tg_id=callback_query.from_user.id, symbol=symbol, auto_trading=True
)
await callback_query.answer(text="Торговля запущена")
await state.clear()
else:
text = error_messages.get(res, "Произошла ошибка при запуске торговли")
await callback_query.answer(text=text)
task = asyncio.create_task(delay_start())
user_trade_tasks[callback_query.from_user.id] = task
except asyncio.CancelledError:
logger.error("Cancelled timer for user %s", callback_query.from_user.id)
except Exception as e:
await callback_query.answer(text="Произошла ошибка при запуске торговли")
logger.error(
"Error processing command start switch for user %s: %s",
callback_query.from_user.id,
e,
)
@router_start_trading.callback_query(F.data == "cancel_timer")
async def cancel_start_trading(
callback_query: CallbackQuery, state: FSMContext
) -> None:
"""
Handles the "cancel_timer" callback query.
Clears the FSM state and sends a message to the user to cancel the start trading process.
:param callback_query: Message
:param state: FSMContext
:return: None
"""
try:
task = user_trade_tasks.get(callback_query.from_user.id)
if task and not task.done():
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
user_trade_tasks.pop(callback_query.from_user.id, None)
await callback_query.message.edit_text(
"Таймер отменён.", reply_markup=kbi.profile_bybit
)
else:
await callback_query.message.edit_text(
"Таймер не запущен.", reply_markup=kbi.profile_bybit
)
except Exception as e:
await callback_query.answer("Произошла ошибка при отмене запуска торговли")
logger.error(
"Error processing command cancel_timer for user %s: %s",
callback_query.from_user.id,
e,
)
finally:
await state.clear()