2
0
forked from kodorvan/stcs

12 Commits

Author SHA1 Message Date
algizn97
72ed35ccf8 The price of the trading pair has been removed, and the trade cancellation button has been removed. The text has been corrected 2025-10-10 14:00:58 +05:00
algizn97
b890df9af8 When choosing a coin, the leverage is set to the maximum possible for this coin, also SL in accordance with the leverage. The verification range from 1 to 100 has been removed, now the verification is within the acceptable values from the exchange 2025-10-10 14:00:42 +05:00
algizn97
e40fa91125 Added the ability to summarize all commissions within a series.Minor bugs have been fixed 2025-10-10 14:00:28 +05:00
algizn97
3ec9d00650 The currency of the coin is treason on USDT, unnecessary parameters are removed 2025-10-10 14:00:01 +05:00
algizn97
e792130332 Unnecessary buttons have been removed, the buttons of the trading mode and the direction of the first transaction of the series have been moved. 2025-10-10 13:59:47 +05:00
algizn97
1a1a5a727f When adjusting the leverage, the SL changes according to the criteria. In place of the position mode, there is now a trading mode. All unnecessary functions are also removed. 2025-10-10 13:59:24 +05:00
algizn97
9f9a79bf81 The function allows you to write the number 0 2025-10-10 13:59:04 +05:00
algizn97
58397c4723 The stop trading button has been removed. 2025-10-10 13:58:23 +05:00
algizn97
6bfb816d2a Fixed percentages of TP and SL from integers to floats 2025-10-10 13:57:29 +05:00
algizn97
fb82f365f2 The trading mode has been moved to the main settings, Position mode, limit order and conditional order have been removed. The number of bids has been renamed to the base rate. The choice of the direction of the first transaction has been moved to the main settings 2025-10-10 13:57:13 +05:00
algizn97
0945be242a Added the addition of a common commission 2025-10-10 13:56:43 +05:00
algizn97
4663888190 TP AND SL have been converted to float. Switch control has been moved to the main settings, Removed unnecessary parameters 2025-10-10 13:55:42 +05:00
21 changed files with 269 additions and 265 deletions

2
.gitignore vendored
View File

@@ -146,8 +146,6 @@ myenv
ENV/
env.bak/
venv.bak/
/logger_helper/loggers
/app/bybit/logger_bybit/loggers
# Spyder project settings
.spyderproject
.spyproject

View File

@@ -55,12 +55,7 @@ cp .env.sample .env
nvim .env
```
5. Для применения миграций выполните команду:
```bash
alembic upgrade head
```
6. Запустите бота:
5. Запустите бота:
```bash
python run.py

View File

@@ -1,40 +0,0 @@
"""fixed switch_side type
Revision ID: 07020b2808d3
Revises: c710f4e2259c
Create Date: 2025-10-09 14:36:07.393387
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '07020b2808d3'
down_revision: Union[str, Sequence[str], None] = 'c710f4e2259c'
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.alter_column('user_additional_settings', 'switch_side',
existing_type=sa.BOOLEAN(),
type_=sa.String(),
existing_nullable=False,
existing_server_default=sa.text('false'))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('user_additional_settings', 'switch_side',
existing_type=sa.String(),
type_=sa.BOOLEAN(),
existing_nullable=False,
existing_server_default=sa.text('false'))
# ### end Alembic commands ###

View File

@@ -1,34 +0,0 @@
"""added base_quantity
Revision ID: baf03ce269e0
Revises: 07020b2808d3
Create Date: 2025-10-09 16:49:52.979556
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'baf03ce269e0'
down_revision: Union[str, Sequence[str], None] = '07020b2808d3'
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('base_quantity', sa.Float(), nullable=True))
op.drop_column('user_deals', 'trading_type')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user_deals', sa.Column('trading_type', sa.VARCHAR(), autoincrement=False, nullable=True))
op.drop_column('user_deals', 'base_quantity')
# ### end Alembic commands ###

View File

