Compare commits

...

13 Commits

13 changed files with 500 additions and 310 deletions

View File

@@ -0,0 +1,44 @@
"""Added TP_SL and PNL
Revision ID: 3fca121b7554
Revises: adf3d2991896
Create Date: 2025-10-29 11:07:45.350771
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy import inspect
# revision identifiers, used by Alembic.
revision: str = '3fca121b7554'
down_revision: Union[str, Sequence[str], None] = 'adf3d2991896'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
conn = op.get_bind()
inspector = inspect(conn)
columns = [col['name'] for col in inspector.get_columns('user_deals')]
if 'pnl_series' not in columns:
op.add_column('user_deals', sa.Column('pnl_series', sa.Float(), nullable=True))
if 'take_profit' not in columns:
op.add_column('user_deals', sa.Column('take_profit', sa.Boolean(), nullable=False, server_default=sa.false()))
if 'stop_loss' not in columns:
op.add_column('user_deals', sa.Column('stop_loss', sa.Boolean(), nullable=False, server_default=sa.false()))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user_deals', 'stop_loss')
op.drop_column('user_deals', 'take_profit')
op.drop_column('user_deals', 'pnl_series')
# ### end Alembic commands ###

View File

@@ -0,0 +1,49 @@
"""Fixed TP_SL type
Revision ID: 8329c0994b26
Revises: 3fca121b7554
Create Date: 2025-10-29 13:07:52.161139
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '8329c0994b26'
down_revision: Union[str, Sequence[str], None] = '3fca121b7554'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade():
with op.batch_alter_table('user_deals', recreate='always') as batch_op:
# Добавляем новую колонку с нужным типом
batch_op.add_column(sa.Column('take_profit_new', sa.Float(), nullable=False, server_default='0'))
# После закрытия batch создается и переименовывается таблица.
# Теперь мы можем обновить данные.
op.execute(
"UPDATE user_deals SET take_profit_new = CAST(take_profit AS FLOAT)"
)
with op.batch_alter_table('user_deals', recreate='always') as batch_op:
# Удаляем старую колонку
batch_op.drop_column('take_profit')
# Меняем имя новой колонки на старое
batch_op.alter_column('take_profit_new', new_column_name='take_profit')
def downgrade():
# Аналогично, но в обратном порядке и типе
with op.batch_alter_table('user_deals', recreate='always') as batch_op:
batch_op.add_column(sa.Column('take_profit_old', sa.Boolean(), nullable=False, server_default='0'))
op.execute(
"UPDATE user_deals SET take_profit_old = CAST(take_profit AS BOOLEAN)"
)
with op.batch_alter_table('user_deals', recreate='always') as batch_op:
batch_op.drop_column('take_profit')
batch_op.alter_column('take_profit_old', new_column_name='take_profit')

View File

