2
0
forked from kodorvan/stcs

42 Commits

Author SHA1 Message Date
f0732607e2 Merge pull request 'The instruction has been corrected' (#13) from Alex/stcs:devel into stable
Reviewed-on: kodorvan/stcs#13
2025-10-11 16:36:48 +07:00
algizn97
458b34fcec The instruction has been corrected 2025-10-11 14:14:27 +05:00
56af1d8f3b Merge pull request 'Added migrations for the database' (#12) from Alex/stcs:devel into stable
Reviewed-on: kodorvan/stcs#12
2025-10-11 15:49:40 +07:00
algizn97
4a7577b977 Added migrations for the database 2025-10-11 13:36:38 +05:00
9f069df68a Merge pull request 'devel' (#11) from Alex/stcs:devel into stable
Reviewed-on: kodorvan/stcs#11
2025-10-11 11:58:36 +07:00
algizn97
6e0a170f4b Merge branch 'devel' of https://git.svoboda.works/Alex/stcs into devel 2025-10-10 15:21:09 +05:00
algizn97
c7b4a08a6a Merge branch 'stable' of https://git.svoboda.works/Alex/stcs into devel 2025-10-10 15:19:31 +05:00
algizn97
d0971f59b4 Added environments 2025-10-10 14:42:47 +05:00
b92376d2da merge upstream 2025-10-10 16:35:16 +07:00
630f2002d3 Удалить alembic.ini 2025-10-10 16:24:01 +07:00
0784cbb54a Удалить alembic/versions/fd8581c0cc87_updated_leverage.py 2025-10-10 16:23:51 +07:00
eeb7f81440 Удалить alembic/versions/f00a94ccdf01_updated_deals.py 2025-10-10 16:23:47 +07:00
b03d05bb75 Удалить alembic/versions/ef38c90eed55_added_last_side_the_conditional_data.py 2025-10-10 16:23:43 +07:00
e0e4ad5d4b Удалить alembic/versions/ef342b38e17b_added_fee_user_deals.py 2025-10-10 16:23:39 +07:00
fab8ff5040 Удалить alembic/versions/dbffe818030c_added_last_side_and_auto_trading_for_.py 2025-10-10 16:23:35 +07:00
8071f8c896 Удалить alembic/versions/d3c85bad8c98_added_limit_and_trigger_price_for_user_.py 2025-10-10 16:23:30 +07:00
3db001bd19 Удалить alembic/versions/ccdc5764eb4f_added_userdeals.py 2025-10-10 16:23:26 +07:00
99c59be9ed Удалить alembic/versions/acbcc95de48d_updated_userdeals.py 2025-10-10 16:23:21 +07:00
37b7b6effd Удалить alembic/versions/c98b9dc36d15_fixed_auto_trade.py 2025-10-10 16:23:17 +07:00
ee285523f2 Удалить alembic/versions/8f1476c68efa_added_position_idx_for_user_deals_table.py 2025-10-10 16:23:12 +07:00
b426eb2136 Удалить alembic/versions/968f8121104f_updated_user_deals_and_user_conditional_.py 2025-10-10 16:23:07 +07:00
2df3b8b40d Удалить alembic/versions/c710f4e2259c_unnecessary_data_has_been_deleted.py 2025-10-10 16:23:03 +07:00
8c08451d82 Удалить alembic/versions/863d6215e1eb_updated_deals.py 2025-10-10 16:22:52 +07:00
d81a47b669 Удалить alembic/versions/77197715747c_deleted_position_idx_for_user_deals_.py 2025-10-10 16:22:48 +07:00
2cdfba3537 Удалить alembic/versions/73a00faa4f7f_added_user_auto_trading_table.py 2025-10-10 16:22:43 +07:00
c89c2ad803 Удалить alembic/versions/70094ba27e80_create_user_conditional_setting.py 2025-10-10 16:22:39 +07:00
3986989dbd Удалить alembic/versions/42c66cfe8d4e_updated_martingale_factor.py 2025-10-10 16:22:35 +07:00
c0e40dc205 Удалить alembic/versions/3534adf891fc_update_last_side_the_conditional_data.py 2025-10-10 16:22:30 +07:00
6c6f0dbb7b Удалить alembic/versions/10bf073c71f9_added_fee_for_user_auto_trading.py 2025-10-10 16:22:26 +07:00
44c4fde036 Удалить alembic/versions/45977e9d8558_updated_order_quantity.py 2025-10-10 16:22:21 +07:00
21a93d47d4 Удалить alembic/versions/2b9572b49ecd_added_side_for_user_auto_trading.py 2025-10-10 16:22:18 +07:00
3f43d42651 Удалить alembic/versions/0eed68eddcdb_added_conditional_order_type.py 2025-10-10 16:22:13 +07:00
aab05994ce Удалить alembic/versions/09db71875980_updated_user_deals_table.py 2025-10-10 16:22:08 +07:00
a58ebe6a46 Удалить alembic/README 2025-10-10 16:21:38 +07:00
1ec1f1784d Удалить alembic/script.py.mako 2025-10-10 16:21:33 +07:00
7901af86af Удалить alembic/env.py 2025-10-10 16:21:24 +07:00
fedfa00c10 Merge pull request 'devel' (#3) from devel into stable
Reviewed-on: #3
2025-10-10 16:18:23 +07:00
algizn97
fc8ab19ae9 Fixed the budget calculation function 2025-10-10 14:14:46 +05:00
898ff91392 Merge pull request 'added the exc_info flag' (#10) from Alex/stcs:dev into stable
Reviewed-on: kodorvan/stcs#10
2025-10-07 12:10:58 +07:00
2047dd5ac6 Merge pull request 'dev' (#9) from Alex/stcs:dev into stable
Reviewed-on: kodorvan/stcs#9
2025-10-06 21:33:56 +07:00
fec367cc1d Merge pull request 'dev' (#8) from Alex/stcs:dev into stable
Reviewed-on: kodorvan/stcs#8
2025-09-19 17:17:25 +07:00
aebcc9dff2 Merge pull request 'dev' (#7) from Alex/stcs:dev into stable
Reviewed-on: kodorvan/stcs#7
2025-09-18 22:49:55 +07:00
21 changed files with 265 additions and 269 deletions

2
.gitignore vendored
View File

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

View File

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

View File

@@ -0,0 +1,40 @@
"""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

@@ -0,0 +1,34 @@
"""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,7 +16,9 @@ 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
@@ -27,7 +29,9 @@ async def start_trading_cycle(tg_id: int) -> str | None:
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
@@ -103,7 +107,7 @@ async def start_trading_cycle(tg_id: int) -> str | None:
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":
@@ -121,7 +125,7 @@ async def start_trading_cycle(tg_id: int) -> str | None:
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 (
@@ -138,7 +142,7 @@ async def start_trading_cycle(tg_id: int) -> str | None:
"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
)
@@ -148,12 +152,12 @@ async def start_trading_cycle(tg_id: int) -> str | None:
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
@@ -184,7 +188,9 @@ async def trading_cycle(tg_id: int, symbol: str, reverse_side: str) -> str | Non
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:
@@ -200,7 +206,7 @@ async def trading_cycle(tg_id: int, symbol: str, reverse_side: str) -> str | Non
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":
@@ -218,7 +224,7 @@ async def trading_cycle(tg_id: int, symbol: str, reverse_side: str) -> str | Non
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"
@@ -249,7 +255,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,7 +46,9 @@ 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, commission_fee_percent
quantity, martingale_factor, max_steps
) -> float:
"""
Calculate the total budget for a series of trading steps.
@@ -167,7 +167,6 @@ 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.
@@ -175,12 +174,8 @@ 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,10 +6,11 @@ from aiogram.types import CallbackQuery, Message
import app.telegram.keyboards.inline as kbi
import database.request as rq
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.get_functions.get_instruments_info import get_instruments_info
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
@@ -98,9 +99,7 @@ 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)
@@ -124,8 +123,7 @@ 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,9 +142,7 @@ 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.
@@ -216,7 +214,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",
@@ -555,8 +553,7 @@ 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
)
@@ -770,14 +767,9 @@ 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(
@@ -886,10 +878,7 @@ 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,10 +98,7 @@ 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,
@@ -222,10 +219,7 @@ 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,
@@ -238,8 +232,7 @@ 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,7 +25,6 @@ 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:
@@ -63,30 +62,15 @@ 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"
@@ -98,7 +82,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:.4f} USDT\n"
f"- Бюджет серии: {total_budget:.2f} USDT\n"
)
keyboard = kbi.get_additional_settings_keyboard(mode=trade_mode)

View File

@@ -10,7 +10,10 @@ 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)
@@ -117,7 +120,9 @@ 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,7 +61,8 @@ 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
@@ -76,23 +77,23 @@ def get_additional_settings_keyboard(mode: str) -> InlineKeyboardMarkup:
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(
@@ -116,7 +117,9 @@ def get_additional_settings_keyboard(mode: str) -> InlineKeyboardMarkup:
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"),
],
@@ -185,7 +188,9 @@ 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,8 +1,10 @@
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,9 +12,7 @@ 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,15 +1,6 @@
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)
@@ -17,77 +8,61 @@ 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)
@@ -96,13 +71,12 @@ 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")
@@ -110,13 +84,12 @@ 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)
@@ -131,13 +104,12 @@ 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")
@@ -147,13 +119,12 @@ 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)
@@ -163,13 +134,12 @@ 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)
@@ -187,18 +157,19 @@ 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,9 +978,7 @@ 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
@@ -1000,9 +998,7 @@ 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
@@ -1044,26 +1040,18 @@ 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:
@@ -1098,9 +1086,7 @@ 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
@@ -1134,17 +1120,10 @@ 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
@@ -1182,18 +1161,11 @@ 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
@@ -1227,10 +1199,5 @@ async def set_total_fee_user_auto_trading(
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