forked from kodorvan/stcs
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			devel
			...
			0945be242a
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 0945be242a | ||
|   | 4663888190 | 
| @@ -1 +1,6 @@ | ||||
| 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
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -146,9 +146,6 @@ myenv | ||||
| ENV/ | ||||
| env.bak/ | ||||
| venv.bak/ | ||||
| /logger_helper/loggers | ||||
| /app/bybit/logger_bybit/loggers | ||||
| *.db | ||||
| # Spyder project settings | ||||
| .spyderproject | ||||
| .spyproject | ||||
|   | ||||
| @@ -54,10 +54,6 @@ sudo -u www-data /usr/bin/pip install -r requirements.txt | ||||
| cp .env.sample .env | ||||
| nvim .env | ||||
| ``` | ||||
| 5. Выполните миграции: | ||||
| ```bash | ||||
| alembic upgrade head | ||||
| ``` | ||||
|  | ||||
| 5. Запустите бота: | ||||
|  | ||||
|   | ||||
| @@ -84,7 +84,7 @@ path_separator = os | ||||
| # 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 | ||||
| # file. | ||||
| sqlalchemy.url = sqlite+aiosqlite:///./database/db/stcs.db | ||||
| sqlalchemy.url = driver://user:pass@localhost/dbname | ||||
|  | ||||
|  | ||||
| [post_write_hooks] | ||||
|   | ||||
| @@ -1,29 +1,73 @@ | ||||
| import asyncio | ||||
| from logging.config import fileConfig | ||||
|  | ||||
| from sqlalchemy import pool | ||||
| from sqlalchemy.engine import Connection | ||||
| from sqlalchemy.ext.asyncio import async_engine_from_config | ||||
|  | ||||
| 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.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: | ||||
|     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 | ||||
| target_metadata = Base.metadata | ||||
|  | ||||
| def do_run_migrations(connection): | ||||
| # other values from the config, defined by the needs of env.py, | ||||
| # 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( | ||||
|         connection=connection, | ||||
|         url=DATABASE_URL, | ||||
|         target_metadata=target_metadata, | ||||
|         compare_type=True, | ||||
|         literal_binds=True, | ||||
|         dialect_opts={"paramstyle": "named"}, | ||||
|     ) | ||||
|  | ||||
|     with context.begin_transaction(): | ||||
|         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( | ||||
|         config.get_section(config.config_ini_section), | ||||
|         config.get_section(config.config_ini_section, {}), | ||||
|         prefix="sqlalchemy.", | ||||
|         poolclass=pool.NullPool, | ||||
|     ) | ||||
| @@ -33,20 +77,13 @@ async def run_async_migrations(): | ||||
|  | ||||
|     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(): | ||||
| def run_migrations_online() -> None: | ||||
|     """Run migrations in 'online' mode.""" | ||||
|  | ||||
|     asyncio.run(run_async_migrations()) | ||||
|  | ||||
|  | ||||
| if context.is_offline_mode(): | ||||
|     run_migrations_offline() | ||||
| else: | ||||
|   | ||||
							
								
								
									
										36
									
								
								alembic/versions/09db71875980_updated_user_deals_table.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								alembic/versions/09db71875980_updated_user_deals_table.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| """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 ### | ||||
| @@ -0,0 +1,40 @@ | ||||
| """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 ### | ||||
| @@ -0,0 +1,34 @@ | ||||
| """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 ### | ||||
| @@ -0,0 +1,34 @@ | ||||
| """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 ### | ||||
| @@ -1,8 +1,8 @@ | ||||
| """Added column side for additional_setiings | ||||
| """update last side the conditional data | ||||
| 
 | ||||
| Revision ID: e5d612e44563 | ||||
| Revises: fbf4e3658310 | ||||
| Create Date: 2025-10-25 18:25:52.746250 | ||||
| Revision ID: 3534adf891fc | ||||
| Revises: ef38c90eed55 | ||||
| Create Date: 2025-09-30 08:39:02.971158 | ||||
| 
 | ||||
| """ | ||||
| from typing import Sequence, Union | ||||
| @@ -12,8 +12,8 @@ import sqlalchemy as sa | ||||
| 
 | ||||
| 
 | ||||
| # revision identifiers, used by Alembic. | ||||
| revision: str = 'e5d612e44563' | ||||
| down_revision: Union[str, Sequence[str], None] = 'fbf4e3658310' | ||||
| 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 | ||||
| 
 | ||||
| @@ -21,14 +21,12 @@ depends_on: Union[str, Sequence[str], None] = None | ||||
| def upgrade() -> None: | ||||
|     """Upgrade schema.""" | ||||
|     # ### commands auto generated by Alembic - please adjust! ### | ||||
|     op.add_column('user_additional_settings', | ||||
|                   sa.Column('side', sa.String(), nullable=False, server_default='') | ||||
|                   ) | ||||
|     pass | ||||
|     # ### end Alembic commands ### | ||||
| 
 | ||||
| 
 | ||||
| def downgrade() -> None: | ||||
|     """Downgrade schema.""" | ||||
|     # ### commands auto generated by Alembic - please adjust! ### | ||||
|     op.drop_column('user_additional_settings', 'side') | ||||
|     pass | ||||
|     # ### end Alembic commands ### | ||||
							
								
								
									
										38
									
								
								alembic/versions/42c66cfe8d4e_updated_martingale_factor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								alembic/versions/42c66cfe8d4e_updated_martingale_factor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| """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 ### | ||||
							
								
								
									
										38
									
								
								alembic/versions/45977e9d8558_updated_order_quantity.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								alembic/versions/45977e9d8558_updated_order_quantity.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| """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 ### | ||||
| @@ -0,0 +1,40 @@ | ||||
| """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 ### | ||||
| @@ -0,0 +1,42 @@ | ||||
| """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 ### | ||||
| @@ -0,0 +1,32 @@ | ||||
| """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 ### | ||||
							
								
								
									
										32
									
								
								alembic/versions/863d6215e1eb_updated_deals.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								alembic/versions/863d6215e1eb_updated_deals.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| """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 ### | ||||
| @@ -0,0 +1,34 @@ | ||||
| """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 ### | ||||
| @@ -0,0 +1,36 @@ | ||||
| """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 ### | ||||
							
								
								
									
										60
									
								
								alembic/versions/acbcc95de48d_updated_userdeals.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								alembic/versions/acbcc95de48d_updated_userdeals.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| """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 ### | ||||
| @@ -0,0 +1,79 @@ | ||||
| """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 ### | ||||
							
								
								
									
										38
									
								
								alembic/versions/c98b9dc36d15_fixed_auto_trade.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								alembic/versions/c98b9dc36d15_fixed_auto_trade.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| """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 ### | ||||
							
								
								
									
										50
									
								
								alembic/versions/ccdc5764eb4f_added_userdeals.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								alembic/versions/ccdc5764eb4f_added_userdeals.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| """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 ### | ||||
| @@ -0,0 +1,34 @@ | ||||
| """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 ### | ||||
| @@ -0,0 +1,40 @@ | ||||
| """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 ### | ||||
							
								
								
									
										32
									
								
								alembic/versions/ef342b38e17b_added_fee_user_deals.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								alembic/versions/ef342b38e17b_added_fee_user_deals.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | ||||
| """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 ### | ||||
| @@ -0,0 +1,38 @@ | ||||
| """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 ### | ||||
							
								
								
									
										40
									
								
								alembic/versions/f00a94ccdf01_updated_deals.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								alembic/versions/f00a94ccdf01_updated_deals.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | ||||
| """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 ### | ||||
| @@ -1,8 +1,8 @@ | ||||
| """Added side_mode column | ||||
| """Updated leverage | ||||
| 
 | ||||
| Revision ID: fbf4e3658310 | ||||
| Revises:  | ||||
| Create Date: 2025-10-22 13:08:02.317419 | ||||
| Revision ID: fd8581c0cc87 | ||||
| Revises: bb586fa9bcd2 | ||||
| Create Date: 2025-09-22 15:13:21.487402 | ||||
| 
 | ||||
| """ | ||||
| from typing import Sequence, Union | ||||
| @@ -12,7 +12,7 @@ import sqlalchemy as sa | ||||
| 
 | ||||
| 
 | ||||
| # revision identifiers, used by Alembic. | ||||
| revision: str = 'fbf4e3658310' | ||||
| revision: str = 'fd8581c0cc87' | ||||
| down_revision: Union[str, Sequence[str], None] = None | ||||
| branch_labels: 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: | ||||
|     """Upgrade schema.""" | ||||
|     # ### commands auto generated by Alembic - please adjust! ### | ||||
|     op.add_column('user_deals', sa.Column('side_mode', sa.String(), nullable=True)) | ||||
|     pass | ||||
|     # ### end Alembic commands ### | ||||
| 
 | ||||
| 
 | ||||
| def downgrade() -> None: | ||||
|     """Downgrade schema.""" | ||||
|     # ### commands auto generated by Alembic - please adjust! ### | ||||
|     op.drop_column('user_deals', 'side_mode') | ||||
|     pass | ||||
|     # ### end Alembic commands ### | ||||
| @@ -7,25 +7,25 @@ logging.config.dictConfig(LOGGING_CONFIG) | ||||
| logger = logging.getLogger("close_positions") | ||||
|  | ||||
|  | ||||
| async def close_position_by_symbol( | ||||
|     tg_id: int, symbol: str | ||||
| async def close_position( | ||||
|     tg_id: int, symbol: str, side: str, position_idx: int, qty: float | ||||
| ) -> bool: | ||||
|     """ | ||||
|     Closes all positions | ||||
|     :param tg_id: Telegram user ID | ||||
|     :param symbol: symbol | ||||
|     :param side: side | ||||
|     :param position_idx: position index | ||||
|     :param qty: quantity | ||||
|     :return: bool | ||||
|     """ | ||||
|     try: | ||||
|         client = await get_bybit_client(tg_id) | ||||
|  | ||||
|         response = client.get_positions( | ||||
|             category="linear", symbol=symbol | ||||
|         ) | ||||
|         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") | ||||
|         if side == "Buy": | ||||
|             r_side = "Sell" | ||||
|         else: | ||||
|             r_side = "Buy" | ||||
|  | ||||
|         response = client.place_order( | ||||
|             category="linear", | ||||
| @@ -37,16 +37,16 @@ async def close_position_by_symbol( | ||||
|             positionIdx=position_idx, | ||||
|         ) | ||||
|         if response["retCode"] == 0: | ||||
|             logger.info("Positions closed for %s for user %s", symbol, tg_id) | ||||
|             logger.info("All positions closed for %s for user %s", symbol, tg_id) | ||||
|             return True | ||||
|         else: | ||||
|             logger.error( | ||||
|                 "Error closing position for %s for user %s", symbol, tg_id | ||||
|                 "Error closing all positions for %s for user %s", symbol, tg_id | ||||
|             ) | ||||
|             return False | ||||
|     except Exception as e: | ||||
|         logger.error( | ||||
|             "Error closing positions for %s for user %s: %s", symbol, tg_id, e | ||||
|             "Error closing all positions for %s for user %s: %s", symbol, tg_id, e | ||||
|         ) | ||||
|         return False | ||||
|  | ||||
|   | ||||
| @@ -10,27 +10,30 @@ from app.bybit.get_functions.get_tickers import get_tickers | ||||
| 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_margin_mode import set_margin_mode | ||||
| from app.bybit.set_functions.set_switch_position_mode import set_switch_position_mode | ||||
| from app.helper_functions import safe_float | ||||
| from app.helper_functions import get_liquidation_price, safe_float | ||||
|  | ||||
| logging.config.dictConfig(LOGGING_CONFIG) | ||||
| logger = logging.getLogger("open_positions") | ||||
|  | ||||
|  | ||||
| async def start_trading_cycle( | ||||
|         tg_id: int | ||||
|     tg_id: int | ||||
| ) -> str | None: | ||||
|     """ | ||||
|     Start trading cycle | ||||
|     :param tg_id: Telegram user ID | ||||
|     """ | ||||
|     try: | ||||
|         client = await get_bybit_client(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) | ||||
|         risk_management_data = await rq.get_user_risk_management(tg_id=tg_id) | ||||
|         commission_fee = risk_management_data.commission_fee | ||||
|         user_deals_data = await rq.get_user_deal_by_symbol( | ||||
|             tg_id=tg_id, symbol=symbol | ||||
|         ) | ||||
|         trade_mode = additional_data.trade_mode | ||||
|         switch_side = additional_data.switch_side | ||||
|         side= additional_data.side | ||||
|         margin_type = additional_data.margin_type | ||||
|         leverage = additional_data.leverage | ||||
|         order_quantity = additional_data.order_quantity | ||||
| @@ -39,20 +42,54 @@ async def start_trading_cycle( | ||||
|         max_bets_in_series = additional_data.max_bets_in_series | ||||
|         take_profit_percent = risk_management_data.take_profit_percent | ||||
|         stop_loss_percent = risk_management_data.stop_loss_percent | ||||
|         total_commission = 0 | ||||
|  | ||||
|         get_side = "Buy" | ||||
|  | ||||
|         if user_deals_data: | ||||
|             get_side = user_deals_data.last_side or "Buy" | ||||
|  | ||||
|         if trade_mode == "Switch": | ||||
|             side = side | ||||
|             if switch_side == "По направлению": | ||||
|                 side = get_side | ||||
|             else: | ||||
|                 if get_side == "Buy": | ||||
|                     side = "Sell" | ||||
|                 else: | ||||
|                     side = "Buy" | ||||
|         else: | ||||
|             if trade_mode == "Long": | ||||
|                 side = "Buy" | ||||
|             else: | ||||
|                 side = "Sell" | ||||
|  | ||||
|         await set_switch_position_mode( | ||||
|             tg_id=tg_id, | ||||
|             symbol=symbol, | ||||
|             mode=0) | ||||
|         # Get fee rates | ||||
|         fee_info = client.get_fee_rates(category="linear", symbol=symbol) | ||||
|  | ||||
|         # Check if commission fee is enabled | ||||
|         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_leverage( | ||||
|             tg_id=tg_id, | ||||
| @@ -77,9 +114,9 @@ async def start_trading_cycle( | ||||
|             await rq.set_user_deal( | ||||
|                 tg_id=tg_id, | ||||
|                 symbol=symbol, | ||||
|                 last_side=side, | ||||
|                 current_step=1, | ||||
|                 trade_mode=trade_mode, | ||||
|                 side_mode=switch_side, | ||||
|                 margin_type=margin_type, | ||||
|                 leverage=leverage, | ||||
|                 order_quantity=order_quantity, | ||||
| @@ -94,19 +131,19 @@ async def start_trading_cycle( | ||||
|         return ( | ||||
|             res | ||||
|             if res | ||||
|                in { | ||||
|                    "Limit price is out min price", | ||||
|                    "Limit price is out max price", | ||||
|                    "Risk is too high for this trade", | ||||
|                    "estimated will trigger liq", | ||||
|                    "ab not enough for new order", | ||||
|                    "InvalidRequestError", | ||||
|                    "Order does not meet minimum order value", | ||||
|                    "position idx not match position mode", | ||||
|                    "Qty invalid", | ||||
|                    "The number of contracts exceeds maximum limit allowed", | ||||
|                    "The number of contracts exceeds minimum limit allowed" | ||||
|                } | ||||
|             in { | ||||
|                 "Limit price is out min price", | ||||
|                 "Limit price is out max price", | ||||
|                 "Risk is too high for this trade", | ||||
|                 "estimated will trigger liq", | ||||
|                 "ab not enough for new order", | ||||
|                 "InvalidRequestError", | ||||
|                 "Order does not meet minimum order value", | ||||
|                 "position idx not match position mode", | ||||
|                 "Qty invalid", | ||||
|                 "The number of contracts exceeds maximum limit allowed", | ||||
|                 "The number of contracts exceeds minimum limit allowed" | ||||
|             } | ||||
|             else None | ||||
|         ) | ||||
|  | ||||
| @@ -115,95 +152,12 @@ async def start_trading_cycle( | ||||
|         return None | ||||
|  | ||||
|  | ||||
| async def trading_cycle_profit( | ||||
|         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) | ||||
|         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 | ||||
|         side_mode = user_deals_data.side_mode | ||||
|         base_quantity = user_deals_data.base_quantity | ||||
|  | ||||
|         await set_margin_mode(tg_id=tg_id, margin_mode=margin_type) | ||||
|         await set_leverage( | ||||
|             tg_id=tg_id, | ||||
|             symbol=symbol, | ||||
|             leverage=leverage, | ||||
|         ) | ||||
|  | ||||
|  | ||||
|         if trade_mode == "Switch": | ||||
|             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, | ||||
|     tg_id: int, symbol: str, reverse_side: str | ||||
| ) -> str | None: | ||||
|     try: | ||||
|         user_deals_data = await rq.get_user_deal_by_symbol(tg_id=tg_id, symbol=symbol) | ||||
|         user_auto_trading_data = await rq.get_user_auto_trading(tg_id=tg_id, symbol=symbol) | ||||
|         user_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 | ||||
| @@ -216,7 +170,23 @@ async def trading_cycle( | ||||
|         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 | ||||
|  | ||||
|         await set_margin_mode(tg_id=tg_id, margin_mode=margin_type) | ||||
|         await set_leverage( | ||||
|             tg_id=tg_id, | ||||
|             symbol=symbol, | ||||
|             leverage=leverage, | ||||
|         ) | ||||
|  | ||||
|         if reverse_side == "Buy": | ||||
|             real_side = "Sell" | ||||
|         else: | ||||
|             real_side = "Buy" | ||||
|  | ||||
|         side = real_side | ||||
|  | ||||
|         if trade_mode == "Switch": | ||||
|             side = "Sell" if real_side == "Buy" else "Buy" | ||||
|  | ||||
|         next_quantity = safe_float(order_quantity) * ( | ||||
|             safe_float(martingale_factor) | ||||
| @@ -226,29 +196,10 @@ async def trading_cycle( | ||||
|         if max_bets_in_series < current_step: | ||||
|             return "Max bets in series" | ||||
|  | ||||
|         await set_margin_mode(tg_id=tg_id, margin_mode=margin_type) | ||||
|         await set_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( | ||||
|             tg_id=tg_id, | ||||
|             symbol=symbol, | ||||
|             side=r_side, | ||||
|             side=side, | ||||
|             order_quantity=next_quantity, | ||||
|             trigger_price=trigger_price, | ||||
|             margin_type=margin_type, | ||||
| @@ -262,9 +213,9 @@ async def trading_cycle( | ||||
|             await rq.set_user_deal( | ||||
|                 tg_id=tg_id, | ||||
|                 symbol=symbol, | ||||
|                 last_side=side, | ||||
|                 current_step=current_step, | ||||
|                 trade_mode=trade_mode, | ||||
|                 side_mode=side_mode, | ||||
|                 margin_type=margin_type, | ||||
|                 leverage=leverage, | ||||
|                 order_quantity=next_quantity, | ||||
| @@ -280,12 +231,12 @@ async def trading_cycle( | ||||
|         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", | ||||
|                } | ||||
|             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 | ||||
|         ) | ||||
|  | ||||
| @@ -295,16 +246,16 @@ async def trading_cycle( | ||||
|  | ||||
|  | ||||
| async def open_positions( | ||||
|         tg_id: int, | ||||
|         side: str, | ||||
|         symbol: str, | ||||
|         order_quantity: float, | ||||
|         trigger_price: float, | ||||
|         margin_type: str, | ||||
|         leverage: str, | ||||
|         take_profit_percent: float, | ||||
|         stop_loss_percent: float, | ||||
|         commission_fee_percent: float | ||||
|     tg_id: int, | ||||
|     side: str, | ||||
|     symbol: str, | ||||
|     order_quantity: float, | ||||
|     trigger_price: float, | ||||
|     margin_type: str, | ||||
|     leverage: str, | ||||
|     take_profit_percent: float, | ||||
|     stop_loss_percent: float, | ||||
|     commission_fee_percent: float | ||||
| ) -> str | None: | ||||
|     try: | ||||
|         client = await get_bybit_client(tg_id=tg_id) | ||||
| @@ -313,7 +264,7 @@ async def open_positions( | ||||
|         instruments_info = await get_instruments_info(tg_id=tg_id, symbol=symbol) | ||||
|         qty_step_str = instruments_info.get("lotSizeFilter").get("qtyStep") | ||||
|         qty_step = safe_float(qty_step_str) | ||||
|         qty = (safe_float(order_quantity) * safe_float(leverage)) / safe_float(price_symbol) | ||||
|         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) | ||||
| @@ -325,33 +276,51 @@ async def open_positions( | ||||
|             po_trigger_price = None | ||||
|             trigger_direction = None | ||||
|  | ||||
|         get_leverage = safe_float(leverage) | ||||
|  | ||||
|         price_for_cals = trigger_price if po_trigger_price is not None else price_symbol | ||||
|  | ||||
|         if qty_formatted <= 0: | ||||
|             return "Order does not meet minimum order value" | ||||
|         tp_multiplier = 1 + (take_profit_percent / 100) | ||||
|         if commission_fee_percent > 0: | ||||
|             tp_multiplier += commission_fee_percent | ||||
|  | ||||
|         if margin_type == "ISOLATED_MARGIN": | ||||
|             if side == "Buy": | ||||
|                 take_profit_price = price_for_cals * ( | ||||
|                         1 + take_profit_percent / 100) + commission_fee_percent / qty_formatted | ||||
|                 stop_loss_price = None | ||||
|             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": | ||||
|                     base_tp = price_for_cals + (price_for_cals - liq_long) | ||||
|                     take_profit_price = base_tp + commission_fee_percent | ||||
|                 else: | ||||
|                     base_tp = price_for_cals - (liq_short - price_for_cals) | ||||
|                     take_profit_price = base_tp - commission_fee_percent | ||||
|                 take_profit_price = max(take_profit_price, 0) | ||||
|             else: | ||||
|                 take_profit_price = price_for_cals * ( | ||||
|                         1 - take_profit_percent / 100) - commission_fee_percent / qty_formatted | ||||
|                 stop_loss_price = None | ||||
|                 take_profit_price = None | ||||
|  | ||||
|             stop_loss_price = None | ||||
|         else: | ||||
|             if side == "Buy": | ||||
|                 take_profit_price = price_for_cals * ( | ||||
|                             1 + take_profit_percent / 100) + commission_fee_percent / qty_formatted | ||||
|                 take_profit_price = price_for_cals * tp_multiplier | ||||
|                 stop_loss_price = price_for_cals * (1 - stop_loss_percent / 100) | ||||
|             else: | ||||
|                 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) | ||||
|                     1 - (take_profit_percent / 100) - commission_fee_percent | ||||
|                 ) | ||||
|                 stop_loss_price = trigger_price * (1 + stop_loss_percent / 100) | ||||
|  | ||||
|             take_profit_price = max(take_profit_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 | ||||
|         order_params = { | ||||
|             "category": "linear", | ||||
| @@ -397,5 +366,5 @@ async def open_positions( | ||||
|         return "InvalidRequestError" | ||||
|  | ||||
|     except Exception as e: | ||||
|         logger.error("Error opening position for user %s: %s", tg_id, e, exc_info=True) | ||||
|         logger.error("Error opening position for user %s: %s", tg_id, e) | ||||
|         return None | ||||
|   | ||||
| @@ -21,20 +21,16 @@ async def user_profile_bybit(tg_id: int, message: Message, state: FSMContext) -> | ||||
|         if wallet: | ||||
|             balance = wallet.get("totalWalletBalance", "0") | ||||
|             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( | ||||
|                     text=f"💎Ваш профиль:\n\n" | ||||
|                          f"⚖️ Баланс: {float(balance):,.2f} USD\n" | ||||
|                          f"📊Торговая пара: {symbol}\n\n" | ||||
|                          f"Краткая инструкция:\n" | ||||
|                          f"1. Укажите торговую пару (например: BTCUSDT).\n" | ||||
|                          f"2. В настройках выставьте все необходимые параметры.\n" | ||||
|                          f"3. Нажмите кнопку 'Начать торговлю'.\n", | ||||
|                     reply_markup=kbi.main_menu, | ||||
|                 ) | ||||
|             await message.answer( | ||||
|                 text=f"💎Ваш профиль:\n\n" | ||||
|                      f"⚖️ Баланс: {float(balance):,.2f} USD\n" | ||||
|                      f"📊Торговая пара: {symbol}\n\n" | ||||
|                      f"Краткая инструкция:\n" | ||||
|                      f"1. Укажите торговую пару (например: BTCUSDT).\n" | ||||
|                      f"2. В настройках выставьте все необходимые параметры.\n" | ||||
|                      f"3. Нажмите кнопку 'Начать торговлю'.\n", | ||||
|                 reply_markup=kbi.main_menu, | ||||
|             ) | ||||
|         else: | ||||
|             await message.answer( | ||||
|                 text="Ошибка при подключении, повторите попытку", | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import logging.config | ||||
| import app.telegram.keyboards.inline as kbi | ||||
| import database.request as rq | ||||
| from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG | ||||
| from app.bybit.open_positions import trading_cycle, trading_cycle_profit | ||||
| from app.bybit.open_positions import trading_cycle | ||||
| from app.helper_functions import format_value, safe_float | ||||
|  | ||||
| logging.config.dictConfig(LOGGING_CONFIG) | ||||
| @@ -41,26 +41,11 @@ class TelegramMessageHandler: | ||||
|             if order_status == "Filled" or order_status not in status_map: | ||||
|                 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 = ( | ||||
|                 f"Торговая пара: {symbol}\n" | ||||
|                 f"Количество: {qty}\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": | ||||
|                 text += f"Цена: {price}\n" | ||||
|             if take_profit and take_profit != "Нет данных": | ||||
| @@ -82,21 +67,13 @@ class TelegramMessageHandler: | ||||
|             closed_size = format_value(execution.get("closedSize")) | ||||
|             symbol = format_value(execution.get("symbol")) | ||||
|             exec_price = format_value(execution.get("execPrice")) | ||||
|             exec_qty = format_value(execution.get("execQty")) | ||||
|             exec_fees = format_value(execution.get("execFee")) | ||||
|             fee_rate = format_value(execution.get("feeRate")) | ||||
|             exec_fee = format_value(execution.get("execFee")) | ||||
|             side = format_value(execution.get("side")) | ||||
|             side_rus = ( | ||||
|                 "Покупка" | ||||
|                 if side == "Buy" | ||||
|                 else "Продажа" if side == "Sell" else "Нет данных" | ||||
|             ) | ||||
|             if safe_float(exec_fees) == 0: | ||||
|                 exec_fee = safe_float(exec_price) * safe_float(exec_qty) * safe_float( | ||||
|                     fee_rate | ||||
|                 ) | ||||
|             else: | ||||
|                 exec_fee = safe_float(exec_fees) | ||||
|  | ||||
|             if safe_float(closed_size) == 0: | ||||
|                 await rq.set_fee_user_auto_trading( | ||||
| @@ -109,7 +86,9 @@ class TelegramMessageHandler: | ||||
|  | ||||
|             get_total_fee = user_auto_trading.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: | ||||
|                 fee = user_auto_trading.fee | ||||
| @@ -117,38 +96,42 @@ class TelegramMessageHandler: | ||||
|                 fee = 0 | ||||
|  | ||||
|             exec_pnl = format_value(execution.get("execPnl")) | ||||
|             total_pnl = safe_float(exec_pnl) - safe_float(exec_fee) - fee | ||||
|             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 | ||||
|             else: | ||||
|                 total_pnl = safe_float(exec_pnl) | ||||
|  | ||||
|             header = ( | ||||
|                 "Сделка закрыта:" if safe_float(closed_size) > 0 else "Сделка открыта:" | ||||
|             ) | ||||
|             text = f"{header}\n" f"Торговая пара: {symbol}\n" | ||||
|  | ||||
|             auto_trading = ( | ||||
|                 user_auto_trading.auto_trading if user_auto_trading else False | ||||
|             ) | ||||
|             user_deals_data = await rq.get_user_deal_by_symbol( | ||||
|                 tg_id=tg_id, symbol=symbol | ||||
|             ) | ||||
|             if user_deals_data is not None and auto_trading: | ||||
|                 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" | ||||
|             exec_bet = user_deals_data.order_quantity | ||||
|             base_quantity = user_deals_data.base_quantity | ||||
|  | ||||
|             text += ( | ||||
|                 f"Цена исполнения: {exec_price}\n" | ||||
|                 f"Комиссия: {exec_fee:.8f}\n" | ||||
|                 f"Текущая ставка: {exec_bet}\n" | ||||
|                 f"Движение: {side_rus}\n" | ||||
|                 f"Комиссия за сделку: {exec_fee}\n" | ||||
|             ) | ||||
|  | ||||
|             if safe_float(closed_size) == 0: | ||||
|                 text += f"Движение: {side_rus}\n" | ||||
|             else: | ||||
|             if safe_float(closed_size) > 0: | ||||
|                 text += f"\nРеализованная прибыль: {total_pnl:.7f}\n" | ||||
|  | ||||
|             await self.telegram_bot.send_message( | ||||
|                 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 | ||||
|  | ||||
|             if ( | ||||
| @@ -157,70 +140,30 @@ class TelegramMessageHandler: | ||||
|                 and user_symbols is not None | ||||
|             ): | ||||
|                 if safe_float(total_pnl) > 0: | ||||
|                     profit_text = "📈 Прибыль достигнута. Начинаем новую серию с базовой ставки\n" | ||||
|                     profit_text = "📈 Прибыль достигнута\n" | ||||
|                     await self.telegram_bot.send_message( | ||||
|                         chat_id=tg_id, text=profit_text, reply_markup=kbi.profile_bybit | ||||
|                     ) | ||||
|                     await rq.set_auto_trading( | ||||
|                         tg_id=tg_id, symbol=symbol, auto_trading=False | ||||
|                     ) | ||||
|  | ||||
|                     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 | ||||
|                     await rq.set_order_quantity( | ||||
|                         tg_id=message.from_user.id, order_quantity=base_quantity | ||||
|                     ) | ||||
|  | ||||
|                     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( | ||||
|                             tg_id=tg_id, symbol=symbol, auto_trading=False | ||||
|                         ) | ||||
|  | ||||
|                         await rq.set_total_fee_user_auto_trading( | ||||
|                             tg_id=tg_id, symbol=symbol, total_fee=0 | ||||
|                         ) | ||||
|                         await rq.set_fee_user_auto_trading( | ||||
|                             tg_id=tg_id, symbol=symbol, fee=0 | ||||
|                         ) | ||||
|                         await self.telegram_bot.send_message( | ||||
|                             chat_id=tg_id, | ||||
|                             text=error_text, | ||||
|                             reply_markup=kbi.profile_bybit, | ||||
|                         ) | ||||
|                 else: | ||||
|                     open_order_text = "\n❗️ Сделка закрылась в минус, открываю новую сделку с увеличенной ставкой.\n" | ||||
|                     await self.telegram_bot.send_message( | ||||
|                         chat_id=tg_id, text=open_order_text | ||||
|                     ) | ||||
|  | ||||
|                     if side == "Buy": | ||||
|                         r_side = "Sell" | ||||
|                     else: | ||||
|                         r_side = "Buy" | ||||
|  | ||||
|                     res = await trading_cycle( | ||||
|                         tg_id=tg_id, symbol=symbol, side=r_side | ||||
|                         tg_id=tg_id, symbol=symbol, reverse_side=side | ||||
|                     ) | ||||
|  | ||||
|                     if res == "OK": | ||||
| @@ -231,7 +174,7 @@ class TelegramMessageHandler: | ||||
|                             "Risk is too high for this trade": "❗️ Риск сделки слишком высок для продолжения", | ||||
|                             "ab not enough for new order": "❗️ Недостаточно средств для продолжения торговли", | ||||
|                             "InvalidRequestError": "❗️ Недостаточно средств для размещения нового ордера с заданным количеством и плечом.", | ||||
|                             "The number of contracts exceeds maximum limit allowed": "❗️ Превышен максимальный лимит ставки", | ||||
|                             "The number of contracts exceeds maximum limit allowed": "❗️ Количество контрактов превышает допустимое максимальное количество контрактов", | ||||
|                         } | ||||
|                         error_text = errors.get( | ||||
|                             res, "❗️ Не удалось открыть новую сделку" | ||||
|   | ||||
| @@ -46,9 +46,7 @@ class WebSocketBot: | ||||
|                     self.user_sockets.clear() | ||||
|                     self.user_messages.clear() | ||||
|                     self.user_keys.clear() | ||||
|                     logger.info( | ||||
|                         "Closed old websocket for user %s due to key change", tg_id | ||||
|                     ) | ||||
|                     logger.info("Closed old websocket for user %s due to key change", tg_id) | ||||
|  | ||||
|                 success = await self.try_connect_user(api_key, api_secret, tg_id) | ||||
|                 if success: | ||||
|   | ||||
| @@ -158,7 +158,7 @@ async def get_liquidation_price( | ||||
|  | ||||
|  | ||||
| async def calculate_total_budget( | ||||
|     quantity, martingale_factor, max_steps | ||||
|     quantity, martingale_factor, max_steps, commission_fee_percent | ||||
| ) -> float: | ||||
|     """ | ||||
|     Calculate the total budget for a series of trading steps. | ||||
| @@ -167,6 +167,7 @@ async def calculate_total_budget( | ||||
|         quantity (float): The initial quantity of the asset. | ||||
|         martingale_factor (float): The factor by which the quantity is multiplied for each step. | ||||
|         max_steps (int): The maximum number of trading steps. | ||||
|         commission_fee_percent (float): The commission fee percentage. | ||||
|  | ||||
|     Returns: | ||||
|         float: The total budget for the series of trading steps. | ||||
| @@ -174,8 +175,12 @@ async def calculate_total_budget( | ||||
|     total = 0 | ||||
|     for step in range(max_steps): | ||||
|         set_quantity = quantity * (martingale_factor**step) | ||||
|  | ||||
|         r_quantity = set_quantity | ||||
|         if commission_fee_percent == 0: | ||||
|             # Commission fee is not added to the position size | ||||
|             r_quantity = set_quantity | ||||
|         else: | ||||
|             # Commission fee is added to the position size | ||||
|             r_quantity = set_quantity * (1 + 2 * commission_fee_percent) | ||||
|  | ||||
|         total += r_quantity | ||||
|     return total | ||||
|   | ||||
| @@ -124,8 +124,6 @@ async def set_symbol(message: Message, state: FSMContext) -> None: | ||||
|         risk_percent = 100 / safe_float(max_leverage) | ||||
|         await rq.set_stop_loss_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_order_quantity(tg_id=message.from_user.id, order_quantity=1.0) | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,9 @@ from aiogram import Router | ||||
| from aiogram.fsm.context import FSMContext | ||||
| 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 | ||||
|  | ||||
| logging.config.dictConfig(LOGGING_CONFIG) | ||||
| @@ -25,6 +28,31 @@ async def close_position_handler( | ||||
|     :return: None | ||||
|     """ | ||||
|     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( | ||||
|             "Command close_position processed successfully for user: %s", | ||||
|             callback_query.from_user.id, | ||||
| @@ -53,6 +81,19 @@ async def cancel_order_handler( | ||||
|     :return: None | ||||
|     """ | ||||
|     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( | ||||
|             "Command close_order processed successfully for user: %s", | ||||
|             callback_query.from_user.id, | ||||
|   | ||||
| @@ -7,7 +7,6 @@ from aiogram.types import CallbackQuery, Message | ||||
| import app.telegram.keyboards.inline as kbi | ||||
| import database.request as rq | ||||
| from app.bybit.get_functions.get_instruments_info import get_instruments_info | ||||
| from app.bybit.get_functions.get_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_margin_mode import set_margin_mode | ||||
| from app.helper_functions import is_int, is_number, safe_float | ||||
| @@ -43,7 +42,7 @@ async def settings_for_trade_mode( | ||||
|             text="Выберите режим торговли:\n\n" | ||||
|                  "Лонг - все сделки серии открываются на покупку.\n" | ||||
|                  "Шорт - все сделки серии открываются на продажу.\n" | ||||
|                  "Свитч - направление каждой сделки в рамках серии меняется попеременно.\n", | ||||
|                  "Свитч - направление каждой сделки серии меняется по переменно.\n", | ||||
|             reply_markup=kbi.trade_mode, | ||||
|         ) | ||||
|         logger.debug( | ||||
| @@ -105,10 +104,10 @@ async def trade_mode(callback_query: CallbackQuery, state: FSMContext) -> None: | ||||
|         await state.clear() | ||||
|  | ||||
|  | ||||
| @router_additional_settings.callback_query(F.data == "switch_side_second") | ||||
| async def switch_side_second(callback_query: CallbackQuery, state: FSMContext) -> None: | ||||
| @router_additional_settings.callback_query(F.data == "switch_side_start") | ||||
| async def switch_side_start(callback_query: CallbackQuery, state: FSMContext) -> None: | ||||
|     """ | ||||
|     Handles the 'switch_side_second' callback query. | ||||
|     Handles the 'switch_side_start' callback query. | ||||
|  | ||||
|     Clears the current FSM state, edits the message text to display the switch side start message, | ||||
|     and shows an inline keyboard for selection. | ||||
| @@ -123,13 +122,13 @@ async def switch_side_second(callback_query: CallbackQuery, state: FSMContext) - | ||||
|     try: | ||||
|         await state.clear() | ||||
|         await callback_query.message.edit_text( | ||||
|             text="Выберите направление первой сделки последующих серии:\n\n" | ||||
|             text="Выберите направление первой сделки серии:\n\n" | ||||
|                  "По направлению - сделка открывается в направлении последней сделки предыдущей серии.\n" | ||||
|                  "Противоположно - сделка открывается в противоположном направлении последней сделки предыдущей серии.\n", | ||||
|             reply_markup=kbi.switch_side, | ||||
|         ) | ||||
|         logger.debug( | ||||
|             "Command switch_side_second processed successfully for user: %s", | ||||
|             "Command switch_side_start processed successfully for user: %s", | ||||
|             callback_query.from_user.id, | ||||
|         ) | ||||
|     except Exception as e: | ||||
| @@ -137,7 +136,7 @@ async def switch_side_second(callback_query: CallbackQuery, state: FSMContext) - | ||||
|             text="Произошла ошибка. Пожалуйста, попробуйте позже." | ||||
|         ) | ||||
|         logger.error( | ||||
|             "Error processing command switch_side_second for user %s: %s", | ||||
|             "Error processing command switch_side_start for user %s: %s", | ||||
|             callback_query.from_user.id, | ||||
|             e, | ||||
|         ) | ||||
| @@ -193,89 +192,6 @@ async def switch_side_handler(callback_query: CallbackQuery, state: FSMContext) | ||||
|         await state.clear() | ||||
|  | ||||
|  | ||||
| @router_additional_settings.callback_query(F.data == "switch_side_start") | ||||
| async def switch_side_start(callback_query: CallbackQuery, state: FSMContext) -> None: | ||||
|     """ | ||||
|     Handles the 'switch_side_start' callback query. | ||||
|  | ||||
|     Clears the current FSM state, edits the message text to display the switch side second message, | ||||
|     and shows an inline keyboard for selection. | ||||
|  | ||||
|     Args: | ||||
|         callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard. | ||||
|         state (FSMContext): Finite State Machine context for the current user session. | ||||
|  | ||||
|     Logs: | ||||
|         Success or error messages with user identification. | ||||
|     """ | ||||
|     try: | ||||
|         await state.clear() | ||||
|         await callback_query.message.edit_text( | ||||
|             text="Выберите направление первой сделки:\n\n", reply_markup=kbi.side_for_switch | ||||
|         ) | ||||
|         logger.debug( | ||||
|             "Command switch_side_start processed successfully for user: %s", | ||||
|             callback_query.from_user.id, | ||||
|         ) | ||||
|     except Exception as e: | ||||
|         await callback_query.answer( | ||||
|             text="Произошла ошибка. Пожалуйста, попробуйте позже." | ||||
|         ) | ||||
|         logger.error( | ||||
|             "Error processing command switch_side_start for user %s: %s", | ||||
|             callback_query.from_user.id, | ||||
|             e, | ||||
|         ) | ||||
|  | ||||
|  | ||||
| @router_additional_settings.callback_query(lambda c: c.data == "buy_switch" or c.data == "sell_switch") | ||||
| async def switch_side_handler_2(callback_query: CallbackQuery, state: FSMContext) -> None: | ||||
|     """ | ||||
|     Handles callback queries related to switch side selection. | ||||
|  | ||||
|     Updates FSM context with selected switch side and persists the choice in database. | ||||
|     Sends an acknowledgement to user and clears FSM state afterward. | ||||
|  | ||||
|     Args: | ||||
|         callback_query (CallbackQuery): Incoming callback query indicating selected switch side. | ||||
|         state (FSMContext): Finite State Machine context for the current user session. | ||||
|  | ||||
|     Logs: | ||||
|         Success or error messages with user identification. | ||||
|     """ | ||||
|     try: | ||||
|         if callback_query.data == "sell_switch": | ||||
|             side = "Sell" | ||||
|             side_rus = "Продажа" | ||||
|         else: | ||||
|             side = "Buy" | ||||
|             side_rus = "Покупка" | ||||
|  | ||||
|         req = await rq.set_side( | ||||
|             tg_id=callback_query.from_user.id, side=side | ||||
|         ) | ||||
|  | ||||
|         if not req: | ||||
|             await callback_query.answer( | ||||
|                 text="Произошла ошибка при смене направления. Пожалуйста, попробуйте позже." | ||||
|             ) | ||||
|             return | ||||
|  | ||||
|         await callback_query.answer(text=f"Выбрано: {side_rus}") | ||||
|         logger.debug( | ||||
|             "Switch side changed successfully for user: %s", callback_query.from_user.id | ||||
|         ) | ||||
|     except Exception as e: | ||||
|         await callback_query.answer(text="Произошла ошибка при смене направления.") | ||||
|         logger.error( | ||||
|             "Error processing set switch_side for user %s: %s", | ||||
|             callback_query.from_user.id, | ||||
|             e, | ||||
|         ) | ||||
|     finally: | ||||
|         await state.clear() | ||||
|  | ||||
|  | ||||
| @router_additional_settings.callback_query(F.data == "margin_type") | ||||
| async def settings_for_margin_type( | ||||
|         callback_query: CallbackQuery, state: FSMContext | ||||
| @@ -295,31 +211,6 @@ async def settings_for_margin_type( | ||||
|     """ | ||||
|     try: | ||||
|         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( | ||||
|             text="Выберите тип маржи:\n\n" | ||||
|                  "Примечание: Если у вас есть открытые позиции, то маржа примениться ко всем позициям", | ||||
| @@ -663,8 +554,6 @@ async def set_leverage_handler(message: Message, state: FSMContext) -> None: | ||||
|             risk_percent = 100 / safe_float(leverage_float) | ||||
|             await rq.set_stop_loss_percent( | ||||
|                 tg_id=message.from_user.id, stop_loss_percent=risk_percent) | ||||
|             await rq.set_take_profit_percent( | ||||
|                 tg_id=message.from_user.id, take_profit_percent=risk_percent) | ||||
|             logger.info( | ||||
|                 "User %s set leverage: %s", message.from_user.id, leverage_float | ||||
|             ) | ||||
|   | ||||
| @@ -6,6 +6,7 @@ from aiogram.types import CallbackQuery | ||||
|  | ||||
| import app.telegram.keyboards.inline as kbi | ||||
| import database.request as rq | ||||
| from app.bybit import get_bybit_client | ||||
|  | ||||
| from app.helper_functions import calculate_total_budget, safe_float | ||||
| from logger_helper.logger_helper import LOGGING_CONFIG | ||||
| @@ -25,6 +26,7 @@ async def additional_settings(callback_query: CallbackQuery, state: FSMContext) | ||||
|     try: | ||||
|         await state.clear() | ||||
|         tg_id = callback_query.from_user.id | ||||
|         symbol = await rq.get_user_symbol(tg_id=tg_id) | ||||
|         additional_data = await rq.get_user_additional_settings(tg_id=tg_id) | ||||
|  | ||||
|         if not additional_data: | ||||
| @@ -62,29 +64,34 @@ async def additional_settings(callback_query: CallbackQuery, state: FSMContext) | ||||
|         max_bets = additional_data.max_bets_in_series | ||||
|         quantity = f(additional_data.order_quantity) | ||||
|         trigger_price = f(additional_data.trigger_price) or 0 | ||||
|         side = additional_data.side | ||||
|         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) | ||||
|  | ||||
|         side_map = { | ||||
|             "Buy": "Лонг", | ||||
|             "Sell": "Шорт", | ||||
|         } | ||||
|         side_rus = side_map.get(side, side) | ||||
|         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 = "" | ||||
|         side = "" | ||||
|         if trade_mode == "Switch": | ||||
|             side = f"- Направление первой сделки: {side_rus}\n" | ||||
|             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( | ||||
|             quantity=quantity, | ||||
|             martingale_factor=martingale, | ||||
|             max_steps=max_bets, | ||||
|             commission_fee_percent=total_commission, | ||||
|         ) | ||||
|         text = ( | ||||
|             f"Основные настройки:\n\n" | ||||
|             f"- Режим торговли: {trade_mode_rus}\n" | ||||
|             f"{side}" | ||||
|             f"{switch_side_mode}" | ||||
|             f"- Тип маржи: {margin_type_rus}\n" | ||||
|             f"- Размер кредитного плеча: {leverage:.2f}\n" | ||||
| @@ -92,7 +99,7 @@ async def additional_settings(callback_query: CallbackQuery, state: FSMContext) | ||||
|             f"- Коэффициент мартингейла: {martingale:.2f}\n" | ||||
|             f"- Триггер цена: {trigger_price:.4f} USDT\n" | ||||
|             f"- Максимальное кол-во ставок в серии: {max_bets}\n\n" | ||||
|             f"- Бюджет серии: {total_budget:.2f} USDT\n" | ||||
|             f"- Бюджет серии: {total_budget:.4f} USDT\n" | ||||
|         ) | ||||
|  | ||||
|         keyboard = kbi.get_additional_settings_keyboard(mode=trade_mode) | ||||
| @@ -133,8 +140,8 @@ async def risk_management(callback_query: CallbackQuery, state: FSMContext) -> N | ||||
|  | ||||
|             await callback_query.message.edit_text( | ||||
|                 text=f"Риск-менеджмент:\n\n" | ||||
|                 f"- Процент изменения цены для фиксации прибыли: {take_profit_percent:.2f}%\n" | ||||
|                 f"- Процент изменения цены для фиксации убытка: {stop_loss_percent:.2f}%\n\n" | ||||
|                 f"- Процент изменения цены для фиксации прибыли: {take_profit_percent}%\n" | ||||
|                 f"- Процент изменения цены для фиксации убытка: {stop_loss_percent}%\n\n" | ||||
|                 f"- Комиссия биржи для расчета прибыли: {commission_fee_rus}\n\n", | ||||
|                 reply_markup=kbi.risk_management, | ||||
|             ) | ||||
| @@ -172,9 +179,11 @@ async def conditions(callback_query: CallbackQuery, state: FSMContext) -> None: | ||||
|         ) | ||||
|         if conditional_settings_data: | ||||
|             start_timer = conditional_settings_data.timer_start or 0 | ||||
|             stop_timer = conditional_settings_data.timer_end or 0 | ||||
|             await callback_query.message.edit_text( | ||||
|                 text="Условия торговли:\n\n" | ||||
|                 f"- Таймер для старта: {start_timer} мин.\n", | ||||
|                 f"- Таймер для старта: {start_timer} мин.\n" | ||||
|                 f"- Таймер для остановки: {stop_timer} мин.\n", | ||||
|                 reply_markup=kbi.conditions, | ||||
|             ) | ||||
|             logger.debug( | ||||
|   | ||||
| @@ -7,7 +7,7 @@ from aiogram.types import CallbackQuery | ||||
|  | ||||
| import app.telegram.keyboards.inline as kbi | ||||
| import database.request as rq | ||||
| from app.bybit.get_functions.get_positions import get_active_positions_by_symbol, get_active_orders_by_symbol | ||||
| from app.bybit.get_functions.get_positions import get_active_positions_by_symbol | ||||
| from app.bybit.open_positions import start_trading_cycle | ||||
| from app.helper_functions import safe_float | ||||
| from app.telegram.tasks.tasks import ( | ||||
| @@ -33,7 +33,6 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non | ||||
|     """ | ||||
|     try: | ||||
|         await state.clear() | ||||
|         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( | ||||
|             tg_id=callback_query.from_user.id, symbol=symbol | ||||
| @@ -47,16 +46,7 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non | ||||
|  | ||||
|         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="У вас есть активный ордер по текущей паре", | ||||
|                 text="У вас есть активная позиция", | ||||
|             ) | ||||
|             return | ||||
|  | ||||
| @@ -83,29 +73,22 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non | ||||
|                 symbol=symbol, | ||||
|                 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( | ||||
|                 tg_id=callback_query.from_user.id, | ||||
|             ) | ||||
|  | ||||
|             error_messages = { | ||||
|                 "Limit price is out min price": "Цена лимитного ордера меньше допустимого", | ||||
|                 "Limit price is out max price": "Цена лимитного ордера больше допустимого", | ||||
|                 "Limit price is out min price": "Цена лимитного ордера меньше минимального", | ||||
|                 "Limit price is out max price": "Цена лимитного ордера больше максимального", | ||||
|                 "Risk is too high for this trade": "Риск сделки превышает допустимый убыток", | ||||
|                 "estimated will trigger liq": "Лимитный ордер может вызвать мгновенную ликвидацию. Проверьте параметры ордера.", | ||||
|                 "ab not enough for new order": "Недостаточно средств для создания нового ордера", | ||||
|                 "InvalidRequestError": "Произошла ошибка при запуске торговли.", | ||||
|                 "Order does not meet minimum order value": "Сумма ставки меньше допустимого для запуска торговли. " | ||||
|                                                            "Увеличьте ставку, чтобы запустить торговлю", | ||||
|                 "position idx not match position mode": "Измените режим позиции, чтобы запустить торговлю", | ||||
|                 "Qty invalid": "Некорректное значение ставки для данного инструмента", | ||||
|                 "The number of contracts exceeds maximum limit allowed": "️️Превышен максимальный лимит ставки", | ||||
|                 "The number of contracts exceeds minimum limit allowed": "️️Лимит ставки меньше минимально допустимого", | ||||
|                 "Order does not meet minimum order value": "Сумма ордера не достаточна для запуска торговли", | ||||
|                 "position idx not match position mode": "Ошибка режима позиции для данного инструмента", | ||||
|                 "Qty invalid": "Некорректное значение ордера для данного инструмента", | ||||
|                 "The number of contracts exceeds maximum limit allowed": "️️Количество контрактов превышает допустимое максимальное количество контрактов", | ||||
|                 "The number of contracts exceeds minimum limit allowed": "️️Количество контрактов превышает допустимое минимальное количество контрактов", | ||||
|             } | ||||
|  | ||||
|             if res == "OK": | ||||
| @@ -129,7 +112,7 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non | ||||
|     except Exception as e: | ||||
|         await callback_query.answer(text="Произошла ошибка при запуске торговли") | ||||
|         logger.error( | ||||
|             "Error processing command start_trading for user %s: %s", | ||||
|             "Error processing command long for user %s: %s", | ||||
|             callback_query.from_user.id, | ||||
|             e, | ||||
|         ) | ||||
|   | ||||
| @@ -5,7 +5,6 @@ from aiogram import F, Router | ||||
| from aiogram.fsm.context import FSMContext | ||||
| from aiogram.types import CallbackQuery | ||||
|  | ||||
| from app.bybit.close_positions import close_position_by_symbol | ||||
| import app.telegram.keyboards.inline as kbi | ||||
| import database.request as rq | ||||
| from app.telegram.tasks.tasks import add_stop_task, cancel_stop_task | ||||
| @@ -28,7 +27,6 @@ async def stop_all_trading(callback_query: CallbackQuery, state: FSMContext): | ||||
|             tg_id=callback_query.from_user.id | ||||
|         ) | ||||
|         timer_end = conditional_data.timer_end | ||||
|         symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id) | ||||
|  | ||||
|         async def delay_start(): | ||||
|             if timer_end > 0: | ||||
| @@ -39,21 +37,30 @@ 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 asyncio.sleep(timer_end * 60) | ||||
|  | ||||
|             user_auto_trading = await rq.get_user_auto_trading( | ||||
|                 tg_id=callback_query.from_user.id, symbol=symbol | ||||
|             user_auto_trading_list = await rq.get_all_user_auto_trading( | ||||
|                 tg_id=callback_query.from_user.id | ||||
|             ) | ||||
|  | ||||
|             if user_auto_trading and user_auto_trading.auto_trading: | ||||
|                 await rq.set_auto_trading( | ||||
|                     tg_id=callback_query.from_user.id, | ||||
|                     symbol=symbol, | ||||
|                     auto_trading=False, | ||||
|             if any(item.auto_trading for item in user_auto_trading_list): | ||||
|                 for active_auto_trading in user_auto_trading_list: | ||||
|                     if active_auto_trading.auto_trading: | ||||
|                         symbol = active_auto_trading.symbol | ||||
|                         req = await rq.set_auto_trading( | ||||
|                             tg_id=callback_query.from_user.id, | ||||
|                             symbol=symbol, | ||||
|                             auto_trading=False, | ||||
|                         ) | ||||
|                         if not req: | ||||
|                             await callback_query.message.edit_text( | ||||
|                                 text="Произошла ошибка при остановке торговли", | ||||
|                                 reply_markup=kbi.profile_bybit, | ||||
|                             ) | ||||
|                             return | ||||
|                 await callback_query.message.edit_text( | ||||
|                     text="Торговля остановлена", reply_markup=kbi.profile_bybit | ||||
|                 ) | ||||
|                 await close_position_by_symbol( | ||||
|                     tg_id=callback_query.from_user.id, symbol=symbol) | ||||
|                 await callback_query.message.edit_text(text=f"Торговля для {symbol} остановлена", reply_markup=kbi.profile_bybit) | ||||
|             else: | ||||
|                 await callback_query.message.edit_text(text=f"Нет активной торговли для {symbol}", reply_markup=kbi.profile_bybit) | ||||
|                 await callback_query.message.edit_text(text="Нет активной торговли") | ||||
|  | ||||
|         task = asyncio.create_task(delay_start()) | ||||
|         await add_stop_task(user_id=callback_query.from_user.id, task=task) | ||||
|   | ||||
| @@ -36,7 +36,6 @@ main_menu = InlineKeyboardMarkup( | ||||
|             ) | ||||
|         ], | ||||
|         [InlineKeyboardButton(text="Начать торговлю", callback_data="start_trading")], | ||||
|         [InlineKeyboardButton(text="Остановить торговлю", callback_data="stop_trading")], | ||||
|     ] | ||||
| ) | ||||
|  | ||||
| @@ -94,10 +93,7 @@ def get_additional_settings_keyboard(mode: str | ||||
|  | ||||
|     if mode == "Switch": | ||||
|         buttons.append( | ||||
|             [InlineKeyboardButton(text="Направление первой сделки первой серии", callback_data="switch_side_start")] | ||||
|         ) | ||||
|         buttons.append( | ||||
|             [InlineKeyboardButton(text="Направление первой сделки последующих серии", callback_data="switch_side_second")] | ||||
|             [InlineKeyboardButton(text="Направление первой сделки", callback_data="switch_side_start")] | ||||
|         ) | ||||
|  | ||||
|     buttons.append( | ||||
| @@ -151,19 +147,6 @@ switch_side = InlineKeyboardMarkup( | ||||
|     ] | ||||
| ) | ||||
|  | ||||
| side_for_switch = InlineKeyboardMarkup( | ||||
|     inline_keyboard=[ | ||||
|         [ | ||||
|             InlineKeyboardButton(text="Лонг", callback_data="buy_switch"), | ||||
|             InlineKeyboardButton(text="Шорт", callback_data="sell_switch"), | ||||
|         ], | ||||
|         [ | ||||
|             InlineKeyboardButton(text="Назад", callback_data="additional_settings"), | ||||
|             InlineKeyboardButton(text="На главную", callback_data="profile_bybit"), | ||||
|         ], | ||||
|     ] | ||||
| ) | ||||
|  | ||||
| margin_type = InlineKeyboardMarkup( | ||||
|     inline_keyboard=[ | ||||
|         [ | ||||
| @@ -244,6 +227,9 @@ conditions = InlineKeyboardMarkup( | ||||
|     inline_keyboard=[ | ||||
|         [ | ||||
|             InlineKeyboardButton(text="Таймер для старта", callback_data="start_timer"), | ||||
|             InlineKeyboardButton( | ||||
|                 text="Таймер для остановки", callback_data="stop_timer" | ||||
|             ), | ||||
|         ], | ||||
|         [ | ||||
|             InlineKeyboardButton(text="Назад", callback_data="main_settings"), | ||||
|   | ||||
| @@ -1,10 +1,8 @@ | ||||
| from aiogram.types import KeyboardButton, ReplyKeyboardMarkup | ||||
|  | ||||
| profile = ReplyKeyboardMarkup( | ||||
|     keyboard=[ | ||||
|         [KeyboardButton(text="Панель Bybit"), KeyboardButton(text="Профиль")], | ||||
|         [KeyboardButton(text="Подключить платформу Bybit")], | ||||
|     ], | ||||
|     keyboard=[[KeyboardButton(text="Панель Bybit"), KeyboardButton(text="Профиль")], | ||||
|               [KeyboardButton(text="Подключить платформу Bybit")]], | ||||
|     resize_keyboard=True, | ||||
|     one_time_keyboard=True, | ||||
|     input_field_placeholder="Выберите пункт меню...", | ||||
|   | ||||
							
								
								
									
										25
									
								
								config.py
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								config.py
									
									
									
									
									
								
							| @@ -1,8 +1,31 @@ | ||||
| import os | ||||
| 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() | ||||
|  | ||||
| if 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}" | ||||
| @@ -1,38 +1,19 @@ | ||||
| from database.models import Base, User, UserAdditionalSettings, UserApi, UserConditionalSettings, UserDeals, \ | ||||
|     UserRiskManagement, UserSymbol | ||||
| import logging.config | ||||
| from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession | ||||
| from sqlalchemy import event | ||||
| from pathlib import Path | ||||
|  | ||||
| from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine, AsyncSession | ||||
|  | ||||
| from database.models import Base | ||||
|  | ||||
| from config import DATABASE_URL | ||||
| from logger_helper.logger_helper import LOGGING_CONFIG | ||||
|  | ||||
| logging.config.dictConfig(LOGGING_CONFIG) | ||||
| logger = logging.getLogger("database") | ||||
|  | ||||
| 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_engine = create_async_engine(DATABASE_URL, echo=False) | ||||
|  | ||||
| async_session = async_sessionmaker( | ||||
|     async_engine, | ||||
|     class_=AsyncSession, | ||||
|     expire_on_commit=False | ||||
|     async_engine, class_=AsyncSession, expire_on_commit=False | ||||
| ) | ||||
|  | ||||
|  | ||||
| @@ -42,4 +23,4 @@ async def init_db(): | ||||
|             await conn.run_sync(Base.metadata.create_all) | ||||
|         logger.info("Database initialized.") | ||||
|     except Exception as e: | ||||
|         logger.error("Database initialization failed: %s", e) | ||||
|         logger.error("Database initialization failed: %s", e, exc_info=True) | ||||
|   | ||||
| @@ -1,6 +1,15 @@ | ||||
| from sqlalchemy.ext.declarative import declarative_base | ||||
| from sqlalchemy.ext.asyncio import AsyncAttrs | ||||
| from sqlalchemy import Column, ForeignKey, Integer, String, Float, Boolean, UniqueConstraint | ||||
| from sqlalchemy import ( | ||||
|     Column, | ||||
|     ForeignKey, | ||||
|     Integer, | ||||
|     String, | ||||
|     BigInteger, | ||||
|     Float, | ||||
|     Boolean, | ||||
|     UniqueConstraint, | ||||
| ) | ||||
| from sqlalchemy.orm import relationship | ||||
|  | ||||
| Base = declarative_base(cls=AsyncAttrs) | ||||
| @@ -8,61 +17,77 @@ Base = declarative_base(cls=AsyncAttrs) | ||||
|  | ||||
| class User(Base): | ||||
|     """User model.""" | ||||
|  | ||||
|     __tablename__ = "users" | ||||
|  | ||||
|     id = Column(Integer, primary_key=True, autoincrement=True) | ||||
|     tg_id = Column(Integer, nullable=False, unique=True) | ||||
|     tg_id = Column(BigInteger, nullable=False, unique=True) | ||||
|     username = Column(String, nullable=False) | ||||
|  | ||||
|     user_api = relationship("UserApi", | ||||
|                             back_populates="user", | ||||
|                             cascade="all, delete-orphan", | ||||
|                             passive_deletes=True, | ||||
|                             uselist=False) | ||||
|     user_api = relationship( | ||||
|         "UserApi", | ||||
|         back_populates="user", | ||||
|         cascade="all, delete-orphan", | ||||
|         passive_deletes=True, | ||||
|         uselist=False, | ||||
|     ) | ||||
|  | ||||
|     user_symbol = relationship("UserSymbol", | ||||
|                                back_populates="user", | ||||
|                                cascade="all, delete-orphan", | ||||
|                                passive_deletes=True, | ||||
|                                uselist=False) | ||||
|     user_symbol = relationship( | ||||
|         "UserSymbol", | ||||
|         back_populates="user", | ||||
|         cascade="all, delete-orphan", | ||||
|         passive_deletes=True, | ||||
|         uselist=False, | ||||
|     ) | ||||
|  | ||||
|     user_additional_settings = relationship("UserAdditionalSettings", | ||||
|                                             back_populates="user", | ||||
|                                             cascade="all, delete-orphan", | ||||
|                                             passive_deletes=True, | ||||
|                                             uselist=False) | ||||
|     user_additional_settings = relationship( | ||||
|         "UserAdditionalSettings", | ||||
|         back_populates="user", | ||||
|         cascade="all, delete-orphan", | ||||
|         passive_deletes=True, | ||||
|         uselist=False, | ||||
|     ) | ||||
|  | ||||
|     user_risk_management = relationship("UserRiskManagement", | ||||
|                                         back_populates="user", | ||||
|                                         cascade="all, delete-orphan", | ||||
|                                         passive_deletes=True, | ||||
|                                         uselist=False) | ||||
|     user_risk_management = relationship( | ||||
|         "UserRiskManagement", | ||||
|         back_populates="user", | ||||
|         cascade="all, delete-orphan", | ||||
|         passive_deletes=True, | ||||
|         uselist=False, | ||||
|     ) | ||||
|  | ||||
|     user_conditional_settings = relationship("UserConditionalSettings", | ||||
|                                              back_populates="user", | ||||
|                                              cascade="all, delete-orphan", | ||||
|                                              passive_deletes=True, | ||||
|                                              uselist=False) | ||||
|     user_conditional_settings = relationship( | ||||
|         "UserConditionalSettings", | ||||
|         back_populates="user", | ||||
|         cascade="all, delete-orphan", | ||||
|         passive_deletes=True, | ||||
|         uselist=False, | ||||
|     ) | ||||
|  | ||||
|     user_deals = relationship("UserDeals", | ||||
|                               back_populates="user", | ||||
|                               cascade="all, delete-orphan", | ||||
|                               passive_deletes=True) | ||||
|     user_deals = relationship( | ||||
|         "UserDeals", | ||||
|         back_populates="user", | ||||
|         cascade="all, delete-orphan", | ||||
|         passive_deletes=True, | ||||
|     ) | ||||
|  | ||||
|     user_auto_trading = relationship("UserAutoTrading", | ||||
|                                      back_populates="user", | ||||
|                                      cascade="all, delete-orphan", | ||||
|                                      passive_deletes=True) | ||||
|     user_auto_trading = relationship( | ||||
|         "UserAutoTrading", | ||||
|         back_populates="user", | ||||
|         cascade="all, delete-orphan", | ||||
|         passive_deletes=True, | ||||
|     ) | ||||
|  | ||||
|  | ||||
| class UserApi(Base): | ||||
|     """User API model.""" | ||||
|  | ||||
|     __tablename__ = "user_api" | ||||
|  | ||||
|     id = Column(Integer, primary_key=True, autoincrement=True) | ||||
|     user_id = Column(Integer, | ||||
|                      ForeignKey("users.id", ondelete="CASCADE"), | ||||
|                      nullable=False, unique=True) | ||||
|     user_id = Column( | ||||
|         Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, unique=True | ||||
|     ) | ||||
|     api_key = Column(String, nullable=False) | ||||
|     api_secret = Column(String, nullable=False) | ||||
|  | ||||
| @@ -71,12 +96,13 @@ class UserApi(Base): | ||||
|  | ||||
| class UserSymbol(Base): | ||||
|     """User symbol model.""" | ||||
|  | ||||
|     __tablename__ = "user_symbol" | ||||
|  | ||||
|     id = Column(Integer, primary_key=True, autoincrement=True) | ||||
|     user_id = Column(Integer, | ||||
|                      ForeignKey("users.id", ondelete="CASCADE"), | ||||
|                      nullable=False, unique=True) | ||||
|     user_id = Column( | ||||
|         Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, unique=True | ||||
|     ) | ||||
|     symbol = Column(String, nullable=False, default="BTCUSDT") | ||||
|  | ||||
|     user = relationship("User", back_populates="user_symbol") | ||||
| @@ -84,15 +110,15 @@ class UserSymbol(Base): | ||||
|  | ||||
| class UserAdditionalSettings(Base): | ||||
|     """User additional settings model.""" | ||||
|  | ||||
|     __tablename__ = "user_additional_settings" | ||||
|  | ||||
|     id = Column(Integer, primary_key=True, autoincrement=True) | ||||
|     user_id = Column(Integer, | ||||
|                      ForeignKey("users.id", ondelete="CASCADE"), | ||||
|                      nullable=False, unique=True) | ||||
|     user_id = Column( | ||||
|         Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, unique=True | ||||
|     ) | ||||
|     trade_mode = Column(String, nullable=False, default="Merged_Single") | ||||
|     switch_side = Column(String, nullable=False, default="По направлению") | ||||
|     side = Column(String, nullable=False, default="Buy") | ||||
|     trigger_price = Column(Float, nullable=False, default=0.0) | ||||
|     margin_type = Column(String, nullable=False, default="ISOLATED_MARGIN") | ||||
|     leverage = Column(String, nullable=False, default="10") | ||||
| @@ -105,12 +131,13 @@ class UserAdditionalSettings(Base): | ||||
|  | ||||
| class UserRiskManagement(Base): | ||||
|     """User risk management model.""" | ||||
|  | ||||
|     __tablename__ = "user_risk_management" | ||||
|  | ||||
|     id = Column(Integer, primary_key=True, autoincrement=True) | ||||
|     user_id = Column(Integer, | ||||
|                      ForeignKey("users.id", ondelete="CASCADE"), | ||||
|                      nullable=False, unique=True) | ||||
|     user_id = Column( | ||||
|         Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, unique=True | ||||
|     ) | ||||
|     take_profit_percent = Column(Float, nullable=False, default=1) | ||||
|     stop_loss_percent = Column(Float, nullable=False, default=1) | ||||
|     commission_fee = Column(String, nullable=False, default="Yes_commission_fee") | ||||
| @@ -120,12 +147,13 @@ class UserRiskManagement(Base): | ||||
|  | ||||
| class UserConditionalSettings(Base): | ||||
|     """User conditional settings model.""" | ||||
|  | ||||
|     __tablename__ = "user_conditional_settings" | ||||
|  | ||||
|     id = Column(Integer, primary_key=True, autoincrement=True) | ||||
|     user_id = Column(Integer, | ||||
|                      ForeignKey("users.id", ondelete="CASCADE"), | ||||
|                      nullable=False, unique=True) | ||||
|     user_id = Column( | ||||
|         Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, unique=True | ||||
|     ) | ||||
|  | ||||
|     timer_start = Column(Integer, nullable=False, default=0) | ||||
|     timer_end = Column(Integer, nullable=False, default=0) | ||||
| @@ -135,16 +163,16 @@ class UserConditionalSettings(Base): | ||||
|  | ||||
| class UserDeals(Base): | ||||
|     """User deals model.""" | ||||
|  | ||||
|     __tablename__ = "user_deals" | ||||
|  | ||||
|     id = Column(Integer, primary_key=True, autoincrement=True) | ||||
|     user_id = Column(Integer, | ||||
|                      ForeignKey("users.id", ondelete="CASCADE"), | ||||
|                      nullable=False) | ||||
|     user_id = Column( | ||||
|         Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False | ||||
|     ) | ||||
|     current_step = Column(Integer, nullable=True) | ||||
|     symbol = Column(String, nullable=True) | ||||
|     trade_mode = Column(String, nullable=True) | ||||
|     side_mode = Column(String, nullable=True) | ||||
|     base_quantity = Column(Float, nullable=True) | ||||
|     margin_type = Column(String, nullable=True) | ||||
|     leverage = Column(String, nullable=True) | ||||
| @@ -159,19 +187,18 @@ class UserDeals(Base): | ||||
|  | ||||
|     user = relationship("User", back_populates="user_deals") | ||||
|  | ||||
|     __table_args__ = ( | ||||
|         UniqueConstraint('user_id', 'symbol', name='uq_user_symbol'), | ||||
|     ) | ||||
|     __table_args__ = (UniqueConstraint("user_id", "symbol", name="uq_user_symbol"),) | ||||
|  | ||||
|  | ||||
| class UserAutoTrading(Base): | ||||
|     """User auto trading model.""" | ||||
|  | ||||
|     __tablename__ = "user_auto_trading" | ||||
|  | ||||
|     id = Column(Integer, primary_key=True, autoincrement=True) | ||||
|     user_id = Column(Integer, | ||||
|                      ForeignKey("users.id", ondelete="CASCADE"), | ||||
|                      nullable=False) | ||||
|     user_id = Column( | ||||
|         Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False | ||||
|     ) | ||||
|     symbol = Column(String, nullable=True) | ||||
|     auto_trading = Column(Boolean, nullable=True) | ||||
|     fee = Column(Float, nullable=True) | ||||
|   | ||||
| @@ -358,45 +358,6 @@ async def set_switch_side(tg_id: int, switch_side: str) -> bool: | ||||
|         return False | ||||
|  | ||||
|  | ||||
| async def set_side(tg_id: int, side: str) -> bool: | ||||
|     """ | ||||
|     Set side for a user in the database. | ||||
|     :param tg_id: Telegram user ID | ||||
|     :param side: "BUY" or "SELL" | ||||
|     :return: True if successful, False otherwise | ||||
|     """ | ||||
|     try: | ||||
|         async with async_session() as session: | ||||
|             result = await session.execute( | ||||
|                 select(User) | ||||
|                 .options(joinedload(User.user_additional_settings)) | ||||
|                 .filter_by(tg_id=tg_id) | ||||
|             ) | ||||
|             user = result.scalars().first() | ||||
|  | ||||
|             if user: | ||||
|                 if user.user_additional_settings: | ||||
|                     # Updating existing record | ||||
|                     user.user_additional_settings.side = side | ||||
|                 else: | ||||
|                     # Creating new record | ||||
|                     user_additional_settings = UserAdditionalSettings( | ||||
|                         side=side, | ||||
|                         user=user, | ||||
|                     ) | ||||
|                     session.add(user_additional_settings) | ||||
|  | ||||
|                 await session.commit() | ||||
|                 logger.info("User side updated for user: %s", tg_id) | ||||
|                 return True | ||||
|             else: | ||||
|                 logger.error("User not found with tg_id: %s", tg_id) | ||||
|                 return False | ||||
|     except Exception as e: | ||||
|         logger.error("Error adding/updating user side for user %s: %s", tg_id, e) | ||||
|         return False | ||||
|  | ||||
|  | ||||
| async def set_leverage(tg_id: int, leverage: str) -> bool: | ||||
|     """ | ||||
|     Set leverage for a user in the database. | ||||
| @@ -932,28 +893,28 @@ async def set_stop_timer(tg_id: int, timer_end: int) -> bool: | ||||
|  | ||||
| # USER DEALS | ||||
| async def set_user_deal( | ||||
|         tg_id: int, | ||||
|         symbol: str, | ||||
|         current_step: int, | ||||
|         trade_mode: str, | ||||
|         side_mode: str, | ||||
|         margin_type: str, | ||||
|         leverage: str, | ||||
|         order_quantity: float, | ||||
|         trigger_price: float, | ||||
|         martingale_factor: float, | ||||
|         max_bets_in_series: int, | ||||
|         take_profit_percent: int, | ||||
|         stop_loss_percent: int, | ||||
|         base_quantity: float | ||||
|     tg_id: int, | ||||
|     symbol: str, | ||||
|     last_side: str, | ||||
|     current_step: int, | ||||
|     trade_mode: str, | ||||
|     margin_type: str, | ||||
|     leverage: str, | ||||
|     order_quantity: float, | ||||
|     trigger_price: float, | ||||
|     martingale_factor: float, | ||||
|     max_bets_in_series: int, | ||||
|     take_profit_percent: int, | ||||
|     stop_loss_percent: int, | ||||
|     base_quantity: float, | ||||
| ): | ||||
|     """ | ||||
|     Set the user deal in the database. | ||||
|     :param tg_id: Telegram user ID | ||||
|     :param symbol: Symbol | ||||
|     :param last_side: Last side | ||||
|     :param current_step: Current step | ||||
|     :param trade_mode: Trade mode | ||||
|     :param side_mode: Side mode | ||||
|     :param margin_type: Margin type | ||||
|     :param leverage: Leverage | ||||
|     :param order_quantity: Order quantity | ||||
| @@ -980,9 +941,9 @@ async def set_user_deal( | ||||
|  | ||||
|             if deal: | ||||
|                 # Updating existing record | ||||
|                 deal.last_side = last_side | ||||
|                 deal.current_step = current_step | ||||
|                 deal.trade_mode = trade_mode | ||||
|                 deal.side_mode = side_mode | ||||
|                 deal.margin_type = margin_type | ||||
|                 deal.leverage = leverage | ||||
|                 deal.order_quantity = order_quantity | ||||
| @@ -997,9 +958,9 @@ async def set_user_deal( | ||||
|                 new_deal = UserDeals( | ||||
|                     user=user, | ||||
|                     symbol=symbol, | ||||
|                     last_side=last_side, | ||||
|                     current_step=current_step, | ||||
|                     trade_mode=trade_mode, | ||||
|                     side_mode=side_mode, | ||||
|                     margin_type=margin_type, | ||||
|                     leverage=leverage, | ||||
|                     order_quantity=order_quantity, | ||||
| @@ -1008,7 +969,7 @@ async def set_user_deal( | ||||
|                     max_bets_in_series=max_bets_in_series, | ||||
|                     take_profit_percent=take_profit_percent, | ||||
|                     stop_loss_percent=stop_loss_percent, | ||||
|                     base_quantity=base_quantity | ||||
|                     base_quantity=base_quantity, | ||||
|                 ) | ||||
|                 session.add(new_deal) | ||||
|  | ||||
| @@ -1017,7 +978,9 @@ async def set_user_deal( | ||||
|             return True | ||||
|  | ||||
|     except Exception as e: | ||||
|         logger.error("Error setting user deal for user %s and symbol %s: %s", tg_id, symbol, e) | ||||
|         logger.error( | ||||
|             "Error setting user deal for user %s and symbol %s: %s", tg_id, symbol, e | ||||
|         ) | ||||
|         return False | ||||
|  | ||||
|  | ||||
| @@ -1037,7 +1000,9 @@ async def get_user_deal_by_symbol(tg_id: int, symbol: str): | ||||
|             deal = result_deal.scalars().first() | ||||
|             return deal | ||||
|     except Exception as e: | ||||
|         logger.error("Error getting deal for user %s and symbol %s: %s", tg_id, symbol, e) | ||||
|         logger.error( | ||||
|             "Error getting deal for user %s and symbol %s: %s", tg_id, symbol, e | ||||
|         ) | ||||
|         return None | ||||
|  | ||||
|  | ||||
| @@ -1079,46 +1044,26 @@ async def set_fee_user_deal_by_symbol(tg_id: int, symbol: str, fee: float): | ||||
|             if record: | ||||
|                 record.fee = fee | ||||
|             else: | ||||
|                 logger.error(f"User deal with user_id={user.id} and symbol={symbol} not found") | ||||
|                 logger.error( | ||||
|                     f"User deal with user_id={user.id} and symbol={symbol} not found" | ||||
|                 ) | ||||
|                 return False | ||||
|             await session.commit() | ||||
|             logger.info("Set fee for user %s and symbol %s", tg_id, symbol) | ||||
|             return True | ||||
|     except Exception as e: | ||||
|         logger.error("Error setting user deal fee for user %s and symbol %s: %s", tg_id, symbol, e) | ||||
|         return False | ||||
|  | ||||
|  | ||||
| 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) | ||||
|         logger.error( | ||||
|             "Error setting user deal fee for user %s and symbol %s: %s", | ||||
|             tg_id, | ||||
|             symbol, | ||||
|             e, | ||||
|         ) | ||||
|         return False | ||||
|  | ||||
|  | ||||
| # USER AUTO TRADING | ||||
|  | ||||
|  | ||||
| async def get_all_user_auto_trading(tg_id: int): | ||||
|     """Get all user auto trading from the database asynchronously.""" | ||||
|     try: | ||||
| @@ -1153,7 +1098,9 @@ async def get_user_auto_trading(tg_id: int, symbol: str): | ||||
|             auto_trading = result_auto_trading.scalars().first() | ||||
|             return auto_trading | ||||
|     except Exception as e: | ||||
|         logger.error("Error getting auto trading for user %s and symbol %s: %s", tg_id, symbol, e) | ||||
|         logger.error( | ||||
|             "Error getting auto trading for user %s and symbol %s: %s", tg_id, symbol, e | ||||
|         ) | ||||
|         return None | ||||
|  | ||||
|  | ||||
| @@ -1187,10 +1134,17 @@ async def set_auto_trading(tg_id: int, symbol: str, auto_trading: bool) -> bool: | ||||
|                 ) | ||||
|                 session.add(new_record) | ||||
|             await session.commit() | ||||
|             logger.info("Set auto_trading=%s for user %s and symbol %s", auto_trading, tg_id, symbol) | ||||
|             logger.info( | ||||
|                 "Set auto_trading=%s for user %s and symbol %s", | ||||
|                 auto_trading, | ||||
|                 tg_id, | ||||
|                 symbol, | ||||
|             ) | ||||
|             return True | ||||
|     except Exception as e: | ||||
|         logger.error("Error setting auto_trading for user %s and symbol %s: %s", tg_id, symbol, e) | ||||
|         logger.error( | ||||
|             "Error setting auto_trading for user %s and symbol %s: %s", tg_id, symbol, e | ||||
|         ) | ||||
|         return False | ||||
|  | ||||
|  | ||||
| @@ -1228,11 +1182,18 @@ async def set_fee_user_auto_trading(tg_id: int, symbol: str, fee: float) -> bool | ||||
|             logger.info("Set fee for user %s and symbol %s", tg_id, symbol) | ||||
|             return True | ||||
|     except Exception as e: | ||||
|         logger.error("Error setting user auto trading fee for user %s and symbol %s: %s", tg_id, symbol, e) | ||||
|         logger.error( | ||||
|             "Error setting user auto trading fee for user %s and symbol %s: %s", | ||||
|             tg_id, | ||||
|             symbol, | ||||
|             e, | ||||
|         ) | ||||
|         return False | ||||
|  | ||||
|  | ||||
| async def set_total_fee_user_auto_trading(tg_id: int, symbol: str, total_fee: float) -> bool: | ||||
| async def set_total_fee_user_auto_trading( | ||||
|     tg_id: int, symbol: str, total_fee: float | ||||
| ) -> bool: | ||||
|     """ | ||||
|     Set the total fee for a user auto trading in the database. | ||||
|     :param tg_id: Telegram user ID | ||||
| @@ -1266,5 +1227,10 @@ async def set_total_fee_user_auto_trading(tg_id: int, symbol: str, total_fee: fl | ||||
|             logger.info("Set total fee for user %s and symbol %s", tg_id, symbol) | ||||
|             return True | ||||
|     except Exception as e: | ||||
|         logger.error("Error setting user auto trading total fee for user %s and symbol %s: %s", tg_id, symbol, e) | ||||
|         logger.error( | ||||
|             "Error setting user auto trading total fee for user %s and symbol %s: %s", | ||||
|             tg_id, | ||||
|             symbol, | ||||
|             e, | ||||
|         ) | ||||
|         return False | ||||
|   | ||||
							
								
								
									
										3
									
								
								run.py
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								run.py
									
									
									
									
									
								
							| @@ -5,10 +5,10 @@ import logging.config | ||||
| from aiogram import Bot, Dispatcher | ||||
| from aiogram.fsm.storage.redis import RedisStorage | ||||
|  | ||||
| from database import init_db | ||||
| from app.bybit.web_socket import WebSocketBot | ||||
| from app.telegram.handlers import router | ||||
| from config import BOT_TOKEN | ||||
| from database import init_db | ||||
| from logger_helper.logger_helper import LOGGING_CONFIG | ||||
|  | ||||
| logging.config.dictConfig(LOGGING_CONFIG) | ||||
| @@ -46,6 +46,7 @@ async def main(): | ||||
|             with contextlib.suppress(asyncio.CancelledError): | ||||
|                 await ws_task | ||||
|                 await tg_task | ||||
|             await web_socket.clear_user_sockets() | ||||
|  | ||||
|     except Exception as e: | ||||
|         logger.error("Bot stopped with error: %s", e) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user