2
0
forked from kodorvan/stcs

49 Commits

Author SHA1 Message Date
algizn97
ddfa3a7360 Fixed the switch trading mode, adjusted the take profit, added a trading cycle 2025-10-22 17:15:25 +05:00
algizn97
e61b7334a4 The formula for calculating the number of contracts by price has been changed 2025-10-21 13:59:09 +05:00
algizn97
5ad69f3f6d Fixed take profit calculation. Added a position check for the current pair when trying to change the margin. 2025-10-18 13:52:20 +05:00
algizn97
abad01352a Fixed the output 2025-10-17 11:28:57 +05:00
algizn97
720b30d681 Redundant call removed 2025-10-17 11:13:31 +05:00
algizn97
3616e2cbd3 Added verification for open orders. Adjusted responses for the user 2025-10-17 11:12:50 +05:00
algizn97
7d108337fa Fixed receiving the commission and calculating the total commission 2025-10-17 11:10:35 +05:00
algizn97
0f6e6a2168 Added position mode setting, fixed stop loss calculation 2025-10-17 11:09:21 +05:00
algizn97
258ed970f1 Fixed database creation 2025-10-12 15:08:27 +05:00
algizn97
a3a6509933 Fixed database creation 2025-10-12 15:05:50 +05:00
algizn97
8251938b2f The database has been converted to SQLite 2025-10-12 12:34:32 +05:00
algizn97
458b34fcec The instruction has been corrected 2025-10-11 14:14:27 +05:00
algizn97
4a7577b977 Added migrations for the database 2025-10-11 13:36:38 +05:00
algizn97
6e0a170f4b Merge branch 'devel' of https://git.svoboda.works/Alex/stcs into devel 2025-10-10 15:21:09 +05:00
algizn97
c7b4a08a6a Merge branch 'stable' of https://git.svoboda.works/Alex/stcs into devel 2025-10-10 15:19:31 +05:00
algizn97
d0971f59b4 Added environments 2025-10-10 14:42:47 +05:00
b92376d2da merge upstream 2025-10-10 16:35:16 +07:00
630f2002d3 Удалить alembic.ini 2025-10-10 16:24:01 +07:00
0784cbb54a Удалить alembic/versions/fd8581c0cc87_updated_leverage.py 2025-10-10 16:23:51 +07:00
eeb7f81440 Удалить alembic/versions/f00a94ccdf01_updated_deals.py 2025-10-10 16:23:47 +07:00
b03d05bb75 Удалить alembic/versions/ef38c90eed55_added_last_side_the_conditional_data.py 2025-10-10 16:23:43 +07:00
e0e4ad5d4b Удалить alembic/versions/ef342b38e17b_added_fee_user_deals.py 2025-10-10 16:23:39 +07:00
fab8ff5040 Удалить alembic/versions/dbffe818030c_added_last_side_and_auto_trading_for_.py 2025-10-10 16:23:35 +07:00
8071f8c896 Удалить alembic/versions/d3c85bad8c98_added_limit_and_trigger_price_for_user_.py 2025-10-10 16:23:30 +07:00
3db001bd19 Удалить alembic/versions/ccdc5764eb4f_added_userdeals.py 2025-10-10 16:23:26 +07:00
99c59be9ed Удалить alembic/versions/acbcc95de48d_updated_userdeals.py 2025-10-10 16:23:21 +07:00
37b7b6effd Удалить alembic/versions/c98b9dc36d15_fixed_auto_trade.py 2025-10-10 16:23:17 +07:00
ee285523f2 Удалить alembic/versions/8f1476c68efa_added_position_idx_for_user_deals_table.py 2025-10-10 16:23:12 +07:00
b426eb2136 Удалить alembic/versions/968f8121104f_updated_user_deals_and_user_conditional_.py 2025-10-10 16:23:07 +07:00
2df3b8b40d Удалить alembic/versions/c710f4e2259c_unnecessary_data_has_been_deleted.py 2025-10-10 16:23:03 +07:00
8c08451d82 Удалить alembic/versions/863d6215e1eb_updated_deals.py 2025-10-10 16:22:52 +07:00
d81a47b669 Удалить alembic/versions/77197715747c_deleted_position_idx_for_user_deals_.py 2025-10-10 16:22:48 +07:00
2cdfba3537 Удалить alembic/versions/73a00faa4f7f_added_user_auto_trading_table.py 2025-10-10 16:22:43 +07:00
c89c2ad803 Удалить alembic/versions/70094ba27e80_create_user_conditional_setting.py 2025-10-10 16:22:39 +07:00
3986989dbd Удалить alembic/versions/42c66cfe8d4e_updated_martingale_factor.py 2025-10-10 16:22:35 +07:00
c0e40dc205 Удалить alembic/versions/3534adf891fc_update_last_side_the_conditional_data.py 2025-10-10 16:22:30 +07:00
6c6f0dbb7b Удалить alembic/versions/10bf073c71f9_added_fee_for_user_auto_trading.py 2025-10-10 16:22:26 +07:00
44c4fde036 Удалить alembic/versions/45977e9d8558_updated_order_quantity.py 2025-10-10 16:22:21 +07:00
21a93d47d4 Удалить alembic/versions/2b9572b49ecd_added_side_for_user_auto_trading.py 2025-10-10 16:22:18 +07:00
3f43d42651 Удалить alembic/versions/0eed68eddcdb_added_conditional_order_type.py 2025-10-10 16:22:13 +07:00
aab05994ce Удалить alembic/versions/09db71875980_updated_user_deals_table.py 2025-10-10 16:22:08 +07:00
a58ebe6a46 Удалить alembic/README 2025-10-10 16:21:38 +07:00
1ec1f1784d Удалить alembic/script.py.mako 2025-10-10 16:21:33 +07:00
7901af86af Удалить alembic/env.py 2025-10-10 16:21:24 +07:00
fedfa00c10 Merge pull request 'devel' (#3) from devel into stable
Reviewed-on: #3
2025-10-10 16:18:23 +07:00
898ff91392 Merge pull request 'added the exc_info flag' (#10) from Alex/stcs:dev into stable
Reviewed-on: kodorvan/stcs#10
2025-10-07 12:10:58 +07:00
2047dd5ac6 Merge pull request 'dev' (#9) from Alex/stcs:dev into stable
Reviewed-on: kodorvan/stcs#9
2025-10-06 21:33:56 +07:00
fec367cc1d Merge pull request 'dev' (#8) from Alex/stcs:dev into stable
Reviewed-on: kodorvan/stcs#8
2025-09-19 17:17:25 +07:00
aebcc9dff2 Merge pull request 'dev' (#7) from Alex/stcs:dev into stable
Reviewed-on: kodorvan/stcs#7
2025-09-18 22:49:55 +07:00
41 changed files with 417 additions and 1156 deletions

View File

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

3
.gitignore vendored
View File

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

View File

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

View File

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

View File

@@ -1,73 +1,29 @@
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
# 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.
"""
def do_run_migrations(connection):
context.configure(
url=DATABASE_URL,
connection=connection,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
compare_type=True,
)
with context.begin_transaction():
context.run_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.
"""
async def run_async_migrations():
connectable = async_engine_from_config(
config.get_section(config.config_ini_section, {}),
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
@@ -77,13 +33,20 @@ async def run_async_migrations() -> None:
await connectable.dispose()
def run_migrations_offline():
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online() -> None:
"""Run migrations in 'online' mode."""
def run_migrations_online():
asyncio.run(run_async_migrations())
if context.is_offline_mode():
run_migrations_offline()
else:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,50 +0,0 @@
"""Added UserDeals
Revision ID: ccdc5764eb4f
Revises: 0eed68eddcdb
Create Date: 2025-09-25 22:39:17.246594
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'ccdc5764eb4f'
down_revision: Union[str, Sequence[str], None] = '0eed68eddcdb'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user_conditional_settings', sa.Column('timer_start', sa.Integer(), nullable=False))
op.add_column('user_conditional_settings', sa.Column('timer_end', sa.Integer(), nullable=False))
op.add_column('user_deals', sa.Column('trade_mode', sa.String(), nullable=True))
op.add_column('user_deals', sa.Column('order_type', sa.String(), nullable=True))
op.add_column('user_deals', sa.Column('leverage', sa.String(), nullable=True))
op.add_column('user_deals', sa.Column('leverage_to_buy', sa.String(), nullable=True))
op.add_column('user_deals', sa.Column('leverage_to_sell', sa.String(), nullable=True))
op.add_column('user_deals', sa.Column('closed_side', sa.String(), nullable=True))
op.add_column('user_deals', sa.Column('martingale_factor', sa.Float(), nullable=True))
op.add_column('user_deals', sa.Column('max_bets_in_series', sa.Integer(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user_deals', 'max_bets_in_series')
op.drop_column('user_deals', 'martingale_factor')
op.drop_column('user_deals', 'closed_side')
op.drop_column('user_deals', 'leverage_to_sell')
op.drop_column('user_deals', 'leverage_to_buy')
op.drop_column('user_deals', 'leverage')
op.drop_column('user_deals', 'order_type')
op.drop_column('user_deals', 'trade_mode')
op.drop_column('user_conditional_settings', 'timer_end')
op.drop_column('user_conditional_settings', 'timer_start')
# ### end Alembic commands ###

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,8 @@
"""Updated leverage
"""Added side_mode column
Revision ID: fd8581c0cc87
Revises: bb586fa9bcd2
Create Date: 2025-09-22 15:13:21.487402
Revision ID: fbf4e3658310
Revises:
Create Date: 2025-10-22 13:08:02.317419
"""
from typing import Sequence, Union
@@ -12,7 +12,7 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'fd8581c0cc87'
revision: str = 'fbf4e3658310'
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! ###
pass
op.add_column('user_deals', sa.Column('side_mode', sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
op.drop_column('user_deals', 'side_mode')
# ### end Alembic commands ###

View File

@@ -10,25 +10,24 @@ 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.helper_functions import get_liquidation_price, safe_float
from app.bybit.set_functions.set_switch_position_mode import set_switch_position_mode
from app.helper_functions import safe_float
logging.config.dictConfig(LOGGING_CONFIG)
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
)
@@ -42,6 +41,7 @@ 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"
@@ -62,34 +62,10 @@ async def start_trading_cycle(
else:
side = "Sell"
# 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_switch_position_mode(
tg_id=tg_id,
symbol=symbol,
mode=0)
await set_margin_mode(tg_id=tg_id, margin_mode=margin_type)
await set_leverage(
tg_id=tg_id,
@@ -117,6 +93,7 @@ async def start_trading_cycle(
last_side=side,
current_step=1,
trade_mode=trade_mode,
side_mode=switch_side,
margin_type=margin_type,
leverage=leverage,
order_quantity=order_quantity,
@@ -131,19 +108,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
)
@@ -152,8 +129,90 @@ 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,
last_side=side,
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, reverse_side: str
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)
@@ -170,23 +229,7 @@ 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
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"
side_mode = user_deals_data.side_mode
next_quantity = safe_float(order_quantity) * (
safe_float(martingale_factor)
@@ -196,6 +239,13 @@ 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,
)
res = await open_positions(
tg_id=tg_id,
symbol=symbol,
@@ -216,6 +266,7 @@ async def trading_cycle(
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,
@@ -231,12 +282,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
)
@@ -246,16 +297,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)
@@ -264,7 +315,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(price_symbol)
qty = (safe_float(order_quantity) * safe_float(leverage)) / 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)
@@ -276,51 +327,33 @@ 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
tp_multiplier = 1 + (take_profit_percent / 100)
if commission_fee_percent > 0:
tp_multiplier += commission_fee_percent
if qty_formatted <= 0:
return "Order does not meet minimum order value"
if margin_type == "ISOLATED_MARGIN":
liq_long, liq_short = await get_liquidation_price(
tg_id=tg_id,
entry_price=price_for_cals,
symbol=symbol,
leverage=get_leverage,
)
if (liq_long > 0 or liq_short > 0) and price_for_cals > 0:
if side == "Buy":
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)
if side == "Buy":
take_profit_price = price_for_cals * (
1 + take_profit_percent / 100) + commission_fee_percent / qty_formatted
stop_loss_price = None
else:
take_profit_price = None
stop_loss_price = None
take_profit_price = price_for_cals * (
1 - take_profit_percent / 100) - commission_fee_percent / qty_formatted
stop_loss_price = None
else:
if side == "Buy":
take_profit_price = price_for_cals * tp_multiplier
take_profit_price = price_for_cals * (
1 + take_profit_percent / 100) + commission_fee_percent / qty_formatted
stop_loss_price = price_for_cals * (1 - stop_loss_percent / 100)
else:
take_profit_price = price_for_cals * (
1 - (take_profit_percent / 100) - commission_fee_percent
)
stop_loss_price = trigger_price * (1 + stop_loss_percent / 100)
1 - take_profit_percent / 100) - commission_fee_percent / qty_formatted
stop_loss_price = price_for_cals * (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",
@@ -366,5 +399,5 @@ async def open_positions(
return "InvalidRequestError"
except Exception as e:
logger.error("Error opening position for user %s: %s", tg_id, e)
logger.error("Error opening position for user %s: %s", tg_id, e, exc_info=True)
return None

View File

@@ -21,16 +21,20 @@ 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)
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,
)
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,
)
else:
await message.answer(
text="Ошибка при подключении, повторите попытку",

View File

@@ -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
from app.bybit.open_positions import trading_cycle, trading_cycle_profit
from app.helper_functions import format_value, safe_float
logging.config.dictConfig(LOGGING_CONFIG)
@@ -41,11 +41,26 @@ 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 != "Нет данных":
@@ -67,13 +82,21 @@ class TelegramMessageHandler:
closed_size = format_value(execution.get("closedSize"))
symbol = format_value(execution.get("symbol"))
exec_price = format_value(execution.get("execPrice"))
exec_fee = format_value(execution.get("execFee"))
exec_qty = format_value(execution.get("execQty"))
exec_fees = format_value(execution.get("execFee"))
fee_rate = format_value(execution.get("feeRate"))
side = format_value(execution.get("side"))
side_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(
@@ -86,9 +109,7 @@ 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
@@ -109,29 +130,34 @@ class TelegramMessageHandler:
)
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
)
exec_bet = user_deals_data.order_quantity
base_quantity = user_deals_data.base_quantity
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"
else:
text += f"Количество: {exec_qty}\n"
text += (
f"Цена исполнения: {exec_price}\n"
f"Текущая ставка: {exec_bet}\n"
f"Движение: {side_rus}\n"
f"Комиссия за сделку: {exec_fee}\n"
f"Комиссия: {exec_fee:.8f}\n"
)
if safe_float(closed_size) > 0:
if safe_float(closed_size) == 0:
text += f"Движение: {side_rus}\n"
else:
text += f"\nРеализованная прибыль: {total_pnl:.7f}\n"
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 (
@@ -140,30 +166,27 @@ 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
)
await rq.set_order_quantity(
tg_id=message.from_user.id, order_quantity=base_quantity
)
else:
open_order_text = "\n❗️ Сделка закрылась в минус, открываю новую сделку с увеличенной ставкой.\n"
await self.telegram_bot.send_message(
chat_id=tg_id, text=open_order_text
)
res = await trading_cycle(
tg_id=tg_id, symbol=symbol, reverse_side=side
res = await trading_cycle_profit(
tg_id=tg_id, symbol=symbol, side=r_side
)
if res == "OK":
@@ -174,7 +197,50 @@ 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, "❗️ Не удалось открыть новую сделку"
)
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
)
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, "❗️ Не удалось открыть новую сделку"

View File

@@ -124,6 +124,8 @@ 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)

View File

@@ -7,6 +7,7 @@ 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
@@ -42,7 +43,7 @@ async def settings_for_trade_mode(
text="Выберите режим торговли:\n\n"
"Лонг - все сделки серии открываются на покупку.\n"
"Шорт - все сделки серии открываются на продажу.\n"
"Свитч - направление каждой сделки серии меняется по переменно.\n",
"Свитч - направление первой сделки серии меняется по переменно.\n",
reply_markup=kbi.trade_mode,
)
logger.debug(
@@ -211,6 +212,31 @@ 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"
"Примечание: Если у вас есть открытые позиции, то маржа примениться ко всем позициям",
@@ -554,6 +580,8 @@ async def set_leverage_handler(message: Message, state: FSMContext) -> None:
risk_percent = 100 / safe_float(leverage_float)
await rq.set_stop_loss_percent(
tg_id=message.from_user.id, stop_loss_percent=risk_percent)
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
)

View File

@@ -123,8 +123,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}%\n"
f"- Процент изменения цены для фиксации убытка: {stop_loss_percent}%\n\n"
f"- Процент изменения цены для фиксации прибыли: {take_profit_percent:.2f}%\n"
f"- Процент изменения цены для фиксации убытка: {stop_loss_percent:.2f}%\n\n"
f"- Комиссия биржи для расчета прибыли: {commission_fee_rus}\n\n",
reply_markup=kbi.risk_management,
)
@@ -162,11 +162,9 @@ 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"- Таймер для остановки: {stop_timer} мин.\n",
f"- Таймер для старта: {start_timer} мин.\n",
reply_markup=kbi.conditions,
)
logger.debug(

View File

@@ -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
from app.bybit.get_functions.get_positions import get_active_positions_by_symbol, get_active_orders_by_symbol
from app.bybit.open_positions import start_trading_cycle
from app.helper_functions import safe_float
from app.telegram.tasks.tasks import (
@@ -33,6 +33,7 @@ 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
@@ -46,7 +47,16 @@ async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> Non
if safe_float(size) > 0:
await callback_query.answer(
text="У вас есть активная позиция",
text="У вас есть активная позиция по текущей паре",
)
return
orders = await get_active_orders_by_symbol(
tg_id=callback_query.from_user.id, symbol=symbol)
if orders is not None:
await callback_query.answer(
text="У вас есть активный ордер по текущей паре",
)
return
@@ -73,22 +83,29 @@ 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":
@@ -112,7 +129,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 long for user %s: %s",
"Error processing command start_trading for user %s: %s",
callback_query.from_user.id,
e,
)

View File

@@ -227,9 +227,6 @@ conditions = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(text="Таймер для старта", callback_data="start_timer"),
InlineKeyboardButton(
text="Таймер для остановки", callback_data="stop_timer"
),
],
[
InlineKeyboardButton(text="Назад", callback_data="main_settings"),

View File

@@ -1,31 +1,8 @@
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')
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}"
BOT_TOKEN = os.getenv("BOT_TOKEN")

