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: