This commit is contained in:
algizn97
2025-10-03 14:19:18 +05:00
parent 4adbd70948
commit 1508629727
15 changed files with 412 additions and 210 deletions

View File

@@ -22,7 +22,7 @@ async def get_active_positions(tg_id: int) -> list | None:
] ]
if active_symbols: if active_symbols:
logger.info("Active positions for user: %s", tg_id) logger.info("Active positions for user: %s", tg_id)
return active_symbols return positions
else: else:
logger.warning("No active positions found for user: %s", tg_id) logger.warning("No active positions found for user: %s", tg_id)
return ["No active positions found"] return ["No active positions found"]
@@ -50,7 +50,7 @@ async def get_active_positions_by_symbol(tg_id: int, symbol: str) -> dict | None
positions = response.get("result", {}).get("list", []) positions = response.get("result", {}).get("list", [])
if positions: if positions:
logger.info("Active positions for user: %s", tg_id) logger.info("Active positions for user: %s", tg_id)
return positions[0] return positions
else: else:
logger.warning("No active positions found for user: %s", tg_id) logger.warning("No active positions found for user: %s", tg_id)
return None return None
@@ -85,7 +85,7 @@ async def get_active_orders(tg_id: int) -> list | None:
] ]
if active_orders: if active_orders:
logger.info("Active orders for user: %s", tg_id) logger.info("Active orders for user: %s", tg_id)
return active_orders return orders
else: else:
logger.warning("No active orders found for user: %s", tg_id) logger.warning("No active orders found for user: %s", tg_id)
return ["No active orders found"] return ["No active orders found"]
@@ -115,7 +115,7 @@ async def get_active_orders_by_symbol(tg_id: int, symbol: str) -> dict | None:
orders = response.get("result", {}).get("list", []) orders = response.get("result", {}).get("list", [])
if orders: if orders:
logger.info("Active orders for user: %s", tg_id) logger.info("Active orders for user: %s", tg_id)
return orders[0] return orders
else: else:
logger.warning("No active orders found for user: %s", tg_id) logger.warning("No active orders found for user: %s", tg_id)
return None return None

View File

