diff --git a/.gitignore b/.gitignore index 8fb70e2..91631b6 100644 --- a/.gitignore +++ b/.gitignore @@ -146,9 +146,8 @@ myenv ENV/ env.bak/ venv.bak/ -/alembic/versions -/alembic alembic.ini +/alembic # Spyder project settings .spyderproject .spyproject diff --git a/app/bybit/open_positions.py b/app/bybit/open_positions.py index 5e5eed5..7b8d3b4 100644 --- a/app/bybit/open_positions.py +++ b/app/bybit/open_positions.py @@ -1,89 +1,113 @@ import logging.config +import math from pybit.exceptions import InvalidRequestError import database.request as rq from app.bybit import get_bybit_client -from app.bybit.get_functions.get_balance import get_balance from app.bybit.get_functions.get_instruments_info import get_instruments_info from app.bybit.get_functions.get_tickers import get_tickers from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG -from app.bybit.set_functions.set_leverage import ( - set_leverage, - set_leverage_to_buy_and_sell, -) +from app.bybit.set_functions.set_leverage import set_leverage from app.bybit.set_functions.set_margin_mode import set_margin_mode -from app.bybit.set_functions.set_switch_position_mode import set_switch_position_mode -from app.helper_functions import check_limit_price, get_liquidation_price, safe_float +from app.helper_functions import get_liquidation_price, safe_float logging.config.dictConfig(LOGGING_CONFIG) logger = logging.getLogger("open_positions") async def start_trading_cycle( - tg_id: int, side: str, switch_side_mode: bool + tg_id: int ) -> str | None: """ Start trading cycle :param tg_id: Telegram user ID - :param side: Buy or Sell - :param switch_side_mode: switch_side_mode """ try: + client = await get_bybit_client(tg_id=tg_id) symbol = await rq.get_user_symbol(tg_id=tg_id) additional_data = await rq.get_user_additional_settings(tg_id=tg_id) risk_management_data = await rq.get_user_risk_management(tg_id=tg_id) - + commission_fee = risk_management_data.commission_fee + user_deals_data = await rq.get_user_deal_by_symbol( + tg_id=tg_id, symbol=symbol + ) trade_mode = additional_data.trade_mode + switch_side = additional_data.switch_side margin_type = additional_data.margin_type leverage = additional_data.leverage - leverage_to_buy = additional_data.leverage_to_buy - leverage_to_sell = additional_data.leverage_to_sell - order_type = additional_data.order_type - conditional_order_type = additional_data.conditional_order_type order_quantity = additional_data.order_quantity - limit_price = additional_data.limit_price trigger_price = additional_data.trigger_price martingale_factor = additional_data.martingale_factor max_bets_in_series = additional_data.max_bets_in_series take_profit_percent = risk_management_data.take_profit_percent stop_loss_percent = risk_management_data.stop_loss_percent - max_risk_percent = risk_management_data.max_risk_percent - mode = 3 if trade_mode == "Both_Sides" else 0 - await set_switch_position_mode(tg_id=tg_id, symbol=symbol, mode=mode) - await set_margin_mode(tg_id=tg_id, margin_mode=margin_type) - if trade_mode == "Both_Sides" and margin_type == "ISOLATED_MARGIN": - await set_leverage_to_buy_and_sell( - tg_id=tg_id, - symbol=symbol, - leverage_to_buy=leverage_to_buy, - leverage_to_sell=leverage_to_sell, - ) + get_side = "Buy" + + if user_deals_data: + get_side = user_deals_data.last_side or "Buy" + + if trade_mode == "Switch": + if switch_side == "По направлению": + side = get_side + else: + if get_side == "Buy": + side = "Sell" + else: + side = "Buy" else: - await set_leverage( - tg_id=tg_id, - symbol=symbol, - leverage=leverage, + if trade_mode == "Long": + side = "Buy" + else: + side = "Sell" + + # Get fee rates + fee_info = client.get_fee_rates(category="linear", symbol=symbol) + + # Check if commission fee is enabled + commission_fee_percent = 0.0 + if commission_fee == "Yes_commission_fee": + commission_fee_percent = safe_float( + fee_info["result"]["list"][0]["takerFeeRate"] ) + get_ticker = await get_tickers(tg_id, symbol=symbol) + price_symbol = safe_float(get_ticker.get("lastPrice")) or 0 + instruments_info = await get_instruments_info(tg_id=tg_id, symbol=symbol) + qty_step_str = instruments_info.get("lotSizeFilter").get("qtyStep") + qty_step = safe_float(qty_step_str) + qty = safe_float(order_quantity) / safe_float(price_symbol) + decimals = abs(int(round(math.log10(qty_step)))) + qty_formatted = math.floor(qty / qty_step) * qty_step + qty_formatted = round(qty_formatted, decimals) + + if trigger_price > 0: + po_trigger_price = str(trigger_price) + else: + po_trigger_price = None + + price_for_cals = trigger_price if po_trigger_price is not None else price_symbol + total_commission = price_for_cals * qty_formatted * commission_fee_percent + + await set_margin_mode(tg_id=tg_id, margin_mode=margin_type) + await set_leverage( + tg_id=tg_id, + symbol=symbol, + leverage=leverage, + ) + res = await open_positions( tg_id=tg_id, symbol=symbol, side=side, - order_type=order_type, - conditional_order_type=conditional_order_type, order_quantity=order_quantity, - limit_price=limit_price, trigger_price=trigger_price, - trade_mode=trade_mode, margin_type=margin_type, leverage=leverage, - leverage_to_buy=leverage_to_buy, - leverage_to_sell=leverage_to_sell, take_profit_percent=take_profit_percent, stop_loss_percent=stop_loss_percent, - max_risk_percent=max_risk_percent, + commission_fee_percent=total_commission ) if res == "OK": @@ -95,19 +119,13 @@ async def start_trading_cycle( trade_mode=trade_mode, margin_type=margin_type, leverage=leverage, - leverage_to_buy=leverage_to_buy, - leverage_to_sell=leverage_to_sell, - order_type="Market", - conditional_order_type=conditional_order_type, order_quantity=order_quantity, - limit_price=limit_price, trigger_price=trigger_price, martingale_factor=martingale_factor, max_bets_in_series=max_bets_in_series, take_profit_percent=take_profit_percent, stop_loss_percent=stop_loss_percent, - max_risk_percent=max_risk_percent, - switch_side_mode=switch_side_mode, + base_quantity=order_quantity ) return "OK" return ( @@ -124,6 +142,7 @@ async def start_trading_cycle( "position idx not match position mode", "Qty invalid", "The number of contracts exceeds maximum limit allowed", + "The number of contracts exceeds minimum limit allowed" } else None ) @@ -134,43 +153,30 @@ async def start_trading_cycle( async def trading_cycle( - tg_id: int, symbol: str, reverse_side: str, size: str + tg_id: int, symbol: str, reverse_side: str ) -> str | None: try: user_deals_data = await rq.get_user_deal_by_symbol(tg_id=tg_id, symbol=symbol) + user_auto_trading_data = await rq.get_user_auto_trading(tg_id=tg_id, symbol=symbol) + total_fee = user_auto_trading_data.total_fee trade_mode = user_deals_data.trade_mode - order_type = user_deals_data.order_type - conditional_order_type = user_deals_data.conditional_order_type margin_type = user_deals_data.margin_type leverage = user_deals_data.leverage - leverage_to_buy = user_deals_data.leverage_to_buy - leverage_to_sell = user_deals_data.leverage_to_sell - limit_price = user_deals_data.limit_price - trigger_price = user_deals_data.trigger_price + trigger_price = 0 take_profit_percent = user_deals_data.take_profit_percent stop_loss_percent = user_deals_data.stop_loss_percent - max_risk_percent = user_deals_data.max_risk_percent max_bets_in_series = user_deals_data.max_bets_in_series martingale_factor = user_deals_data.martingale_factor current_step = user_deals_data.current_step - switch_side_mode = user_deals_data.switch_side_mode + order_quantity = user_deals_data.order_quantity + base_quantity = user_deals_data.base_quantity - mode = 3 if trade_mode == "Both_Sides" else 0 - await set_switch_position_mode(tg_id=tg_id, symbol=symbol, mode=mode) await set_margin_mode(tg_id=tg_id, margin_mode=margin_type) - if trade_mode == "Both_Sides" and margin_type == "ISOLATED_MARGIN": - await set_leverage_to_buy_and_sell( - tg_id=tg_id, - symbol=symbol, - leverage_to_buy=leverage_to_buy, - leverage_to_sell=leverage_to_sell, - ) - else: - await set_leverage( - tg_id=tg_id, - symbol=symbol, - leverage=leverage, - ) + await set_leverage( + tg_id=tg_id, + symbol=symbol, + leverage=leverage, + ) if reverse_side == "Buy": real_side = "Sell" @@ -179,11 +185,11 @@ async def trading_cycle( side = real_side - if switch_side_mode: + if trade_mode == "Switch": side = "Sell" if real_side == "Buy" else "Buy" - next_quantity = safe_float(size) * ( - safe_float(martingale_factor) ** current_step + next_quantity = safe_float(order_quantity) * ( + safe_float(martingale_factor) ) current_step += 1 @@ -194,19 +200,13 @@ async def trading_cycle( tg_id=tg_id, symbol=symbol, side=side, - order_type="Market", - conditional_order_type=conditional_order_type, order_quantity=next_quantity, - limit_price=limit_price, trigger_price=trigger_price, - trade_mode=trade_mode, margin_type=margin_type, leverage=leverage, - leverage_to_buy=leverage_to_buy, - leverage_to_sell=leverage_to_sell, take_profit_percent=take_profit_percent, stop_loss_percent=stop_loss_percent, - max_risk_percent=max_risk_percent, + commission_fee_percent=total_fee ) if res == "OK": @@ -218,19 +218,13 @@ async def trading_cycle( trade_mode=trade_mode, margin_type=margin_type, leverage=leverage, - leverage_to_buy=leverage_to_buy, - leverage_to_sell=leverage_to_sell, - order_type=order_type, - conditional_order_type=conditional_order_type, order_quantity=next_quantity, - limit_price=limit_price, trigger_price=trigger_price, martingale_factor=martingale_factor, max_bets_in_series=max_bets_in_series, take_profit_percent=take_profit_percent, stop_loss_percent=stop_loss_percent, - max_risk_percent=max_risk_percent, - switch_side_mode=switch_side_mode, + base_quantity=base_quantity ) return "OK" @@ -255,123 +249,56 @@ async def open_positions( tg_id: int, side: str, symbol: str, - order_type: str, - conditional_order_type: str, order_quantity: float, - limit_price: float, trigger_price: float, - trade_mode: str, margin_type: str, leverage: str, - leverage_to_buy: str, - leverage_to_sell: str, take_profit_percent: float, stop_loss_percent: float, - max_risk_percent: float, + commission_fee_percent: float ) -> str | None: try: client = await get_bybit_client(tg_id=tg_id) - - risk_management_data = await rq.get_user_risk_management(tg_id=tg_id) - commission_fee = risk_management_data.commission_fee - wallet = await get_balance(tg_id=tg_id) - user_balance = wallet.get("totalWalletBalance", 0) - instruments_resp = await get_instruments_info(tg_id=tg_id, symbol=symbol) - get_order_prices = instruments_resp.get("priceFilter") - min_price = safe_float(get_order_prices.get("minPrice")) - max_price = safe_float(get_order_prices.get("maxPrice")) get_ticker = await get_tickers(tg_id, symbol=symbol) price_symbol = safe_float(get_ticker.get("lastPrice")) or 0 + instruments_info = await get_instruments_info(tg_id=tg_id, symbol=symbol) + qty_step_str = instruments_info.get("lotSizeFilter").get("qtyStep") + qty_step = safe_float(qty_step_str) + qty = safe_float(order_quantity) / safe_float(price_symbol) + decimals = abs(int(round(math.log10(qty_step)))) + qty_formatted = math.floor(qty / qty_step) * qty_step + qty_formatted = round(qty_formatted, decimals) - if order_type == "Conditional": + if trigger_price > 0: po_trigger_price = str(trigger_price) trigger_direction = 1 if trigger_price > price_symbol else 2 - if conditional_order_type == "Limit": - error = check_limit_price(limit_price, min_price, max_price) - if error in { - "Limit price is out min price", - "Limit price is out max price", - }: - return error - - order_type = "Limit" - price_for_calc = limit_price - tpsl_mode = "Partial" - else: - order_type = "Market" - price_for_calc = trigger_price - tpsl_mode = "Full" else: - if order_type == "Limit": - error = check_limit_price(limit_price, min_price, max_price) - if error in { - "Limit price is out min price", - "Limit price is out max price", - }: - return error - - price_for_calc = limit_price - tpsl_mode = "Partial" - else: - order_type = "Market" - price_for_calc = price_symbol - tpsl_mode = "Full" po_trigger_price = None trigger_direction = None - if trade_mode == "Both_Sides": - po_position_idx = 1 if side == "Buy" else 2 - if margin_type == "ISOLATED_MARGIN": - get_leverage = safe_float( - leverage_to_buy if side == "Buy" else leverage_to_sell - ) - else: - get_leverage = safe_float(leverage) - else: - po_position_idx = 0 - get_leverage = safe_float(leverage) + get_leverage = safe_float(leverage) - potential_loss = ( - safe_float(order_quantity) - * safe_float(price_for_calc) - * (stop_loss_percent / 100) - ) - adjusted_loss = potential_loss / get_leverage - allowed_loss = safe_float(user_balance) * (max_risk_percent / 100) + price_for_cals = trigger_price if po_trigger_price is not None else price_symbol - if adjusted_loss > allowed_loss: - return "Risk is too high for this trade" - - # Get fee rates - fee_info = client.get_fee_rates(category="linear", symbol=symbol) - - # Check if commission fee is enabled - commission_fee_percent = 0.0 - if commission_fee == "Yes_commission_fee": - commission_fee_percent = safe_float( - fee_info["result"]["list"][0]["takerFeeRate"] - ) - - total_commission = price_for_calc * order_quantity * commission_fee_percent tp_multiplier = 1 + (take_profit_percent / 100) - if total_commission > 0: - tp_multiplier += total_commission + if commission_fee_percent > 0: + tp_multiplier += commission_fee_percent if margin_type == "ISOLATED_MARGIN": liq_long, liq_short = await get_liquidation_price( tg_id=tg_id, - entry_price=price_for_calc, + entry_price=price_for_cals, symbol=symbol, 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_cals > 0: if side == "Buy": - base_tp = price_for_calc + (price_for_calc - liq_long) - take_profit_price = base_tp + total_commission + base_tp = price_for_cals + (price_for_cals - liq_long) + take_profit_price = base_tp + commission_fee_percent else: - base_tp = price_for_calc - (liq_short - price_for_calc) - take_profit_price = base_tp - total_commission + base_tp = price_for_cals - (liq_short - price_for_cals) + take_profit_price = base_tp - commission_fee_percent take_profit_price = max(take_profit_price, 0) else: take_profit_price = None @@ -379,40 +306,38 @@ async def open_positions( stop_loss_price = None else: if side == "Buy": - take_profit_price = price_for_calc * tp_multiplier - stop_loss_price = price_for_calc * (1 - stop_loss_percent / 100) + take_profit_price = price_for_cals * tp_multiplier + stop_loss_price = price_for_cals * (1 - stop_loss_percent / 100) else: - take_profit_price = price_for_calc * ( - 1 - (take_profit_percent / 100) - total_commission + take_profit_price = price_for_cals * ( + 1 - (take_profit_percent / 100) - commission_fee_percent ) - stop_loss_price = price_for_calc * (1 + stop_loss_percent / 100) + stop_loss_price = trigger_price * (1 + stop_loss_percent / 100) take_profit_price = max(take_profit_price, 0) stop_loss_price = max(stop_loss_price, 0) + logger.info("Take profit price: %s", take_profit_price) + logger.info("Stop loss price: %s", stop_loss_price) + logger.info("Commission fee percent: %s", commission_fee_percent) + # Place order order_params = { "category": "linear", "symbol": symbol, "side": side, - "orderType": order_type, - "qty": str(order_quantity), + "orderType": "Market", + "qty": str(qty_formatted), "triggerDirection": trigger_direction, "triggerPrice": po_trigger_price, "triggerBy": "LastPrice", "timeInForce": "GTC", - "positionIdx": po_position_idx, - "tpslMode": tpsl_mode, + "positionIdx": 0, + "tpslMode": "Full", "takeProfit": str(take_profit_price) if take_profit_price else None, "stopLoss": str(stop_loss_price) if stop_loss_price else None, } - if order_type == "Conditional": - if conditional_order_type == "Limit": - order_params["price"] = str(limit_price) - if order_type == "Limit": - order_params["price"] = str(limit_price) - response = client.place_order(**order_params) if response["retCode"] == 0: @@ -430,6 +355,8 @@ async def open_positions( "ab not enough for new order": "ab not enough for new order", "position idx not match position mode": "position idx not match position mode", "Qty invalid": "Qty invalid", + "The number of contracts exceeds maximum limit allowed": "The number of contracts exceeds maximum limit allowed", + "The number of contracts exceeds minimum limit allowed": "The number of contracts exceeds minimum limit allowed", } for key, msg in known_errors.items(): if key in error_text: diff --git a/app/bybit/profile_bybit.py b/app/bybit/profile_bybit.py index 3ec82a7..e930a32 100644 --- a/app/bybit/profile_bybit.py +++ b/app/bybit/profile_bybit.py @@ -6,7 +6,6 @@ from aiogram.types import Message import app.telegram.keyboards.inline as kbi import database.request as rq from app.bybit.get_functions.get_balance import get_balance -from app.bybit.get_functions.get_tickers import get_tickers from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG logging.config.dictConfig(LOGGING_CONFIG) @@ -22,17 +21,14 @@ async def user_profile_bybit(tg_id: int, message: Message, state: FSMContext) -> if wallet: balance = wallet.get("totalWalletBalance", "0") symbol = await rq.get_user_symbol(tg_id=tg_id) - get_tickers_info = await get_tickers(tg_id=tg_id, symbol=symbol) - price_symbol = get_tickers_info.get("lastPrice") or 0 await message.answer( - text=f"💎Ваш профиль Bybit:\n\n" + text=f"💎Ваш профиль:\n\n" f"⚖️ Баланс: {float(balance):,.2f} USD\n" - f"📊Торговая пара: {symbol}\n" - f"$$$ Цена: {float(price_symbol):,.4f}\n\n" + f"📊Торговая пара: {symbol}\n\n" f"Краткая инструкция:\n" f"1. Укажите торговую пару (например: BTCUSDT).\n" f"2. В настройках выставьте все необходимые параметры.\n" - f"3. Нажмите кнопку 'Начать торговлю' и выберите режим торговли.\n", + f"3. Нажмите кнопку 'Начать торговлю'.\n", reply_markup=kbi.main_menu, ) else: diff --git a/app/bybit/telegram_message_handler.py b/app/bybit/telegram_message_handler.py index 6ac14f1..9662eab 100644 --- a/app/bybit/telegram_message_handler.py +++ b/app/bybit/telegram_message_handler.py @@ -4,7 +4,7 @@ import app.telegram.keyboards.inline as kbi import database.request as rq from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG from app.bybit.open_positions import trading_cycle -from app.helper_functions import format_value, safe_float, safe_int +from app.helper_functions import format_value, safe_float logging.config.dictConfig(LOGGING_CONFIG) logger = logging.getLogger("telegram_message_handler") @@ -22,12 +22,6 @@ class TelegramMessageHandler: order_data = message.get("data", [{}])[0] symbol = format_value(order_data.get("symbol")) qty = format_value(order_data.get("qty")) - order_type = format_value(order_data.get("orderType")) - order_type_rus = ( - "Рыночный" - if order_type == "Market" - else "Лимитный" if order_type == "Limit" else "Нет данных" - ) side = format_value(order_data.get("side")) side_rus = ( "Покупка" @@ -40,39 +34,16 @@ class TelegramMessageHandler: take_profit = format_value(order_data.get("takeProfit")) stop_loss = format_value(order_data.get("stopLoss")) - position_idx = safe_int(order_data.get("positionIdx")) - position_idx_rus = ( - "Односторонний" - if position_idx == 0 - else ( - "Покупка в режиме хеджирования" - if position_idx == 1 - else ( - "Продажа в режиме хеджирования" - if position_idx == 2 - else "Нет данных" - ) - ) - ) - status_map = { - "New": "Ордер создан", - "Cancelled": "Ордер отменен", - "Deactivated": "Ордер деактивирован", "Untriggered": "Условный ордер выставлен", } if order_status == "Filled" or order_status not in status_map: return None - status_text = status_map[order_status] - text = ( - f"{status_text}:\n" f"Торговая пара: {symbol}\n" - f"Режим позиции: {position_idx_rus}\n" f"Количество: {qty}\n" - f"Тип ордера: {order_type_rus}\n" f"Движение: {side_rus}\n" ) if price and price != "0": @@ -97,13 +68,6 @@ class TelegramMessageHandler: symbol = format_value(execution.get("symbol")) exec_price = format_value(execution.get("execPrice")) exec_fee = format_value(execution.get("execFee")) - exec_qty = format_value(execution.get("execQty")) - order_type = format_value(execution.get("orderType")) - order_type_rus = ( - "Рыночный" - if order_type == "Market" - else "Лимитный" if order_type == "Limit" else "Нет данных" - ) side = format_value(execution.get("side")) side_rus = ( "Покупка" @@ -113,14 +77,17 @@ class TelegramMessageHandler: 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) + tg_id=tg_id, symbol=symbol, fee=safe_float(exec_fee) ) - if side == "Buy": - res_side = "Sell" - else: - res_side = "Buy" + user_auto_trading = await rq.get_user_auto_trading( - tg_id=tg_id, symbol=symbol, side=res_side + tg_id=tg_id, symbol=symbol + ) + + get_total_fee = user_auto_trading.total_fee + total_fee = safe_float(exec_fee) + safe_float(get_total_fee) + await rq.set_total_fee_user_auto_trading( + tg_id=tg_id, symbol=symbol, total_fee=total_fee ) if user_auto_trading is not None and user_auto_trading.fee is not None: @@ -142,13 +109,15 @@ class TelegramMessageHandler: ) text = f"{header}\n" f"Торговая пара: {symbol}\n" - if safe_float(closed_size) > 0: - text += f"Количество закрытых сделок: {closed_size}\n" + user_deals_data = await rq.get_user_deal_by_symbol( + tg_id=tg_id, symbol=symbol + ) + exec_bet = user_deals_data.order_quantity + base_quantity = user_deals_data.base_quantity text += ( f"Цена исполнения: {exec_price}\n" - f"Количество исполненных сделок: {exec_qty}\n" - f"Тип ордера: {order_type_rus}\n" + f"Текущая ставка: {exec_bet}\n" f"Движение: {side_rus}\n" f"Комиссия за сделку: {exec_fee}\n" ) @@ -175,27 +144,26 @@ class TelegramMessageHandler: await self.telegram_bot.send_message( 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 + tg_id=tg_id, symbol=symbol, auto_trading=False ) - user_deals_data = await rq.get_user_deal_by_symbol( - tg_id=tg_id, symbol=symbol + + await rq.set_total_fee_user_auto_trading( + tg_id=tg_id, symbol=symbol, total_fee=0 + ) + await rq.set_fee_user_auto_trading( + tg_id=tg_id, symbol=symbol, fee=0 + ) + await rq.set_order_quantity( + tg_id=message.from_user.id, order_quantity=base_quantity ) - if user_deals_data and user_deals_data.switch_side_mode: - await rq.set_auto_trading( - tg_id=tg_id, symbol=symbol, auto_trading=False, side=side - ) else: open_order_text = "\n❗️ Сделка закрылась в минус, открываю новую сделку с увеличенной ставкой.\n" await self.telegram_bot.send_message( chat_id=tg_id, text=open_order_text ) res = await trading_cycle( - tg_id=tg_id, symbol=symbol, reverse_side=side, size=closed_size + tg_id=tg_id, symbol=symbol, reverse_side=side ) if res == "OK": @@ -211,27 +179,21 @@ class TelegramMessageHandler: error_text = errors.get( res, "❗️ Не удалось открыть новую сделку" ) - 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 + tg_id=tg_id, symbol=symbol, auto_trading=False ) - user_deals_data = await rq.get_user_deal_by_symbol( - tg_id=tg_id, symbol=symbol + + await rq.set_total_fee_user_auto_trading( + tg_id=tg_id, symbol=symbol, total_fee=0 + ) + await rq.set_fee_user_auto_trading( + tg_id=tg_id, symbol=symbol, fee=0 ) - if user_deals_data and user_deals_data.switch_side_mode: - await rq.set_auto_trading( - tg_id=tg_id, - symbol=symbol, - auto_trading=False, - side=side, - ) await self.telegram_bot.send_message( chat_id=tg_id, text=error_text, reply_markup=kbi.profile_bybit, ) + except Exception as e: logger.error("Error in telegram_message_handler: %s", e) diff --git a/app/bybit/web_socket.py b/app/bybit/web_socket.py index b89fb6e..4c3579f 100644 --- a/app/bybit/web_socket.py +++ b/app/bybit/web_socket.py @@ -46,7 +46,9 @@ class WebSocketBot: self.user_sockets.clear() self.user_messages.clear() self.user_keys.clear() - logger.info("Closed old websocket for user %s due to key change", tg_id) + logger.info( + "Closed old websocket for user %s due to key change", tg_id + ) success = await self.try_connect_user(api_key, api_secret, tg_id) if success: diff --git a/app/helper_functions.py b/app/helper_functions.py index d12f42f..07fd74d 100644 --- a/app/helper_functions.py +++ b/app/helper_functions.py @@ -34,7 +34,7 @@ def is_number(value: str) -> bool: # Convert the string to a float num = float(value) # Check if the number is positive - if num <= 0: + if num < 0: return False # Check if the string contains "+" or "-" if "+" in value or "-" in value: @@ -158,7 +158,7 @@ async def get_liquidation_price( async def calculate_total_budget( - quantity, martingale_factor, max_steps, commission_fee_percent + quantity, martingale_factor, max_steps ) -> float: """ Calculate the total budget for a series of trading steps. @@ -167,7 +167,6 @@ async def calculate_total_budget( quantity (float): The initial quantity of the asset. martingale_factor (float): The factor by which the quantity is multiplied for each step. max_steps (int): The maximum number of trading steps. - commission_fee_percent (float): The commission fee percentage. Returns: float: The total budget for the series of trading steps. @@ -175,12 +174,8 @@ async def calculate_total_budget( total = 0 for step in range(max_steps): set_quantity = quantity * (martingale_factor**step) - if commission_fee_percent == 0: - # Commission fee is not added to the position size - r_quantity = set_quantity - else: - # Commission fee is added to the position size - r_quantity = set_quantity * (1 + 2 * commission_fee_percent) + + r_quantity = set_quantity total += r_quantity return total diff --git a/app/telegram/handlers/changing_the_symbol.py b/app/telegram/handlers/changing_the_symbol.py index f13d9cb..16e5568 100644 --- a/app/telegram/handlers/changing_the_symbol.py +++ b/app/telegram/handlers/changing_the_symbol.py @@ -7,13 +7,12 @@ from aiogram.types import CallbackQuery, Message import app.telegram.keyboards.inline as kbi import database.request as rq from app.bybit.get_functions.get_tickers import get_tickers +from app.bybit.get_functions.get_instruments_info import get_instruments_info from app.bybit.profile_bybit import user_profile_bybit -from app.bybit.set_functions.set_leverage import ( - set_leverage, - set_leverage_to_buy_and_sell, -) +from app.bybit.set_functions.set_leverage import set_leverage + from app.bybit.set_functions.set_margin_mode import set_margin_mode -from app.bybit.set_functions.set_switch_position_mode import set_switch_position_mode +from app.helper_functions import safe_float from app.telegram.states.states import ChangingTheSymbolState from logger_helper.logger_helper import LOGGING_CONFIG @@ -91,12 +90,7 @@ async def set_symbol(message: Message, state: FSMContext) -> None: await rq.create_user_additional_settings(tg_id=message.from_user.id) return - trade_mode = additional_settings.trade_mode or "Merged_Single" - mode = 0 if trade_mode == "Merged_Single" else 3 margin_type = additional_settings.margin_type or "ISOLATED_MARGIN" - leverage = "10" - leverage_to_buy = "10" - leverage_to_sell = "10" ticker = await get_tickers(tg_id=message.from_user.id, symbol=symbol) if ticker is None: @@ -105,6 +99,8 @@ async def set_symbol(message: Message, state: FSMContext) -> None: ) return + instruments_info = await get_instruments_info(tg_id=message.from_user.id, symbol=symbol) + max_leverage = instruments_info.get("leverageFilter").get("maxLeverage") req = await rq.set_user_symbol(tg_id=message.from_user.id, symbol=symbol) if not req: @@ -118,45 +114,18 @@ async def set_symbol(message: Message, state: FSMContext) -> None: tg_id=message.from_user.id, message=message, state=state ) - res = await set_switch_position_mode( - tg_id=message.from_user.id, symbol=symbol, mode=mode - ) - if res == "You have an existing position, so position mode cannot be switched": - if mode == 0: - mode = 3 - else: - mode = 0 - await set_switch_position_mode( - tg_id=message.from_user.id, symbol=symbol, mode=mode - ) - if trade_mode == "Merged_Single": - trade_mode = "Both_Sides" - else: - trade_mode = "Merged_Single" - await rq.set_trade_mode(tg_id=message.from_user.id, trade_mode=trade_mode) - await set_margin_mode(tg_id=message.from_user.id, margin_mode=margin_type) - if margin_type == "ISOLATED_MARGIN": - await set_leverage_to_buy_and_sell( - tg_id=message.from_user.id, - symbol=symbol, - leverage_to_buy=str(leverage_to_buy), - leverage_to_sell=str(leverage_to_sell), - ) - else: - await set_leverage( - tg_id=message.from_user.id, symbol=symbol, leverage=str(leverage) - ) - - await rq.set_leverage(tg_id=message.from_user.id, leverage=str(leverage)) - await rq.set_leverage_to_buy_and_sell( - tg_id=message.from_user.id, - leverage_to_buy=str(leverage_to_buy), - leverage_to_sell=str(leverage_to_sell), + await set_leverage( + tg_id=message.from_user.id, symbol=symbol, leverage=str(max_leverage) ) - await rq.set_limit_price(tg_id=message.from_user.id, limit_price=0) + + await rq.set_leverage(tg_id=message.from_user.id, leverage=str(max_leverage)) + risk_percent = 100 / safe_float(max_leverage) + await rq.set_stop_loss_percent( + tg_id=message.from_user.id, stop_loss_percent=risk_percent) await rq.set_trigger_price(tg_id=message.from_user.id, trigger_price=0) + await rq.set_order_quantity(tg_id=message.from_user.id, order_quantity=1.0) await state.clear() except Exception as e: diff --git a/app/telegram/handlers/handlers_main.py b/app/telegram/handlers/handlers_main.py index 5efb478..fa5a125 100644 --- a/app/telegram/handlers/handlers_main.py +++ b/app/telegram/handlers/handlers_main.py @@ -53,7 +53,7 @@ async def cmd_start(message: Message, state: FSMContext) -> None: await rq.create_user_conditional_settings(tg_id=tg_id) await message.answer( text=f"Добро пожаловать, {full_name}!\n\n" - "PHANTOM TRADING - ваш надежный помощник для автоматизации трейдинга😉", + "Чат-робот для трейдинга - ваш надежный помощник для анализа рынка и принятия взвешенных решений.😉", reply_markup=kbi.connect_the_platform, ) logger.debug( diff --git a/app/telegram/handlers/main_settings/additional_settings.py b/app/telegram/handlers/main_settings/additional_settings.py index 1d1dd4e..df2d1d9 100644 --- a/app/telegram/handlers/main_settings/additional_settings.py +++ b/app/telegram/handlers/main_settings/additional_settings.py @@ -7,14 +7,9 @@ from aiogram.types import CallbackQuery, Message import app.telegram.keyboards.inline as kbi import database.request as rq from app.bybit.get_functions.get_instruments_info import get_instruments_info -from app.bybit.get_functions.get_tickers import get_tickers -from app.bybit.set_functions.set_leverage import ( - set_leverage, - set_leverage_to_buy_and_sell, -) +from app.bybit.set_functions.set_leverage import set_leverage from app.bybit.set_functions.set_margin_mode import set_margin_mode -from app.bybit.set_functions.set_switch_position_mode import set_switch_position_mode -from app.helper_functions import get_base_currency, is_int, is_number, safe_float +from app.helper_functions import is_int, is_number, safe_float from app.telegram.states.states import AdditionalSettingsState from logger_helper.logger_helper import LOGGING_CONFIG @@ -26,7 +21,7 @@ router_additional_settings = Router(name="additional_settings") @router_additional_settings.callback_query(F.data == "trade_mode") async def settings_for_trade_mode( - callback_query: CallbackQuery, state: FSMContext + callback_query: CallbackQuery, state: FSMContext ) -> None: """ Handles the 'trade_mode' callback query. @@ -44,9 +39,10 @@ async def settings_for_trade_mode( try: await state.clear() await callback_query.message.edit_text( - text="Выберите режим позиции:\n\n" - "Односторонний режим — возможно удержание Лонг или же Шорт позиции в контракте.\n\n" - "Хеджирование — возможно удержание обеих Лонг и Шорт позиций в контракте одновременно.", + text="Выберите режим торговли:\n\n" + "Лонг - все сделки серии открываются на покупку.\n" + "Шорт - все сделки серии открываются на продажу.\n" + "Свитч - направление каждой сделки серии меняется по переменно.\n", reply_markup=kbi.trade_mode, ) logger.debug( @@ -66,7 +62,7 @@ async def settings_for_trade_mode( @router_additional_settings.callback_query( - lambda c: c.data == "Merged_Single" or c.data == "Both_Sides" + lambda c: c.data == "Long" or c.data == "Short" or c.data == "Switch" ) async def trade_mode(callback_query: CallbackQuery, state: FSMContext) -> None: """ @@ -83,78 +79,20 @@ async def trade_mode(callback_query: CallbackQuery, state: FSMContext) -> None: Success or error messages with user identification. """ try: - symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id) - additional_settings = await rq.get_user_additional_settings( - tg_id=callback_query.from_user.id - ) - get_leverage = additional_settings.leverage or "10" - get_leverage_to_buy = additional_settings.leverage_to_buy or "10" - get_leverage_to_sell = additional_settings.leverage_to_sell or "10" - leverage_to_float = safe_float(get_leverage) - leverage_to_buy_float = safe_float(get_leverage_to_buy) - leverage_to_sell_float = safe_float(get_leverage_to_sell) - margin_type = additional_settings.margin_type or "ISOLATED_MARGIN" - mode = 0 if callback_query.data.startswith("Merged_Single") else 3 - response = await set_switch_position_mode( - tg_id=callback_query.from_user.id, symbol=symbol, mode=mode - ) - - if not response: - await callback_query.answer( - text="Произошла ошибка при обновлении режима позиции." - ) - return - req = await rq.set_trade_mode( tg_id=callback_query.from_user.id, trade_mode=callback_query.data ) + if not req: await callback_query.answer( - text="Произошла ошибка при обновлении режима позиции." + text="Произошла ошибка при установке режима торговли" ) return - if ( - response - == "You have an existing position, so position mode cannot be switched" - ): - await callback_query.answer( - text="У вас уже есть позиция по паре, " - "поэтому режим позиции не может быть изменен." - ) - 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"): - await callback_query.answer(text="Выбран режим позиции: Односторонний") - await set_leverage( - tg_id=callback_query.from_user.id, - symbol=symbol, - leverage=str(leverage_to_float), - ) - - elif callback_query.data.startswith("Both_Sides"): - await callback_query.answer(text="Выбран режим позиции: Хеджирование") - if margin_type == "ISOLATED_MARGIN": - await set_leverage_to_buy_and_sell( - tg_id=callback_query.from_user.id, - symbol=symbol, - leverage_to_buy=str(leverage_to_buy_float), - leverage_to_sell=str(leverage_to_sell_float), - ) - else: - await set_leverage( - tg_id=callback_query.from_user.id, - symbol=symbol, - leverage=str(leverage_to_float), - ) - + await callback_query.answer(text="Режим торговли успешно изменен") + logger.debug( + "Trade mode changed successfully for user: %s", callback_query.from_user.id + ) except Exception as e: await callback_query.answer(text="Произошла ошибка при смене режима позиции.") logger.error( @@ -166,9 +104,97 @@ async def trade_mode(callback_query: CallbackQuery, state: FSMContext) -> None: await state.clear() +@router_additional_settings.callback_query(F.data == "switch_side_start") +async def switch_side_start(callback_query: CallbackQuery, state: FSMContext) -> None: + """ + Handles the 'switch_side_start' callback query. + + Clears the current FSM state, edits the message text to display the switch side start message, + and shows an inline keyboard for selection. + + Args: + callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard. + state (FSMContext): Finite State Machine context for the current user session. + + Logs: + Success or error messages with user identification. + """ + try: + await state.clear() + await callback_query.message.edit_text( + text="Выберите направление первой сделки серии:\n\n" + "По направлению - сделка открывается в направлении последней сделки предыдущей серии.\n" + "Противоположно - сделка открывается в противоположном направлении последней сделки предыдущей серии.\n", + reply_markup=kbi.switch_side, + ) + logger.debug( + "Command switch_side_start processed successfully for user: %s", + callback_query.from_user.id, + ) + except Exception as e: + await callback_query.answer( + text="Произошла ошибка. Пожалуйста, попробуйте позже." + ) + logger.error( + "Error processing command switch_side_start for user %s: %s", + callback_query.from_user.id, + e, + ) + + +@router_additional_settings.callback_query(lambda c: c.data == "switch_direction" or c.data == "switch_opposite") +async def switch_side_handler(callback_query: CallbackQuery, state: FSMContext) -> None: + """ + Handles callback queries related to switch side selection. + + Updates FSM context with selected switch side and persists the choice in database. + Sends an acknowledgement to user and clears FSM state afterward. + + Args: + callback_query (CallbackQuery): Incoming callback query indicating selected switch side. + state (FSMContext): Finite State Machine context for the current user session. + + Logs: + Success or error messages with user identification. + """ + try: + if callback_query.data == "switch_direction": + switch_side = "По направлению" + elif callback_query.data == "switch_opposite": + switch_side = "Противоположно" + else: + switch_side = None + + req = await rq.set_switch_side( + tg_id=callback_query.from_user.id, switch_side=switch_side + ) + + if not req: + await callback_query.answer( + text="Произошла ошибка при установке направления переключения" + ) + return + + await callback_query.answer(text=f"Выбрано: {switch_side}") + logger.debug( + "Switch side changed successfully for user: %s", callback_query.from_user.id + ) + except Exception as e: + await callback_query.answer( + text="Произошла ошибка при смене направления переключения" + ) + logger.error( + "Error processing set switch_side for user %s: %s", + callback_query.from_user.id, + e, + ) + finally: + await state.clear() + + @router_additional_settings.callback_query(F.data == "margin_type") async def settings_for_margin_type( - callback_query: CallbackQuery, state: FSMContext + callback_query: CallbackQuery, state: FSMContext ) -> None: """ Handles the 'margin_type' callback query. @@ -228,11 +254,8 @@ async def set_margin_type(callback_query: CallbackQuery, state: FSMContext) -> N tg_id=callback_query.from_user.id ) get_leverage = additional_settings.leverage or "10" - get_leverage_to_buy = additional_settings.leverage_to_buy or "10" - get_leverage_to_sell = additional_settings.leverage_to_sell or "10" + leverage_to_float = safe_float(get_leverage) - leverage_to_buy_float = safe_float(get_leverage_to_buy) - leverage_to_sell_float = safe_float(get_leverage_to_sell) bybit_margin_mode = callback_query.data response = await set_margin_mode( tg_id=callback_query.from_user.id, margin_mode=bybit_margin_mode @@ -254,21 +277,16 @@ async def set_margin_type(callback_query: CallbackQuery, state: FSMContext) -> N ) return + await set_leverage( + tg_id=callback_query.from_user.id, + symbol=symbol, + leverage=str(leverage_to_float), + ) + if callback_query.data.startswith("ISOLATED_MARGIN"): await callback_query.answer(text="Выбран тип маржи: Изолированная") - await set_leverage_to_buy_and_sell( - tg_id=callback_query.from_user.id, - symbol=symbol, - leverage_to_buy=str(leverage_to_buy_float), - leverage_to_sell=str(leverage_to_sell_float), - ) elif callback_query.data.startswith("REGULAR_MARGIN"): await callback_query.answer(text="Выбран тип маржи: Кросс") - await set_leverage( - tg_id=callback_query.from_user.id, - symbol=symbol, - leverage=str(leverage_to_float), - ) else: await callback_query.answer( text="Произошла ошибка при установке типа маржи" @@ -285,394 +303,6 @@ async def set_margin_type(callback_query: CallbackQuery, state: FSMContext) -> N await state.clear() -@router_additional_settings.callback_query(F.data == "order_type") -async def settings_for_order_type( - callback_query: CallbackQuery, state: FSMContext -) -> None: - """ - Handles the 'order_type' callback query. - - Clears the current FSM state, edits the message text to display order type options, - and shows an inline keyboard for selection. - - Args: - callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard. - state (FSMContext): Finite State Machine context for the current user session. - - Logs: - Success or error messages with user identification. - """ - try: - await state.clear() - await callback_query.message.edit_text( - text="Выберите тип ордера:\n\n" - "Рыночный ордер - исполняется немедленно по лучшей доступной цене.\n\n" - "Лимитный ордер - это ордер на покупку или продажу по указанной цене или лучше.\n\n" - "Условный ордер - активируются при достижении триггерной цены.", - reply_markup=kbi.order_type, - ) - logger.debug( - "Command order_type processed successfully for user: %s", - callback_query.from_user.id, - ) - except Exception as e: - await callback_query.answer( - text="Произошла ошибка. Пожалуйста, попробуйте позже." - ) - logger.error( - "Error processing command order_type for user %s: %s", - callback_query.from_user.id, - e, - ) - - -@router_additional_settings.callback_query( - lambda c: c.data == "Market" or c.data == "Limit" or c.data == "Conditional" -) -async def set_order_type(callback_query: CallbackQuery, state: FSMContext) -> None: - """ - Handles callback queries starting with 'Market', 'Limit', or 'Conditional'. - - Updates FSM context with selected order type and persists the choice in database. - Sends an acknowledgement to user and clears FSM state afterward. - - Args: - callback_query (CallbackQuery): Incoming callback query indicating selected order type. - state (FSMContext): Finite State Machine context for the current user session. - - Logs: - Success or error messages with user identification. - """ - try: - await state.update_data(order_type=callback_query.data) - req = await rq.set_order_type( - tg_id=callback_query.from_user.id, order_type=callback_query.data - ) - - if not req: - await callback_query.answer( - text="Произошла ошибка при установке типа ордера" - ) - return - - if callback_query.data.startswith("Market"): - await callback_query.answer(text="Выбран тип ордера: Рыночный") - elif callback_query.data.startswith("Limit"): - await callback_query.answer(text="Выбран тип ордера: Лимитный") - elif callback_query.data.startswith("Conditional"): - await callback_query.answer(text="Выбран тип ордера: Условный") - else: - await callback_query.answer( - text="Произошла ошибка при установке типа ордера" - ) - - except Exception as e: - await callback_query.answer(text="Произошла ошибка при установке типа ордера") - logger.error( - "Error processing command order_type for user %s: %s", - callback_query.from_user.id, - e, - ) - finally: - await state.clear() - - -@router_additional_settings.callback_query(F.data == "conditional_order_type") -async def settings_for_conditional_order_type( - callback_query: CallbackQuery, state: FSMContext -) -> None: - """ - Handles the 'conditional_order_type' callback query. - - Clears the current FSM state, edits the message text to display conditional order type options, - and shows an inline keyboard for selection. - - Args: - callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard. - state (FSMContext): Finite State Machine context for the current user session. - - Logs: - Success or error messages with user identification. - """ - try: - await state.clear() - await callback_query.message.edit_text( - text="Выберите тип условного ордера:\n\n" - "Рыночный ордер - исполняется немедленно по лучшей доступной цене при достижении триггерной цены.\n\n" - "Лимитный ордер - это ордер на покупку или продажу по указанной цене или лучше.\n\n", - reply_markup=kbi.conditional_order_type, - ) - logger.debug( - "Command conditional_order_type processed successfully for user: %s", - callback_query.from_user.id, - ) - except Exception as e: - await callback_query.answer( - text="Произошла ошибка. Пожалуйста, попробуйте позже." - ) - logger.error( - "Error processing command conditional_order_type for user %s: %s", - callback_query.from_user.id, - e, - ) - - -@router_additional_settings.callback_query( - lambda c: c.data == "set_market" or c.data == "set_limit" -) -async def conditional_order_type( - callback_query: CallbackQuery, state: FSMContext -) -> None: - """ - Handles callback queries starting with 'set_market' or 'set_limit'. - - Updates FSM context with selected conditional order type and persists the choice in database. - Sends an acknowledgement to user and clears FSM state afterward. - - Args: - callback_query (CallbackQuery): Incoming callback query indicating selected conditional order type. - state (FSMContext): Finite State Machine context for the current user session. - - Logs: - Success or error messages with user identification. - """ - try: - await state.update_data(conditional_order_type=callback_query.data) - - if callback_query.data.startswith("set_market"): - await callback_query.answer(text="Выбран тип условного ордера: Рыночный") - order_type = "Market" - elif callback_query.data.startswith("set_limit"): - await callback_query.answer(text="Выбран тип условного ордера: Лимитный") - order_type = "Limit" - else: - await callback_query.answer( - text="Произошла ошибка при обновлении типа условного ордера" - ) - return - - req = await rq.set_conditional_order_type( - tg_id=callback_query.from_user.id, conditional_order_type=order_type - ) - - if not req: - await callback_query.answer( - text="Произошла ошибка при обновлении типа условного ордера" - ) - return - except Exception as e: - await callback_query.answer( - text="Произошла ошибка при обновлении типа условного ордера." - ) - logger.error( - "Error processing conditional_order_type for user %s: %s", - callback_query.from_user.id, - e, - ) - finally: - await state.clear() - - -@router_additional_settings.callback_query(F.data == "limit_price") -async def limit_price(callback_query: CallbackQuery, state: FSMContext) -> None: - """ - Handles the 'limit_price' callback query. - - Clears the current FSM state, edits the message text to display the limit price options, - and shows an inline keyboard for selection. - - Args: - callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard. - state (FSMContext): Finite State Machine context for the current user session. - - Logs: - Success or error messages with user identification. - """ - try: - await state.clear() - await callback_query.message.edit_text( - text="Выберите цену лимита:\n\n" - "1. Установить цену - указать цену\n" - "2. Последняя цена - использовать последнюю цену\n", - reply_markup=kbi.change_limit_price, - ) - logger.debug( - "Command limit_price processed successfully for user: %s", - callback_query.from_user.id, - ) - except Exception as e: - await callback_query.answer( - text="Произошла ошибка. Пожалуйста, попробуйте позже." - ) - logger.error( - "Error processing command limit_price for user %s: %s", - callback_query.from_user.id, - e, - ) - - -@router_additional_settings.callback_query(lambda c: c.data == "last_price") -async def last_price(callback_query: CallbackQuery, state: FSMContext) -> None: - """ - Handles the 'last_price' callback query. - - Clears the current FSM state, edits the message text to display the last price option, - and shows an inline keyboard for selection. - - Args: - callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard. - state (FSMContext): Finite State Machine context for the current user session. - - Logs: - Success or error messages with user identification. - """ - try: - symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id) - get_tickers_info = await get_tickers( - tg_id=callback_query.from_user.id, symbol=symbol - ) - if get_tickers_info is None: - await callback_query.answer( - text="Произошла ошибка при установке цены лимита." - ) - return - - mark_price = get_tickers_info.get("lastPrice") or 0 - req = await rq.set_limit_price( - tg_id=callback_query.from_user.id, limit_price=safe_float(mark_price) - ) - if req: - await callback_query.answer( - text=f"Цена лимита установлена на последнюю цену: {mark_price}" - ) - else: - await callback_query.answer( - text="Произошла ошибка при установке цены лимита." - ) - - except Exception as e: - await callback_query.answer(text="Произошла ошибка при установке цены лимита.") - logger.error( - "Error processing last_price for user %s: %s", - callback_query.from_user.id, - e, - ) - finally: - await state.clear() - - -@router_additional_settings.callback_query(lambda c: c.data == "set_limit_price") -async def set_limit_price_handler( - callback_query: CallbackQuery, state: FSMContext -) -> None: - """ - Handles the 'set_limit_price_handler' callback query. - - Clears the current FSM state, edits the message text to prompt for the limit price, - and shows an inline keyboard for input. - - Args: - callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard. - state (FSMContext): Finite State Machine context for the current user session. - - Logs: - Success or error messages with user identification. - """ - try: - await state.clear() - await state.set_state(AdditionalSettingsState.limit_price_state) - await callback_query.answer() - await state.update_data(prompt_message_id=callback_query.message.message_id) - msg = await callback_query.message.edit_text( - text="Введите цену:", reply_markup=kbi.back_to_change_limit_price - ) - await state.update_data(prompt_message_id=msg.message_id) - logger.debug( - "Command set_limit_price processed successfully for user: %s", - callback_query.from_user.id, - ) - except Exception as e: - await callback_query.answer( - text="Произошла ошибка. Пожалуйста, попробуйте позже." - ) - logger.error( - "Error processing command set_limit_price for user %s: %s", - callback_query.from_user.id, - e, - ) - - -@router_additional_settings.message(AdditionalSettingsState.limit_price_state) -async def set_limit_price(message: Message, state: FSMContext) -> None: - """ - Handles user input for setting the limit price. - - Updates FSM context with the selected limit price and persists the choice in database. - Sends an acknowledgement to user and clears FSM state afterward. - - Args: - message (Message): Incoming message from user containing the selected limit price. - state (FSMContext): Finite State Machine context for the current user session. - - Logs: - Success or error messages with user identification. - """ - try: - try: - data = await state.get_data() - if "prompt_message_id" in data: - prompt_message_id = data["prompt_message_id"] - await message.bot.delete_message( - chat_id=message.chat.id, message_id=prompt_message_id - ) - await message.delete() - except Exception as e: - if "message to delete not found" in str(e).lower(): - pass # Ignore this error - else: - raise e - - limit_price_value = message.text - - if not is_number(limit_price_value): - await message.answer( - "Ошибка: введите валидное число.", - reply_markup=kbi.back_to_additional_settings, - ) - logger.debug( - "User %s input invalid (not an valid number): %s", - message.from_user.id, - limit_price_value, - ) - return - - req = await rq.set_limit_price( - tg_id=message.from_user.id, limit_price=safe_float(limit_price_value) - ) - if req: - await message.answer( - text=f"Цена лимита установлена на: {limit_price_value}", - reply_markup=kbi.back_to_additional_settings, - ) - else: - await message.answer( - text="Произошла ошибка при установке цены лимита.", - reply_markup=kbi.back_to_change_limit_price, - ) - - await state.clear() - except Exception as e: - await message.answer( - text="Произошла ошибка при установке цены лимита.", - reply_markup=kbi.back_to_additional_settings, - ) - logger.error( - "Error processing set_limit_price for user %s: %s", - message.from_user.id, - e, - ) - - @router_additional_settings.callback_query(lambda c: c.data == "trigger_price") async def trigger_price(callback_query: CallbackQuery, state: FSMContext) -> None: """ @@ -784,7 +414,7 @@ async def set_trigger_price(message: Message, state: FSMContext) -> None: @router_additional_settings.callback_query(F.data == "leverage") -async def leverage_to_buy(callback_query: CallbackQuery, state: FSMContext) -> None: +async def leverage_handler(callback_query: CallbackQuery, state: FSMContext) -> None: """ Handles the 'leverage' callback query. @@ -801,25 +431,12 @@ async def leverage_to_buy(callback_query: CallbackQuery, state: FSMContext) -> N try: await state.clear() await callback_query.answer() - additional_settings = await rq.get_user_additional_settings( - callback_query.from_user.id + await state.set_state(AdditionalSettingsState.leverage_state) + msg = await callback_query.message.edit_text( + text="Введите размер кредитного плеча:", + reply_markup=kbi.back_to_additional_settings, ) - get_trade_mode = additional_settings.trade_mode or "Both_Sides" - get_margin_type = additional_settings.margin_type or "ISOLATED_MARGIN" - if get_trade_mode == "Both_Sides" and get_margin_type == "ISOLATED_MARGIN": - await state.set_state(AdditionalSettingsState.leverage_to_buy_state) - msg = await callback_query.message.edit_text( - text="Введите размер кредитного плеча для Лонг:", - reply_markup=kbi.back_to_additional_settings, - ) - await state.update_data(prompt_message_id=msg.message_id) - else: - await state.set_state(AdditionalSettingsState.leverage_state) - msg = await callback_query.message.edit_text( - text="Введите размер кредитного плеча:", - reply_markup=kbi.back_to_additional_settings, - ) - await state.update_data(prompt_message_id=msg.message_id) + await state.update_data(prompt_message_id=msg.message_id) logger.debug( "Command leverage processed successfully for user: %s", callback_query.from_user.id, @@ -836,7 +453,7 @@ async def leverage_to_buy(callback_query: CallbackQuery, state: FSMContext) -> N @router_additional_settings.message(AdditionalSettingsState.leverage_state) -async def leverage(message: Message, state: FSMContext) -> None: +async def set_leverage_handler(message: Message, state: FSMContext) -> None: """ Handles user input for setting the leverage. @@ -880,29 +497,18 @@ async def leverage(message: Message, state: FSMContext) -> None: return leverage_float = safe_float(get_leverage) - if leverage_float < 1 or leverage_float > 100: - await message.answer( - text="Ошибка: число должно быть от 1 до 100.", - reply_markup=kbi.back_to_additional_settings, - ) - logger.debug( - "User %s input invalid (out of range): %s", - message.from_user.id, - leverage_float, - ) - return symbol = await rq.get_user_symbol(tg_id=tg_id) instruments_info = await get_instruments_info(tg_id=tg_id, symbol=symbol) if instruments_info is not None: min_leverage = ( - safe_float(instruments_info.get("leverageFilter").get("minLeverage")) - or 1 + safe_float(instruments_info.get("leverageFilter").get("minLeverage")) + or 1 ) max_leverage = ( - safe_float(instruments_info.get("leverageFilter").get("maxLeverage")) - or 100 + safe_float(instruments_info.get("leverageFilter").get("maxLeverage")) + or 100 ) if leverage_float > max_leverage or leverage_float < min_leverage: @@ -939,16 +545,15 @@ async def leverage(message: Message, state: FSMContext) -> None: req_leverage = await rq.set_leverage( tg_id=message.from_user.id, leverage=str(leverage_float) ) - req_leverage_to_buy_and_sell = await rq.set_leverage_to_buy_and_sell( - tg_id=message.from_user.id, - leverage_to_buy=str(leverage_float), - leverage_to_sell=str(leverage_float), - ) - if req_leverage and req_leverage_to_buy_and_sell: + + if req_leverage: await message.answer( text=f"Кредитное плечо успешно установлено на {leverage_float}", reply_markup=kbi.back_to_additional_settings, ) + risk_percent = 100 / safe_float(leverage_float) + await rq.set_stop_loss_percent( + tg_id=message.from_user.id, stop_loss_percent=risk_percent) logger.info( "User %s set leverage: %s", message.from_user.id, leverage_float ) @@ -969,247 +574,6 @@ async def leverage(message: Message, state: FSMContext) -> None: ) -@router_additional_settings.message(AdditionalSettingsState.leverage_to_buy_state) -async def set_leverage_to_buy(message: Message, state: FSMContext) -> None: - """ - Handles user input for setting the leverage to buy. - - Updates FSM context with the selected leverage to buy and persists the choice in database. - Sends an acknowledgement to user and clears FSM state afterward. - - Args: - message (Message): Incoming message from user containing the selected leverage to buy. - state (FSMContext): Finite State Machine context for the current user session. - - Logs: - Success or error messages with user identification. - """ - try: - try: - data = await state.get_data() - if "prompt_message_id" in data: - prompt_message_id = data["prompt_message_id"] - await message.bot.delete_message( - chat_id=message.chat.id, message_id=prompt_message_id - ) - await message.delete() - except Exception as e: - if "message to delete not found" in str(e).lower(): - pass # Ignore this error - else: - raise e - - get_leverage_to_buy = message.text - tg_id = message.from_user.id - - if not is_number(get_leverage_to_buy): - await message.answer( - "Ошибка: введите валидное число.", - reply_markup=kbi.back_to_additional_settings, - ) - logger.debug( - "User %s input invalid (not an valid number): %s", - message.from_user.id, - get_leverage_to_buy, - ) - return - - leverage_to_buy_float = safe_float(get_leverage_to_buy) - if leverage_to_buy_float < 1 or leverage_to_buy_float > 100: - await message.answer( - text="Ошибка: число должно быть от 1 до 100.", - reply_markup=kbi.back_to_additional_settings, - ) - logger.debug( - "User %s input invalid (out of range): %s", - message.from_user.id, - get_leverage_to_buy, - ) - return - - symbol = await rq.get_user_symbol(tg_id=tg_id) - instruments_info = await get_instruments_info(tg_id=tg_id, symbol=symbol) - - if instruments_info is not None: - max_leverage = safe_float( - instruments_info.get("leverageFilter").get("maxLeverage") - ) - if leverage_to_buy_float > max_leverage: - await message.answer( - text=f"Кредитное плечо {leverage_to_buy_float} превышает максимальное {max_leverage} для {symbol}", - reply_markup=kbi.back_to_additional_settings, - ) - logger.info( - "The requested leverage %s exceeds the maximum %s for %s for user: %s: %s", - leverage_to_buy_float, - max_leverage, - symbol, - message.from_user.id, - ) - return - else: - await message.answer( - text="Произошла ошибка при установке кредитного плеча. Пожалуйста, попробуйте позже.", - reply_markup=kbi.back_to_additional_settings, - ) - logger.error( - "Error processing command leverage_to_buy for user %s", - message.from_user.id, - ) - - await state.update_data(leverage_to_buy=leverage_to_buy_float) - await state.set_state(AdditionalSettingsState.leverage_to_sell_state) - msg = await message.answer( - text="Введите размер кредитного плеча для Шорт:", - reply_markup=kbi.back_to_additional_settings, - ) - await state.update_data(prompt_message_id=msg.message_id) - logger.debug( - "Command leverage_to_buy processed successfully for user: %s", - message.from_user.id, - ) - except Exception as e: - await message.answer( - text="Произошла ошибка при установке кредитного плеча.. Пожалуйста, попробуйте позже.", - reply_markup=kbi.back_to_additional_settings, - ) - logger.error( - "Error processing command leverage_to_buy for user %s: %s", - message.from_user.id, - e, - ) - - -@router_additional_settings.message(AdditionalSettingsState.leverage_to_sell_state) -async def set_leverage_to_sell(message: Message, state: FSMContext) -> None: - """ - Handles user input for setting the leverage to sell. - - Updates FSM context with the selected leverage and persists the choice in database. - Sends an acknowledgement to user and clears FSM state afterward. - - Args: - message (Message): Incoming message from user containing the selected leverage. - state (FSMContext): Finite State Machine context for the current user session. - - Logs: - Success or error messages with user identification. - """ - try: - try: - data = await state.get_data() - if "prompt_message_id" in data: - prompt_message_id = data["prompt_message_id"] - await message.bot.delete_message( - chat_id=message.chat.id, message_id=prompt_message_id - ) - await message.delete() - except Exception as e: - if "message to delete not found" in str(e).lower(): - pass # Ignore this error - else: - raise e - - get_leverage_to_sell = message.text - get_leverage_to_buy = (await state.get_data()).get("leverage_to_buy") - tg_id = message.from_user.id - - if not is_number(get_leverage_to_sell): - await message.answer( - "Ошибка: введите валидное число.", - reply_markup=kbi.back_to_additional_settings, - ) - logger.debug( - "User %s input invalid (not an valid number): %s", - message.from_user.id, - leverage_to_buy or get_leverage_to_sell, - ) - return - - leverage_to_buy_float = safe_float(get_leverage_to_buy) - leverage_to_sell_float = safe_float(get_leverage_to_sell) - if leverage_to_sell_float < 1 or leverage_to_sell_float > 100: - await message.answer( - text="Ошибка: число должно быть от 1 до 100.", - reply_markup=kbi.back_to_additional_settings, - ) - logger.debug( - "User %s input invalid (out of range): %s", - message.from_user.id, - get_leverage_to_sell, - ) - return - - symbol = await rq.get_user_symbol(tg_id=tg_id) - instruments_info = await get_instruments_info(tg_id=tg_id, symbol=symbol) - - if instruments_info is not None: - min_leverage = safe_float( - instruments_info.get("leverageFilter").get("minLeverage") - ) - if leverage_to_sell_float < min_leverage: - await message.answer( - text=f"Кредитное плечо {leverage_to_sell_float} ниже минимального {min_leverage} для {symbol}", - reply_markup=kbi.back_to_additional_settings, - ) - logger.info( - "The requested leverage %s is below the minimum %s for %s for user: %s", - leverage_to_sell_float, - min_leverage, - symbol, - message.from_user.id, - ) - return - else: - await message.answer( - text="Произошла ошибка при установке кредитного плеча. Пожалуйста, попробуйте позже.", - reply_markup=kbi.back_to_additional_settings, - ) - - response = await set_leverage_to_buy_and_sell( - tg_id=message.from_user.id, - symbol=symbol, - leverage_to_buy=str(leverage_to_buy_float), - leverage_to_sell=str(leverage_to_sell_float), - ) - if not response: - await message.answer( - text="Невозможно установить кредитное плечо для текущего режима торговли.", - reply_markup=kbi.back_to_additional_settings, - ) - return - - req = await rq.set_leverage_to_buy_and_sell( - tg_id=message.from_user.id, - leverage_to_buy=str(leverage_to_buy_float), - leverage_to_sell=str(leverage_to_sell_float), - ) - - if req: - await message.answer( - text=f"Размер кредитного плеча установлен на {leverage_to_buy_float} для Лонга " - f"и {leverage_to_sell_float} для Шорта", - reply_markup=kbi.back_to_additional_settings, - ) - else: - await message.answer( - text="Произошла ошибка при установке кредитного плеча. Пожалуйста, попробуйте позже.", - reply_markup=kbi.back_to_additional_settings, - ) - - await state.clear() - except Exception as e: - await message.answer( - text="Произошла ошибка при установке кредитного плеча. Пожалуйста, попробуйте позже.", - reply_markup=kbi.back_to_additional_settings, - ) - logger.error( - "Error processing command set_leverage for user %s: %s", - message.from_user.id, - e, - ) - - @router_additional_settings.callback_query(F.data == "order_quantity") async def order_quantity(callback_query: CallbackQuery, state: FSMContext) -> None: """ @@ -1228,10 +592,8 @@ async def order_quantity(callback_query: CallbackQuery, state: FSMContext) -> No try: await state.clear() await state.set_state(AdditionalSettingsState.quantity_state) - symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id) - name_symbol = get_base_currency(symbol) msg = await callback_query.message.edit_text( - text=f"Укажите размер для ордера в следующей валюте: {name_symbol}.", + text=f"Введите базовую ставку в USDT:", reply_markup=kbi.back_to_additional_settings, ) await state.update_data(prompt_message_id=msg.message_id) @@ -1295,25 +657,6 @@ async def set_order_quantity(message: Message, state: FSMContext) -> None: return quantity = safe_float(order_quantity_value) - symbol = await rq.get_user_symbol(tg_id=message.from_user.id) - instruments_info = await get_instruments_info( - tg_id=message.from_user.id, symbol=symbol - ) - - if instruments_info is not None: - max_order_qty = safe_float( - instruments_info.get("lotSizeFilter").get("maxOrderQty") - ) - min_order_qty = safe_float( - instruments_info.get("lotSizeFilter").get("minOrderQty") - ) - - if quantity < min_order_qty or quantity > max_order_qty: - await message.answer( - text=f"Количество ордера должно быть от {min_order_qty} до {max_order_qty}", - reply_markup=kbi.back_to_additional_settings, - ) - return req = await rq.set_order_quantity( tg_id=message.from_user.id, order_quantity=quantity @@ -1321,7 +664,7 @@ async def set_order_quantity(message: Message, state: FSMContext) -> None: if req: await message.answer( - text=f"Количество ордера установлено на {message.text}", + text=f"Базовая ставка установлена на {message.text} USDT", reply_markup=kbi.back_to_additional_settings, ) else: @@ -1333,7 +676,7 @@ async def set_order_quantity(message: Message, state: FSMContext) -> None: await state.clear() except Exception as e: await message.answer( - text="Произошла ошибка при установке кол-ва ордера. Пожалуйста, попробуйте позже.", + text="Произошла ошибка при установке базовой ставки. Пожалуйста, попробуйте позже.", reply_markup=kbi.back_to_additional_settings, ) logger.error("Error processing command set_order_quantity: %s", e) @@ -1422,6 +765,13 @@ async def set_martingale_factor(message: Message, state: FSMContext) -> None: return martingale_factor_value_float = safe_float(martingale_factor_value) + + if martingale_factor_value_float < 0.1 or martingale_factor_value_float > 10: + await message.answer(text="Ошибка: коэффициент мартингейла должен быть в диапазоне от 0.1 до 10") + logger.debug("User %s input invalid (not in range 0.1 to 10): %s", message.from_user.id, + martingale_factor_value_float) + return + req = await rq.set_martingale_factor( tg_id=message.from_user.id, martingale_factor=martingale_factor_value_float ) @@ -1528,6 +878,18 @@ async def set_max_bets_in_series(message: Message, state: FSMContext) -> None: ) return + if safe_float(max_bets_in_series_value) < 1 or safe_float(max_bets_in_series_value) > 100: + await message.answer( + "Ошибка: число должно быть в диапазоне от 1 до 100.", + reply_markup=kbi.back_to_additional_settings, + ) + logger.debug( + "User %s input invalid (not in range 1 to 100): %s", + message.from_user.id, + max_bets_in_series_value, + ) + return + req = await rq.set_max_bets_in_series( tg_id=message.from_user.id, max_bets_in_series=int(max_bets_in_series_value) ) diff --git a/app/telegram/handlers/main_settings/risk_management.py b/app/telegram/handlers/main_settings/risk_management.py index ad17564..86bb052 100644 --- a/app/telegram/handlers/main_settings/risk_management.py +++ b/app/telegram/handlers/main_settings/risk_management.py @@ -6,7 +6,7 @@ from aiogram.types import CallbackQuery, Message import app.telegram.keyboards.inline as kbi import database.request as rq -from app.helper_functions import is_int +from app.helper_functions import is_number, safe_float from app.telegram.states.states import RiskManagementState from logger_helper.logger_helper import LOGGING_CONFIG @@ -86,7 +86,7 @@ async def set_take_profit_percent(message: Message, state: FSMContext) -> None: take_profit_percent_value = message.text - if not is_int(take_profit_percent_value): + if not is_number(take_profit_percent_value): await message.answer( text="Ошибка: введите валидное число.", reply_markup=kbi.back_to_risk_management, @@ -98,7 +98,7 @@ async def set_take_profit_percent(message: Message, state: FSMContext) -> None: ) return - if int(take_profit_percent_value) < 1 or int(take_profit_percent_value) > 100: + if safe_float(take_profit_percent_value) < 1 or safe_float(take_profit_percent_value) > 100: await message.answer( text="Ошибка: введите число от 1 до 100.", reply_markup=kbi.back_to_risk_management, @@ -112,7 +112,7 @@ async def set_take_profit_percent(message: Message, state: FSMContext) -> None: req = await rq.set_take_profit_percent( tg_id=message.from_user.id, - take_profit_percent=int(take_profit_percent_value), + take_profit_percent=safe_float(take_profit_percent_value), ) if req: @@ -207,7 +207,7 @@ async def set_stop_loss_percent(message: Message, state: FSMContext) -> None: stop_loss_percent_value = message.text - if not is_int(stop_loss_percent_value): + if not is_number(stop_loss_percent_value): await message.answer( text="Ошибка: введите валидное число.", reply_markup=kbi.back_to_risk_management, @@ -219,7 +219,7 @@ async def set_stop_loss_percent(message: Message, state: FSMContext) -> None: ) return - if int(stop_loss_percent_value) < 1 or int(stop_loss_percent_value) > 100: + if safe_float(stop_loss_percent_value) < 1 or safe_float(stop_loss_percent_value) > 100: await message.answer( text="Ошибка: введите число от 1 до 100.", reply_markup=kbi.back_to_risk_management, @@ -232,7 +232,7 @@ async def set_stop_loss_percent(message: Message, state: FSMContext) -> None: return req = await rq.set_stop_loss_percent( - tg_id=message.from_user.id, stop_loss_percent=int(stop_loss_percent_value) + tg_id=message.from_user.id, stop_loss_percent=safe_float(stop_loss_percent_value) ) if req: @@ -262,130 +262,6 @@ async def set_stop_loss_percent(message: Message, state: FSMContext) -> None: ) -@router_risk_management.callback_query(F.data == "max_risk_percent") -async def max_risk_percent(callback_query: CallbackQuery, state: FSMContext) -> None: - """ - Handles the 'max_risk_percent' callback query. - - Clears the current FSM state, edits the message text to display the maximum risk percentage options, - and shows an inline keyboard for selection. - - Args: - callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard. - state (FSMContext): Finite State Machine context for the current user session. - - Logs: - Success or error messages with user identification. - """ - try: - await state.clear() - await state.set_state(RiskManagementState.max_risk_percent_state) - msg = await callback_query.message.edit_text( - text="Введите максимальный процент риска: ", - reply_markup=kbi.back_to_risk_management, - ) - await state.update_data(prompt_message_id=msg.message_id) - logger.debug( - "Command max_risk_percent processed successfully for user: %s", - callback_query.from_user.id, - ) - except Exception as e: - await callback_query.answer( - text="Произошла ошибка. Пожалуйста, попробуйте позже." - ) - logger.error( - "Error processing command max_risk_percent for user %s: %s", - callback_query.from_user.id, - e, - ) - - -@router_risk_management.message(RiskManagementState.max_risk_percent_state) -async def set_max_risk_percent(message: Message, state: FSMContext) -> None: - """ - Handles user input for setting the maximum risk percentage. - - Updates FSM context with the selected percentage and persists the choice in database. - Sends an acknowledgement to user and clears FSM state afterward. - - Args: - message (Message): Incoming message from user containing the maximum risk percentage. - state (FSMContext): Finite State Machine context for the current user session. - - Logs: - Success or error messages with user identification. - """ - try: - try: - data = await state.get_data() - if "prompt_message_id" in data: - prompt_message_id = data["prompt_message_id"] - await message.bot.delete_message( - chat_id=message.chat.id, message_id=prompt_message_id - ) - await message.delete() - except Exception as e: - if "message to delete not found" in str(e).lower(): - pass # Ignore this error - else: - raise e - - max_risk_percent_value = message.text - - if not is_int(max_risk_percent_value): - await message.answer( - text="Ошибка: введите валидное число.", - reply_markup=kbi.back_to_risk_management, - ) - logger.debug( - "User %s input invalid (not an valid number): %s", - message.from_user.id, - max_risk_percent_value, - ) - return - - if int(max_risk_percent_value) < 1 or int(max_risk_percent_value) > 100: - await message.answer( - text="Ошибка: введите число от 1 до 100.", - reply_markup=kbi.back_to_risk_management, - ) - logger.debug( - "User %s input invalid (not an valid number): %s", - message.from_user.id, - max_risk_percent_value, - ) - return - - req = await rq.set_max_risk_percent( - tg_id=message.from_user.id, max_risk_percent=int(max_risk_percent_value) - ) - - if req: - await message.answer( - text=f"Максимальный процент риска установлен на {max_risk_percent_value}%.", - reply_markup=kbi.back_to_risk_management, - ) - else: - await message.answer( - text="Произошла ошибка при установке максимального процента риска. " - "Пожалуйста, попробуйте позже.", - reply_markup=kbi.back_to_risk_management, - ) - - await state.clear() - except Exception as e: - await message.answer( - text="Произошла ошибка при установке максимального процента риска. " - "Пожалуйста, попробуйте позже.", - reply_markup=kbi.back_to_risk_management, - ) - logger.error( - "Error processing command max_risk_percent for user %s: %s", - message.from_user.id, - e, - ) - - @router_risk_management.callback_query(F.data == "commission_fee") async def commission_fee(callback_query: CallbackQuery, state: FSMContext) -> None: """ diff --git a/app/telegram/handlers/settings.py b/app/telegram/handlers/settings.py index b687e0a..27487d4 100644 --- a/app/telegram/handlers/settings.py +++ b/app/telegram/handlers/settings.py @@ -6,9 +6,8 @@ from aiogram.types import CallbackQuery import app.telegram.keyboards.inline as kbi import database.request as rq -from app.bybit import get_bybit_client -from app.bybit.get_functions.get_tickers import get_tickers -from app.helper_functions import calculate_total_budget, get_base_currency, safe_float + +from app.helper_functions import calculate_total_budget, safe_float from logger_helper.logger_helper import LOGGING_CONFIG logging.config.dictConfig(LOGGING_CONFIG) @@ -26,7 +25,6 @@ async def additional_settings(callback_query: CallbackQuery, state: FSMContext) try: await state.clear() tg_id = callback_query.from_user.id - symbol = await rq.get_user_symbol(tg_id=tg_id) additional_data = await rq.get_user_additional_settings(tg_id=tg_id) if not additional_data: @@ -40,138 +38,54 @@ async def additional_settings(callback_query: CallbackQuery, state: FSMContext) return trade_mode_map = { - "Merged_Single": "Односторонний режим", - "Both_Sides": "Хеджирование", + "Long": "Лонг", + "Short": "Шорт", + "Switch": "Свитч", } margin_type_map = { "ISOLATED_MARGIN": "Изолированная", "REGULAR_MARGIN": "Кросс", } - order_type_map = {"Market": "Рыночный", "Limit": "Лимитный"} trade_mode = additional_data.trade_mode or "" margin_type = additional_data.margin_type or "" - order_type = additional_data.order_type or "" trade_mode_rus = trade_mode_map.get(trade_mode, trade_mode) margin_type_rus = margin_type_map.get(margin_type, margin_type) - order_type_rus = order_type_map.get(order_type, "Условный") + switch_side = additional_data.switch_side def f(x): return safe_float(x) leverage = f(additional_data.leverage) - leverage_to_buy = f(additional_data.leverage_to_buy) - leverage_to_sell = f(additional_data.leverage_to_sell) martingale = f(additional_data.martingale_factor) max_bets = additional_data.max_bets_in_series quantity = f(additional_data.order_quantity) - limit_price = f(additional_data.limit_price) trigger_price = f(additional_data.trigger_price) or 0 - tickers = await get_tickers(tg_id=tg_id, symbol=symbol) - price_symbol = safe_float(tickers.get("lastPrice")) or 0 - bid = f(tickers.get("bid1Price")) or 0 - ask = f(tickers.get("ask1Price")) or 0 + switch_side_mode = "" + if trade_mode == "Switch": + switch_side_mode = f"- Направление первой сделки: {switch_side}\n" - sym = get_base_currency(symbol) - - if trade_mode == "Merged_Single": - leverage_str = f"{leverage:.2f}x" - else: - if margin_type == "ISOLATED_MARGIN": - leverage_str = f"{leverage_to_buy:.2f}x:{leverage_to_sell:.2f}x" - else: - leverage_str = f"{leverage:.2f}x" - - conditional_order_type = additional_data.conditional_order_type or "" - conditional_order_type_rus = ( - "Лимитный" - if conditional_order_type == "Limit" - else ( - "Рыночный" - if conditional_order_type == "Market" - else conditional_order_type - ) - ) - - conditional_order_type_text = ( - f"- Тип условного ордера: {conditional_order_type_rus}\n" - if order_type == "Conditional" - else "" - ) - - limit_price_text = "" - trigger_price_text = "" - - if order_type == "Limit": - limit_price_text = f"- Цена лимитного ордера: {limit_price:.4f} USDT\n" - elif order_type == "Conditional": - if conditional_order_type == "Limit": - limit_price_text = f"- Цена лимитного ордера: {limit_price:.4f} USDT\n" - trigger_price_text = f"- Триггер цена: {trigger_price:.4f} USDT\n" - - risk_management_data = await rq.get_user_risk_management(tg_id=tg_id) - commission_fee = risk_management_data.commission_fee - client = await get_bybit_client(tg_id=tg_id) - fee_info = client.get_fee_rates(category="linear", symbol=symbol) - - if commission_fee == "Yes_commission_fee": - commission_fee_percent = safe_float( - fee_info["result"]["list"][0]["takerFeeRate"] - ) - - else: - commission_fee_percent = 0.0 - - if order_type == "Conditional": - if conditional_order_type == "Limit": - entry_price = limit_price - ask_price = limit_price - bid_price = limit_price - else: - ask_price = trigger_price - bid_price = trigger_price - entry_price = trigger_price - else: - if order_type == "Limit": - entry_price = limit_price - ask_price = limit_price - bid_price = limit_price - else: - entry_price = price_symbol - ask_price = ask - bid_price = bid - - durability_buy = quantity * bid_price - durability_sell = quantity * ask_price - quantity_price = quantity * entry_price - total_commission = quantity_price * commission_fee_percent total_budget = await calculate_total_budget( - quantity=durability_buy, + quantity=quantity, martingale_factor=martingale, max_steps=max_bets, - commission_fee_percent=total_commission, ) text = ( f"Основные настройки:\n\n" - f"- Режим позиции: {trade_mode_rus}\n" + f"- Режим торговли: {trade_mode_rus}\n" + f"{switch_side_mode}" f"- Тип маржи: {margin_type_rus}\n" - f"- Размер кредитного плеча: {leverage_str}\n" - f"- Тип ордера: {order_type_rus}\n" - f"- Количество ордера: {quantity} {sym}\n" + f"- Размер кредитного плеча: {leverage:.2f}\n" + f"- Базовая ставка: {quantity} USDT\n" f"- Коэффициент мартингейла: {martingale:.2f}\n" - f"{conditional_order_type_text}" - f"{trigger_price_text}" - f"{limit_price_text}" + f"- Триггер цена: {trigger_price:.4f} USDT\n" f"- Максимальное кол-во ставок в серии: {max_bets}\n\n" - f"- Стоимость: {durability_buy:.2f}/{durability_sell:.2f} USDT\n" - f"- Рекомендуемый бюджет: {total_budget:.4f} USDT\n" + f"- Бюджет серии: {total_budget:.2f} USDT\n" ) - keyboard = kbi.get_additional_settings_keyboard( - current_order_type=order_type, conditional_order=conditional_order_type - ) + keyboard = kbi.get_additional_settings_keyboard(mode=trade_mode) await callback_query.message.edit_text(text=text, reply_markup=keyboard) logger.debug( "Command additional_settings processed successfully for user: %s", tg_id @@ -202,7 +116,6 @@ async def risk_management(callback_query: CallbackQuery, state: FSMContext) -> N if risk_management_data: take_profit_percent = risk_management_data.take_profit_percent or "" stop_loss_percent = risk_management_data.stop_loss_percent or "" - max_risk_percent = risk_management_data.max_risk_percent or "" commission_fee = risk_management_data.commission_fee or "" commission_fee_rus = ( "Да" if commission_fee == "Yes_commission_fee" else "Нет" @@ -212,7 +125,6 @@ async def risk_management(callback_query: CallbackQuery, state: FSMContext) -> N text=f"Риск-менеджмент:\n\n" f"- Процент изменения цены для фиксации прибыли: {take_profit_percent}%\n" f"- Процент изменения цены для фиксации убытка: {stop_loss_percent}%\n\n" - f"- Максимальный риск на сделку (в % от баланса): {max_risk_percent}%\n\n" f"- Комиссия биржи для расчета прибыли: {commission_fee_rus}\n\n", reply_markup=kbi.risk_management, ) diff --git a/app/telegram/handlers/start_trading.py b/app/telegram/handlers/start_trading.py index 351bb33..36c8801 100644 --- a/app/telegram/handlers/start_trading.py +++ b/app/telegram/handlers/start_trading.py @@ -12,9 +12,7 @@ from app.bybit.open_positions import start_trading_cycle from app.helper_functions import safe_float from app.telegram.tasks.tasks import ( add_start_task_merged, - add_start_task_switch, - cancel_start_task_merged, - cancel_start_task_switch, + cancel_start_task_merged ) from logger_helper.logger_helper import LOGGING_CONFIG @@ -35,97 +33,20 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non """ 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 ) position = next((d for d in deals if d.get("symbol") == symbol), None) + 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 safe_float(size) > 0: await callback_query.answer( - text="У вас есть активная позиция в режиме хеджирования. " - "Открытие сделки в одностороннем режиме невозможно.", - ) - return - - if position_idx == 0 and safe_float(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 - ) - position = next((d for d in deals if d.get("symbol") == symbol), None) - 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: - await callback_query.answer( - text="Торговля уже запущена в одностороннем режиме для данного инструмента" + text="У вас есть активная позиция", ) return @@ -151,12 +72,9 @@ async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) - tg_id=callback_query.from_user.id, symbol=symbol, auto_trading=True, - side=side, ) res = await start_trading_cycle( tg_id=callback_query.from_user.id, - side=side, - switch_side_mode=False, ) error_messages = { @@ -167,9 +85,10 @@ async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) - "ab not enough for new order": "Недостаточно средств для создания нового ордера", "InvalidRequestError": "Произошла ошибка при запуске торговли.", "Order does not meet minimum order value": "Сумма ордера не достаточна для запуска торговли", - "position idx not match position mode": "Торговля уже запущена в режиме хеджирования на продажу для данного инструмента", + "position idx not match position mode": "Ошибка режима позиции для данного инструмента", "Qty invalid": "Некорректное значение ордера для данного инструмента", "The number of contracts exceeds maximum limit allowed": "️️Количество контрактов превышает допустимое максимальное количество контрактов", + "The number of contracts exceeds minimum limit allowed": "️️Количество контрактов превышает допустимое минимальное количество контрактов", } if res == "OK": @@ -180,7 +99,6 @@ async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) - tg_id=callback_query.from_user.id, symbol=symbol, auto_trading=False, - side=side, ) text = error_messages.get(res, "Произошла ошибка при запуске торговли") await callback_query.message.edit_text( @@ -202,189 +120,8 @@ async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) - 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 - ) - position = next((d for d in deals if d.get("symbol") == symbol), None) - 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": - await callback_query.answer( - text="Торговля уже запущена в режиме хеджирования на покупку для данного инструмента" - ) - return - - if position_idx == 2 and safe_float(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 - - cancel_start_task_switch(user_id=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_switch, - ) - await rq.set_start_timer( - tg_id=callback_query.from_user.id, timer_start=0 - ) - 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, - ) - if side == "Buy": - r_side = "Sell" - else: - r_side = "Buy" - await rq.set_auto_trading( - tg_id=callback_query.from_user.id, - symbol=symbol, - auto_trading=True, - side=r_side, - ) - 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": "Сумма ордера не достаточна для запуска торговли", - "position idx not match position mode": "Торговля уже запущена в режиме хеджирования на продажу для данного инструмента", - "Qty invalid": "Некорректное значение ордера для данного инструмента", - "The number of contracts exceeds maximum limit allowed": "️ ️️Количество контрактов превышает допустимое максимальное количество контрактов", - } - - if res == "OK": - await callback_query.message.edit_text(text="Торговля запущена") - await state.clear() - else: - await rq.set_auto_trading( - tg_id=callback_query.from_user.id, - symbol=symbol, - auto_trading=False, - side=side, - ) - if side == "Buy": - r_side = "Sell" - else: - r_side = "Buy" - await rq.set_auto_trading( - tg_id=callback_query.from_user.id, - symbol=symbol, - auto_trading=False, - side=r_side, - ) - text = error_messages.get(res, "Произошла ошибка при запуске торговли") - await callback_query.message.edit_text( - text=text, reply_markup=kbi.profile_bybit - ) - - await callback_query.message.edit_text("Запуск торговли...") - task = asyncio.create_task(delay_start()) - await add_start_task_switch(user_id=callback_query.from_user.id, task=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( - lambda c: c.data == "cancel_timer_merged" or c.data == "cancel_timer_switch" + lambda c: c.data == "cancel_timer_merged" ) async def cancel_start_trading( callback_query: CallbackQuery, state: FSMContext @@ -400,8 +137,6 @@ async def cancel_start_trading( await state.clear() if callback_query.data == "cancel_timer_merged": cancel_start_task_merged(user_id=callback_query.from_user.id) - elif callback_query.data == "cancel_timer_switch": - cancel_start_task_switch(user_id=callback_query.from_user.id) await callback_query.message.edit_text( text="Запуск торговли отменен", reply_markup=kbi.profile_bybit ) diff --git a/app/telegram/handlers/stop_trading.py b/app/telegram/handlers/stop_trading.py index e9fc6c4..64e21e0 100644 --- a/app/telegram/handlers/stop_trading.py +++ b/app/telegram/handlers/stop_trading.py @@ -45,12 +45,10 @@ async def stop_all_trading(callback_query: CallbackQuery, state: FSMContext): for active_auto_trading in user_auto_trading_list: 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.message.edit_text( diff --git a/app/telegram/keyboards/inline.py b/app/telegram/keyboards/inline.py index 7c1f559..8635eb2 100644 --- a/app/telegram/keyboards/inline.py +++ b/app/telegram/keyboards/inline.py @@ -35,13 +35,7 @@ main_menu = InlineKeyboardMarkup( text="Сменить торговую пару", callback_data="change_symbol" ) ], - [InlineKeyboardButton(text="Мои сделки", callback_data="my_deals")], [InlineKeyboardButton(text="Начать торговлю", callback_data="start_trading")], - [ - InlineKeyboardButton( - text="Остановить торговлю", callback_data="stop_trading" - ) - ], ] ) @@ -67,62 +61,39 @@ main_settings = InlineKeyboardMarkup( # additional_settings -def get_additional_settings_keyboard( - current_order_type: str, conditional_order: str +def get_additional_settings_keyboard(mode: str ) -> InlineKeyboardMarkup: """ Create keyboard for additional settings - :param current_order_type: Market, Limit or Conditional - :param conditional_order: Market or Limit + :param mode: Trade mode :return: InlineKeyboardMarkup """ buttons = [ [ - InlineKeyboardButton(text="Режим позиции", callback_data="trade_mode"), + InlineKeyboardButton(text="Режим торговли", callback_data="trade_mode"), InlineKeyboardButton(text="Тип маржи", callback_data="margin_type"), ], [ InlineKeyboardButton( text="Размер кредитного плеча", callback_data="leverage" ), - InlineKeyboardButton(text="Тип ордера", callback_data="order_type"), + InlineKeyboardButton( + text="Базовая ставка", callback_data="order_quantity"), ], + [ - InlineKeyboardButton( - text="Количество ордера", callback_data="order_quantity" - ), InlineKeyboardButton( text="Коэффициент мартингейла", callback_data="martingale_factor" ), + InlineKeyboardButton(text="Триггер цена", callback_data="trigger_price" + + ), ], ] - if current_order_type == "Conditional": + if mode == "Switch": buttons.append( - [ - InlineKeyboardButton( - text="Тип условного ордера", callback_data="conditional_order_type" - ) - ] - ) - buttons.append( - [InlineKeyboardButton(text="Триггер цена", callback_data="trigger_price")] - ) - if conditional_order == "Limit": - buttons.append( - [ - InlineKeyboardButton( - text="Цена лимитного ордера", callback_data="limit_price" - ) - ] - ) - elif current_order_type == "Limit": - buttons.append( - [ - InlineKeyboardButton( - text="Цена лимитного ордера", callback_data="limit_price" - ) - ] + [InlineKeyboardButton(text="Направление первой сделки", callback_data="switch_side_start")] ) buttons.append( @@ -143,40 +114,31 @@ def get_additional_settings_keyboard( return InlineKeyboardMarkup(inline_keyboard=buttons) -order_type = InlineKeyboardMarkup( - inline_keyboard=[ - [ - InlineKeyboardButton(text="Рыночный", callback_data="Market"), - InlineKeyboardButton(text="Лимитный", callback_data="Limit"), - ], - [InlineKeyboardButton(text="Условный", callback_data="Conditional")], - [ - InlineKeyboardButton(text="Назад", callback_data="additional_settings"), - InlineKeyboardButton(text="На главную", callback_data="profile_bybit"), - ], - ] -) - -conditional_order_type = InlineKeyboardMarkup( - inline_keyboard=[ - [ - InlineKeyboardButton(text="Рыночный", callback_data="set_market"), - InlineKeyboardButton(text="Лимитный", callback_data="set_limit"), - ], - [ - InlineKeyboardButton(text="Назад", callback_data="additional_settings"), - InlineKeyboardButton(text="На главную", callback_data="profile_bybit"), - ], - ] -) - trade_mode = InlineKeyboardMarkup( inline_keyboard=[ [ InlineKeyboardButton( - text="Односторонний режим", callback_data="Merged_Single" + text="Лонг", callback_data="Long" + ), + InlineKeyboardButton(text="Шорт", callback_data="Short"), + InlineKeyboardButton(text="Свитч", callback_data="Switch"), + ], + [ + InlineKeyboardButton(text="Назад", callback_data="additional_settings"), + InlineKeyboardButton(text="На главную", callback_data="profile_bybit"), + ], + ] +) + +switch_side = InlineKeyboardMarkup( + inline_keyboard=[ + [ + InlineKeyboardButton( + text="По направлению", callback_data="switch_direction" + ), + InlineKeyboardButton( + text="Противоположно", callback_data="switch_opposite" ), - InlineKeyboardButton(text="Хеджирование", callback_data="Both_Sides"), ], [ InlineKeyboardButton(text="Назад", callback_data="additional_settings"), @@ -207,21 +169,6 @@ back_to_additional_settings = InlineKeyboardMarkup( ] ) -change_limit_price = InlineKeyboardMarkup( - inline_keyboard=[ - [ - InlineKeyboardButton( - text="Установить цену", callback_data="set_limit_price" - ), - InlineKeyboardButton(text="Последняя цена", callback_data="last_price"), - ], - [ - InlineKeyboardButton(text="Назад", callback_data="additional_settings"), - InlineKeyboardButton(text="На главную", callback_data="profile_bybit"), - ], - ] -) - back_to_change_limit_price = InlineKeyboardMarkup( inline_keyboard=[ [ @@ -239,17 +186,12 @@ risk_management = InlineKeyboardMarkup( inline_keyboard=[ [ InlineKeyboardButton( - text="Изм. цены прибыли", callback_data="take_profit_percent" + text="Тейк-профит", callback_data="take_profit_percent" ), InlineKeyboardButton( - text="Изм. цены убытка", callback_data="stop_loss_percent" + text="Стоп-лосс", callback_data="stop_loss_percent" ), ], - [ - InlineKeyboardButton( - text="Максимальный риск", callback_data="max_risk_percent" - ) - ], [InlineKeyboardButton(text="Комиссия биржи", callback_data="commission_fee")], [ InlineKeyboardButton(text="Назад", callback_data="main_settings"), @@ -391,48 +333,6 @@ def make_close_orders_keyboard(symbol_order: str, order_id: str): # START TRADING -merged_start_trading = InlineKeyboardMarkup( - inline_keyboard=[ - [ - InlineKeyboardButton(text="Лонг", callback_data="long"), - InlineKeyboardButton(text="Шорт", callback_data="short"), - ], - [InlineKeyboardButton(text="Свитч", callback_data="switch")], - [InlineKeyboardButton(text="Назад", callback_data="profile_bybit")], - ] -) - -both_start_trading = InlineKeyboardMarkup( - inline_keyboard=[ - [ - InlineKeyboardButton(text="Лонг", callback_data="long"), - InlineKeyboardButton(text="Шорт", callback_data="short"), - ], - [InlineKeyboardButton(text="Назад", callback_data="profile_bybit")], - ] -) - -switch_side = InlineKeyboardMarkup( - inline_keyboard=[ - [ - InlineKeyboardButton(text="Лонг", callback_data="switch_long"), - InlineKeyboardButton(text="Шорт", callback_data="switch_short"), - ], - [ - InlineKeyboardButton( - text="По направлению", callback_data="switch_direction" - ), - InlineKeyboardButton( - text="Противоположно", callback_data="switch_opposite" - ), - ], - [ - InlineKeyboardButton(text="Назад", callback_data="start_trading"), - InlineKeyboardButton(text="На главную", callback_data="profile_bybit"), - ], - ] -) - back_to_start_trading = InlineKeyboardMarkup( inline_keyboard=[ [ diff --git a/app/telegram/keyboards/reply.py b/app/telegram/keyboards/reply.py index fed4241..f4b8716 100644 --- a/app/telegram/keyboards/reply.py +++ b/app/telegram/keyboards/reply.py @@ -1,8 +1,10 @@ from aiogram.types import KeyboardButton, ReplyKeyboardMarkup profile = ReplyKeyboardMarkup( - keyboard=[[KeyboardButton(text="Панель Bybit"), KeyboardButton(text="Профиль")], - [KeyboardButton(text="Подключить платформу Bybit")]], + keyboard=[ + [KeyboardButton(text="Панель Bybit"), KeyboardButton(text="Профиль")], + [KeyboardButton(text="Подключить платформу Bybit")], + ], resize_keyboard=True, one_time_keyboard=True, input_field_placeholder="Выберите пункт меню...", diff --git a/database/models.py b/database/models.py index af5e5cd..df8845e 100644 --- a/database/models.py +++ b/database/models.py @@ -91,14 +91,10 @@ class UserAdditionalSettings(Base): ForeignKey("users.id", ondelete="CASCADE"), nullable=False, unique=True) trade_mode = Column(String, nullable=False, default="Merged_Single") - order_type = Column(String, nullable=False, default="Market") - conditional_order_type = Column(String, nullable=False, default="Market") - limit_price = Column(Float, nullable=False, default=0.0) + switch_side = Column(String, nullable=False, default="По направлению") trigger_price = Column(Float, nullable=False, default=0.0) margin_type = Column(String, nullable=False, default="ISOLATED_MARGIN") leverage = Column(String, nullable=False, default="10") - leverage_to_buy = Column(String, nullable=False, default="10") - leverage_to_sell = Column(String, nullable=False, default="10") order_quantity = Column(Float, nullable=False, default=5.0) martingale_factor = Column(Float, nullable=False, default=1.0) max_bets_in_series = Column(Integer, nullable=False, default=1) @@ -114,9 +110,8 @@ class UserRiskManagement(Base): user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, unique=True) - take_profit_percent = Column(Integer, nullable=False, default=1) - stop_loss_percent = Column(Integer, nullable=False, default=1) - max_risk_percent = Column(Integer, nullable=False, default=100) + take_profit_percent = Column(Float, nullable=False, default=1) + stop_loss_percent = Column(Float, nullable=False, default=1) commission_fee = Column(String, nullable=False, default="Yes_commission_fee") user = relationship("User", back_populates="user_risk_management") @@ -148,13 +143,9 @@ class UserDeals(Base): current_step = Column(Integer, nullable=True) symbol = Column(String, nullable=True) trade_mode = Column(String, nullable=True) - trading_type = Column(String, nullable=True) + base_quantity = Column(Float, nullable=True) margin_type = Column(String, nullable=True) - order_type = Column(String, nullable=True) - conditional_order_type = Column(String, nullable=True) leverage = Column(String, nullable=True) - leverage_to_buy = Column(String, nullable=True) - leverage_to_sell = Column(String, nullable=True) last_side = Column(String, nullable=True) closed_side = Column(String, nullable=True) order_quantity = Column(Float, nullable=True) @@ -162,9 +153,6 @@ class UserDeals(Base): max_bets_in_series = Column(Integer, nullable=True) take_profit_percent = Column(Integer, nullable=True) stop_loss_percent = Column(Integer, nullable=True) - max_risk_percent = Column(Integer, nullable=True) - switch_side_mode = Column(Boolean, nullable=True) - limit_price = Column(Float, nullable=True) trigger_price = Column(Float, nullable=True) user = relationship("User", back_populates="user_deals") @@ -184,7 +172,7 @@ class UserAutoTrading(Base): nullable=False) symbol = Column(String, nullable=True) auto_trading = Column(Boolean, nullable=True) - side = Column(String, nullable=True) fee = Column(Float, nullable=True) + total_fee = Column(Float, nullable=True) user = relationship("User", back_populates="user_auto_trading") \ No newline at end of file diff --git a/database/request.py b/database/request.py index 78c401f..6492cea 100644 --- a/database/request.py +++ b/database/request.py @@ -198,15 +198,12 @@ async def create_user_additional_settings(tg_id: int) -> None: # Create the user additional settings user_additional_settings = UserAdditionalSettings( user=user, - trade_mode="Merged_Single", # Default value - order_type="Market", - conditional_order_type="Market", + trade_mode="Long", # Default value + switch_side="По направлению", margin_type="ISOLATED_MARGIN", leverage="10", - leverage_to_buy="10", - leverage_to_sell="10", - order_quantity=5.0, - martingale_factor=1.0, + order_quantity=1.0, + martingale_factor=2.0, max_bets_in_series=10, ) session.add(user_additional_settings) @@ -283,90 +280,6 @@ async def set_trade_mode(tg_id: int, trade_mode: str) -> bool: return False -async def set_order_type(tg_id: int, order_type: str) -> bool: - """ - Set order type for a user in the database. - :param tg_id: Telegram user ID - :param order_type: "Market" or "Limit" - :return: True if successful, False otherwise - """ - try: - async with async_session() as session: - result = await session.execute( - select(User) - .options(joinedload(User.user_additional_settings)) - .filter_by(tg_id=tg_id) - ) - user = result.scalars().first() - - if user: - if user.user_additional_settings: - # Updating existing record - user.user_additional_settings.order_type = order_type - else: - # Creating new record - user_additional_settings = UserAdditionalSettings( - order_type=order_type, - user=user, - ) - session.add(user_additional_settings) - - await session.commit() - logger.info("User order type updated for user: %s", tg_id) - return True - else: - logger.error("User not found with tg_id: %s", tg_id) - return False - except Exception as e: - logger.error("Error adding/updating user order type for user %s: %s", tg_id, e) - return False - - -async def set_conditional_order_type(tg_id: int, conditional_order_type: str) -> bool: - """ - Set conditional order type for a user in the database. - :param tg_id: Telegram user ID - :param conditional_order_type: "Market" or "Limit" - :return: True if successful, False otherwise - """ - try: - async with async_session() as session: - result = await session.execute( - select(User) - .options(joinedload(User.user_additional_settings)) - .filter_by(tg_id=tg_id) - ) - user = result.scalars().first() - - if user: - if user.user_additional_settings: - # Updating existing record - user.user_additional_settings.conditional_order_type = ( - conditional_order_type - ) - else: - # Creating new record - user_additional_settings = UserAdditionalSettings( - conditional_order_type=conditional_order_type, - user=user, - ) - session.add(user_additional_settings) - - await session.commit() - logger.info("User conditional order type updated for user: %s", tg_id) - return True - else: - logger.error("User not found with tg_id: %s", tg_id) - return False - except Exception as e: - logger.error( - "Error adding/updating user conditional order type for user %s: %s", - tg_id, - e, - ) - return False - - async def set_margin_type(tg_id: int, margin_type: str) -> bool: """ Set margin type for a user in the database. @@ -406,6 +319,45 @@ async def set_margin_type(tg_id: int, margin_type: str) -> bool: return False +async def set_switch_side(tg_id: int, switch_side: str) -> bool: + """ + Set switch side for a user in the database. + :param tg_id: Telegram user ID + :param switch_side: "По направлению" or "По цене" + :return: True if successful, False otherwise + """ + try: + async with async_session() as session: + result = await session.execute( + select(User) + .options(joinedload(User.user_additional_settings)) + .filter_by(tg_id=tg_id) + ) + user = result.scalars().first() + + if user: + if user.user_additional_settings: + # Updating existing record + user.user_additional_settings.switch_side = switch_side + else: + # Creating new record + user_additional_settings = UserAdditionalSettings( + switch_side=switch_side, + user=user, + ) + session.add(user_additional_settings) + + await session.commit() + logger.info("User switch side updated for user: %s", tg_id) + return True + else: + logger.error("User not found with tg_id: %s", tg_id) + return False + except Exception as e: + logger.error("Error adding/updating user switch side for user %s: %s", tg_id, e) + return False + + async def set_leverage(tg_id: int, leverage: str) -> bool: """ Set leverage for a user in the database. @@ -445,50 +397,6 @@ async def set_leverage(tg_id: int, leverage: str) -> bool: return False -async def set_leverage_to_buy_and_sell( - tg_id: int, leverage_to_buy: str, leverage_to_sell: str -) -> bool: - """ - Set leverage for a user in the database. - :param tg_id: Telegram user ID - :param leverage_to_buy: Leverage to buy - :param leverage_to_sell: Leverage to sell - :return: True if successful, False otherwise - """ - try: - async with async_session() as session: - result = await session.execute( - select(User) - .options(joinedload(User.user_additional_settings)) - .filter_by(tg_id=tg_id) - ) - user = result.scalars().first() - - if user: - if user.user_additional_settings: - # Updating existing record - user.user_additional_settings.leverage_to_buy = leverage_to_buy - user.user_additional_settings.leverage_to_sell = leverage_to_sell - else: - # Creating new record - user_additional_settings = UserAdditionalSettings( - leverage_to_buy=leverage_to_buy, - leverage_to_sell=leverage_to_sell, - user=user, - ) - session.add(user_additional_settings) - - await session.commit() - logger.info("User leverage updated for user: %s", tg_id) - return True - else: - logger.error("User not found with tg_id: %s", tg_id) - return False - except Exception as e: - logger.error("Error adding/updating user leverage for user %s: %s", tg_id, e) - return False - - async def set_order_quantity(tg_id: int, order_quantity: float) -> bool: """ Set order quantity for a user in the database. @@ -614,45 +522,6 @@ async def set_max_bets_in_series(tg_id: int, max_bets_in_series: int) -> bool: return False -async def set_limit_price(tg_id: int, limit_price: float) -> bool: - """ - Set limit price for a user in the database. - :param tg_id: - :param limit_price: - :return: bool - """ - try: - async with async_session() as session: - result = await session.execute( - select(User) - .options(joinedload(User.user_additional_settings)) - .filter_by(tg_id=tg_id) - ) - user = result.scalars().first() - - if user: - if user.user_additional_settings: - # Updating existing record - user.user_additional_settings.limit_price = limit_price - else: - # Creating new record - user_additional_settings = UserAdditionalSettings( - limit_price=limit_price, - user=user, - ) - session.add(user_additional_settings) - - await session.commit() - logger.info("User limit price updated for user: %s", tg_id) - return True - else: - logger.error("User not found with tg_id: %s", tg_id) - return False - except Exception as e: - logger.error("Error adding/updating user limit price for user %s: %s", tg_id, e) - return False - - async def set_trigger_price(tg_id: int, trigger_price: float) -> bool: """ Set trigger price for a user in the database. @@ -718,9 +587,8 @@ async def create_user_risk_management(tg_id: int) -> None: # Create the user risk management user_risk_management = UserRiskManagement( user=user, - take_profit_percent=1, - stop_loss_percent=1, - max_risk_percent=100, + take_profit_percent=1.0, + stop_loss_percent=1.0, commission_fee="Yes_commission_fee", ) session.add(user_risk_management) @@ -758,7 +626,7 @@ async def get_user_risk_management(tg_id: int): return None -async def set_take_profit_percent(tg_id: int, take_profit_percent: int) -> bool: +async def set_take_profit_percent(tg_id: int, take_profit_percent: float) -> bool: """ Set take profit percent for a user in the database. :param tg_id: Telegram user ID @@ -799,7 +667,7 @@ async def set_take_profit_percent(tg_id: int, take_profit_percent: int) -> bool: return False -async def set_stop_loss_percent(tg_id: int, stop_loss_percent: int) -> bool: +async def set_stop_loss_percent(tg_id: int, stop_loss_percent: float) -> bool: """ Set stop loss percent for a user in the database. :param tg_id: Telegram user ID @@ -840,47 +708,6 @@ async def set_stop_loss_percent(tg_id: int, stop_loss_percent: int) -> bool: return False -async def set_max_risk_percent(tg_id: int, max_risk_percent: int) -> bool: - """ - Set max risk percent for a user in the database. - :param tg_id: Telegram user ID - :param max_risk_percent: Max risk percent - :return: True if successful, False otherwise - """ - try: - async with async_session() as session: - result = await session.execute( - select(User) - .options(joinedload(User.user_risk_management)) - .filter_by(tg_id=tg_id) - ) - user = result.scalars().first() - - if user: - if user.user_risk_management: - # Updating existing record - user.user_risk_management.max_risk_percent = max_risk_percent - else: - # Creating new record - user_risk_management = UserRiskManagement( - max_risk_percent=max_risk_percent, - user=user, - ) - session.add(user_risk_management) - - await session.commit() - logger.info("User max risk percent updated for user: %s", tg_id) - return True - else: - logger.error("User not found with tg_id: %s", tg_id) - return False - except Exception as e: - logger.error( - "Error adding/updating user max risk percent for user %s: %s", tg_id, e - ) - return False - - async def set_commission_fee(tg_id: int, commission_fee: str) -> bool: """ Set commission fee for a user in the database. @@ -1073,19 +900,13 @@ async def set_user_deal( trade_mode: str, margin_type: str, leverage: str, - leverage_to_buy: str, - leverage_to_sell: str, - order_type: str, - conditional_order_type: str, order_quantity: float, - limit_price: float, trigger_price: float, martingale_factor: float, max_bets_in_series: int, take_profit_percent: int, stop_loss_percent: int, - max_risk_percent: int, - switch_side_mode: bool, + base_quantity: float ): """ Set the user deal in the database. @@ -1096,19 +917,13 @@ async def set_user_deal( :param trade_mode: Trade mode :param margin_type: Margin type :param leverage: Leverage - :param leverage_to_buy: Leverage to buy - :param leverage_to_sell: Leverage to sell - :param order_type: Order type - :param conditional_order_type: Conditional order type :param order_quantity: Order quantity - :param limit_price: Limit price :param trigger_price: Trigger price :param martingale_factor: Martingale factor :param max_bets_in_series: Max bets in series :param take_profit_percent: Take profit percent :param stop_loss_percent: Stop loss percent - :param max_risk_percent: Max risk percent - :param switch_side_mode: Switch side mode + :param base_quantity: Base quantity :return: bool """ try: @@ -1131,19 +946,13 @@ async def set_user_deal( deal.trade_mode = trade_mode deal.margin_type = margin_type deal.leverage = leverage - deal.leverage_to_buy = leverage_to_buy - deal.leverage_to_sell = leverage_to_sell - deal.order_type = order_type - deal.conditional_order_type = conditional_order_type deal.order_quantity = order_quantity - deal.limit_price = limit_price deal.trigger_price = trigger_price deal.martingale_factor = martingale_factor deal.max_bets_in_series = max_bets_in_series deal.take_profit_percent = take_profit_percent deal.stop_loss_percent = stop_loss_percent - deal.max_risk_percent = max_risk_percent - deal.switch_side_mode = switch_side_mode + deal.base_quantity = base_quantity else: # Creating new record new_deal = UserDeals( @@ -1154,19 +963,13 @@ async def set_user_deal( trade_mode=trade_mode, margin_type=margin_type, leverage=leverage, - leverage_to_buy=leverage_to_buy, - leverage_to_sell=leverage_to_sell, - order_type=order_type, - conditional_order_type=conditional_order_type, order_quantity=order_quantity, - limit_price=limit_price, trigger_price=trigger_price, martingale_factor=martingale_factor, max_bets_in_series=max_bets_in_series, take_profit_percent=take_profit_percent, stop_loss_percent=stop_loss_percent, - max_risk_percent=max_risk_percent, - switch_side_mode=switch_side_mode, + base_quantity=base_quantity ) session.add(new_deal) @@ -1268,7 +1071,7 @@ async def get_all_user_auto_trading(tg_id: int): return [] -async def get_user_auto_trading(tg_id: int, symbol: str, side: str): +async def get_user_auto_trading(tg_id: int, symbol: str): """Get user auto trading from the database asynchronously.""" try: async with async_session() as session: @@ -1278,7 +1081,7 @@ async def get_user_auto_trading(tg_id: int, symbol: str, side: str): return None result_auto_trading = await session.execute( - select(UserAutoTrading).filter_by(user_id=user.id, symbol=symbol, side=side) + select(UserAutoTrading).filter_by(user_id=user.id, symbol=symbol) ) auto_trading = result_auto_trading.scalars().first() return auto_trading @@ -1287,13 +1090,12 @@ async def get_user_auto_trading(tg_id: int, symbol: str, side: str): return None -async def set_auto_trading(tg_id: int, symbol: str, auto_trading: bool, side: str) -> bool: +async def set_auto_trading(tg_id: int, symbol: str, auto_trading: bool) -> bool: """ Set the auto trading for a user in the database. :param tg_id: Telegram user ID :param symbol: Symbol :param auto_trading: Auto trading - :param side: Side :return: bool """ try: @@ -1305,7 +1107,7 @@ async def set_auto_trading(tg_id: int, symbol: str, auto_trading: bool, side: st return False result = await session.execute( - select(UserAutoTrading).filter_by(user_id=user.id, symbol=symbol, side=side) + select(UserAutoTrading).filter_by(user_id=user.id, symbol=symbol) ) record = result.scalars().first() if record: @@ -1315,7 +1117,6 @@ async def set_auto_trading(tg_id: int, symbol: str, auto_trading: bool, side: st user_id=user.id, symbol=symbol, auto_trading=auto_trading, - side=side ) session.add(new_record) await session.commit() @@ -1326,12 +1127,11 @@ async def set_auto_trading(tg_id: int, symbol: str, auto_trading: bool, side: st return False -async def set_fee_user_auto_trading(tg_id: int, symbol: str, side: str, fee: float) -> bool: +async def set_fee_user_auto_trading(tg_id: int, symbol: 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: """ @@ -1344,7 +1144,7 @@ async def set_fee_user_auto_trading(tg_id: int, symbol: str, side: str, fee: flo return False result = await session.execute( - select(UserAutoTrading).filter_by(user_id=user.id, symbol=symbol, side=side) + select(UserAutoTrading).filter_by(user_id=user.id, symbol=symbol) ) record = result.scalars().first() @@ -1355,7 +1155,6 @@ async def set_fee_user_auto_trading(tg_id: int, symbol: str, side: str, fee: flo user_id=user.id, symbol=symbol, fee=fee, - side=side ) session.add(user_fee) await session.commit() @@ -1364,3 +1163,41 @@ async def set_fee_user_auto_trading(tg_id: int, symbol: str, side: str, fee: flo 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 + + +async def set_total_fee_user_auto_trading(tg_id: int, symbol: str, total_fee: float) -> bool: + """ + Set the total fee for a user auto trading in the database. + :param tg_id: Telegram user ID + :param symbol: Symbol + :param total_fee: Total fee + :return: bool + """ + 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) + ) + record = result.scalars().first() + + if record: + record.total_fee = total_fee + else: + user_total_fee = UserAutoTrading( + user_id=user.id, + symbol=symbol, + total_fee=total_fee, + ) + session.add(user_total_fee) + await session.commit() + logger.info("Set total fee for user %s and symbol %s", tg_id, symbol) + return True + except Exception as e: + logger.error("Error setting user auto trading total fee for user %s and symbol %s: %s", tg_id, symbol, e) + return False