424 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			424 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
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.bybit.set_functions.set_switch_position_mode import set_switch_position_mode
 | 
						|
from app.helper_functions import 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:
 | 
						|
        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
 | 
						|
        switch_side = additional_data.switch_side
 | 
						|
        side= additional_data.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
 | 
						|
        commission_fee = risk_management_data.commission_fee
 | 
						|
        commission_place = risk_management_data.commission_place
 | 
						|
 | 
						|
        if trade_mode == "Switch":
 | 
						|
            side = side
 | 
						|
        else:
 | 
						|
            if trade_mode == "Long":
 | 
						|
                side = "Buy"
 | 
						|
            else:
 | 
						|
                side = "Sell"
 | 
						|
 | 
						|
        await set_switch_position_mode(
 | 
						|
            tg_id=tg_id,
 | 
						|
            symbol=symbol,
 | 
						|
            mode=0)
 | 
						|
        await set_margin_mode(tg_id=tg_id, margin_mode=margin_type)
 | 
						|
        await set_leverage(
 | 
						|
            tg_id=tg_id,
 | 
						|
            symbol=symbol,
 | 
						|
            leverage=leverage,
 | 
						|
        )
 | 
						|
 | 
						|
        await rq.set_user_deal(
 | 
						|
            tg_id=tg_id,
 | 
						|
            symbol=symbol,
 | 
						|
            current_step=1,
 | 
						|
            current_series=1,
 | 
						|
            trade_mode=trade_mode,
 | 
						|
            side_mode=switch_side,
 | 
						|
            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,
 | 
						|
            commission_fee=commission_fee,
 | 
						|
            commission_place=commission_place
 | 
						|
        )
 | 
						|
 | 
						|
        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,
 | 
						|
        )
 | 
						|
 | 
						|
        if res == "OK":
 | 
						|
            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_profit(
 | 
						|
        tg_id: int, symbol: str, side: 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
 | 
						|
        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
 | 
						|
        side_mode = user_deals_data.side_mode
 | 
						|
        base_quantity = user_deals_data.base_quantity
 | 
						|
        current_series = user_deals_data.current_series
 | 
						|
        commission_fee = user_deals_data.commission_fee
 | 
						|
        commission_place = user_deals_data.commission_place
 | 
						|
 | 
						|
        await set_margin_mode(tg_id=tg_id, margin_mode=margin_type)
 | 
						|
        await set_leverage(
 | 
						|
            tg_id=tg_id,
 | 
						|
            symbol=symbol,
 | 
						|
            leverage=leverage,
 | 
						|
        )
 | 
						|
 | 
						|
        if trade_mode == "Switch":
 | 
						|
            if side_mode == "Противоположно":
 | 
						|
                s_side = "Sell" if side == "Buy" else "Buy"
 | 
						|
            else:
 | 
						|
                s_side = side
 | 
						|
        else:
 | 
						|
            s_side = side
 | 
						|
 | 
						|
        next_series = current_series + 1
 | 
						|
 | 
						|
        await rq.set_user_deal(
 | 
						|
            tg_id=tg_id,
 | 
						|
            symbol=symbol,
 | 
						|
            current_step=1,
 | 
						|
            current_series=next_series,
 | 
						|
            trade_mode=trade_mode,
 | 
						|
            side_mode=side_mode,
 | 
						|
            margin_type=margin_type,
 | 
						|
            leverage=leverage,
 | 
						|
            order_quantity=base_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,
 | 
						|
            commission_fee=commission_fee,
 | 
						|
            commission_place=commission_place
 | 
						|
        )
 | 
						|
 | 
						|
        res = await open_positions(
 | 
						|
            tg_id=tg_id,
 | 
						|
            symbol=symbol,
 | 
						|
            side=s_side,
 | 
						|
            order_quantity=base_quantity,
 | 
						|
            trigger_price=trigger_price,
 | 
						|
            margin_type=margin_type,
 | 
						|
            leverage=leverage,
 | 
						|
            take_profit_percent=take_profit_percent,
 | 
						|
            stop_loss_percent=stop_loss_percent,
 | 
						|
        )
 | 
						|
 | 
						|
        if res == "OK":
 | 
						|
            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_profit: %s", e)
 | 
						|
        return None
 | 
						|
 | 
						|
 | 
						|
async def trading_cycle(
 | 
						|
        tg_id: int, symbol: str, 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)
 | 
						|
        commission_fee = user_deals_data.commission_fee
 | 
						|
        commission_place = user_deals_data.commission_place
 | 
						|
        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
 | 
						|
        side_mode = user_deals_data.side_mode
 | 
						|
        current_series = user_deals_data.current_series
 | 
						|
 | 
						|
        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"
 | 
						|
 | 
						|
        await set_margin_mode(tg_id=tg_id, margin_mode=margin_type)
 | 
						|
        await set_leverage(
 | 
						|
            tg_id=tg_id,
 | 
						|
            symbol=symbol,
 | 
						|
            leverage=leverage,
 | 
						|
        )
 | 
						|
        total_quantity = next_quantity
 | 
						|
        if commission_fee == "Yes_commission_fee":
 | 
						|
            if commission_place == "Commission_for_qty":
 | 
						|
                total_quantity = next_quantity + total_fee
 | 
						|
 | 
						|
        if trade_mode == "Switch":
 | 
						|
            if side == "Buy":
 | 
						|
                r_side = "Sell"
 | 
						|
            else:
 | 
						|
                r_side = "Buy"
 | 
						|
        else:
 | 
						|
            r_side = side
 | 
						|
 | 
						|
        await rq.set_user_deal(
 | 
						|
            tg_id=tg_id,
 | 
						|
            symbol=symbol,
 | 
						|
            current_step=current_step,
 | 
						|
            current_series=current_series,
 | 
						|
            trade_mode=trade_mode,
 | 
						|
            side_mode=side_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,
 | 
						|
            commission_fee=commission_fee,
 | 
						|
            commission_place=commission_place
 | 
						|
        )
 | 
						|
 | 
						|
        res = await open_positions(
 | 
						|
            tg_id=tg_id,
 | 
						|
            symbol=symbol,
 | 
						|
            side=r_side,
 | 
						|
            order_quantity=total_quantity,
 | 
						|
            trigger_price=trigger_price,
 | 
						|
            margin_type=margin_type,
 | 
						|
            leverage=leverage,
 | 
						|
            take_profit_percent=take_profit_percent,
 | 
						|
            stop_loss_percent=stop_loss_percent,
 | 
						|
        )
 | 
						|
 | 
						|
        if res == "OK":
 | 
						|
            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
 | 
						|
) -> str | None:
 | 
						|
    try:
 | 
						|
        client = await get_bybit_client(tg_id=tg_id)
 | 
						|
        user_deals_data = await rq.get_user_deal_by_symbol(tg_id=tg_id, symbol=symbol)
 | 
						|
        commission_fee = user_deals_data.commission_fee
 | 
						|
        commission_place = user_deals_data.commission_place
 | 
						|
        user_auto_trading_data = await rq.get_user_auto_trading(tg_id=tg_id, symbol=symbol)
 | 
						|
        total_fee = user_auto_trading_data.total_fee
 | 
						|
        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(leverage)) / safe_float(price_symbol)
 | 
						|
        decimals = abs(int(round(math.log10(qty_step))))
 | 
						|
        qty_format = math.floor(qty / qty_step) * qty_step
 | 
						|
        qty_formatted = round(qty_format, 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
 | 
						|
 | 
						|
        price_for_cals = trigger_price if po_trigger_price is not None else price_symbol
 | 
						|
 | 
						|
        if qty_formatted <= 0:
 | 
						|
            return "Order does not meet minimum order value"
 | 
						|
 | 
						|
        total_commission = 0
 | 
						|
        if commission_fee == "Yes_commission_fee":
 | 
						|
            if commission_place == "Commission_for_tp":
 | 
						|
                total_commission = safe_float(total_fee) / qty_formatted
 | 
						|
 | 
						|
        if margin_type == "ISOLATED_MARGIN":
 | 
						|
            if side == "Buy":
 | 
						|
                take_profit_price = price_for_cals * (
 | 
						|
                        1 + take_profit_percent / 100) + total_commission
 | 
						|
                stop_loss_price = None
 | 
						|
            else:
 | 
						|
                take_profit_price = price_for_cals * (
 | 
						|
                        1 - take_profit_percent / 100) - total_commission
 | 
						|
                stop_loss_price = None
 | 
						|
        else:
 | 
						|
            if side == "Buy":
 | 
						|
                take_profit_price = price_for_cals * (
 | 
						|
                            1 + take_profit_percent / 100) + total_commission
 | 
						|
                stop_loss_price = price_for_cals * (1 - stop_loss_percent / 100)
 | 
						|
            else:
 | 
						|
                take_profit_price = price_for_cals * (
 | 
						|
                            1 - take_profit_percent / 100) - total_commission
 | 
						|
                stop_loss_price = price_for_cals * (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": "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, exc_info=True)
 | 
						|
        return None
 |