@@ -30,7 +30,7 @@ async def start_trading_cycle(
risk_management_data = await rq.get_user_risk_management(tg_id=tg_id) risk_management_data = await rq.get_user_risk_management(tg_id=tg_id)
trade_mode = additional_data.trade_mode trade_mode = additional_data.trade_mode
switch_side = additional_data.switch_side switch_side = additional_data.switch_side
side= additional_data.side side = additional_data.side
margin_type = additional_data.margin_type margin_type = additional_data.margin_type
leverage = additional_data.leverage leverage = additional_data.leverage
order_quantity = additional_data.order_quantity order_quantity = additional_data.order_quantity
@@ -54,12 +54,6 @@ async def start_trading_cycle(
tg_id=tg_id, tg_id=tg_id,
symbol=symbol, symbol=symbol,
mode=0) 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( await rq.set_user_deal(
tg_id=tg_id, tg_id=tg_id,
@@ -78,7 +72,8 @@ async def start_trading_cycle(
stop_loss_percent=stop_loss_percent, stop_loss_percent=stop_loss_percent,
base_quantity=order_quantity, base_quantity=order_quantity,
commission_fee=commission_fee, commission_fee=commission_fee,
commission_place=commission_place commission_place=commission_place,
pnl_series=0
) )
res = await open_positions( res = await open_positions(
@@ -87,10 +82,7 @@ async def start_trading_cycle(
side=side, side=side,
order_quantity=order_quantity, order_quantity=order_quantity,
trigger_price=trigger_price, trigger_price=trigger_price,
margin_type=margin_type, leverage=leverage
leverage=leverage,
take_profit_percent=take_profit_percent,
stop_loss_percent=stop_loss_percent,
) )
if res == "OK": if res == "OK":
@@ -109,7 +101,8 @@ async def start_trading_cycle(
"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",
"Order placement failed as your position may exceed the max",
} }
else None else None
) )
@@ -171,7 +164,8 @@ async def trading_cycle_profit(
stop_loss_percent=stop_loss_percent, stop_loss_percent=stop_loss_percent,
base_quantity=base_quantity, base_quantity=base_quantity,
commission_fee=commission_fee, commission_fee=commission_fee,
commission_place=commission_place commission_place=commission_place,
pnl_series=0
) )
res = await open_positions( res = await open_positions(
@@ -180,10 +174,7 @@ async def trading_cycle_profit(
side=s_side, side=s_side,
order_quantity=base_quantity, order_quantity=base_quantity,
trigger_price=trigger_price, trigger_price=trigger_price,
margin_type=margin_type, leverage=leverage
leverage=leverage,
take_profit_percent=take_profit_percent,
stop_loss_percent=stop_loss_percent,
) )
if res == "OK": if res == "OK":
@@ -197,6 +188,7 @@ async def trading_cycle_profit(
"ab not enough for new order", "ab not enough for new order",
"InvalidRequestError", "InvalidRequestError",
"The number of contracts exceeds maximum limit allowed", "The number of contracts exceeds maximum limit allowed",
"Order placement failed as your position may exceed the max",
} }
else None else None
) )
@@ -227,6 +219,7 @@ async def trading_cycle(
base_quantity = user_deals_data.base_quantity base_quantity = user_deals_data.base_quantity
side_mode = user_deals_data.side_mode side_mode = user_deals_data.side_mode
current_series = user_deals_data.current_series current_series = user_deals_data.current_series
pnl_series = user_deals_data.pnl_series
next_quantity = safe_float(order_quantity) * ( next_quantity = safe_float(order_quantity) * (
safe_float(martingale_factor) safe_float(martingale_factor)
@@ -272,7 +265,8 @@ async def trading_cycle(
stop_loss_percent=stop_loss_percent, stop_loss_percent=stop_loss_percent,
base_quantity=base_quantity, base_quantity=base_quantity,
commission_fee=commission_fee, commission_fee=commission_fee,
commission_place=commission_place commission_place=commission_place,
pnl_series=pnl_series
) )
res = await open_positions( res = await open_positions(
@@ -281,10 +275,7 @@ async def trading_cycle(
side=r_side, side=r_side,
order_quantity=total_quantity, order_quantity=total_quantity,
trigger_price=trigger_price, trigger_price=trigger_price,
margin_type=margin_type, leverage=leverage
leverage=leverage,
take_profit_percent=take_profit_percent,
stop_loss_percent=stop_loss_percent,
) )
if res == "OK": if res == "OK":
@@ -298,6 +289,7 @@ async def trading_cycle(
"ab not enough for new order", "ab not enough for new order",
"InvalidRequestError", "InvalidRequestError",
"The number of contracts exceeds maximum limit allowed", "The number of contracts exceeds maximum limit allowed",
"Order placement failed as your position may exceed the max",
} }
else None else None
) )
@@ -313,20 +305,17 @@ async def open_positions(
symbol: str, symbol: str,
order_quantity: float, order_quantity: float,
trigger_price: float, trigger_price: float,
margin_type: str, leverage: str
leverage: str,
take_profit_percent: float,
stop_loss_percent: float
) -> str | None: ) -> str | None:
try: try:
client = await get_bybit_client(tg_id=tg_id) 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) get_ticker = await get_tickers(tg_id, symbol=symbol)
price_symbol = safe_float(get_ticker.get("lastPrice")) or 0
if get_ticker is None:
price_symbol = 0
else:
price_symbol = safe_float(get_ticker.get("lastPrice"))
instruments_info = await get_instruments_info(tg_id=tg_id, symbol=symbol) instruments_info = await get_instruments_info(tg_id=tg_id, symbol=symbol)
qty_step_str = instruments_info.get("lotSizeFilter").get("qtyStep") qty_step_str = instruments_info.get("lotSizeFilter").get("qtyStep")
qty_step = safe_float(qty_step_str) qty_step = safe_float(qty_step_str)
@@ -342,38 +331,6 @@ async def open_positions(
po_trigger_price = None po_trigger_price = None
trigger_direction = 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 # Place order
order_params = { order_params = {
"category": "linear", "category": "linear",
@@ -387,8 +344,6 @@ async def open_positions(
"timeInForce": "GTC", "timeInForce": "GTC",
"positionIdx": 0, "positionIdx": 0,
"tpslMode": "Full", "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) response = client.place_order(**order_params)
@@ -410,6 +365,7 @@ async def open_positions(
"Qty invalid": "Qty invalid", "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 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", "The number of contracts exceeds minimum limit allowed": "The number of contracts exceeds minimum limit allowed",
"Order placement failed as your position may exceed the max": "Order placement failed as your position may exceed the max",
} }
for key, msg in known_errors.items(): for key, msg in known_errors.items():
if key in error_text: if key in error_text:

View File

@@ -25,11 +25,13 @@ async def set_tp_sl_for_position(
""" """
try: try:
client = await get_bybit_client(tg_id) client = await get_bybit_client(tg_id)
take_profit = round(take_profit_price, 6) if take_profit_price is not None else None
stop_loss = round(stop_loss_price, 6) if stop_loss_price is not None else None
resp = client.set_trading_stop( resp = client.set_trading_stop(
category="linear", category="linear",
symbol=symbol, symbol=symbol,
takeProfit=str(round(take_profit_price, 5)), takeProfit=str(take_profit) if take_profit is not None else None,
stopLoss=str(round(stop_loss_price, 5)), stopLoss=str(stop_loss) if stop_loss is not None else None,
positionIdx=position_idx, positionIdx=position_idx,
tpslMode="Full", tpslMode="Full",
) )

View File

@@ -1,10 +1,14 @@
import logging.config import logging.config
import math
# import json
import app.telegram.keyboards.inline as kbi import app.telegram.keyboards.inline as kbi
import database.request as rq import database.request as rq
from app.bybit.get_functions.get_instruments_info import get_instruments_info
from app.bybit.get_functions.get_positions import get_active_positions_by_symbol
from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG
from app.bybit.open_positions import trading_cycle, trading_cycle_profit from app.bybit.open_positions import trading_cycle, trading_cycle_profit
from app.helper_functions import format_value, safe_float from app.bybit.set_functions.set_tp_sl import set_tp_sl_for_position
from app.helper_functions import format_value, safe_float, truncate_float
logging.config.dictConfig(LOGGING_CONFIG) logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("telegram_message_handler") logger = logging.getLogger("telegram_message_handler")
@@ -14,253 +18,328 @@ class TelegramMessageHandler:
def __init__(self, telegram_bot): def __init__(self, telegram_bot):
self.telegram_bot = telegram_bot self.telegram_bot = telegram_bot
async def format_position_update(self, message):
pass
async def format_order_update(self, message, tg_id): async def format_order_update(self, message, tg_id):
try: try:
order_data = message.get("data", [{}])[0] user_additional_data = await rq.get_user_additional_settings(tg_id=tg_id)
symbol = format_value(order_data.get("symbol")) trigger_price = safe_float(user_additional_data.trigger_price)
qty = format_value(order_data.get("qty")) if trigger_price > 0:
side = format_value(order_data.get("side")) order_data = message.get("data", [{}])[0]
side_rus = ( symbol = format_value(order_data.get("symbol"))
"Покупка" side = format_value(order_data.get("side"))
if side == "Buy" side_rus = (
else "Продажа" if side == "Sell" else "Нет данных" "Покупка"
) if side == "Buy"
order_status = format_value(order_data.get("orderStatus")) else "Продажа" if side == "Sell" else "Нет данных"
price = format_value(order_data.get("price")) )
trigger_price = format_value(order_data.get("triggerPrice")) order_status = format_value(order_data.get("orderStatus"))
take_profit = format_value(order_data.get("takeProfit")) tr_price = format_value(order_data.get("triggerPrice"))
stop_loss = format_value(order_data.get("stopLoss"))
status_map = { status_map = {
"Untriggered": "Условный ордер выставлен", "Untriggered": "Условный ордер выставлен",
} }
if order_status == "Filled" or order_status not in status_map: if order_status == "Filled" or order_status not in status_map:
return None return None
user_auto_trading = await rq.get_user_auto_trading( text = (
tg_id=tg_id, symbol=symbol f"Торговая пара: {symbol}\n"
) f"Движение: {side_rus}\n"
auto_trading = ( )
user_auto_trading.auto_trading if user_auto_trading else False if tr_price and tr_price != "Нет данных":
) text += f"Триггер цена: {tr_price}\n"
user_deals_data = await rq.get_user_deal_by_symbol(
tg_id=tg_id, symbol=symbol
)
text = ( await self.telegram_bot.send_message(
f"Торговая пара: {symbol}\n" chat_id=tg_id, text=text, reply_markup=kbi.profile_bybit
f"Движение: {side_rus}\n" )
) await rq.set_trigger_price(tg_id=tg_id, trigger_price=0)
if user_deals_data is not None and auto_trading:
text += f"Текущая ставка: {user_deals_data.order_quantity} USDT\n"
else:
text += f"Количество: {qty}\n"
if price and price != "0":
text += f"Цена: {price}\n"
if take_profit and take_profit != "Нет данных":
text += f"Тейк-профит: {take_profit}\n"
if stop_loss and stop_loss != "Нет данных":
text += f"Стоп-лосс: {stop_loss}\n"
if trigger_price and trigger_price != "Нет данных":
text += f"Триггер цена: {trigger_price}\n"
await self.telegram_bot.send_message(
chat_id=tg_id, text=text, reply_markup=kbi.profile_bybit
)
except Exception as e: except Exception as e:
logger.error("Error in format_order_update: %s", e) logger.error("Error in format_order_update: %s", e)
async def format_execution_update(self, message, tg_id): async def format_execution_update(self, message, tg_id):
try: try:
# logger.info("Execution update: %s", json.dumps(message))
execution = message.get("data", [{}])[0] execution = message.get("data", [{}])[0]
closed_size = format_value(execution.get("closedSize")) exec_type = format_value(execution.get("execType"))
symbol = format_value(execution.get("symbol")) if exec_type == "Trade" or exec_type == "BustTrade":
exec_price = format_value(execution.get("execPrice")) closed_size = format_value(execution.get("closedSize"))
exec_qty = format_value(execution.get("execQty")) symbol = format_value(execution.get("symbol"))
exec_fees = format_value(execution.get("execFee")) exec_price = format_value(execution.get("execPrice"))
fee_rate = format_value(execution.get("feeRate")) exec_qty = format_value(execution.get("execQty"))
side = format_value(execution.get("side")) exec_fees = format_value(execution.get("execFee"))
side_rus = ( fee_rate = format_value(execution.get("feeRate"))
"Покупка" side = format_value(execution.get("side"))
if side == "Buy"
else "Продажа" if side == "Sell" else "Нет данных"
)
if safe_float(exec_fees) == 0:
exec_fee = safe_float(exec_price) * safe_float(exec_qty) * safe_float(
fee_rate
)
else:
exec_fee = safe_float(exec_fees)
if safe_float(closed_size) == 0: user_auto_trading = await rq.get_user_auto_trading(
await rq.set_fee_user_auto_trading( tg_id=tg_id, symbol=symbol
tg_id=tg_id, symbol=symbol, fee=safe_float(exec_fee) )
auto_trading = (
user_auto_trading.auto_trading if user_auto_trading else False
) )
user_auto_trading = await rq.get_user_auto_trading( side_rus = (
tg_id=tg_id, symbol=symbol "Покупка"
) if side == "Buy"
else "Продажа" if side == "Sell" else "Нет данных"
get_total_fee = user_auto_trading.total_fee )
total_fee = safe_float(exec_fee) + safe_float(get_total_fee) if safe_float(exec_fees) == 0:
exec_fee = safe_float(exec_price) * safe_float(exec_qty) * safe_float(
exec_pnl = format_value(execution.get("execPnl")) fee_rate
pnl = safe_float(exec_pnl)
header = (
"Сделка закрыта:" if safe_float(closed_size) > 0 else "Сделка открыта:"
)
text = f"{header}\n" f"Торговая пара: {symbol}\n"
auto_trading = (
user_auto_trading.auto_trading if user_auto_trading else False
)
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
current_series = user_deals_data.current_series
if commission_fee == "Yes_commission_fee":
if commission_place == "Commission_for_qty":
total_quantity = safe_float(user_deals_data.order_quantity) + safe_float(
total_fee
) )
else: else:
total_quantity = safe_float(user_deals_data.order_quantity) exec_fee = safe_float(exec_fees)
else:
total_quantity = safe_float(user_deals_data.order_quantity)
if user_deals_data is not None and auto_trading and safe_float(closed_size) == 0: if safe_float(closed_size) == 0:
await rq.set_total_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, total_fee=total_fee
)
text += f"Текущая ставка: {total_quantity:.2f} USDT\n"
text += f"Серия №: {user_deals_data.current_series}\n"
text += f"Сделка №: {user_deals_data.current_step}\n"
text += (
f"Цена исполнения: {exec_price}\n"
f"Комиссия: {exec_fee:.8f}\n"
)
if safe_float(closed_size) == 0:
text += f"Движение: {side_rus}\n"
else:
text += f"\nПрибыль: {pnl:.7f}\n"
await self.telegram_bot.send_message(
chat_id=tg_id, text=text, reply_markup=kbi.profile_bybit
)
user_symbols = user_auto_trading.symbol if user_auto_trading else None
if (
auto_trading
and safe_float(closed_size) > 0
and user_symbols is not None
):
if safe_float(pnl) > 0:
profit_text = "📈 Начинаю новую серию с базовой ставки\n"
await self.telegram_bot.send_message(
chat_id=tg_id, text=profit_text, reply_markup=kbi.profile_bybit
)
if side == "Buy":
r_side = "Sell"
else:
r_side = "Buy"
await rq.set_last_side_by_symbol(
tg_id=tg_id, symbol=symbol, last_side=r_side)
await rq.set_total_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, total_fee=0
)
await rq.set_fee_user_auto_trading( await rq.set_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, fee=0 tg_id=tg_id, symbol=symbol, fee=safe_float(exec_fee)
) )
res = await trading_cycle_profit( get_total_fee = 0
tg_id=tg_id, symbol=symbol, side=r_side
) if user_auto_trading is not None:
if res == "OK": get_total_fee = user_auto_trading.total_fee
pass
else: total_fee = safe_float(exec_fee) + safe_float(get_total_fee)
errors = {
"Max bets in series": "❗️ Максимальное количество сделок в серии достигнуто", exec_pnl = format_value(execution.get("execPnl"))
"Risk is too high for this trade": "❗️ Риск сделки слишком высок для продолжения", ex_pnl = safe_float(exec_pnl)
"ab not enough for new order": "❗️ Недостаточно средств для продолжения торговли", pnl = safe_float(exec_pnl)
"InvalidRequestError": "❗️ Недостаточно средств для размещения нового ордера с заданным количеством и плечом.",
"The number of contracts exceeds maximum limit allowed": "❗️ Превышен максимальный лимит ставки", header = (
} "Сделка закрыта:" if safe_float(closed_size) > 0 else "Сделка открыта:"
error_text = errors.get( )
res, "❗️ Не удалось открыть новую сделку" text = f"{header}\n" f"Торговая пара: {symbol}\n"
) user_deals_data = await rq.get_user_deal_by_symbol(
await rq.set_auto_trading( tg_id=tg_id, symbol=symbol
tg_id=tg_id, symbol=symbol, auto_trading=False )
) if user_deals_data is None:
commission_fee = "Yes_commission_fee"
commission_place = "Commission_for_qty"
await rq.set_total_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, total_fee=0
)
await rq.set_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, fee=0
)
await self.telegram_bot.send_message(
chat_id=tg_id,
text=error_text,
reply_markup=kbi.profile_bybit,
)
else: else:
open_order_text = "\n❗️ Открываю новую сделку с увеличенной ставкой.\n" commission_fee = user_deals_data.commission_fee or "Yes_commission_fee"
await self.telegram_bot.send_message( commission_place = user_deals_data.commission_place or "Commission_for_qty"
chat_id=tg_id, text=open_order_text
)
if side == "Buy": current_series = user_deals_data.current_series
r_side = "Sell" current_step = user_deals_data.current_step
order_quantity = user_deals_data.order_quantity
pnl_series = user_deals_data.pnl_series
margin_type = user_deals_data.margin_type
take_profit_percent = user_deals_data.take_profit_percent
stop_loss_percent = user_deals_data.stop_loss_percent
fee = safe_float(user_auto_trading.fee)
total_pnl = safe_float(exec_pnl) - safe_float(exec_fee) - fee
leverage = safe_float(user_deals_data.leverage)
if commission_fee == "Yes_commission_fee":
if commission_place == "Commission_for_qty":
total_quantity = safe_float(order_quantity) + safe_float(
total_fee
) * 2
else: else:
r_side = "Buy" total_quantity = safe_float(order_quantity)
else:
total_quantity = safe_float(order_quantity)
res = await trading_cycle( if user_deals_data is not None and auto_trading and safe_float(closed_size) == 0:
tg_id=tg_id, symbol=symbol, side=r_side await rq.set_total_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, total_fee=total_fee
) )
text += f"Текущая ставка: {total_quantity:.2f} USDT\n"
text += f"Серия №: {current_series}\n"
text += f"Сделка №: {current_step}\n"
if res == "OK": text += (
pass f"Цена исполнения: {exec_price}\n"
f"Комиссия: {exec_fee:.8f}\n"
)
if safe_float(closed_size) == 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(exec_price)
decimals = abs(int(round(math.log10(qty_step))))
qty_format = math.floor(qty / qty_step) * qty_step
qty_formatted = round(qty_format, decimals)
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 = safe_float(exec_price) * (
1 + take_profit_percent / 100) + total_commission
stop_loss_price = None
else:
take_profit_price = safe_float(exec_price) * (
1 - take_profit_percent / 100) - total_commission
stop_loss_price = None
else: else:
errors = { if side == "Buy":
"Max bets in series": "❗️ Максимальное количество сделок в серии достигнуто", take_profit_price = safe_float(exec_price) * (
"Risk is too high for this trade": "❗️ Риск сделки слишком высок для продолжения", 1 + take_profit_percent / 100) + total_commission
"ab not enough for new order": "❗️ Недостаточно средств для продолжения торговли", stop_loss_price = safe_float(exec_price) * (1 - stop_loss_percent / 100)
"InvalidRequestError": "❗️ Недостаточно средств для размещения нового ордера с заданным количеством и плечом.", else:
"The number of contracts exceeds maximum limit allowed": "❗️ Превышен максимальный лимит ставки", take_profit_price = safe_float(exec_price) * (
} 1 - take_profit_percent / 100) - total_commission
error_text = errors.get( stop_loss_price = safe_float(exec_price) * (1 + stop_loss_percent / 100)
res, "❗️ Не удалось открыть новую сделку"
) take_profit_price = max(take_profit_price, 0)
await rq.set_auto_trading( stop_loss_price = max(stop_loss_price, 0)
tg_id=tg_id, symbol=symbol, auto_trading=False
ress = await set_tp_sl_for_position(tg_id=tg_id,
symbol=symbol,
take_profit_price=take_profit_price,
stop_loss_price=stop_loss_price,
position_idx=0)
if ress:
take_profit_truncated = await truncate_float(take_profit_price, 6)
text += (f"Движение: {side_rus}\n"
f"Тейк-профит: {take_profit_truncated}\n"
)
if stop_loss_price is not None:
stop_loss_truncated = await truncate_float(stop_loss_price, 6)
else:
stop_loss_truncated = None
if stop_loss_truncated is not None:
text += f"Стоп-лосс: {stop_loss_truncated}\n"
else:
deals = await get_active_positions_by_symbol(
tg_id=tg_id, symbol=symbol
)
position = next((d for d in deals if d.get("symbol") == symbol), None)
if position:
liq_price = position.get("liqPrice", 0)
text += f"Цена ликвидации: {liq_price}\n"
else:
text += (f"Движение: {side_rus}\n"
"Не удалось установить ТП и СЛ\n")
else:
if auto_trading:
new_pnl = safe_float(pnl_series) + total_pnl
await rq.set_pnl_series_by_symbol(
tg_id=tg_id, symbol=symbol, pnl_series=new_pnl)
text += f"\nПрибыль без комиссии: {ex_pnl:.4f}\n"
text += f"Реализованная прибыль: {total_pnl:.4f}\n"
text += f"Прибыль серии: {safe_float(new_pnl):.4f}\n"
else:
text += f"\nПрибыль без комиссии: {ex_pnl:.4f}\n"
text += f"Реализованная прибыль: {total_pnl:.4f}\n"
await self.telegram_bot.send_message(
chat_id=tg_id, text=text, reply_markup=kbi.profile_bybit
)
user_symbols = user_auto_trading.symbol if user_auto_trading else None
if (
auto_trading
and safe_float(closed_size) > 0
and user_symbols is not None
):
if safe_float(pnl) > 0:
profit_text = "📈 Начинаю новую серию с базовой ставки\n"
await self.telegram_bot.send_message(
chat_id=tg_id, text=profit_text, reply_markup=kbi.profile_bybit
) )
if side == "Buy":
r_side = "Sell"
else:
r_side = "Buy"
await rq.set_last_side_by_symbol(
tg_id=tg_id, symbol=symbol, last_side=r_side)
await rq.set_total_fee_user_auto_trading( await rq.set_total_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, total_fee=0 tg_id=tg_id, symbol=symbol, total_fee=0
) )
await rq.set_fee_user_auto_trading( await rq.set_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, fee=0 tg_id=tg_id, symbol=symbol, fee=0
) )
await rq.set_pnl_series_by_symbol(tg_id=tg_id, symbol=symbol, pnl_series=0)
res = await trading_cycle_profit(
tg_id=tg_id, symbol=symbol, side=r_side
)
if res == "OK":
pass
else:
errors = {
"Max bets in series": "❗️ Максимальное количество сделок в серии достигнуто",
"Risk is too high for this trade": "❗️ Риск сделки слишком высок для продолжения",
"ab not enough for new order": "❗️ Недостаточно средств для продолжения торговли",
"InvalidRequestError": "❗️ Недостаточно средств для размещения нового ордера с заданным количеством и плечом.",
"The number of contracts exceeds maximum limit allowed": "❗️ Превышен максимальный лимит ставки",
"Order placement failed as your position may exceed the max": "❗️ Превышен максимальный лимит ставки",
}
error_text = errors.get(
res, "❗️ Не удалось открыть новую сделку"
)
await rq.set_auto_trading(
tg_id=tg_id, symbol=symbol, auto_trading=False
)
await rq.set_total_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, total_fee=0
)
await rq.set_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, fee=0
)
await self.telegram_bot.send_message(
chat_id=tg_id,
text=error_text,
reply_markup=kbi.profile_bybit,
)
else:
open_order_text = "\n❗️ Открываю новую сделку с увеличенной ставкой.\n"
await self.telegram_bot.send_message( await self.telegram_bot.send_message(
chat_id=tg_id, chat_id=tg_id, text=open_order_text
text=error_text,
reply_markup=kbi.profile_bybit,
) )
if side == "Buy":
r_side = "Sell"
else:
r_side = "Buy"
res = await trading_cycle(
tg_id=tg_id, symbol=symbol, side=r_side
)
if res == "OK":
pass
else:
errors = {
"Max bets in series": "❗️ Максимальное количество сделок в серии достигнуто",
"Risk is too high for this trade": "❗️ Риск сделки слишком высок для продолжения",
"ab not enough for new order": "❗️ Недостаточно средств для продолжения торговли",
"InvalidRequestError": "❗️ Недостаточно средств для размещения нового ордера с заданным количеством и плечом.",
"The number of contracts exceeds maximum limit allowed": "❗️ Превышен максимальный лимит ставки",
"Order placement failed as your position may exceed the max": "❗️ Превышен максимальный лимит ставки",
}
error_text = errors.get(
res, "❗️ Не удалось открыть новую сделку"
)
await rq.set_auto_trading(
tg_id=tg_id, symbol=symbol, auto_trading=False
)
await rq.set_total_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, total_fee=0
)
await rq.set_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, fee=0
)
await self.telegram_bot.send_message(
chat_id=tg_id,
text=error_text,
reply_markup=kbi.profile_bybit,
)
except Exception as e: except Exception as e:
logger.error("Error in telegram_message_handler: %s", e) logger.error("Error in telegram_message_handler: %s", e, exc_info=True)

