2
0
forked from kodorvan/stcs

1 Commits

21 changed files with 585 additions and 597 deletions

View File

@@ -84,7 +84,7 @@ path_separator = os
# database URL. This is consumed by the user-maintained env.py script only. # database URL. This is consumed by the user-maintained env.py script only.
# other means of configuring database URLs may be customized within the env.py # other means of configuring database URLs may be customized within the env.py
# file. # file.
sqlalchemy.url = sqlite+aiosqlite:///./database/stcs.db sqlalchemy.url = sqlite+aiosqlite:///./database/db/stcs.db
[post_write_hooks] [post_write_hooks]

View File

@@ -0,0 +1,32 @@
"""Added column current_series
Revision ID: 0ee52ab23e66
Revises: e5d612e44563
Create Date: 2025-10-26 11:48:48.055031
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '0ee52ab23e66'
down_revision: Union[str, Sequence[str], None] = 'e5d612e44563'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user_deals', sa.Column('current_series', sa.Integer(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user_deals', 'current_series')
# ### end Alembic commands ###

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

@@ -0,0 +1,45 @@
"""Added column commission_place
Revision ID: adf3d2991896
Revises: 0ee52ab23e66
Create Date: 2025-10-26 13:37:33.662318
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy import inspect
# revision identifiers, used by Alembic.
revision: str = 'adf3d2991896'
down_revision: Union[str, Sequence[str], None] = '0ee52ab23e66'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
bind = op.get_bind()
inspector = inspect(bind)
columns_user_deals = [col['name'] for col in inspector.get_columns('user_deals')]
if 'commission_fee' not in columns_user_deals:
op.add_column('user_deals', sa.Column('commission_fee', sa.String(), server_default='', nullable=True))
if 'commission_place' not in columns_user_deals:
op.add_column('user_deals', sa.Column('commission_place', sa.String(), server_default='', nullable=True))
columns_user_risk_mgmt = [col['name'] for col in inspector.get_columns('user_risk_management')]
if 'commission_place' not in columns_user_risk_mgmt:
op.add_column('user_risk_management',
sa.Column('commission_place', sa.String(), server_default='', nullable=False))
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user_risk_management', 'commission_place')
op.drop_column('user_deals', 'commission_place')
op.drop_column('user_deals', 'commission_fee')
# ### end Alembic commands ###

View File