View File

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

View File

@@ -1,6 +1,6 @@
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.asyncio import AsyncAttrs
from sqlalchemy import Column, ForeignKey, Integer, String, BigInteger, Float, Boolean, UniqueConstraint
from sqlalchemy import Column, ForeignKey, Integer, String, Float, Boolean, UniqueConstraint
from sqlalchemy.orm import relationship
Base = declarative_base(cls=AsyncAttrs)
@@ -11,7 +11,7 @@ class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, autoincrement=True)
tg_id = Column(BigInteger, nullable=False, unique=True)
tg_id = Column(Integer, nullable=False, unique=True)
username = Column(String, nullable=False)
user_api = relationship("UserApi",
@@ -143,6 +143,7 @@ class UserDeals(Base):
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)

View File

@@ -898,6 +898,7 @@ async def set_user_deal(
last_side: str,
current_step: int,
trade_mode: str,
side_mode: str,
margin_type: str,
leverage: str,
order_quantity: float,
@@ -915,6 +916,7 @@ async def set_user_deal(
: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
@@ -944,6 +946,7 @@ async def set_user_deal(
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
@@ -961,6 +964,7 @@ async def set_user_deal(
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,
@@ -1050,6 +1054,34 @@ async def set_fee_user_deal_by_symbol(tg_id: int, symbol: str, fee: float):
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)
return False
# USER AUTO TRADING
async def get_all_user_auto_trading(tg_id: int):

3
run.py
View File

@@ -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,7 +46,6 @@ 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)