View File

@@ -83,12 +83,6 @@ class WebSocketBot:
self.user_sockets[tg_id] = self.ws_private self.user_sockets[tg_id] = self.ws_private
# Connect to the WebSocket private channel # Connect to the WebSocket private channel
# Handle position updates
self.ws_private.position_stream(
lambda msg: self.loop.call_soon_threadsafe(
asyncio.create_task, self.handle_position_update(msg)
)
)
# Handle order updates # Handle order updates
self.ws_private.order_stream( self.ws_private.order_stream(
lambda msg: self.loop.call_soon_threadsafe( lambda msg: self.loop.call_soon_threadsafe(
@@ -106,16 +100,12 @@ class WebSocketBot:
logger.error("Error connecting user %s: %s", tg_id, e) logger.error("Error connecting user %s: %s", tg_id, e)
return False return False
async def handle_position_update(self, message):
"""Handle position updates."""
await self.message_handler.format_position_update(message)
async def handle_order_update(self, message, tg_id): async def handle_order_update(self, message, tg_id):
"""Handle order updates.""" """Handle order updates."""
await self.message_handler.format_order_update(message, tg_id) await self.message_handler.format_order_update(message, tg_id)
async def handle_execution_update(self, message, tg_id): async def handle_execution_update(self, message, tg_id):
"""Handle execution updates.""" """Handle execution updates without duplicate processing."""
await self.message_handler.format_execution_update(message, tg_id) await self.message_handler.format_execution_update(message, tg_id)
@staticmethod @staticmethod

View File

@@ -179,3 +179,9 @@ async def calculate_total_budget(
total += r_quantity total += r_quantity
return total return total
async def truncate_float(f, decimals=4):
factor = 10 ** decimals
return int(f * factor) / factor

View File

@@ -121,7 +121,7 @@ async def set_symbol(message: Message, state: FSMContext) -> None:
) )
await rq.set_leverage(tg_id=message.from_user.id, leverage=str(max_leverage)) await rq.set_leverage(tg_id=message.from_user.id, leverage=str(max_leverage))
risk_percent = 100 / safe_float(max_leverage) risk_percent = 10 / safe_float(max_leverage)
await rq.set_stop_loss_percent( await rq.set_stop_loss_percent(
tg_id=message.from_user.id, stop_loss_percent=risk_percent) tg_id=message.from_user.id, stop_loss_percent=risk_percent)
await rq.set_take_profit_percent( await rq.set_take_profit_percent(

View File

@@ -660,7 +660,7 @@ async def set_leverage_handler(message: Message, state: FSMContext) -> None:
text=f"Кредитное плечо успешно установлено на {leverage_float}", text=f"Кредитное плечо успешно установлено на {leverage_float}",
reply_markup=kbi.back_to_additional_settings, reply_markup=kbi.back_to_additional_settings,
) )
risk_percent = 100 / safe_float(leverage_float) risk_percent = 10 / safe_float(leverage_float)
await rq.set_stop_loss_percent( await rq.set_stop_loss_percent(
tg_id=message.from_user.id, stop_loss_percent=risk_percent) tg_id=message.from_user.id, stop_loss_percent=risk_percent)
await rq.set_take_profit_percent( await rq.set_take_profit_percent(

View File

@@ -97,7 +97,8 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non
"Limit price is out min price": "Цена лимитного ордера меньше допустимого", "Limit price is out min price": "Цена лимитного ордера меньше допустимого",
"Limit price is out max price": "Цена лимитного ордера больше допустимого", "Limit price is out max price": "Цена лимитного ордера больше допустимого",
"Risk is too high for this trade": "Риск сделки превышает допустимый убыток", "Risk is too high for this trade": "Риск сделки превышает допустимый убыток",
"estimated will trigger liq": "Лимитный ордер может вызвать мгновенную ликвидацию. Проверьте параметры ордера.", "estimated will trigger liq": "Лимитный ордер может вызвать мгновенную ликвидацию. "
"Проверьте параметры ордера.",
"ab not enough for new order": "Недостаточно средств для создания нового ордера", "ab not enough for new order": "Недостаточно средств для создания нового ордера",
"InvalidRequestError": "Произошла ошибка при запуске торговли.", "InvalidRequestError": "Произошла ошибка при запуске торговли.",
"Order does not meet minimum order value": "Сумма ставки меньше допустимого для запуска торговли. " "Order does not meet minimum order value": "Сумма ставки меньше допустимого для запуска торговли. "
@@ -106,6 +107,9 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non
"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": "️️Лимит ставки меньше минимально допустимого",
"Order placement failed as your position may exceed the max":
"Не удалось разместить ордер, так как ваша позиция может превышать максимальный лимит."
"Пожалуйста, уменьшите кредитное плечо, чтобы увеличить максимальное значение"
} }
if res == "OK": if res == "OK":
@@ -141,7 +145,7 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non
lambda c: c.data == "cancel_timer_merged" lambda c: c.data == "cancel_timer_merged"
) )
async def cancel_start_trading( async def cancel_start_trading(
callback_query: CallbackQuery, state: FSMContext callback_query: CallbackQuery, state: FSMContext
) -> None: ) -> None:
""" """
Handles the "cancel_timer" callback query. Handles the "cancel_timer" callback query.

View File

@@ -39,21 +39,15 @@ async def stop_all_trading(callback_query: CallbackQuery, state: FSMContext):
await rq.set_stop_timer(tg_id=callback_query.from_user.id, timer_end=0) await rq.set_stop_timer(tg_id=callback_query.from_user.id, timer_end=0)
await asyncio.sleep(timer_end * 60) await asyncio.sleep(timer_end * 60)
user_auto_trading = await rq.get_user_auto_trading( await rq.set_auto_trading(
tg_id=callback_query.from_user.id, symbol=symbol tg_id=callback_query.from_user.id,
symbol=symbol,
auto_trading=False,
) )
await close_position_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol)
await callback_query.message.edit_text(text=f"Торговля для {symbol} остановлена", reply_markup=kbi.profile_bybit)
if user_auto_trading and user_auto_trading.auto_trading:
await rq.set_auto_trading(
tg_id=callback_query.from_user.id,
symbol=symbol,
auto_trading=False,
)
await close_position_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol)
await callback_query.message.edit_text(text=f"Торговля для {symbol} остановлена", reply_markup=kbi.profile_bybit)
else:
await callback_query.message.edit_text(text=f"Нет активной торговли для {symbol}", reply_markup=kbi.profile_bybit)
task = asyncio.create_task(delay_start()) task = asyncio.create_task(delay_start())
await add_stop_task(user_id=callback_query.from_user.id, task=task) await add_stop_task(user_id=callback_query.from_user.id, task=task)

