2
0
forked from kodorvan/stcs

63 Commits

Author SHA1 Message Date
12d1db16d3 вот бы ебануло нормально...
Reviewed-on: kodorvan/stcs#20
2025-10-25 19:54:27 +07:00
algizn97
7350c86927 The text has been corrected, fixed the commission check 2025-10-25 17:50:46 +05:00
0a369b10f2 Merge pull request 'devel' (#19) from Alex/stcs:devel into stable
Reviewed-on: kodorvan/stcs#19
2025-10-23 14:35:32 +07:00
algizn97
42f0f8ddc0 The text has been corrected 2025-10-23 11:31:15 +05:00
algizn97
3df88d07ab The stop trading button has been added, and the switch mode has been fixed 2025-10-23 11:28:44 +05:00
7b1a803db4 Merge pull request 'Fixed the switch trading mode, adjusted the take profit, added a trading cycle' (#18) from Alex/stcs:devel into stable
Reviewed-on: kodorvan/stcs#18
2025-10-22 22:20:21 +07:00
algizn97
ddfa3a7360 Fixed the switch trading mode, adjusted the take profit, added a trading cycle 2025-10-22 17:15:25 +05:00
9fcd92cc72 Merge pull request 'The formula for calculating the number of contracts by price has been changed' (#17) from Alex/stcs:devel into stable
Reviewed-on: kodorvan/stcs#17
2025-10-21 20:33:42 +07:00
algizn97
e61b7334a4 The formula for calculating the number of contracts by price has been changed 2025-10-21 13:59:09 +05:00
97a199f31e Merge pull request 'devel' (#16) from Alex/stcs:devel into stable
Reviewed-on: kodorvan/stcs#16
2025-10-18 18:09:23 +07:00
algizn97
5ad69f3f6d Fixed take profit calculation. Added a position check for the current pair when trying to change the margin. 2025-10-18 13:52:20 +05:00
algizn97
abad01352a Fixed the output 2025-10-17 11:28:57 +05:00
algizn97
720b30d681 Redundant call removed 2025-10-17 11:13:31 +05:00
algizn97
3616e2cbd3 Added verification for open orders. Adjusted responses for the user 2025-10-17 11:12:50 +05:00
algizn97
7d108337fa Fixed receiving the commission and calculating the total commission 2025-10-17 11:10:35 +05:00
algizn97
0f6e6a2168 Added position mode setting, fixed stop loss calculation 2025-10-17 11:09:21 +05:00
951bc15957 Merge pull request 'devel' (#15) from Alex/stcs:devel into stable
Reviewed-on: kodorvan/stcs#15
2025-10-12 17:26:11 +07:00
algizn97
258ed970f1 Fixed database creation 2025-10-12 15:08:27 +05:00
algizn97
a3a6509933 Fixed database creation 2025-10-12 15:05:50 +05:00
5937058899 Merge pull request 'The database has been converted to SQLite' (#14) from Alex/stcs:devel into stable
Reviewed-on: kodorvan/stcs#14
2025-10-12 14:35:54 +07:00
algizn97
8251938b2f The database has been converted to SQLite 2025-10-12 12:34:32 +05:00
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
47 changed files with 539 additions and 1409 deletions

View File

@@ -1,6 +1 @@
BOT_TOKEN=YOUR_BOT_TOKEN BOT_TOKEN=YOUR_BOT_TOKEN
DB_USER=your_username
DB_PASS=your_password
DB_HOST=your_host
DB_PORT=your_port
DB_NAME=your_database

3
.gitignore vendored
View File

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

View File

@@ -54,6 +54,10 @@ sudo -u www-data /usr/bin/pip install -r requirements.txt
cp .env.sample .env cp .env.sample .env
nvim .env nvim .env
``` ```
5. Выполните миграции:
```bash
alembic upgrade head
```
5. Запустите бота: 5. Запустите бота:

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 = driver://user:pass@localhost/dbname sqlalchemy.url = sqlite+aiosqlite:///./database/db/stcs.db
[post_write_hooks] [post_write_hooks]

View File

@@ -1,73 +1,29 @@
import asyncio import asyncio
from logging.config import fileConfig from logging.config import fileConfig
from sqlalchemy import pool from sqlalchemy import pool
from sqlalchemy.engine import Connection
from sqlalchemy.ext.asyncio import async_engine_from_config from sqlalchemy.ext.asyncio import async_engine_from_config
from alembic import context from alembic import context
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
from config import DATABASE_URL
config = context.config config = context.config
config.set_main_option('sqlalchemy.url', DATABASE_URL)
# Interpret the config file for Python logging.
# This line sets up loggers basically.
if config.config_file_name is not None: if config.config_file_name is not None:
fileConfig(config.config_file_name) fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from database.models import Base from database.models import Base
target_metadata = Base.metadata target_metadata = Base.metadata
# other values from the config, defined by the needs of env.py, def do_run_migrations(connection):
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline() -> None:
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
context.configure( context.configure(
url=DATABASE_URL, connection=connection,
target_metadata=target_metadata, target_metadata=target_metadata,
literal_binds=True, compare_type=True,
dialect_opts={"paramstyle": "named"},
) )
with context.begin_transaction(): with context.begin_transaction():
context.run_migrations() context.run_migrations()
async def run_async_migrations():
def do_run_migrations(connection: Connection) -> None:
context.configure(connection=connection, target_metadata=target_metadata)
with context.begin_transaction():
context.run_migrations()
async def run_async_migrations() -> None:
"""In this scenario we need to create an Engine
and associate a connection with the context.
"""
connectable = async_engine_from_config( connectable = async_engine_from_config(
config.get_section(config.config_ini_section, {}), config.get_section(config.config_ini_section),
prefix="sqlalchemy.", prefix="sqlalchemy.",
poolclass=pool.NullPool, poolclass=pool.NullPool,
) )
@@ -77,13 +33,20 @@ async def run_async_migrations() -> None:
await connectable.dispose() await connectable.dispose()
def run_migrations_offline():
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None: def run_migrations_online():
"""Run migrations in 'online' mode."""
asyncio.run(run_async_migrations()) asyncio.run(run_async_migrations())
if context.is_offline_mode(): if context.is_offline_mode():
run_migrations_offline() run_migrations_offline()
else: else:

View File

@@ -1,36 +0,0 @@
"""updated user deals table
Revision ID: 09db71875980
Revises: 77197715747c
Create Date: 2025-09-29 12:57:39.943294
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '09db71875980'
down_revision: Union[str, Sequence[str], None] = '77197715747c'
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('order_quantity', sa.Float(), nullable=True))
op.create_unique_constraint('uq_user_symbol', 'user_deals', ['user_id', 'symbol'])
op.drop_column('user_deals', 'quantity')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user_deals', sa.Column('quantity', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True))
op.drop_constraint('uq_user_symbol', 'user_deals', type_='unique')
op.drop_column('user_deals', 'order_quantity')
# ### end Alembic commands ###

View File

@@ -1,40 +0,0 @@
"""Added conditional_order_type
Revision ID: 0eed68eddcdb
Revises: 70094ba27e80
Create Date: 2025-09-24 13:47:23.282807
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '0eed68eddcdb'
down_revision: Union[str, Sequence[str], None] = '70094ba27e80'
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('conditional_order_type', sa.String(), nullable=False))
op.add_column('user_additional_settings', sa.Column('limit_price', sa.Float(), nullable=False))
op.add_column('user_additional_settings', sa.Column('trigger_price', sa.Float(), nullable=False))
op.drop_column('user_conditional_settings', 'trigger_price')
op.drop_column('user_conditional_settings', 'limit_price')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user_conditional_settings', sa.Column('limit_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=False))
op.add_column('user_conditional_settings', sa.Column('trigger_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=False))
op.drop_column('user_additional_settings', 'trigger_price')
op.drop_column('user_additional_settings', 'limit_price')
op.drop_column('user_additional_settings', 'conditional_order_type')
# ### end Alembic commands ###

View File

@@ -1,34 +0,0 @@
"""Added fee for user auto trading
Revision ID: 10bf073c71f9
Revises: 2b9572b49ecd
Create Date: 2025-10-02 17:52:05.235523
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '10bf073c71f9'
down_revision: Union[str, Sequence[str], None] = '2b9572b49ecd'
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_auto_trading', sa.Column('fee', sa.Float(), nullable=True))
op.drop_column('user_deals', 'fee')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user_deals', sa.Column('fee', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True))
op.drop_column('user_auto_trading', 'fee')
# ### end Alembic commands ###

View File

@@ -1,34 +0,0 @@
"""Added side for user auto trading
Revision ID: 2b9572b49ecd
Revises: ef342b38e17b
Create Date: 2025-10-02 17:21:20.904797
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '2b9572b49ecd'
down_revision: Union[str, Sequence[str], None] = 'ef342b38e17b'
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_auto_trading', sa.Column('side', sa.String(), nullable=True))
op.drop_constraint(op.f('uq_user_auto_trading_symbol'), 'user_auto_trading', type_='unique')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_unique_constraint(op.f('uq_user_auto_trading_symbol'), 'user_auto_trading', ['user_id', 'symbol'], postgresql_nulls_not_distinct=False)
op.drop_column('user_auto_trading', 'side')
# ### end Alembic commands ###

View File

@@ -1,32 +0,0 @@
"""update last side the conditional data
Revision ID: 3534adf891fc
Revises: ef38c90eed55
Create Date: 2025-09-30 08:39:02.971158
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '3534adf891fc'
down_revision: Union[str, Sequence[str], None] = 'ef38c90eed55'
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! ###
pass
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###

View File

@@ -1,38 +0,0 @@
"""Updated martingale factor
Revision ID: 42c66cfe8d4e
Revises: 45977e9d8558
Create Date: 2025-09-22 17:17:39.779979
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '42c66cfe8d4e'
down_revision: Union[str, Sequence[str], None] = '45977e9d8558'
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', 'martingale_factor',
existing_type=sa.INTEGER(),
type_=sa.Float(),
existing_nullable=False)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('user_additional_settings', 'martingale_factor',
existing_type=sa.Float(),
type_=sa.INTEGER(),
existing_nullable=False)
# ### end Alembic commands ###

View File

@@ -1,38 +0,0 @@
"""Updated order quantity
Revision ID: 45977e9d8558
Revises: fd8581c0cc87
Create Date: 2025-09-22 16:59:40.415398
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '45977e9d8558'
down_revision: Union[str, Sequence[str], None] = 'fd8581c0cc87'
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', 'order_quantity',
existing_type=sa.INTEGER(),
type_=sa.Float(),
existing_nullable=False)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('user_additional_settings', 'order_quantity',
existing_type=sa.Float(),
type_=sa.INTEGER(),
existing_nullable=False)
# ### end Alembic commands ###

View File

@@ -1,40 +0,0 @@
"""Create User Conditional Setting
Revision ID: 70094ba27e80
Revises: 42c66cfe8d4e
Create Date: 2025-09-23 16:47:07.161544
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '70094ba27e80'
down_revision: Union[str, Sequence[str], None] = '42c66cfe8d4e'
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.create_table('user_conditional_settings',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('limit_price', sa.Float(), nullable=False),
sa.Column('trigger_price', sa.Float(), nullable=False),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('user_id')
)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('user_conditional_settings')
# ### end Alembic commands ###

View File

@@ -1,42 +0,0 @@
"""added user_auto_trading table
Revision ID: 73a00faa4f7f
Revises: 968f8121104f
Create Date: 2025-10-01 12:30:21.830851
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '73a00faa4f7f'
down_revision: Union[str, Sequence[str], None] = '968f8121104f'
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.create_table('user_auto_trading',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('symbol', sa.String(), nullable=True),
sa.Column('auto_trading', sa.Boolean(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('user_id', 'symbol', name='uq_user_auto_trading_symbol')
)
op.drop_column('user_conditional_settings', 'auto_trading')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user_conditional_settings', sa.Column('auto_trading', sa.BOOLEAN(), autoincrement=False, nullable=True))
op.drop_table('user_auto_trading')
# ### end Alembic commands ###

View File

@@ -1,32 +0,0 @@
"""deleted position_idx for user deals table
Revision ID: 77197715747c
Revises: 8f1476c68efa
Create Date: 2025-09-29 12:20:18.928995
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '77197715747c'
down_revision: Union[str, Sequence[str], None] = '8f1476c68efa'
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.drop_column('user_deals', 'position_idx')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user_deals', sa.Column('position_idx', sa.INTEGER(), autoincrement=False, nullable=True))
# ### end Alembic commands ###

View File

@@ -1,32 +0,0 @@
"""Updated Deals
Revision ID: 863d6215e1eb
Revises: f00a94ccdf01
Create Date: 2025-09-28 23:13:39.484468
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '863d6215e1eb'
down_revision: Union[str, Sequence[str], None] = 'f00a94ccdf01'
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('margin_type', sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user_deals', 'margin_type')
# ### end Alembic commands ###

View File

@@ -1,34 +0,0 @@
"""added position_idx for user deals table
Revision ID: 8f1476c68efa
Revises: 863d6215e1eb
Create Date: 2025-09-29 11:40:46.512160
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '8f1476c68efa'
down_revision: Union[str, Sequence[str], None] = '863d6215e1eb'
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('position_idx', sa.Integer(), nullable=True))
op.drop_constraint(op.f('user_deals_user_id_key'), 'user_deals', type_='unique')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_unique_constraint(op.f('user_deals_user_id_key'), 'user_deals', ['user_id'], postgresql_nulls_not_distinct=False)
op.drop_column('user_deals', 'position_idx')
# ### end Alembic commands ###

View File

@@ -1,36 +0,0 @@
"""updated user_deals and user_conditional_settings
Revision ID: 968f8121104f
Revises: dbffe818030c
Create Date: 2025-10-01 11:45:49.073865
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '968f8121104f'
down_revision: Union[str, Sequence[str], None] = 'dbffe818030c'
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_conditional_settings', sa.Column('auto_trading', sa.Boolean(), nullable=True))
op.drop_column('user_deals', 'commission_fee')
op.drop_column('user_deals', 'auto_trading')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user_deals', sa.Column('auto_trading', sa.BOOLEAN(), autoincrement=False, nullable=True))
op.add_column('user_deals', sa.Column('commission_fee', sa.VARCHAR(), autoincrement=False, nullable=True))
op.drop_column('user_conditional_settings', 'auto_trading')
# ### end Alembic commands ###

View File

@@ -1,60 +0,0 @@
"""Updated UserDeals
Revision ID: acbcc95de48d
Revises: ccdc5764eb4f
Create Date: 2025-09-28 16:57:28.384116
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'acbcc95de48d'
down_revision: Union[str, Sequence[str], None] = 'ccdc5764eb4f'
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_conditional_settings', sa.Column('auto_trading', sa.String(), nullable=False))
op.add_column('user_deals', sa.Column('trading_type', sa.String(), nullable=True))
op.add_column('user_deals', sa.Column('conditional_order_type', sa.String(), nullable=True))
op.add_column('user_deals', sa.Column('take_profit_percent', sa.Integer(), nullable=True))
op.add_column('user_deals', sa.Column('stop_loss_percent', sa.Integer(), nullable=True))
op.add_column('user_deals', sa.Column('max_risk_percent', sa.Integer(), nullable=True))
op.add_column('user_deals', sa.Column('commission_fee', sa.String(), nullable=True))
op.add_column('user_deals', sa.Column('switch_side_mode', sa.String(), nullable=True))
op.drop_index(op.f('ix_user_deals_deal_series_id'), table_name='user_deals')
op.drop_column('user_deals', 'take_profit')
op.drop_column('user_deals', 'deal_series_id')
op.drop_column('user_deals', 'price')
op.drop_column('user_deals', 'exec_fee')
op.drop_column('user_deals', 'stop_loss')
op.drop_column('user_deals', 'closed_size')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user_deals', sa.Column('closed_size', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('user_deals', sa.Column('stop_loss', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True))
op.add_column('user_deals', sa.Column('exec_fee', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('user_deals', sa.Column('price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True))
op.add_column('user_deals', sa.Column('deal_series_id', sa.INTEGER(), autoincrement=False, nullable=True))
op.add_column('user_deals', sa.Column('take_profit', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True))
op.create_index(op.f('ix_user_deals_deal_series_id'), 'user_deals', ['deal_series_id'], unique=False)
op.drop_column('user_deals', 'switch_side_mode')
op.drop_column('user_deals', 'commission_fee')
op.drop_column('user_deals', 'max_risk_percent')
op.drop_column('user_deals', 'stop_loss_percent')
op.drop_column('user_deals', 'take_profit_percent')
op.drop_column('user_deals', 'conditional_order_type')
op.drop_column('user_deals', 'trading_type')
op.drop_column('user_conditional_settings', 'auto_trading')
# ### end Alembic commands ###

View File

@@ -1,79 +0,0 @@
"""unnecessary data has been deleted
Revision ID: c710f4e2259c
Revises: 10bf073c71f9
Create Date: 2025-10-09 14:17:32.632574
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'c710f4e2259c'
down_revision: Union[str, Sequence[str], None] = '10bf073c71f9'
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('switch_side', sa.Boolean(), nullable=False, server_default=sa.false()))
op.drop_column('user_additional_settings', 'leverage_to_buy')
op.drop_column('user_additional_settings', 'order_type')
op.drop_column('user_additional_settings', 'limit_price')
op.drop_column('user_additional_settings', 'leverage_to_sell')
op.drop_column('user_additional_settings', 'conditional_order_type')
op.add_column('user_auto_trading', sa.Column('total_fee', sa.Float(), nullable=True))
op.drop_column('user_auto_trading', 'side')
op.drop_column('user_deals', 'switch_side_mode')
op.drop_column('user_deals', 'leverage_to_buy')
op.drop_column('user_deals', 'order_type')
op.drop_column('user_deals', 'limit_price')
op.drop_column('user_deals', 'max_risk_percent')
op.drop_column('user_deals', 'leverage_to_sell')
op.drop_column('user_deals', 'conditional_order_type')
op.alter_column('user_risk_management', 'take_profit_percent',
existing_type=sa.INTEGER(),
type_=sa.Float(),
existing_nullable=False)
op.alter_column('user_risk_management', 'stop_loss_percent',
existing_type=sa.INTEGER(),
type_=sa.Float(),
existing_nullable=False)
op.drop_column('user_risk_management', 'max_risk_percent')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user_risk_management', sa.Column('max_risk_percent', sa.INTEGER(), autoincrement=False, nullable=False))
op.alter_column('user_risk_management', 'stop_loss_percent',
existing_type=sa.Float(),
type_=sa.INTEGER(),
existing_nullable=False)
op.alter_column('user_risk_management', 'take_profit_percent',
existing_type=sa.Float(),
type_=sa.INTEGER(),
existing_nullable=False)
op.add_column('user_deals', sa.Column('conditional_order_type', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('user_deals', sa.Column('leverage_to_sell', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('user_deals', sa.Column('max_risk_percent', sa.INTEGER(), autoincrement=False, nullable=True))
op.add_column('user_deals', sa.Column('limit_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True))
op.add_column('user_deals', sa.Column('order_type', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('user_deals', sa.Column('leverage_to_buy', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('user_deals', sa.Column('switch_side_mode', sa.BOOLEAN(), autoincrement=False, nullable=True))
op.add_column('user_auto_trading', sa.Column('side', sa.VARCHAR(), autoincrement=False, nullable=True))
op.drop_column('user_auto_trading', 'total_fee')
op.add_column('user_additional_settings', sa.Column('conditional_order_type', sa.VARCHAR(), autoincrement=False, nullable=False))
op.add_column('user_additional_settings', sa.Column('leverage_to_sell', sa.VARCHAR(), autoincrement=False, nullable=False))
op.add_column('user_additional_settings', sa.Column('limit_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=False))
op.add_column('user_additional_settings', sa.Column('order_type', sa.VARCHAR(), server_default=sa.text("'Market'::character varying"), autoincrement=False, nullable=False))
op.add_column('user_additional_settings', sa.Column('leverage_to_buy', sa.VARCHAR(), autoincrement=False, nullable=False))
op.drop_column('user_additional_settings', 'switch_side')
# ### end Alembic commands ###

View File

@@ -1,38 +0,0 @@
"""Fixed auto_trade
Revision ID: c98b9dc36d15
Revises: acbcc95de48d
Create Date: 2025-09-28 21:33:08.319232
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'c98b9dc36d15'
down_revision: Union[str, Sequence[str], None] = 'acbcc95de48d'
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_conditional_settings', 'auto_trading',
existing_type=sa.VARCHAR(),
type_=sa.Boolean(),
existing_nullable=False)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('user_conditional_settings', 'auto_trading',
existing_type=sa.Boolean(),
type_=sa.VARCHAR(),
existing_nullable=False)
# ### end Alembic commands ###

View File

@@ -1,50 +0,0 @@
"""Added UserDeals
Revision ID: ccdc5764eb4f
Revises: 0eed68eddcdb
Create Date: 2025-09-25 22:39:17.246594
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'ccdc5764eb4f'
down_revision: Union[str, Sequence[str], None] = '0eed68eddcdb'
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_conditional_settings', sa.Column('timer_start', sa.Integer(), nullable=False))
op.add_column('user_conditional_settings', sa.Column('timer_end', sa.Integer(), nullable=False))
op.add_column('user_deals', sa.Column('trade_mode', sa.String(), nullable=True))
op.add_column('user_deals', sa.Column('order_type', sa.String(), nullable=True))
op.add_column('user_deals', sa.Column('leverage', sa.String(), nullable=True))
op.add_column('user_deals', sa.Column('leverage_to_buy', sa.String(), nullable=True))
op.add_column('user_deals', sa.Column('leverage_to_sell', sa.String(), nullable=True))
op.add_column('user_deals', sa.Column('closed_side', sa.String(), nullable=True))
op.add_column('user_deals', sa.Column('martingale_factor', sa.Float(), nullable=True))
op.add_column('user_deals', sa.Column('max_bets_in_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', 'max_bets_in_series')
op.drop_column('user_deals', 'martingale_factor')
op.drop_column('user_deals', 'closed_side')
op.drop_column('user_deals', 'leverage_to_sell')
op.drop_column('user_deals', 'leverage_to_buy')
op.drop_column('user_deals', 'leverage')
op.drop_column('user_deals', 'order_type')
op.drop_column('user_deals', 'trade_mode')
op.drop_column('user_conditional_settings', 'timer_end')
op.drop_column('user_conditional_settings', 'timer_start')
# ### end Alembic commands ###

View File

@@ -1,34 +0,0 @@
"""added limit and trigger price for user deals
Revision ID: d3c85bad8c98
Revises: 09db71875980
Create Date: 2025-09-29 16:50:36.818798
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'd3c85bad8c98'
down_revision: Union[str, Sequence[str], None] = '09db71875980'
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('limit_price', sa.Float(), nullable=True))
op.add_column('user_deals', sa.Column('trigger_price', sa.Float(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user_deals', 'trigger_price')
op.drop_column('user_deals', 'limit_price')
# ### end Alembic commands ###

View File

@@ -1,40 +0,0 @@
"""added last_side and auto_trading for user_deals
Revision ID: dbffe818030c
Revises: 3534adf891fc
Create Date: 2025-10-01 09:29:55.554101
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'dbffe818030c'
down_revision: Union[str, Sequence[str], None] = '3534adf891fc'
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.drop_column('user_conditional_settings', 'last_side')
op.drop_column('user_conditional_settings', 'auto_trading')
op.add_column('user_deals', sa.Column('last_side', sa.String(), nullable=True))
op.add_column('user_deals', sa.Column('auto_trading', sa.Boolean(), nullable=True))
op.drop_column('user_deals', 'side')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user_deals', sa.Column('side', sa.VARCHAR(), autoincrement=False, nullable=True))
op.drop_column('user_deals', 'auto_trading')
op.drop_column('user_deals', 'last_side')
op.add_column('user_conditional_settings', sa.Column('auto_trading', sa.BOOLEAN(), server_default=sa.text('false'), autoincrement=False, nullable=False))
op.add_column('user_conditional_settings', sa.Column('last_side', sa.VARCHAR(), autoincrement=False, nullable=False))
# ### end Alembic commands ###

View File

@@ -1,32 +0,0 @@
"""added fee user deals
Revision ID: ef342b38e17b
Revises: 73a00faa4f7f
Create Date: 2025-10-02 15:10:25.456983
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'ef342b38e17b'
down_revision: Union[str, Sequence[str], None] = '73a00faa4f7f'
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('fee', sa.Float(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user_deals', 'fee')
# ### end Alembic commands ###

View File

@@ -1,38 +0,0 @@
"""added last side the conditional data
Revision ID: ef38c90eed55
Revises: d3c85bad8c98
Create Date: 2025-09-30 08:33:23.415545
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'ef38c90eed55'
down_revision: Union[str, Sequence[str], None] = 'd3c85bad8c98'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
op.add_column('user_conditional_settings', sa.Column('last_side', sa.String(), nullable=True))
# Обновляем все существующие строки значением по умолчанию
op.execute(
"UPDATE user_conditional_settings SET last_side = 'default_value' WHERE last_side IS NULL"
)
# Устанавливаем ограничение NOT NULL
op.alter_column('user_conditional_settings', 'last_side', nullable=False)
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user_conditional_settings', 'last_side')
# ### end Alembic commands ###

View File

@@ -1,40 +0,0 @@
"""Updated Deals
Revision ID: f00a94ccdf01
Revises: c98b9dc36d15
Create Date: 2025-09-28 22:25:00.092196
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'f00a94ccdf01'
down_revision: Union[str, Sequence[str], None] = 'c98b9dc36d15'
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_deals', 'switch_side_mode',
existing_type=sa.VARCHAR(),
type_=sa.Boolean(),
existing_nullable=True)
op.create_unique_constraint(None, 'user_deals', ['user_id'])
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'user_deals', type_='unique')
op.alter_column('user_deals', 'switch_side_mode',
existing_type=sa.Boolean(),
type_=sa.VARCHAR(),
existing_nullable=True)
# ### end Alembic commands ###

View File

@@ -1,8 +1,8 @@
"""Updated leverage """Added side_mode column
Revision ID: fd8581c0cc87 Revision ID: fbf4e3658310
Revises: bb586fa9bcd2 Revises:
Create Date: 2025-09-22 15:13:21.487402 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 = 'fd8581c0cc87' 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

@@ -7,25 +7,25 @@ logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("close_positions") logger = logging.getLogger("close_positions")
async def close_position( async def close_position_by_symbol(
tg_id: int, symbol: str, side: str, position_idx: int, qty: float tg_id: int, symbol: str
) -> bool: ) -> bool:
""" """
Closes all positions Closes all positions
:param tg_id: Telegram user ID :param tg_id: Telegram user ID
:param symbol: symbol :param symbol: symbol
:param side: side
:param position_idx: position index
:param qty: quantity
:return: bool :return: bool
""" """
try: try:
client = await get_bybit_client(tg_id) client = await get_bybit_client(tg_id)
if side == "Buy": response = client.get_positions(
r_side = "Sell" category="linear", symbol=symbol
else: )
r_side = "Buy" positions = response.get("result", {}).get("list", [])
r_side = "Sell" if positions[0].get("side") == "Buy" else "Buy"
qty = positions[0].get("size")
position_idx = positions[0].get("positionIdx")
response = client.place_order( response = client.place_order(
category="linear", category="linear",
@@ -37,16 +37,16 @@ async def close_position(
positionIdx=position_idx, positionIdx=position_idx,
) )
if response["retCode"] == 0: if response["retCode"] == 0:
logger.info("All positions closed for %s for user %s", symbol, tg_id) logger.info("Positions closed for %s for user %s", symbol, tg_id)
return True return True
else: else:
logger.error( logger.error(
"Error closing all positions for %s for user %s", symbol, tg_id "Error closing position for %s for user %s", symbol, tg_id
) )
return False return False
except Exception as e: except Exception as e:
logger.error( logger.error(
"Error closing all positions for %s for user %s: %s", symbol, tg_id, e "Error closing positions for %s for user %s: %s", symbol, tg_id, e
) )
return False return False

View File

@@ -10,7 +10,8 @@ from app.bybit.get_functions.get_tickers import get_tickers
from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG
from app.bybit.set_functions.set_leverage import set_leverage from app.bybit.set_functions.set_leverage import set_leverage
from app.bybit.set_functions.set_margin_mode import set_margin_mode from app.bybit.set_functions.set_margin_mode import set_margin_mode
from app.helper_functions import get_liquidation_price, safe_float from app.bybit.set_functions.set_switch_position_mode import set_switch_position_mode
from app.helper_functions import safe_float
logging.config.dictConfig(LOGGING_CONFIG) logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("open_positions") logger = logging.getLogger("open_positions")
@@ -24,11 +25,9 @@ async def start_trading_cycle(
:param tg_id: Telegram user ID :param tg_id: Telegram user ID
""" """
try: try:
client = await get_bybit_client(tg_id=tg_id)
symbol = await rq.get_user_symbol(tg_id=tg_id) symbol = await rq.get_user_symbol(tg_id=tg_id)
additional_data = await rq.get_user_additional_settings(tg_id=tg_id) additional_data = await rq.get_user_additional_settings(tg_id=tg_id)
risk_management_data = await rq.get_user_risk_management(tg_id=tg_id) 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( user_deals_data = await rq.get_user_deal_by_symbol(
tg_id=tg_id, symbol=symbol tg_id=tg_id, symbol=symbol
) )
@@ -42,6 +41,7 @@ async def start_trading_cycle(
max_bets_in_series = additional_data.max_bets_in_series max_bets_in_series = additional_data.max_bets_in_series
take_profit_percent = risk_management_data.take_profit_percent take_profit_percent = risk_management_data.take_profit_percent
stop_loss_percent = risk_management_data.stop_loss_percent stop_loss_percent = risk_management_data.stop_loss_percent
total_commission = 0
get_side = "Buy" get_side = "Buy"
@@ -62,34 +62,10 @@ async def start_trading_cycle(
else: else:
side = "Sell" side = "Sell"
# Get fee rates await set_switch_position_mode(
fee_info = client.get_fee_rates(category="linear", symbol=symbol) tg_id=tg_id,
symbol=symbol,
# Check if commission fee is enabled mode=0)
commission_fee_percent = 0.0
if commission_fee == "Yes_commission_fee":
commission_fee_percent = safe_float(
fee_info["result"]["list"][0]["takerFeeRate"]
)
get_ticker = await get_tickers(tg_id, symbol=symbol)
price_symbol = safe_float(get_ticker.get("lastPrice")) or 0
instruments_info = await get_instruments_info(tg_id=tg_id, symbol=symbol)
qty_step_str = instruments_info.get("lotSizeFilter").get("qtyStep")
qty_step = safe_float(qty_step_str)
qty = safe_float(order_quantity) / safe_float(price_symbol)
decimals = abs(int(round(math.log10(qty_step))))
qty_formatted = math.floor(qty / qty_step) * qty_step
qty_formatted = round(qty_formatted, decimals)
if trigger_price > 0:
po_trigger_price = str(trigger_price)
else:
po_trigger_price = None
price_for_cals = trigger_price if po_trigger_price is not None else price_symbol
total_commission = price_for_cals * qty_formatted * commission_fee_percent
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,
@@ -114,9 +90,9 @@ async def start_trading_cycle(
await rq.set_user_deal( await rq.set_user_deal(
tg_id=tg_id, tg_id=tg_id,
symbol=symbol, symbol=symbol,
last_side=side,
current_step=1, current_step=1,
trade_mode=trade_mode, trade_mode=trade_mode,
side_mode=switch_side,
margin_type=margin_type, margin_type=margin_type,
leverage=leverage, leverage=leverage,
order_quantity=order_quantity, order_quantity=order_quantity,
@@ -152,9 +128,8 @@ async def start_trading_cycle(
return None return None
async def trading_cycle( async def trading_cycle_profit(
tg_id: int, symbol: str, reverse_side: str tg_id: int, symbol: str, side: str) -> str | None:
) -> str | None:
try: try:
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)
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)
@@ -167,8 +142,7 @@ async def trading_cycle(
stop_loss_percent = user_deals_data.stop_loss_percent stop_loss_percent = user_deals_data.stop_loss_percent
max_bets_in_series = user_deals_data.max_bets_in_series max_bets_in_series = user_deals_data.max_bets_in_series
martingale_factor = user_deals_data.martingale_factor martingale_factor = user_deals_data.martingale_factor
current_step = user_deals_data.current_step side_mode = user_deals_data.side_mode
order_quantity = user_deals_data.order_quantity
base_quantity = user_deals_data.base_quantity base_quantity = user_deals_data.base_quantity
await set_margin_mode(tg_id=tg_id, margin_mode=margin_type) await set_margin_mode(tg_id=tg_id, margin_mode=margin_type)
@@ -178,15 +152,84 @@ async def trading_cycle(
leverage=leverage, leverage=leverage,
) )
if reverse_side == "Buy":
real_side = "Sell"
else:
real_side = "Buy"
side = real_side
if trade_mode == "Switch": if trade_mode == "Switch":
side = "Sell" if real_side == "Buy" else "Buy" if side_mode == "Противоположно":
s_side = "Sell" if side == "Buy" else "Buy"
else:
s_side = side
else:
s_side = side
res = await open_positions(
tg_id=tg_id,
symbol=symbol,
side=s_side,
order_quantity=base_quantity,
trigger_price=trigger_price,
margin_type=margin_type,
leverage=leverage,
take_profit_percent=take_profit_percent,
stop_loss_percent=stop_loss_percent,
commission_fee_percent=total_fee
)
if res == "OK":
await rq.set_user_deal(
tg_id=tg_id,
symbol=symbol,
current_step=1,
trade_mode=trade_mode,
side_mode=side_mode,
margin_type=margin_type,
leverage=leverage,
order_quantity=base_quantity,
trigger_price=trigger_price,
martingale_factor=martingale_factor,
max_bets_in_series=max_bets_in_series,
take_profit_percent=take_profit_percent,
stop_loss_percent=stop_loss_percent,
base_quantity=base_quantity
)
return "OK"
return (
res
if res
in {
"Risk is too high for this trade",
"ab not enough for new order",
"InvalidRequestError",
"The number of contracts exceeds maximum limit allowed",
}
else None
)
except Exception as e:
logger.error("Error in trading_cycle_profit: %s", e)
return None
async def trading_cycle(
tg_id: int, symbol: str, 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_risk_management_data = await rq.get_user_risk_management(tg_id=tg_id)
commission_fee = user_risk_management_data.commission_fee
total_fee = user_auto_trading_data.total_fee
trade_mode = user_deals_data.trade_mode
margin_type = user_deals_data.margin_type
leverage = user_deals_data.leverage
trigger_price = 0
take_profit_percent = user_deals_data.take_profit_percent
stop_loss_percent = user_deals_data.stop_loss_percent
max_bets_in_series = user_deals_data.max_bets_in_series
martingale_factor = user_deals_data.martingale_factor
current_step = user_deals_data.current_step
order_quantity = user_deals_data.order_quantity
base_quantity = user_deals_data.base_quantity
side_mode = user_deals_data.side_mode
next_quantity = safe_float(order_quantity) * ( next_quantity = safe_float(order_quantity) * (
safe_float(martingale_factor) safe_float(martingale_factor)
@@ -196,10 +239,29 @@ async def trading_cycle(
if max_bets_in_series < current_step: if max_bets_in_series < current_step:
return "Max bets in series" return "Max bets in series"
await set_margin_mode(tg_id=tg_id, margin_mode=margin_type)
await set_leverage(
tg_id=tg_id,
symbol=symbol,
leverage=leverage,
)
if commission_fee == "Yes_commission_fee":
total_fee = total_fee
else:
total_fee = 0
if trade_mode == "Switch":
if side == "Buy":
r_side = "Sell"
else:
r_side = "Buy"
else:
r_side = side
res = await open_positions( res = await open_positions(
tg_id=tg_id, tg_id=tg_id,
symbol=symbol, symbol=symbol,
side=side, side=r_side,
order_quantity=next_quantity, order_quantity=next_quantity,
trigger_price=trigger_price, trigger_price=trigger_price,
margin_type=margin_type, margin_type=margin_type,
@@ -213,9 +275,9 @@ async def trading_cycle(
await rq.set_user_deal( await rq.set_user_deal(
tg_id=tg_id, tg_id=tg_id,
symbol=symbol, symbol=symbol,
last_side=side,
current_step=current_step, current_step=current_step,
trade_mode=trade_mode, trade_mode=trade_mode,
side_mode=side_mode,
margin_type=margin_type, margin_type=margin_type,
leverage=leverage, leverage=leverage,
order_quantity=next_quantity, order_quantity=next_quantity,
@@ -264,7 +326,7 @@ async def open_positions(
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)
qty = safe_float(order_quantity) / safe_float(price_symbol) qty = (safe_float(order_quantity) * safe_float(leverage)) / safe_float(price_symbol)
decimals = abs(int(round(math.log10(qty_step)))) decimals = abs(int(round(math.log10(qty_step))))
qty_formatted = math.floor(qty / qty_step) * qty_step qty_formatted = math.floor(qty / qty_step) * qty_step
qty_formatted = round(qty_formatted, decimals) qty_formatted = round(qty_formatted, decimals)
@@ -276,51 +338,33 @@ async def open_positions(
po_trigger_price = None po_trigger_price = None
trigger_direction = None trigger_direction = None
get_leverage = safe_float(leverage)
price_for_cals = trigger_price if po_trigger_price is not None else price_symbol price_for_cals = trigger_price if po_trigger_price is not None else price_symbol
tp_multiplier = 1 + (take_profit_percent / 100) if qty_formatted <= 0:
if commission_fee_percent > 0: return "Order does not meet minimum order value"
tp_multiplier += commission_fee_percent
if margin_type == "ISOLATED_MARGIN": if margin_type == "ISOLATED_MARGIN":
liq_long, liq_short = await get_liquidation_price(
tg_id=tg_id,
entry_price=price_for_cals,
symbol=symbol,
leverage=get_leverage,
)
if (liq_long > 0 or liq_short > 0) and price_for_cals > 0:
if side == "Buy": if side == "Buy":
base_tp = price_for_cals + (price_for_cals - liq_long) take_profit_price = price_for_cals * (
take_profit_price = base_tp + commission_fee_percent 1 + take_profit_percent / 100) + commission_fee_percent / qty_formatted
stop_loss_price = None
else: else:
base_tp = price_for_cals - (liq_short - price_for_cals) take_profit_price = price_for_cals * (
take_profit_price = base_tp - commission_fee_percent 1 - take_profit_percent / 100) - commission_fee_percent / qty_formatted
take_profit_price = max(take_profit_price, 0)
else:
take_profit_price = None
stop_loss_price = None stop_loss_price = None
else: else:
if side == "Buy": if side == "Buy":
take_profit_price = price_for_cals * tp_multiplier take_profit_price = price_for_cals * (
1 + take_profit_percent / 100) + commission_fee_percent / qty_formatted
stop_loss_price = price_for_cals * (1 - stop_loss_percent / 100) stop_loss_price = price_for_cals * (1 - stop_loss_percent / 100)
else: else:
take_profit_price = price_for_cals * ( take_profit_price = price_for_cals * (
1 - (take_profit_percent / 100) - commission_fee_percent 1 - take_profit_percent / 100) - commission_fee_percent / qty_formatted
) stop_loss_price = price_for_cals * (1 + stop_loss_percent / 100)
stop_loss_price = trigger_price * (1 + stop_loss_percent / 100)
take_profit_price = max(take_profit_price, 0) take_profit_price = max(take_profit_price, 0)
stop_loss_price = max(stop_loss_price, 0) stop_loss_price = max(stop_loss_price, 0)
logger.info("Take profit price: %s", take_profit_price)
logger.info("Stop loss price: %s", stop_loss_price)
logger.info("Commission fee percent: %s", commission_fee_percent)
# Place order # Place order
order_params = { order_params = {
"category": "linear", "category": "linear",
@@ -366,5 +410,5 @@ async def open_positions(
return "InvalidRequestError" return "InvalidRequestError"
except Exception as e: except Exception as e:
logger.error("Error opening position for user %s: %s", tg_id, e) logger.error("Error opening position for user %s: %s", tg_id, e, exc_info=True)
return None return None

View File

@@ -21,6 +21,10 @@ async def user_profile_bybit(tg_id: int, message: Message, state: FSMContext) ->
if wallet: if wallet:
balance = wallet.get("totalWalletBalance", "0") balance = wallet.get("totalWalletBalance", "0")
symbol = await rq.get_user_symbol(tg_id=tg_id) symbol = await rq.get_user_symbol(tg_id=tg_id)
if symbol is None:
await rq.set_user_symbol(tg_id=tg_id, symbol="BTCUSDT")
await user_profile_bybit(tg_id=tg_id, message=message, state=state)
else:
await message.answer( await message.answer(
text=f"💎Ваш профиль:\n\n" text=f"💎Ваш профиль:\n\n"
f"⚖️ Баланс: {float(balance):,.2f} USD\n" f"⚖️ Баланс: {float(balance):,.2f} USD\n"

View File

@@ -3,7 +3,7 @@ import logging.config
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.logger_bybit.logger_bybit import LOGGING_CONFIG from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG
from app.bybit.open_positions import trading_cycle from app.bybit.open_positions import trading_cycle, trading_cycle_profit
from app.helper_functions import format_value, safe_float from app.helper_functions import format_value, safe_float
logging.config.dictConfig(LOGGING_CONFIG) logging.config.dictConfig(LOGGING_CONFIG)
@@ -41,11 +41,26 @@ class TelegramMessageHandler:
if order_status == "Filled" or order_status not in status_map: if order_status == "Filled" or order_status not in status_map:
return None return None
user_auto_trading = await rq.get_user_auto_trading(
tg_id=tg_id, symbol=symbol
)
auto_trading = (
user_auto_trading.auto_trading if user_auto_trading else False
)
user_deals_data = await rq.get_user_deal_by_symbol(
tg_id=tg_id, symbol=symbol
)
text = ( text = (
f"Торговая пара: {symbol}\n" f"Торговая пара: {symbol}\n"
f"Количество: {qty}\n"
f"Движение: {side_rus}\n" f"Движение: {side_rus}\n"
) )
if user_deals_data is not None and auto_trading:
text += f"Текущая ставка: {user_deals_data.order_quantity} USDT\n"
else:
text += f"Количество: {qty}\n"
if price and price != "0": if price and price != "0":
text += f"Цена: {price}\n" text += f"Цена: {price}\n"
if take_profit and take_profit != "Нет данных": if take_profit and take_profit != "Нет данных":
@@ -67,13 +82,21 @@ class TelegramMessageHandler:
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"))
exec_fee = format_value(execution.get("execFee")) exec_qty = format_value(execution.get("execQty"))
exec_fees = format_value(execution.get("execFee"))
fee_rate = format_value(execution.get("feeRate"))
side = format_value(execution.get("side")) side = format_value(execution.get("side"))
side_rus = ( side_rus = (
"Покупка" "Покупка"
if side == "Buy" if side == "Buy"
else "Продажа" if side == "Sell" else "Нет данных" else "Продажа" if side == "Sell" else "Нет данных"
) )
if safe_float(exec_fees) == 0:
exec_fee = safe_float(exec_price) * safe_float(exec_qty) * safe_float(
fee_rate
)
else:
exec_fee = safe_float(exec_fees)
if safe_float(closed_size) == 0: if safe_float(closed_size) == 0:
await rq.set_fee_user_auto_trading( await rq.set_fee_user_auto_trading(
@@ -86,9 +109,7 @@ class TelegramMessageHandler:
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)
await rq.set_total_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, total_fee=total_fee
)
if user_auto_trading is not None and user_auto_trading.fee is not None: if user_auto_trading is not None and user_auto_trading.fee is not None:
fee = user_auto_trading.fee fee = user_auto_trading.fee
@@ -96,42 +117,38 @@ class TelegramMessageHandler:
fee = 0 fee = 0
exec_pnl = format_value(execution.get("execPnl")) exec_pnl = format_value(execution.get("execPnl"))
risk_management_data = await rq.get_user_risk_management(tg_id=tg_id)
commission_fee = risk_management_data.commission_fee
if commission_fee == "Yes_commission_fee":
total_pnl = safe_float(exec_pnl) - safe_float(exec_fee) - fee total_pnl = safe_float(exec_pnl) - safe_float(exec_fee) - fee
else:
total_pnl = safe_float(exec_pnl)
header = ( header = (
"Сделка закрыта:" if safe_float(closed_size) > 0 else "Сделка открыта:" "Сделка закрыта:" if safe_float(closed_size) > 0 else "Сделка открыта:"
) )
text = f"{header}\n" f"Торговая пара: {symbol}\n" text = f"{header}\n" f"Торговая пара: {symbol}\n"
auto_trading = (
user_auto_trading.auto_trading if user_auto_trading else False
)
user_deals_data = await rq.get_user_deal_by_symbol( user_deals_data = await rq.get_user_deal_by_symbol(
tg_id=tg_id, symbol=symbol tg_id=tg_id, symbol=symbol
) )
exec_bet = user_deals_data.order_quantity if user_deals_data is not None and auto_trading:
base_quantity = user_deals_data.base_quantity await rq.set_total_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, total_fee=total_fee
)
text += f"Текущая ставка: {user_deals_data.order_quantity} USDT\n"
text += ( text += (
f"Цена исполнения: {exec_price}\n" f"Цена исполнения: {exec_price}\n"
f"Текущая ставка: {exec_bet}\n" f"Комиссия: {exec_fee:.8f}\n"
f"Движение: {side_rus}\n"
f"Комиссия за сделку: {exec_fee}\n"
) )
if safe_float(closed_size) > 0: if safe_float(closed_size) == 0:
text += f"Движение: {side_rus}\n"
else:
text += f"\nРеализованная прибыль: {total_pnl:.7f}\n" text += f"\nРеализованная прибыль: {total_pnl:.7f}\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
) )
auto_trading = (
user_auto_trading.auto_trading if user_auto_trading else False
)
user_symbols = user_auto_trading.symbol if user_auto_trading else None user_symbols = user_auto_trading.symbol if user_auto_trading else None
if ( if (
@@ -140,10 +157,42 @@ class TelegramMessageHandler:
and user_symbols is not None and user_symbols is not None
): ):
if safe_float(total_pnl) > 0: if safe_float(total_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
) )
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
)
res = await trading_cycle_profit(
tg_id=tg_id, symbol=symbol, side=r_side
)
if res == "OK":
pass
else:
errors = {
"Max bets in series": "❗️ Максимальное количество сделок в серии достигнуто",
"Risk is too high for this trade": "❗️ Риск сделки слишком высок для продолжения",
"ab not enough for new order": "❗️ Недостаточно средств для продолжения торговли",
"InvalidRequestError": "❗️ Недостаточно средств для размещения нового ордера с заданным количеством и плечом.",
"The number of contracts exceeds maximum limit allowed": "❗️ Превышен максимальный лимит ставки",
}
error_text = errors.get(
res, "❗️ Не удалось открыть новую сделку"
)
await rq.set_auto_trading( await rq.set_auto_trading(
tg_id=tg_id, symbol=symbol, auto_trading=False tg_id=tg_id, symbol=symbol, auto_trading=False
) )
@@ -154,16 +203,24 @@ class TelegramMessageHandler:
await rq.set_fee_user_auto_trading( await rq.set_fee_user_auto_trading(
tg_id=tg_id, symbol=symbol, fee=0 tg_id=tg_id, symbol=symbol, fee=0
) )
await rq.set_order_quantity( await self.telegram_bot.send_message(
tg_id=message.from_user.id, order_quantity=base_quantity chat_id=tg_id,
text=error_text,
reply_markup=kbi.profile_bybit,
) )
else: else:
open_order_text = "\n❗️ Сделка закрылась в минус, открываю новую сделку с увеличенной ставкой.\n" open_order_text = "\n❗️ Сделка закрылась в минус, открываю новую сделку с увеличенной ставкой.\n"
await self.telegram_bot.send_message( await self.telegram_bot.send_message(
chat_id=tg_id, text=open_order_text chat_id=tg_id, text=open_order_text
) )
if side == "Buy":
r_side = "Sell"
else:
r_side = "Buy"
res = await trading_cycle( res = await trading_cycle(
tg_id=tg_id, symbol=symbol, reverse_side=side tg_id=tg_id, symbol=symbol, side=r_side
) )
if res == "OK": if res == "OK":
@@ -174,7 +231,7 @@ class TelegramMessageHandler:
"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": "❗️ Превышен максимальный лимит ставки",
} }
error_text = errors.get( error_text = errors.get(
res, "❗️ Не удалось открыть новую сделку" res, "❗️ Не удалось открыть новую сделку"

View File

@@ -46,7 +46,9 @@ class WebSocketBot:
self.user_sockets.clear() self.user_sockets.clear()
self.user_messages.clear() self.user_messages.clear()
self.user_keys.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) success = await self.try_connect_user(api_key, api_secret, tg_id)
if success: if success:

View File

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

View File

@@ -124,6 +124,8 @@ async def set_symbol(message: Message, state: FSMContext) -> None:
risk_percent = 100 / safe_float(max_leverage) risk_percent = 100 / safe_float(max_leverage)
await rq.set_stop_loss_percent( await rq.set_stop_loss_percent(
tg_id=message.from_user.id, stop_loss_percent=risk_percent) tg_id=message.from_user.id, stop_loss_percent=risk_percent)
await rq.set_take_profit_percent(
tg_id=message.from_user.id, take_profit_percent=risk_percent)
await rq.set_trigger_price(tg_id=message.from_user.id, trigger_price=0) 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) await rq.set_order_quantity(tg_id=message.from_user.id, order_quantity=1.0)

View File

@@ -4,9 +4,6 @@ from aiogram import Router
from aiogram.fsm.context import FSMContext from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery from aiogram.types import CallbackQuery
import database.request as rq
from app.bybit.close_positions import cancel_order, close_position
from app.helper_functions import safe_float
from logger_helper.logger_helper import LOGGING_CONFIG from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG) logging.config.dictConfig(LOGGING_CONFIG)
@@ -28,31 +25,6 @@ async def close_position_handler(
:return: None :return: None
""" """
try: try:
data = callback_query.data
parts = data.split("_")
symbol = parts[2]
side = parts[3]
position_idx = int(parts[4])
qty = safe_float(parts[5])
await rq.set_auto_trading(
tg_id=callback_query.from_user.id,
symbol=symbol,
auto_trading=False,
side=side,
)
res = await close_position(
tg_id=callback_query.from_user.id,
symbol=symbol,
side=side,
position_idx=position_idx,
qty=qty,
)
if not res:
await callback_query.answer(text="Произошла ошибка при закрытии позиции.")
return
await callback_query.answer(text="Позиция успешно закрыта.")
logger.debug( logger.debug(
"Command close_position processed successfully for user: %s", "Command close_position processed successfully for user: %s",
callback_query.from_user.id, callback_query.from_user.id,
@@ -81,19 +53,6 @@ async def cancel_order_handler(
:return: None :return: None
""" """
try: try:
data = callback_query.data
parts = data.split("_")
symbol = parts[2]
order_id = parts[3]
res = await cancel_order(
tg_id=callback_query.from_user.id, symbol=symbol, order_id=order_id
)
if not res:
await callback_query.answer(text="Произошла ошибка при закрытии ордера.")
return
await callback_query.answer(text="Ордер успешно закрыт.")
logger.debug( logger.debug(
"Command close_order processed successfully for user: %s", "Command close_order processed successfully for user: %s",
callback_query.from_user.id, callback_query.from_user.id,

View File

@@ -7,6 +7,7 @@ from aiogram.types import CallbackQuery, Message
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
from app.bybit.get_functions.get_positions import get_active_positions_by_symbol, get_active_orders_by_symbol
from app.bybit.set_functions.set_leverage import set_leverage from app.bybit.set_functions.set_leverage import set_leverage
from app.bybit.set_functions.set_margin_mode import set_margin_mode from app.bybit.set_functions.set_margin_mode import set_margin_mode
from app.helper_functions import is_int, is_number, safe_float from app.helper_functions import is_int, is_number, safe_float
@@ -42,7 +43,7 @@ async def settings_for_trade_mode(
text="Выберите режим торговли:\n\n" text="Выберите режим торговли:\n\n"
"Лонг - все сделки серии открываются на покупку.\n" "Лонг - все сделки серии открываются на покупку.\n"
"Шорт - все сделки серии открываются на продажу.\n" "Шорт - все сделки серии открываются на продажу.\n"
"Свитч - направление каждой сделки серии меняется по переменно.\n", "Свитч - направление каждой сделки в рамках серии меняется попеременно.\n",
reply_markup=kbi.trade_mode, reply_markup=kbi.trade_mode,
) )
logger.debug( logger.debug(
@@ -211,6 +212,31 @@ async def settings_for_margin_type(
""" """
try: try:
await state.clear() await state.clear()
symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id)
deals = await get_active_positions_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol
)
position = next((d for d in deals if d.get("symbol") == symbol), None)
if position:
size = position.get("size", 0)
else:
size = 0
if safe_float(size) > 0:
await callback_query.answer(
text="У вас есть активная позиция по текущей паре",
)
return
orders = await get_active_orders_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol)
if orders is not None:
await callback_query.answer(
text="У вас есть активный ордер по текущей паре",
)
return
await callback_query.message.edit_text( await callback_query.message.edit_text(
text="Выберите тип маржи:\n\n" text="Выберите тип маржи:\n\n"
"Примечание: Если у вас есть открытые позиции, то маржа примениться ко всем позициям", "Примечание: Если у вас есть открытые позиции, то маржа примениться ко всем позициям",
@@ -554,6 +580,8 @@ async def set_leverage_handler(message: Message, state: FSMContext) -> None:
risk_percent = 100 / safe_float(leverage_float) risk_percent = 100 / safe_float(leverage_float)
await rq.set_stop_loss_percent( await rq.set_stop_loss_percent(
tg_id=message.from_user.id, stop_loss_percent=risk_percent) tg_id=message.from_user.id, stop_loss_percent=risk_percent)
await rq.set_take_profit_percent(
tg_id=message.from_user.id, take_profit_percent=risk_percent)
logger.info( logger.info(
"User %s set leverage: %s", message.from_user.id, leverage_float "User %s set leverage: %s", message.from_user.id, leverage_float
) )

View File

@@ -6,7 +6,6 @@ from aiogram.types import CallbackQuery
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 import get_bybit_client
from app.helper_functions import calculate_total_budget, safe_float from app.helper_functions import calculate_total_budget, safe_float
from logger_helper.logger_helper import LOGGING_CONFIG from logger_helper.logger_helper import LOGGING_CONFIG
@@ -26,7 +25,6 @@ async def additional_settings(callback_query: CallbackQuery, state: FSMContext)
try: try:
await state.clear() await state.clear()
tg_id = callback_query.from_user.id 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) additional_data = await rq.get_user_additional_settings(tg_id=tg_id)
if not additional_data: if not additional_data:
@@ -64,30 +62,15 @@ async def additional_settings(callback_query: CallbackQuery, state: FSMContext)
max_bets = additional_data.max_bets_in_series max_bets = additional_data.max_bets_in_series
quantity = f(additional_data.order_quantity) quantity = f(additional_data.order_quantity)
trigger_price = f(additional_data.trigger_price) or 0 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 = "" switch_side_mode = ""
if trade_mode == "Switch": if trade_mode == "Switch":
switch_side_mode = f"- Направление первой сделки: {switch_side}\n" 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( total_budget = await calculate_total_budget(
quantity=quantity, quantity=quantity,
martingale_factor=martingale, martingale_factor=martingale,
max_steps=max_bets, max_steps=max_bets,
commission_fee_percent=total_commission,
) )
text = ( text = (
f"Основные настройки:\n\n" f"Основные настройки:\n\n"
@@ -99,7 +82,7 @@ async def additional_settings(callback_query: CallbackQuery, state: FSMContext)
f"- Коэффициент мартингейла: {martingale:.2f}\n" f"- Коэффициент мартингейла: {martingale:.2f}\n"
f"- Триггер цена: {trigger_price:.4f} USDT\n" f"- Триггер цена: {trigger_price:.4f} USDT\n"
f"- Максимальное кол-во ставок в серии: {max_bets}\n\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) keyboard = kbi.get_additional_settings_keyboard(mode=trade_mode)
@@ -140,8 +123,8 @@ async def risk_management(callback_query: CallbackQuery, state: FSMContext) -> N
await callback_query.message.edit_text( await callback_query.message.edit_text(
text=f"Риск-менеджмент:\n\n" text=f"Риск-менеджмент:\n\n"
f"- Процент изменения цены для фиксации прибыли: {take_profit_percent}%\n" f"- Процент изменения цены для фиксации прибыли: {take_profit_percent:.2f}%\n"
f"- Процент изменения цены для фиксации убытка: {stop_loss_percent}%\n\n" f"- Процент изменения цены для фиксации убытка: {stop_loss_percent:.2f}%\n\n"
f"- Комиссия биржи для расчета прибыли: {commission_fee_rus}\n\n", f"- Комиссия биржи для расчета прибыли: {commission_fee_rus}\n\n",
reply_markup=kbi.risk_management, reply_markup=kbi.risk_management,
) )
@@ -179,11 +162,9 @@ async def conditions(callback_query: CallbackQuery, state: FSMContext) -> None:
) )
if conditional_settings_data: if conditional_settings_data:
start_timer = conditional_settings_data.timer_start or 0 start_timer = conditional_settings_data.timer_start or 0
stop_timer = conditional_settings_data.timer_end or 0
await callback_query.message.edit_text( await callback_query.message.edit_text(
text="Условия торговли:\n\n" text="Условия торговли:\n\n"
f"- Таймер для старта: {start_timer} мин.\n" f"- Таймер для старта: {start_timer} мин.\n",
f"- Таймер для остановки: {stop_timer} мин.\n",
reply_markup=kbi.conditions, reply_markup=kbi.conditions,
) )
logger.debug( logger.debug(

View File

@@ -7,7 +7,7 @@ from aiogram.types import CallbackQuery
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_positions import get_active_positions_by_symbol from app.bybit.get_functions.get_positions import get_active_positions_by_symbol, get_active_orders_by_symbol
from app.bybit.open_positions import start_trading_cycle from app.bybit.open_positions import start_trading_cycle
from app.helper_functions import safe_float from app.helper_functions import safe_float
from app.telegram.tasks.tasks import ( from app.telegram.tasks.tasks import (
@@ -33,6 +33,7 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non
""" """
try: try:
await state.clear() await state.clear()
tg_id = callback_query.from_user.id
symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id) symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id)
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
@@ -46,7 +47,16 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non
if safe_float(size) > 0: if safe_float(size) > 0:
await callback_query.answer( await callback_query.answer(
text="У вас есть активная позиция", text="У вас есть активная позиция по текущей паре",
)
return
orders = await get_active_orders_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol)
if orders is not None:
await callback_query.answer(
text="У вас есть активный ордер по текущей паре",
) )
return return
@@ -73,22 +83,29 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non
symbol=symbol, symbol=symbol,
auto_trading=True, auto_trading=True,
) )
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 start_trading_cycle( res = await start_trading_cycle(
tg_id=callback_query.from_user.id, tg_id=callback_query.from_user.id,
) )
error_messages = { error_messages = {
"Limit price is out min price": "Цена лимитного ордера меньше минимального", "Limit price is out min price": "Цена лимитного ордера меньше допустимого",
"Limit price is out max price": "Цена лимитного ордера больше максимального", "Limit price is out max price": "Цена лимитного ордера больше допустимого",
"Risk is too high for this trade": "Риск сделки превышает допустимый убыток", "Risk is too high for this trade": "Риск сделки превышает допустимый убыток",
"estimated will trigger liq": "Лимитный ордер может вызвать мгновенную ликвидацию. Проверьте параметры ордера.", "estimated will trigger liq": "Лимитный ордер может вызвать мгновенную ликвидацию. Проверьте параметры ордера.",
"ab not enough for new order": "Недостаточно средств для создания нового ордера", "ab not enough for new order": "Недостаточно средств для создания нового ордера",
"InvalidRequestError": "Произошла ошибка при запуске торговли.", "InvalidRequestError": "Произошла ошибка при запуске торговли.",
"Order does not meet minimum order value": "Сумма ордера не достаточна для запуска торговли", "Order does not meet minimum order value": "Сумма ставки меньше допустимого для запуска торговли. "
"position idx not match position mode": "Ошибка режима позиции для данного инструмента", "Увеличьте ставку, чтобы запустить торговлю",
"Qty invalid": "Некорректное значение ордера для данного инструмента", "position idx not match position mode": "Измените режим позиции, чтобы запустить торговлю",
"The number of contracts exceeds maximum limit allowed": "️️Количество контрактов превышает допустимое максимальное количество контрактов", "Qty invalid": "Некорректное значение ставки для данного инструмента",
"The number of contracts exceeds minimum limit allowed": "Количество контрактов превышает допустимое минимальное количество контрактов", "The number of contracts exceeds maximum limit allowed": "Превышен максимальный лимит ставки",
"The number of contracts exceeds minimum limit allowed": "️️Лимит ставки меньше минимально допустимого",
} }
if res == "OK": if res == "OK":
@@ -112,7 +129,7 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non
except Exception as e: except Exception as e:
await callback_query.answer(text="Произошла ошибка при запуске торговли") await callback_query.answer(text="Произошла ошибка при запуске торговли")
logger.error( logger.error(
"Error processing command long for user %s: %s", "Error processing command start_trading for user %s: %s",
callback_query.from_user.id, callback_query.from_user.id,
e, e,
) )

View File

@@ -5,6 +5,7 @@ from aiogram import F, Router
from aiogram.fsm.context import FSMContext from aiogram.fsm.context import FSMContext
from aiogram.types import CallbackQuery from aiogram.types import CallbackQuery
from app.bybit.close_positions import close_position_by_symbol
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.telegram.tasks.tasks import add_stop_task, cancel_stop_task from app.telegram.tasks.tasks import add_stop_task, cancel_stop_task
@@ -27,6 +28,7 @@ async def stop_all_trading(callback_query: CallbackQuery, state: FSMContext):
tg_id=callback_query.from_user.id tg_id=callback_query.from_user.id
) )
timer_end = conditional_data.timer_end timer_end = conditional_data.timer_end
symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id)
async def delay_start(): async def delay_start():
if timer_end > 0: if timer_end > 0:
@@ -37,30 +39,21 @@ async def stop_all_trading(callback_query: CallbackQuery, state: FSMContext):
await rq.set_stop_timer(tg_id=callback_query.from_user.id, timer_end=0) await rq.set_stop_timer(tg_id=callback_query.from_user.id, timer_end=0)
await asyncio.sleep(timer_end * 60) await asyncio.sleep(timer_end * 60)
user_auto_trading_list = await rq.get_all_user_auto_trading( user_auto_trading = await rq.get_user_auto_trading(
tg_id=callback_query.from_user.id tg_id=callback_query.from_user.id, symbol=symbol
) )
if any(item.auto_trading for item in user_auto_trading_list): if user_auto_trading and user_auto_trading.auto_trading:
for active_auto_trading in user_auto_trading_list: await rq.set_auto_trading(
if active_auto_trading.auto_trading:
symbol = active_auto_trading.symbol
req = 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,
) )
if not req: await close_position_by_symbol(
await callback_query.message.edit_text( tg_id=callback_query.from_user.id, symbol=symbol)
text="Произошла ошибка при остановке торговли", await callback_query.message.edit_text(text=f"Торговля для {symbol} остановлена", reply_markup=kbi.profile_bybit)
reply_markup=kbi.profile_bybit,
)
return
await callback_query.message.edit_text(
text="Торговля остановлена", reply_markup=kbi.profile_bybit
)
else: else:
await callback_query.message.edit_text(text="Нет активной торговли") await callback_query.message.edit_text(text=f"Нет активной торговли для {symbol}", reply_markup=kbi.profile_bybit)
task = asyncio.create_task(delay_start()) task = asyncio.create_task(delay_start())
await add_stop_task(user_id=callback_query.from_user.id, task=task) await add_stop_task(user_id=callback_query.from_user.id, task=task)

View File

@@ -36,6 +36,7 @@ main_menu = InlineKeyboardMarkup(
) )
], ],
[InlineKeyboardButton(text="Начать торговлю", callback_data="start_trading")], [InlineKeyboardButton(text="Начать торговлю", callback_data="start_trading")],
[InlineKeyboardButton(text="Остановить торговлю", callback_data="stop_trading")],
] ]
) )
@@ -227,9 +228,6 @@ conditions = InlineKeyboardMarkup(
inline_keyboard=[ inline_keyboard=[
[ [
InlineKeyboardButton(text="Таймер для старта", callback_data="start_timer"), InlineKeyboardButton(text="Таймер для старта", callback_data="start_timer"),
InlineKeyboardButton(
text="Таймер для остановки", callback_data="stop_timer"
),
], ],
[ [
InlineKeyboardButton(text="Назад", callback_data="main_settings"), InlineKeyboardButton(text="Назад", callback_data="main_settings"),

View File

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

View File

@@ -1,31 +1,8 @@
import os import os
from dotenv import load_dotenv, find_dotenv from dotenv import load_dotenv, find_dotenv
import logging.config
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("config")
env_path = find_dotenv() env_path = find_dotenv()
if env_path: if env_path:
load_dotenv(env_path) load_dotenv(env_path)
logging.info(f"Loaded env from {env_path}")
else:
logging.warning(".env file not found, environment variables won't be loaded")
BOT_TOKEN = os.getenv('BOT_TOKEN') BOT_TOKEN = os.getenv("BOT_TOKEN")
if not BOT_TOKEN:
logging.error("BOT_TOKEN is not set in environment variables")
DB_USER = os.getenv('DB_USER')
DB_PASS = os.getenv('DB_PASS')
DB_HOST = os.getenv('DB_HOST')
DB_PORT = os.getenv('DB_PORT')
DB_NAME = os.getenv('DB_NAME')
if not all([DB_USER, DB_PASS, DB_HOST, DB_PORT, DB_NAME]):
logger.error("One or more database environment variables are not set")
DATABASE_URL = f"postgresql+asyncpg://{DB_USER}:{DB_PASS}@{DB_HOST}:{DB_PORT}/{DB_NAME}"

View File

@@ -1,19 +1,38 @@
from database.models import Base, User, UserAdditionalSettings, UserApi, UserConditionalSettings, UserDeals, \
UserRiskManagement, UserSymbol
import logging.config import logging.config
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine, AsyncSession from sqlalchemy import event
from pathlib import Path
from database.models import Base
from config import DATABASE_URL
from logger_helper.logger_helper import LOGGING_CONFIG from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG) logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("database") logger = logging.getLogger("database")
async_engine = create_async_engine(DATABASE_URL, echo=False) BASE_DIR = Path(__file__).parent.resolve()
DATA_DIR = BASE_DIR / "db"
DATA_DIR.mkdir(parents=True, exist_ok=True)
DATABASE_URL = f"sqlite+aiosqlite:///{DATA_DIR / 'stcs.db'}"
async_engine = create_async_engine(
DATABASE_URL,
echo=False,
connect_args={"check_same_thread": False}
)
@event.listens_for(async_engine.sync_engine, "connect")
def _enable_foreign_keys(dbapi_connection, connection_record):
cursor = dbapi_connection.cursor()
cursor.execute("PRAGMA foreign_keys=ON")
cursor.close()
async_session = async_sessionmaker( async_session = async_sessionmaker(
async_engine, class_=AsyncSession, expire_on_commit=False async_engine,
class_=AsyncSession,
expire_on_commit=False
) )
@@ -23,4 +42,4 @@ async def init_db():
await conn.run_sync(Base.metadata.create_all) 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, exc_info=True) logger.error("Database initialization failed: %s", e)

View File

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

View File

@@ -895,9 +895,9 @@ async def set_stop_timer(tg_id: int, timer_end: int) -> bool:
async def set_user_deal( async def set_user_deal(
tg_id: int, tg_id: int,
symbol: str, symbol: str,
last_side: str,
current_step: int, current_step: int,
trade_mode: str, trade_mode: str,
side_mode: str,
margin_type: str, margin_type: str,
leverage: str, leverage: str,
order_quantity: float, order_quantity: float,
@@ -906,15 +906,15 @@ async def set_user_deal(
max_bets_in_series: int, max_bets_in_series: int,
take_profit_percent: int, take_profit_percent: int,
stop_loss_percent: int, stop_loss_percent: int,
base_quantity: float, base_quantity: float
): ):
""" """
Set the user deal in the database. Set the user deal in the database.
:param tg_id: Telegram user ID :param tg_id: Telegram user ID
:param symbol: Symbol :param symbol: Symbol
:param last_side: Last side
:param current_step: Current step :param current_step: Current step
:param trade_mode: Trade mode :param trade_mode: Trade mode
:param side_mode: Side mode
:param margin_type: Margin type :param margin_type: Margin type
:param leverage: Leverage :param leverage: Leverage
:param order_quantity: Order quantity :param order_quantity: Order quantity
@@ -941,9 +941,9 @@ async def set_user_deal(
if deal: if deal:
# Updating existing record # Updating existing record
deal.last_side = last_side
deal.current_step = current_step deal.current_step = current_step
deal.trade_mode = trade_mode deal.trade_mode = trade_mode
deal.side_mode = side_mode
deal.margin_type = margin_type deal.margin_type = margin_type
deal.leverage = leverage deal.leverage = leverage
deal.order_quantity = order_quantity deal.order_quantity = order_quantity
@@ -958,9 +958,9 @@ async def set_user_deal(
new_deal = UserDeals( new_deal = UserDeals(
user=user, user=user,
symbol=symbol, symbol=symbol,
last_side=last_side,
current_step=current_step, current_step=current_step,
trade_mode=trade_mode, trade_mode=trade_mode,
side_mode=side_mode,
margin_type=margin_type, margin_type=margin_type,
leverage=leverage, leverage=leverage,
order_quantity=order_quantity, order_quantity=order_quantity,
@@ -969,7 +969,7 @@ async def set_user_deal(
max_bets_in_series=max_bets_in_series, max_bets_in_series=max_bets_in_series,
take_profit_percent=take_profit_percent, take_profit_percent=take_profit_percent,
stop_loss_percent=stop_loss_percent, stop_loss_percent=stop_loss_percent,
base_quantity=base_quantity, base_quantity=base_quantity
) )
session.add(new_deal) session.add(new_deal)
@@ -978,9 +978,7 @@ async def set_user_deal(
return True return True
except Exception as e: except Exception as e:
logger.error( logger.error("Error setting user deal for user %s and symbol %s: %s", tg_id, symbol, e)
"Error setting user deal for user %s and symbol %s: %s", tg_id, symbol, e
)
return False return False
@@ -1000,9 +998,7 @@ async def get_user_deal_by_symbol(tg_id: int, symbol: str):
deal = result_deal.scalars().first() deal = result_deal.scalars().first()
return deal return deal
except Exception as e: except Exception as e:
logger.error( logger.error("Error getting deal for user %s and symbol %s: %s", tg_id, symbol, e)
"Error getting deal for user %s and symbol %s: %s", tg_id, symbol, e
)
return None return None
@@ -1044,26 +1040,46 @@ async def set_fee_user_deal_by_symbol(tg_id: int, symbol: str, fee: float):
if record: if record:
record.fee = fee record.fee = fee
else: else:
logger.error( logger.error(f"User deal with user_id={user.id} and symbol={symbol} not found")
f"User deal with user_id={user.id} and symbol={symbol} not found"
)
return False return False
await session.commit() await session.commit()
logger.info("Set fee for user %s and symbol %s", tg_id, symbol) logger.info("Set fee for user %s and symbol %s", tg_id, symbol)
return True return True
except Exception as e: except Exception as e:
logger.error( logger.error("Error setting user deal fee for user %s and symbol %s: %s", tg_id, symbol, e)
"Error setting user deal fee for user %s and symbol %s: %s", return False
tg_id,
symbol,
e, async def set_last_side_by_symbol(tg_id: int, symbol: str, last_side: str):
"""Set last side for a user deal by symbol in the database."""
try:
async with async_session() as session:
result = await session.execute(select(User).filter_by(tg_id=tg_id))
user = result.scalars().first()
if user is None:
logger.error(f"User with tg_id={tg_id} not found")
return False
result = await session.execute(
select(UserDeals).filter_by(user_id=user.id, symbol=symbol)
) )
record = result.scalars().first()
if record:
record.last_side = last_side
else:
logger.error(f"User deal with user_id={user.id} and symbol={symbol} not found")
return False
await session.commit()
logger.info("Set last side for user %s and symbol %s", tg_id, symbol)
return True
except Exception as e:
logger.error("Error setting user deal last side for user %s and symbol %s: %s", tg_id, symbol, e)
return False return False
# USER AUTO TRADING # USER AUTO TRADING
async def get_all_user_auto_trading(tg_id: int): async def get_all_user_auto_trading(tg_id: int):
"""Get all user auto trading from the database asynchronously.""" """Get all user auto trading from the database asynchronously."""
try: try:
@@ -1098,9 +1114,7 @@ async def get_user_auto_trading(tg_id: int, symbol: str):
auto_trading = result_auto_trading.scalars().first() auto_trading = result_auto_trading.scalars().first()
return auto_trading return auto_trading
except Exception as e: except Exception as e:
logger.error( logger.error("Error getting auto trading for user %s and symbol %s: %s", tg_id, symbol, e)
"Error getting auto trading for user %s and symbol %s: %s", tg_id, symbol, e
)
return None return None
@@ -1134,17 +1148,10 @@ async def set_auto_trading(tg_id: int, symbol: str, auto_trading: bool) -> bool:
) )
session.add(new_record) session.add(new_record)
await session.commit() await session.commit()
logger.info( logger.info("Set auto_trading=%s for user %s and symbol %s", auto_trading, tg_id, symbol)
"Set auto_trading=%s for user %s and symbol %s",
auto_trading,
tg_id,
symbol,
)
return True return True
except Exception as e: except Exception as e:
logger.error( logger.error("Error setting auto_trading for user %s and symbol %s: %s", tg_id, symbol, e)
"Error setting auto_trading for user %s and symbol %s: %s", tg_id, symbol, e
)
return False return False
@@ -1182,18 +1189,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) logger.info("Set fee for user %s and symbol %s", tg_id, symbol)
return True return True
except Exception as e: except Exception as e:
logger.error( logger.error("Error setting user auto trading fee for user %s and symbol %s: %s", tg_id, symbol, e)
"Error setting user auto trading fee for user %s and symbol %s: %s",
tg_id,
symbol,
e,
)
return False return False
async def set_total_fee_user_auto_trading( async def set_total_fee_user_auto_trading(tg_id: int, symbol: str, total_fee: float) -> bool:
tg_id: int, symbol: str, total_fee: float
) -> bool:
""" """
Set the total fee for a user auto trading in the database. Set the total fee for a user auto trading in the database.
:param tg_id: Telegram user ID :param tg_id: Telegram user ID
@@ -1227,10 +1227,5 @@ async def set_total_fee_user_auto_trading(
logger.info("Set total fee for user %s and symbol %s", tg_id, symbol) logger.info("Set total fee for user %s and symbol %s", tg_id, symbol)
return True return True
except Exception as e: except Exception as e:
logger.error( logger.error("Error setting user auto trading total fee for user %s and symbol %s: %s", tg_id, symbol, e)
"Error setting user auto trading total fee for user %s and symbol %s: %s",
tg_id,
symbol,
e,
)
return False return False

3
run.py
View File

@@ -5,10 +5,10 @@ import logging.config
from aiogram import Bot, Dispatcher from aiogram import Bot, Dispatcher
from aiogram.fsm.storage.redis import RedisStorage from aiogram.fsm.storage.redis import RedisStorage
from database import init_db
from app.bybit.web_socket import WebSocketBot from app.bybit.web_socket import WebSocketBot
from app.telegram.handlers import router from app.telegram.handlers import router
from config import BOT_TOKEN from config import BOT_TOKEN
from database import init_db
from logger_helper.logger_helper import LOGGING_CONFIG from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG) logging.config.dictConfig(LOGGING_CONFIG)
@@ -46,7 +46,6 @@ async def main():
with contextlib.suppress(asyncio.CancelledError): with contextlib.suppress(asyncio.CancelledError):
await ws_task await ws_task
await tg_task await tg_task
await web_socket.clear_user_sockets()
except Exception as e: except Exception as e:
logger.error("Bot stopped with error: %s", e) logger.error("Bot stopped with error: %s", e)