@@ -16,9 +16,7 @@ logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("open_positions")
async def start_trading_cycle(
tg_id: int
) -> str | None:
async def start_trading_cycle(tg_id: int) -> str | None:
"""
Start trading cycle
:param tg_id: Telegram user ID
@@ -29,9 +27,7 @@ async def start_trading_cycle(
additional_data = await rq.get_user_additional_settings(tg_id=tg_id)
risk_management_data = await rq.get_user_risk_management(tg_id=tg_id)
commission_fee = risk_management_data.commission_fee
user_deals_data = await rq.get_user_deal_by_symbol(
tg_id=tg_id, symbol=symbol
)
user_deals_data = await rq.get_user_deal_by_symbol(tg_id=tg_id, symbol=symbol)
trade_mode = additional_data.trade_mode
switch_side = additional_data.switch_side
margin_type = additional_data.margin_type
@@ -107,7 +103,7 @@ async def start_trading_cycle(
leverage=leverage,
take_profit_percent=take_profit_percent,
stop_loss_percent=stop_loss_percent,
commission_fee_percent=total_commission
commission_fee_percent=total_commission,
)
if res == "OK":
@@ -125,7 +121,7 @@ async def start_trading_cycle(
max_bets_in_series=max_bets_in_series,
take_profit_percent=take_profit_percent,
stop_loss_percent=stop_loss_percent,
base_quantity=order_quantity
base_quantity=order_quantity,
)
return "OK"
return (
@@ -142,7 +138,7 @@ async def start_trading_cycle(
"position idx not match position mode",
"Qty invalid",
"The number of contracts exceeds maximum limit allowed",
"The number of contracts exceeds minimum limit allowed"
"The number of contracts exceeds minimum limit allowed",
}
else None
)
@@ -152,12 +148,12 @@ async def start_trading_cycle(
return None
async def trading_cycle(
tg_id: int, symbol: str, reverse_side: str
) -> str | None:
async def trading_cycle(tg_id: int, symbol: str, reverse_side: str) -> str | None:
try:
user_deals_data = await rq.get_user_deal_by_symbol(tg_id=tg_id, symbol=symbol)
user_auto_trading_data = await rq.get_user_auto_trading(tg_id=tg_id, symbol=symbol)
user_auto_trading_data = await rq.get_user_auto_trading(
tg_id=tg_id, symbol=symbol
)
total_fee = user_auto_trading_data.total_fee
trade_mode = user_deals_data.trade_mode
margin_type = user_deals_data.margin_type
@@ -188,9 +184,7 @@ async def trading_cycle(
if trade_mode == "Switch":
side = "Sell" if real_side == "Buy" else "Buy"
next_quantity = safe_float(order_quantity) * (
safe_float(martingale_factor)
)
next_quantity = safe_float(order_quantity) * (safe_float(martingale_factor))
current_step += 1
if max_bets_in_series < current_step:
@@ -206,7 +200,7 @@ async def trading_cycle(
leverage=leverage,
take_profit_percent=take_profit_percent,
stop_loss_percent=stop_loss_percent,
commission_fee_percent=total_fee
commission_fee_percent=total_fee,
)
if res == "OK":
@@ -224,7 +218,7 @@ async def trading_cycle(
max_bets_in_series=max_bets_in_series,
take_profit_percent=take_profit_percent,
stop_loss_percent=stop_loss_percent,
base_quantity=base_quantity
base_quantity=base_quantity,
)
return "OK"
@@ -255,7 +249,7 @@ async def open_positions(
leverage: str,
take_profit_percent: float,
stop_loss_percent: float,
commission_fee_percent: float
commission_fee_percent: float,
) -> str | None:
try:
client = await get_bybit_client(tg_id=tg_id)

View File

@@ -46,9 +46,7 @@ class WebSocketBot:
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
)
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)
if success:

View File

@@ -158,7 +158,7 @@ async def get_liquidation_price(
async def calculate_total_budget(
quantity, martingale_factor, max_steps
quantity, martingale_factor, max_steps, commission_fee_percent
) -> float:
"""
Calculate the total budget for a series of trading steps.
@@ -167,6 +167,7 @@ async def calculate_total_budget(
quantity (float): The initial quantity of the asset.
martingale_factor (float): The factor by which the quantity is multiplied for each step.
max_steps (int): The maximum number of trading steps.
commission_fee_percent (float): The commission fee percentage.
Returns:
float: The total budget for the series of trading steps.
@@ -174,8 +175,12 @@ async def calculate_total_budget(
total = 0
for step in range(max_steps):
set_quantity = quantity * (martingale_factor ** step)
if commission_fee_percent == 0:
# Commission fee is not added to the position size
r_quantity = set_quantity
else:
# Commission fee is added to the position size
r_quantity = set_quantity * (1 + 2 * commission_fee_percent)
total += r_quantity
return total

View File

@@ -6,11 +6,10 @@ from aiogram.types import CallbackQuery, Message
import app.telegram.keyboards.inline as kbi
import database.request as rq
from app.bybit.get_functions.get_tickers import get_tickers
from app.bybit.get_functions.get_instruments_info import get_instruments_info
from app.bybit.get_functions.get_tickers import get_tickers
from app.bybit.profile_bybit import user_profile_bybit
from app.bybit.set_functions.set_leverage import set_leverage
from app.bybit.set_functions.set_margin_mode import set_margin_mode
from app.helper_functions import safe_float
from app.telegram.states.states import ChangingTheSymbolState
@@ -99,7 +98,9 @@ async def set_symbol(message: Message, state: FSMContext) -> None:
)
return
instruments_info = await get_instruments_info(tg_id=message.from_user.id, symbol=symbol)
instruments_info = await get_instruments_info(
tg_id=message.from_user.id, symbol=symbol
)
max_leverage = instruments_info.get("leverageFilter").get("maxLeverage")
req = await rq.set_user_symbol(tg_id=message.from_user.id, symbol=symbol)
@@ -123,7 +124,8 @@ async def set_symbol(message: Message, state: FSMContext) -> None:
await rq.set_leverage(tg_id=message.from_user.id, leverage=str(max_leverage))
risk_percent = 100 / safe_float(max_leverage)
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_trigger_price(tg_id=message.from_user.id, trigger_price=0)
await rq.set_order_quantity(tg_id=message.from_user.id, order_quantity=1.0)

View File

@@ -142,7 +142,9 @@ async def switch_side_start(callback_query: CallbackQuery, state: FSMContext) ->
)
@router_additional_settings.callback_query(lambda c: c.data == "switch_direction" or c.data == "switch_opposite")
@router_additional_settings.callback_query(
lambda c: c.data == "switch_direction" or c.data == "switch_opposite"
)
async def switch_side_handler(callback_query: CallbackQuery, state: FSMContext) -> None:
"""
Handles callback queries related to switch side selection.
@@ -214,7 +216,7 @@ async def settings_for_margin_type(
await callback_query.message.edit_text(
text="Выберите тип маржи:\n\n"
"Примечание: Если у вас есть открытые позиции, то маржа примениться ко всем позициям",
reply_markup=kbi.margin_type
reply_markup=kbi.margin_type,
)
logger.debug(
"Command margin_type processed successfully for user: %s",
@@ -553,7 +555,8 @@ async def set_leverage_handler(message: Message, state: FSMContext) -> None:
)
risk_percent = 100 / safe_float(leverage_float)
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
)
logger.info(
"User %s set leverage: %s", message.from_user.id, leverage_float
)
@@ -767,9 +770,14 @@ async def set_martingale_factor(message: Message, state: FSMContext) -> None:
martingale_factor_value_float = safe_float(martingale_factor_value)
if martingale_factor_value_float < 0.1 or martingale_factor_value_float > 10:
await message.answer(text="Ошибка: коэффициент мартингейла должен быть в диапазоне от 0.1 до 10")
logger.debug("User %s input invalid (not in range 0.1 to 10): %s", message.from_user.id,
martingale_factor_value_float)
await message.answer(
text="Ошибка: коэффициент мартингейла должен быть в диапазоне от 0.1 до 10"
)
logger.debug(
"User %s input invalid (not in range 0.1 to 10): %s",
message.from_user.id,
martingale_factor_value_float,
)
return
req = await rq.set_martingale_factor(
@@ -878,7 +886,10 @@ async def set_max_bets_in_series(message: Message, state: FSMContext) -> None:
)
return
if safe_float(max_bets_in_series_value) < 1 or safe_float(max_bets_in_series_value) > 100:
if (
safe_float(max_bets_in_series_value) < 1
or safe_float(max_bets_in_series_value) > 100
):
await message.answer(
"Ошибка: число должно быть в диапазоне от 1 до 100.",
reply_markup=kbi.back_to_additional_settings,

View File

@@ -98,7 +98,10 @@ async def set_take_profit_percent(message: Message, state: FSMContext) -> None:
)
return
if safe_float(take_profit_percent_value) < 1 or safe_float(take_profit_percent_value) > 100:
if (
safe_float(take_profit_percent_value) < 1
or safe_float(take_profit_percent_value) > 100
):
await message.answer(
text="Ошибка: введите число от 1 до 100.",
reply_markup=kbi.back_to_risk_management,
@@ -219,7 +222,10 @@ async def set_stop_loss_percent(message: Message, state: FSMContext) -> None:
)
return
if safe_float(stop_loss_percent_value) < 1 or safe_float(stop_loss_percent_value) > 100:
if (
safe_float(stop_loss_percent_value) < 1
or safe_float(stop_loss_percent_value) > 100
):
await message.answer(
text="Ошибка: введите число от 1 до 100.",
reply_markup=kbi.back_to_risk_management,
@@ -232,7 +238,8 @@ async def set_stop_loss_percent(message: Message, state: FSMContext) -> None:
return
req = await rq.set_stop_loss_percent(
tg_id=message.from_user.id, stop_loss_percent=safe_float(stop_loss_percent_value)
tg_id=message.from_user.id,
stop_loss_percent=safe_float(stop_loss_percent_value),
)
if req:

View File

@@ -6,7 +6,7 @@ from aiogram.types import CallbackQuery
import app.telegram.keyboards.inline as kbi
import database.request as rq
from app.bybit import get_bybit_client
from app.helper_functions import calculate_total_budget, safe_float
from logger_helper.logger_helper import LOGGING_CONFIG
@@ -25,6 +25,7 @@ async def additional_settings(callback_query: CallbackQuery, state: FSMContext)
try:
await state.clear()
tg_id = callback_query.from_user.id
symbol = await rq.get_user_symbol(tg_id=tg_id)
additional_data = await rq.get_user_additional_settings(tg_id=tg_id)
if not additional_data:
@@ -62,15 +63,30 @@ async def additional_settings(callback_query: CallbackQuery, state: FSMContext)
max_bets = additional_data.max_bets_in_series
quantity = f(additional_data.order_quantity)
trigger_price = f(additional_data.trigger_price) or 0
risk_management_data = await rq.get_user_risk_management(tg_id=tg_id)
commission_fee = risk_management_data.commission_fee
client = await get_bybit_client(tg_id=tg_id)
fee_info = client.get_fee_rates(category="linear", symbol=symbol)
if commission_fee == "Yes_commission_fee":
commission_fee_percent = safe_float(
fee_info["result"]["list"][0]["takerFeeRate"]
)
else:
commission_fee_percent = 0.0
switch_side_mode = ""
if trade_mode == "Switch":
switch_side_mode = f"- Направление первой сделки: {switch_side}\n"
quantity_price = quantity * trigger_price
total_commission = quantity_price * commission_fee_percent
total_budget = await calculate_total_budget(
quantity=quantity,
martingale_factor=martingale,
max_steps=max_bets,
commission_fee_percent=total_commission,
)
text = (
f"Основные настройки:\n\n"
@@ -82,7 +98,7 @@ async def additional_settings(callback_query: CallbackQuery, state: FSMContext)
f"- Коэффициент мартингейла: {martingale:.2f}\n"
f"- Триггер цена: {trigger_price:.4f} USDT\n"
f"- Максимальное кол-во ставок в серии: {max_bets}\n\n"
f"- Бюджет серии: {total_budget:.2f} USDT\n"
f"- Бюджет серии: {total_budget:.4f} USDT\n"
)
keyboard = kbi.get_additional_settings_keyboard(mode=trade_mode)

View File

@@ -10,10 +10,7 @@ import database.request as rq
from app.bybit.get_functions.get_positions import get_active_positions_by_symbol
from app.bybit.open_positions import start_trading_cycle
from app.helper_functions import safe_float
from app.telegram.tasks.tasks import (
add_start_task_merged,
cancel_start_task_merged
)
from app.telegram.tasks.tasks import add_start_task_merged, cancel_start_task_merged
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
@@ -120,9 +117,7 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non
logger.error("Cancelled timer for user %s", callback_query.from_user.id)
@router_start_trading.callback_query(
lambda c: c.data == "cancel_timer_merged"
)
@router_start_trading.callback_query(lambda c: c.data == "cancel_timer_merged")
async def cancel_start_trading(
callback_query: CallbackQuery, state: FSMContext
) -> None:

View File

@@ -61,8 +61,7 @@ main_settings = InlineKeyboardMarkup(
# additional_settings
def get_additional_settings_keyboard(mode: str
) -> InlineKeyboardMarkup:
def get_additional_settings_keyboard(mode: str) -> InlineKeyboardMarkup:
"""
Create keyboard for additional settings
:param mode: Trade mode
@@ -77,23 +76,23 @@ def get_additional_settings_keyboard(mode: str
InlineKeyboardButton(
text="Размер кредитного плеча", callback_data="leverage"
),
InlineKeyboardButton(
text="Базовая ставка", callback_data="order_quantity"),
InlineKeyboardButton(text="Базовая ставка", callback_data="order_quantity"),
],
[
InlineKeyboardButton(
text="Коэффициент мартингейла", callback_data="martingale_factor"
),
InlineKeyboardButton(text="Триггер цена", callback_data="trigger_price"
),
InlineKeyboardButton(text="Триггер цена", callback_data="trigger_price"),
],
]
if mode == "Switch":
buttons.append(
[InlineKeyboardButton(text="Направление первой сделки", callback_data="switch_side_start")]
[
InlineKeyboardButton(
text="Направление первой сделки", callback_data="switch_side_start"
)
]
)
buttons.append(
@@ -117,9 +116,7 @@ def get_additional_settings_keyboard(mode: str
trade_mode = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text="Лонг", callback_data="Long"
),
InlineKeyboardButton(text="Лонг", callback_data="Long"),
InlineKeyboardButton(text="Шорт", callback_data="Short"),
InlineKeyboardButton(text="Свитч", callback_data="Switch"),
],
@@ -188,9 +185,7 @@ risk_management = InlineKeyboardMarkup(
InlineKeyboardButton(
text="Тейк-профит", callback_data="take_profit_percent"
),
InlineKeyboardButton(
text="Стоп-лосс", callback_data="stop_loss_percent"
),
InlineKeyboardButton(text="Стоп-лосс", callback_data="stop_loss_percent"),
],
[InlineKeyboardButton(text="Комиссия биржи", callback_data="commission_fee")],
[

View File

@@ -1,10 +1,8 @@
from aiogram.types import KeyboardButton, ReplyKeyboardMarkup
profile = ReplyKeyboardMarkup(
keyboard=[
[KeyboardButton(text="Панель Bybit"), KeyboardButton(text="Профиль")],
[KeyboardButton(text="Подключить платформу Bybit")],
],
keyboard=[[KeyboardButton(text="Панель Bybit"), KeyboardButton(text="Профиль")],
[KeyboardButton(text="Подключить платформу Bybit")]],
resize_keyboard=True,
one_time_keyboard=True,
input_field_placeholder="Выберите пункт меню...",

View File

@@ -12,7 +12,9 @@ logger = logging.getLogger("database")
async_engine = create_async_engine(DATABASE_URL, echo=False)
async_session = async_sessionmaker(async_engine, class_=AsyncSession, expire_on_commit=False)
async_session = async_sessionmaker(
async_engine, class_=AsyncSession, expire_on_commit=False
)
async def init_db():

View File

@@ -1,6 +1,15 @@
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.asyncio import AsyncAttrs
from sqlalchemy import Column, ForeignKey, Integer, String, BigInteger, Float, Boolean, UniqueConstraint
from sqlalchemy import (
Column,
ForeignKey,
Integer,
String,
BigInteger,
Float,
Boolean,
UniqueConstraint,
)
from sqlalchemy.orm import relationship
Base = declarative_base(cls=AsyncAttrs)
@@ -8,61 +17,77 @@ Base = declarative_base(cls=AsyncAttrs)
class User(Base):
"""User model."""
__tablename__ = "users"
id = Column(Integer, primary_key=True, autoincrement=True)
tg_id = Column(BigInteger, nullable=False, unique=True)
username = Column(String, nullable=False)
user_api = relationship("UserApi",
user_api = relationship(
"UserApi",
back_populates="user",
cascade="all, delete-orphan",
passive_deletes=True,
uselist=False)
uselist=False,
)
user_symbol = relationship("UserSymbol",
user_symbol = relationship(
"UserSymbol",
back_populates="user",
cascade="all, delete-orphan",
passive_deletes=True,
uselist=False)
uselist=False,
)
user_additional_settings = relationship("UserAdditionalSettings",
user_additional_settings = relationship(
"UserAdditionalSettings",
back_populates="user",
cascade="all, delete-orphan",
passive_deletes=True,
uselist=False)
uselist=False,
)
user_risk_management = relationship("UserRiskManagement",
user_risk_management = relationship(
"UserRiskManagement",
back_populates="user",
cascade="all, delete-orphan",
passive_deletes=True,
uselist=False)
uselist=False,
)
user_conditional_settings = relationship("UserConditionalSettings",
user_conditional_settings = relationship(
"UserConditionalSettings",
back_populates="user",
cascade="all, delete-orphan",
passive_deletes=True,
uselist=False)
uselist=False,
)
user_deals = relationship("UserDeals",
user_deals = relationship(
"UserDeals",
back_populates="user",
cascade="all, delete-orphan",
passive_deletes=True)
passive_deletes=True,
)
user_auto_trading = relationship("UserAutoTrading",
user_auto_trading = relationship(
"UserAutoTrading",
back_populates="user",
cascade="all, delete-orphan",
passive_deletes=True)
passive_deletes=True,
)
class UserApi(Base):
"""User API model."""
__tablename__ = "user_api"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer,
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False, unique=True)
user_id = Column(
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, unique=True
)
api_key = Column(String, nullable=False)
api_secret = Column(String, nullable=False)
@@ -71,12 +96,13 @@ class UserApi(Base):
class UserSymbol(Base):
"""User symbol model."""
__tablename__ = "user_symbol"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer,
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False, unique=True)
user_id = Column(
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, unique=True
)
symbol = Column(String, nullable=False, default="BTCUSDT")
user = relationship("User", back_populates="user_symbol")
@@ -84,12 +110,13 @@ class UserSymbol(Base):
class UserAdditionalSettings(Base):
"""User additional settings model."""
__tablename__ = "user_additional_settings"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer,
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False, unique=True)
user_id = Column(
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, unique=True
)
trade_mode = Column(String, nullable=False, default="Merged_Single")
switch_side = Column(String, nullable=False, default="По направлению")
trigger_price = Column(Float, nullable=False, default=0.0)
@@ -104,12 +131,13 @@ class UserAdditionalSettings(Base):
class UserRiskManagement(Base):
"""User risk management model."""
__tablename__ = "user_risk_management"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer,
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False, unique=True)
user_id = Column(
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, unique=True
)
take_profit_percent = Column(Float, nullable=False, default=1)
stop_loss_percent = Column(Float, nullable=False, default=1)
commission_fee = Column(String, nullable=False, default="Yes_commission_fee")
@@ -119,12 +147,13 @@ class UserRiskManagement(Base):
class UserConditionalSettings(Base):
"""User conditional settings model."""
__tablename__ = "user_conditional_settings"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer,
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False, unique=True)
user_id = Column(
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, unique=True
)
timer_start = Column(Integer, nullable=False, default=0)
timer_end = Column(Integer, nullable=False, default=0)
@@ -134,12 +163,13 @@ class UserConditionalSettings(Base):
class UserDeals(Base):
"""User deals model."""
__tablename__ = "user_deals"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer,
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False)
user_id = Column(
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False
)
current_step = Column(Integer, nullable=True)
symbol = Column(String, nullable=True)
trade_mode = Column(String, nullable=True)
@@ -157,19 +187,18 @@ class UserDeals(Base):
user = relationship("User", back_populates="user_deals")
__table_args__ = (
UniqueConstraint('user_id', 'symbol', name='uq_user_symbol'),
)
__table_args__ = (UniqueConstraint("user_id", "symbol", name="uq_user_symbol"),)
class UserAutoTrading(Base):
"""User auto trading model."""
__tablename__ = "user_auto_trading"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer,
ForeignKey("users.id", ondelete="CASCADE"),
nullable=False)
user_id = Column(
Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False
)
symbol = Column(String, nullable=True)
auto_trading = Column(Boolean, nullable=True)
fee = Column(Float, nullable=True)