@@ -169,6 +169,7 @@ async def trading_cycle(tg_id: int, symbol: str, reverse_side: str, size: str) -
leverage=leverage, leverage=leverage,
) )
if reverse_side == "Buy": if reverse_side == "Buy":
real_side = "Sell" real_side = "Sell"
else: else:
@@ -219,7 +220,7 @@ async def trading_cycle(tg_id: int, symbol: str, reverse_side: str, size: str) -
leverage_to_sell=leverage_to_sell, leverage_to_sell=leverage_to_sell,
order_type=order_type, order_type=order_type,
conditional_order_type=conditional_order_type, conditional_order_type=conditional_order_type,
order_quantity=current_step, order_quantity=next_quantity,
limit_price=limit_price, limit_price=limit_price,
trigger_price=trigger_price, trigger_price=trigger_price,
martingale_factor=martingale_factor, martingale_factor=martingale_factor,
@@ -258,9 +259,9 @@ async def open_positions(
trigger_price: float, trigger_price: float,
trade_mode: str, trade_mode: str,
margin_type: str, margin_type: str,
leverage: float, leverage: str,
leverage_to_buy: float, leverage_to_buy: str,
leverage_to_sell: float, leverage_to_sell: str,
take_profit_percent: float, take_profit_percent: float,
stop_loss_percent: float, stop_loss_percent: float,
max_risk_percent: float, max_risk_percent: float,
@@ -317,19 +318,22 @@ async def open_positions(
if trade_mode == "Both_Sides": if trade_mode == "Both_Sides":
po_position_idx = 1 if side == "Buy" else 2 po_position_idx = 1 if side == "Buy" else 2
leverage = safe_float( if margin_type == "ISOLATED_MARGIN":
get_leverage = safe_float(
leverage_to_buy if side == "Buy" else leverage_to_sell leverage_to_buy if side == "Buy" else leverage_to_sell
) )
else:
get_leverage = safe_float(leverage)
else: else:
po_position_idx = 0 po_position_idx = 0
leverage = safe_float(leverage) get_leverage = safe_float(leverage)
potential_loss = ( potential_loss = (
safe_float(order_quantity) safe_float(order_quantity)
* safe_float(price_for_calc) * safe_float(price_for_calc)
* (stop_loss_percent / 100) * (stop_loss_percent / 100)
) )
adjusted_loss = potential_loss / leverage adjusted_loss = potential_loss / get_leverage
allowed_loss = safe_float(user_balance) * (max_risk_percent / 100) allowed_loss = safe_float(user_balance) * (max_risk_percent / 100)
if adjusted_loss > allowed_loss: if adjusted_loss > allowed_loss:
@@ -356,10 +360,11 @@ async def open_positions(
entry_price=price_for_calc, entry_price=price_for_calc,
symbol=symbol, symbol=symbol,
order_quantity=order_quantity, order_quantity=order_quantity,
leverage=get_leverage,
) )
if liq_long > 0 or liq_short > 0 and price_for_calc > 0: if (liq_long > 0 or liq_short > 0) and price_for_calc > 0:
if side.lower() == "buy": if side == "Buy":
base_tp = price_for_calc + (price_for_calc - liq_long) base_tp = price_for_calc + (price_for_calc - liq_long)
take_profit_price = base_tp + total_commission take_profit_price = base_tp + total_commission
else: else:
@@ -371,7 +376,7 @@ async def open_positions(
stop_loss_price = None stop_loss_price = None
else: else:
if side.lower() == "buy": if side == "Buy":
take_profit_price = price_for_calc * tp_multiplier take_profit_price = price_for_calc * tp_multiplier
stop_loss_price = price_for_calc * (1 - stop_loss_percent / 100) stop_loss_price = price_for_calc * (1 - stop_loss_percent / 100)
else: else:
@@ -383,6 +388,7 @@ async def open_positions(
take_profit_price = max(take_profit_price, 0) take_profit_price = max(take_profit_price, 0)
stop_loss_price = max(stop_loss_price, 0) stop_loss_price = max(stop_loss_price, 0)
# Place order # Place order
order_params = { order_params = {
"category": "linear", "category": "linear",

View File

@@ -43,6 +43,12 @@ async def set_switch_position_mode(tg_id: int, symbol: str, mode: int) -> str |
tg_id, tg_id,
) )
return "You have an existing position, so position mode cannot be switched" return "You have an existing position, so position mode cannot be switched"
if str(e).startswith("Open orders exist, so you cannot change position mode"):
logger.debug(
"Open orders exist, so you cannot change position mode for user: %s",
tg_id,
)
return "Open orders exist, so you cannot change position mode"
else: else:
logger.error("Error connecting to Bybit for user %s: %s", tg_id, e) logger.error("Error connecting to Bybit for user %s: %s", tg_id, e)
return False return False

View File

@@ -1,7 +1,6 @@
import logging.config import logging.config
from app.bybit import get_bybit_client from app.bybit import get_bybit_client
from app.bybit.get_functions.get_positions import get_active_positions_by_symbol
from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG) logging.config.dictConfig(LOGGING_CONFIG)
@@ -13,6 +12,7 @@ async def set_tp_sl_for_position(
symbol: str, symbol: str,
take_profit_price: float, take_profit_price: float,
stop_loss_price: float, stop_loss_price: float,
position_idx: int,
) -> bool: ) -> bool:
""" """
Set take profit and stop loss for a symbol. Set take profit and stop loss for a symbol.
@@ -20,14 +20,11 @@ async def set_tp_sl_for_position(
:param symbol: Symbol to set take profit and stop loss for :param symbol: Symbol to set take profit and stop loss for
:param take_profit_price: Take profit price :param take_profit_price: Take profit price
:param stop_loss_price: Stop loss price :param stop_loss_price: Stop loss price
:param position_idx: Position index
:return: bool :return: bool
""" """
try: try:
client = await get_bybit_client(tg_id) client = await get_bybit_client(tg_id)
user_positions = await get_active_positions_by_symbol(
tg_id=tg_id, symbol=symbol
)
position_idx = user_positions.get("positionIdx")
resp = client.set_trading_stop( resp = client.set_trading_stop(
category="linear", category="linear",
symbol=symbol, symbol=symbol,

View File

@@ -106,12 +106,27 @@ class TelegramMessageHandler:
if side == "Buy" if side == "Buy"
else "Продажа" if side == "Sell" else "Нет данных" else "Продажа" if side == "Sell" else "Нет данных"
) )
if safe_float(closed_size) == 0:
await rq.set_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, side=side, fee=safe_float(exec_fee)
)
user_auto_trading = await rq.get_user_auto_trading(
tg_id=tg_id, symbol=symbol
)
if user_auto_trading is not None and user_auto_trading.fee is not None:
fee = user_auto_trading.fee
else:
fee = 0
exec_pnl = format_value(execution.get("execPnl")) exec_pnl = format_value(execution.get("execPnl"))
risk_management_data = await rq.get_user_risk_management(tg_id=tg_id) risk_management_data = await rq.get_user_risk_management(tg_id=tg_id)
commission_fee = risk_management_data.commission_fee commission_fee = risk_management_data.commission_fee
if commission_fee == "Yes_commission_fee": if commission_fee == "Yes_commission_fee":
total_pnl = safe_float(exec_pnl) - safe_float(exec_fee) total_pnl = safe_float(exec_pnl) - safe_float(exec_fee) - fee
else: else:
total_pnl = safe_float(exec_pnl) total_pnl = safe_float(exec_pnl)
@@ -138,13 +153,12 @@ class TelegramMessageHandler:
chat_id=tg_id, text=text, reply_markup=kbi.profile_bybit chat_id=tg_id, text=text, reply_markup=kbi.profile_bybit
) )
user_auto_trading = await rq.get_user_auto_trading(
tg_id=tg_id, symbol=symbol
)
auto_trading = ( auto_trading = (
user_auto_trading.auto_trading if user_auto_trading else False user_auto_trading.auto_trading if user_auto_trading else False
) )
user_symbols = await rq.get_user_deal_by_symbol(tg_id=tg_id, symbol=symbol) user_symbols = (
user_auto_trading.symbol if user_auto_trading else None
)
if ( if (
auto_trading auto_trading
@@ -152,20 +166,22 @@ class TelegramMessageHandler:
and user_symbols is not None and user_symbols is not None
): ):
if safe_float(total_pnl) > 0: if safe_float(total_pnl) > 0:
await rq.set_auto_trading(
tg_id=tg_id, symbol=symbol, auto_trading=False
)
profit_text = "📈 Прибыль достигнута\n" profit_text = "📈 Прибыль достигнута\n"
await self.telegram_bot.send_message( await self.telegram_bot.send_message(
chat_id=tg_id, text=profit_text, reply_markup=kbi.profile_bybit chat_id=tg_id, text=profit_text, reply_markup=kbi.profile_bybit
) )
if side == "Buy":
r_side = "Sell"
else:
r_side = "Buy"
await rq.set_auto_trading(tg_id=tg_id, symbol=symbol, auto_trading=False, side=r_side)
else: else:
open_order_text = "\n❗️ Сделка закрылась в минус, открываю новую сделку с увеличенной ставкой.\n" open_order_text = "\n❗️ Сделка закрылась в минус, открываю новую сделку с увеличенной ставкой.\n"
await self.telegram_bot.send_message( await self.telegram_bot.send_message(
chat_id=tg_id, text=open_order_text chat_id=tg_id, text=open_order_text
) )
res = await trading_cycle( res = await trading_cycle(
tg_id=tg_id, symbol=symbol, reverse_side=side tg_id=tg_id, symbol=symbol, reverse_side=side, size=closed_size
) )
if res == "OK": if res == "OK":
@@ -180,8 +196,12 @@ class TelegramMessageHandler:
error_text = errors.get( error_text = errors.get(
res, "❗️ Не удалось открыть новую сделку" res, "❗️ Не удалось открыть новую сделку"
) )
if side == "Buy":
r_side = "Sell"
else:
r_side = "Buy"
await rq.set_auto_trading( await rq.set_auto_trading(
tg_id=tg_id, symbol=symbol, auto_trading=False tg_id=tg_id, symbol=symbol, auto_trading=False, side=r_side
) )
await self.telegram_bot.send_message( await self.telegram_bot.send_message(
chat_id=tg_id, chat_id=tg_id,

View File

@@ -64,7 +64,6 @@ class WebSocketBot:
channel_type="private", channel_type="private",
api_key=api_key, api_key=api_key,
api_secret=api_secret, api_secret=api_secret,
restart_on_error=True,
) )
self.user_sockets[tg_id] = self.ws_private self.user_sockets[tg_id] = self.ws_private

View File

@@ -5,7 +5,7 @@ from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery from aiogram.types import CallbackQuery
import app.telegram.keyboards.inline as kbi import app.telegram.keyboards.inline as kbi
import database.request as rq
from app.bybit.get_functions.get_positions import ( from app.bybit.get_functions.get_positions import (
get_active_orders, get_active_orders,
get_active_orders_by_symbol, get_active_orders_by_symbol,
@@ -22,7 +22,7 @@ router_get_positions_handlers = Router(name="get_positions_handlers")
@router_get_positions_handlers.callback_query(F.data == "my_deals") @router_get_positions_handlers.callback_query(F.data == "my_deals")
async def get_positions_handlers( async def get_positions_handlers(
callback_query: CallbackQuery, state: FSMContext callback_query: CallbackQuery, state: FSMContext
) -> None: ) -> None:
""" """
Gets the user's active positions. Gets the user's active positions.
@@ -43,7 +43,7 @@ async def get_positions_handlers(
@router_get_positions_handlers.callback_query(F.data == "change_position") @router_get_positions_handlers.callback_query(F.data == "change_position")
async def get_positions_handler( async def get_positions_handler(
callback_query: CallbackQuery, state: FSMContext callback_query: CallbackQuery, state: FSMContext
) -> None: ) -> None:
""" """
Gets the user's active positions. Gets the user's active positions.
@@ -64,9 +64,17 @@ async def get_positions_handler(
await callback_query.answer(text="Нет активных позиций.") await callback_query.answer(text="Нет активных позиций.")
return return
active_positions = [pos for pos in res if float(pos.get("size", 0)) > 0]
if not active_positions:
await callback_query.answer(text="Нет активных позиций.")
return
active_symbols_sides = [(pos.get("symbol"), pos.get("side")) for pos in active_positions]
await callback_query.message.edit_text( await callback_query.message.edit_text(
text="Ваши активные позиции:", text="Ваши активные позиции:",
reply_markup=kbi.create_active_positions_keyboard(res), reply_markup=kbi.create_active_positions_keyboard(symbols=active_symbols_sides),
) )
except Exception as e: except Exception as e:
logger.error("Error in get_positions_handler: %s", e) logger.error("Error in get_positions_handler: %s", e)
@@ -82,7 +90,10 @@ async def get_positions_handler(
) )
async def get_position_handler(callback_query: CallbackQuery, state: FSMContext): async def get_position_handler(callback_query: CallbackQuery, state: FSMContext):
try: try:
symbol = callback_query.data.split("_", 2)[2] data = callback_query.data
parts = data.split("_")
symbol = parts[2]
get_side = parts[3]
res = await get_active_positions_by_symbol( res = await get_active_positions_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol tg_id=callback_query.from_user.id, symbol=symbol
) )
@@ -93,26 +104,48 @@ async def get_position_handler(callback_query: CallbackQuery, state: FSMContext)
) )
return return
symbol = res.get("symbol") or "Нет данных" position = next((pos for pos in res if pos.get("side") == get_side), None)
avg_price = res.get("avgPrice") or "Нет данных"
size = res.get("size") or "Нет данных" if position:
side = res.get("side") or "" side = position.get("side")
symbol = position.get("symbol") or "Нет данных"
avg_price = position.get("avgPrice") or "Нет данных"
size = position.get("size") or "Нет данных"
take_profit = position.get("takeProfit") or "Нет данных"
stop_loss = position.get("stopLoss") or "Нет данных"
position_idx = position.get("positionIdx")
else:
side = "Нет данных"
symbol = "Нет данных"
avg_price = "Нет данных"
size = "Нет данных"
take_profit = "Нет данных"
stop_loss = "Нет данных"
position_idx = "Нет данных"
side_rus = ( side_rus = (
"Покупка" "Покупка"
if side == "Buy" if side == "Buy"
else "Продажа" if side == "Sell" else "Нет данных" else "Продажа" if side == "Sell" else "Нет данных"
) )
take_profit = res.get("takeProfit") or "Нет данных"
stop_loss = res.get("stopLoss") or "Нет данных" position_idx_rus = ("Односторонний" if position_idx == 0
else "Покупка в режиме хеджирования" if position_idx == 1
else "Продажа в режиме хеджирования" if position_idx == 2
else "Нет данных")
await callback_query.message.edit_text( await callback_query.message.edit_text(
text=f"Торговая пара: {symbol}\n" text=f"Торговая пара: {symbol}\n"
f"Цена входа: {avg_price}\n" f"Режим позиции: {position_idx_rus}\n"
f"Количество: {size}\n" f"Цена входа: {avg_price}\n"
f"Движение: {side_rus}\n" f"Количество: {size}\n"
f"Тейк-профит: {take_profit}\n" f"Движение: {side_rus}\n"
f"Стоп-лосс: {stop_loss}\n", f"Тейк-профит: {take_profit}\n"
reply_markup=kbi.make_close_position_keyboard(symbol_pos=symbol), f"Стоп-лосс: {stop_loss}\n",
reply_markup=kbi.make_close_position_keyboard(symbol_pos=symbol,
side=side,
position_idx=position_idx,
qty=size),
) )
except Exception as e: except Exception as e:
@@ -126,7 +159,7 @@ async def get_position_handler(callback_query: CallbackQuery, state: FSMContext)
@router_get_positions_handlers.callback_query(F.data == "open_orders") @router_get_positions_handlers.callback_query(F.data == "open_orders")
async def get_open_orders_handler( async def get_open_orders_handler(
callback_query: CallbackQuery, state: FSMContext callback_query: CallbackQuery, state: FSMContext
) -> None: ) -> None:
""" """
Gets the user's open orders. Gets the user's open orders.
@@ -147,9 +180,17 @@ async def get_open_orders_handler(
await callback_query.answer(text="Нет активных ордеров.") await callback_query.answer(text="Нет активных ордеров.")
return return
active_positions = [pos for pos in res if pos.get("orderStatus", 0) == "New"]
if not active_positions:
await callback_query.answer(text="Нет активных ордеров.")
return
active_orders_sides = [(pos.get("symbol"), pos.get("side")) for pos in active_positions]
await callback_query.message.edit_text( await callback_query.message.edit_text(
text="Ваши активные ордера:", text="Ваши активные ордера:",
reply_markup=kbi.create_active_orders_keyboard(res), reply_markup=kbi.create_active_orders_keyboard(orders=active_orders_sides),
) )
except Exception as e: except Exception as e:
logger.error("Error in get_open_orders_handler: %s", e) logger.error("Error in get_open_orders_handler: %s", e)
@@ -163,7 +204,10 @@ async def get_open_orders_handler(
@router_get_positions_handlers.callback_query(lambda c: c.data.startswith("get_order_")) @router_get_positions_handlers.callback_query(lambda c: c.data.startswith("get_order_"))
async def get_order_handler(callback_query: CallbackQuery, state: FSMContext): async def get_order_handler(callback_query: CallbackQuery, state: FSMContext):
try: try:
symbol = callback_query.data.split("_", 2)[2] data = callback_query.data
parts = data.split("_")
symbol = parts[2]
get_side = parts[3]
res = await get_active_orders_by_symbol( res = await get_active_orders_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol tg_id=callback_query.from_user.id, symbol=symbol
) )
@@ -174,37 +218,52 @@ async def get_order_handler(callback_query: CallbackQuery, state: FSMContext):
) )
return return
symbol = res.get("symbol") or "Нет данных" orders = next((pos for pos in res if pos.get("side") == get_side), None)
price = res.get("price") or "Нет данных"
qty = res.get("qty") or "Нет данных" if orders:
side = res.get("side") or "" side = orders.get("side")
symbol = orders.get("symbol") or "Нет данных"
price = orders.get("price") or "Нет данных"
qty = orders.get("qty") or "Нет данных"
order_type = orders.get("orderType") or ""
trigger_price = orders.get("triggerPrice") or "Нет данных"
take_profit = orders.get("takeProfit") or "Нет данных"
stop_loss = orders.get("stopLoss") or "Нет данных"
order_id = orders.get("orderId")
else:
side = "Нет данных"
symbol = "Нет данных"
price = "Нет данных"
qty = "Нет данных"
order_type = "Нет данных"
trigger_price = "Нет данных"
take_profit = "Нет данных"
stop_loss = "Нет данных"
order_id = "Нет данных"
side_rus = ( side_rus = (
"Покупка" "Покупка"
if side == "Buy" if side == "Buy"
else "Продажа" if side == "Sell" else "Нет данных" else "Продажа" if side == "Sell" else "Нет данных"
) )
order_type = res.get("orderType") or ""
order_type_rus = ( order_type_rus = (
"Рыночный" "Рыночный"
if order_type == "Market" if order_type == "Market"
else "Лимитный" if order_type == "Limit" else "Нет данных" else "Лимитный" if order_type == "Limit" else "Нет данных"
) )
trigger_price = res.get("triggerPrice") or "Нет данных"
take_profit = res.get("takeProfit") or "Нет данных"
stop_loss = res.get("stopLoss") or "Нет данных"
await callback_query.message.edit_text( await callback_query.message.edit_text(
text=f"Торговая пара: {symbol}\n" text=f"Торговая пара: {symbol}\n"
f"Цена: {price}\n" f"Цена: {price}\n"
f"Количество: {qty}\n" f"Количество: {qty}\n"
f"Движение: {side_rus}\n" f"Движение: {side_rus}\n"
f"Тип ордера: {order_type_rus}\n" f"Тип ордера: {order_type_rus}\n"
f"Триггер цена: {trigger_price}\n" f"Триггер цена: {trigger_price}\n"
f"Тейк-профит: {take_profit}\n" f"Тейк-профит: {take_profit}\n"
f"Стоп-лосс: {stop_loss}\n", f"Стоп-лосс: {stop_loss}\n",
reply_markup=kbi.make_close_orders_keyboard(symbol_order=symbol), reply_markup=kbi.make_close_orders_keyboard(symbol_order=symbol, order_id=order_id),
) )
await rq.set_user_symbol(tg_id=callback_query.from_user.id, symbol=symbol)
except Exception as e: except Exception as e:
logger.error("Error in get_order_handler: %s", e) logger.error("Error in get_order_handler: %s", e)
await callback_query.answer( await callback_query.answer(

View File

@@ -124,6 +124,13 @@ async def trade_mode(callback_query: CallbackQuery, state: FSMContext) -> None:
) )
return return
if response == "Open orders exist, so you cannot change position mode":
await callback_query.answer(
text="У вас есть открытые ордера, "
"поэтому режим позиции не может быть изменен."
)
return
if callback_query.data.startswith("Merged_Single"): if callback_query.data.startswith("Merged_Single"):
await callback_query.answer(text="Выбран режим позиции: Односторонний") await callback_query.answer(text="Выбран режим позиции: Односторонний")
await set_leverage( await set_leverage(

View File

@@ -5,6 +5,8 @@ from aiogram import F, Router
from aiogram.fsm.context import FSMContext from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery from aiogram.types import CallbackQuery
from app.telegram.tasks.tasks import add_start_task_merged, cancel_start_task_merged, add_start_task_switch, \
cancel_start_task_switch
import app.telegram.keyboards.inline as kbi import app.telegram.keyboards.inline as kbi
import database.request as rq import database.request as rq
from app.bybit.get_functions.get_positions import get_active_positions_by_symbol from app.bybit.get_functions.get_positions import get_active_positions_by_symbol
@@ -17,8 +19,6 @@ logger = logging.getLogger("start_trading")
router_start_trading = Router(name="start_trading") router_start_trading = Router(name="start_trading")
user_trade_tasks = {}
@router_start_trading.callback_query(F.data == "start_trading") @router_start_trading.callback_query(F.data == "start_trading")
async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> None: async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> None:
@@ -39,36 +39,41 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non
deals = await get_active_positions_by_symbol( deals = await get_active_positions_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol tg_id=callback_query.from_user.id, symbol=symbol
) )
size = deals.get("size") or 0 position = next((d for d in deals if d.get("symbol") == symbol), None)
position_idx = deals.get("positionIdx") if position:
size = position.get("size", 0)
position_idx = position.get("positionIdx")
else:
size = 0
position_idx = None
if position_idx != 0 and safe_float(size) > 0 and trade_mode == "Merged_Single": if position_idx != 0 and safe_float(size) > 0 and trade_mode == "Merged_Single":
await callback_query.answer( await callback_query.answer(
text="У вас есть активная позиция в режиме хеджирования. " text="У вас есть активная позиция в режиме хеджирования. "
"Открытие сделки в одностороннем режиме невозможно.", "Открытие сделки в одностороннем режиме невозможно.",
) )
return return
if position_idx == 0 and safe_float(size) > 0 and trade_mode == "Both_Sides": if position_idx == 0 and safe_float(size) > 0 and trade_mode == "Both_Sides":
await callback_query.answer( await callback_query.answer(
text="У вас есть активная позиция в одностороннем режиме. " text="У вас есть активная позиция в одностороннем режиме. "
"Открытие сделки в режиме хеджирования невозможно.", "Открытие сделки в режиме хеджирования невозможно.",
) )
return return
if trade_mode == "Merged_Single": if trade_mode == "Merged_Single":
await callback_query.message.edit_text( await callback_query.message.edit_text(
text="Выберите режим торговли:\n\n" text="Выберите режим торговли:\n\n"
"Лонг - все сделки серии открываются на покупку.\n" "Лонг - все сделки серии открываются на покупку.\n"
"Шорт - все сделки серии открываются на продажу.\n" "Шорт - все сделки серии открываются на продажу.\n"
"Свитч - направление каждой сделки серии меняется по переменно.\n", "Свитч - направление каждой сделки серии меняется по переменно.\n",
reply_markup=kbi.merged_start_trading, reply_markup=kbi.merged_start_trading,
) )
else: # trade_mode == "Both_Sides": else: # trade_mode == "Both_Sides":
await callback_query.message.edit_text( await callback_query.message.edit_text(
text="Выберите режим торговли:\n\n" text="Выберите режим торговли:\n\n"
"Лонг - все сделки открываются на покупку.\n" "Лонг - все сделки открываются на покупку.\n"
"Шорт - все сделки открываются на продажу.\n", "Шорт - все сделки открываются на продажу.\n",
reply_markup=kbi.both_start_trading, reply_markup=kbi.both_start_trading,
) )
logger.debug( logger.debug(
@@ -106,8 +111,13 @@ async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) -
deals = await get_active_positions_by_symbol( deals = await get_active_positions_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol tg_id=callback_query.from_user.id, symbol=symbol
) )
size = deals.get("size") or 0 position = next((d for d in deals if d.get("symbol") == symbol), None)
position_idx = deals.get("positionIdx") if position:
size = position.get("size", 0)
position_idx = position.get("positionIdx")
else:
size = 0
position_idx = None
if position_idx == 0 and safe_float(size) > 0: if position_idx == 0 and safe_float(size) > 0:
await callback_query.answer( await callback_query.answer(
@@ -120,19 +130,22 @@ async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) -
) )
timer_start = conditional_data.timer_start timer_start = conditional_data.timer_start
if callback_query.from_user.id in user_trade_tasks: cancel_start_task_merged(user_id=callback_query.from_user.id)
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(): async def delay_start():
if timer_start > 0: if timer_start > 0:
await callback_query.message.edit_text( await callback_query.message.edit_text(
text=f"Торговля будет запущена с задержкой {timer_start} мин.", text=f"Торговля будет запущена с задержкой {timer_start} мин.",
reply_markup=kbi.cancel_timer, reply_markup=kbi.cancel_timer_merged,
)
await rq.set_start_timer(
tg_id=callback_query.from_user.id, timer_start=0
) )
await asyncio.sleep(timer_start * 60) await asyncio.sleep(timer_start * 60)
await rq.set_auto_trading(
tg_id=callback_query.from_user.id, symbol=symbol, auto_trading=True, side=side
)
res = await start_trading_cycle( res = await start_trading_cycle(
tg_id=callback_query.from_user.id, tg_id=callback_query.from_user.id,
side=side, side=side,
@@ -146,27 +159,24 @@ async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) -
"estimated will trigger liq": "Лимитный ордер может вызвать мгновенную ликвидацию. Проверьте параметры ордера.", "estimated will trigger liq": "Лимитный ордер может вызвать мгновенную ликвидацию. Проверьте параметры ордера.",
"ab not enough for new order": "Недостаточно средств для создания нового ордера", "ab not enough for new order": "Недостаточно средств для создания нового ордера",
"InvalidRequestError": "Произошла ошибка при запуске торговли.", "InvalidRequestError": "Произошла ошибка при запуске торговли.",
"Order does not meet minimum order value": "Сумма ордера не sufficientдля запуска торговли", "Order does not meet minimum order value": "Сумма ордера не достаточна для запуска торговли",
"position idx not match position mode": "Торговля уже запущена в режиме хеджирования на продажу для данного инструмента", "position idx not match position mode": "Торговля уже запущена в режиме хеджирования на продажу для данного инструмента",
"Qty invalid": "Некорректное значение ордера для данного инструмента", "Qty invalid": "Некорректное значение ордера для данного инструмента",
} }
if res == "OK": 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 callback_query.answer(text="Торговля запущена")
await state.clear() await state.clear()
else: else:
# Получаем сообщение из таблицы, или дефолтный текст await rq.set_auto_trading(
tg_id=callback_query.from_user.id, symbol=symbol, auto_trading=False, side=side
)
text = error_messages.get(res, "Произошла ошибка при запуске торговли") text = error_messages.get(res, "Произошла ошибка при запуске торговли")
await callback_query.answer(text=text) await callback_query.answer(text=text)
await callback_query.message.edit_text("Запуск торговли...")
task = asyncio.create_task(delay_start()) task = asyncio.create_task(delay_start())
user_trade_tasks[callback_query.from_user.id] = task await add_start_task_merged(user_id=callback_query.from_user.id, task=task)
except Exception as e: except Exception as e:
await callback_query.answer(text="Произошла ошибка при запуске торговли") await callback_query.answer(text="Произошла ошибка при запуске торговли")
@@ -181,7 +191,7 @@ async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) -
@router_start_trading.callback_query(lambda c: c.data == "switch") @router_start_trading.callback_query(lambda c: c.data == "switch")
async def start_trading_switch( async def start_trading_switch(
callback_query: CallbackQuery, state: FSMContext callback_query: CallbackQuery, state: FSMContext
) -> None: ) -> None:
""" """
Handles the "switch" callback query. Handles the "switch" callback query.
@@ -194,10 +204,10 @@ async def start_trading_switch(
await state.clear() await state.clear()
await callback_query.message.edit_text( await callback_query.message.edit_text(
text="Выберите направление первой сделки серии:\n\n" text="Выберите направление первой сделки серии:\n\n"
"Лонг - открывается первая сделка на покупку.\n" "Лонг - открывается первая сделка на покупку.\n"
"Шорт - открывается первая сделка на продажу.\n" "Шорт - открывается первая сделка на продажу.\n"
"По направлению - сделка открывается в направлении последней сделки предыдущей серии.\n" "По направлению - сделка открывается в направлении последней сделки предыдущей серии.\n"
"Противоположно - сделка открывается в противоположном направлении последней сделки предыдущей серии.\n", "Противоположно - сделка открывается в противоположном направлении последней сделки предыдущей серии.\n",
reply_markup=kbi.switch_side, reply_markup=kbi.switch_side,
) )
except Exception as e: except Exception as e:
@@ -211,7 +221,7 @@ async def start_trading_switch(
@router_start_trading.callback_query( @router_start_trading.callback_query(
lambda c: c.data lambda c: c.data
in {"switch_long", "switch_short", "switch_direction", "switch_opposite"} in {"switch_long", "switch_short", "switch_direction", "switch_opposite"}
) )
async def start_switch(callback_query: CallbackQuery, state: FSMContext) -> None: async def start_switch(callback_query: CallbackQuery, state: FSMContext) -> None:
""" """
@@ -249,8 +259,13 @@ async def start_switch(callback_query: CallbackQuery, state: FSMContext) -> None
deals = await get_active_positions_by_symbol( deals = await get_active_positions_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol tg_id=callback_query.from_user.id, symbol=symbol
) )
size = deals.get("size") or 0 position = next((d for d in deals if d.get("symbol") == symbol), None)
position_idx = deals.get("positionIdx") if position:
size = position.get("size", 0)
position_idx = position.get("positionIdx")
else:
size = 0
position_idx = None
if position_idx == 1 and safe_float(size) > 0 and side == "Buy": if position_idx == 1 and safe_float(size) > 0 and side == "Buy":
await callback_query.answer( await callback_query.answer(
@@ -269,19 +284,21 @@ async def start_switch(callback_query: CallbackQuery, state: FSMContext) -> None
) )
timer_start = conditional_data.timer_start timer_start = conditional_data.timer_start
if callback_query.from_user.id in user_trade_tasks: cancel_start_task_switch(user_id=callback_query.from_user.id)
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(): async def delay_start():
if timer_start > 0: if timer_start > 0:
await callback_query.message.edit_text( await callback_query.message.edit_text(
text=f"Торговля будет запущена с задержкой {timer_start} мин.", text=f"Торговля будет запущена с задержкой {timer_start} мин.",
reply_markup=kbi.cancel_timer, reply_markup=kbi.cancel_timer_switch,
)
await rq.set_start_timer(
tg_id=callback_query.from_user.id, timer_start=0
) )
await asyncio.sleep(timer_start * 60) await asyncio.sleep(timer_start * 60)
await rq.set_auto_trading(
tg_id=callback_query.from_user.id, symbol=symbol, auto_trading=True, side=side
)
res = await start_trading_cycle( res = await start_trading_cycle(
tg_id=callback_query.from_user.id, tg_id=callback_query.from_user.id,
side=side, side=side,
@@ -295,26 +312,24 @@ async def start_switch(callback_query: CallbackQuery, state: FSMContext) -> None
"estimated will trigger liq": "Лимитный ордер может вызвать мгновенную ликвидацию. Проверьте параметры ордера.", "estimated will trigger liq": "Лимитный ордер может вызвать мгновенную ликвидацию. Проверьте параметры ордера.",
"ab not enough for new order": "Недостаточно средств для создания нового ордера", "ab not enough for new order": "Недостаточно средств для создания нового ордера",
"InvalidRequestError": "Произошла ошибка при запуске торговли.", "InvalidRequestError": "Произошла ошибка при запуске торговли.",
"Order does not meet minimum order value": "Сумма ордера не sufficientдля запуска торговли", "Order does not meet minimum order value": "Сумма ордера не достаточна для запуска торговли",
"position idx not match position mode": "Торговля уже запущена в режиме хеджирования на продажу для данного инструмента", "position idx not match position mode": "Торговля уже запущена в режиме хеджирования на продажу для данного инструмента",
"Qty invalid": "Некорректное значение ордера для данного инструмента", "Qty invalid": "Некорректное значение ордера для данного инструмента",
} }
if res == "OK": 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 callback_query.answer(text="Торговля запущена")
await state.clear() await state.clear()
else: else:
await rq.set_auto_trading(
tg_id=callback_query.from_user.id, symbol=symbol, auto_trading=False, side=side
)
text = error_messages.get(res, "Произошла ошибка при запуске торговли") text = error_messages.get(res, "Произошла ошибка при запуске торговли")
await callback_query.answer(text=text) await callback_query.answer(text=text)
await callback_query.message.edit_text("Запуск торговли...")
task = asyncio.create_task(delay_start()) task = asyncio.create_task(delay_start())
user_trade_tasks[callback_query.from_user.id] = task await add_start_task_switch(user_id=callback_query.from_user.id, task=task)
except asyncio.CancelledError: except asyncio.CancelledError:
logger.error("Cancelled timer for user %s", callback_query.from_user.id) logger.error("Cancelled timer for user %s", callback_query.from_user.id)
except Exception as e: except Exception as e:
@@ -326,9 +341,9 @@ async def start_switch(callback_query: CallbackQuery, state: FSMContext) -> None
) )
@router_start_trading.callback_query(F.data == "cancel_timer") @router_start_trading.callback_query(lambda c: c.data == "cancel_timer_merged" or c.data == "cancel_timer_switch")
async def cancel_start_trading( async def cancel_start_trading(
callback_query: CallbackQuery, state: FSMContext callback_query: CallbackQuery, state: FSMContext
) -> None: ) -> None:
""" """
Handles the "cancel_timer" callback query. Handles the "cancel_timer" callback query.
@@ -338,21 +353,12 @@ async def cancel_start_trading(
:return: None :return: None
""" """
try: try:
task = user_trade_tasks.get(callback_query.from_user.id) await state.clear()
if task and not task.done(): if callback_query.data == "cancel_timer_merged":
task.cancel() cancel_start_task_merged(user_id=callback_query.from_user.id)
try: elif callback_query.data == "cancel_timer_switch":
await task cancel_start_task_switch(user_id=callback_query.from_user.id)
except asyncio.CancelledError: await callback_query.message.edit_text(text="Запуск торговли отменен", reply_markup=kbi.profile_bybit)
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: except Exception as e:
await callback_query.answer("Произошла ошибка при отмене запуска торговли") await callback_query.answer("Произошла ошибка при отмене запуска торговли")
logger.error( logger.error(
@@ -360,5 +366,3 @@ async def cancel_start_trading(
callback_query.from_user.id, callback_query.from_user.id,
e, e,
) )
finally:
await state.clear()

View File

@@ -7,7 +7,7 @@ from aiogram.types import CallbackQuery
import app.telegram.keyboards.inline as kbi import app.telegram.keyboards.inline as kbi
import database.request as rq import database.request as rq
from app.bybit.close_positions import cancel_order, close_position from app.telegram.tasks.tasks import add_stop_task, cancel_stop_task
from logger_helper.logger_helper import LOGGING_CONFIG from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG) logging.config.dictConfig(LOGGING_CONFIG)
@@ -15,50 +15,46 @@ logger = logging.getLogger("stop_trading")
router_stop_trading = Router(name="stop_trading") router_stop_trading = Router(name="stop_trading")
user_trade_tasks = {}
@router_stop_trading.callback_query(F.data == "stop_trading") @router_stop_trading.callback_query(F.data == "stop_trading")
async def stop_trading(callback_query: CallbackQuery, state: FSMContext): async def stop_all_trading(callback_query: CallbackQuery, state: FSMContext):
try: try:
await state.clear() await state.clear()
if callback_query.from_user.id in user_trade_tasks: cancel_stop_task(callback_query.from_user.id)
task = user_trade_tasks[callback_query.from_user.id]
if not task.done():
task.cancel()
del user_trade_tasks[callback_query.from_user.id]
conditional_data = await rq.get_user_conditional_settings( conditional_data = await rq.get_user_conditional_settings(
tg_id=callback_query.from_user.id tg_id=callback_query.from_user.id
) )
timer_end = conditional_data.timer_end timer_end = conditional_data.timer_end
symbols = await rq.get_all_symbols(tg_id=callback_query.from_user.id)
async def delay_start(): async def delay_start():
if timer_end > 0: if timer_end > 0:
await callback_query.message.edit_text( await callback_query.message.edit_text(
text=f"Торговля будет остановлена с задержкой {timer_end} мин.", text=f"Торговля будет остановлена с задержкой {timer_end} мин.",
reply_markup=kbi.cancel_timer, reply_markup=kbi.cancel_timer_stop,
) )
await rq.set_stop_timer(tg_id=callback_query.from_user.id, timer_end=0)
await asyncio.sleep(timer_end * 60) await asyncio.sleep(timer_end * 60)
for symbol in symbols: user_auto_trading_list = await rq.get_all_user_auto_trading(tg_id=callback_query.from_user.id)
auto_trading_data = await rq.get_user_auto_trading(
tg_id=callback_query.from_user.id, symbol=symbol
)
if auto_trading_data is not None and auto_trading_data.auto_trading:
await close_position(tg_id=callback_query.from_user.id, symbol=symbol)
await cancel_order(tg_id=callback_query.from_user.id, symbol=symbol)
await rq.set_auto_trading(
tg_id=callback_query.from_user.id, symbol=symbol, auto_trading=False
)
await callback_query.answer(text="Торговля остановлена") for active_auto_trading in user_auto_trading_list:
await rq.set_stop_timer(tg_id=callback_query.from_user.id, timer_end=0) if active_auto_trading.auto_trading:
symbol = active_auto_trading.symbol
get_side = active_auto_trading.side
req = await rq.set_auto_trading(
tg_id=callback_query.from_user.id, symbol=symbol, auto_trading=False, side=get_side
)
if not req:
await callback_query.edit_text(text="Произошла ошибка при остановке торговли",
reply_markup=kbi.profile_bybit)
return
await callback_query.message.edit_text(text="Торговля остановлена", reply_markup=kbi.profile_bybit)
task = asyncio.create_task(delay_start()) task = asyncio.create_task(delay_start())
user_trade_tasks[callback_query.from_user.id] = task await add_stop_task(user_id=callback_query.from_user.id, task=task)
logger.debug( logger.debug(
"Command stop_trading processed successfully for user: %s", "Command stop_trading processed successfully for user: %s",
@@ -71,3 +67,18 @@ async def stop_trading(callback_query: CallbackQuery, state: FSMContext):
callback_query.from_user.id, callback_query.from_user.id,
e, e,
) )
@router_stop_trading.callback_query(F.data == "cancel_timer_stop")
async def cancel_stop_trading(callback_query: CallbackQuery, state: FSMContext):
try:
await state.clear()
cancel_stop_task(callback_query.from_user.id)
await callback_query.message.edit_text(text="Таймер отменён.", reply_markup=kbi.profile_bybit)
except Exception as e:
await callback_query.answer(text="Произошла ошибка при отмене остановки торговли")
logger.error(
"Error processing command cancel_timer_stop for user %s: %s",
callback_query.from_user.id,
e,
)

View File

@@ -29,9 +29,14 @@ async def set_tp_sl_handler(callback_query: CallbackQuery, state: FSMContext) ->
""" """
try: try:
await state.clear() await state.clear()
symbol = callback_query.data.split("_", 3)[3] data = callback_query.data
parts = data.split("_")
symbol = parts[3]
position_idx = int(parts[4])
await state.set_state(SetTradingStopState.take_profit_state) await state.set_state(SetTradingStopState.take_profit_state)
await state.update_data(symbol=symbol) await state.update_data(symbol=symbol)
await state.update_data(position_idx=position_idx)
msg = await callback_query.message.answer( msg = await callback_query.message.answer(
text="Введите тейк-профит:", reply_markup=kbi.cancel text="Введите тейк-профит:", reply_markup=kbi.cancel
) )
@@ -142,11 +147,13 @@ async def set_stop_loss_handler(message: Message, state: FSMContext) -> None:
data = await state.get_data() data = await state.get_data()
symbol = data["symbol"] symbol = data["symbol"]
take_profit = data["take_profit"] take_profit = data["take_profit"]
position_idx = data["position_idx"]
res = await set_tp_sl_for_position( res = await set_tp_sl_for_position(
tg_id=message.from_user.id, tg_id=message.from_user.id,
symbol=symbol, symbol=symbol,
take_profit_price=float(take_profit), take_profit_price=float(take_profit),
stop_loss_price=float(stop_loss), stop_loss_price=float(stop_loss),
position_idx=position_idx,
) )
if res: if res:

View File

@@ -39,7 +39,7 @@ main_menu = InlineKeyboardMarkup(
[InlineKeyboardButton(text="Начать торговлю", callback_data="start_trading")], [InlineKeyboardButton(text="Начать торговлю", callback_data="start_trading")],
[ [
InlineKeyboardButton( InlineKeyboardButton(
text="Остановить торговлю", callback_data="stop_trading" text="Остановить торговлю", callback_data="trading_stop"
) )
], ],
] ]
@@ -327,58 +327,57 @@ change_position = InlineKeyboardMarkup(
) )
def create_active_positions_keyboard(symbols): def create_active_positions_keyboard(symbols: list):
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
for sym in symbols: for sym, side in symbols:
builder.button(text=f"{sym}", callback_data=f"get_position_{sym}") builder.button(text=f"{sym}:{side}", callback_data=f"get_position_{sym}_{side}")
builder.button(text="Назад", callback_data="my_deals") builder.button(text="Назад", callback_data="my_deals")
builder.button(text="На главную", callback_data="profile_bybit") builder.button(text="На главную", callback_data="profile_bybit")
builder.adjust(2) builder.adjust(2)
return builder.as_markup() return builder.as_markup()
def make_close_position_keyboard(symbol_pos: str): def make_close_position_keyboard(symbol_pos: str, side: str, position_idx: int, qty: int):
return InlineKeyboardMarkup( return InlineKeyboardMarkup(
inline_keyboard=[ inline_keyboard=[
[ [
InlineKeyboardButton( InlineKeyboardButton(
text="Закрыть позицию", callback_data=f"close_position_{symbol_pos}" text="Закрыть позицию", callback_data=f"close_position_{symbol_pos}_{side}_{position_idx}_{qty}"
) )
], ],
[ [
InlineKeyboardButton( InlineKeyboardButton(
text="Установить TP/SL", callback_data=f"pos_tp_sl_{symbol_pos}" text="Установить TP/SL", callback_data=f"pos_tp_sl_{symbol_pos}_{position_idx}"
) )
], ],
[ [
InlineKeyboardButton(text="Назад", callback_data="my_deals"), InlineKeyboardButton(text="Назад", callback_data="change_position"),
InlineKeyboardButton(text="На главную", callback_data="profile_bybit"), InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
], ],
] ]
) )
def create_active_orders_keyboard(orders): def create_active_orders_keyboard(orders:list):
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
for order in orders: for order, side in orders:
builder.button(text=f"{order}", callback_data=f"get_order_{order}") builder.button(text=f"{order}", callback_data=f"get_order_{order}_{side}")
builder.button(text="Закрыть все ордера", callback_data="cancel_all_orders")
builder.button(text="Назад", callback_data="my_deals") builder.button(text="Назад", callback_data="my_deals")
builder.button(text="На главную", callback_data="profile_bybit") builder.button(text="На главную", callback_data="profile_bybit")
builder.adjust(2) builder.adjust(2)
return builder.as_markup() return builder.as_markup()
def make_close_orders_keyboard(symbol_order: str): def make_close_orders_keyboard(symbol_order: str, order_id: str):
return InlineKeyboardMarkup( return InlineKeyboardMarkup(
inline_keyboard=[ inline_keyboard=[
[ [
InlineKeyboardButton( InlineKeyboardButton(
text="Закрыть ордер", callback_data=f"close_order_{symbol_order}" text="Закрыть ордер", callback_data=f"close_order_{symbol_order}_{order_id}"
) )
], ],
[ [
InlineKeyboardButton(text="Назад", callback_data="my_deals"), InlineKeyboardButton(text="Назад", callback_data="open_orders"),
InlineKeyboardButton(text="На главную", callback_data="profile_bybit"), InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
], ],
] ]
@@ -438,12 +437,31 @@ back_to_start_trading = InlineKeyboardMarkup(
] ]
) )
cancel_timer = InlineKeyboardMarkup( cancel_timer_merged = InlineKeyboardMarkup(
inline_keyboard=[ inline_keyboard=[
[InlineKeyboardButton(text="Отменить таймер", callback_data="cancel_timer")], [InlineKeyboardButton(text="Отменить таймер", callback_data="cancel_timer_merged")],
[ [
InlineKeyboardButton(text="Назад", callback_data="conditions"),
InlineKeyboardButton(text="На главную", callback_data="profile_bybit"), InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
], ],
] ]
) )
cancel_timer_switch = InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(text="Отменить таймер", callback_data="cancel_timer_switch")],
[
InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
],
]
)
# STOP TRADING
cancel_timer_stop = InlineKeyboardMarkup(
inline_keyboard=[
[InlineKeyboardButton(text="Отменить таймер", callback_data="cancel_timer_stop")],
[
InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
],
]
)

