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_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 from app.bybit.set_functions.set_margin_mode import set_margin_mode 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 ) -> str | None: """ Start trading cycle :param tg_id: Telegram user ID """ 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 order_quantity = additional_data.order_quantity 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 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: 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_quantity=order_quantity, trigger_price=trigger_price, margin_type=margin_type, leverage=leverage, take_profit_percent=take_profit_percent, stop_loss_percent=stop_loss_percent, commission_fee_percent=total_commission ) 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, order_quantity=order_quantity, 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, base_quantity=order_quantity ) 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", "The number of contracts exceeds minimum 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 ) -> 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 margin_type = user_deals_data.margin_type leverage = user_deals_data.leverage trigger_price = 0 take_profit_percent = user_deals_data.take_profit_percent stop_loss_percent = user_deals_data.stop_loss_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 order_quantity = user_deals_data.order_quantity base_quantity = user_deals_data.base_quantity await set_margin_mode(tg_id=tg_id, margin_mode=margin_type) 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 trade_mode == "Switch": side = "Sell" if real_side == "Buy" else "Buy" next_quantity = safe_float(order_quantity) * ( safe_float(martingale_factor) ) 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_quantity=next_quantity, trigger_price=trigger_price, margin_type=margin_type, leverage=leverage, take_profit_percent=take_profit_percent, stop_loss_percent=stop_loss_percent, commission_fee_percent=total_fee ) 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, order_quantity=next_quantity, 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, base_quantity=base_quantity ) 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_quantity: float, trigger_price: float, margin_type: str, leverage: str, take_profit_percent: float, stop_loss_percent: float, commission_fee_percent: float ) -> str | None: try: client = await get_bybit_client(tg_id=tg_id) 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) trigger_direction = 1 if trigger_price > price_symbol else 2 else: po_trigger_price = None trigger_direction = None get_leverage = safe_float(leverage) price_for_cals = trigger_price if po_trigger_price is not None else price_symbol tp_multiplier = 1 + (take_profit_percent / 100) 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_cals, symbol=symbol, leverage=get_leverage, ) if (liq_long > 0 or liq_short > 0) and price_for_cals > 0: if side == "Buy": base_tp = price_for_cals + (price_for_cals - liq_long) take_profit_price = base_tp + commission_fee_percent else: 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 stop_loss_price = None else: if side == "Buy": 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_cals * ( 1 - (take_profit_percent / 100) - commission_fee_percent ) 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": "Market", "qty": str(qty_formatted), "triggerDirection": trigger_direction, "triggerPrice": po_trigger_price, "triggerBy": "LastPrice", "timeInForce": "GTC", "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, } 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", "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: 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