2
0
forked from kodorvan/stcs
Files
stcs/app/bybit/open_positions.py

340 lines
12 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 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:
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)
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
total_commission = 0
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"
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,
)
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(leverage)) / 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
if qty_formatted <= 0:
return "Order does not meet minimum order value"
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 / qty_formatted
else:
base_tp = price_for_cals - (liq_short - price_for_cals)
take_profit_price = base_tp - commission_fee_percent / qty_formatted
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 * (1 + take_profit_percent / 100) + commission_fee_percent / qty_formatted
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 / qty_formatted
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