View File

@@ -906,7 +906,7 @@ async def set_user_deal(
max_bets_in_series: int,
take_profit_percent: int,
stop_loss_percent: int,
base_quantity: float
base_quantity: float,
):
"""
Set the user deal in the database.
@@ -969,7 +969,7 @@ async def set_user_deal(
max_bets_in_series=max_bets_in_series,
take_profit_percent=take_profit_percent,
stop_loss_percent=stop_loss_percent,
base_quantity=base_quantity
base_quantity=base_quantity,
)
session.add(new_deal)
@@ -978,7 +978,9 @@ async def set_user_deal(
return True
except Exception as e:
logger.error("Error setting user deal for user %s and symbol %s: %s", tg_id, symbol, e)
logger.error(
"Error setting user deal for user %s and symbol %s: %s", tg_id, symbol, e
)
return False
@@ -998,7 +1000,9 @@ async def get_user_deal_by_symbol(tg_id: int, symbol: str):
deal = result_deal.scalars().first()
return deal
except Exception as e:
logger.error("Error getting deal for user %s and symbol %s: %s", tg_id, symbol, e)
logger.error(
"Error getting deal for user %s and symbol %s: %s", tg_id, symbol, e
)
return None
@@ -1040,18 +1044,26 @@ async def set_fee_user_deal_by_symbol(tg_id: int, symbol: str, fee: float):
if record:
record.fee = fee
else:
logger.error(f"User deal with user_id={user.id} and symbol={symbol} not found")
logger.error(
f"User deal with user_id={user.id} and symbol={symbol} not found"
)
return False
await session.commit()
logger.info("Set fee for user %s and symbol %s", tg_id, symbol)
return True
except Exception as e:
logger.error("Error setting user deal fee for user %s and symbol %s: %s", tg_id, symbol, e)
logger.error(
"Error setting user deal fee for user %s and symbol %s: %s",
tg_id,
symbol,
e,
)
return False
# USER AUTO TRADING
async def get_all_user_auto_trading(tg_id: int):
"""Get all user auto trading from the database asynchronously."""
try:
@@ -1086,7 +1098,9 @@ async def get_user_auto_trading(tg_id: int, symbol: str):
auto_trading = result_auto_trading.scalars().first()
return auto_trading
except Exception as e:
logger.error("Error getting auto trading for user %s and symbol %s: %s", tg_id, symbol, e)
logger.error(
"Error getting auto trading for user %s and symbol %s: %s", tg_id, symbol, e
)
return None
@@ -1120,10 +1134,17 @@ async def set_auto_trading(tg_id: int, symbol: str, auto_trading: bool) -> bool:
)
session.add(new_record)
await session.commit()
logger.info("Set auto_trading=%s for user %s and symbol %s", auto_trading, tg_id, symbol)
logger.info(
"Set auto_trading=%s for user %s and symbol %s",
auto_trading,
tg_id,
symbol,
)
return True
except Exception as e:
logger.error("Error setting auto_trading for user %s and symbol %s: %s", tg_id, symbol, e)
logger.error(
"Error setting auto_trading for user %s and symbol %s: %s", tg_id, symbol, e
)
return False
@@ -1161,11 +1182,18 @@ async def set_fee_user_auto_trading(tg_id: int, symbol: str, fee: float) -> bool
logger.info("Set fee for user %s and symbol %s", tg_id, symbol)
return True
except Exception as e:
logger.error("Error setting user auto trading fee for user %s and symbol %s: %s", tg_id, symbol, e)
logger.error(
"Error setting user auto trading fee for user %s and symbol %s: %s",
tg_id,
symbol,
e,
)
return False
async def set_total_fee_user_auto_trading(tg_id: int, symbol: str, total_fee: float) -> bool:
async def set_total_fee_user_auto_trading(
tg_id: int, symbol: str, total_fee: float
) -> bool:
"""
Set the total fee for a user auto trading in the database.
:param tg_id: Telegram user ID
@@ -1199,5 +1227,10 @@ async def set_total_fee_user_auto_trading(tg_id: int, symbol: str, total_fee: fl
logger.info("Set total fee for user %s and symbol %s", tg_id, symbol)
return True
except Exception as e:
logger.error("Error setting user auto trading total fee for user %s and symbol %s: %s", tg_id, symbol, e)
logger.error(
"Error setting user auto trading total fee for user %s and symbol %s: %s",
tg_id,
symbol,
e,
)
return False