@@ -0,0 +1,34 @@
"""Added column side for additional_setiings
Revision ID: e5d612e44563
Revises: fbf4e3658310
Create Date: 2025-10-25 18:25:52.746250
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'e5d612e44563'
down_revision: Union[str, Sequence[str], None] = 'fbf4e3658310'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user_additional_settings',
sa.Column('side', sa.String(), nullable=False, server_default='')
)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user_additional_settings', 'side')
# ### end Alembic commands ###

View File

@@ -1,8 +1,8 @@
"""initial """Added side_mode column
Revision ID: f6e7eb3f25c0 Revision ID: fbf4e3658310
Revises: Revises:
Create Date: 2025-11-12 22:53:02.189445 Create Date: 2025-10-22 13:08:02.317419
""" """
from typing import Sequence, Union from typing import Sequence, Union
@@ -12,7 +12,7 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision: str = 'f6e7eb3f25c0' revision: str = 'fbf4e3658310'
down_revision: Union[str, Sequence[str], None] = None down_revision: Union[str, Sequence[str], None] = None
branch_labels: Union[str, Sequence[str], None] = None branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None
@@ -21,12 +21,12 @@ depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None: def upgrade() -> None:
"""Upgrade schema.""" """Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
pass op.add_column('user_deals', sa.Column('side_mode', sa.String(), nullable=True))
# ### end Alembic commands ### # ### end Alembic commands ###
def downgrade() -> None: def downgrade() -> None:
"""Downgrade schema.""" """Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ### # ### commands auto generated by Alembic - please adjust! ###
pass op.drop_column('user_deals', 'side_mode')
# ### end Alembic commands ### # ### end Alembic commands ###

View File

@@ -38,7 +38,7 @@ async def get_active_positions(tg_id: int) -> list | None:
return None return None
async def get_active_positions_by_symbol(tg_id: int, symbol: str): async def get_active_positions_by_symbol(tg_id: int, symbol: str) -> dict | None:
""" """
Get active positions for a user by symbol Get active positions for a user by symbol
""" """
@@ -62,10 +62,6 @@ async def get_active_positions_by_symbol(tg_id: int, symbol: str):
) )
return None return None
except Exception as e: except Exception as e:
errors = str(e)
if errors.startswith("Permission denied, please check your API key permissions"):
return "Invalid API key permissions"
else:
logger.error("Error getting active positions for user %s: %s", tg_id, e) logger.error("Error getting active positions for user %s: %s", tg_id, e)
return None return None

View File

@@ -54,6 +54,12 @@ 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,
@@ -75,12 +81,6 @@ async def start_trading_cycle(
commission_place=commission_place, commission_place=commission_place,
pnl_series=0 pnl_series=0
) )
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
)
res = await open_positions( res = await open_positions(
tg_id=tg_id, tg_id=tg_id,
@@ -109,7 +109,6 @@ async def start_trading_cycle(
"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", "Order placement failed as your position may exceed the max",
"Permission denied, please check your API key permissions"
} }
else None else None
) )
@@ -233,6 +232,9 @@ async def trading_cycle(
) )
current_step += 1 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_margin_mode(tg_id=tg_id, margin_mode=margin_type)
await set_leverage( await set_leverage(
tg_id=tg_id, tg_id=tg_id,
@@ -314,12 +316,7 @@ async def open_positions(
try: try:
client = await get_bybit_client(tg_id=tg_id) client = await get_bybit_client(tg_id=tg_id)
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)
@@ -347,6 +344,7 @@ async def open_positions(
"triggerBy": "LastPrice", "triggerBy": "LastPrice",
"timeInForce": "GTC", "timeInForce": "GTC",
"positionIdx": 0, "positionIdx": 0,
"tpslMode": "Full",
} }
response = client.place_order(**order_params) response = client.place_order(**order_params)
@@ -369,7 +367,6 @@ async def open_positions(
"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", "Order placement failed as your position may exceed the max": "Order placement failed as your position may exceed the max",
"Permission denied, please check your API key permissions": "Permission denied, please check your API key permissions"
} }
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

@@ -37,7 +37,7 @@ async def user_profile_bybit(tg_id: int, message: Message, state: FSMContext) ->
) )
else: else:
await message.answer( await message.answer(
text="Ошибка при подключении к платформе. Проверьте корректность и разрешения API ключа и добавьте повторно.", text="Ошибка при подключении, повторите попытку",
reply_markup=kbi.connect_the_platform, reply_markup=kbi.connect_the_platform,
) )
logger.error("Error processing user profile for user %s", tg_id) logger.error("Error processing user profile for user %s", tg_id)

View File

@@ -13,7 +13,7 @@ async def set_tp_sl_for_position(
take_profit_price: float, take_profit_price: float,
stop_loss_price: float, stop_loss_price: float,
position_idx: int, position_idx: int,
) -> bool | str: ) -> bool:
""" """
Set take profit and stop loss for a symbol. Set take profit and stop loss for a symbol.
:param tg_id: Telegram user ID :param tg_id: Telegram user ID
@@ -21,17 +21,15 @@ async def set_tp_sl_for_position(
:param take_profit_price: Take profit price :param take_profit_price: Take profit price
:param stop_loss_price: Stop loss price :param stop_loss_price: Stop loss price
:param position_idx: Position index :param position_idx: Position index
:return: bool or str :return: bool
""" """
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(take_profit) if take_profit is not None else None, takeProfit=str(round(take_profit_price, 5)),
stopLoss=str(stop_loss) if stop_loss is not None else None, stopLoss=str(round(stop_loss_price, 5)),
positionIdx=position_idx, positionIdx=position_idx,
tpslMode="Full", tpslMode="Full",
) )
@@ -40,18 +38,8 @@ async def set_tp_sl_for_position(
logger.info("TP/SL for %s has been set", symbol) logger.info("TP/SL for %s has been set", symbol)
return True return True
else: else:
error_msg = resp.get("retMsg") logger.error("Error setting TP/SL for %s: %s", symbol, resp.get("retMsg"))
if "not modified" in error_msg.lower():
logger.info("TP/SL for %s not modified: %s", symbol, error_msg)
return "not modified"
else:
logger.error("Error setting TP/SL for %s: %s", symbol, error_msg)
return False return False
except Exception as e: except Exception as e:
error_msg = str(e) logger.error("Error setting TP/SL for %s: %s", symbol, e)
if "not modified" in error_msg.lower():
logger.info("TP/SL for %s not modified: %s", symbol, error_msg)
return "not modified"
else:
logger.error("Error set TP/SL for %s: %s", symbol, e)
return False return False

View File

@@ -1,6 +1,6 @@
import logging.config import logging.config
import math 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_instruments_info import get_instruments_info
@@ -15,13 +15,10 @@ logger = logging.getLogger("telegram_message_handler")
class TelegramMessageHandler: class TelegramMessageHandler:
def __init__(self, telegram_bot): def __init__(self, telegram_bot):
"""Initialize the TelegramMessageHandler class."""
self.telegram_bot = telegram_bot self.telegram_bot = telegram_bot
async def format_order_update(self, message, tg_id): async def format_order_update(self, message, tg_id):
"""Handle order updates."""
try: try:
# logger.info("Order update: %s", json.dumps(message))
user_additional_data = await rq.get_user_additional_settings(tg_id=tg_id) user_additional_data = await rq.get_user_additional_settings(tg_id=tg_id)
trigger_price = safe_float(user_additional_data.trigger_price) trigger_price = safe_float(user_additional_data.trigger_price)
if trigger_price > 0: if trigger_price > 0:
@@ -58,12 +55,8 @@ class TelegramMessageHandler:
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):
"""Handle execution updates without duplicate processing."""
try: try:
# logger.info("Execution update: %s", json.dumps(message))
execution = message.get("data", [{}])[0] execution = message.get("data", [{}])[0]
exec_type = format_value(execution.get("execType"))
if exec_type == "Trade" or exec_type == "BustTrade":
closed_size = format_value(execution.get("closedSize")) closed_size = format_value(execution.get("closedSize"))
symbol = format_value(execution.get("symbol")) symbol = format_value(execution.get("symbol"))
exec_price = format_value(execution.get("execPrice")) exec_price = format_value(execution.get("execPrice"))
@@ -71,9 +64,6 @@ class TelegramMessageHandler:
exec_fees = format_value(execution.get("execFee")) exec_fees = format_value(execution.get("execFee"))
fee_rate = format_value(execution.get("feeRate")) fee_rate = format_value(execution.get("feeRate"))
side = format_value(execution.get("side")) side = format_value(execution.get("side"))
exec_pnl = format_value(execution.get("execPnl"))
stop_order_type = format_value(execution.get("stopOrderType"))
create_type = format_value(execution.get("createType"))
user_auto_trading = await rq.get_user_auto_trading( user_auto_trading = await rq.get_user_auto_trading(
tg_id=tg_id, symbol=symbol tg_id=tg_id, symbol=symbol
@@ -82,7 +72,6 @@ class TelegramMessageHandler:
user_auto_trading.auto_trading if user_auto_trading else False user_auto_trading.auto_trading if user_auto_trading else False
) )
if auto_trading:
side_rus = ( side_rus = (
"Покупка" "Покупка"
if side == "Buy" if side == "Buy"
@@ -100,13 +89,12 @@ class TelegramMessageHandler:
tg_id=tg_id, symbol=symbol, fee=safe_float(exec_fee) tg_id=tg_id, symbol=symbol, fee=safe_float(exec_fee)
) )
get_total_fee = 0
if user_auto_trading is not None:
get_total_fee = user_auto_trading.total_fee get_total_fee = user_auto_trading.total_fee
total_fee = safe_float(exec_fee) + safe_float(get_total_fee) total_fee = safe_float(exec_fee) + safe_float(get_total_fee)
exec_pnl = format_value(execution.get("execPnl"))
ex_pnl = safe_float(exec_pnl) ex_pnl = safe_float(exec_pnl)
pnl = safe_float(exec_pnl)
header = ( header = (
"Сделка закрыта:" if safe_float(closed_size) > 0 else "Сделка открыта:" "Сделка закрыта:" if safe_float(closed_size) > 0 else "Сделка открыта:"
@@ -115,18 +103,18 @@ class TelegramMessageHandler:
user_deals_data = await rq.get_user_deal_by_symbol( user_deals_data = await rq.get_user_deal_by_symbol(
tg_id=tg_id, symbol=symbol tg_id=tg_id, symbol=symbol
) )
commission_fee = user_deals_data.commission_fee or "Yes_commission_fee"
commission_fee = user_deals_data.commission_fee commission_place = user_deals_data.commission_place or "Commission_for_qty"
commission_place = user_deals_data.commission_place
current_series = user_deals_data.current_series current_series = user_deals_data.current_series
current_step = user_deals_data.current_step current_step = user_deals_data.current_step
order_quantity = user_deals_data.order_quantity order_quantity = user_deals_data.order_quantity
pnl_series = user_deals_data.pnl_series pnl_series = user_deals_data.pnl_series
margin_type = user_deals_data.margin_type
take_profit_percent = user_deals_data.take_profit_percent take_profit_percent = user_deals_data.take_profit_percent
stop_loss_percent = user_deals_data.stop_loss_percent stop_loss_percent = user_deals_data.stop_loss_percent
leverage = safe_float(user_deals_data.leverage)
fee = safe_float(user_auto_trading.fee) fee = safe_float(user_auto_trading.fee)
total_pnl = safe_float(exec_pnl) - safe_float(exec_fee) - 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_fee == "Yes_commission_fee":
if commission_place == "Commission_for_qty": if commission_place == "Commission_for_qty":
@@ -138,7 +126,7 @@ class TelegramMessageHandler:
else: else:
total_quantity = safe_float(order_quantity) total_quantity = safe_float(order_quantity)
if user_deals_data is not None and safe_float(closed_size) == 0: if user_deals_data is not None and auto_trading and safe_float(closed_size) == 0:
await rq.set_total_fee_user_auto_trading( await rq.set_total_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, total_fee=total_fee tg_id=tg_id, symbol=symbol, total_fee=total_fee
) )
@@ -165,6 +153,16 @@ class TelegramMessageHandler:
if commission_place == "Commission_for_tp": if commission_place == "Commission_for_tp":
total_commission = safe_float(total_fee) / qty_formatted 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:
if side == "Buy": if side == "Buy":
take_profit_price = safe_float(exec_price) * ( take_profit_price = safe_float(exec_price) * (
1 + take_profit_percent / 100) + total_commission 1 + take_profit_percent / 100) + total_commission
@@ -174,14 +172,18 @@ class TelegramMessageHandler:
1 - take_profit_percent / 100) - total_commission 1 - take_profit_percent / 100) - total_commission
stop_loss_price = safe_float(exec_price) * (1 + stop_loss_percent / 100) stop_loss_price = safe_float(exec_price) * (1 + stop_loss_percent / 100)
take_profit_price = max(take_profit_price, 0)
stop_loss_price = max(stop_loss_price, 0)
ress = await set_tp_sl_for_position(tg_id=tg_id, ress = await set_tp_sl_for_position(tg_id=tg_id,
symbol=symbol, symbol=symbol,
take_profit_price=take_profit_price, take_profit_price=take_profit_price,
stop_loss_price=stop_loss_price, stop_loss_price=stop_loss_price,
position_idx=0) position_idx=0)
if ress or ress == "not modified": if ress:
take_profit_truncated = await truncate_float(take_profit_price, 6) take_profit_truncated = await truncate_float(take_profit_price, 6)
stop_loss_truncated = await truncate_float(stop_loss_price, 6) stop_loss_truncated = await truncate_float(stop_loss_price, 6)
text += (f"Движение: {side_rus}\n" text += (f"Движение: {side_rus}\n"
f"Тейк-профит: {take_profit_truncated}\n" f"Тейк-профит: {take_profit_truncated}\n"
f"Стоп-лосс: {stop_loss_truncated}\n" f"Стоп-лосс: {stop_loss_truncated}\n"
@@ -190,19 +192,30 @@ class TelegramMessageHandler:
text += (f"Движение: {side_rus}\n" text += (f"Движение: {side_rus}\n"
"Не удалось установить ТП и СЛ\n") "Не удалось установить ТП и СЛ\n")
elif safe_float(closed_size) > 0 and auto_trading: else:
if auto_trading:
new_pnl = safe_float(pnl_series) + total_pnl new_pnl = safe_float(pnl_series) + total_pnl
await rq.set_pnl_series_by_symbol( await rq.set_pnl_series_by_symbol(
tg_id=tg_id, symbol=symbol, pnl_series=new_pnl) tg_id=tg_id, symbol=symbol, pnl_series=new_pnl)
text += f"\nПрибыль без комиссии: {ex_pnl:.4f}\n" text += f"\nПрибыль без комиссии: {ex_pnl:.4f}\n"
text += f"Реализованная прибыль: {total_pnl:.4f}\n" text += f"Реализованная прибыль: {total_pnl:.4f}\n"
text += f"Прибыль серии: {safe_float(new_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( await self.telegram_bot.send_message(
chat_id=tg_id, text=text, reply_markup=kbi.profile_bybit chat_id=tg_id, text=text, reply_markup=kbi.profile_bybit
) )
if stop_order_type == "TakeProfit": 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" profit_text = "📈 Начинаю новую серию с базовой ставки\n"
await self.telegram_bot.send_message( await self.telegram_bot.send_message(
chat_id=tg_id, text=profit_text, reply_markup=kbi.profile_bybit chat_id=tg_id, text=profit_text, reply_markup=kbi.profile_bybit
@@ -230,64 +243,7 @@ class TelegramMessageHandler:
pass pass
else: else:
errors = { errors = {
"Risk is too high for this trade": "❗️ Риск сделки слишком высок для продолжения", "Max bets in series": "❗️ Максимальное количество сделок в серии достигнуто",
"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,
)
elif stop_order_type == "StopLoss" or exec_type == "BustTrade":
current_step = user_deals_data.current_step
max_bets_in_series = user_deals_data.max_bets_in_series
current_step += 1
if max_bets_in_series < current_step:
text_series = ("\n❗️ Максимальное количество сделок в серии достигнуто.\n"
"📈 Начинаю новую серию с базовой ставки\n")
await self.telegram_bot.send_message(
chat_id=tg_id, text=text_series
)
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(
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 = {
"Risk is too high for this trade": "❗️ Риск сделки слишком высок для продолжения", "Risk is too high for this trade": "❗️ Риск сделки слишком высок для продолжения",
"ab not enough for new order": "❗️ Недостаточно средств для продолжения торговли", "ab not enough for new order": "❗️ Недостаточно средств для продолжения торговли",
"InvalidRequestError": "❗️ Недостаточно средств для размещения нового ордера с заданным количеством и плечом.", "InvalidRequestError": "❗️ Недостаточно средств для размещения нового ордера с заданным количеством и плечом.",
@@ -331,11 +287,12 @@ class TelegramMessageHandler:
pass pass
else: else:
errors = { errors = {
"Max bets in series": "❗️ Максимальное количество сделок в серии достигнуто",
"Risk is too high for this trade": "❗️ Риск сделки слишком высок для продолжения", "Risk is too high for this trade": "❗️ Риск сделки слишком высок для продолжения",
"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": "❗️ Превышен максимальный лимит ставки с текущим плечом", "Order placement failed as your position may exceed the max": "❗️ Превышен максимальный лимит ставки",
} }
error_text = errors.get( error_text = errors.get(
res, "❗️ Не удалось открыть новую сделку" res, "❗️ Не удалось открыть новую сделку"
@@ -355,25 +312,5 @@ class TelegramMessageHandler:
text=error_text, text=error_text,
reply_markup=kbi.profile_bybit, reply_markup=kbi.profile_bybit,
) )
elif create_type == "CreateByClosing":
await self.telegram_bot.send_message(
chat_id=tg_id,
text=f"❗️ Торговля для {symbol} остановлена",
reply_markup=kbi.profile_bybit,
)
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
)
logger.info("Stop trading for symbol: %s, create_type: %s, stop_order_type: %s: %s",
symbol, create_type, stop_order_type, tg_id)
else:
logger.info("Execution update: %s", json.dumps(message))
except Exception as e: except Exception as e:
logger.error("Error in telegram_message_handler: %s", e, exc_info=True) logger.error("Error in telegram_message_handler: %s", e, exc_info=True)

View File

@@ -1,5 +1,4 @@
import asyncio import asyncio
from collections import deque
import logging.config import logging.config
from pybit.unified_trading import WebSocket from pybit.unified_trading import WebSocket
@@ -12,49 +11,26 @@ logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("web_socket") logger = logging.getLogger("web_socket")
class CustomWebSocket(WebSocket):
"""Custom WebSocket wrapper with enhanced error handling."""
def _on_error(self, error):
logger.error(f"WebSocket error: {error}")
return super()._on_error(error)
def _on_close(self):
logger.warning("WebSocket connection closed")
super()._on_close()
class WebSocketBot: class WebSocketBot:
""" """
Manages multiple Bybit private WebSocket connections for Telegram users. Class to handle WebSocket connections and messages.
Uses queue-based message processing to handle thread-safe async calls.
""" """
def __init__(self, telegram_bot): def __init__(self, telegram_bot):
""" """Initialize the TradingBot class."""
Initialize WebSocketBot.
Args:
telegram_bot: Telegram bot instance for message handling
"""
self.telegram_bot = telegram_bot self.telegram_bot = telegram_bot
self.user_sockets = {} self.ws_private = None
self.user_messages = {} self.user_messages = {}
self.user_sockets = {}
self.user_keys = {} self.user_keys = {}
self.loop = None self.loop = None
self.message_handler = TelegramMessageHandler(telegram_bot) self.message_handler = TelegramMessageHandler(telegram_bot)
self.last_execution_seq = {}
self.order_queues = {} # {tg_id: deque}
self.execution_queues = {} # {tg_id: deque}
self.processing_tasks = {} # {tg_id: task}
async def run_user_check_loop(self): async def run_user_check_loop(self):
"""Main loop that continuously checks users and maintains connections.""" """Run a loop to check for users and connect them to the WebSocket."""
self.loop = asyncio.get_running_loop() self.loop = asyncio.get_running_loop()
logger.info("Starting WebSocket user check loop")
while True: while True:
try:
users = await WebSocketBot.get_users_from_db() users = await WebSocketBot.get_users_from_db()
for user in users: for user in users:
tg_id = user.tg_id tg_id = user.tg_id
@@ -64,13 +40,16 @@ class WebSocketBot:
continue continue
keys_stored = self.user_keys.get(tg_id) keys_stored = self.user_keys.get(tg_id)
socket_exists = tg_id in self.user_sockets if tg_id in self.user_sockets and keys_stored == (api_key, api_secret):
if socket_exists and keys_stored == (api_key, api_secret):
continue continue
if socket_exists: if tg_id in self.user_sockets:
await self.close_user_socket(tg_id) self.user_sockets.clear()
self.user_messages.clear()
self.user_keys.clear()
logger.info(
"Closed old websocket for user %s due to key change", tg_id
)
success = await self.try_connect_user(api_key, api_secret, tg_id) success = await self.try_connect_user(api_key, api_secret, tg_id)
if success: if success:
@@ -78,112 +57,69 @@ class WebSocketBot:
self.user_messages.setdefault( self.user_messages.setdefault(
tg_id, {"position": None, "order": None, "execution": None} tg_id, {"position": None, "order": None, "execution": None}
) )
logger.info("User %s successfully connected", tg_id) logger.info("User %s connected to WebSocket", tg_id)
else:
except Exception as e: await asyncio.sleep(5)
logger.error("Error in user check loop: %s", e) await self.try_connect_user(api_key, api_secret, tg_id)
await asyncio.sleep(10) await asyncio.sleep(10)
async def clear_user_sockets(self):
"""Clear the user_sockets and user_messages dictionaries."""
self.user_sockets.clear()
self.user_messages.clear()
self.user_keys.clear()
logger.info("Cleared user_sockets")
async def try_connect_user(self, api_key, api_secret, tg_id): async def try_connect_user(self, api_key, api_secret, tg_id):
""" """Try to connect a user to the WebSocket."""
Create and setup WebSocket streams with thread-safe queues.
"""
try: try:
ws = CustomWebSocket( self.ws_private = WebSocket(
demo=True, demo=True,
testnet=False, testnet=False,
channel_type="private", channel_type="private",
api_key=api_key, api_key=api_key,
api_secret=api_secret api_secret=api_secret,
) )
self.user_sockets[tg_id] = ws self.user_sockets[tg_id] = self.ws_private
# Connect to the WebSocket private channel
self.order_queues[tg_id] = deque() # Handle order updates
self.execution_queues[tg_id] = deque() self.ws_private.order_stream(
lambda msg: self.loop.call_soon_threadsafe(
self.processing_tasks[tg_id] = asyncio.create_task( asyncio.create_task, self.handle_order_update(msg, tg_id)
self._process_order_queue(tg_id) )
)
# Handle execution updates
self.ws_private.execution_stream(
lambda msg: self.loop.call_soon_threadsafe(
asyncio.create_task, self.handle_execution_update(msg, tg_id)
) )
self.processing_tasks[tg_id + 1] = asyncio.create_task(
self._process_execution_queue(tg_id)
) )
def order_callback(msg):
self.order_queues[tg_id].append(msg)
def execution_callback(msg):
self.execution_queues[tg_id].append(msg)
ws.order_stream(order_callback)
ws.execution_stream(execution_callback)
logger.info("WebSocket streams configured for user %s", tg_id)
return True return True
except Exception as e: except Exception as e:
logger.error("Error connecting user %s: %s", tg_id, e) logger.error("Error connecting user %s: %s", tg_id, e)
self.user_sockets.pop(tg_id, None)
return False return False
async def _process_order_queue(self, tg_id):
"""Continuously process order queue for user."""
while tg_id in self.user_sockets:
try:
if self.order_queues[tg_id]:
msg = self.order_queues[tg_id].popleft()
await self.handle_order_update(msg, tg_id)
except Exception as e:
logger.error("Error processing order queue %s: %s", tg_id, e)
await asyncio.sleep(0.01)
async def _process_execution_queue(self, tg_id):
"""Continuously process execution queue for user."""
while tg_id in self.user_sockets:
try:
if self.execution_queues[tg_id]:
msg = self.execution_queues[tg_id].popleft()
await self.handle_execution_update(msg, tg_id)
except Exception as e:
logger.error("Error processing execution queue %s: %s", tg_id, e)
await asyncio.sleep(0.01)
async def close_user_socket(self, tg_id):
"""Gracefully close user connection."""
if tg_id in self.user_sockets:
self.user_sockets.pop(tg_id, None)
for key in (tg_id, tg_id + 1):
task = self.processing_tasks.pop(key, None)
if task and not task.done():
task.cancel()
self.order_queues.pop(tg_id, None)
self.execution_queues.pop(tg_id, None)
self.user_messages.pop(tg_id, None)
self.user_keys.pop(tg_id, None)
logger.info("Cleaned up user %s", tg_id)
async def handle_order_update(self, message, tg_id): async def handle_order_update(self, message, tg_id):
"""Process order updates.""" """Handle order updates."""
try:
await self.message_handler.format_order_update(message, tg_id) await self.message_handler.format_order_update(message, tg_id)
except Exception as e:
logger.error("Error handling order update for %s: %s", tg_id, e)
async def handle_execution_update(self, message, tg_id): async def handle_execution_update(self, message, tg_id):
"""Process execution updates.""" """Handle execution updates."""
try: data_items = message.get('data', [])
if not data_items:
return
for exec_data in data_items:
seq = exec_data.get('seq')
if tg_id not in self.last_execution_seq:
self.last_execution_seq[tg_id] = -1
if seq <= self.last_execution_seq[tg_id]:
continue
self.last_execution_seq[tg_id] = seq
await self.message_handler.format_execution_update(message, tg_id) await self.message_handler.format_execution_update(message, tg_id)
except Exception as e:
logger.error("Error handling execution update for %s: %s", tg_id, e)
@staticmethod @staticmethod
async def get_users_from_db(): async def get_users_from_db():
"""Fetch all users from database.""" """Get all users from the database."""
try:
return await rq.get_users() return await rq.get_users()
except Exception as e:
logger.error("Error getting users from DB: %s", e)
return []

View File

@@ -85,18 +85,6 @@ async def cmd_to_main(message: Message, state: FSMContext) -> None:
None: Exceptions are caught and logged internally. None: Exceptions are caught and logged internally.
""" """
try: try:
await state.clear()
user = await rq.get_user(tg_id=message.from_user.id)
if user:
await user_profile_tg(tg_id=message.from_user.id, message=message)
else:
await rq.create_user(
tg_id=message.from_user.id, username=message.from_user.username
)
await rq.set_user_symbol(tg_id=message.from_user.id, symbol="BTCUSDT")
await rq.create_user_additional_settings(tg_id=message.from_user.id)
await rq.create_user_risk_management(tg_id=message.from_user.id)
await rq.create_user_conditional_settings(tg_id=message.from_user.id)
await user_profile_tg(tg_id=message.from_user.id, message=message) await user_profile_tg(tg_id=message.from_user.id, message=message)
logger.debug( logger.debug(
"Command to_profile_tg processed successfully for user: %s", "Command to_profile_tg processed successfully for user: %s",
@@ -129,21 +117,9 @@ async def profile_bybit(message: Message, state: FSMContext) -> None:
""" """
try: try:
await state.clear() await state.clear()
user = await rq.get_user(tg_id=message.from_user.id)
if user:
await user_profile_bybit( await user_profile_bybit(
tg_id=message.from_user.id, message=message, state=state tg_id=message.from_user.id, message=message, state=state
) )
else:
await rq.create_user(
tg_id=message.from_user.id, username=message.from_user.username
)
await rq.set_user_symbol(tg_id=message.from_user.id, symbol="BTCUSDT")
await rq.create_user_additional_settings(tg_id=message.from_user.id)
await rq.create_user_risk_management(tg_id=message.from_user.id)
await rq.create_user_conditional_settings(tg_id=message.from_user.id)
await user_profile_bybit(
tg_id=message.from_user.id, message=message, state=state)
logger.debug( logger.debug(
"Command to_profile_bybit processed successfully for user: %s", "Command to_profile_bybit processed successfully for user: %s",
message.from_user.id, message.from_user.id,
@@ -174,9 +150,6 @@ async def profile_bybit_callback(
""" """
try: try:
await state.clear() await state.clear()
user = await rq.get_user(tg_id=callback_query.from_user.id)
if user:
await user_profile_bybit( await user_profile_bybit(
tg_id=callback_query.from_user.id, tg_id=callback_query.from_user.id,
message=callback_query.message, message=callback_query.message,
@@ -186,19 +159,6 @@ async def profile_bybit_callback(
"Callback profile_bybit processed successfully for user: %s", "Callback profile_bybit processed successfully for user: %s",
callback_query.from_user.id, callback_query.from_user.id,
) )
else:
await rq.create_user(
tg_id=callback_query.from_user.id, username=callback_query.from_user.username
)
await rq.set_user_symbol(tg_id=callback_query.from_user.id, symbol="BTCUSDT")
await rq.create_user_additional_settings(tg_id=callback_query.from_user.id)
await rq.create_user_risk_management(tg_id=callback_query.from_user.id)
await rq.create_user_conditional_settings(tg_id=callback_query.from_user.id)
await user_profile_bybit(
tg_id=callback_query.from_user.id,
message=callback_query.message,
state=state,
)
await callback_query.answer() await callback_query.answer()
except Exception as e: except Exception as e:
logger.error( logger.error(

View File

@@ -299,12 +299,6 @@ async def settings_for_margin_type(
deals = await get_active_positions_by_symbol( deals = await get_active_positions_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol tg_id=callback_query.from_user.id, symbol=symbol
) )
if deals == "Invalid API key permissions":
await callback_query.answer(
text="API ключ не имеет достаточных прав для смены маржи",
)
return
position = next((d for d in deals if d.get("symbol") == symbol), None) position = next((d for d in deals if d.get("symbol") == symbol), None)
if position: if position:
@@ -682,15 +676,6 @@ async def set_leverage_handler(message: Message, state: FSMContext) -> None:
await state.clear() await state.clear()
except Exception as e: except Exception as e:
errors_text = str(e)
known_errors = {
"Permission denied, please check your API key permissions": "API ключ не имеет достаточных прав для установки кредитного плеча"
}
for key, msg in known_errors.items():
if key in errors_text:
await message.answer(msg, reply_markup=kbi.back_to_additional_settings)
else:
await message.answer( await message.answer(
text="Произошла ошибка при установке кредитного плеча. Пожалуйста, попробуйте позже.", text="Произошла ошибка при установке кредитного плеча. Пожалуйста, попробуйте позже.",
reply_markup=kbi.back_to_additional_settings, reply_markup=kbi.back_to_additional_settings,

View File

@@ -38,12 +38,6 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non
deals = await get_active_positions_by_symbol( deals = await get_active_positions_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol tg_id=callback_query.from_user.id, symbol=symbol
) )
if deals == "Invalid API key permissions":
await callback_query.answer(
text="API ключ не имеет достаточных прав для запуска торговли",
)
return
position = next((d for d in deals if d.get("symbol") == symbol), None) position = next((d for d in deals if d.get("symbol") == symbol), None)
if position: if position:
@@ -115,9 +109,7 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non
"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":
"Не удалось разместить ордер, так как ваша позиция может превышать максимальный лимит." "Не удалось разместить ордер, так как ваша позиция может превышать максимальный лимит."
"Пожалуйста, уменьшите кредитное плечо, чтобы увеличить максимальное значение", "Пожалуйста, уменьшите кредитное плечо, чтобы увеличить максимальное значение"
"Permission denied, please check your API key permissions": "API ключ не имеет достаточных прав для запуска торговли"
} }
if res == "OK": if res == "OK":
@@ -139,15 +131,6 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non
await add_start_task_merged(user_id=callback_query.from_user.id, task=task) await add_start_task_merged(user_id=callback_query.from_user.id, task=task)
except Exception as e: except Exception as e:
error_text = str(e)
known_errors = {
"Permission denied, please check your API key permissions": "API ключ не имеет достаточных прав для запуска торговли"
}
for key, msg in known_errors.items():
if key in error_text:
await callback_query.answer(msg)
else:
await callback_query.answer(text="Произошла ошибка при запуске торговли") await callback_query.answer(text="Произошла ошибка при запуске торговли")
logger.error( logger.error(
"Error processing command start_trading for user %s: %s", "Error processing command start_trading for user %s: %s",

View File

@@ -39,13 +39,13 @@ 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)
await close_position_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol)
await rq.set_auto_trading( await rq.set_auto_trading(
tg_id=callback_query.from_user.id, tg_id=callback_query.from_user.id,
symbol=symbol, symbol=symbol,
auto_trading=False, 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) await callback_query.message.edit_text(text=f"Торговля для {symbol} остановлена", reply_markup=kbi.profile_bybit)

View File

@@ -10,9 +10,10 @@ logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("database") logger = logging.getLogger("database")
BASE_DIR = Path(__file__).parent.resolve() BASE_DIR = Path(__file__).parent.resolve()
BASE_DIR.mkdir(parents=True, exist_ok=True) DATA_DIR = BASE_DIR / "db"
DATA_DIR.mkdir(parents=True, exist_ok=True)
DATABASE_URL = f"sqlite+aiosqlite:///{BASE_DIR / 'stcs.db'}" DATABASE_URL = f"sqlite+aiosqlite:///{DATA_DIR / 'stcs.db'}"
async_engine = create_async_engine( async_engine = create_async_engine(
DATABASE_URL, DATABASE_URL,
@@ -38,7 +39,7 @@ async_session = async_sessionmaker(
async def init_db(): async def init_db():
try: try:
async with async_engine.begin() as conn: async with async_engine.begin() as conn:
await conn.run_sync(lambda sync_conn: Base.metadata.create_all(bind=sync_conn, checkfirst=True)) await conn.run_sync(Base.metadata.create_all)
logger.info("Database initialized.") logger.info("Database initialized.")
except Exception as e: except Exception as e:
logger.error("Database initialization failed: %s", e) logger.error("Database initialization failed: %s", e)

View File

@@ -154,8 +154,8 @@ class UserDeals(Base):
order_quantity = Column(Float, nullable=True) order_quantity = Column(Float, nullable=True)
martingale_factor = Column(Float, nullable=True) martingale_factor = Column(Float, nullable=True)
max_bets_in_series = Column(Integer, nullable=True) max_bets_in_series = Column(Integer, nullable=True)
take_profit_percent = Column(Float, nullable=True) take_profit_percent = Column(Integer, nullable=True)
stop_loss_percent = Column(Float, nullable=True) stop_loss_percent = Column(Integer, nullable=True)
trigger_price = Column(Float, nullable=True) trigger_price = Column(Float, nullable=True)
current_series = Column(Integer, nullable=True) current_series = Column(Integer, nullable=True)
commission_fee = Column(String, nullable=True) commission_fee = Column(String, nullable=True)

View File

@@ -86,7 +86,7 @@ async def set_user_api(tg_id: int, api_key: str, api_secret: str) -> bool:
else: else:
# Creating new record # Creating new record
user_api = UserApi( user_api = UserApi(
user_id=user.id, api_key=api_key, api_secret=api_secret user=user, api_key=api_key, api_secret=api_secret
) )
session.add(user_api) session.add(user_api)
@@ -141,7 +141,7 @@ async def set_user_symbol(tg_id: int, symbol: str) -> bool:
# Creating new record # Creating new record
user_symbol = UserSymbol( user_symbol = UserSymbol(
symbol=symbol, symbol=symbol,
user_id=user.id, user=user,
) )
session.add(user_symbol) session.add(user_symbol)
@@ -197,7 +197,7 @@ async def create_user_additional_settings(tg_id: int) -> None:
# Create the user additional settings # Create the user additional settings
user_additional_settings = UserAdditionalSettings( user_additional_settings = UserAdditionalSettings(
user_id=user.id, user=user,
trade_mode="Long", # Default value trade_mode="Long", # Default value
switch_side="По направлению", switch_side="По направлению",
side="Buy", side="Buy",
@@ -267,7 +267,7 @@ async def set_trade_mode(tg_id: int, trade_mode: str) -> bool:
# Creating new record # Creating new record
user_additional_settings = UserAdditionalSettings( user_additional_settings = UserAdditionalSettings(
trade_mode=trade_mode, trade_mode=trade_mode,
user_id=user.id, user=user,
) )
session.add(user_additional_settings) session.add(user_additional_settings)
@@ -306,7 +306,7 @@ async def set_margin_type(tg_id: int, margin_type: str) -> bool:
# Creating new record # Creating new record
user_additional_settings = UserAdditionalSettings( user_additional_settings = UserAdditionalSettings(
margin_type=margin_type, margin_type=margin_type,
user_id=user.id, user=user,
) )
session.add(user_additional_settings) session.add(user_additional_settings)
@@ -345,7 +345,7 @@ async def set_switch_side(tg_id: int, switch_side: str) -> bool:
# Creating new record # Creating new record
user_additional_settings = UserAdditionalSettings( user_additional_settings = UserAdditionalSettings(
switch_side=switch_side, switch_side=switch_side,
user_id=user.id, user=user,
) )
session.add(user_additional_settings) session.add(user_additional_settings)
@@ -384,7 +384,7 @@ async def set_side(tg_id: int, side: str) -> bool:
# Creating new record # Creating new record
user_additional_settings = UserAdditionalSettings( user_additional_settings = UserAdditionalSettings(
side=side, side=side,
user_id=user.id, user=user,
) )
session.add(user_additional_settings) session.add(user_additional_settings)
@@ -423,7 +423,7 @@ async def set_leverage(tg_id: int, leverage: str) -> bool:
# Creating new record # Creating new record
user_additional_settings = UserAdditionalSettings( user_additional_settings = UserAdditionalSettings(
leverage=leverage, leverage=leverage,
user_id=user.id, user=user,
) )
session.add(user_additional_settings) session.add(user_additional_settings)
@@ -462,7 +462,7 @@ async def set_order_quantity(tg_id: int, order_quantity: float) -> bool:
# Creating new record # Creating new record
user_additional_settings = UserAdditionalSettings( user_additional_settings = UserAdditionalSettings(
order_quantity=order_quantity, order_quantity=order_quantity,
user_id=user.id, user=user,
) )
session.add(user_additional_settings) session.add(user_additional_settings)
@@ -503,7 +503,7 @@ async def set_martingale_factor(tg_id: int, martingale_factor: float) -> bool:
# Creating new record # Creating new record
user_additional_settings = UserAdditionalSettings( user_additional_settings = UserAdditionalSettings(
martingale_factor=martingale_factor, martingale_factor=martingale_factor,
user_id=user.id, user=user,
) )
session.add(user_additional_settings) session.add(user_additional_settings)
@@ -546,7 +546,7 @@ async def set_max_bets_in_series(tg_id: int, max_bets_in_series: int) -> bool:
# Creating new record # Creating new record
user_additional_settings = UserAdditionalSettings( user_additional_settings = UserAdditionalSettings(
max_bets_in_series=max_bets_in_series, max_bets_in_series=max_bets_in_series,
user_id=user.id, user=user,
) )
session.add(user_additional_settings) session.add(user_additional_settings)
@@ -587,7 +587,7 @@ async def set_trigger_price(tg_id: int, trigger_price: float) -> bool:
# Creating new record # Creating new record
user_additional_settings = UserAdditionalSettings( user_additional_settings = UserAdditionalSettings(
trigger_price=trigger_price, trigger_price=trigger_price,
user_id=user.id, user=user,
) )
session.add(user_additional_settings) session.add(user_additional_settings)
@@ -627,7 +627,7 @@ async def create_user_risk_management(tg_id: int) -> None:
# Create the user risk management # Create the user risk management
user_risk_management = UserRiskManagement( user_risk_management = UserRiskManagement(
user_id=user.id, user=user,
take_profit_percent=1.0, take_profit_percent=1.0,
stop_loss_percent=1.0, stop_loss_percent=1.0,
commission_fee="Yes_commission_fee", commission_fee="Yes_commission_fee",
@@ -692,7 +692,7 @@ async def set_take_profit_percent(tg_id: int, take_profit_percent: float) -> boo
# Creating new record # Creating new record
user_risk_management = UserRiskManagement( user_risk_management = UserRiskManagement(
take_profit_percent=take_profit_percent, take_profit_percent=take_profit_percent,
user_id=user.id, user=user,
) )
session.add(user_risk_management) session.add(user_risk_management)
@@ -733,7 +733,7 @@ async def set_stop_loss_percent(tg_id: int, stop_loss_percent: float) -> bool:
# Creating new record # Creating new record
user_risk_management = UserRiskManagement( user_risk_management = UserRiskManagement(
stop_loss_percent=stop_loss_percent, stop_loss_percent=stop_loss_percent,
user_id=user.id, user=user,
) )
session.add(user_risk_management) session.add(user_risk_management)
@@ -774,7 +774,7 @@ async def set_commission_fee(tg_id: int, commission_fee: str) -> bool:
# Creating new record # Creating new record
user_risk_management = UserRiskManagement( user_risk_management = UserRiskManagement(
commission_fee=commission_fee, commission_fee=commission_fee,
user_id=user.id, user=user,
) )
session.add(user_risk_management) session.add(user_risk_management)
@@ -815,7 +815,7 @@ async def set_commission_place(tg_id: int, commission_place: str) -> bool:
# Creating new record # Creating new record
user_risk_management = UserRiskManagement( user_risk_management = UserRiskManagement(
commission_place=commission_place, commission_place=commission_place,
user_id=user.id, user=user,
) )
session.add(user_risk_management) session.add(user_risk_management)
@@ -855,7 +855,7 @@ async def create_user_conditional_settings(tg_id: int) -> None:
# Create the user conditional settings # Create the user conditional settings
user_conditional_settings = UserConditionalSettings( user_conditional_settings = UserConditionalSettings(
user_id=user.id, user=user,
timer_start=0, timer_start=0,
timer_end=0, timer_end=0,
) )
@@ -920,7 +920,7 @@ async def set_start_timer(tg_id: int, timer_start: int) -> bool:
# Creating new record # Creating new record
user_conditional_settings = UserConditionalSettings( user_conditional_settings = UserConditionalSettings(
timer_start=timer_start, timer_start=timer_start,
user_id=user.id, user=user,
) )
session.add(user_conditional_settings) session.add(user_conditional_settings)
@@ -959,7 +959,7 @@ async def set_stop_timer(tg_id: int, timer_end: int) -> bool:
# Creating new record # Creating new record
user_conditional_settings = UserConditionalSettings( user_conditional_settings = UserConditionalSettings(
timer_end=timer_end, timer_end=timer_end,
user_id=user.id, user=user,
) )
session.add(user_conditional_settings) session.add(user_conditional_settings)
@@ -1051,7 +1051,7 @@ async def set_user_deal(
else: else:
# Creating new record # Creating new record
new_deal = UserDeals( new_deal = UserDeals(
user_id=user.id, user=user,
symbol=symbol, symbol=symbol,
current_step=current_step, current_step=current_step,
current_series=current_series, current_series=current_series,

1
run.py
View File

@@ -31,6 +31,7 @@ async def main():
dp = Dispatcher(storage=storage) dp = Dispatcher(storage=storage)
dp.include_router(router) dp.include_router(router)
web_socket = WebSocketBot(telegram_bot=bot) web_socket = WebSocketBot(telegram_bot=bot)
await web_socket.clear_user_sockets()
ws_task = asyncio.create_task(web_socket.run_user_check_loop()) ws_task = asyncio.create_task(web_socket.run_user_check_loop())
tg_task = asyncio.create_task(dp.start_polling(bot)) tg_task = asyncio.create_task(dp.start_polling(bot))