View File

@@ -160,6 +160,9 @@ class UserDeals(Base):
current_series = Column(Integer, nullable=True) current_series = Column(Integer, nullable=True)
commission_fee = Column(String, nullable=True) commission_fee = Column(String, nullable=True)
commission_place = Column(String, nullable=True) commission_place = Column(String, nullable=True)
pnl_series = Column(Float, nullable=True)
take_profit = Column(Float, nullable=False, default=0.0)
stop_loss = Column(Float, nullable=False, default=0.0)
user = relationship("User", back_populates="user_deals") user = relationship("User", back_populates="user_deals")

View File

@@ -200,6 +200,8 @@ async def create_user_additional_settings(tg_id: int) -> None:
user=user, user=user,
trade_mode="Long", # Default value trade_mode="Long", # Default value
switch_side="По направлению", switch_side="По направлению",
side="Buy",
trigger_price=0.0,
margin_type="ISOLATED_MARGIN", margin_type="ISOLATED_MARGIN",
leverage="10", leverage="10",
order_quantity=1.0, order_quantity=1.0,
@@ -990,7 +992,8 @@ async def set_user_deal(
stop_loss_percent: int, stop_loss_percent: int,
base_quantity: float, base_quantity: float,
commission_fee: str, commission_fee: str,
commission_place: str commission_place: str,
pnl_series: float
): ):
""" """
Set the user deal in the database. Set the user deal in the database.
@@ -1011,6 +1014,7 @@ async def set_user_deal(
:param base_quantity: Base quantity :param base_quantity: Base quantity
:param commission_fee: Commission fee :param commission_fee: Commission fee
:param commission_place: Commission place :param commission_place: Commission place
:param pnl_series: PNL series
:return: bool :return: bool
""" """
try: try:
@@ -1043,6 +1047,7 @@ async def set_user_deal(
deal.base_quantity = base_quantity deal.base_quantity = base_quantity
deal.commission_fee = commission_fee deal.commission_fee = commission_fee
deal.commission_place = commission_place deal.commission_place = commission_place
deal.pnl_series = pnl_series
else: else:
# Creating new record # Creating new record
new_deal = UserDeals( new_deal = UserDeals(
@@ -1062,7 +1067,8 @@ async def set_user_deal(
stop_loss_percent=stop_loss_percent, stop_loss_percent=stop_loss_percent,
base_quantity=base_quantity, base_quantity=base_quantity,
commission_fee=commission_fee, commission_fee=commission_fee,
commission_place=commission_place commission_place=commission_place,
pnl_series=pnl_series
) )
session.add(new_deal) session.add(new_deal)
@@ -1199,6 +1205,63 @@ async def set_current_series(tg_id: int, symbol: str, current_series: int):
return False return False
async def set_tp_sl_by_symbol(tg_id: int, symbol: str, tp: float, sl: float):
"""Set tp and sl for a user deal by symbol in the database."""
try:
async with async_session() as session:
result = await session.execute(select(User).filter_by(tg_id=tg_id))
user = result.scalars().first()
if user is None:
logger.error(f"User with tg_id={tg_id} not found")
return False
result = await session.execute(
select(UserDeals).filter_by(user_id=user.id, symbol=symbol)
)
record = result.scalars().first()
if record:
record.take_profit = tp
record.stop_loss = sl
else:
logger.error(f"User deal with user_id={user.id} and symbol={symbol} not found")
return False
await session.commit()
logger.info("Set tp and sl for user %s and symbol %s", tg_id, symbol)
return True
except Exception as e:
logger.error("Error setting user deal tp and sl for user %s and symbol %s: %s", tg_id, symbol, e)
return False
async def set_pnl_series_by_symbol(tg_id: int, symbol: str, pnl_series: float):
"""Set pnl series for a user deal by symbol in the database."""
try:
async with async_session() as session:
result = await session.execute(select(User).filter_by(tg_id=tg_id))
user = result.scalars().first()
if user is None:
logger.error(f"User with tg_id={tg_id} not found")
return False
result = await session.execute(
select(UserDeals).filter_by(user_id=user.id, symbol=symbol)
)
record = result.scalars().first()
if record:
record.pnl_series = pnl_series
else:
logger.error(f"User deal with user_id={user.id} and symbol={symbol} not found")
return False
await session.commit()
logger.info("Set pnl series for user %s and symbol %s", tg_id, symbol)
return True
except Exception as e:
logger.error("Error setting user deal pnl series for user %s and symbol %s: %s", tg_id, symbol, e)
return False
# USER AUTO TRADING # USER AUTO TRADING
async def get_all_user_auto_trading(tg_id: int): async def get_all_user_auto_trading(tg_id: int):