import logging.config 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_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 logging.config.dictConfig(LOGGING_CONFIG) logger = logging.getLogger("open_positions") async def start_trading_cycle( tg_id: int, side: str, switch_side_mode: bool ) -> str | None: """ Start trading cycle :param tg_id: Telegram user ID :param side: Buy or Sell :param switch_side_mode: switch_side_mode """ try: 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) trade_mode = additional_data.trade_mode 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, ) else: 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, ) if res == "OK": await rq.set_user_deal( tg_id=tg_id, symbol=symbol, last_side=side, current_step=1, 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, ) return "OK" return ( res if res in { "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", } else None ) except Exception as e: logger.error("Error in start_trading: %s", e) return None async def trading_cycle( tg_id: int, symbol: str, reverse_side: str, size: str ) -> str | None: try: user_deals_data = await rq.get_user_deal_by_symbol(tg_id=tg_id, symbol=symbol) 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 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 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, ) if reverse_side == "Buy": real_side = "Sell" else: real_side = "Buy" side = real_side if switch_side_mode: side = "Sell" if real_side == "Buy" else "Buy" next_quantity = safe_float(size) * ( safe_float(martingale_factor) ** current_step ) current_step += 1 if max_bets_in_series < current_step: return "Max bets in series" res = await open_positions( 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, ) if res == "OK": await rq.set_user_deal( tg_id=tg_id, symbol=symbol, last_side=side, current_step=current_step, 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, ) return "OK" return ( res if res in { "Risk is too high for this trade", "ab not enough for new order", "InvalidRequestError", "The number of contracts exceeds maximum limit allowed", } else None ) except Exception as e: logger.error("Error in trading_cycle: %s", e) return None 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, ) -> 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 if order_type == "Conditional": 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) 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) 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 margin_type == "ISOLATED_MARGIN": liq_long, liq_short = await get_liquidation_price( tg_id=tg_id, entry_price=price_for_calc, symbol=symbol, leverage=get_leverage, ) if (liq_long > 0 or liq_short > 0) and price_for_calc > 0: if side == "Buy": base_tp = price_for_calc + (price_for_calc - liq_long) take_profit_price = base_tp + total_commission else: base_tp = price_for_calc - (liq_short - price_for_calc) take_profit_price = base_tp - total_commission take_profit_price = max(take_profit_price, 0) else: take_profit_price = None 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) else: take_profit_price = price_for_calc * ( 1 - (take_profit_percent / 100) - total_commission ) stop_loss_price = price_for_calc * (1 + stop_loss_percent / 100) take_profit_price = max(take_profit_price, 0) stop_loss_price = max(stop_loss_price, 0) # Place order order_params = { "category": "linear", "symbol": symbol, "side": side, "orderType": order_type, "qty": str(order_quantity), "triggerDirection": trigger_direction, "triggerPrice": po_trigger_price, "triggerBy": "LastPrice", "timeInForce": "GTC", "positionIdx": po_position_idx, "tpslMode": tpsl_mode, "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: logger.info("Position opened for user: %s", tg_id) return "OK" logger.error("Error opening position for user: %s", tg_id) return None except InvalidRequestError as e: error_text = str(e) known_errors = { "Order does not meet minimum order value": "Order does not meet minimum order value", "estimated will trigger liq": "estimated will trigger liq", "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", } for key, msg in known_errors.items(): if key in error_text: logger.error(msg) return msg logger.error("InvalidRequestError: %s", e) return "InvalidRequestError" except Exception as e: logger.error("Error opening position for user %s: %s", tg_id, e) return None