View File

@@ -166,7 +166,6 @@ class UserDeals(Base):
switch_side_mode = Column(Boolean, nullable=True) switch_side_mode = Column(Boolean, nullable=True)
limit_price = Column(Float, nullable=True) limit_price = Column(Float, nullable=True)
trigger_price = Column(Float, nullable=True) trigger_price = Column(Float, nullable=True)
fee = Column(Float, nullable=True)
user = relationship("User", back_populates="user_deals") user = relationship("User", back_populates="user_deals")
@@ -185,9 +184,7 @@ class UserAutoTrading(Base):
nullable=False) nullable=False)
symbol = Column(String, nullable=True) symbol = Column(String, nullable=True)
auto_trading = Column(Boolean, nullable=True) auto_trading = Column(Boolean, nullable=True)
side = Column(String, nullable=True)
fee = Column(Float, nullable=True)
user = relationship("User", back_populates="user_auto_trading") user = relationship("User", back_populates="user_auto_trading")
__table_args__ = (
UniqueConstraint('user_id', 'symbol', name='uq_user_auto_trading_symbol'),
)

View File

@@ -446,7 +446,7 @@ async def set_leverage(tg_id: int, leverage: str) -> bool:
async def set_leverage_to_buy_and_sell( async def set_leverage_to_buy_and_sell(
tg_id: int, leverage_to_buy: str, leverage_to_sell: str tg_id: int, leverage_to_buy: str, leverage_to_sell: str
) -> bool: ) -> bool:
""" """
Set leverage for a user in the database. Set leverage for a user in the database.
@@ -1066,26 +1066,26 @@ async def set_stop_timer(tg_id: int, timer_end: int) -> bool:
# USER DEALS # USER DEALS
async def set_user_deal( async def set_user_deal(
tg_id: int, tg_id: int,
symbol: str, symbol: str,
last_side: str, last_side: str,
current_step: int, current_step: int,
trade_mode: str, trade_mode: str,
margin_type: str, margin_type: str,
leverage: str, leverage: str,
leverage_to_buy: str, leverage_to_buy: str,
leverage_to_sell: str, leverage_to_sell: str,
order_type: str, order_type: str,
conditional_order_type: str, conditional_order_type: str,
order_quantity: float, order_quantity: float,
limit_price: float, limit_price: float,
trigger_price: float, trigger_price: float,
martingale_factor: float, martingale_factor: float,
max_bets_in_series: int, max_bets_in_series: int,
take_profit_percent: int, take_profit_percent: int,
stop_loss_percent: int, stop_loss_percent: int,
max_risk_percent: int, max_risk_percent: int,
switch_side_mode: bool, switch_side_mode: bool,
): ):
""" """
Set the user deal in the database. Set the user deal in the database.
@@ -1249,6 +1249,25 @@ async def set_fee_user_deal_by_symbol(tg_id: int, symbol: str, fee: float):
# USER AUTO TRADING # USER AUTO TRADING
async def get_all_user_auto_trading(tg_id: int):
"""Get all user auto trading from the database asynchronously."""
try:
async with async_session() as session:
result_user = await session.execute(select(User).filter_by(tg_id=tg_id))
user = result_user.scalars().first()
if not user:
return []
result_auto_trading = await session.execute(
select(UserAutoTrading).filter_by(user_id=user.id)
)
auto_trading = result_auto_trading.scalars().all()
return auto_trading
except Exception as e:
logger.error("Error getting auto trading for user %s: %s", tg_id, e)
return []
async def get_user_auto_trading(tg_id: int, symbol: str): async def get_user_auto_trading(tg_id: int, symbol: str):
"""Get user auto trading from the database asynchronously.""" """Get user auto trading from the database asynchronously."""
try: try:
@@ -1268,12 +1287,13 @@ async def get_user_auto_trading(tg_id: int, symbol: str):
return None return None
async def set_auto_trading(tg_id: int, symbol: str, auto_trading: bool) -> bool: async def set_auto_trading(tg_id: int, symbol: str, auto_trading: bool, side: str) -> bool:
""" """
Set the auto trading for a user in the database. Set the auto trading for a user in the database.
:param tg_id: Telegram user ID :param tg_id: Telegram user ID
:param symbol: Symbol :param symbol: Symbol
:param auto_trading: Auto trading :param auto_trading: Auto trading
:param side: Side
:return: bool :return: bool
""" """
try: try:
@@ -1285,7 +1305,7 @@ async def set_auto_trading(tg_id: int, symbol: str, auto_trading: bool) -> bool:
return False return False
result = await session.execute( result = await session.execute(
select(UserAutoTrading).filter_by(user_id=user.id, symbol=symbol) select(UserAutoTrading).filter_by(user_id=user.id, symbol=symbol, side=side)
) )
record = result.scalars().first() record = result.scalars().first()
if record: if record:
@@ -1294,7 +1314,8 @@ async def set_auto_trading(tg_id: int, symbol: str, auto_trading: bool) -> bool:
new_record = UserAutoTrading( new_record = UserAutoTrading(
user_id=user.id, user_id=user.id,
symbol=symbol, symbol=symbol,
auto_trading=auto_trading auto_trading=auto_trading,
side=side
) )
session.add(new_record) session.add(new_record)
await session.commit() await session.commit()
@@ -1302,4 +1323,44 @@ async def set_auto_trading(tg_id: int, symbol: str, auto_trading: bool) -> bool:
return True return True
except Exception as e: except Exception as e:
logger.error("Error setting auto_trading for user %s and symbol %s: %s", tg_id, symbol, e) logger.error("Error setting auto_trading for user %s and symbol %s: %s", tg_id, symbol, e)
return False return False
async def set_fee_user_auto_trading(tg_id: int, symbol: str, side: str, fee: float) -> bool:
"""
Set the fee for a user auto trading in the database.
:param tg_id:
:param symbol:
:param side:
:param fee:
:return:
"""
try:
async with async_session() as session:
result = await session.execute(select(User).filter_by(tg_id=tg_id))
user = result.scalars().first()
if user is None:
logger.error(f"User with tg_id={tg_id} not found")
return False
result = await session.execute(
select(UserAutoTrading).filter_by(user_id=user.id, symbol=symbol, side=side)
)
record = result.scalars().first()
if record:
record.fee = fee
else:
user_fee = UserAutoTrading(
user_id=user.id,
symbol=symbol,
fee=fee,
side=side
)
session.add(user_fee)
await session.commit()
logger.info("Set fee for user %s and symbol %s", tg_id, symbol)
return True
except Exception as e:
logger.error("Error setting user auto trading fee for user %s and symbol %s: %s", tg_id, symbol, e)
return False

View File

@@ -110,6 +110,11 @@ LOGGING_CONFIG = {
"level": "DEBUG", "level": "DEBUG",
"propagate": False, "propagate": False,
}, },
"stop_trading": {
"handlers": ["console", "timed_rotating_file", "error_file"],
"level": "DEBUG",
"propagate": False,
},
"changing_the_symbol": { "changing_the_symbol": {
"handlers": ["console", "timed_rotating_file", "error_file"], "handlers": ["console", "timed_rotating_file", "error_file"],
"level": "DEBUG", "level": "DEBUG",
@@ -135,5 +140,10 @@ LOGGING_CONFIG = {
"level": "DEBUG", "level": "DEBUG",
"propagate": False, "propagate": False,
}, },
"tasks": {
"handlers": ["console", "timed_rotating_file", "error_file"],
"level": "DEBUG",
"propagate": False,
},
}, },
} }