Compare commits
	
		
			45 Commits
		
	
	
		
			0.2.0
			...
			898ff91392
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 898ff91392 | |||
| 
						 | 
					f5677e6e7e | ||
| 2047dd5ac6 | |||
| 
						 | 
					c49df2794d | ||
| 
						 | 
					c687811ea5 | ||
| 
						 | 
					5da00dbaa1 | ||
| 
						 | 
					01fe339d56 | ||
| 
						 | 
					220c45d54c | ||
| 
						 | 
					163f4dcba9 | ||
| 
						 | 
					ce5d0605de | ||
| 
						 | 
					086c7c8170 | ||
| 
						 | 
					8e73dcf81f | ||
| 
						 | 
					057cfad675 | ||
| 
						 | 
					1508629727 | ||
| 
						 | 
					4adbd70948 | ||
| 
						 | 
					6705bf4492 | ||
| 
						 | 
					8dbc8d57f9 | ||
| 
						 | 
					fa782f748a | ||
| 
						 | 
					a1a7355dc3 | ||
| 
						 | 
					9d2b049e56 | ||
| 
						 | 
					3306c6e826 | ||
| 
						 | 
					2666f90707 | ||
| 
						 | 
					bed53c0a2c | ||
| 
						 | 
					a9f7c4f7c4 | ||
| 
						 | 
					1981510963 | ||
| 
						 | 
					4f2ce0c1a4 | ||
| 
						 | 
					3ae8c15007 | ||
| 
						 | 
					f81f63b198 | ||
| 
						 | 
					97662081ce | ||
| 
						 | 
					e5a3de4ed8 | ||
| 
						 | 
					66a566e6a3 | ||
| 
						 | 
					eca9d2c7c8 | ||
| 
						 | 
					6d86b230ca | ||
| fec367cc1d | |||
| 
						 | 
					4bbff680aa | ||
| 
						 | 
					49d4bb26bf | ||
| 
						 | 
					29bb6bd0a8 | ||
| 
						 | 
					2fb8cb4acb | ||
| 
						 | 
					887b46c1d4 | ||
| 
						 | 
					b074d1d8a1 | ||
| aebcc9dff2 | |||
| 
						 | 
					e2f9478971 | ||
| 
						 | 
					4f0668970f | ||
| 
						 | 
					4c9901c14a | ||
| 
						 | 
					17dba19078 | 
@@ -1,3 +1,6 @@
 | 
				
			|||||||
TOKEN_TELEGRAM_BOT_1=
 | 
					BOT_TOKEN=YOUR_BOT_TOKEN
 | 
				
			||||||
TOKEN_TELEGRAM_BOT_2=
 | 
					DB_USER=your_username
 | 
				
			||||||
TOKEN_TELEGRAM_BOT_3=
 | 
					DB_PASS=your_password
 | 
				
			||||||
 | 
					DB_HOST=your_host
 | 
				
			||||||
 | 
					DB_PORT=your_port
 | 
				
			||||||
 | 
					DB_NAME=your_database
 | 
				
			||||||
							
								
								
									
										215
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										215
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,17 +1,212 @@
 | 
				
			|||||||
.env
 | 
					# Byte-compiled / optimized / DLL files
 | 
				
			||||||
!*.sample
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
__pycache__/
 | 
					__pycache__/
 | 
				
			||||||
*.pyc
 | 
					*.py[codz]
 | 
				
			||||||
 | 
					*$py.class
 | 
				
			||||||
 | 
					
 | 
				
			||||||
env/
 | 
					# C extensions
 | 
				
			||||||
venv/
 | 
					*.so
 | 
				
			||||||
.venv/
 | 
					
 | 
				
			||||||
 | 
					# Distribution / packaging
 | 
				
			||||||
 | 
					.Python
 | 
				
			||||||
 | 
					build/
 | 
				
			||||||
 | 
					develop-eggs/
 | 
				
			||||||
 | 
					dist/
 | 
				
			||||||
 | 
					downloads/
 | 
				
			||||||
 | 
					eggs/
 | 
				
			||||||
 | 
					.eggs/
 | 
				
			||||||
 | 
					lib/
 | 
				
			||||||
 | 
					lib64/
 | 
				
			||||||
 | 
					parts/
 | 
				
			||||||
 | 
					sdist/
 | 
				
			||||||
 | 
					var/
 | 
				
			||||||
 | 
					wheels/
 | 
				
			||||||
 | 
					share/python-wheels/
 | 
				
			||||||
 | 
					*.egg-info/
 | 
				
			||||||
 | 
					.installed.cfg
 | 
				
			||||||
 | 
					*.egg
 | 
				
			||||||
 | 
					MANIFEST
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# PyInstaller
 | 
				
			||||||
 | 
					#  Usually these files are written by a python script from a template
 | 
				
			||||||
 | 
					#  before PyInstaller builds the exe, so as to inject date/other infos into it.
 | 
				
			||||||
 | 
					*.manifest
 | 
				
			||||||
 | 
					*.spec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Installer logs
 | 
				
			||||||
 | 
					pip-log.txt
 | 
				
			||||||
 | 
					pip-delete-this-directory.txt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Unit test / coverage reports
 | 
				
			||||||
 | 
					htmlcov/
 | 
				
			||||||
 | 
					.tox/
 | 
				
			||||||
 | 
					.nox/
 | 
				
			||||||
 | 
					.coverage
 | 
				
			||||||
 | 
					.coverage.*
 | 
				
			||||||
 | 
					.cache
 | 
				
			||||||
 | 
					nosetests.xml
 | 
				
			||||||
 | 
					coverage.xml
 | 
				
			||||||
 | 
					*.cover
 | 
				
			||||||
 | 
					*.py.cover
 | 
				
			||||||
 | 
					.hypothesis/
 | 
				
			||||||
 | 
					.pytest_cache/
 | 
				
			||||||
 | 
					cover/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Translations
 | 
				
			||||||
 | 
					*.mo
 | 
				
			||||||
 | 
					*.pot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Django stuff:
 | 
				
			||||||
 | 
					*.log
 | 
				
			||||||
 | 
					local_settings.py
 | 
				
			||||||
 | 
					db.sqlite3
 | 
				
			||||||
 | 
					db.sqlite3-journal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Flask stuff:
 | 
				
			||||||
 | 
					instance/
 | 
				
			||||||
 | 
					.webassets-cache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Scrapy stuff:
 | 
				
			||||||
 | 
					.scrapy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Sphinx documentation
 | 
				
			||||||
 | 
					docs/_build/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# PyBuilder
 | 
				
			||||||
 | 
					.pybuilder/
 | 
				
			||||||
 | 
					target/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Jupyter Notebook
 | 
				
			||||||
 | 
					.ipynb_checkpoints
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# IPython
 | 
				
			||||||
 | 
					profile_default/
 | 
				
			||||||
 | 
					ipython_config.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# pyenv
 | 
				
			||||||
 | 
					#   For a library or package, you might want to ignore these files since the code is
 | 
				
			||||||
 | 
					#   intended to run in multiple environments; otherwise, check them in:
 | 
				
			||||||
 | 
					# .python-version
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# pipenv
 | 
				
			||||||
 | 
					#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
 | 
				
			||||||
 | 
					#   However, in case of collaboration, if having platform-specific dependencies or dependencies
 | 
				
			||||||
 | 
					#   having no cross-platform support, pipenv may install dependencies that don't work, or not
 | 
				
			||||||
 | 
					#   install all needed dependencies.
 | 
				
			||||||
 | 
					#Pipfile.lock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# UV
 | 
				
			||||||
 | 
					#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
 | 
				
			||||||
 | 
					#   This is especially recommended for binary packages to ensure reproducibility, and is more
 | 
				
			||||||
 | 
					#   commonly ignored for libraries.
 | 
				
			||||||
 | 
					#uv.lock
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# poetry
 | 
				
			||||||
 | 
					#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
 | 
				
			||||||
 | 
					#   This is especially recommended for binary packages to ensure reproducibility, and is more
 | 
				
			||||||
 | 
					#   commonly ignored for libraries.
 | 
				
			||||||
 | 
					#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
 | 
				
			||||||
 | 
					#poetry.lock
 | 
				
			||||||
 | 
					#poetry.toml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# pdm
 | 
				
			||||||
 | 
					#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
 | 
				
			||||||
 | 
					#   pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
 | 
				
			||||||
 | 
					#   https://pdm-project.org/en/latest/usage/project/#working-with-version-control
 | 
				
			||||||
 | 
					#pdm.lock
 | 
				
			||||||
 | 
					#pdm.toml
 | 
				
			||||||
 | 
					.pdm-python
 | 
				
			||||||
 | 
					.pdm-build/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# pixi
 | 
				
			||||||
 | 
					#   Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
 | 
				
			||||||
 | 
					#pixi.lock
 | 
				
			||||||
 | 
					#   Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
 | 
				
			||||||
 | 
					#   in the .venv directory. It is recommended not to include this directory in version control.
 | 
				
			||||||
 | 
					.pixi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
 | 
				
			||||||
 | 
					__pypackages__/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Celery stuff
 | 
				
			||||||
 | 
					celerybeat-schedule
 | 
				
			||||||
 | 
					celerybeat.pid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# SageMath parsed files
 | 
				
			||||||
 | 
					*.sage.py
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Environments
 | 
				
			||||||
.idea
 | 
					.idea
 | 
				
			||||||
/.idea
 | 
					/.idea
 | 
				
			||||||
/myenv
 | 
					.env
 | 
				
			||||||
 | 
					.envrc
 | 
				
			||||||
 | 
					.venv
 | 
				
			||||||
 | 
					env/
 | 
				
			||||||
 | 
					venv/
 | 
				
			||||||
myenv
 | 
					myenv
 | 
				
			||||||
 | 
					ENV/
 | 
				
			||||||
 | 
					env.bak/
 | 
				
			||||||
 | 
					venv.bak/
 | 
				
			||||||
 | 
					/alembic/versions
 | 
				
			||||||
 | 
					/alembic
 | 
				
			||||||
 | 
					alembic.ini
 | 
				
			||||||
 | 
					# Spyder project settings
 | 
				
			||||||
 | 
					.spyderproject
 | 
				
			||||||
 | 
					.spyproject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
*.sqlite3
 | 
					# Rope project settings
 | 
				
			||||||
 | 
					.ropeproject
 | 
				
			||||||
 | 
					
 | 
				
			||||||
*.log
 | 
					# mkdocs documentation
 | 
				
			||||||
 | 
					/site
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# mypy
 | 
				
			||||||
 | 
					.mypy_cache/
 | 
				
			||||||
 | 
					.dmypy.json
 | 
				
			||||||
 | 
					dmypy.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Pyre type checker
 | 
				
			||||||
 | 
					.pyre/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# pytype static type analyzer
 | 
				
			||||||
 | 
					.pytype/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Cython debug symbols
 | 
				
			||||||
 | 
					cython_debug/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# PyCharm
 | 
				
			||||||
 | 
					#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
 | 
				
			||||||
 | 
					#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
 | 
				
			||||||
 | 
					#  and can be added to the global gitignore or merged into this file.  For a more nuclear
 | 
				
			||||||
 | 
					#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
 | 
				
			||||||
 | 
					#.idea/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Abstra
 | 
				
			||||||
 | 
					# Abstra is an AI-powered process automation framework.
 | 
				
			||||||
 | 
					# Ignore directories containing user credentials, local state, and settings.
 | 
				
			||||||
 | 
					# Learn more at https://abstra.io/docs
 | 
				
			||||||
 | 
					.abstra/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Visual Studio Code
 | 
				
			||||||
 | 
					#  Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore 
 | 
				
			||||||
 | 
					#  that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
 | 
				
			||||||
 | 
					#  and can be added to the global gitignore or merged into this file. However, if you prefer, 
 | 
				
			||||||
 | 
					#  you could uncomment the following to ignore the entire vscode folder
 | 
				
			||||||
 | 
					# .vscode/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Ruff stuff:
 | 
				
			||||||
 | 
					.ruff_cache/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# PyPI configuration file
 | 
				
			||||||
 | 
					.pypirc
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Cursor
 | 
				
			||||||
 | 
					#  Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
 | 
				
			||||||
 | 
					#  exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
 | 
				
			||||||
 | 
					#  refer to https://docs.cursor.com/context/ignore-files
 | 
				
			||||||
 | 
					.cursorignore
 | 
				
			||||||
 | 
					.cursorindexingignore
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Marimo
 | 
				
			||||||
 | 
					marimo/_static/
 | 
				
			||||||
 | 
					marimo/_lsp/
 | 
				
			||||||
 | 
					__marimo__/
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,51 +0,0 @@
 | 
				
			|||||||
import asyncio
 | 
					 | 
				
			||||||
import logging.config
 | 
					 | 
				
			||||||
from aiogram import Bot, Dispatcher
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from app.services.Bybit.functions.bybit_ws import get_or_create_event_loop, set_event_loop
 | 
					 | 
				
			||||||
from app.telegram.database.models import async_main
 | 
					 | 
				
			||||||
from app.telegram.handlers.handlers import router
 | 
					 | 
				
			||||||
from app.telegram.functions.main_settings.settings import router_main_settings
 | 
					 | 
				
			||||||
from app.telegram.functions.risk_management_settings.settings import router_risk_management_settings
 | 
					 | 
				
			||||||
from app.telegram.functions.condition_settings.settings import condition_settings_router
 | 
					 | 
				
			||||||
from app.services.Bybit.functions.Add_Bybit_API import router_register_bybit_api
 | 
					 | 
				
			||||||
from app.services.Bybit.functions.functions import router_functions_bybit_trade
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from logger_helper.logger_helper import LOGGING_CONFIG
 | 
					 | 
				
			||||||
from config import TOKEN_TG_BOT_1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
logging.config.dictConfig(LOGGING_CONFIG)
 | 
					 | 
				
			||||||
logger = logging.getLogger("main")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bot = Bot(token=TOKEN_TG_BOT_1)
 | 
					 | 
				
			||||||
dp = Dispatcher()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def main() -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Основная асинхронная функция запуска бота:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    loop = get_or_create_event_loop()
 | 
					 | 
				
			||||||
    set_event_loop(loop)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await async_main()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    dp.include_router(router)
 | 
					 | 
				
			||||||
    dp.include_router(router_main_settings)
 | 
					 | 
				
			||||||
    dp.include_router(router_risk_management_settings)
 | 
					 | 
				
			||||||
    dp.include_router(condition_settings_router)
 | 
					 | 
				
			||||||
    dp.include_router(router_register_bybit_api)
 | 
					 | 
				
			||||||
    dp.include_router(router_functions_bybit_trade)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        await dp.start_polling(bot)
 | 
					 | 
				
			||||||
    except asyncio.CancelledError:
 | 
					 | 
				
			||||||
        logger.info("Bot is off")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
if __name__ == '__main__':
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        logger.info("Bot is on")
 | 
					 | 
				
			||||||
        asyncio.run(main())
 | 
					 | 
				
			||||||
    except KeyboardInterrupt:
 | 
					 | 
				
			||||||
        logger.info("Bot is off")
 | 
					 | 
				
			||||||
@@ -1,68 +0,0 @@
 | 
				
			|||||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
 | 
					 | 
				
			||||||
  <PropertyGroup>
 | 
					 | 
				
			||||||
    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
 | 
					 | 
				
			||||||
    <SchemaVersion>2.0</SchemaVersion>
 | 
					 | 
				
			||||||
    <ProjectGuid>bc1d7460-d8ca-4977-a249-0f6d6cc2375a</ProjectGuid>
 | 
					 | 
				
			||||||
    <ProjectHome>.</ProjectHome>
 | 
					 | 
				
			||||||
    <StartupFile>BibytBot_API.py</StartupFile>
 | 
					 | 
				
			||||||
    <SearchPath>
 | 
					 | 
				
			||||||
    </SearchPath>
 | 
					 | 
				
			||||||
    <WorkingDirectory>.</WorkingDirectory>
 | 
					 | 
				
			||||||
    <OutputPath>.</OutputPath>
 | 
					 | 
				
			||||||
    <Name>BibytBot_API</Name>
 | 
					 | 
				
			||||||
    <RootNamespace>BibytBot_API</RootNamespace>
 | 
					 | 
				
			||||||
  </PropertyGroup>
 | 
					 | 
				
			||||||
  <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
 | 
					 | 
				
			||||||
    <DebugSymbols>true</DebugSymbols>
 | 
					 | 
				
			||||||
    <EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
 | 
					 | 
				
			||||||
  </PropertyGroup>
 | 
					 | 
				
			||||||
  <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
 | 
					 | 
				
			||||||
    <DebugSymbols>true</DebugSymbols>
 | 
					 | 
				
			||||||
    <EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
 | 
					 | 
				
			||||||
  </PropertyGroup>
 | 
					 | 
				
			||||||
  <ItemGroup>
 | 
					 | 
				
			||||||
    <Compile Include="app\services\Bybit\functions\Add_Bybit_API.py" />
 | 
					 | 
				
			||||||
    <Compile Include="app\services\Bybit\functions\balance.py" />
 | 
					 | 
				
			||||||
    <Compile Include="app\services\Bybit\functions\functions.py" />
 | 
					 | 
				
			||||||
    <Compile Include="app\services\Bybit\functions\func_min_qty.py" />
 | 
					 | 
				
			||||||
    <Compile Include="app\services\Bybit\functions\Futures.py" />
 | 
					 | 
				
			||||||
    <Compile Include="app\services\Bybit\functions\price_symbol.py" />
 | 
					 | 
				
			||||||
    <Compile Include="app\telegram\functions\additional_settings\settings.py" />
 | 
					 | 
				
			||||||
    <Compile Include="app\telegram\functions\condition_settings\settings.py" />
 | 
					 | 
				
			||||||
    <Compile Include="app\telegram\functions\functions.py" />
 | 
					 | 
				
			||||||
    <Compile Include="app\telegram\database\models.py" />
 | 
					 | 
				
			||||||
    <Compile Include="app\telegram\database\requests.py" />
 | 
					 | 
				
			||||||
    <Compile Include="app\telegram\functions\main_settings\settings.py" />
 | 
					 | 
				
			||||||
    <Compile Include="app\telegram\functions\risk_management_settings\settings.py" />
 | 
					 | 
				
			||||||
    <Compile Include="app\telegram\handlers\handlers.py" />
 | 
					 | 
				
			||||||
    <Compile Include="app\telegram\Keyboards\inline_keyboards.py" />
 | 
					 | 
				
			||||||
    <Compile Include="app\telegram\Keyboards\reply_keyboards.py" />
 | 
					 | 
				
			||||||
    <Compile Include="app\telegram\logs.py" />
 | 
					 | 
				
			||||||
    <Compile Include="BibytBot_API.py" />
 | 
					 | 
				
			||||||
    <Compile Include="config.py" />
 | 
					 | 
				
			||||||
  </ItemGroup>
 | 
					 | 
				
			||||||
  <ItemGroup>
 | 
					 | 
				
			||||||
    <Folder Include="app\" />
 | 
					 | 
				
			||||||
    <Folder Include="app\services\Bybit\" />
 | 
					 | 
				
			||||||
    <Folder Include="app\services\" />
 | 
					 | 
				
			||||||
    <Folder Include="app\services\Bybit\functions\" />
 | 
					 | 
				
			||||||
    <Folder Include="app\telegram\database\" />
 | 
					 | 
				
			||||||
    <Folder Include="app\telegram\functions\condition_settings\" />
 | 
					 | 
				
			||||||
    <Folder Include="app\telegram\functions\additional_settings\" />
 | 
					 | 
				
			||||||
    <Folder Include="app\telegram\functions\risk_management_settings\" />
 | 
					 | 
				
			||||||
    <Folder Include="app\telegram\handlers\" />
 | 
					 | 
				
			||||||
    <Folder Include="app\telegram\Keyboards\" />
 | 
					 | 
				
			||||||
    <Folder Include="app\telegram\functions\main_settings\" />
 | 
					 | 
				
			||||||
    <Folder Include="app\telegram\functions\" />
 | 
					 | 
				
			||||||
    <Folder Include="app\telegram\" />
 | 
					 | 
				
			||||||
  </ItemGroup>
 | 
					 | 
				
			||||||
  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets" />
 | 
					 | 
				
			||||||
  <!-- Uncomment the CoreCompile target to enable the Build command in
 | 
					 | 
				
			||||||
       Visual Studio and specify your pre- and post-build commands in
 | 
					 | 
				
			||||||
       the BeforeBuild and AfterBuild targets below. -->
 | 
					 | 
				
			||||||
  <!--<Target Name="CoreCompile" />-->
 | 
					 | 
				
			||||||
  <Target Name="BeforeBuild">
 | 
					 | 
				
			||||||
  </Target>
 | 
					 | 
				
			||||||
  <Target Name="AfterBuild">
 | 
					 | 
				
			||||||
  </Target>
 | 
					 | 
				
			||||||
</Project>
 | 
					 | 
				
			||||||
@@ -1,23 +0,0 @@
 | 
				
			|||||||
 | 
					 | 
				
			||||||
Microsoft Visual Studio Solution File, Format Version 12.00
 | 
					 | 
				
			||||||
# Visual Studio Version 17
 | 
					 | 
				
			||||||
VisualStudioVersion = 17.13.35825.156 d17.13
 | 
					 | 
				
			||||||
MinimumVisualStudioVersion = 10.0.40219.1
 | 
					 | 
				
			||||||
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "BibytBot_API", "BibytBot_API.pyproj", "{BC1D7460-D8CA-4977-A249-0F6D6CC2375A}"
 | 
					 | 
				
			||||||
EndProject
 | 
					 | 
				
			||||||
Global
 | 
					 | 
				
			||||||
	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 | 
					 | 
				
			||||||
		Debug|Any CPU = Debug|Any CPU
 | 
					 | 
				
			||||||
		Release|Any CPU = Release|Any CPU
 | 
					 | 
				
			||||||
	EndGlobalSection
 | 
					 | 
				
			||||||
	GlobalSection(ProjectConfigurationPlatforms) = postSolution
 | 
					 | 
				
			||||||
		{BC1D7460-D8CA-4977-A249-0F6D6CC2375A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 | 
					 | 
				
			||||||
		{BC1D7460-D8CA-4977-A249-0F6D6CC2375A}.Release|Any CPU.ActiveCfg = Release|Any CPU
 | 
					 | 
				
			||||||
	EndGlobalSection
 | 
					 | 
				
			||||||
	GlobalSection(SolutionProperties) = preSolution
 | 
					 | 
				
			||||||
		HideSolutionNode = FALSE
 | 
					 | 
				
			||||||
	EndGlobalSection
 | 
					 | 
				
			||||||
	GlobalSection(ExtensibilityGlobals) = postSolution
 | 
					 | 
				
			||||||
		SolutionGuid = {9AF00E9A-19FB-4146-96C0-B86C8B1E02C0}
 | 
					 | 
				
			||||||
	EndGlobalSection
 | 
					 | 
				
			||||||
EndGlobal
 | 
					 | 
				
			||||||
@@ -58,7 +58,7 @@ nvim .env
 | 
				
			|||||||
5. Запустите бота:
 | 
					5. Запустите бота:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
python BybitBot_API.py
 | 
					python run.py
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Настройка автономной работы
 | 
					## Настройка автономной работы
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										0
									
								
								app/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								app/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										21
									
								
								app/bybit/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								app/bybit/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from pybit.unified_trading import HTTP
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG
 | 
				
			||||||
 | 
					from database import request as rq
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("bybit")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def get_bybit_client(tg_id: int) -> HTTP | None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Get bybit client
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        api_key, api_secret = await rq.get_user_api(tg_id=tg_id)
 | 
				
			||||||
 | 
					        return HTTP(api_key=api_key, api_secret=api_secret)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error getting bybit client for user %s: %s", tg_id, e)
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
							
								
								
									
										100
									
								
								app/bybit/close_positions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								app/bybit/close_positions.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from app.bybit import get_bybit_client
 | 
				
			||||||
 | 
					from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("close_positions")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if side == "Buy":
 | 
				
			||||||
 | 
					            r_side = "Sell"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            r_side = "Buy"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        response = client.place_order(
 | 
				
			||||||
 | 
					            category="linear",
 | 
				
			||||||
 | 
					            symbol=symbol,
 | 
				
			||||||
 | 
					            side=r_side,
 | 
				
			||||||
 | 
					            orderType="Market",
 | 
				
			||||||
 | 
					            qty=qty,
 | 
				
			||||||
 | 
					            timeInForce="GTC",
 | 
				
			||||||
 | 
					            positionIdx=position_idx,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        if response["retCode"] == 0:
 | 
				
			||||||
 | 
					            logger.info("All positions closed for %s for user %s", symbol, tg_id)
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logger.error(
 | 
				
			||||||
 | 
					                "Error closing all positions for %s for user %s", symbol, tg_id
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error closing all positions for %s for user %s: %s", symbol, tg_id, e
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def cancel_order(tg_id: int, symbol: str, order_id: str) -> bool:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Cancel order by order id
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        client = await get_bybit_client(tg_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cancel_resp = client.cancel_order(
 | 
				
			||||||
 | 
					            category="linear", symbol=symbol, orderId=order_id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if cancel_resp.get("retCode") == 0:
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logger.error(
 | 
				
			||||||
 | 
					                "Error canceling order for user %s: %s",
 | 
				
			||||||
 | 
					                tg_id,
 | 
				
			||||||
 | 
					                cancel_resp.get("retMsg"),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error canceling order for user %s: %s", tg_id, e)
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def cancel_all_orders(tg_id: int) -> bool:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Cancel all open orders
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        client = await get_bybit_client(tg_id)
 | 
				
			||||||
 | 
					        cancel_resp = client.cancel_all_orders(category="linear", settleCoin="USDT")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if cancel_resp.get("retCode") == 0:
 | 
				
			||||||
 | 
					            logger.info("All orders canceled for user %s", tg_id)
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logger.error(
 | 
				
			||||||
 | 
					                "Error canceling order for user %s: %s",
 | 
				
			||||||
 | 
					                tg_id,
 | 
				
			||||||
 | 
					                cancel_resp.get("retMsg"),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error canceling order for user %s: %s", tg_id, e)
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
							
								
								
									
										0
									
								
								app/bybit/get_functions/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								app/bybit/get_functions/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										28
									
								
								app/bybit/get_functions/get_balance.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								app/bybit/get_functions/get_balance.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from app.bybit import get_bybit_client
 | 
				
			||||||
 | 
					from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("get_balance")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def get_balance(tg_id: int) -> bool | dict:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Get balance bybit
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    client = await get_bybit_client(tg_id=tg_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        response = client.get_wallet_balance(accountType="UNIFIED")
 | 
				
			||||||
 | 
					        if response["retCode"] == 0:
 | 
				
			||||||
 | 
					            info = response["result"]["list"][0]
 | 
				
			||||||
 | 
					            return info
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logger.error(
 | 
				
			||||||
 | 
					                "Error getting balance for user %s: %s", tg_id, response.get("retMsg")
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error connecting to Bybit for user %s: %s", tg_id, e)
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
							
								
								
									
										28
									
								
								app/bybit/get_functions/get_instruments_info.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								app/bybit/get_functions/get_instruments_info.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from app.bybit import get_bybit_client
 | 
				
			||||||
 | 
					from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("get_instruments_info")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def get_instruments_info(tg_id: int, symbol: str) -> dict | None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Get instruments info
 | 
				
			||||||
 | 
					    :param tg_id: int - User ID
 | 
				
			||||||
 | 
					    :param symbol: str - Symbol
 | 
				
			||||||
 | 
					    :return: dict - Instruments info
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        client = await get_bybit_client(tg_id=tg_id)
 | 
				
			||||||
 | 
					        response = client.get_instruments_info(category="linear", symbol=symbol)
 | 
				
			||||||
 | 
					        if response["retCode"] == 0:
 | 
				
			||||||
 | 
					            logger.info("Instruments info for user: %s", tg_id)
 | 
				
			||||||
 | 
					            return response["result"]["list"][0]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logger.error("Error getting price: %s", tg_id)
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error connecting to Bybit for user %s: %s", tg_id, e)
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
							
								
								
									
										129
									
								
								app/bybit/get_functions/get_positions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								app/bybit/get_functions/get_positions.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from app.bybit import get_bybit_client
 | 
				
			||||||
 | 
					from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("get_positions")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def get_active_positions(tg_id: int) -> list | None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Get active positions for a user
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        client = await get_bybit_client(tg_id)
 | 
				
			||||||
 | 
					        response = client.get_positions(category="linear", settleCoin="USDT")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if response["retCode"] == 0:
 | 
				
			||||||
 | 
					            positions = response.get("result", {}).get("list", [])
 | 
				
			||||||
 | 
					            active_symbols = [
 | 
				
			||||||
 | 
					                pos.get("symbol") for pos in positions if float(pos.get("size", 0)) > 0
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					            if active_symbols:
 | 
				
			||||||
 | 
					                logger.info("Active positions for user: %s", tg_id)
 | 
				
			||||||
 | 
					                return positions
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                logger.warning("No active positions found for user: %s", tg_id)
 | 
				
			||||||
 | 
					                return ["No active positions found"]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logger.error(
 | 
				
			||||||
 | 
					                "Error getting active positions for user %s: %s",
 | 
				
			||||||
 | 
					                tg_id,
 | 
				
			||||||
 | 
					                response["retMsg"],
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error getting active positions for user %s: %s", tg_id, e)
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def get_active_positions_by_symbol(tg_id: int, symbol: str) -> dict | None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Get active positions for a user by symbol
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        client = await get_bybit_client(tg_id)
 | 
				
			||||||
 | 
					        response = client.get_positions(category="linear", symbol=symbol)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if response["retCode"] == 0:
 | 
				
			||||||
 | 
					            positions = response.get("result", {}).get("list", [])
 | 
				
			||||||
 | 
					            if positions:
 | 
				
			||||||
 | 
					                logger.info("Active positions for user: %s", tg_id)
 | 
				
			||||||
 | 
					                return positions
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                logger.warning("No active positions found for user: %s", tg_id)
 | 
				
			||||||
 | 
					                return None
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logger.error(
 | 
				
			||||||
 | 
					                "Error getting active positions for user %s: %s",
 | 
				
			||||||
 | 
					                tg_id,
 | 
				
			||||||
 | 
					                response["retMsg"],
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error getting active positions for user %s: %s", tg_id, e)
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def get_active_orders(tg_id: int) -> list | None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Get active orders
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        client = await get_bybit_client(tg_id)
 | 
				
			||||||
 | 
					        response = client.get_open_orders(
 | 
				
			||||||
 | 
					            category="linear",
 | 
				
			||||||
 | 
					            settleCoin="USDT",
 | 
				
			||||||
 | 
					            limit=50,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if response["retCode"] == 0:
 | 
				
			||||||
 | 
					            orders = response.get("result", {}).get("list", [])
 | 
				
			||||||
 | 
					            active_orders = [
 | 
				
			||||||
 | 
					                pos.get("symbol") for pos in orders if float(pos.get("qty", 0)) > 0
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					            if active_orders:
 | 
				
			||||||
 | 
					                logger.info("Active orders for user: %s", tg_id)
 | 
				
			||||||
 | 
					                return orders
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                logger.warning("No active orders found for user: %s", tg_id)
 | 
				
			||||||
 | 
					                return ["No active orders found"]
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logger.error(
 | 
				
			||||||
 | 
					                "Error getting active orders for user %s: %s", tg_id, response["retMsg"]
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error getting active orders for user %s: %s", tg_id, e)
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def get_active_orders_by_symbol(tg_id: int, symbol: str) -> dict | None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Get active orders by symbol
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        client = await get_bybit_client(tg_id)
 | 
				
			||||||
 | 
					        response = client.get_open_orders(
 | 
				
			||||||
 | 
					            category="linear",
 | 
				
			||||||
 | 
					            symbol=symbol,
 | 
				
			||||||
 | 
					            limit=50,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if response["retCode"] == 0:
 | 
				
			||||||
 | 
					            orders = response.get("result", {}).get("list", [])
 | 
				
			||||||
 | 
					            if orders:
 | 
				
			||||||
 | 
					                logger.info("Active orders for user: %s", tg_id)
 | 
				
			||||||
 | 
					                return orders
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                logger.warning("No active orders found for user: %s", tg_id)
 | 
				
			||||||
 | 
					                return None
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logger.error(
 | 
				
			||||||
 | 
					                "Error getting active orders for user %s: %s", tg_id, response["retMsg"]
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error getting active orders for user %s: %s", tg_id, e)
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
							
								
								
									
										35
									
								
								app/bybit/get_functions/get_tickers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								app/bybit/get_functions/get_tickers.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from app.bybit import get_bybit_client
 | 
				
			||||||
 | 
					from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("get_tickers")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def get_tickers(tg_id: int, symbol: str) -> dict | None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Get tickers
 | 
				
			||||||
 | 
					    :param tg_id: int Telegram ID
 | 
				
			||||||
 | 
					    :param symbol: str Symbol
 | 
				
			||||||
 | 
					    :return: dict
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        client = await get_bybit_client(tg_id=tg_id)
 | 
				
			||||||
 | 
					        response = client.get_tickers(category="linear", symbol=symbol)
 | 
				
			||||||
 | 
					        if response["retCode"] == 0:
 | 
				
			||||||
 | 
					            tickers = response["result"]["list"]
 | 
				
			||||||
 | 
					            # USDT quoteCoin
 | 
				
			||||||
 | 
					            usdt_tickers = [t for t in tickers if t.get("symbol", "").endswith("USDT")]
 | 
				
			||||||
 | 
					            if usdt_tickers:
 | 
				
			||||||
 | 
					                logger.info("USDT tickers for user: %s", tg_id)
 | 
				
			||||||
 | 
					                return usdt_tickers[0]
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                logger.warning("No USDT tickers found for user: %s", tg_id)
 | 
				
			||||||
 | 
					                return None
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logger.error("Error getting price: %s", tg_id)
 | 
				
			||||||
 | 
					            return None
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error connecting to Bybit for user %s: %s", tg_id, e)
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
							
								
								
									
										0
									
								
								app/bybit/logger_bybit/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								app/bybit/logger_bybit/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										129
									
								
								app/bybit/logger_bybit/logger_bybit.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								app/bybit/logger_bybit/logger_bybit.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
				
			|||||||
 | 
					import os
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					current_directory = os.path.dirname(os.path.abspath(__file__))
 | 
				
			||||||
 | 
					log_directory = os.path.join(current_directory, "loggers")
 | 
				
			||||||
 | 
					error_log_directory = os.path.join(log_directory, "errors")
 | 
				
			||||||
 | 
					os.makedirs(log_directory, exist_ok=True)
 | 
				
			||||||
 | 
					os.makedirs(error_log_directory, exist_ok=True)
 | 
				
			||||||
 | 
					log_filename = os.path.join(log_directory, "app.log")
 | 
				
			||||||
 | 
					error_log_filename = os.path.join(error_log_directory, "error.log")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LOGGING_CONFIG = {
 | 
				
			||||||
 | 
					    "version": 1,
 | 
				
			||||||
 | 
					    "disable_existing_loggers": False,
 | 
				
			||||||
 | 
					    "formatters": {
 | 
				
			||||||
 | 
					        "default": {
 | 
				
			||||||
 | 
					            "format": "BYBIT: %(asctime)s - %(name)s - %(levelname)s - %(message)s",
 | 
				
			||||||
 | 
					            "datefmt": "%Y-%m-%d %H:%M:%S",  # Формат даты
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "handlers": {
 | 
				
			||||||
 | 
					        "timed_rotating_file": {
 | 
				
			||||||
 | 
					            "class": "logging.handlers.TimedRotatingFileHandler",
 | 
				
			||||||
 | 
					            "filename": log_filename,
 | 
				
			||||||
 | 
					            "when": "midnight",  # Время ротации (каждую полночь)
 | 
				
			||||||
 | 
					            "interval": 1,  # Интервал в днях
 | 
				
			||||||
 | 
					            "backupCount": 7,  # Количество сохраняемых архивов (0 - не сохранять)
 | 
				
			||||||
 | 
					            "formatter": "default",
 | 
				
			||||||
 | 
					            "encoding": "utf-8",
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "error_file": {
 | 
				
			||||||
 | 
					            "class": "logging.handlers.TimedRotatingFileHandler",
 | 
				
			||||||
 | 
					            "filename": error_log_filename,
 | 
				
			||||||
 | 
					            "when": "midnight",
 | 
				
			||||||
 | 
					            "interval": 1,
 | 
				
			||||||
 | 
					            "backupCount": 30,
 | 
				
			||||||
 | 
					            "formatter": "default",
 | 
				
			||||||
 | 
					            "encoding": "utf-8",
 | 
				
			||||||
 | 
					            "level": "ERROR",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "console": {
 | 
				
			||||||
 | 
					            "class": "logging.StreamHandler",
 | 
				
			||||||
 | 
					            "formatter": "default",
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "loggers": {
 | 
				
			||||||
 | 
					        "profile_bybit": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "get_balance": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "price_symbol": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "bybit": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "web_socket": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "get_tickers": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "set_margin_mode": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "set_switch_margin_mode": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "set_switch_position_mode": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "set_leverage": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "get_instruments_info": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "get_positions": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "open_positions": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "close_positions": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "telegram_message_handler": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "set_tp_sl": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										443
									
								
								app/bybit/open_positions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										443
									
								
								app/bybit/open_positions.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,443 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from pybit.exceptions import InvalidRequestError
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import database.request as rq
 | 
				
			||||||
 | 
					from app.bybit import get_bybit_client
 | 
				
			||||||
 | 
					from app.bybit.get_functions.get_balance import get_balance
 | 
				
			||||||
 | 
					from app.bybit.get_functions.get_instruments_info import get_instruments_info
 | 
				
			||||||
 | 
					from app.bybit.get_functions.get_tickers import get_tickers
 | 
				
			||||||
 | 
					from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG
 | 
				
			||||||
 | 
					from app.bybit.set_functions.set_leverage import (
 | 
				
			||||||
 | 
					    set_leverage,
 | 
				
			||||||
 | 
					    set_leverage_to_buy_and_sell,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					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 check_limit_price, get_liquidation_price, safe_float
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("open_positions")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def start_trading_cycle(
 | 
				
			||||||
 | 
					    tg_id: int, side: str, switch_side_mode: bool
 | 
				
			||||||
 | 
					) -> str | None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Start trading cycle
 | 
				
			||||||
 | 
					    :param tg_id: Telegram user ID
 | 
				
			||||||
 | 
					    :param side: Buy or Sell
 | 
				
			||||||
 | 
					    :param switch_side_mode: switch_side_mode
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        trade_mode = additional_data.trade_mode
 | 
				
			||||||
 | 
					        margin_type = additional_data.margin_type
 | 
				
			||||||
 | 
					        leverage = additional_data.leverage
 | 
				
			||||||
 | 
					        leverage_to_buy = additional_data.leverage_to_buy
 | 
				
			||||||
 | 
					        leverage_to_sell = additional_data.leverage_to_sell
 | 
				
			||||||
 | 
					        order_type = additional_data.order_type
 | 
				
			||||||
 | 
					        conditional_order_type = additional_data.conditional_order_type
 | 
				
			||||||
 | 
					        order_quantity = additional_data.order_quantity
 | 
				
			||||||
 | 
					        limit_price = additional_data.limit_price
 | 
				
			||||||
 | 
					        trigger_price = additional_data.trigger_price
 | 
				
			||||||
 | 
					        martingale_factor = additional_data.martingale_factor
 | 
				
			||||||
 | 
					        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
 | 
				
			||||||
 | 
					        max_risk_percent = risk_management_data.max_risk_percent
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mode = 3 if trade_mode == "Both_Sides" else 0
 | 
				
			||||||
 | 
					        await set_switch_position_mode(tg_id=tg_id, symbol=symbol, mode=mode)
 | 
				
			||||||
 | 
					        await set_margin_mode(tg_id=tg_id, margin_mode=margin_type)
 | 
				
			||||||
 | 
					        if trade_mode == "Both_Sides" and margin_type == "ISOLATED_MARGIN":
 | 
				
			||||||
 | 
					            await set_leverage_to_buy_and_sell(
 | 
				
			||||||
 | 
					                tg_id=tg_id,
 | 
				
			||||||
 | 
					                symbol=symbol,
 | 
				
			||||||
 | 
					                leverage_to_buy=leverage_to_buy,
 | 
				
			||||||
 | 
					                leverage_to_sell=leverage_to_sell,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await set_leverage(
 | 
				
			||||||
 | 
					                tg_id=tg_id,
 | 
				
			||||||
 | 
					                symbol=symbol,
 | 
				
			||||||
 | 
					                leverage=leverage,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        res = await open_positions(
 | 
				
			||||||
 | 
					            tg_id=tg_id,
 | 
				
			||||||
 | 
					            symbol=symbol,
 | 
				
			||||||
 | 
					            side=side,
 | 
				
			||||||
 | 
					            order_type=order_type,
 | 
				
			||||||
 | 
					            conditional_order_type=conditional_order_type,
 | 
				
			||||||
 | 
					            order_quantity=order_quantity,
 | 
				
			||||||
 | 
					            limit_price=limit_price,
 | 
				
			||||||
 | 
					            trigger_price=trigger_price,
 | 
				
			||||||
 | 
					            trade_mode=trade_mode,
 | 
				
			||||||
 | 
					            margin_type=margin_type,
 | 
				
			||||||
 | 
					            leverage=leverage,
 | 
				
			||||||
 | 
					            leverage_to_buy=leverage_to_buy,
 | 
				
			||||||
 | 
					            leverage_to_sell=leverage_to_sell,
 | 
				
			||||||
 | 
					            take_profit_percent=take_profit_percent,
 | 
				
			||||||
 | 
					            stop_loss_percent=stop_loss_percent,
 | 
				
			||||||
 | 
					            max_risk_percent=max_risk_percent,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if res == "OK":
 | 
				
			||||||
 | 
					            await rq.set_user_deal(
 | 
				
			||||||
 | 
					                tg_id=tg_id,
 | 
				
			||||||
 | 
					                symbol=symbol,
 | 
				
			||||||
 | 
					                last_side=side,
 | 
				
			||||||
 | 
					                current_step=1,
 | 
				
			||||||
 | 
					                trade_mode=trade_mode,
 | 
				
			||||||
 | 
					                margin_type=margin_type,
 | 
				
			||||||
 | 
					                leverage=leverage,
 | 
				
			||||||
 | 
					                leverage_to_buy=leverage_to_buy,
 | 
				
			||||||
 | 
					                leverage_to_sell=leverage_to_sell,
 | 
				
			||||||
 | 
					                order_type="Market",
 | 
				
			||||||
 | 
					                conditional_order_type=conditional_order_type,
 | 
				
			||||||
 | 
					                order_quantity=order_quantity,
 | 
				
			||||||
 | 
					                limit_price=limit_price,
 | 
				
			||||||
 | 
					                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,
 | 
				
			||||||
 | 
					                max_risk_percent=max_risk_percent,
 | 
				
			||||||
 | 
					                switch_side_mode=switch_side_mode,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return "OK"
 | 
				
			||||||
 | 
					        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",
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else None
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error in start_trading: %s", e)
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def trading_cycle(
 | 
				
			||||||
 | 
					    tg_id: int, symbol: str, reverse_side: str, size: str
 | 
				
			||||||
 | 
					) -> str | None:
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        user_deals_data = await rq.get_user_deal_by_symbol(tg_id=tg_id, symbol=symbol)
 | 
				
			||||||
 | 
					        trade_mode = user_deals_data.trade_mode
 | 
				
			||||||
 | 
					        order_type = user_deals_data.order_type
 | 
				
			||||||
 | 
					        conditional_order_type = user_deals_data.conditional_order_type
 | 
				
			||||||
 | 
					        margin_type = user_deals_data.margin_type
 | 
				
			||||||
 | 
					        leverage = user_deals_data.leverage
 | 
				
			||||||
 | 
					        leverage_to_buy = user_deals_data.leverage_to_buy
 | 
				
			||||||
 | 
					        leverage_to_sell = user_deals_data.leverage_to_sell
 | 
				
			||||||
 | 
					        limit_price = user_deals_data.limit_price
 | 
				
			||||||
 | 
					        trigger_price = user_deals_data.trigger_price
 | 
				
			||||||
 | 
					        take_profit_percent = user_deals_data.take_profit_percent
 | 
				
			||||||
 | 
					        stop_loss_percent = user_deals_data.stop_loss_percent
 | 
				
			||||||
 | 
					        max_risk_percent = user_deals_data.max_risk_percent
 | 
				
			||||||
 | 
					        max_bets_in_series = user_deals_data.max_bets_in_series
 | 
				
			||||||
 | 
					        martingale_factor = user_deals_data.martingale_factor
 | 
				
			||||||
 | 
					        current_step = user_deals_data.current_step
 | 
				
			||||||
 | 
					        switch_side_mode = user_deals_data.switch_side_mode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        mode = 3 if trade_mode == "Both_Sides" else 0
 | 
				
			||||||
 | 
					        await set_switch_position_mode(tg_id=tg_id, symbol=symbol, mode=mode)
 | 
				
			||||||
 | 
					        await set_margin_mode(tg_id=tg_id, margin_mode=margin_type)
 | 
				
			||||||
 | 
					        if trade_mode == "Both_Sides" and margin_type == "ISOLATED_MARGIN":
 | 
				
			||||||
 | 
					            await set_leverage_to_buy_and_sell(
 | 
				
			||||||
 | 
					                tg_id=tg_id,
 | 
				
			||||||
 | 
					                symbol=symbol,
 | 
				
			||||||
 | 
					                leverage_to_buy=leverage_to_buy,
 | 
				
			||||||
 | 
					                leverage_to_sell=leverage_to_sell,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            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 switch_side_mode:
 | 
				
			||||||
 | 
					            side = "Sell" if real_side == "Buy" else "Buy"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        next_quantity = safe_float(size) * (
 | 
				
			||||||
 | 
					            safe_float(martingale_factor) ** current_step
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        current_step += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if max_bets_in_series < current_step:
 | 
				
			||||||
 | 
					            return "Max bets in series"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        res = await open_positions(
 | 
				
			||||||
 | 
					            tg_id=tg_id,
 | 
				
			||||||
 | 
					            symbol=symbol,
 | 
				
			||||||
 | 
					            side=side,
 | 
				
			||||||
 | 
					            order_type="Market",
 | 
				
			||||||
 | 
					            conditional_order_type=conditional_order_type,
 | 
				
			||||||
 | 
					            order_quantity=next_quantity,
 | 
				
			||||||
 | 
					            limit_price=limit_price,
 | 
				
			||||||
 | 
					            trigger_price=trigger_price,
 | 
				
			||||||
 | 
					            trade_mode=trade_mode,
 | 
				
			||||||
 | 
					            margin_type=margin_type,
 | 
				
			||||||
 | 
					            leverage=leverage,
 | 
				
			||||||
 | 
					            leverage_to_buy=leverage_to_buy,
 | 
				
			||||||
 | 
					            leverage_to_sell=leverage_to_sell,
 | 
				
			||||||
 | 
					            take_profit_percent=take_profit_percent,
 | 
				
			||||||
 | 
					            stop_loss_percent=stop_loss_percent,
 | 
				
			||||||
 | 
					            max_risk_percent=max_risk_percent,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if res == "OK":
 | 
				
			||||||
 | 
					            await rq.set_user_deal(
 | 
				
			||||||
 | 
					                tg_id=tg_id,
 | 
				
			||||||
 | 
					                symbol=symbol,
 | 
				
			||||||
 | 
					                last_side=side,
 | 
				
			||||||
 | 
					                current_step=current_step,
 | 
				
			||||||
 | 
					                trade_mode=trade_mode,
 | 
				
			||||||
 | 
					                margin_type=margin_type,
 | 
				
			||||||
 | 
					                leverage=leverage,
 | 
				
			||||||
 | 
					                leverage_to_buy=leverage_to_buy,
 | 
				
			||||||
 | 
					                leverage_to_sell=leverage_to_sell,
 | 
				
			||||||
 | 
					                order_type=order_type,
 | 
				
			||||||
 | 
					                conditional_order_type=conditional_order_type,
 | 
				
			||||||
 | 
					                order_quantity=next_quantity,
 | 
				
			||||||
 | 
					                limit_price=limit_price,
 | 
				
			||||||
 | 
					                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,
 | 
				
			||||||
 | 
					                max_risk_percent=max_risk_percent,
 | 
				
			||||||
 | 
					                switch_side_mode=switch_side_mode,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            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: %s", e)
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def open_positions(
 | 
				
			||||||
 | 
					    tg_id: int,
 | 
				
			||||||
 | 
					    side: str,
 | 
				
			||||||
 | 
					    symbol: str,
 | 
				
			||||||
 | 
					    order_type: str,
 | 
				
			||||||
 | 
					    conditional_order_type: str,
 | 
				
			||||||
 | 
					    order_quantity: float,
 | 
				
			||||||
 | 
					    limit_price: float,
 | 
				
			||||||
 | 
					    trigger_price: float,
 | 
				
			||||||
 | 
					    trade_mode: str,
 | 
				
			||||||
 | 
					    margin_type: str,
 | 
				
			||||||
 | 
					    leverage: str,
 | 
				
			||||||
 | 
					    leverage_to_buy: str,
 | 
				
			||||||
 | 
					    leverage_to_sell: str,
 | 
				
			||||||
 | 
					    take_profit_percent: float,
 | 
				
			||||||
 | 
					    stop_loss_percent: float,
 | 
				
			||||||
 | 
					    max_risk_percent: float,
 | 
				
			||||||
 | 
					) -> str | None:
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        client = await get_bybit_client(tg_id=tg_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        risk_management_data = await rq.get_user_risk_management(tg_id=tg_id)
 | 
				
			||||||
 | 
					        commission_fee = risk_management_data.commission_fee
 | 
				
			||||||
 | 
					        wallet = await get_balance(tg_id=tg_id)
 | 
				
			||||||
 | 
					        user_balance = wallet.get("totalWalletBalance", 0)
 | 
				
			||||||
 | 
					        instruments_resp = await get_instruments_info(tg_id=tg_id, symbol=symbol)
 | 
				
			||||||
 | 
					        get_order_prices = instruments_resp.get("priceFilter")
 | 
				
			||||||
 | 
					        min_price = safe_float(get_order_prices.get("minPrice"))
 | 
				
			||||||
 | 
					        max_price = safe_float(get_order_prices.get("maxPrice"))
 | 
				
			||||||
 | 
					        get_ticker = await get_tickers(tg_id, symbol=symbol)
 | 
				
			||||||
 | 
					        price_symbol = safe_float(get_ticker.get("lastPrice")) or 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if order_type == "Conditional":
 | 
				
			||||||
 | 
					            po_trigger_price = str(trigger_price)
 | 
				
			||||||
 | 
					            trigger_direction = 1 if trigger_price > price_symbol else 2
 | 
				
			||||||
 | 
					            if conditional_order_type == "Limit":
 | 
				
			||||||
 | 
					                error = check_limit_price(limit_price, min_price, max_price)
 | 
				
			||||||
 | 
					                if error in {
 | 
				
			||||||
 | 
					                    "Limit price is out min price",
 | 
				
			||||||
 | 
					                    "Limit price is out max price",
 | 
				
			||||||
 | 
					                }:
 | 
				
			||||||
 | 
					                    return error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                order_type = "Limit"
 | 
				
			||||||
 | 
					                price_for_calc = limit_price
 | 
				
			||||||
 | 
					                tpsl_mode = "Partial"
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                order_type = "Market"
 | 
				
			||||||
 | 
					                price_for_calc = trigger_price
 | 
				
			||||||
 | 
					                tpsl_mode = "Full"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if order_type == "Limit":
 | 
				
			||||||
 | 
					                error = check_limit_price(limit_price, min_price, max_price)
 | 
				
			||||||
 | 
					                if error in {
 | 
				
			||||||
 | 
					                    "Limit price is out min price",
 | 
				
			||||||
 | 
					                    "Limit price is out max price",
 | 
				
			||||||
 | 
					                }:
 | 
				
			||||||
 | 
					                    return error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                price_for_calc = limit_price
 | 
				
			||||||
 | 
					                tpsl_mode = "Partial"
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                order_type = "Market"
 | 
				
			||||||
 | 
					                price_for_calc = price_symbol
 | 
				
			||||||
 | 
					                tpsl_mode = "Full"
 | 
				
			||||||
 | 
					            po_trigger_price = None
 | 
				
			||||||
 | 
					            trigger_direction = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if trade_mode == "Both_Sides":
 | 
				
			||||||
 | 
					            po_position_idx = 1 if side == "Buy" else 2
 | 
				
			||||||
 | 
					            if margin_type == "ISOLATED_MARGIN":
 | 
				
			||||||
 | 
					                get_leverage = safe_float(
 | 
				
			||||||
 | 
					                    leverage_to_buy if side == "Buy" else leverage_to_sell
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                get_leverage = safe_float(leverage)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            po_position_idx = 0
 | 
				
			||||||
 | 
					            get_leverage = safe_float(leverage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        potential_loss = (
 | 
				
			||||||
 | 
					            safe_float(order_quantity)
 | 
				
			||||||
 | 
					            * safe_float(price_for_calc)
 | 
				
			||||||
 | 
					            * (stop_loss_percent / 100)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        adjusted_loss = potential_loss / get_leverage
 | 
				
			||||||
 | 
					        allowed_loss = safe_float(user_balance) * (max_risk_percent / 100)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if adjusted_loss > allowed_loss:
 | 
				
			||||||
 | 
					            return "Risk is too high for this trade"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # 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"]
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        total_commission = price_for_calc * order_quantity * commission_fee_percent
 | 
				
			||||||
 | 
					        tp_multiplier = 1 + (take_profit_percent / 100)
 | 
				
			||||||
 | 
					        if total_commission > 0:
 | 
				
			||||||
 | 
					            tp_multiplier += total_commission
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if margin_type == "ISOLATED_MARGIN":
 | 
				
			||||||
 | 
					            liq_long, liq_short = await get_liquidation_price(
 | 
				
			||||||
 | 
					                tg_id=tg_id,
 | 
				
			||||||
 | 
					                entry_price=price_for_calc,
 | 
				
			||||||
 | 
					                symbol=symbol,
 | 
				
			||||||
 | 
					                leverage=get_leverage,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (liq_long > 0 or liq_short > 0) and price_for_calc > 0:
 | 
				
			||||||
 | 
					                if side == "Buy":
 | 
				
			||||||
 | 
					                    base_tp = price_for_calc + (price_for_calc - liq_long)
 | 
				
			||||||
 | 
					                    take_profit_price = base_tp + total_commission
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    base_tp = price_for_calc - (liq_short - price_for_calc)
 | 
				
			||||||
 | 
					                    take_profit_price = base_tp - total_commission
 | 
				
			||||||
 | 
					                take_profit_price = max(take_profit_price, 0)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                take_profit_price = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            stop_loss_price = None
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if side == "Buy":
 | 
				
			||||||
 | 
					                take_profit_price = price_for_calc * tp_multiplier
 | 
				
			||||||
 | 
					                stop_loss_price = price_for_calc * (1 - stop_loss_percent / 100)
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                take_profit_price = price_for_calc * (
 | 
				
			||||||
 | 
					                    1 - (take_profit_percent / 100) - total_commission
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                stop_loss_price = price_for_calc * (1 + stop_loss_percent / 100)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            take_profit_price = max(take_profit_price, 0)
 | 
				
			||||||
 | 
					            stop_loss_price = max(stop_loss_price, 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Place order
 | 
				
			||||||
 | 
					        order_params = {
 | 
				
			||||||
 | 
					            "category": "linear",
 | 
				
			||||||
 | 
					            "symbol": symbol,
 | 
				
			||||||
 | 
					            "side": side,
 | 
				
			||||||
 | 
					            "orderType": order_type,
 | 
				
			||||||
 | 
					            "qty": str(order_quantity),
 | 
				
			||||||
 | 
					            "triggerDirection": trigger_direction,
 | 
				
			||||||
 | 
					            "triggerPrice": po_trigger_price,
 | 
				
			||||||
 | 
					            "triggerBy": "LastPrice",
 | 
				
			||||||
 | 
					            "timeInForce": "GTC",
 | 
				
			||||||
 | 
					            "positionIdx": po_position_idx,
 | 
				
			||||||
 | 
					            "tpslMode": tpsl_mode,
 | 
				
			||||||
 | 
					            "takeProfit": str(take_profit_price) if take_profit_price else None,
 | 
				
			||||||
 | 
					            "stopLoss": str(stop_loss_price) if stop_loss_price else None,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if order_type == "Conditional":
 | 
				
			||||||
 | 
					            if conditional_order_type == "Limit":
 | 
				
			||||||
 | 
					                order_params["price"] = str(limit_price)
 | 
				
			||||||
 | 
					        if order_type == "Limit":
 | 
				
			||||||
 | 
					            order_params["price"] = str(limit_price)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        response = client.place_order(**order_params)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if response["retCode"] == 0:
 | 
				
			||||||
 | 
					            logger.info("Position opened for user: %s", tg_id)
 | 
				
			||||||
 | 
					            return "OK"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        logger.error("Error opening position for user: %s", tg_id)
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    except InvalidRequestError as e:
 | 
				
			||||||
 | 
					        error_text = str(e)
 | 
				
			||||||
 | 
					        known_errors = {
 | 
				
			||||||
 | 
					            "Order does not meet minimum order value": "Order does not meet minimum order value",
 | 
				
			||||||
 | 
					            "estimated will trigger liq": "estimated will trigger liq",
 | 
				
			||||||
 | 
					            "ab not enough for new order": "ab not enough for new order",
 | 
				
			||||||
 | 
					            "position idx not match position mode": "position idx not match position mode",
 | 
				
			||||||
 | 
					            "Qty invalid": "Qty invalid",
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        for key, msg in known_errors.items():
 | 
				
			||||||
 | 
					            if key in error_text:
 | 
				
			||||||
 | 
					                logger.error(msg)
 | 
				
			||||||
 | 
					                return msg
 | 
				
			||||||
 | 
					        logger.error("InvalidRequestError: %s", e)
 | 
				
			||||||
 | 
					        return "InvalidRequestError"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error opening position for user %s: %s", tg_id, e)
 | 
				
			||||||
 | 
					        return None
 | 
				
			||||||
							
								
								
									
										45
									
								
								app/bybit/profile_bybit.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								app/bybit/profile_bybit.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aiogram.fsm.context import FSMContext
 | 
				
			||||||
 | 
					from aiogram.types import Message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import app.telegram.keyboards.inline as kbi
 | 
				
			||||||
 | 
					import database.request as rq
 | 
				
			||||||
 | 
					from app.bybit.get_functions.get_balance import get_balance
 | 
				
			||||||
 | 
					from app.bybit.get_functions.get_tickers import get_tickers
 | 
				
			||||||
 | 
					from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("profile_bybit")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def user_profile_bybit(tg_id: int, message: Message, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """Get user profile bybit"""
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					        wallet = await get_balance(tg_id=tg_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if wallet:
 | 
				
			||||||
 | 
					            balance = wallet.get("totalWalletBalance", "0")
 | 
				
			||||||
 | 
					            symbol = await rq.get_user_symbol(tg_id=tg_id)
 | 
				
			||||||
 | 
					            get_tickers_info = await get_tickers(tg_id=tg_id, symbol=symbol)
 | 
				
			||||||
 | 
					            price_symbol = get_tickers_info.get("lastPrice") or 0
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                text=f"💎Ваш профиль Bybit:\n\n"
 | 
				
			||||||
 | 
					                     f"⚖️ Баланс: {float(balance):,.2f} USD\n"
 | 
				
			||||||
 | 
					                     f"📊Торговая пара: {symbol}\n"
 | 
				
			||||||
 | 
					                     f"$$$ Цена: {float(price_symbol):,.4f}\n\n"
 | 
				
			||||||
 | 
					                     f"Краткая инструкция:\n"
 | 
				
			||||||
 | 
					                     f"1. Укажите торговую пару (например: BTCUSDT).\n"
 | 
				
			||||||
 | 
					                     f"2. В настройках выставьте все необходимые параметры.\n"
 | 
				
			||||||
 | 
					                     f"3. Нажмите кнопку 'Начать торговлю' и выберите режим торговли.\n",
 | 
				
			||||||
 | 
					                reply_markup=kbi.main_menu,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                text="Ошибка при подключении, повторите попытку",
 | 
				
			||||||
 | 
					                reply_markup=kbi.connect_the_platform,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            logger.error("Error processing user profile for user %s", tg_id)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error processing user profile for user %s: %s", tg_id, e)
 | 
				
			||||||
							
								
								
									
										0
									
								
								app/bybit/set_functions/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								app/bybit/set_functions/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										96
									
								
								app/bybit/set_functions/set_leverage.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								app/bybit/set_functions/set_leverage.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from pybit import exceptions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from app.bybit import get_bybit_client
 | 
				
			||||||
 | 
					from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("set_leverage")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def set_leverage(tg_id: int, symbol: str, leverage: str) -> bool:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Set leverage
 | 
				
			||||||
 | 
					    :param tg_id: int - User ID
 | 
				
			||||||
 | 
					    :param symbol: str - Symbol
 | 
				
			||||||
 | 
					    :param leverage: str - Leverage
 | 
				
			||||||
 | 
					    :return: bool
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        client = await get_bybit_client(tg_id=tg_id)
 | 
				
			||||||
 | 
					        response = client.set_leverage(
 | 
				
			||||||
 | 
					            category="linear",
 | 
				
			||||||
 | 
					            symbol=symbol,
 | 
				
			||||||
 | 
					            buyLeverage=str(leverage),
 | 
				
			||||||
 | 
					            sellLeverage=str(leverage),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        if response["retCode"] == 0:
 | 
				
			||||||
 | 
					            logger.info(
 | 
				
			||||||
 | 
					                "Leverage set to %s for user: %s",
 | 
				
			||||||
 | 
					                leverage,
 | 
				
			||||||
 | 
					                tg_id,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logger.error("Error setting leverage: %s", response["retMsg"])
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					    except exceptions.InvalidRequestError as e:
 | 
				
			||||||
 | 
					        if "110043" in str(e):
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "Leverage set to %s for user: %s",
 | 
				
			||||||
 | 
					                leverage,
 | 
				
			||||||
 | 
					                tg_id,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            raise
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error connecting to Bybit for user %s: %s", tg_id, e)
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def set_leverage_to_buy_and_sell(
 | 
				
			||||||
 | 
					    tg_id: int, symbol: str, leverage_to_buy: str, leverage_to_sell: str
 | 
				
			||||||
 | 
					) -> bool:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Set leverage to buy and sell
 | 
				
			||||||
 | 
					    :param tg_id: int - User ID
 | 
				
			||||||
 | 
					    :param symbol: str - Symbol
 | 
				
			||||||
 | 
					    :param leverage_to_buy: str - Leverage to buy
 | 
				
			||||||
 | 
					    :param leverage_to_sell: str - Leverage to sell
 | 
				
			||||||
 | 
					    :return: bool
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        client = await get_bybit_client(tg_id=tg_id)
 | 
				
			||||||
 | 
					        response = client.set_leverage(
 | 
				
			||||||
 | 
					            category="linear",
 | 
				
			||||||
 | 
					            symbol=symbol,
 | 
				
			||||||
 | 
					            buyLeverage=str(leverage_to_buy),
 | 
				
			||||||
 | 
					            sellLeverage=str(leverage_to_sell),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        if response["retCode"] == 0:
 | 
				
			||||||
 | 
					            logger.info(
 | 
				
			||||||
 | 
					                "Leverage set to %s and %s for user: %s",
 | 
				
			||||||
 | 
					                leverage_to_buy,
 | 
				
			||||||
 | 
					                leverage_to_sell,
 | 
				
			||||||
 | 
					                tg_id,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logger.error("Error setting leverage for buy and sell for user: %s", tg_id)
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					    except exceptions.InvalidRequestError as e:
 | 
				
			||||||
 | 
					        if "110043" in str(e):
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "Leverage set to %s and %s for user: %s",
 | 
				
			||||||
 | 
					                leverage_to_buy,
 | 
				
			||||||
 | 
					                leverage_to_sell,
 | 
				
			||||||
 | 
					                tg_id,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            raise
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error connecting to Bybit for user %s: %s", tg_id, e)
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
							
								
								
									
										28
									
								
								app/bybit/set_functions/set_margin_mode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								app/bybit/set_functions/set_margin_mode.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from app.bybit import get_bybit_client
 | 
				
			||||||
 | 
					from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("set_margin_mode")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def set_margin_mode(tg_id: int, margin_mode: str) -> bool:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Set margin mode
 | 
				
			||||||
 | 
					    :param tg_id: int - User ID
 | 
				
			||||||
 | 
					    :param margin_mode: str - Margin mode
 | 
				
			||||||
 | 
					    :return: bool
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        client = await get_bybit_client(tg_id=tg_id)
 | 
				
			||||||
 | 
					        response = client.set_margin_mode(setMarginMode=margin_mode)
 | 
				
			||||||
 | 
					        if response["retCode"] == 0:
 | 
				
			||||||
 | 
					            logger.info("Margin mode set to %s for user: %s", margin_mode, tg_id)
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logger.error("Error setting margin mode: %s", tg_id)
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error connecting to Bybit for user %s: %s", tg_id, e)
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
							
								
								
									
										54
									
								
								app/bybit/set_functions/set_switch_position_mode.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								app/bybit/set_functions/set_switch_position_mode.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from app.bybit import get_bybit_client
 | 
				
			||||||
 | 
					from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("set_switch_position_mode")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def set_switch_position_mode(tg_id: int, symbol: str, mode: int) -> str | bool:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Set switch position mode
 | 
				
			||||||
 | 
					    :param tg_id: int - User ID
 | 
				
			||||||
 | 
					    :param symbol: str - Symbol
 | 
				
			||||||
 | 
					    :param mode: int - Mode
 | 
				
			||||||
 | 
					    :return: bool
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        client = await get_bybit_client(tg_id=tg_id)
 | 
				
			||||||
 | 
					        response = client.switch_position_mode(
 | 
				
			||||||
 | 
					            category="linear",
 | 
				
			||||||
 | 
					            symbol=symbol,
 | 
				
			||||||
 | 
					            mode=mode,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        if response["retCode"] == 0:
 | 
				
			||||||
 | 
					            logger.info("Switch position mode set successfully")
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logger.error("Error setting switch position mode for user: %s", tg_id)
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        if str(e).startswith("Position mode is not modified"):
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "Position mode is not modified for user: %s",
 | 
				
			||||||
 | 
					                tg_id,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        if str(e).startswith(
 | 
				
			||||||
 | 
					            "You have an existing position, so position mode cannot be switched"
 | 
				
			||||||
 | 
					        ):
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "You have an existing position, so position mode cannot be switched for user: %s",
 | 
				
			||||||
 | 
					                tg_id,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return "You have an existing position, so position mode cannot be switched"
 | 
				
			||||||
 | 
					        if str(e).startswith("Open orders exist, so you cannot change position mode"):
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "Open orders exist, so you cannot change position mode for user: %s",
 | 
				
			||||||
 | 
					                tg_id,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return "Open orders exist, so you cannot change position mode"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logger.error("Error connecting to Bybit for user %s: %s", tg_id, e)
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
							
								
								
									
										45
									
								
								app/bybit/set_functions/set_tp_sl.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								app/bybit/set_functions/set_tp_sl.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from app.bybit import get_bybit_client
 | 
				
			||||||
 | 
					from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("set_tp_sl")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def set_tp_sl_for_position(
 | 
				
			||||||
 | 
					    tg_id: int,
 | 
				
			||||||
 | 
					    symbol: str,
 | 
				
			||||||
 | 
					    take_profit_price: float,
 | 
				
			||||||
 | 
					    stop_loss_price: float,
 | 
				
			||||||
 | 
					    position_idx: int,
 | 
				
			||||||
 | 
					) -> bool:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Set take profit and stop loss for a symbol.
 | 
				
			||||||
 | 
					    :param tg_id: Telegram user ID
 | 
				
			||||||
 | 
					    :param symbol: Symbol to set take profit and stop loss for
 | 
				
			||||||
 | 
					    :param take_profit_price: Take profit price
 | 
				
			||||||
 | 
					    :param stop_loss_price: Stop loss price
 | 
				
			||||||
 | 
					    :param position_idx: Position index
 | 
				
			||||||
 | 
					    :return: bool
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        client = await get_bybit_client(tg_id)
 | 
				
			||||||
 | 
					        resp = client.set_trading_stop(
 | 
				
			||||||
 | 
					            category="linear",
 | 
				
			||||||
 | 
					            symbol=symbol,
 | 
				
			||||||
 | 
					            takeProfit=str(round(take_profit_price, 5)),
 | 
				
			||||||
 | 
					            stopLoss=str(round(stop_loss_price, 5)),
 | 
				
			||||||
 | 
					            positionIdx=position_idx,
 | 
				
			||||||
 | 
					            tpslMode="Full",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if resp.get("retCode") == 0:
 | 
				
			||||||
 | 
					            logger.info("TP/SL for %s has been set", symbol)
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            logger.error("Error setting TP/SL for %s: %s", symbol, resp.get("retMsg"))
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error setting TP/SL for %s: %s", symbol, e)
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
							
								
								
									
										237
									
								
								app/bybit/telegram_message_handler.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										237
									
								
								app/bybit/telegram_message_handler.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,237 @@
 | 
				
			|||||||
 | 
					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.helper_functions import format_value, safe_float, safe_int
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("telegram_message_handler")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class TelegramMessageHandler:
 | 
				
			||||||
 | 
					    def __init__(self, telegram_bot):
 | 
				
			||||||
 | 
					        self.telegram_bot = telegram_bot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def format_position_update(self, message):
 | 
				
			||||||
 | 
					        pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def format_order_update(self, message, tg_id):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            order_data = message.get("data", [{}])[0]
 | 
				
			||||||
 | 
					            symbol = format_value(order_data.get("symbol"))
 | 
				
			||||||
 | 
					            qty = format_value(order_data.get("qty"))
 | 
				
			||||||
 | 
					            order_type = format_value(order_data.get("orderType"))
 | 
				
			||||||
 | 
					            order_type_rus = (
 | 
				
			||||||
 | 
					                "Рыночный"
 | 
				
			||||||
 | 
					                if order_type == "Market"
 | 
				
			||||||
 | 
					                else "Лимитный" if order_type == "Limit" else "Нет данных"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            side = format_value(order_data.get("side"))
 | 
				
			||||||
 | 
					            side_rus = (
 | 
				
			||||||
 | 
					                "Покупка"
 | 
				
			||||||
 | 
					                if side == "Buy"
 | 
				
			||||||
 | 
					                else "Продажа" if side == "Sell" else "Нет данных"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            order_status = format_value(order_data.get("orderStatus"))
 | 
				
			||||||
 | 
					            price = format_value(order_data.get("price"))
 | 
				
			||||||
 | 
					            trigger_price = format_value(order_data.get("triggerPrice"))
 | 
				
			||||||
 | 
					            take_profit = format_value(order_data.get("takeProfit"))
 | 
				
			||||||
 | 
					            stop_loss = format_value(order_data.get("stopLoss"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            position_idx = safe_int(order_data.get("positionIdx"))
 | 
				
			||||||
 | 
					            position_idx_rus = (
 | 
				
			||||||
 | 
					                "Односторонний"
 | 
				
			||||||
 | 
					                if position_idx == 0
 | 
				
			||||||
 | 
					                else (
 | 
				
			||||||
 | 
					                    "Покупка в режиме хеджирования"
 | 
				
			||||||
 | 
					                    if position_idx == 1
 | 
				
			||||||
 | 
					                    else (
 | 
				
			||||||
 | 
					                        "Продажа в режиме хеджирования"
 | 
				
			||||||
 | 
					                        if position_idx == 2
 | 
				
			||||||
 | 
					                        else "Нет данных"
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            status_map = {
 | 
				
			||||||
 | 
					                "New": "Ордер создан",
 | 
				
			||||||
 | 
					                "Cancelled": "Ордер отменен",
 | 
				
			||||||
 | 
					                "Deactivated": "Ордер деактивирован",
 | 
				
			||||||
 | 
					                "Untriggered": "Условный ордер выставлен",
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if order_status == "Filled" or order_status not in status_map:
 | 
				
			||||||
 | 
					                return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            status_text = status_map[order_status]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            text = (
 | 
				
			||||||
 | 
					                f"{status_text}:\n"
 | 
				
			||||||
 | 
					                f"Торговая пара: {symbol}\n"
 | 
				
			||||||
 | 
					                f"Режим позиции: {position_idx_rus}\n"
 | 
				
			||||||
 | 
					                f"Количество: {qty}\n"
 | 
				
			||||||
 | 
					                f"Тип ордера: {order_type_rus}\n"
 | 
				
			||||||
 | 
					                f"Движение: {side_rus}\n"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            if price and price != "0":
 | 
				
			||||||
 | 
					                text += f"Цена: {price}\n"
 | 
				
			||||||
 | 
					            if take_profit and take_profit != "Нет данных":
 | 
				
			||||||
 | 
					                text += f"Тейк-профит: {take_profit}\n"
 | 
				
			||||||
 | 
					            if stop_loss and stop_loss != "Нет данных":
 | 
				
			||||||
 | 
					                text += f"Стоп-лосс: {stop_loss}\n"
 | 
				
			||||||
 | 
					            if trigger_price and trigger_price != "Нет данных":
 | 
				
			||||||
 | 
					                text += f"Триггер цена: {trigger_price}\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await self.telegram_bot.send_message(
 | 
				
			||||||
 | 
					                chat_id=tg_id, text=text, reply_markup=kbi.profile_bybit
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            logger.error("Error in format_order_update: %s", e)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def format_execution_update(self, message, tg_id):
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            execution = message.get("data", [{}])[0]
 | 
				
			||||||
 | 
					            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"))
 | 
				
			||||||
 | 
					            order_type = format_value(execution.get("orderType"))
 | 
				
			||||||
 | 
					            order_type_rus = (
 | 
				
			||||||
 | 
					                "Рыночный"
 | 
				
			||||||
 | 
					                if order_type == "Market"
 | 
				
			||||||
 | 
					                else "Лимитный" if order_type == "Limit" else "Нет данных"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            side = format_value(execution.get("side"))
 | 
				
			||||||
 | 
					            side_rus = (
 | 
				
			||||||
 | 
					                "Покупка"
 | 
				
			||||||
 | 
					                if side == "Buy"
 | 
				
			||||||
 | 
					                else "Продажа" if side == "Sell" else "Нет данных"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if safe_float(closed_size) == 0:
 | 
				
			||||||
 | 
					                await rq.set_fee_user_auto_trading(
 | 
				
			||||||
 | 
					                    tg_id=tg_id, symbol=symbol, side=side, fee=safe_float(exec_fee)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            if side == "Buy":
 | 
				
			||||||
 | 
					                res_side = "Sell"
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                res_side = "Buy"
 | 
				
			||||||
 | 
					            user_auto_trading = await rq.get_user_auto_trading(
 | 
				
			||||||
 | 
					                tg_id=tg_id, symbol=symbol, side=res_side
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if user_auto_trading is not None and user_auto_trading.fee is not None:
 | 
				
			||||||
 | 
					                fee = user_auto_trading.fee
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                fee = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            exec_pnl = format_value(execution.get("execPnl"))
 | 
				
			||||||
 | 
					            risk_management_data = await rq.get_user_risk_management(tg_id=tg_id)
 | 
				
			||||||
 | 
					            commission_fee = risk_management_data.commission_fee
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if commission_fee == "Yes_commission_fee":
 | 
				
			||||||
 | 
					                total_pnl = safe_float(exec_pnl) - safe_float(exec_fee) - fee
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                total_pnl = safe_float(exec_pnl)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            header = (
 | 
				
			||||||
 | 
					                "Сделка закрыта:" if safe_float(closed_size) > 0 else "Сделка открыта:"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            text = f"{header}\n" f"Торговая пара: {symbol}\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if safe_float(closed_size) > 0:
 | 
				
			||||||
 | 
					                text += f"Количество закрытых сделок: {closed_size}\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            text += (
 | 
				
			||||||
 | 
					                f"Цена исполнения: {exec_price}\n"
 | 
				
			||||||
 | 
					                f"Количество исполненных сделок: {exec_qty}\n"
 | 
				
			||||||
 | 
					                f"Тип ордера: {order_type_rus}\n"
 | 
				
			||||||
 | 
					                f"Движение: {side_rus}\n"
 | 
				
			||||||
 | 
					                f"Комиссия за сделку: {exec_fee}\n"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            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 (
 | 
				
			||||||
 | 
					                auto_trading
 | 
				
			||||||
 | 
					                and safe_float(closed_size) > 0
 | 
				
			||||||
 | 
					                and user_symbols is not None
 | 
				
			||||||
 | 
					            ):
 | 
				
			||||||
 | 
					                if safe_float(total_pnl) > 0:
 | 
				
			||||||
 | 
					                    profit_text = "📈 Прибыль достигнута\n"
 | 
				
			||||||
 | 
					                    await self.telegram_bot.send_message(
 | 
				
			||||||
 | 
					                        chat_id=tg_id, text=profit_text, reply_markup=kbi.profile_bybit
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    if side == "Buy":
 | 
				
			||||||
 | 
					                        r_side = "Sell"
 | 
				
			||||||
 | 
					                    else:
 | 
				
			||||||
 | 
					                        r_side = "Buy"
 | 
				
			||||||
 | 
					                    await rq.set_auto_trading(
 | 
				
			||||||
 | 
					                        tg_id=tg_id, symbol=symbol, auto_trading=False, side=r_side
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    user_deals_data = await rq.get_user_deal_by_symbol(
 | 
				
			||||||
 | 
					                        tg_id=tg_id, symbol=symbol
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    if user_deals_data and user_deals_data.switch_side_mode:
 | 
				
			||||||
 | 
					                        await rq.set_auto_trading(
 | 
				
			||||||
 | 
					                            tg_id=tg_id, symbol=symbol, auto_trading=False, side=side
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                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, size=closed_size
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    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, "❗️ Не удалось открыть новую сделку"
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        if side == "Buy":
 | 
				
			||||||
 | 
					                            r_side = "Sell"
 | 
				
			||||||
 | 
					                        else:
 | 
				
			||||||
 | 
					                            r_side = "Buy"
 | 
				
			||||||
 | 
					                        await rq.set_auto_trading(
 | 
				
			||||||
 | 
					                            tg_id=tg_id, symbol=symbol, auto_trading=False, side=r_side
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        user_deals_data = await rq.get_user_deal_by_symbol(
 | 
				
			||||||
 | 
					                            tg_id=tg_id, symbol=symbol
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        if user_deals_data and user_deals_data.switch_side_mode:
 | 
				
			||||||
 | 
					                            await rq.set_auto_trading(
 | 
				
			||||||
 | 
					                                tg_id=tg_id,
 | 
				
			||||||
 | 
					                                symbol=symbol,
 | 
				
			||||||
 | 
					                                auto_trading=False,
 | 
				
			||||||
 | 
					                                side=side,
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                        await self.telegram_bot.send_message(
 | 
				
			||||||
 | 
					                            chat_id=tg_id,
 | 
				
			||||||
 | 
					                            text=error_text,
 | 
				
			||||||
 | 
					                            reply_markup=kbi.profile_bybit,
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            logger.error("Error in telegram_message_handler: %s", e)
 | 
				
			||||||
							
								
								
									
										120
									
								
								app/bybit/web_socket.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								app/bybit/web_socket.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,120 @@
 | 
				
			|||||||
 | 
					import asyncio
 | 
				
			||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from pybit.unified_trading import WebSocket
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import database.request as rq
 | 
				
			||||||
 | 
					from app.bybit.logger_bybit.logger_bybit import LOGGING_CONFIG
 | 
				
			||||||
 | 
					from app.bybit.telegram_message_handler import TelegramMessageHandler
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("web_socket")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class WebSocketBot:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Class to handle WebSocket connections and messages.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, telegram_bot):
 | 
				
			||||||
 | 
					        """Initialize the TradingBot class."""
 | 
				
			||||||
 | 
					        self.telegram_bot = telegram_bot
 | 
				
			||||||
 | 
					        self.ws_private = None
 | 
				
			||||||
 | 
					        self.user_messages = {}
 | 
				
			||||||
 | 
					        self.user_sockets = {}
 | 
				
			||||||
 | 
					        self.user_keys = {}
 | 
				
			||||||
 | 
					        self.loop = None
 | 
				
			||||||
 | 
					        self.message_handler = TelegramMessageHandler(telegram_bot)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def run_user_check_loop(self):
 | 
				
			||||||
 | 
					        """Run a loop to check for users and connect them to the WebSocket."""
 | 
				
			||||||
 | 
					        self.loop = asyncio.get_running_loop()
 | 
				
			||||||
 | 
					        while True:
 | 
				
			||||||
 | 
					            users = await WebSocketBot.get_users_from_db()
 | 
				
			||||||
 | 
					            for user in users:
 | 
				
			||||||
 | 
					                tg_id = user.tg_id
 | 
				
			||||||
 | 
					                api_key, api_secret = await rq.get_user_api(tg_id=tg_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if not api_key or not api_secret:
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                keys_stored = self.user_keys.get(tg_id)
 | 
				
			||||||
 | 
					                if tg_id in self.user_sockets and keys_stored == (api_key, api_secret):
 | 
				
			||||||
 | 
					                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if tg_id in self.user_sockets:
 | 
				
			||||||
 | 
					                    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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                success = await self.try_connect_user(api_key, api_secret, tg_id)
 | 
				
			||||||
 | 
					                if success:
 | 
				
			||||||
 | 
					                    self.user_keys[tg_id] = (api_key, api_secret)
 | 
				
			||||||
 | 
					                    self.user_messages.setdefault(
 | 
				
			||||||
 | 
					                        tg_id, {"position": None, "order": None, "execution": None}
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    logger.info("User %s connected to WebSocket", tg_id)
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    await asyncio.sleep(30)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await asyncio.sleep(10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def clear_user_sockets(self):
 | 
				
			||||||
 | 
					        """Clear the user_sockets and user_messages dictionaries."""
 | 
				
			||||||
 | 
					        self.user_sockets.clear()
 | 
				
			||||||
 | 
					        self.user_messages.clear()
 | 
				
			||||||
 | 
					        self.user_keys.clear()
 | 
				
			||||||
 | 
					        logger.info("Cleared user_sockets")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def try_connect_user(self, api_key, api_secret, tg_id):
 | 
				
			||||||
 | 
					        """Try to connect a user to the WebSocket."""
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            self.ws_private = WebSocket(
 | 
				
			||||||
 | 
					                testnet=False,
 | 
				
			||||||
 | 
					                channel_type="private",
 | 
				
			||||||
 | 
					                api_key=api_key,
 | 
				
			||||||
 | 
					                api_secret=api_secret,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.user_sockets[tg_id] = self.ws_private
 | 
				
			||||||
 | 
					            # Connect to the WebSocket private channel
 | 
				
			||||||
 | 
					            # Handle position updates
 | 
				
			||||||
 | 
					            self.ws_private.position_stream(
 | 
				
			||||||
 | 
					                lambda msg: self.loop.call_soon_threadsafe(
 | 
				
			||||||
 | 
					                    asyncio.create_task, self.handle_position_update(msg)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            # Handle order updates
 | 
				
			||||||
 | 
					            self.ws_private.order_stream(
 | 
				
			||||||
 | 
					                lambda msg: self.loop.call_soon_threadsafe(
 | 
				
			||||||
 | 
					                    asyncio.create_task, self.handle_order_update(msg, tg_id)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            # Handle execution updates
 | 
				
			||||||
 | 
					            self.ws_private.execution_stream(
 | 
				
			||||||
 | 
					                lambda msg: self.loop.call_soon_threadsafe(
 | 
				
			||||||
 | 
					                    asyncio.create_task, self.handle_execution_update(msg, tg_id)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return True
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            logger.error("Error connecting user %s: %s", tg_id, e)
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def handle_position_update(self, message):
 | 
				
			||||||
 | 
					        """Handle position updates."""
 | 
				
			||||||
 | 
					        await self.message_handler.format_position_update(message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def handle_order_update(self, message, tg_id):
 | 
				
			||||||
 | 
					        """Handle order updates."""
 | 
				
			||||||
 | 
					        await self.message_handler.format_order_update(message, tg_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async def handle_execution_update(self, message, tg_id):
 | 
				
			||||||
 | 
					        """Handle execution updates."""
 | 
				
			||||||
 | 
					        await self.message_handler.format_execution_update(message, tg_id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @staticmethod
 | 
				
			||||||
 | 
					    async def get_users_from_db():
 | 
				
			||||||
 | 
					        """Get all users from the database."""
 | 
				
			||||||
 | 
					        return await rq.get_users()
 | 
				
			||||||
							
								
								
									
										186
									
								
								app/helper_functions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								app/helper_functions.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,186 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from app.bybit import get_bybit_client
 | 
				
			||||||
 | 
					from logger_helper.logger_helper import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("helper_functions")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def safe_float(val) -> float:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Function to safely convert string to float
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        if val is None or val == "":
 | 
				
			||||||
 | 
					            return 0.0
 | 
				
			||||||
 | 
					        return float(val)
 | 
				
			||||||
 | 
					    except (ValueError, TypeError):
 | 
				
			||||||
 | 
					        logger.error("Error converting value to float: %s", val)
 | 
				
			||||||
 | 
					        return 0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def is_number(value: str) -> bool:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Checks if a given string represents a number.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        value (str): The string to check.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Returns:
 | 
				
			||||||
 | 
					        bool: True if the string represents a number, False otherwise.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        # Convert the string to a float
 | 
				
			||||||
 | 
					        num = float(value)
 | 
				
			||||||
 | 
					        # Check if the number is positive
 | 
				
			||||||
 | 
					        if num <= 0:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        # Check if the string contains "+" or "-"
 | 
				
			||||||
 | 
					        if "+" in value or "-" in value:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        # Check if the string contains only digits
 | 
				
			||||||
 | 
					        allowed_chars = set("0123456789.")
 | 
				
			||||||
 | 
					        if not all(ch in allowed_chars for ch in value):
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					        return True
 | 
				
			||||||
 | 
					    except ValueError:
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def is_int(value: str) -> bool:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Checks if a given string represents an integer.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        value (str): The string to check.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Returns:
 | 
				
			||||||
 | 
					        bool: True if the string represents an integer, False otherwise.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    # Check if the string contains only digits
 | 
				
			||||||
 | 
					    if not value.isdigit():
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					    # Convert the string to an integer
 | 
				
			||||||
 | 
					    num = int(value)
 | 
				
			||||||
 | 
					    return num > 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def is_int_for_timer(value: str) -> bool | int:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Checks if a given string represents an integer for timer.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        value (str): The string to check.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Returns:
 | 
				
			||||||
 | 
					        bool: True if the string represents an integer, False otherwise.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    # Check if the string contains only digits
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        num = int(value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if num >= 0:
 | 
				
			||||||
 | 
					            return num
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            return False
 | 
				
			||||||
 | 
					    except ValueError:
 | 
				
			||||||
 | 
					        return False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def get_base_currency(symbol: str) -> str:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Extracts the base currency from a symbol string.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        symbol (str): The symbol string to extract the base currency from.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Returns:
 | 
				
			||||||
 | 
					        str: The base currency extracted from the symbol string.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    if symbol.endswith("USDT"):
 | 
				
			||||||
 | 
					        return symbol[:-4]
 | 
				
			||||||
 | 
					    return symbol
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def safe_int(value, default=0) -> int:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Integer conversion with default value.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        return int(value)
 | 
				
			||||||
 | 
					    except (ValueError, TypeError):
 | 
				
			||||||
 | 
					        return default
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def format_value(value) -> str:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Function to format value
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    if not value or value.strip() == "":
 | 
				
			||||||
 | 
					        return "Нет данных"
 | 
				
			||||||
 | 
					    return value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def check_limit_price(limit_price, min_price, max_price) -> str | None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Function to check limit price
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    if limit_price < min_price:
 | 
				
			||||||
 | 
					        return "Limit price is out min price"
 | 
				
			||||||
 | 
					    if limit_price > max_price:
 | 
				
			||||||
 | 
					        return "Limit price is out max price"
 | 
				
			||||||
 | 
					    return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def get_liquidation_price(
 | 
				
			||||||
 | 
					    tg_id: int, symbol: str, entry_price: float, leverage: float
 | 
				
			||||||
 | 
					) -> tuple[float, float]:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Function to get liquidation price
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        client = await get_bybit_client(tg_id=tg_id)
 | 
				
			||||||
 | 
					        get_risk_info = client.get_risk_limit(category="linear", symbol=symbol)
 | 
				
			||||||
 | 
					        risk_list = get_risk_info.get("result", {}).get("list", [])
 | 
				
			||||||
 | 
					        risk_level = risk_list[0] if risk_list else {}
 | 
				
			||||||
 | 
					        maintenance_margin_rate = safe_float(risk_level.get("maintenanceMargin"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        liq_price_long = entry_price * (1 - 1 / leverage + maintenance_margin_rate)
 | 
				
			||||||
 | 
					        liq_price_short = entry_price * (1 + 1 / leverage - maintenance_margin_rate)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        liq_price = liq_price_long, liq_price_short
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return liq_price
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error getting liquidation price: %s", e)
 | 
				
			||||||
 | 
					        return 0, 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def calculate_total_budget(
 | 
				
			||||||
 | 
					    quantity, martingale_factor, max_steps, commission_fee_percent
 | 
				
			||||||
 | 
					) -> float:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Calculate the total budget for a series of trading steps.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        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.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    total = 0
 | 
				
			||||||
 | 
					    for step in range(max_steps):
 | 
				
			||||||
 | 
					        set_quantity = quantity * (martingale_factor**step)
 | 
				
			||||||
 | 
					        if commission_fee_percent == 0:
 | 
				
			||||||
 | 
					            # Commission fee is not added to the position size
 | 
				
			||||||
 | 
					            r_quantity = set_quantity
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # Commission fee is added to the position size
 | 
				
			||||||
 | 
					            r_quantity = set_quantity * (1 + 2 * commission_fee_percent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        total += r_quantity
 | 
				
			||||||
 | 
					    return total
 | 
				
			||||||
@@ -1,111 +0,0 @@
 | 
				
			|||||||
from aiogram import F, Router
 | 
					 | 
				
			||||||
import logging.config
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from app.services.Bybit.functions.functions import start_bybit_trade_message
 | 
					 | 
				
			||||||
from logger_helper.logger_helper import LOGGING_CONFIG
 | 
					 | 
				
			||||||
import app.telegram.Keyboards.inline_keyboards as inline_markup
 | 
					 | 
				
			||||||
import app.telegram.Keyboards.reply_keyboards as reply_markup
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import app.telegram.functions.main_settings.settings as func_main_settings
 | 
					 | 
				
			||||||
import app.telegram.functions.risk_management_settings.settings as func_rmanagement_settings
 | 
					 | 
				
			||||||
import app.telegram.functions.condition_settings.settings as func_condition_settings
 | 
					 | 
				
			||||||
import app.telegram.functions.additional_settings.settings as func_additional_settings
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import app.telegram.database.requests as rq
 | 
					 | 
				
			||||||
from aiogram.types import Message, CallbackQuery
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from app.states.States import state_reg_bybit_api
 | 
					 | 
				
			||||||
from aiogram.fsm.context import FSMContext
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
logging.config.dictConfig(LOGGING_CONFIG)
 | 
					 | 
				
			||||||
logger = logging.getLogger("add_bybit_api")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
router_register_bybit_api = Router()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_register_bybit_api.callback_query(F.data == 'clb_new_user_connect_bybit_api_message')
 | 
					 | 
				
			||||||
async def info_for_bybit_api_message(callback: CallbackQuery) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Отвечает пользователю подробной инструкцией по подключению аккаунта Bybit.
 | 
					 | 
				
			||||||
    Показывает как создать API ключ и передать его чат-боту.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    text = '''<b>Подключение Bybit аккаунта</b>
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
<b>1. Зарегистрируйтесь или войдите в свой аккаунт на Bybit (https://www.bybit.com/).</b>
 | 
					 | 
				
			||||||
<b>2. В личном кабинете выберите раздел API. </b>  
 | 
					 | 
				
			||||||
<b>3. Создание нового API ключа</b>  
 | 
					 | 
				
			||||||
   - Нажмите кнопку Create New Key (Создать новый ключ).
 | 
					 | 
				
			||||||
   - Выберите системно-сгенерированный ключ.
 | 
					 | 
				
			||||||
   - Укажите название API ключа (любое).  
 | 
					 | 
				
			||||||
   - Выберите права доступа для торговли (Trade).  
 | 
					 | 
				
			||||||
   - Можно ограничить доступ по IP для безопасности.
 | 
					 | 
				
			||||||
<b>4. Подтверждение создания</b>  
 | 
					 | 
				
			||||||
   - Подтвердите создание ключа.
 | 
					 | 
				
			||||||
   - Отправьте чат-роботу.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<b>Важно: сохраните отдельно API Key и Secret Key в надежном месте. Secret ключ отображается только один раз. </b>            
 | 
					 | 
				
			||||||
    '''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await callback.message.answer(text=text, parse_mode='html', reply_markup=inline_markup.connect_bybit_api_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_register_bybit_api.callback_query(F.data == 'clb_new_user_connect_bybit_api')
 | 
					 | 
				
			||||||
async def add_api_key_message(callback: CallbackQuery, state: FSMContext) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Инициирует процесс добавления API ключа.
 | 
					 | 
				
			||||||
    Переводит пользователя в состояние ожидания ввода API Key.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await state.set_state(state_reg_bybit_api.api_key)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    text = 'Отправьте KEY_API ниже: '
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await callback.message.answer(text=text)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_register_bybit_api.message(state_reg_bybit_api.api_key)
 | 
					 | 
				
			||||||
async def add_api_key_and_message_for_secret_key(message: Message, state: FSMContext) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Сохраняет API Key во временное состояние FSM,
 | 
					 | 
				
			||||||
    затем запрашивает у пользователя ввод Secret Key.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await state.update_data(api_key=message.text)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    text = 'Отправьте SECRET_KEY ниже'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer(text=text)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await state.set_state(state_reg_bybit_api.secret_key)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_register_bybit_api.message(state_reg_bybit_api.secret_key)
 | 
					 | 
				
			||||||
async def add_secret_key(message: Message, state: FSMContext) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Сохраняет Secret Key и финализирует регистрацию,
 | 
					 | 
				
			||||||
    обновляет базу данных, устанавливает символ пользователя и очищает состояние.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await state.update_data(secret_key=message.text)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    data = await state.get_data()
 | 
					 | 
				
			||||||
    user = await rq.check_user(message.from_user.id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await rq.upsert_api_keys(message.from_user.id, data['api_key'], data['secret_key'])
 | 
					 | 
				
			||||||
    await rq.set_new_user_symbol(message.from_user.id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await state.clear()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer('Данные добавлены.',
 | 
					 | 
				
			||||||
                         reply_markup=reply_markup.base_buttons_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if user:
 | 
					 | 
				
			||||||
        await start_bybit_trade_message(message)
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        await rq.save_tg_id_new_user(message.from_user.id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await func_main_settings.reg_new_user_default_main_settings(message.from_user.id, message)
 | 
					 | 
				
			||||||
        await func_rmanagement_settings.reg_new_user_default_risk_management_settings(message.from_user.id,
 | 
					 | 
				
			||||||
                                                                                      message)
 | 
					 | 
				
			||||||
        await func_condition_settings.reg_new_user_default_condition_settings(message.from_user.id)
 | 
					 | 
				
			||||||
        await func_additional_settings.reg_new_user_default_additional_settings(message.from_user.id, message)
 | 
					 | 
				
			||||||
        await start_bybit_trade_message(message)
 | 
					 | 
				
			||||||
@@ -1,874 +0,0 @@
 | 
				
			|||||||
import asyncio
 | 
					 | 
				
			||||||
import logging.config
 | 
					 | 
				
			||||||
import time
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import app.services.Bybit.functions.balance as balance_g
 | 
					 | 
				
			||||||
import app.services.Bybit.functions.price_symbol as price_symbol
 | 
					 | 
				
			||||||
import app.telegram.database.requests as rq
 | 
					 | 
				
			||||||
import app.telegram.Keyboards.inline_keyboards as inline_markup
 | 
					 | 
				
			||||||
from logger_helper.logger_helper import LOGGING_CONFIG
 | 
					 | 
				
			||||||
from pybit import exceptions
 | 
					 | 
				
			||||||
from pybit.unified_trading import HTTP
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
logging.config.dictConfig(LOGGING_CONFIG)
 | 
					 | 
				
			||||||
logger = logging.getLogger("futures")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
processed_trade_ids = set()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_bybit_client(tg_id):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Асинхронно получает экземпляр клиента Bybit.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    :param tg_id: int - ID пользователя Telegram
 | 
					 | 
				
			||||||
    :return: HTTP - экземпляр клиента Bybit
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    api_key = await rq.get_bybit_api_key(tg_id)
 | 
					 | 
				
			||||||
    secret_key = await rq.get_bybit_secret_key(tg_id)
 | 
					 | 
				
			||||||
    return HTTP(api_key=api_key, api_secret=secret_key)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def safe_float(val) -> float:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Безопасное преобразование значения в float.
 | 
					 | 
				
			||||||
    Возвращает 0.0, если значение None, пустое или некорректное.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        if val is None or val == "":
 | 
					 | 
				
			||||||
            return 0.0
 | 
					 | 
				
			||||||
        return float(val)
 | 
					 | 
				
			||||||
    except (ValueError, TypeError):
 | 
					 | 
				
			||||||
        logger.error("Некорректное значение для преобразования в float", exc_info=True)
 | 
					 | 
				
			||||||
        return 0.0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def format_trade_details_position(data, commission_fee):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Форматирует информацию о сделке в виде строки.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    msg = data.get("data", [{}])[0]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    closed_size = safe_float(msg.get("closedSize", 0))
 | 
					 | 
				
			||||||
    symbol = msg.get("symbol", "N/A")
 | 
					 | 
				
			||||||
    entry_price = safe_float(msg.get("execPrice", 0))
 | 
					 | 
				
			||||||
    qty = safe_float(msg.get("execQty", 0))
 | 
					 | 
				
			||||||
    order_type = msg.get("orderType", "N/A")
 | 
					 | 
				
			||||||
    side = msg.get("side", "")
 | 
					 | 
				
			||||||
    commission = safe_float(msg.get("execFee", 0))
 | 
					 | 
				
			||||||
    pnl = safe_float(msg.get("execPnl", 0))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if commission_fee == "Да":
 | 
					 | 
				
			||||||
        pnl -= commission
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    movement = ""
 | 
					 | 
				
			||||||
    if side.lower() == "buy":
 | 
					 | 
				
			||||||
        movement = "Покупка"
 | 
					 | 
				
			||||||
    elif side.lower() == "sell":
 | 
					 | 
				
			||||||
        movement = "Продажа"
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        movement = side
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if closed_size > 0:
 | 
					 | 
				
			||||||
        return (
 | 
					 | 
				
			||||||
            f"Сделка закрыта:\n"
 | 
					 | 
				
			||||||
            f"Торговая пара: {symbol}\n"
 | 
					 | 
				
			||||||
            f"Цена исполнения: {entry_price:.6f}\n"
 | 
					 | 
				
			||||||
            f"Количество: {qty}\n"
 | 
					 | 
				
			||||||
            f"Закрыто позиций: {closed_size}\n"
 | 
					 | 
				
			||||||
            f"Тип ордера: {order_type}\n"
 | 
					 | 
				
			||||||
            f"Движение: {movement}\n"
 | 
					 | 
				
			||||||
            f"Комиссия за сделку: {commission:.6f}\n"
 | 
					 | 
				
			||||||
            f"Реализованная прибыль: {pnl:.6f} USDT"
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    if order_type == "Market":
 | 
					 | 
				
			||||||
        return (
 | 
					 | 
				
			||||||
            f"Сделка открыта:\n"
 | 
					 | 
				
			||||||
            f"Торговая пара: {symbol}\n"
 | 
					 | 
				
			||||||
            f"Цена исполнения: {entry_price:.6f}\n"
 | 
					 | 
				
			||||||
            f"Количество: {qty}\n"
 | 
					 | 
				
			||||||
            f"Тип ордера: {order_type}\n"
 | 
					 | 
				
			||||||
            f"Движение: {movement}\n"
 | 
					 | 
				
			||||||
            f"Комиссия за сделку: {commission:.6f}"
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def format_order_details_position(data):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Форматирует информацию об ордере в виде строки.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    msg = data.get("data", [{}])[0]
 | 
					 | 
				
			||||||
    price = safe_float(msg.get("price", 0))
 | 
					 | 
				
			||||||
    qty = safe_float(msg.get("qty", 0))
 | 
					 | 
				
			||||||
    cum_exec_qty = safe_float(msg.get("cumExecQty", 0))
 | 
					 | 
				
			||||||
    cum_exec_fee = safe_float(msg.get("cumExecFee", 0))
 | 
					 | 
				
			||||||
    take_profit = safe_float(msg.get("takeProfit", 0))
 | 
					 | 
				
			||||||
    stop_loss = safe_float(msg.get("stopLoss", 0))
 | 
					 | 
				
			||||||
    order_status = msg.get("orderStatus", "N/A")
 | 
					 | 
				
			||||||
    symbol = msg.get("symbol", "N/A")
 | 
					 | 
				
			||||||
    order_type = msg.get("orderType", "N/A")
 | 
					 | 
				
			||||||
    side = msg.get("side", "")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    movement = ""
 | 
					 | 
				
			||||||
    if side.lower() == "buy":
 | 
					 | 
				
			||||||
        movement = "Покупка"
 | 
					 | 
				
			||||||
    elif side.lower() == "sell":
 | 
					 | 
				
			||||||
        movement = "Продажа"
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        movement = side
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if order_status.lower() == "filled" and order_type.lower() == "limit":
 | 
					 | 
				
			||||||
        text = (
 | 
					 | 
				
			||||||
            f"Ордер исполнен:\n"
 | 
					 | 
				
			||||||
            f"Торговая пара: {symbol}\n"
 | 
					 | 
				
			||||||
            f"Цена исполнения: {price:.6f}\n"
 | 
					 | 
				
			||||||
            f"Количество: {qty}\n"
 | 
					 | 
				
			||||||
            f"Исполнено позиций: {cum_exec_qty}\n"
 | 
					 | 
				
			||||||
            f"Тип ордера: {order_type}\n"
 | 
					 | 
				
			||||||
            f"Движение: {movement}\n"
 | 
					 | 
				
			||||||
            f"Тейк-профит: {take_profit:.6f}\n"
 | 
					 | 
				
			||||||
            f"Стоп-лосс: {stop_loss:.6f}\n"
 | 
					 | 
				
			||||||
            f"Комиссия за сделку: {cum_exec_fee:.6f}\n"
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        return text
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    elif order_status.lower() == "new":
 | 
					 | 
				
			||||||
        text = (
 | 
					 | 
				
			||||||
            f"Ордер создан:\n"
 | 
					 | 
				
			||||||
            f"Торговая пара: {symbol}\n"
 | 
					 | 
				
			||||||
            f"Цена: {price:.6f}\n"
 | 
					 | 
				
			||||||
            f"Количество: {qty}\n"
 | 
					 | 
				
			||||||
            f"Тип ордера: {order_type}\n"
 | 
					 | 
				
			||||||
            f"Движение: {movement}\n"
 | 
					 | 
				
			||||||
            f"Тейк-профит: {take_profit:.6f}\n"
 | 
					 | 
				
			||||||
            f"Стоп-лосс: {stop_loss:.6f}\n"
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        return text
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    elif order_status.lower() == "cancelled":
 | 
					 | 
				
			||||||
        text = (
 | 
					 | 
				
			||||||
            f"Ордер отменен:\n"
 | 
					 | 
				
			||||||
            f"Торговая пара: {symbol}\n"
 | 
					 | 
				
			||||||
            f"Цена: {price:.6f}\n"
 | 
					 | 
				
			||||||
            f"Количество: {qty}\n"
 | 
					 | 
				
			||||||
            f"Тип ордера: {order_type}\n"
 | 
					 | 
				
			||||||
            f"Движение: {movement}\n"
 | 
					 | 
				
			||||||
            f"Тейк-профит: {take_profit:.6f}\n"
 | 
					 | 
				
			||||||
            f"Стоп-лосс: {stop_loss:.6f}\n"
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        return text
 | 
					 | 
				
			||||||
    return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def parse_pnl_from_msg(msg) -> float:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Извлекает реализованную прибыль/убыток из сообщения.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        data = msg.get("data", [{}])[0]
 | 
					 | 
				
			||||||
        return float(data.get("execPnl", 0))
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        logger.error("Ошибка при извлечении реализованной прибыли: %s", e)
 | 
					 | 
				
			||||||
        return 0.0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def calculate_total_budget(starting_quantity, martingale_factor, max_steps, commission_fee_percent, leverage, current_price):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Вычисляет общий бюджет серии ставок с учётом цены пары, комиссии и кредитного плеча.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Параметры:
 | 
					 | 
				
			||||||
    - starting_quantity_usdt: стартовый размер ставки в долларах (USD)
 | 
					 | 
				
			||||||
    - martingale_factor: множитель увеличения ставки при каждом проигрыше
 | 
					 | 
				
			||||||
    - max_steps: максимальное количество шагов удвоения ставки
 | 
					 | 
				
			||||||
    - commission_fee_percent: процент комиссии на одну операцию (открытие или закрытие)
 | 
					 | 
				
			||||||
    - leverage: кредитное плечо
 | 
					 | 
				
			||||||
    - current_price: текущая цена актива (например BTCUSDT)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Возвращает:
 | 
					 | 
				
			||||||
    - общий бюджет в долларах, который необходимо иметь на счету
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    total = 0
 | 
					 | 
				
			||||||
    for step in range(max_steps):
 | 
					 | 
				
			||||||
        quantity = starting_quantity * (martingale_factor ** step)  # размер ставки на текущем шаге в USDT
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Переводим ставку из USDT в количество актива по текущей цене
 | 
					 | 
				
			||||||
        quantity_in_asset = quantity / current_price
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Учитываем комиссию за вход и выход (умножаем на 2)
 | 
					 | 
				
			||||||
        quantity_with_fee = quantity * (1 + 2 * commission_fee_percent / 100)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Учитываем кредитное плечо - реальные собственные вложения меньше
 | 
					 | 
				
			||||||
        effective_quantity = quantity_with_fee / leverage
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        total += effective_quantity
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Возвращаем бюджет в USDT
 | 
					 | 
				
			||||||
    total_usdt = total * current_price
 | 
					 | 
				
			||||||
    return total_usdt
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def handle_execution_message(message, msg):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Обработчик сообщений об исполнении сделки.
 | 
					 | 
				
			||||||
    Логирует событие и проверяет условия для мартингейла и TP.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    tg_id = message.from_user.id
 | 
					 | 
				
			||||||
    data = msg.get("data", [{}])[0]
 | 
					 | 
				
			||||||
    data_main_risk_stgs = await rq.get_user_risk_management_settings(tg_id)
 | 
					 | 
				
			||||||
    commission_fee = data_main_risk_stgs.get("commission_fee", "ДА")
 | 
					 | 
				
			||||||
    pnl = parse_pnl_from_msg(msg)
 | 
					 | 
				
			||||||
    data_main_stgs = await rq.get_user_main_settings(tg_id)
 | 
					 | 
				
			||||||
    symbol = data.get("symbol")
 | 
					 | 
				
			||||||
    trading_mode = data_main_stgs.get("trading_mode", "Long")
 | 
					 | 
				
			||||||
    trigger = await rq.get_for_registration_trigger(tg_id)
 | 
					 | 
				
			||||||
    margin_mode = data_main_stgs.get("margin_type", "Isolated")
 | 
					 | 
				
			||||||
    starting_quantity = safe_float(data_main_stgs.get("starting_quantity"))
 | 
					 | 
				
			||||||
    martingale_factor = safe_float(data_main_stgs.get("martingale_factor"))
 | 
					 | 
				
			||||||
    closed_size = safe_float(data.get("closedSize", 0))
 | 
					 | 
				
			||||||
    commission = safe_float(data.get("execFee", 0))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if commission_fee == "Да":
 | 
					 | 
				
			||||||
        pnl -= commission
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    trade_info = format_trade_details_position(
 | 
					 | 
				
			||||||
        data=msg,
 | 
					 | 
				
			||||||
        commission_fee=commission_fee
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if trade_info:
 | 
					 | 
				
			||||||
        await message.answer(f"{trade_info}", reply_markup=inline_markup.back_to_main)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if closed_size == 0:
 | 
					 | 
				
			||||||
        side = data.get("side", "")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if side.lower() == "buy":
 | 
					 | 
				
			||||||
            await rq.set_last_series_info(tg_id, last_side="Buy")
 | 
					 | 
				
			||||||
        elif side.lower() == "sell":
 | 
					 | 
				
			||||||
            await rq.set_last_series_info(tg_id, last_side="Sell")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if trigger == "Автоматический" and closed_size > 0:
 | 
					 | 
				
			||||||
        if pnl < 0:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if trading_mode == 'Switch':
 | 
					 | 
				
			||||||
                side = data_main_stgs.get("last_side")
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                side = "Buy" if trading_mode == "Long" else "Sell"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            current_martingale = await rq.get_martingale_step(tg_id)
 | 
					 | 
				
			||||||
            current_martingale_step = int(current_martingale)
 | 
					 | 
				
			||||||
            current_martingale += 1
 | 
					 | 
				
			||||||
            next_quantity = float(starting_quantity) * (
 | 
					 | 
				
			||||||
                    float(martingale_factor) ** current_martingale_step
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            await rq.update_martingale_step(tg_id, current_martingale)
 | 
					 | 
				
			||||||
            await message.answer(
 | 
					 | 
				
			||||||
                f"❗️ Сделка закрылась в минус, открываю новую сделку с увеличенной ставкой.\n"
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            await open_position(
 | 
					 | 
				
			||||||
                tg_id,
 | 
					 | 
				
			||||||
                message,
 | 
					 | 
				
			||||||
                side=side,
 | 
					 | 
				
			||||||
                margin_mode=margin_mode,
 | 
					 | 
				
			||||||
                symbol=symbol,
 | 
					 | 
				
			||||||
                quantity=next_quantity,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        elif pnl > 0:
 | 
					 | 
				
			||||||
            await rq.update_martingale_step(tg_id, 0)
 | 
					 | 
				
			||||||
            await message.answer(
 | 
					 | 
				
			||||||
                "❗️ Прибыль достигнута, шаг мартингейла сброшен."
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def handle_order_message(message, msg: dict) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Обработчик сообщений об исполнении ордера.
 | 
					 | 
				
			||||||
    Логирует событие и проверяет условия для мартингейла и TP.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    # logger.info(f"Исполнен ордер:\n{json.dumps(msg, indent=4, ensure_ascii=False)}")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    trade_info = format_order_details_position(msg)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if trade_info:
 | 
					 | 
				
			||||||
        await message.answer(f"{trade_info}", reply_markup=inline_markup.back_to_main)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def error_max_step(message) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Сообщение об ошибке превышения максимального количества шагов мартингейла.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    logger.error(
 | 
					 | 
				
			||||||
        "Сделка не была совершена, превышен лимит максимального количества ставок в серии."
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    await message.answer(
 | 
					 | 
				
			||||||
        "Сделка не была совершена, превышен лимит максимального количества ставок в серии.",
 | 
					 | 
				
			||||||
        reply_markup=inline_markup.back_to_main,
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def error_max_risk(message) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Сообщение об ошибке превышения риск-лимита сделки.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    logger.error("Сделка не была совершена, риск убытка превышает допустимый лимит.")
 | 
					 | 
				
			||||||
    await message.answer(
 | 
					 | 
				
			||||||
        "Сделка не была совершена, риск убытка превышает допустимый лимит.",
 | 
					 | 
				
			||||||
        reply_markup=inline_markup.back_to_main,
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def open_position(
 | 
					 | 
				
			||||||
        tg_id, message, side: str, margin_mode: str, symbol, quantity, tpsl_mode="Full"
 | 
					 | 
				
			||||||
):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Открывает позицию на Bybit с учётом настроек пользователя, маржи, размера лота, платформы и риска.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Возвращает True при успехе, False при ошибках открытия ордера, None при исключениях.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        client = await get_bybit_client(tg_id)
 | 
					 | 
				
			||||||
        data_main_stgs = await rq.get_user_main_settings(tg_id)
 | 
					 | 
				
			||||||
        order_type = data_main_stgs.get("entry_order_type")
 | 
					 | 
				
			||||||
        bybit_margin_mode = (
 | 
					 | 
				
			||||||
            "ISOLATED_MARGIN" if margin_mode == "Isolated" else "REGULAR_MARGIN"
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        limit_price = None
 | 
					 | 
				
			||||||
        if order_type == "Limit":
 | 
					 | 
				
			||||||
            limit_price = await rq.get_limit_price(tg_id)
 | 
					 | 
				
			||||||
        data_risk_stgs = await rq.get_user_risk_management_settings(tg_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        price = await price_symbol.get_price(tg_id, symbol=symbol)
 | 
					 | 
				
			||||||
        entry_price = safe_float(price)
 | 
					 | 
				
			||||||
        leverage = safe_float(data_main_stgs.get("size_leverage", 1))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        max_martingale_steps = int(data_main_stgs.get("maximal_quantity", 0))
 | 
					 | 
				
			||||||
        current_martingale = await rq.get_martingale_step(tg_id)
 | 
					 | 
				
			||||||
        max_risk_percent = safe_float(data_risk_stgs.get("max_risk_deal"))
 | 
					 | 
				
			||||||
        loss_profit = safe_float(data_risk_stgs.get("price_loss"))
 | 
					 | 
				
			||||||
        commission_fee = data_risk_stgs.get("commission_fee")
 | 
					 | 
				
			||||||
        starting_quantity = safe_float(data_main_stgs.get('starting_quantity'))
 | 
					 | 
				
			||||||
        martingale_factor = safe_float(data_main_stgs.get('martingale_factor'))
 | 
					 | 
				
			||||||
        fee_info = client.get_fee_rates(category='linear', symbol=symbol)
 | 
					 | 
				
			||||||
        instruments_resp = client.get_instruments_info(category="linear", symbol=symbol)
 | 
					 | 
				
			||||||
        instrument = instruments_resp.get("result", {}).get("list", [])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if commission_fee == "Да":
 | 
					 | 
				
			||||||
            commission_fee_percent = safe_float(fee_info['result']['list'][0]['takerFeeRate'])
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            commission_fee_percent = 0.0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        total_budget = await calculate_total_budget(
 | 
					 | 
				
			||||||
            starting_quantity=starting_quantity,
 | 
					 | 
				
			||||||
            martingale_factor=martingale_factor,
 | 
					 | 
				
			||||||
            max_steps=max_martingale_steps,
 | 
					 | 
				
			||||||
            commission_fee_percent=commission_fee_percent,
 | 
					 | 
				
			||||||
            leverage=leverage,
 | 
					 | 
				
			||||||
            current_price=entry_price,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        balance = await balance_g.get_balance(tg_id, message)
 | 
					 | 
				
			||||||
        if safe_float(balance) < total_budget:
 | 
					 | 
				
			||||||
            logger.error(
 | 
					 | 
				
			||||||
                f"Недостаточно средств для серии из {max_martingale_steps} шагов с текущими параметрами. "
 | 
					 | 
				
			||||||
                f"Требуемый бюджет: {total_budget:.2f} USDT, доступно: {balance} USDT."
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            await message.answer(
 | 
					 | 
				
			||||||
                f"Недостаточно средств для серии из {max_martingale_steps} шагов с текущими параметрами. "
 | 
					 | 
				
			||||||
                f"Требуемый бюджет: {total_budget:.2f} USDT, доступно: {balance} USDT.",
 | 
					 | 
				
			||||||
                reply_markup=inline_markup.back_to_main,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if order_type == "Limit" and limit_price:
 | 
					 | 
				
			||||||
            price_for_calc = limit_price
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            price_for_calc = entry_price
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        potential_loss = safe_float(quantity) * price_for_calc * (loss_profit / 100)
 | 
					 | 
				
			||||||
        adjusted_loss = potential_loss / leverage
 | 
					 | 
				
			||||||
        allowed_loss = safe_float(balance) * (max_risk_percent / 100)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if adjusted_loss > allowed_loss:
 | 
					 | 
				
			||||||
            await error_max_risk(message)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if max_martingale_steps < current_martingale:
 | 
					 | 
				
			||||||
            await error_max_step(message)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        client.set_margin_mode(setMarginMode=bybit_margin_mode)
 | 
					 | 
				
			||||||
        max_leverage = safe_float(instrument[0].get("leverageFilter", {}).get("maxLeverage", 0))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if safe_float(leverage) > max_leverage:
 | 
					 | 
				
			||||||
            await message.answer(
 | 
					 | 
				
			||||||
                f"Запрошенное кредитное плечо {leverage} превышает максимальное {max_leverage} для {symbol}. "
 | 
					 | 
				
			||||||
                f"Устанавливаю максимальное.",
 | 
					 | 
				
			||||||
                reply_markup=inline_markup.back_to_main,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            logger.info(
 | 
					 | 
				
			||||||
                f"Запрошенное кредитное плечо {leverage} превышает максимальное {max_leverage} для {symbol}. Устанавливаю максимальное.")
 | 
					 | 
				
			||||||
            leverage_to_set = max_leverage
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            leverage_to_set = safe_float(leverage)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            client.set_leverage(
 | 
					 | 
				
			||||||
                category="linear",
 | 
					 | 
				
			||||||
                symbol=symbol,
 | 
					 | 
				
			||||||
                buyLeverage=str(leverage_to_set),
 | 
					 | 
				
			||||||
                sellLeverage=str(leverage_to_set),
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            logger.info(f"Set leverage to {leverage_to_set} for {symbol}")
 | 
					 | 
				
			||||||
        except exceptions.InvalidRequestError as e:
 | 
					 | 
				
			||||||
            if "110043" in str(e):
 | 
					 | 
				
			||||||
                logger.info(f"Leverage already set to {leverage} for {symbol}")
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                raise e
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if instruments_resp.get("retCode") == 0:
 | 
					 | 
				
			||||||
            instrument_info = instruments_resp.get("result", {}).get("list", [])
 | 
					 | 
				
			||||||
            if instrument_info:
 | 
					 | 
				
			||||||
                instrument_info = instrument_info[0]
 | 
					 | 
				
			||||||
                min_notional_value = float(instrument_info.get("lotSizeFilter", {}).get("minNotionalValue", 0))
 | 
					 | 
				
			||||||
                min_order_value = min_notional_value
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                min_order_value = 5.0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        order_value = float(quantity) * price_for_calc
 | 
					 | 
				
			||||||
        if order_value < min_order_value:
 | 
					 | 
				
			||||||
            logger.error(
 | 
					 | 
				
			||||||
                f"Сумма ордера слишком мала: {order_value:.2f} USDT. "
 | 
					 | 
				
			||||||
                f"Минимум для торговли — {min_order_value} USDT. "
 | 
					 | 
				
			||||||
                f"Пожалуйста, увеличьте количество позиций."
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            await message.answer(
 | 
					 | 
				
			||||||
                f"Сумма ордера слишком мала: {order_value:.2f} USDT. "
 | 
					 | 
				
			||||||
                f"Минимум для торговли — {min_order_value} USDT. "
 | 
					 | 
				
			||||||
                f"Пожалуйста, увеличьте количество позиций.",
 | 
					 | 
				
			||||||
                reply_markup=inline_markup.back_to_main,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if bybit_margin_mode == "ISOLATED_MARGIN":
 | 
					 | 
				
			||||||
            # Открываем позицию
 | 
					 | 
				
			||||||
            response = client.place_order(
 | 
					 | 
				
			||||||
                category="linear",
 | 
					 | 
				
			||||||
                symbol=symbol,
 | 
					 | 
				
			||||||
                side=side,
 | 
					 | 
				
			||||||
                orderType=order_type,
 | 
					 | 
				
			||||||
                qty=str(quantity),
 | 
					 | 
				
			||||||
                price=(
 | 
					 | 
				
			||||||
                    str(limit_price) if order_type == "Limit" and limit_price else None
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                timeInForce="GTC",
 | 
					 | 
				
			||||||
                orderLinkId=f"deal_{symbol}_{int(time.time())}",
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            if response.get("retCode", -1) != 0:
 | 
					 | 
				
			||||||
                logger.error(f"Ошибка открытия ордера: {response}")
 | 
					 | 
				
			||||||
                await message.answer(
 | 
					 | 
				
			||||||
                    f"Ошибка открытия ордера", reply_markup=inline_markup.back_to_main
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            # Получаем цену ликвидации
 | 
					 | 
				
			||||||
            positions = client.get_positions(category="linear", symbol=symbol)
 | 
					 | 
				
			||||||
            pos = positions.get("result", {}).get("list", [{}])[0]
 | 
					 | 
				
			||||||
            avg_price = float(pos.get("avgPrice", 0))
 | 
					 | 
				
			||||||
            liq_price = safe_float(pos.get("liqPrice", 0))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if liq_price > 0 and avg_price > 0:
 | 
					 | 
				
			||||||
                if side.lower() == "buy":
 | 
					 | 
				
			||||||
                    take_profit_price = avg_price + (avg_price - liq_price)
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    take_profit_price = avg_price - (liq_price - avg_price)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                take_profit_price = max(take_profit_price, 0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                try:
 | 
					 | 
				
			||||||
                    try:
 | 
					 | 
				
			||||||
                        client.set_tp_sl_mode(
 | 
					 | 
				
			||||||
                            symbol=symbol, category="linear", tpSlMode="Full"
 | 
					 | 
				
			||||||
                        )
 | 
					 | 
				
			||||||
                    except exceptions.InvalidRequestError as e:
 | 
					 | 
				
			||||||
                        if "same tp sl mode" in str(e):
 | 
					 | 
				
			||||||
                            logger.info("Режим TP/SL уже установлен - пропускаем")
 | 
					 | 
				
			||||||
                        else:
 | 
					 | 
				
			||||||
                            raise
 | 
					 | 
				
			||||||
                    resp = client.set_trading_stop(
 | 
					 | 
				
			||||||
                        category="linear",
 | 
					 | 
				
			||||||
                        symbol=symbol,
 | 
					 | 
				
			||||||
                        takeProfit=str(round(take_profit_price, 5)),
 | 
					 | 
				
			||||||
                        tpTriggerBy="LastPrice",
 | 
					 | 
				
			||||||
                        slTriggerBy="LastPrice",
 | 
					 | 
				
			||||||
                        positionIdx=0,
 | 
					 | 
				
			||||||
                        reduceOnly=False,
 | 
					 | 
				
			||||||
                        tpslMode=tpsl_mode,
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                except Exception as e:
 | 
					 | 
				
			||||||
                    logger.error(f"Ошибка установки TP/SL: {e}")
 | 
					 | 
				
			||||||
                    await message.answer(
 | 
					 | 
				
			||||||
                        "Ошибка при установке Take Profit и Stop Loss.",
 | 
					 | 
				
			||||||
                        reply_markup=inline_markup.back_to_main,
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    return False
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                logger.warning("Не удалось получить цену ликвидации для позиции")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        else:  # REGULAR_MARGIN
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                client.set_tp_sl_mode(symbol=symbol, category="linear", tpSlMode="Full")
 | 
					 | 
				
			||||||
            except exceptions.InvalidRequestError as e:
 | 
					 | 
				
			||||||
                if "same tp sl mode" in str(e):
 | 
					 | 
				
			||||||
                    logger.info("Режим TP/SL уже установлен - пропускаем")
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    raise
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if order_type == "Market":
 | 
					 | 
				
			||||||
                base_price = entry_price
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                base_price = limit_price
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if side.lower() == "buy":
 | 
					 | 
				
			||||||
                take_profit_price = base_price * (1 + loss_profit / 100)
 | 
					 | 
				
			||||||
                stop_loss_price = base_price * (1 - loss_profit / 100)
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                take_profit_price = base_price * (1 - loss_profit / 100)
 | 
					 | 
				
			||||||
                stop_loss_price = base_price * (1 + loss_profit / 100)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            take_profit_price = max(take_profit_price, 0)
 | 
					 | 
				
			||||||
            stop_loss_price = max(stop_loss_price, 0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if tpsl_mode == "Full":
 | 
					 | 
				
			||||||
                tp_order_type = "Market"
 | 
					 | 
				
			||||||
                sl_order_type = "Market"
 | 
					 | 
				
			||||||
                tp_limit_price = None
 | 
					 | 
				
			||||||
                sl_limit_price = None
 | 
					 | 
				
			||||||
            else:  # Partial
 | 
					 | 
				
			||||||
                tp_order_type = "Limit"
 | 
					 | 
				
			||||||
                sl_order_type = "Limit"
 | 
					 | 
				
			||||||
                tp_limit_price = take_profit_price
 | 
					 | 
				
			||||||
                sl_limit_price = stop_loss_price
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            response = client.place_order(
 | 
					 | 
				
			||||||
                category="linear",
 | 
					 | 
				
			||||||
                symbol=symbol,
 | 
					 | 
				
			||||||
                side=side,
 | 
					 | 
				
			||||||
                orderType=order_type,
 | 
					 | 
				
			||||||
                qty=str(quantity),
 | 
					 | 
				
			||||||
                price=(
 | 
					 | 
				
			||||||
                    str(limit_price) if order_type == "Limit" and limit_price else None
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                takeProfit=str(take_profit_price),
 | 
					 | 
				
			||||||
                tpOrderType=tp_order_type,
 | 
					 | 
				
			||||||
                tpLimitPrice=str(tp_limit_price) if tp_limit_price else None,
 | 
					 | 
				
			||||||
                stopLoss=str(stop_loss_price),
 | 
					 | 
				
			||||||
                slOrderType=sl_order_type,
 | 
					 | 
				
			||||||
                slLimitPrice=str(sl_limit_price) if sl_limit_price else None,
 | 
					 | 
				
			||||||
                tpslMode=tpsl_mode,
 | 
					 | 
				
			||||||
                timeInForce="GTC",
 | 
					 | 
				
			||||||
                orderLinkId=f"deal_{symbol}_{int(time.time())}",
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if response.get("retCode", -1) == 0:
 | 
					 | 
				
			||||||
                return True
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                logger.error(f"Ошибка открытия ордера: {response}")
 | 
					 | 
				
			||||||
                await message.answer(
 | 
					 | 
				
			||||||
                    f"Ошибка открытия ордера", reply_markup=inline_markup.back_to_main
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return None
 | 
					 | 
				
			||||||
    except exceptions.InvalidRequestError as e:
 | 
					 | 
				
			||||||
        logger.error("InvalidRequestError: %s", e)
 | 
					 | 
				
			||||||
        error_text = str(e)
 | 
					 | 
				
			||||||
        if "estimated will trigger liq" in error_text:
 | 
					 | 
				
			||||||
            await message.answer(
 | 
					 | 
				
			||||||
                "Лимитный ордер может вызвать мгновенную ликвидацию. Проверьте параметры ордера.",
 | 
					 | 
				
			||||||
                reply_markup=inline_markup.back_to_main,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        elif "ab not enough for new order" in error_text:
 | 
					 | 
				
			||||||
            await message.answer("Недостаточно средств для нового ордера",
 | 
					 | 
				
			||||||
                                 reply_markup=inline_markup.back_to_main)
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            logger.error("Ошибка при совершении сделки: %s", e)
 | 
					 | 
				
			||||||
            await message.answer(
 | 
					 | 
				
			||||||
                "Недостаточно средств для размещения нового ордера с заданным количеством и плечом.",
 | 
					 | 
				
			||||||
                reply_markup=inline_markup.back_to_main,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        logger.error("Ошибка при совершении сделки: %s", e)
 | 
					 | 
				
			||||||
        await message.answer(
 | 
					 | 
				
			||||||
            "Возникла ошибка при попытке открыть позицию.",
 | 
					 | 
				
			||||||
            reply_markup=inline_markup.back_to_main,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def set_take_profit_stop_loss(
 | 
					 | 
				
			||||||
        tg_id: int,
 | 
					 | 
				
			||||||
        message,
 | 
					 | 
				
			||||||
        take_profit_price: float,
 | 
					 | 
				
			||||||
        stop_loss_price: float,
 | 
					 | 
				
			||||||
        tpsl_mode="Full",
 | 
					 | 
				
			||||||
):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Устанавливает уровни Take Profit и Stop Loss для открытой позиции.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    symbol = await rq.get_symbol(tg_id)
 | 
					 | 
				
			||||||
    client = await get_bybit_client(tg_id)
 | 
					 | 
				
			||||||
    await cancel_all_tp_sl_orders(tg_id, symbol)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            client.set_tp_sl_mode(symbol=symbol, category="linear", tpSlMode=tpsl_mode)
 | 
					 | 
				
			||||||
        except exceptions.InvalidRequestError as e:
 | 
					 | 
				
			||||||
            if "same tp sl mode" in str(e).lower():
 | 
					 | 
				
			||||||
                logger.info("Режим TP/SL уже установлен для %s - пропускаем", symbol)
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                raise
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        resp = client.set_trading_stop(
 | 
					 | 
				
			||||||
            category="linear",
 | 
					 | 
				
			||||||
            symbol=symbol,
 | 
					 | 
				
			||||||
            takeProfit=str(round(take_profit_price, 5)),
 | 
					 | 
				
			||||||
            stopLoss=str(round(stop_loss_price, 5)),
 | 
					 | 
				
			||||||
            tpTriggerBy="LastPrice",
 | 
					 | 
				
			||||||
            slTriggerBy="LastPrice",
 | 
					 | 
				
			||||||
            positionIdx=0,
 | 
					 | 
				
			||||||
            reduceOnly=False,
 | 
					 | 
				
			||||||
            tpslMode=tpsl_mode,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if resp.get("retCode") != 0:
 | 
					 | 
				
			||||||
            await message.answer(
 | 
					 | 
				
			||||||
                f"Ошибка обновления TP/SL: {resp.get('retMsg')}",
 | 
					 | 
				
			||||||
                reply_markup=inline_markup.back_to_main,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await message.answer(
 | 
					 | 
				
			||||||
            f"ТП и СЛ успешно установлены:\nТейк-профит: {take_profit_price:.5f}\nСтоп-лосс: {stop_loss_price:.5f}",
 | 
					 | 
				
			||||||
            reply_markup=inline_markup.back_to_main,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        logger.error(f"Ошибка установки TP/SL для {symbol}: {e}", exc_info=True)
 | 
					 | 
				
			||||||
        await message.answer(
 | 
					 | 
				
			||||||
            "Произошла ошибка при установке TP и SL.",
 | 
					 | 
				
			||||||
            reply_markup=inline_markup.back_to_main,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def cancel_all_tp_sl_orders(tg_id, symbol):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Отменяет лимитные ордера для указанного символа.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    client = await get_bybit_client(tg_id)
 | 
					 | 
				
			||||||
    last_response = None
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        orders_resp = client.get_open_orders(category="linear", symbol=symbol)
 | 
					 | 
				
			||||||
        orders = orders_resp.get("result", {}).get("list", [])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for order in orders:
 | 
					 | 
				
			||||||
            order_id = order.get("orderId")
 | 
					 | 
				
			||||||
            order_symbol = order.get("symbol")
 | 
					 | 
				
			||||||
            cancel_resp = client.cancel_order(
 | 
					 | 
				
			||||||
                category="linear", symbol=symbol, orderId=order_id
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            if cancel_resp.get("retCode") != 0:
 | 
					 | 
				
			||||||
                logger.warning(
 | 
					 | 
				
			||||||
                    f"Не удалось отменить ордер {order_id}: {cancel_resp.get('retMsg')}"
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                last_response = order_symbol
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        logger.error(f"Ошибка при отмене ордера: {e}")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return last_response
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_active_positions(tg_id, message):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Показывает активные позиции пользователя.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    client = await get_bybit_client(tg_id)
 | 
					 | 
				
			||||||
    active_positions = client.get_positions(category="linear", settleCoin="USDT")
 | 
					 | 
				
			||||||
    positions = active_positions.get("result", {}).get("list", [])
 | 
					 | 
				
			||||||
    active_symbols = [
 | 
					 | 
				
			||||||
        pos.get("symbol") for pos in positions if float(pos.get("size", 0)) > 0
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if active_symbols:
 | 
					 | 
				
			||||||
        await message.answer(
 | 
					 | 
				
			||||||
            "📈 Ваши активные позиции:",
 | 
					 | 
				
			||||||
            reply_markup=inline_markup.create_trades_inline_keyboard(active_symbols),
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        await message.answer(
 | 
					 | 
				
			||||||
            "❗️ У вас нет активных позиций.", reply_markup=inline_markup.back_to_main
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_active_positions_by_symbol(tg_id, symbol, message):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Показывает активные позиции пользователя по символу.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    client = await get_bybit_client(tg_id)
 | 
					 | 
				
			||||||
    active_positions = client.get_positions(category="linear", symbol=symbol)
 | 
					 | 
				
			||||||
    positions = active_positions.get("result", {}).get("list", [])
 | 
					 | 
				
			||||||
    pos = positions[0] if positions else None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if float(pos.get("size", 0)) == 0:
 | 
					 | 
				
			||||||
        await message.answer(
 | 
					 | 
				
			||||||
            "❗️ У вас нет активных позиций.", reply_markup=inline_markup.back_to_main
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    text = (
 | 
					 | 
				
			||||||
        f"Торговая пара: {pos.get('symbol')}\n"
 | 
					 | 
				
			||||||
        f"Цена входа: {pos.get('avgPrice')}\n"
 | 
					 | 
				
			||||||
        f"Движение: {pos.get('side')}\n"
 | 
					 | 
				
			||||||
        f"Кредитное плечо: {pos.get('leverage')}x\n"
 | 
					 | 
				
			||||||
        f"Количество: {pos.get('size')}\n"
 | 
					 | 
				
			||||||
        f"Тейк-профит: {pos.get('takeProfit')}\n"
 | 
					 | 
				
			||||||
        f"Стоп-лосс: {pos.get('stopLoss')}\n"
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer(
 | 
					 | 
				
			||||||
        text, reply_markup=inline_markup.create_close_deal_markup(symbol)
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_active_orders(tg_id, message):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Показывает активные лимитные ордера пользователя.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    client = await get_bybit_client(tg_id)
 | 
					 | 
				
			||||||
    response = client.get_open_orders(
 | 
					 | 
				
			||||||
        category="linear", settleCoin="USDT", orderType="Limit"
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    orders = response.get("result", {}).get("list", [])
 | 
					 | 
				
			||||||
    limit_orders = [order for order in orders if order.get("orderType") == "Limit"]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if limit_orders:
 | 
					 | 
				
			||||||
        symbols = [order["symbol"] for order in limit_orders]
 | 
					 | 
				
			||||||
        await message.answer(
 | 
					 | 
				
			||||||
            "📈 Ваши активные лимитные ордера:",
 | 
					 | 
				
			||||||
            reply_markup=inline_markup.create_trades_inline_keyboard_limits(symbols),
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        await message.answer(
 | 
					 | 
				
			||||||
            "❗️ У вас нет активных лимитных ордеров.",
 | 
					 | 
				
			||||||
            reply_markup=inline_markup.back_to_main,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_active_orders_by_symbol(tg_id, symbol, message):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Показывает активные лимитные ордера пользователя по символу.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    client = await get_bybit_client(tg_id)
 | 
					 | 
				
			||||||
    active_orders = client.get_open_orders(category="linear", symbol=symbol)
 | 
					 | 
				
			||||||
    limit_orders = [
 | 
					 | 
				
			||||||
        order
 | 
					 | 
				
			||||||
        for order in active_orders.get("result", {}).get("list", [])
 | 
					 | 
				
			||||||
        if order.get("orderType") == "Limit"
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if not limit_orders:
 | 
					 | 
				
			||||||
        await message.answer(
 | 
					 | 
				
			||||||
            "Нет активных лимитных ордеров по данной торговой паре.",
 | 
					 | 
				
			||||||
            reply_markup=inline_markup.back_to_main,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    texts = []
 | 
					 | 
				
			||||||
    for order in limit_orders:
 | 
					 | 
				
			||||||
        text = (
 | 
					 | 
				
			||||||
            f"Торговая пара: {order.get('symbol')}\n"
 | 
					 | 
				
			||||||
            f"Тип ордера: {order.get('orderType')}\n"
 | 
					 | 
				
			||||||
            f"Сторона: {order.get('side')}\n"
 | 
					 | 
				
			||||||
            f"Цена: {order.get('price')}\n"
 | 
					 | 
				
			||||||
            f"Количество: {order.get('qty')}\n"
 | 
					 | 
				
			||||||
            f"Тейк-профит: {order.get('takeProfit')}\n"
 | 
					 | 
				
			||||||
            f"Стоп-лосс: {order.get('stopLoss')}\n"
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        texts.append(text)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer(
 | 
					 | 
				
			||||||
        "\n\n".join(texts), reply_markup=inline_markup.create_close_limit_markup(symbol)
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def close_user_trade(tg_id: int, symbol: str):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Закрывает открытые позиции пользователя по символу рыночным ордером.
 | 
					 | 
				
			||||||
    Возвращает True при успехе, False при ошибках.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        client = await get_bybit_client(tg_id)
 | 
					 | 
				
			||||||
        positions_resp = client.get_positions(category="linear", symbol=symbol)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if positions_resp.get("retCode") != 0:
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
        positions_list = positions_resp.get("result", {}).get("list", [])
 | 
					 | 
				
			||||||
        if not positions_list:
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        position = positions_list[0]
 | 
					 | 
				
			||||||
        qty = abs(safe_float(position.get("size")))
 | 
					 | 
				
			||||||
        side = position.get("side")
 | 
					 | 
				
			||||||
        if qty == 0:
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        close_side = "Sell" if side == "Buy" else "Buy"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        place_resp = client.place_order(
 | 
					 | 
				
			||||||
            category="linear",
 | 
					 | 
				
			||||||
            symbol=symbol,
 | 
					 | 
				
			||||||
            side=close_side,
 | 
					 | 
				
			||||||
            orderType="Market",
 | 
					 | 
				
			||||||
            qty=str(qty),
 | 
					 | 
				
			||||||
            timeInForce="GTC",
 | 
					 | 
				
			||||||
            reduceOnly=True,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if place_resp.get("retCode") == 0:
 | 
					 | 
				
			||||||
            return True
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            return False
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        logger.error(
 | 
					 | 
				
			||||||
            f"Ошибка закрытия сделки {symbol} для пользователя {tg_id}: {e}",
 | 
					 | 
				
			||||||
            exc_info=True,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        return False
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def close_trade_after_delay(tg_id: int, message, symbol: str, delay_sec: int):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Закрывает сделку пользователя после задержки delay_sec секунд.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        await asyncio.sleep(delay_sec)
 | 
					 | 
				
			||||||
        result = await close_user_trade(tg_id, symbol)
 | 
					 | 
				
			||||||
        if result:
 | 
					 | 
				
			||||||
            await message.answer(
 | 
					 | 
				
			||||||
                f"Сделка {symbol} успешно закрыта по таймеру."
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            logger.info(f"Сделка {symbol} успешно закрыта по таймеру.")
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            await message.answer(
 | 
					 | 
				
			||||||
                f"Не удалось закрыть сделку {symbol} по таймеру.",
 | 
					 | 
				
			||||||
                reply_markup=inline_markup.back_to_main,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            logger.error(f"Не удалось закрыть сделку {symbol} по таймеру.")
 | 
					 | 
				
			||||||
    except asyncio.CancelledError:
 | 
					 | 
				
			||||||
        await message.answer(
 | 
					 | 
				
			||||||
            f"Закрытие сделки {symbol} по таймеру отменено.",
 | 
					 | 
				
			||||||
            reply_markup=inline_markup.back_to_main,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        logger.info(f"Закрытие сделки {symbol} по таймеру отменено.")
 | 
					 | 
				
			||||||
@@ -1,52 +0,0 @@
 | 
				
			|||||||
import app.telegram.database.requests as rq
 | 
					 | 
				
			||||||
import app.telegram.Keyboards.inline_keyboards as inline_markup
 | 
					 | 
				
			||||||
import logging.config
 | 
					 | 
				
			||||||
from logger_helper.logger_helper import LOGGING_CONFIG
 | 
					 | 
				
			||||||
from pybit.unified_trading import HTTP
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
logging.config.dictConfig(LOGGING_CONFIG)
 | 
					 | 
				
			||||||
logger = logging.getLogger("balance")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_balance(tg_id: int, message) -> float:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Асинхронно получает общий баланс пользователя на Bybit.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Процедура:
 | 
					 | 
				
			||||||
    - Получает API ключ и секрет пользователя из базы данных.
 | 
					 | 
				
			||||||
    - Если ключи не заданы, отправляет пользователю сообщение с предложением подключить платформу.
 | 
					 | 
				
			||||||
    - Создает клиент Bybit с ключами.
 | 
					 | 
				
			||||||
    - Запрашивает общий баланс по типу аккаунта UNIFIED.
 | 
					 | 
				
			||||||
    - Если ответ успешен, возвращает баланс в виде float.
 | 
					 | 
				
			||||||
    - При ошибках API или исключениях логирует ошибку и уведомляет пользователя.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    :param tg_id: int - идентификатор пользователя Telegram
 | 
					 | 
				
			||||||
    :param message: объект сообщения для отправки ответов пользователю
 | 
					 | 
				
			||||||
    :return: float - общий баланс пользователя; 0 при ошибке или отсутствии ключей
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    api_key = await rq.get_bybit_api_key(tg_id)
 | 
					 | 
				
			||||||
    secret_key = await rq.get_bybit_secret_key(tg_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    client = HTTP(
 | 
					 | 
				
			||||||
        api_key=api_key,
 | 
					 | 
				
			||||||
        api_secret=secret_key
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if api_key is None or secret_key is None:
 | 
					 | 
				
			||||||
        await message.answer('⚠️ Подключите платформу для торговли',
 | 
					 | 
				
			||||||
                             reply_markup=inline_markup.connect_bybit_api_message)
 | 
					 | 
				
			||||||
        return 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        response = client.get_wallet_balance(accountType='UNIFIED')
 | 
					 | 
				
			||||||
        if response['retCode'] == 0:
 | 
					 | 
				
			||||||
            total_balance = response['result']['list'][0].get('totalWalletBalance', '0')
 | 
					 | 
				
			||||||
            return total_balance
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            logger.error(f"Ошибка API: {response.get('retMsg')}")
 | 
					 | 
				
			||||||
            await message.answer(f"⚠️ Ошибка API: {response.get('retMsg')}")
 | 
					 | 
				
			||||||
            return 0
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        logger.error(f"Ошибка при получении общего баланса: {e}")
 | 
					 | 
				
			||||||
        await message.answer('Ошибка при подключении, повторите попытку', reply_markup=inline_markup.connect_bybit_api_message)
 | 
					 | 
				
			||||||
        return 0
 | 
					 | 
				
			||||||
@@ -1,115 +0,0 @@
 | 
				
			|||||||
import asyncio
 | 
					 | 
				
			||||||
import logging.config
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from pybit.unified_trading import WebSocket
 | 
					 | 
				
			||||||
from websocket import WebSocketConnectionClosedException
 | 
					 | 
				
			||||||
from logger_helper.logger_helper import LOGGING_CONFIG
 | 
					 | 
				
			||||||
import app.telegram.database.requests as rq
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
logging.config.dictConfig(LOGGING_CONFIG)
 | 
					 | 
				
			||||||
logger = logging.getLogger("bybit_ws")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
event_loop = None  # Сюда нужно будет установить event loop из основного приложения
 | 
					 | 
				
			||||||
active_ws_tasks = {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def on_ws_error(ws, error):
 | 
					 | 
				
			||||||
    logger.error(f"WebSocket internal error: {error}")
 | 
					 | 
				
			||||||
    # Запланировать переподключение через event loop
 | 
					 | 
				
			||||||
    if event_loop:
 | 
					 | 
				
			||||||
        asyncio.run_coroutine_threadsafe(reconnect_ws(ws), event_loop)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def on_ws_close(ws, close_status_code, close_msg):
 | 
					 | 
				
			||||||
    logger.warning(f"WebSocket closed: {close_status_code} - {close_msg}")
 | 
					 | 
				
			||||||
    # Запланировать переподключение через event loop
 | 
					 | 
				
			||||||
    if event_loop:
 | 
					 | 
				
			||||||
        asyncio.run_coroutine_threadsafe(reconnect_ws(ws), event_loop)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def reconnect_ws(ws):
 | 
					 | 
				
			||||||
    logger.info("Запускаем переподключение WebSocket...")
 | 
					 | 
				
			||||||
    await asyncio.sleep(5)
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        await ws.run_forever()
 | 
					 | 
				
			||||||
    except WebSocketConnectionClosedException:
 | 
					 | 
				
			||||||
        logger.info("WebSocket переподключение успешно завершено.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def get_or_create_event_loop() -> asyncio.AbstractEventLoop:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Возвращает текущий активный цикл событий asyncio или создает новый, если его нет.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        return asyncio.get_running_loop()
 | 
					 | 
				
			||||||
    except RuntimeError:
 | 
					 | 
				
			||||||
        loop = asyncio.new_event_loop()
 | 
					 | 
				
			||||||
        asyncio.set_event_loop(loop)
 | 
					 | 
				
			||||||
        return loop
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def set_event_loop(loop: asyncio.AbstractEventLoop):
 | 
					 | 
				
			||||||
    global event_loop
 | 
					 | 
				
			||||||
    event_loop = loop
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def run_ws_for_user(tg_id, message) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Запускает WebSocket Bybit для пользователя с указанным tg_id.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    if tg_id not in active_ws_tasks or active_ws_tasks[tg_id].done():
 | 
					 | 
				
			||||||
        api_key = await rq.get_bybit_api_key(tg_id)
 | 
					 | 
				
			||||||
        api_secret = await rq.get_bybit_secret_key(tg_id)
 | 
					 | 
				
			||||||
        # Запускаем WebSocket как асинхронную задачу
 | 
					 | 
				
			||||||
        active_ws_tasks[tg_id] = asyncio.create_task(
 | 
					 | 
				
			||||||
            start_execution_ws(api_key, api_secret, message)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        logger.info(f"WebSocket для пользователя {tg_id} запущен.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def on_order_callback(message, msg):
 | 
					 | 
				
			||||||
    if event_loop is not None:
 | 
					 | 
				
			||||||
        from app.services.Bybit.functions.Futures import handle_order_message
 | 
					 | 
				
			||||||
        asyncio.run_coroutine_threadsafe(handle_order_message(message, msg), event_loop)
 | 
					 | 
				
			||||||
        logger.info("Callback выполнен.")
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        logger.error("Event loop не установлен, callback пропущен.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def on_execution_callback(message, ws_msg):
 | 
					 | 
				
			||||||
    if event_loop is not None:
 | 
					 | 
				
			||||||
        from app.services.Bybit.functions.Futures import handle_execution_message
 | 
					 | 
				
			||||||
        asyncio.run_coroutine_threadsafe(handle_execution_message(message, ws_msg), event_loop)
 | 
					 | 
				
			||||||
        logger.info("Callback выполнен.")
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        logger.error("Event loop не установлен, callback пропущен.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def start_execution_ws(api_key: str, api_secret: str, message):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Запускает и поддерживает WebSocket подключение для исполнения сделок.
 | 
					 | 
				
			||||||
    Реконнект при потерях соединения.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    reconnect_delay = 5
 | 
					 | 
				
			||||||
    while True:
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            if not api_key or not api_secret:
 | 
					 | 
				
			||||||
                logger.error("API_KEY и API_SECRET должны быть указаны для подключения к приватным каналам.")
 | 
					 | 
				
			||||||
                await asyncio.sleep(reconnect_delay)
 | 
					 | 
				
			||||||
                continue
 | 
					 | 
				
			||||||
            ws = WebSocket(api_key=api_key, api_secret=api_secret, testnet=False, channel_type="private")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            ws.on_error = on_ws_error
 | 
					 | 
				
			||||||
            ws.on_close = on_ws_close
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            ws.subscribe("order", lambda ws_msg: on_order_callback(message, ws_msg))
 | 
					 | 
				
			||||||
            ws.subscribe("execution", lambda ws_msg: on_execution_callback(message, ws_msg))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            while True:
 | 
					 | 
				
			||||||
                await asyncio.sleep(1)  # Поддержание активности
 | 
					 | 
				
			||||||
        except WebSocketConnectionClosedException:
 | 
					 | 
				
			||||||
            logger.warning("WebSocket закрыт, переподключение через 5 секунд...")
 | 
					 | 
				
			||||||
            await asyncio.sleep(reconnect_delay)
 | 
					 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            logger.error(f"Ошибка WebSocket: {e}")
 | 
					 | 
				
			||||||
            await asyncio.sleep(reconnect_delay)
 | 
					 | 
				
			||||||
@@ -1,562 +0,0 @@
 | 
				
			|||||||
import asyncio
 | 
					 | 
				
			||||||
import logging.config
 | 
					 | 
				
			||||||
from aiogram import F, Router
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from app.services.Bybit.functions.bybit_ws import run_ws_for_user
 | 
					 | 
				
			||||||
from app.telegram.functions.main_settings.settings import main_settings_message
 | 
					 | 
				
			||||||
from logger_helper.logger_helper import LOGGING_CONFIG
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from app.services.Bybit.functions.Futures import (close_user_trade, set_take_profit_stop_loss, \
 | 
					 | 
				
			||||||
                                                  get_active_positions_by_symbol, get_active_orders_by_symbol,
 | 
					 | 
				
			||||||
                                                  get_active_positions, get_active_orders, cancel_all_tp_sl_orders,
 | 
					 | 
				
			||||||
                                                  open_position, close_trade_after_delay, safe_float,
 | 
					 | 
				
			||||||
                                                  )
 | 
					 | 
				
			||||||
from app.services.Bybit.functions.balance import get_balance
 | 
					 | 
				
			||||||
import app.telegram.Keyboards.inline_keyboards as inline_markup
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import app.telegram.database.requests as rq
 | 
					 | 
				
			||||||
from aiogram.types import Message, CallbackQuery
 | 
					 | 
				
			||||||
from app.services.Bybit.functions.price_symbol import get_price
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from app.states.States import (state_update_entry_type, state_update_symbol, state_limit_price,
 | 
					 | 
				
			||||||
                               SetTP_SL_State, CloseTradeTimerState)
 | 
					 | 
				
			||||||
from aiogram.fsm.context import FSMContext
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from app.services.Bybit.functions.get_valid_symbol import get_valid_symbols
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
logging.config.dictConfig(LOGGING_CONFIG)
 | 
					 | 
				
			||||||
logger = logging.getLogger("functions")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
router_functions_bybit_trade = Router()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
user_trade_tasks = {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(F.data.in_(['clb_start_trading', 'clb_back_to_main', 'back_to_main']))
 | 
					 | 
				
			||||||
async def clb_start_bybit_trade_message(callback: CallbackQuery) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Обработка нажатия кнопок запуска торговли или возврата в главное меню.
 | 
					 | 
				
			||||||
    Отправляет информацию о балансе, символе, цене и инструкциях по торговле.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    user_id = callback.from_user.id
 | 
					 | 
				
			||||||
    balance = await get_balance(user_id, callback.message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if balance:
 | 
					 | 
				
			||||||
        symbol = await rq.get_symbol(user_id)
 | 
					 | 
				
			||||||
        price = await get_price(user_id, symbol=symbol)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        text = (
 | 
					 | 
				
			||||||
            f"💎 Торговля на Bybit\n\n"
 | 
					 | 
				
			||||||
            f"⚖️ Ваш баланс (USDT): {float(balance):.2f}\n"
 | 
					 | 
				
			||||||
            f"📊 Текущая торговая пара: {symbol}\n"
 | 
					 | 
				
			||||||
            f"$$$ Цена: {price}\n\n"
 | 
					 | 
				
			||||||
            "Как начать торговлю?\n\n"
 | 
					 | 
				
			||||||
            "1️⃣ Проверьте и тщательно настройте все параметры в вашем профиле.\n"
 | 
					 | 
				
			||||||
            "2️⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару, без лишних символов (например: BTCUSDT).\n"
 | 
					 | 
				
			||||||
            "3️⃣ Нажмите кнопку 'Начать торговать'.\n"
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        await callback.message.edit_text(text=text, parse_mode='html', reply_markup=inline_markup.trading_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def start_bybit_trade_message(message: Message) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Отправляет пользователю информацию о балансе, символе и текущей цене,
 | 
					 | 
				
			||||||
    вместе с инструкциями по началу торговли.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    balance = await get_balance(message.from_user.id, message)
 | 
					 | 
				
			||||||
    tg_id = message.from_user.id
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if balance:
 | 
					 | 
				
			||||||
        await run_ws_for_user(tg_id, message)
 | 
					 | 
				
			||||||
        symbol = await rq.get_symbol(message.from_user.id)
 | 
					 | 
				
			||||||
        price = await get_price(message.from_user.id, symbol=symbol)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        text = (
 | 
					 | 
				
			||||||
            f"💎 Торговля на Bybit\n\n"
 | 
					 | 
				
			||||||
            f"⚖️ Ваш баланс (USDT): {float(balance):.2f}\n"
 | 
					 | 
				
			||||||
            f"📊 Текущая торговая пара: {symbol}\n"
 | 
					 | 
				
			||||||
            f"$$$ Цена: {price}\n\n"
 | 
					 | 
				
			||||||
            "Как начать торговлю?\n\n"
 | 
					 | 
				
			||||||
            "1️⃣ Проверьте и тщательно настройте все параметры в вашем профиле.\n"
 | 
					 | 
				
			||||||
            "2️⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару, без лишних символов (например: BTCUSDT).\n"
 | 
					 | 
				
			||||||
            "3️⃣ Нажмите кнопку 'Начать торговать'.\n"
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.trading_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(F.data == 'clb_update_trading_pair')
 | 
					 | 
				
			||||||
async def update_symbol_for_trade_message(callback: CallbackQuery, state: FSMContext) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Начинает процедуру обновления торговой пары, переводит пользователя в состояние ожидания пары.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await state.set_state(state_update_symbol.symbol)
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await callback.message.answer(
 | 
					 | 
				
			||||||
        text='Укажите торговую пару заглавными буквами без пробелов и лишних символов (пример: BTCUSDT): ',
 | 
					 | 
				
			||||||
        reply_markup=inline_markup.cancel)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.message(state_update_symbol.symbol)
 | 
					 | 
				
			||||||
async def update_symbol_for_trade(message: Message, state: FSMContext) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Обрабатывает ввод торговой пары пользователем и проверяет её валидность.
 | 
					 | 
				
			||||||
    При успешном обновлении сохранит пару и отправит обновлённую информацию.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    user_input = message.text.strip().upper()
 | 
					 | 
				
			||||||
    exists = await get_valid_symbols(message.from_user.id, user_input)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if not exists:
 | 
					 | 
				
			||||||
        await message.answer("Введена некорректная торговая пара или такой пары нет в списке. Попробуйте снова.")
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await state.update_data(symbol=message.text)
 | 
					 | 
				
			||||||
    await message.answer('Пара была успешно обновлена')
 | 
					 | 
				
			||||||
    await rq.update_symbol(message.from_user.id, user_input)
 | 
					 | 
				
			||||||
    await start_bybit_trade_message(message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await state.clear()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(F.data == 'clb_update_entry_type')
 | 
					 | 
				
			||||||
async def update_entry_type_message(callback: CallbackQuery, state: FSMContext) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Запрашивает у пользователя тип входа в позицию (Market или Limit).
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await state.set_state(state_update_entry_type.entry_type)
 | 
					 | 
				
			||||||
    await callback.message.answer("Выберите тип входа в позицию:", reply_markup=inline_markup.entry_order_type_markup)
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith('entry_order_type:'))
 | 
					 | 
				
			||||||
async def entry_order_type_callback(callback: CallbackQuery, state: FSMContext) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Обработка выбора типа входа в позицию.
 | 
					 | 
				
			||||||
    Если Limit, запрашивает цену лимитного ордера.
 | 
					 | 
				
			||||||
    Если Market — обновляет настройки.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    order_type = callback.data.split(':')[1]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if order_type not in ['Market', 'Limit']:
 | 
					 | 
				
			||||||
        await callback.answer("Ошибка выбора", show_alert=True)
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if order_type == 'Limit':
 | 
					 | 
				
			||||||
        await state.set_state(state_limit_price.price)
 | 
					 | 
				
			||||||
        await callback.message.answer("Введите цену лимитного ордера:", reply_markup=inline_markup.cancel)
 | 
					 | 
				
			||||||
        await callback.answer()
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        await state.update_data(entry_order_type=order_type)
 | 
					 | 
				
			||||||
        await rq.update_entry_order_type(callback.from_user.id, order_type)
 | 
					 | 
				
			||||||
        await callback.message.answer(f"Выбран тип входа в позицию: {order_type}",
 | 
					 | 
				
			||||||
                                      reply_markup=inline_markup.start_trading_markup)
 | 
					 | 
				
			||||||
        await callback.answer()
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        logger.error("Произошла ошибка при обновлении типа входа в позицию: %s", e)
 | 
					 | 
				
			||||||
        await callback.message.answer("Произошла ошибка при обновлении типа входа в позицию",
 | 
					 | 
				
			||||||
                                      reply_markup=inline_markup.back_to_main)
 | 
					 | 
				
			||||||
    await state.clear()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.message(state_limit_price.price)
 | 
					 | 
				
			||||||
async def set_limit_price(message: Message, state: FSMContext) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Обрабатывает ввод цены лимитного ордера, проверяет формат и сохраняет настройки.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        price = float(message.text)
 | 
					 | 
				
			||||||
        if price <= 0:
 | 
					 | 
				
			||||||
            await message.answer("Цена должна быть положительным числом. Попробуйте снова.",
 | 
					 | 
				
			||||||
                                 reply_markup=inline_markup.cancel)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
    except ValueError:
 | 
					 | 
				
			||||||
        await message.answer("Некорректный формат цены. Введите число.", reply_markup=inline_markup.cancel)
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await state.update_data(entry_order_type='Limit', limit_price=price)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await rq.update_entry_order_type(message.from_user.id, 'Limit')
 | 
					 | 
				
			||||||
    await rq.update_limit_price(message.from_user.id, price)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer(f"Цена лимитного ордера установлена: {price}", reply_markup=inline_markup.start_trading_markup)
 | 
					 | 
				
			||||||
    await state.clear()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(F.data == "clb_start_chatbot_trading")
 | 
					 | 
				
			||||||
async def start_trading_process(callback: CallbackQuery) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Запускает торговый цикл в выбранном режиме Long/Short.
 | 
					 | 
				
			||||||
    Проверяет API-ключи, режим торговли, маржинальный режим и открытые позиции,
 | 
					 | 
				
			||||||
    затем запускает торговый цикл с задержкой или без неё.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
    tg_id = callback.from_user.id
 | 
					 | 
				
			||||||
    message = callback.message
 | 
					 | 
				
			||||||
    data_main_stgs = await rq.get_user_main_settings(tg_id)
 | 
					 | 
				
			||||||
    symbol = await rq.get_symbol(tg_id)
 | 
					 | 
				
			||||||
    margin_mode = data_main_stgs.get('margin_type', 'Isolated')
 | 
					 | 
				
			||||||
    trading_mode = data_main_stgs.get('trading_mode')
 | 
					 | 
				
			||||||
    starting_quantity = safe_float(data_main_stgs.get('starting_quantity'))
 | 
					 | 
				
			||||||
    switch_state = data_main_stgs.get("switch_state", "По направлению")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if trading_mode == 'Switch':
 | 
					 | 
				
			||||||
        if switch_state == "По направлению":
 | 
					 | 
				
			||||||
            side = data_main_stgs.get("last_side")
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            side = data_main_stgs.get("last_side")
 | 
					 | 
				
			||||||
            if side.lower() == "buy":
 | 
					 | 
				
			||||||
                side = "Sell"
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                side = "Buy"
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        if trading_mode == 'Long':
 | 
					 | 
				
			||||||
            side = 'Buy'
 | 
					 | 
				
			||||||
        elif trading_mode == 'Short':
 | 
					 | 
				
			||||||
            side = 'Sell'
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            await message.answer(f"Режим торговли '{trading_mode}' пока не поддерживается.",
 | 
					 | 
				
			||||||
                                 reply_markup=inline_markup.back_to_main)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer("Начинаю торговлю с использованием текущих настроек...")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    timer_data = await rq.get_user_timer(tg_id)
 | 
					 | 
				
			||||||
    if isinstance(timer_data, dict):
 | 
					 | 
				
			||||||
        timer_minute = timer_data.get('timer_minutes', 0)
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        timer_minute = timer_data or 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if timer_minute > 0:
 | 
					 | 
				
			||||||
        await message.answer(f"Торговля начнётся через {timer_minute} мин.", reply_markup=inline_markup.cancel_start)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        async def delay_start():
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                await asyncio.sleep(timer_minute * 60)
 | 
					 | 
				
			||||||
                await open_position(tg_id, message, side, margin_mode, symbol=symbol, quantity=starting_quantity)
 | 
					 | 
				
			||||||
                await rq.update_user_timer(tg_id, minutes=0)
 | 
					 | 
				
			||||||
            except asyncio.exceptions.CancelledError:
 | 
					 | 
				
			||||||
                logger.exception(f"Торговый цикл для пользователя {tg_id} был отменён.")
 | 
					 | 
				
			||||||
                raise
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        task = asyncio.create_task(delay_start())
 | 
					 | 
				
			||||||
        user_trade_tasks[tg_id] = task
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        await open_position(tg_id, message, side, margin_mode, symbol=symbol, quantity=starting_quantity)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(F.data == "clb_cancel_start")
 | 
					 | 
				
			||||||
async def cancel_start_trading(callback: CallbackQuery):
 | 
					 | 
				
			||||||
    tg_id = callback.from_user.id
 | 
					 | 
				
			||||||
    task = user_trade_tasks.get(tg_id)
 | 
					 | 
				
			||||||
    if task and not task.done():
 | 
					 | 
				
			||||||
        task.cancel()
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            await task
 | 
					 | 
				
			||||||
        except asyncio.CancelledError:
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
        user_trade_tasks.pop(tg_id, None)
 | 
					 | 
				
			||||||
        await rq.update_user_timer(tg_id, minutes=0)
 | 
					 | 
				
			||||||
        await callback.message.answer("Запуск торговли отменён.", reply_markup=inline_markup.back_to_main)
 | 
					 | 
				
			||||||
        await callback.message.edit_reply_markup(reply_markup=None)
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        await callback.answer("Нет запланированной задачи запуска.", show_alert=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(F.data == "clb_my_deals")
 | 
					 | 
				
			||||||
async def show_my_trades(callback: CallbackQuery) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Отображает пользователю выбор типа сделки по текущей торговой паре.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        await callback.message.answer("Выберите тип сделки:",
 | 
					 | 
				
			||||||
                                      reply_markup=inline_markup.my_deals_select_markup)
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        logger.error("Произошла ошибка при выборе типа сделки: %s", e)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(F.data == "clb_open_deals")
 | 
					 | 
				
			||||||
async def show_my_trades_callback(callback: CallbackQuery):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Показывает открытые позиции пользователя.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        await get_active_positions(callback.from_user.id, message=callback.message)
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        logger.error("Произошла ошибка при выборе сделки: %s", e)
 | 
					 | 
				
			||||||
        await callback.message.answer("Произошла ошибка при выборе сделки", reply_markup=inline_markup.back_to_main)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("show_deal_"))
 | 
					 | 
				
			||||||
async def show_deal_callback(callback_query: CallbackQuery) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Показывает сделку пользователя по символу.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await callback_query.answer()
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        symbol = callback_query.data[len("show_deal_"):]
 | 
					 | 
				
			||||||
        await rq.update_symbol(callback_query.from_user.id, symbol)
 | 
					 | 
				
			||||||
        tg_id = callback_query.from_user.id
 | 
					 | 
				
			||||||
        await get_active_positions_by_symbol(tg_id, symbol, message=callback_query.message)
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        logger.error("Произошла ошибка при выборе сделки: %s", e)
 | 
					 | 
				
			||||||
        await callback_query.message.answer("Произошла ошибка при выборе сделки",
 | 
					 | 
				
			||||||
                                            reply_markup=inline_markup.back_to_main)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(F.data == "clb_open_orders")
 | 
					 | 
				
			||||||
async def show_my_orders_callback(callback: CallbackQuery) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Показывает открытые позиции пользователя по символу.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        await get_active_orders(callback.from_user.id, message=callback.message)
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        logger.error("Произошла ошибка при выборе ордера: %s", e)
 | 
					 | 
				
			||||||
        await callback.message.answer("Произошла ошибка при выборе ордера", reply_markup=inline_markup.back_to_main)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("show_limit_"))
 | 
					 | 
				
			||||||
async def show_limit_callback(callback_query: CallbackQuery) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Показывает сделку пользователя по символу.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await callback_query.answer()
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        symbol = callback_query.data[len("show_limit_"):]
 | 
					 | 
				
			||||||
        await rq.update_symbol(callback_query.from_user.id, symbol)
 | 
					 | 
				
			||||||
        tg_id = callback_query.from_user.id
 | 
					 | 
				
			||||||
        await get_active_orders_by_symbol(tg_id, symbol, message=callback_query.message)
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        logger.error("Произошла ошибка при выборе сделки: %s", e)
 | 
					 | 
				
			||||||
        await callback_query.message.answer("Произошла ошибка при выборе сделки",
 | 
					 | 
				
			||||||
                                            reply_markup=inline_markup.back_to_main)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(F.data == "clb_set_tp_sl")
 | 
					 | 
				
			||||||
async def set_tp_sl(callback: CallbackQuery, state: FSMContext) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Запускает процесс установки Take Profit и Stop Loss.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
    await state.set_state(SetTP_SL_State.waiting_for_take_profit)
 | 
					 | 
				
			||||||
    await callback.message.answer("Введите значение Take Profit (в цене, например 26000.5):",
 | 
					 | 
				
			||||||
                                  reply_markup=inline_markup.cancel)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.message(SetTP_SL_State.waiting_for_take_profit)
 | 
					 | 
				
			||||||
async def process_take_profit(message: Message, state: FSMContext) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Обрабатывает ввод значения Take Profit и запрашивает Stop Loss.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        tp = float(message.text.strip())
 | 
					 | 
				
			||||||
        if tp <= 0:
 | 
					 | 
				
			||||||
            await message.answer("Значение Take Profit должно быть положительным числом. Попробуйте снова.",
 | 
					 | 
				
			||||||
                                 reply_markup=inline_markup.cancel)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
    except ValueError:
 | 
					 | 
				
			||||||
        await message.answer("Некорректный ввод. Пожалуйста, введите число для Take Profit.",
 | 
					 | 
				
			||||||
                             reply_markup=inline_markup.cancel)
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await state.update_data(take_profit=tp)
 | 
					 | 
				
			||||||
    await state.set_state(SetTP_SL_State.waiting_for_stop_loss)
 | 
					 | 
				
			||||||
    await message.answer("Введите значение Stop Loss (в цене, например 24500.3):", reply_markup=inline_markup.cancel)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.message(SetTP_SL_State.waiting_for_stop_loss)
 | 
					 | 
				
			||||||
async def process_stop_loss(message: Message, state: FSMContext) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Обрабатывает ввод значения Stop Loss и завершает процесс установки TP/SL.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        sl = float(message.text.strip())
 | 
					 | 
				
			||||||
        if sl <= 0:
 | 
					 | 
				
			||||||
            await message.answer("Значение Stop Loss должно быть положительным числом. Попробуйте снова.",
 | 
					 | 
				
			||||||
                                 reply_markup=inline_markup.cancel)
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
    except ValueError:
 | 
					 | 
				
			||||||
        await message.answer("Некорректный ввод. Пожалуйста, введите число для Stop Loss.",
 | 
					 | 
				
			||||||
                             reply_markup=inline_markup.cancel)
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    data = await state.get_data()
 | 
					 | 
				
			||||||
    tp = data.get("take_profit")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if tp is None:
 | 
					 | 
				
			||||||
        await message.answer("Ошибка, не найдено значение Take Profit. Попробуйте снова.")
 | 
					 | 
				
			||||||
        await state.clear()
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    tg_id = message.from_user.id
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await set_take_profit_stop_loss(tg_id, message, take_profit_price=tp, stop_loss_price=sl)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await state.clear()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_deal:"))
 | 
					 | 
				
			||||||
async def close_trade_callback(callback: CallbackQuery) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Закрывает сделку пользователя по символу.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    symbol = callback.data.split(':')[1]
 | 
					 | 
				
			||||||
    tg_id = callback.from_user.id
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    result = await close_user_trade(tg_id, symbol)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if result:
 | 
					 | 
				
			||||||
        logger.info(f"Сделка {symbol} успешно закрыта.")
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        logger.error(f"Не удалось закрыть сделку {symbol}.")
 | 
					 | 
				
			||||||
        await callback.message.answer(f"Не удалось закрыть сделку {symbol}.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_limit:"))
 | 
					 | 
				
			||||||
async def close_trade_callback(callback: CallbackQuery) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Закрывает ордера пользователя по символу.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    symbol = callback.data.split(':')[1]
 | 
					 | 
				
			||||||
    tg_id = callback.from_user.id
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    result = await cancel_all_tp_sl_orders(tg_id, symbol)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if result:
 | 
					 | 
				
			||||||
        logger.info(f"Ордер {result} успешно закрыт.")
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        await callback.message.answer(f"Не удалось закрыть ордер {result}.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_deal_by_timer:"))
 | 
					 | 
				
			||||||
async def ask_close_delay(callback: CallbackQuery, state: FSMContext) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Запускает диалог с пользователем для задания задержки перед закрытием сделки.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    symbol = callback.data.split(":")[1]
 | 
					 | 
				
			||||||
    await state.update_data(symbol=symbol)
 | 
					 | 
				
			||||||
    await state.set_state(CloseTradeTimerState.waiting_for_delay)
 | 
					 | 
				
			||||||
    await callback.message.answer("Введите задержку в минутах до закрытия сделки:",
 | 
					 | 
				
			||||||
                                  reply_markup=inline_markup.cancel)
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.message(CloseTradeTimerState.waiting_for_delay)
 | 
					 | 
				
			||||||
async def process_close_delay(message: Message, state: FSMContext) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Обрабатывает ввод закрытия сделки с задержкой.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        delay_minutes = int(message.text.strip())
 | 
					 | 
				
			||||||
        if delay_minutes <= 0:
 | 
					 | 
				
			||||||
            await message.answer("Введите положительное число.")
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
    except ValueError:
 | 
					 | 
				
			||||||
        await message.answer("Некорректный ввод. Введите число в минутах.")
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    data = await state.get_data()
 | 
					 | 
				
			||||||
    symbol = data.get("symbol")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    delay = delay_minutes * 60
 | 
					 | 
				
			||||||
    await message.answer(f"Закрытие сделки {symbol} запланировано через {delay_minutes} мин.",
 | 
					 | 
				
			||||||
                         reply_markup=inline_markup.back_to_main)
 | 
					 | 
				
			||||||
    await close_trade_after_delay(message.from_user.id, message, symbol, delay)
 | 
					 | 
				
			||||||
    await state.clear()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(F.data == "clb_change_martingale_reset")
 | 
					 | 
				
			||||||
async def reset_martingale(callback: CallbackQuery) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Сбрасывает шаги мартингейла пользователя.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    tg_id = callback.from_user.id
 | 
					 | 
				
			||||||
    await rq.update_martingale_step(tg_id, 1)
 | 
					 | 
				
			||||||
    await callback.answer("Сброс шагов выполнен.")
 | 
					 | 
				
			||||||
    await main_settings_message(tg_id, callback.message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(F.data == "clb_stop_trading")
 | 
					 | 
				
			||||||
async def confirm_stop_trading(callback: CallbackQuery):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Предлагает пользователю выбрать вариант подтверждение остановки торговли.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await callback.message.answer(
 | 
					 | 
				
			||||||
        "Выберите вариант остановки торговли:", reply_markup=inline_markup.stop_choice_markup
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(F.data == "stop_immediately")
 | 
					 | 
				
			||||||
async def stop_immediately(callback: CallbackQuery):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Останавливает торговлю немедленно.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    tg_id = callback.from_user.id
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await rq.update_trigger(tg_id, "Ручной")
 | 
					 | 
				
			||||||
    await callback.message.answer("Автоматическая торговля остановлена.", reply_markup=inline_markup.back_to_main)
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(F.data == "stop_with_timer")
 | 
					 | 
				
			||||||
async def stop_with_timer_start(callback: CallbackQuery, state: FSMContext):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Запускает диалог с пользователем для задания задержки до остановки торговли.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await state.set_state(CloseTradeTimerState.waiting_for_trade)
 | 
					 | 
				
			||||||
    await callback.message.answer("Введите задержку в минутах до остановки торговли:",
 | 
					 | 
				
			||||||
                                  reply_markup=inline_markup.cancel)
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.message(CloseTradeTimerState.waiting_for_trade)
 | 
					 | 
				
			||||||
async def process_stop_delay(message: Message, state: FSMContext):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Обрабатывает ввод задержки и запускает задачу остановки торговли с задержкой.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        delay_minutes = int(message.text.strip())
 | 
					 | 
				
			||||||
        if delay_minutes <= 0:
 | 
					 | 
				
			||||||
            await message.answer("Введите положительное число минут.")
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
    except ValueError:
 | 
					 | 
				
			||||||
        await message.answer("Некорректный формат. Введите число в минутах.")
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    tg_id = message.from_user.id
 | 
					 | 
				
			||||||
    delay_seconds = delay_minutes * 60
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer(f"Торговля будет остановлена через {delay_minutes} минут.",
 | 
					 | 
				
			||||||
                         reply_markup=inline_markup.back_to_main)
 | 
					 | 
				
			||||||
    await asyncio.sleep(delay_seconds)
 | 
					 | 
				
			||||||
    await rq.update_trigger(tg_id, "Ручной")
 | 
					 | 
				
			||||||
    await message.answer("Автоматическая торговля остановлена.", reply_markup=inline_markup.back_to_main)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await state.clear()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_functions_bybit_trade.callback_query(F.data == "clb_cancel")
 | 
					 | 
				
			||||||
async def cancel(callback: CallbackQuery, state: FSMContext) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Отменяет текущее состояние FSM и сообщает пользователю об отмене.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        await state.clear()
 | 
					 | 
				
			||||||
        await callback.message.answer("Отменено!", reply_markup=inline_markup.back_to_main)
 | 
					 | 
				
			||||||
        await callback.answer()
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        logger.error("Ошибка при обработке отмены: %s", e)
 | 
					 | 
				
			||||||
@@ -1,40 +0,0 @@
 | 
				
			|||||||
import logging.config
 | 
					 | 
				
			||||||
from pybit.unified_trading import HTTP
 | 
					 | 
				
			||||||
import app.telegram.database.requests as rq
 | 
					 | 
				
			||||||
from logger_helper.logger_helper import LOGGING_CONFIG
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
logging.config.dictConfig(LOGGING_CONFIG)
 | 
					 | 
				
			||||||
logger = logging.getLogger("get_valid_symbol")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_valid_symbols(user_id: int, symbol: str) -> bool:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Проверяет существование торговой пары на Bybit в категории 'linear'.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Эта функция получает API-ключи пользователя из базы данных и
 | 
					 | 
				
			||||||
    с помощью Bybit API проверяет наличие данного символа в списке
 | 
					 | 
				
			||||||
    торговых инструментов категории 'linear'.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        user_id (int): Идентификатор пользователя Telegram.
 | 
					 | 
				
			||||||
        symbol (str): Торговый символ (валютная пара), например "BTCUSDT".
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Returns:
 | 
					 | 
				
			||||||
        bool: Возвращает True, если торговая пара существует, иначе False.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Raises:
 | 
					 | 
				
			||||||
        Исключения подавляются и вызывается False, если произошла ошибка запроса к API.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    api_key = await rq.get_bybit_api_key(user_id)
 | 
					 | 
				
			||||||
    secret_key = await rq.get_bybit_secret_key(user_id)
 | 
					 | 
				
			||||||
    client = HTTP(api_key=api_key, api_secret=secret_key)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        resp = client.get_instruments_info(category='linear', symbol=symbol)
 | 
					 | 
				
			||||||
        # Проверка наличия результата и непустого списка инструментов
 | 
					 | 
				
			||||||
        if resp.get('retCode') == 0 and resp.get('result') and resp['result'].get('list'):
 | 
					 | 
				
			||||||
            return len(resp['result']['list']) > 0
 | 
					 | 
				
			||||||
        return False
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        logging.error(f"Ошибка при получении списка инструментов: {e}")
 | 
					 | 
				
			||||||
        return False
 | 
					 | 
				
			||||||
@@ -1,52 +0,0 @@
 | 
				
			|||||||
import math
 | 
					 | 
				
			||||||
import logging.config
 | 
					 | 
				
			||||||
from app.services.Bybit.functions.price_symbol import get_price
 | 
					 | 
				
			||||||
import app.telegram.database.requests as rq
 | 
					 | 
				
			||||||
from logger_helper.logger_helper import LOGGING_CONFIG
 | 
					 | 
				
			||||||
from pybit.unified_trading import HTTP
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
logging.config.dictConfig(LOGGING_CONFIG)
 | 
					 | 
				
			||||||
logger = logging.getLogger("min_qty")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def round_up_qty(value: float, step: float) -> float:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Округление value вверх до ближайшего кратного step.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    return math.ceil(value / step) * step
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_min_qty(tg_id: int) -> float:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Получает минимальный объем (количество) ордера для символа пользователя на Bybit,
 | 
					 | 
				
			||||||
    округленное с учетом шага количества qtyStep.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    :param tg_id: int - идентификатор пользователя Telegram
 | 
					 | 
				
			||||||
    :return: float - минимальное количество лота для ордера
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    api_key = await rq.get_bybit_api_key(tg_id)
 | 
					 | 
				
			||||||
    secret_key = await rq.get_bybit_secret_key(tg_id)
 | 
					 | 
				
			||||||
    symbol = await rq.get_symbol(tg_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    client = HTTP(api_key=api_key, api_secret=secret_key)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    price = await get_price(tg_id, symbol=symbol)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    response = client.get_instruments_info(symbol=symbol, category='linear')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    instrument = response['result'][0]
 | 
					 | 
				
			||||||
    lot_size_filter = instrument.get('lotSizeFilter', {})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    min_order_qty = float(lot_size_filter.get('minOrderQty', 0))
 | 
					 | 
				
			||||||
    min_notional_value = float(lot_size_filter.get('minNotionalValue', 0))
 | 
					 | 
				
			||||||
    qty_step = float(lot_size_filter.get('qtyStep', 1))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    calculated_qty = (5 / price) * 1.1
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    min_qty = max(min_order_qty, calculated_qty)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    min_qty_rounded = round_up_qty(min_qty, qty_step)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    logger.debug(f"tg_id={tg_id}: price={price}, min_order_qty={min_order_qty}, "
 | 
					 | 
				
			||||||
                 f"min_notional_value={min_notional_value}, qty_step={qty_step}, "
 | 
					 | 
				
			||||||
                 f"calculated_qty={calculated_qty}, min_qty_rounded={min_qty_rounded}")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return min_qty_rounded
 | 
					 | 
				
			||||||
@@ -1,32 +0,0 @@
 | 
				
			|||||||
import app.telegram.database.requests as rq
 | 
					 | 
				
			||||||
import logging.config
 | 
					 | 
				
			||||||
from logger_helper.logger_helper import LOGGING_CONFIG
 | 
					 | 
				
			||||||
from pybit import exceptions
 | 
					 | 
				
			||||||
from pybit.unified_trading import HTTP
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
logging.config.dictConfig(LOGGING_CONFIG)
 | 
					 | 
				
			||||||
logger = logging.getLogger("price_symbol")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_price(tg_id: int, symbol: str) -> float:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Асинхронно получает текущую цену символа пользователя на Bybit.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    :param tg_id: int - ID пользователя Telegram
 | 
					 | 
				
			||||||
    :return: float - текущая цена символа
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    api_key = await rq.get_bybit_api_key(tg_id)
 | 
					 | 
				
			||||||
    secret_key = await rq.get_bybit_secret_key(tg_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    client = HTTP(
 | 
					 | 
				
			||||||
        api_key=api_key,
 | 
					 | 
				
			||||||
        api_secret=secret_key
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        price = float(
 | 
					 | 
				
			||||||
            client.get_tickers(category='linear', symbol=symbol).get('result').get('list')[0].get('ask1Price'))
 | 
					 | 
				
			||||||
        return price
 | 
					 | 
				
			||||||
    except exceptions.InvalidRequestError as e:
 | 
					 | 
				
			||||||
        logger.error(f"Ошибка при получении цены: {e}")
 | 
					 | 
				
			||||||
        return 1.0
 | 
					 | 
				
			||||||
@@ -1,69 +0,0 @@
 | 
				
			|||||||
from aiogram.fsm.state import State, StatesGroup
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class state_update_symbol(StatesGroup):
 | 
					 | 
				
			||||||
    """FSM состояние для обновления торгового символа."""
 | 
					 | 
				
			||||||
    symbol = State()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class state_update_entry_type(StatesGroup):
 | 
					 | 
				
			||||||
    """FSM состояние для обновления типа входа."""
 | 
					 | 
				
			||||||
    entry_type = State()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class TradeSetup(StatesGroup):
 | 
					 | 
				
			||||||
    """FSM состояния для настройки торговли с таймером и процентом."""
 | 
					 | 
				
			||||||
    waiting_for_timer = State()
 | 
					 | 
				
			||||||
    waiting_for_positive_percent = State()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class state_limit_price(StatesGroup):
 | 
					 | 
				
			||||||
    """FSM состояние для установки лимита."""
 | 
					 | 
				
			||||||
    price = State()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class CloseTradeTimerState(StatesGroup):
 | 
					 | 
				
			||||||
    """FSM состояние ожидания задержки перед закрытием сделки."""
 | 
					 | 
				
			||||||
    waiting_for_delay = State()
 | 
					 | 
				
			||||||
    waiting_for_trade = State()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class SetTP_SL_State(StatesGroup):
 | 
					 | 
				
			||||||
    """FSM состояние для установки TP и SL."""
 | 
					 | 
				
			||||||
    waiting_for_take_profit = State()
 | 
					 | 
				
			||||||
    waiting_for_stop_loss = State()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class update_risk_management_settings(StatesGroup):
 | 
					 | 
				
			||||||
    """FSM состояние для обновления настроек управления рисками."""
 | 
					 | 
				
			||||||
    price_profit = State()
 | 
					 | 
				
			||||||
    price_loss = State()
 | 
					 | 
				
			||||||
    max_risk_deal = State()
 | 
					 | 
				
			||||||
    commission_fee = State()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class state_reg_bybit_api(StatesGroup):
 | 
					 | 
				
			||||||
    """FSM состояние для регистрации API Bybit."""
 | 
					 | 
				
			||||||
    api_key = State()
 | 
					 | 
				
			||||||
    secret_key = State()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class condition_settings(StatesGroup):
 | 
					 | 
				
			||||||
    """FSM состояние для настройки условий трейдинга."""
 | 
					 | 
				
			||||||
    trigger = State()
 | 
					 | 
				
			||||||
    timer = State()
 | 
					 | 
				
			||||||
    volatilty = State()
 | 
					 | 
				
			||||||
    volume = State()
 | 
					 | 
				
			||||||
    integration = State()
 | 
					 | 
				
			||||||
    use_tv_signal = State()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class update_main_settings(StatesGroup):
 | 
					 | 
				
			||||||
    """FSM состояние для обновления основных настройок."""
 | 
					 | 
				
			||||||
    trading_mode = State()
 | 
					 | 
				
			||||||
    size_leverage = State()
 | 
					 | 
				
			||||||
    margin_type = State()
 | 
					 | 
				
			||||||
    martingale_factor = State()
 | 
					 | 
				
			||||||
    starting_quantity = State()
 | 
					 | 
				
			||||||
    maximal_quantity = State()
 | 
					 | 
				
			||||||
    switch_mode_enabled = State()
 | 
					 | 
				
			||||||
@@ -1,217 +0,0 @@
 | 
				
			|||||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
 | 
					 | 
				
			||||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
start_markup = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="🔥 Начать торговлю", callback_data="clb_start_chatbot_message")]
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
settings_markup = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="Запуск", callback_data='clb_start_trading')]
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
cancel_start = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="Отменить запуск", callback_data="clb_cancel_start")]
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
back_btn_list_settings = [InlineKeyboardButton(text="Назад",
 | 
					 | 
				
			||||||
                                               callback_data='clb_back_to_special_settings_message')]  # Кнопка для возврата к списку каталога настроек
 | 
					 | 
				
			||||||
back_btn_list_settings_markup = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Назад",
 | 
					 | 
				
			||||||
                                                                                            callback_data='clb_back_to_special_settings_message')]])  # Клавиатура для возврата к списку каталога настроек
 | 
					 | 
				
			||||||
back_btn_to_main = [InlineKeyboardButton(text="На главную", callback_data='clb_back_to_main')]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
back_btn_profile = [InlineKeyboardButton(text="Назад", callback_data='clb_start_chatbot_message')]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
connect_bybit_api_message = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="Подключить Bybit", callback_data='clb_new_user_connect_bybit_api_message')]
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
special_settings_markup = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="Основные настройки", callback_data='clb_change_main_settings'),
 | 
					 | 
				
			||||||
     InlineKeyboardButton(text="Риск-менеджмент", callback_data='clb_change_risk_management_settings')],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="Условия запуска", callback_data='clb_change_condition_settings')],
 | 
					 | 
				
			||||||
     # InlineKeyboardButton(text="Дополнительные параметры", callback_data='clb_change_additional_settings')],
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="Подключить Bybit", callback_data='clb_new_user_connect_bybit_api_message')],
 | 
					 | 
				
			||||||
    back_btn_to_main
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
connect_bybit_api_markup = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="Подключить Bybit", callback_data='clb_new_user_connect_bybit_api')]
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
trading_markup = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="Настройки", callback_data='clb_settings_message')],
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="Мои сделки", callback_data='clb_my_deals')],
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="Указать торговую пару", callback_data='clb_update_trading_pair')],
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="Начать торговать", callback_data='clb_update_entry_type')],
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="Остановить торговлю", callback_data='clb_stop_trading')],
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
start_trading_markup = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="Начать торговлю", callback_data="clb_start_chatbot_trading")],
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="На главную", callback_data='back_to_main')],
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
cancel = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="Отменить", callback_data="clb_cancel")]
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
entry_order_type_markup = InlineKeyboardMarkup(
 | 
					 | 
				
			||||||
    inline_keyboard=[
 | 
					 | 
				
			||||||
        [
 | 
					 | 
				
			||||||
            InlineKeyboardButton(text="Market (текущая цена)", callback_data="entry_order_type:Market"),
 | 
					 | 
				
			||||||
            InlineKeyboardButton(text="Limit (фиксированная цена)", callback_data="entry_order_type:Limit"),
 | 
					 | 
				
			||||||
        ], back_btn_to_main
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
back_to_main = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="На главную", callback_data='back_to_main')],
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
main_settings_markup = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text='Режим торговли', callback_data='clb_change_trading_mode'),
 | 
					 | 
				
			||||||
     InlineKeyboardButton(text='Состояние свитча', callback_data='clb_change_switch_state'),
 | 
					 | 
				
			||||||
     InlineKeyboardButton(text='Тип маржи', callback_data='clb_change_margin_type')],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text='Размер кредитного плеча', callback_data='clb_change_size_leverage'),
 | 
					 | 
				
			||||||
     InlineKeyboardButton(text='Начальная ставка', callback_data='clb_change_starting_quantity')],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text='Коэффициент Мартингейла', callback_data='clb_change_martingale_factor'),
 | 
					 | 
				
			||||||
     InlineKeyboardButton(text='Сбросить шаги Мартингейла', callback_data='clb_change_martingale_reset')],
 | 
					 | 
				
			||||||
     [InlineKeyboardButton(text='Максимальное кол-во ставок', callback_data='clb_change_maximum_quantity')],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    back_btn_list_settings,
 | 
					 | 
				
			||||||
    back_btn_to_main
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
risk_management_settings_markup = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text='Изм. цены прибыли', callback_data='clb_change_price_profit'),
 | 
					 | 
				
			||||||
     InlineKeyboardButton(text='Изм. цены убытков', callback_data='clb_change_price_loss')],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text='Макс. риск на сделку', callback_data='clb_change_max_risk_deal')],
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text='Учитывать комиссию биржи (Да/Нет)', callback_data='commission_fee')],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    back_btn_list_settings,
 | 
					 | 
				
			||||||
    back_btn_to_main
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
condition_settings_markup = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text='Режим торговли', callback_data='clb_change_mode'),
 | 
					 | 
				
			||||||
     InlineKeyboardButton(text='Таймер', callback_data='clb_change_timer')],
 | 
					 | 
				
			||||||
    #
 | 
					 | 
				
			||||||
    # [InlineKeyboardButton(text='Фильтр волатильности', callback_data='clb_change_filter_volatility'),
 | 
					 | 
				
			||||||
    #  InlineKeyboardButton(text='Внешние сигналы', callback_data='clb_change_external_cues')],
 | 
					 | 
				
			||||||
    #
 | 
					 | 
				
			||||||
    # [InlineKeyboardButton(text='Сигналы TradingView', callback_data='clb_change_tradingview_cues'),
 | 
					 | 
				
			||||||
    #  InlineKeyboardButton(text='Webhook URL', callback_data='clb_change_webhook')],
 | 
					 | 
				
			||||||
    #
 | 
					 | 
				
			||||||
    # [InlineKeyboardButton(text='AI - аналитика', callback_data='clb_change_ai_analytics')],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    back_btn_list_settings,
 | 
					 | 
				
			||||||
    back_btn_to_main
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
additional_settings_markup = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text='Сохранить шаблон', callback_data='clb_change_save_pattern'),
 | 
					 | 
				
			||||||
     InlineKeyboardButton(text='Автозапуск', callback_data='clb_change_auto_start')],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text='Уведомления', callback_data='clb_change_notifications')],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    back_btn_list_settings,
 | 
					 | 
				
			||||||
    back_btn_to_main
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
trading_mode_markup = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="Лонг", callback_data="trade_mode_long"),
 | 
					 | 
				
			||||||
     InlineKeyboardButton(text="Шорт", callback_data="trade_mode_short"),
 | 
					 | 
				
			||||||
     InlineKeyboardButton(text="Свитч", callback_data="trade_mode_switch")],
 | 
					 | 
				
			||||||
     # InlineKeyboardButton(text="Смарт", callback_data="trade_mode_smart")],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    back_btn_list_settings,
 | 
					 | 
				
			||||||
    back_btn_to_main
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
margin_type_markup = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="Изолированный", callback_data="margin_type_isolated"),
 | 
					 | 
				
			||||||
     InlineKeyboardButton(text="Кросс", callback_data="margin_type_cross")],
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    back_btn_list_settings
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
trigger_markup = InlineKeyboardMarkup(inline_keyboard=[  # ИЗМЕНИТЬ НА INLINE
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text='Ручной', callback_data="clb_trigger_manual")],
 | 
					 | 
				
			||||||
     # [InlineKeyboardButton(text='TradingView', callback_data="clb_trigger_tradingview")],
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="Автоматический", callback_data="clb_trigger_auto")],
 | 
					 | 
				
			||||||
    back_btn_list_settings,
 | 
					 | 
				
			||||||
    back_btn_to_main
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
buttons_yes_no_markup = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text='Да', callback_data="clb_yes"),
 | 
					 | 
				
			||||||
     InlineKeyboardButton(text='Нет', callback_data="clb_no")],
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
buttons_on_off_markup = InlineKeyboardMarkup(inline_keyboard=[  # ИЗМЕНИТЬ НА INLINE
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text='Включить', callback_data="clb_on"),
 | 
					 | 
				
			||||||
     InlineKeyboardButton(text='Выключить', callback_data="clb_off")]
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
my_deals_select_markup = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text='Открытые сделки', callback_data="clb_open_deals"),
 | 
					 | 
				
			||||||
     InlineKeyboardButton(text='Лимитные ордера', callback_data="clb_open_orders")],
 | 
					 | 
				
			||||||
    back_btn_to_main
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def create_trades_inline_keyboard(trades):
 | 
					 | 
				
			||||||
    builder = InlineKeyboardBuilder()
 | 
					 | 
				
			||||||
    for trade in trades:
 | 
					 | 
				
			||||||
        builder.button(text=trade, callback_data=f"show_deal_{trade}")
 | 
					 | 
				
			||||||
    builder.adjust(2)
 | 
					 | 
				
			||||||
    return builder.as_markup()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def create_trades_inline_keyboard_limits(trades):
 | 
					 | 
				
			||||||
    builder = InlineKeyboardBuilder()
 | 
					 | 
				
			||||||
    for trade in trades:
 | 
					 | 
				
			||||||
        builder.button(text=trade, callback_data=f"show_limit_{trade}")
 | 
					 | 
				
			||||||
    builder.adjust(2)
 | 
					 | 
				
			||||||
    return builder.as_markup()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def create_close_deal_markup(symbol: str) -> InlineKeyboardMarkup:
 | 
					 | 
				
			||||||
    return InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
        [InlineKeyboardButton(text="Закрыть сделку", callback_data=f"close_deal:{symbol}")],
 | 
					 | 
				
			||||||
        [InlineKeyboardButton(text="Закрыть по таймеру", callback_data=f"close_deal_by_timer:{symbol}")],
 | 
					 | 
				
			||||||
        [InlineKeyboardButton(text="Установить TP/SL", callback_data="clb_set_tp_sl")],
 | 
					 | 
				
			||||||
        back_btn_to_main
 | 
					 | 
				
			||||||
    ])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def create_close_limit_markup(symbol: str) -> InlineKeyboardMarkup:
 | 
					 | 
				
			||||||
    return InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
        [InlineKeyboardButton(text="Закрыть лимитный ордер", callback_data=f"close_limit:{symbol}")],
 | 
					 | 
				
			||||||
        back_btn_to_main
 | 
					 | 
				
			||||||
    ])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
timer_markup = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="Установить таймер", callback_data="clb_set_timer")],
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text="Удалить таймер", callback_data="clb_delete_timer")],
 | 
					 | 
				
			||||||
    back_btn_to_main
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
stop_choice_markup = InlineKeyboardMarkup(
 | 
					 | 
				
			||||||
    inline_keyboard=[
 | 
					 | 
				
			||||||
        [
 | 
					 | 
				
			||||||
            InlineKeyboardButton(text="Остановить сразу", callback_data="stop_immediately"),
 | 
					 | 
				
			||||||
            InlineKeyboardButton(text="Остановить по таймеру", callback_data="stop_with_timer"),
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
switch_state_markup = InlineKeyboardMarkup(inline_keyboard=[
 | 
					 | 
				
			||||||
    [InlineKeyboardButton(text='По направлению', callback_data="clb_long_switch"),
 | 
					 | 
				
			||||||
     InlineKeyboardButton(text='Против направления', callback_data="clb_short_switch")],
 | 
					 | 
				
			||||||
])
 | 
					 | 
				
			||||||
@@ -1,6 +0,0 @@
 | 
				
			|||||||
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
base_buttons_markup = ReplyKeyboardMarkup(keyboard=[
 | 
					 | 
				
			||||||
    [KeyboardButton(text="👤 Профиль")],    
 | 
					 | 
				
			||||||
    # [KeyboardButton(text="Настройки")]         
 | 
					 | 
				
			||||||
], resize_keyboard=True, one_time_keyboard=False)
 | 
					 | 
				
			||||||
							
								
								
									
										0
									
								
								app/telegram/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								app/telegram/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -1,306 +0,0 @@
 | 
				
			|||||||
from datetime import datetime
 | 
					 | 
				
			||||||
import logging.config
 | 
					 | 
				
			||||||
from sqlalchemy.sql.sqltypes import DateTime, Numeric
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from sqlalchemy import BigInteger, Boolean, Integer, String, ForeignKey
 | 
					 | 
				
			||||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
 | 
					 | 
				
			||||||
from sqlalchemy.ext.asyncio import AsyncAttrs, async_sessionmaker, create_async_engine
 | 
					 | 
				
			||||||
from logger_helper.logger_helper import LOGGING_CONFIG
 | 
					 | 
				
			||||||
from sqlalchemy import select, insert
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
logging.config.dictConfig(LOGGING_CONFIG)
 | 
					 | 
				
			||||||
logger = logging.getLogger("models")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
engine = create_async_engine(url='sqlite+aiosqlite:///db.sqlite3')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async_session = async_sessionmaker(engine)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Base(AsyncAttrs, DeclarativeBase):
 | 
					 | 
				
			||||||
    """Базовый класс для declarative моделей SQLAlchemy с поддержкой async."""
 | 
					 | 
				
			||||||
    pass
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class User_Telegram_Id(Base):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Модель таблицы user_telegram_id.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Хранит идентификаторы Telegram пользователей.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Атрибуты:
 | 
					 | 
				
			||||||
        id (int): Внутренний первичный ключ записи.
 | 
					 | 
				
			||||||
        tg_id (int): Уникальный идентификатор пользователя Telegram.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    __tablename__ = 'user_telegram_id'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    id: Mapped[int] = mapped_column(primary_key=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    tg_id = mapped_column(BigInteger)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class User_Bybit_API(Base):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Модель таблицы user_bybit_api.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Хранит API ключи и секреты Bybit для каждого Telegram пользователя.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Атрибуты:
 | 
					 | 
				
			||||||
        id (int): Внутренний первичный ключ записи.
 | 
					 | 
				
			||||||
        tg_id (int): Внешний ключ на Telegram пользователя (user_telegram_id.tg_id).
 | 
					 | 
				
			||||||
        api_key (str): API ключ Bybit (уникальный для пользователя).
 | 
					 | 
				
			||||||
        secret_key (str): Секретный ключ Bybit (уникальный для пользователя).
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    __tablename__ = 'user_bybit_api'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    id: Mapped[int] = mapped_column(primary_key=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    api_key = mapped_column(String(18), unique=True, nullable=True)
 | 
					 | 
				
			||||||
    secret_key = mapped_column(String(36), unique=True, nullable=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class User_Symbol(Base):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Модель таблицы user_main_settings.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Хранит основные настройки торговли для пользователя.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    __tablename__ = 'user_symbols'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    id: Mapped[int] = mapped_column(primary_key=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    symbol = mapped_column(String(18), default='PENGUUSDT')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Trading_Mode(Base):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Справочник доступных режимов торговли.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Атрибуты:
 | 
					 | 
				
			||||||
        id (int): Первичный ключ.
 | 
					 | 
				
			||||||
        mode (str): Уникальный режим (например, 'Long', 'Short', 'Switch).
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    __tablename__ = 'trading_modes'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    id: Mapped[int] = mapped_column(primary_key=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    mode = mapped_column(String(10), unique=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Margin_type(Base):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Справочник типов маржинальной торговли.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Атрибуты:
 | 
					 | 
				
			||||||
        id (int): Первичный ключ.
 | 
					 | 
				
			||||||
        type (str): Тип маржи (например, 'Isolated', 'Cross').
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    __tablename__ = 'margin_types'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    id: Mapped[int] = mapped_column(primary_key=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    type = mapped_column(String(15), unique=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class Trigger(Base):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Справочник триггеров для сделок.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Атрибуты:
 | 
					 | 
				
			||||||
        id (int): Первичный ключ.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    __tablename__ = 'triggers'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    id: Mapped[int] = mapped_column(primary_key=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    trigger_price = mapped_column(Integer(), default=0)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class User_Main_Settings(Base):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Основные настройки пользователя для торговли.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Атрибуты:
 | 
					 | 
				
			||||||
        id (int): Первичный ключ.
 | 
					 | 
				
			||||||
        tg_id (int): Внешний ключ на Telegram пользователя.
 | 
					 | 
				
			||||||
        trading_mode (str): Режим торговли, FK на trading_modes.mode.
 | 
					 | 
				
			||||||
        margin_type (str): Тип маржи, FK на margin_types.type.
 | 
					 | 
				
			||||||
        size_leverage (int): Кредитное плечо.
 | 
					 | 
				
			||||||
        starting_quantity (int): Начальный объем позиции.
 | 
					 | 
				
			||||||
        martingale_factor (int): Коэффициент мартингейла.
 | 
					 | 
				
			||||||
        martingale_step (int): Текущий шаг мартингейла.
 | 
					 | 
				
			||||||
        maximal_quantity (int): Максимальное число шагов мартингейла.
 | 
					 | 
				
			||||||
        entry_order_type (str): Тип ордера входа (Market/Limit).
 | 
					 | 
				
			||||||
        limit_order_price (Optional[str]): Цена лимитного ордера, если есть.
 | 
					 | 
				
			||||||
        last_side (str): Последняя сторона ордера.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    __tablename__ = 'user_main_settings'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    id: Mapped[int] = mapped_column(primary_key=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    trading_mode = mapped_column(ForeignKey("trading_modes.mode"))
 | 
					 | 
				
			||||||
    margin_type = mapped_column(ForeignKey("margin_types.type"))
 | 
					 | 
				
			||||||
    switch_state = mapped_column(String(10), default='По направлению')
 | 
					 | 
				
			||||||
    size_leverage = mapped_column(Integer(), default=1)
 | 
					 | 
				
			||||||
    starting_quantity = mapped_column(Integer(), default=1)
 | 
					 | 
				
			||||||
    martingale_factor = mapped_column(Integer(), default=1)
 | 
					 | 
				
			||||||
    martingale_step = mapped_column(Integer(), default=1)
 | 
					 | 
				
			||||||
    maximal_quantity = mapped_column(Integer(), default=10)
 | 
					 | 
				
			||||||
    entry_order_type = mapped_column(String(10), default='Market')
 | 
					 | 
				
			||||||
    limit_order_price = mapped_column(Numeric(18, 15), nullable=True)
 | 
					 | 
				
			||||||
    last_side = mapped_column(String(10), default='Buy')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class User_Risk_Management_Settings(Base):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Настройки управления рисками пользователя.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Атрибуты:
 | 
					 | 
				
			||||||
        id (int): Первичный ключ.
 | 
					 | 
				
			||||||
        tg_id (int): Внешний ключ на Telegram пользователя.
 | 
					 | 
				
			||||||
        price_profit (int): Процент прибыли для трейда.
 | 
					 | 
				
			||||||
        price_loss (int): Процент убытка для трейда.
 | 
					 | 
				
			||||||
        max_risk_deal (int): Максимально допустимый риск по сделке в процентах.
 | 
					 | 
				
			||||||
        commission_fee (str): Учитывать ли комиссию в расчетах ("Да"/"Нет").
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    __tablename__ = 'user_risk_management_settings'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    id: Mapped[int] = mapped_column(primary_key=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    price_profit = mapped_column(Integer(), default=1)
 | 
					 | 
				
			||||||
    price_loss = mapped_column(Integer(), default=1)
 | 
					 | 
				
			||||||
    max_risk_deal = mapped_column(Integer(), default=100)
 | 
					 | 
				
			||||||
    commission_fee = mapped_column(String(), default="Да")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class User_Condition_Settings(Base):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Дополнительные пользовательские условия для торговли.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Атрибуты:
 | 
					 | 
				
			||||||
        id (int): Первичный ключ.
 | 
					 | 
				
			||||||
        tg_id (int): Внешний ключ на Telegram пользователя.
 | 
					 | 
				
			||||||
        trigger (str): Тип триггера, FK на triggers.trigger.
 | 
					 | 
				
			||||||
        filter_time (str): Временной фильтр.
 | 
					 | 
				
			||||||
        filter_volatility (bool): Фильтр по волатильности.
 | 
					 | 
				
			||||||
        external_cues (bool): Внешние сигналы.
 | 
					 | 
				
			||||||
        tradingview_cues (bool): Сигналы TradingView.
 | 
					 | 
				
			||||||
        webhook (str): URL webhook.
 | 
					 | 
				
			||||||
        ai_analytics (bool): Использование AI для аналитики.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    __tablename__ = 'user_condition_settings'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    id: Mapped[int] = mapped_column(primary_key=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    trigger = mapped_column(String(15), default='Автоматический')
 | 
					 | 
				
			||||||
    filter_time = mapped_column(String(25), default='???')
 | 
					 | 
				
			||||||
    filter_volatility = mapped_column(Boolean, default=False)
 | 
					 | 
				
			||||||
    external_cues = mapped_column(Boolean, default=False)
 | 
					 | 
				
			||||||
    tradingview_cues = mapped_column(Boolean, default=False)
 | 
					 | 
				
			||||||
    webhook = mapped_column(String(40), default='')
 | 
					 | 
				
			||||||
    ai_analytics = mapped_column(Boolean, default=False)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class User_Additional_Settings(Base):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Прочие дополнительные настройки пользователя.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Атрибуты:
 | 
					 | 
				
			||||||
        id (int): Первичный ключ.
 | 
					 | 
				
			||||||
        tg_id (int): Внешний ключ на Telegram пользователя.
 | 
					 | 
				
			||||||
        pattern_save (bool): Сохранять ли шаблоны.
 | 
					 | 
				
			||||||
        autostart (bool): Автоматический запуск.
 | 
					 | 
				
			||||||
        notifications (bool): Получение уведомлений.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    __tablename__ = 'user_additional_settings'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    id: Mapped[int] = mapped_column(primary_key=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pattern_save = mapped_column(Boolean, default=False)
 | 
					 | 
				
			||||||
    autostart = mapped_column(Boolean, default=False)
 | 
					 | 
				
			||||||
    notifications = mapped_column(Boolean, default=False)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class USER_DEALS(Base):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Таблица сделок пользователя.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Атрибуты:
 | 
					 | 
				
			||||||
        id (int): Первичный ключ.
 | 
					 | 
				
			||||||
        tg_id (int): Внешний ключ на Telegram пользователя.
 | 
					 | 
				
			||||||
        symbol (str): Торговая пара.
 | 
					 | 
				
			||||||
        side (str): Направление сделки (Buy/Sell).
 | 
					 | 
				
			||||||
        open_price (int): Цена открытия.
 | 
					 | 
				
			||||||
        positive_percent (int): Процент доходности.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    __tablename__ = 'user_deals'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    id: Mapped[int] = mapped_column(primary_key=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    symbol = mapped_column(String(18), default='PENGUUSDT')
 | 
					 | 
				
			||||||
    side = mapped_column(String(10), nullable=False)
 | 
					 | 
				
			||||||
    open_price = mapped_column(Integer(), nullable=False)
 | 
					 | 
				
			||||||
    positive_percent = mapped_column(Integer(), nullable=False)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class UserTimer(Base):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Таймер пользователя для отсроченного запуска сделок.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Атрибуты:
 | 
					 | 
				
			||||||
        id (int): Первичный ключ.
 | 
					 | 
				
			||||||
        tg_id (int): Внешний ключ на Telegram пользователя.
 | 
					 | 
				
			||||||
        timer_minutes (int): Количество минут таймера.
 | 
					 | 
				
			||||||
        timer_start (datetime): Время начала таймера.
 | 
					 | 
				
			||||||
        timer_end (Optional[datetime]): Время окончания таймера (если установлено).
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    __tablename__ = 'user_timers'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    id: Mapped[int] = mapped_column(primary_key=True)
 | 
					 | 
				
			||||||
    tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True)
 | 
					 | 
				
			||||||
    timer_minutes = mapped_column(Integer, nullable=False, default=0)
 | 
					 | 
				
			||||||
    timer_start = mapped_column(DateTime, default=datetime.utcnow)
 | 
					 | 
				
			||||||
    timer_end = mapped_column(DateTime, nullable=True)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def async_main():
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Асинхронное создание всех таблиц и заполнение справочников начальными данными.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    async with engine.begin() as conn:
 | 
					 | 
				
			||||||
        await conn.run_sync(Base.metadata.create_all)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Заполнение таблиц
 | 
					 | 
				
			||||||
        modes = ['Long', 'Short', 'Switch', 'Smart']
 | 
					 | 
				
			||||||
        for mode in modes:
 | 
					 | 
				
			||||||
            result = await conn.execute(select(Trading_Mode).where(Trading_Mode.mode == mode))
 | 
					 | 
				
			||||||
            if not result.first():
 | 
					 | 
				
			||||||
                logger.info("Заполение таблицы режима торговли")
 | 
					 | 
				
			||||||
                await conn.execute(Trading_Mode.__table__.insert().values(mode=mode))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        types = ['Isolated', 'Cross']
 | 
					 | 
				
			||||||
        for type in types:
 | 
					 | 
				
			||||||
            result = await conn.execute(select(Margin_type).where(Margin_type.type == type))
 | 
					 | 
				
			||||||
            if not result.first():
 | 
					 | 
				
			||||||
                logger.info("Заполение таблицы типов маржи")
 | 
					 | 
				
			||||||
                await conn.execute(Margin_type.__table__.insert().values(type=type))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        last_side = ['Buy', 'Sell']
 | 
					 | 
				
			||||||
        for side in last_side:
 | 
					 | 
				
			||||||
            result = await conn.execute(select(User_Main_Settings).where(User_Main_Settings.last_side == side))
 | 
					 | 
				
			||||||
            if not result.first():
 | 
					 | 
				
			||||||
                logger.info("Заполение таблицы последнего направления")
 | 
					 | 
				
			||||||
                await conn.execute(User_Main_Settings.__table__.insert().values(last_side=side))
 | 
					 | 
				
			||||||
@@ -1,585 +0,0 @@
 | 
				
			|||||||
import logging.config
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from logger_helper.logger_helper import LOGGING_CONFIG
 | 
					 | 
				
			||||||
from datetime import datetime, timedelta
 | 
					 | 
				
			||||||
from typing import Any
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from app.telegram.database.models import (
 | 
					 | 
				
			||||||
    async_session,
 | 
					 | 
				
			||||||
    User_Telegram_Id as UTi,
 | 
					 | 
				
			||||||
    User_Main_Settings as UMS,
 | 
					 | 
				
			||||||
    User_Bybit_API as UBA,
 | 
					 | 
				
			||||||
    User_Symbol,
 | 
					 | 
				
			||||||
    User_Risk_Management_Settings as URMS,
 | 
					 | 
				
			||||||
    User_Condition_Settings as UCS,
 | 
					 | 
				
			||||||
    User_Additional_Settings as UAS,
 | 
					 | 
				
			||||||
    Trading_Mode,
 | 
					 | 
				
			||||||
    Margin_type,
 | 
					 | 
				
			||||||
    Trigger,
 | 
					 | 
				
			||||||
    USER_DEALS,
 | 
					 | 
				
			||||||
    UserTimer,
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from sqlalchemy import select, update
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
logging.config.dictConfig(LOGGING_CONFIG)
 | 
					 | 
				
			||||||
logger = logging.getLogger("requests")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# --- Функции сохранения в БД ---
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def save_tg_id_new_user(tg_id) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Сохраняет Telegram ID нового пользователя в базу, если такого ещё нет.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        tg_id (int): Telegram ID пользователя.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        user = await session.scalar(select(UTi).where(UTi.tg_id == tg_id))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not user:
 | 
					 | 
				
			||||||
            session.add(UTi(tg_id=tg_id))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            logger.info("Новый пользователь был добавлен в бд %s", tg_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def set_new_user_bybit_api(tg_id) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Создаёт запись API пользователя Bybit, если её ещё нет.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        tg_id (int): Telegram ID пользователя.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        user = await session.scalar(select(UBA).where(UBA.tg_id == tg_id))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not user:
 | 
					 | 
				
			||||||
            session.add(UBA(tg_id=tg_id))
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def set_new_user_symbol(tg_id) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Создаёт запись торгового символа пользователя, если её нет.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        tg_id (int): Telegram ID пользователя.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        user = await session.scalar(select(User_Symbol).where(User_Symbol.tg_id == tg_id))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not user:
 | 
					 | 
				
			||||||
            session.add(User_Symbol(tg_id=tg_id))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            logger.info(f"Symbol был успешно добавлен %s", tg_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def set_new_user_default_main_settings(tg_id, trading_mode, margin_type) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Создаёт основные настройки пользователя по умолчанию.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        tg_id (int): Telegram ID пользователя.
 | 
					 | 
				
			||||||
        trading_mode (str): Режим торговли.
 | 
					 | 
				
			||||||
        margin_type (str): Тип маржи.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        settings = await session.scalar(select(UMS).where(UMS.tg_id == tg_id))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not settings:
 | 
					 | 
				
			||||||
            session.add(UMS(
 | 
					 | 
				
			||||||
                tg_id=tg_id,
 | 
					 | 
				
			||||||
                trading_mode=trading_mode,
 | 
					 | 
				
			||||||
                margin_type=margin_type,
 | 
					 | 
				
			||||||
            ))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            logger.info("Основные настройки нового пользователя были заполнены%s", tg_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def set_new_user_default_risk_management_settings(tg_id) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Создаёт настройки риск-менеджмента по умолчанию.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        tg_id (int): Telegram ID пользователя.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        settings = await session.scalar(select(URMS).where(URMS.tg_id == tg_id))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not settings:
 | 
					 | 
				
			||||||
            session.add(URMS(
 | 
					 | 
				
			||||||
                tg_id=tg_id
 | 
					 | 
				
			||||||
            ))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            logger.info("Риск-Менеджмент настройки нового пользователя были заполнены %s", tg_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def set_new_user_default_condition_settings(tg_id, trigger) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Создаёт условные настройки по умолчанию.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        tg_id (int): Telegram ID пользователя.
 | 
					 | 
				
			||||||
        trigger (Any): Значение триггера по умолчанию.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        settings = await session.scalar(select(UCS).where(UCS.tg_id == tg_id))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not settings:
 | 
					 | 
				
			||||||
            session.add(UCS(
 | 
					 | 
				
			||||||
                tg_id=tg_id,
 | 
					 | 
				
			||||||
                trigger=trigger
 | 
					 | 
				
			||||||
            ))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            logger.info("Условные настройки нового пользователя были заполнены %s", tg_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def set_new_user_default_additional_settings(tg_id) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Создаёт дополнительные настройки по умолчанию.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        tg_id (int): Telegram ID пользователя.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        settings = await session.scalar(select(UAS).where(UAS.tg_id == tg_id))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not settings:
 | 
					 | 
				
			||||||
            session.add(UAS(
 | 
					 | 
				
			||||||
                tg_id=tg_id,
 | 
					 | 
				
			||||||
            ))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            logger.info("Дополнительные настройки нового пользователя были заполнены %s", tg_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# --- Функции получения данных из БД ---
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def check_user(tg_id):
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Проверяет наличие пользователя в базе.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        tg_id (int): Telegram ID пользователя.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Returns:
 | 
					 | 
				
			||||||
        Optional[UTi]: Пользователь или None.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        user = await session.scalar(select(UTi).where(UTi.tg_id == tg_id))
 | 
					 | 
				
			||||||
        return user
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_bybit_api_key(tg_id):
 | 
					 | 
				
			||||||
    """Получить API ключ Bybit пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        api_key = await session.scalar(select(UBA.api_key).where(UBA.tg_id == tg_id))
 | 
					 | 
				
			||||||
        return api_key
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_bybit_secret_key(tg_id):
 | 
					 | 
				
			||||||
    """Получить секретный ключ Bybit пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        secret_key = await session.scalar(select(UBA.secret_key).where(UBA.tg_id == tg_id))
 | 
					 | 
				
			||||||
        return secret_key
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_symbol(tg_id):
 | 
					 | 
				
			||||||
    """Получить символ пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        symbol = await session.scalar(select(User_Symbol.symbol).where(User_Symbol.tg_id == tg_id))
 | 
					 | 
				
			||||||
        return symbol
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_user_trades(tg_id):
 | 
					 | 
				
			||||||
    """Получить сделки пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        query = select(USER_DEALS.symbol, USER_DEALS.side).where(USER_DEALS.tg_id == tg_id)
 | 
					 | 
				
			||||||
        result = await session.execute(query)
 | 
					 | 
				
			||||||
        trades = result.all()
 | 
					 | 
				
			||||||
        return trades
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_entry_order_type(tg_id: object) -> str | None | Any:
 | 
					 | 
				
			||||||
    """Получить тип входного ордера пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        order_type = await session.scalar(
 | 
					 | 
				
			||||||
            select(UMS.entry_order_type).where(UMS.tg_id == tg_id)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        # Если в базе не установлен тип — возвращаем значение по умолчанию
 | 
					 | 
				
			||||||
        return order_type or 'Market'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# --- Функции обновления данных ---
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def update_user_trades(tg_id, **kwargs):
 | 
					 | 
				
			||||||
    """Обновить сделки пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        query = update(USER_DEALS).where(USER_DEALS.tg_id == tg_id).values(**kwargs)
 | 
					 | 
				
			||||||
        await session.execute(query)
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def update_symbol(tg_id: int, symbol: str) -> None:
 | 
					 | 
				
			||||||
    """Обновить торговый символ пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        await session.execute(update(User_Symbol).where(User_Symbol.tg_id == tg_id).values(symbol=symbol))
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def upsert_api_keys(tg_id: int, api_key: str, secret_key: str) -> None:
 | 
					 | 
				
			||||||
    """Обновить API ключ пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        result = await session.execute(select(UBA).where(UBA.tg_id == tg_id))
 | 
					 | 
				
			||||||
        user = result.scalars().first()
 | 
					 | 
				
			||||||
        if user:
 | 
					 | 
				
			||||||
            if api_key is not None:
 | 
					 | 
				
			||||||
                user.api_key = api_key
 | 
					 | 
				
			||||||
            if secret_key is not None:
 | 
					 | 
				
			||||||
                user.secret_key = secret_key
 | 
					 | 
				
			||||||
            logger.info(f"Обновлены ключи для пользователя {tg_id}")
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            new_user = UBA(tg_id=tg_id, api_key=api_key, secret_key=secret_key)
 | 
					 | 
				
			||||||
            session.add(new_user)
 | 
					 | 
				
			||||||
            logger.info(f"Добавлен новый пользователь {tg_id} с ключами")
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# --- Более мелкие обновления и запросы по настройкам ---
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def update_trade_mode_user(tg_id, trading_mode) -> None:
 | 
					 | 
				
			||||||
    """Обновить режим торговли пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        mode = await session.scalar(select(Trading_Mode.mode).where(Trading_Mode.mode == trading_mode))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if mode:
 | 
					 | 
				
			||||||
            logger.info("Изменён торговый режим для пользователя %s", tg_id)
 | 
					 | 
				
			||||||
            await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(trading_mode=mode))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def delete_user_trade(tg_id: int, symbol: str):
 | 
					 | 
				
			||||||
    """Удалить сделку пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        await session.execute(
 | 
					 | 
				
			||||||
            USER_DEALS.__table__.delete().where(
 | 
					 | 
				
			||||||
                (USER_DEALS.tg_id == tg_id) & (USER_DEALS.symbol == symbol)
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_for_registration_trading_mode():
 | 
					 | 
				
			||||||
    """Получить режим торговли по умолчанию."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        mode = await session.scalar(select(Trading_Mode.mode).where(Trading_Mode.id == 1))
 | 
					 | 
				
			||||||
        return mode
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_for_registration_margin_type():
 | 
					 | 
				
			||||||
    """Получить тип маржи по умолчанию."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        type = await session.scalar(select(Margin_type.type).where(Margin_type.id == 1))
 | 
					 | 
				
			||||||
        return type
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_for_registration_trigger(tg_id):
 | 
					 | 
				
			||||||
    """Получить триггер по умолчанию."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        trigger = await session.scalar(select(UCS.trigger).where(tg_id == tg_id))
 | 
					 | 
				
			||||||
        return trigger
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_user_main_settings(tg_id):
 | 
					 | 
				
			||||||
    """Получить основные настройки пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        user = await session.scalar(select(UMS).where(UMS.tg_id == tg_id))
 | 
					 | 
				
			||||||
        if user:
 | 
					 | 
				
			||||||
            data = {
 | 
					 | 
				
			||||||
                'trading_mode': user.trading_mode,
 | 
					 | 
				
			||||||
                'margin_type': user.margin_type,
 | 
					 | 
				
			||||||
                'switch_state': user.switch_state,
 | 
					 | 
				
			||||||
                'size_leverage': user.size_leverage,
 | 
					 | 
				
			||||||
                'starting_quantity': user.starting_quantity,
 | 
					 | 
				
			||||||
                'martingale_factor': user.martingale_factor,
 | 
					 | 
				
			||||||
                'maximal_quantity': user.maximal_quantity,
 | 
					 | 
				
			||||||
                'entry_order_type': user.entry_order_type,
 | 
					 | 
				
			||||||
                'limit_order_price': user.limit_order_price,
 | 
					 | 
				
			||||||
                'martingale_step': user.martingale_step,
 | 
					 | 
				
			||||||
                'last_side': user.last_side,
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return data
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_user_risk_management_settings(tg_id):
 | 
					 | 
				
			||||||
    """Получить риск-менеджмента настройки пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        user = await session.scalar(select(URMS).where(URMS.tg_id == tg_id))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if user:
 | 
					 | 
				
			||||||
            logger.info("Получение риск-менеджмента настроек пользователя %s", tg_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            price_profit = await session.scalar(select(URMS.price_profit).where(URMS.tg_id == tg_id))
 | 
					 | 
				
			||||||
            price_loss = await session.scalar(select(URMS.price_loss).where(URMS.tg_id == tg_id))
 | 
					 | 
				
			||||||
            max_risk_deal = await session.scalar(select(URMS.max_risk_deal).where(URMS.tg_id == tg_id))
 | 
					 | 
				
			||||||
            commission_fee = await session.scalar(select(URMS.commission_fee).where(URMS.tg_id == tg_id))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            data = {
 | 
					 | 
				
			||||||
                'price_profit': price_profit,
 | 
					 | 
				
			||||||
                'price_loss': price_loss,
 | 
					 | 
				
			||||||
                'max_risk_deal': max_risk_deal,
 | 
					 | 
				
			||||||
                'commission_fee': commission_fee,
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return data
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def update_margin_type(tg_id, margin_type) -> None:
 | 
					 | 
				
			||||||
    """Обновить тип маржи пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        type = await session.scalar(select(Margin_type.type).where(Margin_type.type == margin_type))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if type:
 | 
					 | 
				
			||||||
            logger.info("Изменен тип маржи %s", tg_id)
 | 
					 | 
				
			||||||
            await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(margin_type=type))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def update_size_leverange(tg_id, num):
 | 
					 | 
				
			||||||
    """Обновить размер левеража пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(size_leverage=num))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def update_starting_quantity(tg_id, num):
 | 
					 | 
				
			||||||
    """Обновить размер левеража пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(starting_quantity=num))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def update_martingale_factor(tg_id, num):
 | 
					 | 
				
			||||||
    """Обновить размер левеража пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(martingale_factor=num))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def update_maximal_quantity(tg_id, num):
 | 
					 | 
				
			||||||
    """Обновить размер левеража пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(maximal_quantity=num))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# ОБНОВЛЕНИЕ НАСТРОЕК РИСК-МЕНЕДЖМЕНТА
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def update_price_profit(tg_id, num):
 | 
					 | 
				
			||||||
    """Обновить цену тейк-профита (прибыль) пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        await session.execute(update(URMS).where(URMS.tg_id == tg_id).values(price_profit=num))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def update_price_loss(tg_id, num):
 | 
					 | 
				
			||||||
    """Обновить цену тейк-лосса (убыток) пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        await session.execute(update(URMS).where(URMS.tg_id == tg_id).values(price_loss=num))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def update_max_risk_deal(tg_id, num):
 | 
					 | 
				
			||||||
    """Обновить максимальную сумму риска пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        await session.execute(update(URMS).where(URMS.tg_id == tg_id).values(max_risk_deal=num))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def update_entry_order_type(tg_id, order_type):
 | 
					 | 
				
			||||||
    """Обновить тип входного ордера пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        await session.execute(
 | 
					 | 
				
			||||||
            update(UMS)
 | 
					 | 
				
			||||||
            .where(UMS.tg_id == tg_id)
 | 
					 | 
				
			||||||
            .values(entry_order_type=order_type)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_limit_price(tg_id):
 | 
					 | 
				
			||||||
    """Получить лимитную цену пользователя как float, либо None."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        result = await session.execute(
 | 
					 | 
				
			||||||
            select(UMS.limit_order_price)
 | 
					 | 
				
			||||||
            .where(UMS.tg_id == tg_id)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        price = result.scalar_one_or_none()
 | 
					 | 
				
			||||||
        if price:
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                return float(price)
 | 
					 | 
				
			||||||
            except ValueError:
 | 
					 | 
				
			||||||
                return None
 | 
					 | 
				
			||||||
        return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def update_limit_price(tg_id, price):
 | 
					 | 
				
			||||||
    """Обновить лимитную цену пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        await session.execute(
 | 
					 | 
				
			||||||
            update(UMS)
 | 
					 | 
				
			||||||
            .where(UMS.tg_id == tg_id)
 | 
					 | 
				
			||||||
            .values(limit_order_price=str(price))
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def update_commission_fee(tg_id, num):
 | 
					 | 
				
			||||||
    """Обновить комиссию пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        await session.execute(update(URMS).where(URMS.tg_id == tg_id).values(commission_fee=num))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_user_timer(tg_id):
 | 
					 | 
				
			||||||
    """Получить данные о таймере пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        result = await session.execute(select(UserTimer).where(UserTimer.tg_id == tg_id))
 | 
					 | 
				
			||||||
        user_timer = result.scalars().first()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if not user_timer:
 | 
					 | 
				
			||||||
            logging.info(f"No timer found for user {tg_id}")
 | 
					 | 
				
			||||||
            return None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        timer_minutes = user_timer.timer_minutes
 | 
					 | 
				
			||||||
        timer_start = user_timer.timer_start
 | 
					 | 
				
			||||||
        timer_end = user_timer.timer_end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        logging.info(f"Timer data for tg_id={tg_id}: "
 | 
					 | 
				
			||||||
                     f"timer_minutes={timer_minutes}, "
 | 
					 | 
				
			||||||
                     f"timer_start={timer_start}, "
 | 
					 | 
				
			||||||
                     f"timer_end={timer_end}")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        remaining = None
 | 
					 | 
				
			||||||
        if timer_end:
 | 
					 | 
				
			||||||
            remaining = max(0, int((timer_end - datetime.utcnow()).total_seconds() // 60))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return {
 | 
					 | 
				
			||||||
            "timer_minutes": timer_minutes,
 | 
					 | 
				
			||||||
            "timer_start": timer_start,
 | 
					 | 
				
			||||||
            "timer_end": timer_end,
 | 
					 | 
				
			||||||
            "remaining_minutes": remaining
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def update_user_timer(tg_id, minutes: int):
 | 
					 | 
				
			||||||
    """Обновить данные о таймере пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            timer_start = None
 | 
					 | 
				
			||||||
            timer_end = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if minutes > 0:
 | 
					 | 
				
			||||||
                timer_start = datetime.utcnow()
 | 
					 | 
				
			||||||
                timer_end = timer_start + timedelta(minutes=minutes)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            result = await session.execute(select(UserTimer).where(UserTimer.tg_id == tg_id))
 | 
					 | 
				
			||||||
            user_timer = result.scalars().first()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if user_timer:
 | 
					 | 
				
			||||||
                user_timer.timer_minutes = minutes
 | 
					 | 
				
			||||||
                user_timer.timer_start = timer_start
 | 
					 | 
				
			||||||
                user_timer.timer_end = timer_end
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                user_timer = UserTimer(
 | 
					 | 
				
			||||||
                    tg_id=tg_id,
 | 
					 | 
				
			||||||
                    timer_minutes=minutes,
 | 
					 | 
				
			||||||
                    timer_start=timer_start,
 | 
					 | 
				
			||||||
                    timer_end=timer_end
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                session.add(user_timer)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            await session.commit()
 | 
					 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            logging.error(f"Ошибка обновления таймера пользователя {tg_id}: {e}")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def get_martingale_step(tg_id):
 | 
					 | 
				
			||||||
    """Получить шаг мартингейла пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        result = await session.execute(select(UMS).where(UMS.tg_id == tg_id))
 | 
					 | 
				
			||||||
        user_settings = result.scalars().first()
 | 
					 | 
				
			||||||
        return user_settings.martingale_step
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def update_martingale_step(tg_id, step):
 | 
					 | 
				
			||||||
    """Обновить шаг мартингейла пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(martingale_step=step))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def update_switch_mode_enabled(tg_id, switch_mode):
 | 
					 | 
				
			||||||
    """Обновить режим переключения пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(switch_mode_enabled=switch_mode))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def update_switch_state(tg_id, switch_state):
 | 
					 | 
				
			||||||
    """Обновить состояние переключения пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(switch_state=switch_state))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def update_trigger(tg_id, trigger):
 | 
					 | 
				
			||||||
    """Обновить триггер пользователя."""
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        await session.execute(update(UCS).where(UCS.tg_id == tg_id).values(trigger=trigger))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def set_last_series_info(tg_id: int, last_side: str):
 | 
					 | 
				
			||||||
    async with async_session() as session:
 | 
					 | 
				
			||||||
        async with session.begin():
 | 
					 | 
				
			||||||
            # Обновляем запись
 | 
					 | 
				
			||||||
            result = await session.execute(
 | 
					 | 
				
			||||||
                update(UMS)
 | 
					 | 
				
			||||||
                .where(UMS.tg_id == tg_id)
 | 
					 | 
				
			||||||
                .values(last_side=last_side)
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            if result.rowcount == 0:
 | 
					 | 
				
			||||||
                # Если запись не существует, создаём новую
 | 
					 | 
				
			||||||
                new_entry = UMS(
 | 
					 | 
				
			||||||
                    tg_id=tg_id,
 | 
					 | 
				
			||||||
                    last_side=last_side,
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                session.add(new_entry)
 | 
					 | 
				
			||||||
        await session.commit()
 | 
					 | 
				
			||||||
@@ -1,38 +0,0 @@
 | 
				
			|||||||
import app.telegram.Keyboards.inline_keyboards as inline_markup
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import app.telegram.database.requests as rq
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def reg_new_user_default_additional_settings(id, message):
 | 
					 | 
				
			||||||
    tg_id = id
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await rq.set_new_user_default_additional_settings(tg_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def main_settings_message(id, message):
 | 
					 | 
				
			||||||
    text = '''<b>Дополнительные параметры</b>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<b>- Сохранить как шаблон стратегии:</b> да / нет  
 | 
					 | 
				
			||||||
<b>- Автозапуск после сохранения:</b> да / нет  
 | 
					 | 
				
			||||||
<b>- Уведомления в Telegram:</b> включено / отключено '''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.edit_text(text=text, parse_mode='html', reply_markup=inline_markup.additional_settings_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def save_pattern_message(message, state):
 | 
					 | 
				
			||||||
    text = '''<b>Сохранение шаблона</b>
 | 
					 | 
				
			||||||
                                                                                          
 | 
					 | 
				
			||||||
    Описание... '''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.buttons_yes_no_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def auto_start_message(message, state):
 | 
					 | 
				
			||||||
    text = '''<b>Автозапуск</b>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Описание... '''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.buttons_yes_no_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def notifications_message(message, state):
 | 
					 | 
				
			||||||
    text = '''<b>Уведомления</b>                                                                
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Описание... '''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.buttons_on_off_markup)                                                                                                                                
 | 
					 | 
				
			||||||
@@ -1,143 +0,0 @@
 | 
				
			|||||||
import logging.config
 | 
					 | 
				
			||||||
import app.telegram.Keyboards.inline_keyboards as inline_markup
 | 
					 | 
				
			||||||
from aiogram import Router, F
 | 
					 | 
				
			||||||
from aiogram.types import Message, CallbackQuery
 | 
					 | 
				
			||||||
from aiogram.fsm.context import FSMContext
 | 
					 | 
				
			||||||
import app.telegram.database.requests as rq
 | 
					 | 
				
			||||||
from app.states.States import condition_settings
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from logger_helper.logger_helper import LOGGING_CONFIG
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
logging.config.dictConfig(LOGGING_CONFIG)
 | 
					 | 
				
			||||||
logger = logging.getLogger("condition_settings")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
condition_settings_router = Router()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def reg_new_user_default_condition_settings(id):
 | 
					 | 
				
			||||||
    tg_id = id
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    trigger = await rq.get_for_registration_trigger(tg_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await rq.set_new_user_default_condition_settings(tg_id, trigger)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def main_settings_message(id, message):
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    tg_id = id
 | 
					 | 
				
			||||||
    trigger = await rq.get_for_registration_trigger(tg_id)
 | 
					 | 
				
			||||||
    text = f""" <b>Условия запуска</b>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<b>- Режим торговли:</b>  {trigger}
 | 
					 | 
				
			||||||
<b>- Таймер: </b> установить таймер / удалить таймер
 | 
					 | 
				
			||||||
"""
 | 
					 | 
				
			||||||
    await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.condition_settings_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def trigger_message(id, message, state: FSMContext):
 | 
					 | 
				
			||||||
    await state.set_state(condition_settings.trigger)
 | 
					 | 
				
			||||||
    text = '''
 | 
					 | 
				
			||||||
<b>- Автоматический:</b> торговля будет происходить в рамках серии ставок.
 | 
					 | 
				
			||||||
<b>- Ручной:</b> торговля будет происходить только в ручном режиме.
 | 
					 | 
				
			||||||
<em>- Выберите тип триггера:</em>'''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.trigger_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@condition_settings_router.callback_query(F.data == "clb_trigger_manual")
 | 
					 | 
				
			||||||
async def trigger_manual_callback(callback: CallbackQuery, state: FSMContext):
 | 
					 | 
				
			||||||
    await state.set_state(condition_settings.trigger)
 | 
					 | 
				
			||||||
    await rq.update_trigger(tg_id=callback.from_user.id, trigger="Ручной")
 | 
					 | 
				
			||||||
    await main_settings_message(callback.from_user.id, callback.message)
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@condition_settings_router.callback_query(F.data == "clb_trigger_auto")
 | 
					 | 
				
			||||||
async def trigger_manual_callback(callback: CallbackQuery, state: FSMContext):
 | 
					 | 
				
			||||||
    await state.set_state(condition_settings.trigger)
 | 
					 | 
				
			||||||
    await rq.update_trigger(tg_id=callback.from_user.id, trigger="Автоматический")
 | 
					 | 
				
			||||||
    await main_settings_message(callback.from_user.id, callback.message)
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def timer_message(id, message: Message, state: FSMContext):
 | 
					 | 
				
			||||||
    await state.set_state(condition_settings.timer)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    timer_info = await rq.get_user_timer(id)
 | 
					 | 
				
			||||||
    if timer_info is None:
 | 
					 | 
				
			||||||
        await message.answer("Таймер не установлен.", reply_markup=inline_markup.timer_markup)
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer(
 | 
					 | 
				
			||||||
        f"Таймер установлен на: {timer_info['timer_minutes']} мин\n",
 | 
					 | 
				
			||||||
        reply_markup=inline_markup.timer_markup
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@condition_settings_router.callback_query(F.data == "clb_set_timer")
 | 
					 | 
				
			||||||
async def set_timer_callback(callback: CallbackQuery, state: FSMContext):
 | 
					 | 
				
			||||||
    await state.set_state(condition_settings.timer)  # состояние для ввода времени
 | 
					 | 
				
			||||||
    await callback.message.answer("Введите время работы в минутах (например, 60):", reply_markup=inline_markup.cancel)
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@condition_settings_router.message(condition_settings.timer)
 | 
					 | 
				
			||||||
async def process_timer_input(message: Message, state: FSMContext):
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        minutes = int(message.text)
 | 
					 | 
				
			||||||
        if minutes <= 0:
 | 
					 | 
				
			||||||
            await message.reply("Введите число больше нуля.")
 | 
					 | 
				
			||||||
            return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await rq.update_user_timer(message.from_user.id, minutes)
 | 
					 | 
				
			||||||
        logger.info("Timer set for user %s: %s minutes", message.from_user.id, minutes)
 | 
					 | 
				
			||||||
        await timer_message(message.from_user.id, message, state)
 | 
					 | 
				
			||||||
        await state.clear()
 | 
					 | 
				
			||||||
    except ValueError:
 | 
					 | 
				
			||||||
        await message.reply("Пожалуйста, введите корректное число.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@condition_settings_router.callback_query(F.data == "clb_delete_timer")
 | 
					 | 
				
			||||||
async def delete_timer_callback(callback: CallbackQuery, state: FSMContext):
 | 
					 | 
				
			||||||
    await state.clear()
 | 
					 | 
				
			||||||
    await rq.update_user_timer(callback.from_user.id, 0)
 | 
					 | 
				
			||||||
    logger.info("Timer deleted for user %s", callback.from_user.id)
 | 
					 | 
				
			||||||
    await timer_message(callback.from_user.id, callback.message, state)
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def filter_volatility_message(message, state):
 | 
					 | 
				
			||||||
    text = '''Фильтр волатильности
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Описание... '''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.buttons_on_off_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def external_cues_message(message, state):
 | 
					 | 
				
			||||||
    text = '''<b>Внешние сигналы</b>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Описание... '''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer(text=text, parse_mode='html', reply_markup=None)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def trading_cues_message(message, state):
 | 
					 | 
				
			||||||
    text = '''<b>Использование сигналов</b>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Описание... '''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.buttons_yes_no_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def webhook_message(message, state):
 | 
					 | 
				
			||||||
    text = '''Скиньте ссылку на <b>webhook</b> (если есть trading view): '''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer(text=text, parse_mode='html')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def ai_analytics_message(message, state):
 | 
					 | 
				
			||||||
    text = '''<b>ИИ - Аналитика</b> 
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Описание... '''
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.buttons_yes_no_markup)
 | 
					 | 
				
			||||||
@@ -1,29 +0,0 @@
 | 
				
			|||||||
import app.telegram.Keyboards.inline_keyboards as inline_markup
 | 
					 | 
				
			||||||
import app.telegram.Keyboards.reply_keyboards as reply_markup
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def start_message(message):
 | 
					 | 
				
			||||||
    username = ''
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    if message.from_user.first_name == None:
 | 
					 | 
				
			||||||
        username = message.from_user.last_name
 | 
					 | 
				
			||||||
    elif message.from_user.last_name == None:
 | 
					 | 
				
			||||||
        username = message.from_user.first_name
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        username = f'{message.from_user.first_name} {message.from_user.last_name}'
 | 
					 | 
				
			||||||
    await message.answer(f""" Привет <b>{username}</b>! 👋""", parse_mode='html')
 | 
					 | 
				
			||||||
    await message.answer("Добро пожаловать в чат-робот для автоматизации трейдинга — вашего надежного помощника для анализа рынка и принятия взвешенных решений.",
 | 
					 | 
				
			||||||
                         parse_mode='html', reply_markup=inline_markup.start_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def profile_message(username, message):
 | 
					 | 
				
			||||||
    await message.answer(f""" <b>@{username}</b>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Баланс  
 | 
					 | 
				
			||||||
⭐️ 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
""", parse_mode='html', reply_markup=inline_markup.settings_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def check_profile_message(message, username):
 | 
					 | 
				
			||||||
    await message.answer(f'С возвращением, {username}!', reply_markup=reply_markup.base_buttons_markup)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
async def settings_message(message):
 | 
					 | 
				
			||||||
    await message.edit_text("Выберите что настроить", reply_markup=inline_markup.special_settings_markup)
 | 
					 | 
				
			||||||
@@ -1,372 +0,0 @@
 | 
				
			|||||||
from aiogram import Router
 | 
					 | 
				
			||||||
import logging.config
 | 
					 | 
				
			||||||
import app.telegram.Keyboards.inline_keyboards as inline_markup
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from pybit.unified_trading import HTTP
 | 
					 | 
				
			||||||
import app.telegram.database.requests as rq
 | 
					 | 
				
			||||||
from aiogram.types import Message, CallbackQuery
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from app.services.Bybit.functions.price_symbol import get_price
 | 
					 | 
				
			||||||
from app.services.Bybit.functions.Futures import safe_float, calculate_total_budget, get_bybit_client
 | 
					 | 
				
			||||||
from app.states.States import update_main_settings
 | 
					 | 
				
			||||||
from logger_helper.logger_helper import LOGGING_CONFIG
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
logging.config.dictConfig(LOGGING_CONFIG)
 | 
					 | 
				
			||||||
logger = logging.getLogger("main_settings")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
router_main_settings = Router()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def reg_new_user_default_main_settings(id, message):
 | 
					 | 
				
			||||||
    tg_id = id
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    trading_mode = await rq.get_for_registration_trading_mode()
 | 
					 | 
				
			||||||
    margin_type = await rq.get_for_registration_margin_type()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await rq.set_new_user_default_main_settings(tg_id, trading_mode, margin_type)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def main_settings_message(id, message):
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        data = await rq.get_user_main_settings(id)
 | 
					 | 
				
			||||||
        tg_id = id
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        data_main_stgs = await rq.get_user_main_settings(id)
 | 
					 | 
				
			||||||
        data_risk_stgs = await rq.get_user_risk_management_settings(id)
 | 
					 | 
				
			||||||
        client = await get_bybit_client(tg_id)
 | 
					 | 
				
			||||||
        symbol = await rq.get_symbol(tg_id)
 | 
					 | 
				
			||||||
        max_martingale_steps = (data_main_stgs or {}).get('maximal_quantity', 0)
 | 
					 | 
				
			||||||
        commission_fee = (data_risk_stgs or {}).get('commission_fee')
 | 
					 | 
				
			||||||
        starting_quantity = safe_float((data_main_stgs or {}).get('starting_quantity'))
 | 
					 | 
				
			||||||
        martingale_factor = safe_float((data_main_stgs or {}).get('martingale_factor'))
 | 
					 | 
				
			||||||
        fee_info = client.get_fee_rates(category='linear', symbol=symbol)
 | 
					 | 
				
			||||||
        leverage = safe_float((data_main_stgs or {}).get('size_leverage'))
 | 
					 | 
				
			||||||
        price = await get_price(tg_id, symbol=symbol)
 | 
					 | 
				
			||||||
        entry_price = safe_float(price)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if commission_fee == "Да":
 | 
					 | 
				
			||||||
            commission_fee_percent = safe_float(fee_info['result']['list'][0]['takerFeeRate'])
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            commission_fee_percent = 0.0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        total_budget = await calculate_total_budget(
 | 
					 | 
				
			||||||
            starting_quantity=starting_quantity,
 | 
					 | 
				
			||||||
            martingale_factor=martingale_factor,
 | 
					 | 
				
			||||||
            max_steps=max_martingale_steps,
 | 
					 | 
				
			||||||
            commission_fee_percent=commission_fee_percent,
 | 
					 | 
				
			||||||
            leverage=leverage,
 | 
					 | 
				
			||||||
            current_price=entry_price,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await message.answer(f"""<b>Основные настройки</b>
 | 
					 | 
				
			||||||
         
 | 
					 | 
				
			||||||
    <b>- Режим торговли:</b> {data['trading_mode']}
 | 
					 | 
				
			||||||
    <b>- Состояние свитча:</b> {data['switch_state']}
 | 
					 | 
				
			||||||
    <b>- Направление последней сделки:</b> {data['last_side']}
 | 
					 | 
				
			||||||
    <b>- Тип маржи:</b> {data['margin_type']}
 | 
					 | 
				
			||||||
    <b>- Размер кредитного плеча:</b> х{data['size_leverage']}
 | 
					 | 
				
			||||||
    <b>- Начальная ставка:</b> {data['starting_quantity']}
 | 
					 | 
				
			||||||
    <b>- Коэффициент мартингейла:</b> {data['martingale_factor']}
 | 
					 | 
				
			||||||
    <b>- Текущий шаг:</b> {data['martingale_step']}
 | 
					 | 
				
			||||||
    <b>- Максимальное количество ставок в серии:</b> {data['maximal_quantity']}   
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    <b>- Требуемый бюджет:</b> {total_budget:.2f} USDT
 | 
					 | 
				
			||||||
    """, parse_mode='html', reply_markup=inline_markup.main_settings_markup)
 | 
					 | 
				
			||||||
    except PermissionError as e:
 | 
					 | 
				
			||||||
        logger.error("Authenticated endpoints require keys: %s", e)
 | 
					 | 
				
			||||||
        await message.answer("Вы не авторизованы.", reply_markup=inline_markup.connect_bybit_api_message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def trading_mode_message(message, state):
 | 
					 | 
				
			||||||
    await state.set_state(update_main_settings.trading_mode)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.edit_text("""<b>Режим торговли</b>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<b>Лонг</b> — стратегия, ориентированная на покупку актива с целью заработать на повышении его стоимости.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<b>Шорт</b> — метод продажи активов, взятых в кредит, чтобы получить прибыль от снижения цены.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<b>Свитч</b> — динамическое переключение между торговыми режимами для максимизации эффективности.
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
<em>Выберите ниже для изменений:</em>    
 | 
					 | 
				
			||||||
""", parse_mode='html', reply_markup=inline_markup.trading_mode_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_main_settings.callback_query(update_main_settings.trading_mode)
 | 
					 | 
				
			||||||
async def state_trading_mode(callback: CallbackQuery, state):
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    id = callback.from_user.id
 | 
					 | 
				
			||||||
    data_settings = await rq.get_user_main_settings(id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        match callback.data:
 | 
					 | 
				
			||||||
            case 'trade_mode_long':
 | 
					 | 
				
			||||||
                await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Long")
 | 
					 | 
				
			||||||
                await rq.update_trade_mode_user(id, 'Long')
 | 
					 | 
				
			||||||
                await main_settings_message(id, callback.message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                await state.clear()
 | 
					 | 
				
			||||||
            case 'trade_mode_short':
 | 
					 | 
				
			||||||
                await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Short")
 | 
					 | 
				
			||||||
                await rq.update_trade_mode_user(id, 'Short')
 | 
					 | 
				
			||||||
                await main_settings_message(id, callback.message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                await state.clear()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            case 'trade_mode_switch':
 | 
					 | 
				
			||||||
                await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Switch")
 | 
					 | 
				
			||||||
                await rq.update_trade_mode_user(id, 'Switch')
 | 
					 | 
				
			||||||
                await main_settings_message(id, callback.message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                await state.clear()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            case 'trade_mode_smart':
 | 
					 | 
				
			||||||
                await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Smart")
 | 
					 | 
				
			||||||
                await rq.update_trade_mode_user(id, 'Smart')
 | 
					 | 
				
			||||||
                await main_settings_message(id, callback.message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                await state.clear()
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        logger.error("Ошибка при обновлении режима торговли: %s", e)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def switch_mode_enabled_message(message, state):
 | 
					 | 
				
			||||||
    await state.set_state(update_main_settings.switch_mode_enabled)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.edit_text(
 | 
					 | 
				
			||||||
        f"""<b> Состояние свитча</b>
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        <b>По направлению</b> - по направлению последней сделки предыдущей серии
 | 
					 | 
				
			||||||
        <b>Против направления</b> - против направления последней сделки предыдущей серии
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        <em>По умолчанию при первом запуске бота, направление сделки установлено на "Buy".</em>
 | 
					 | 
				
			||||||
        <em>Выберите ниже для изменений:</em>""", parse_mode='html',
 | 
					 | 
				
			||||||
        reply_markup=inline_markup.switch_state_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_main_settings.callback_query(lambda c: c.data in ["clb_long_switch", "clb_short_switch"])
 | 
					 | 
				
			||||||
async def state_switch_mode_enabled(callback: CallbackQuery, state):
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
    tg_id = callback.from_user.id
 | 
					 | 
				
			||||||
    val = "По направлению" if callback.data == "clb_long_switch" else "Против направления"
 | 
					 | 
				
			||||||
    if val == "По направлению":
 | 
					 | 
				
			||||||
        await rq.update_switch_state(tg_id, "По направлению")
 | 
					 | 
				
			||||||
        await callback.message.answer(f"Состояние свитча: {val}")
 | 
					 | 
				
			||||||
        await main_settings_message(tg_id, callback.message)
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        await rq.update_switch_state(tg_id, "Против направления")
 | 
					 | 
				
			||||||
        await callback.message.answer(f"Состояние свитча: {val}")
 | 
					 | 
				
			||||||
        await main_settings_message(tg_id, callback.message)
 | 
					 | 
				
			||||||
    await state.clear()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def size_leverage_message(message, state):
 | 
					 | 
				
			||||||
    await state.set_state(update_main_settings.size_leverage)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.edit_text("Введите размер <b>кредитного плеча</b> (от 1 до 100): ", parse_mode='html',
 | 
					 | 
				
			||||||
                            reply_markup=inline_markup.back_btn_list_settings_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_main_settings.message(update_main_settings.size_leverage)
 | 
					 | 
				
			||||||
async def state_size_leverage(message: Message, state):
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        leverage = float(message.text)
 | 
					 | 
				
			||||||
        if leverage <= 0:
 | 
					 | 
				
			||||||
            raise ValueError("Неверное значение")
 | 
					 | 
				
			||||||
    except ValueError:
 | 
					 | 
				
			||||||
        await message.answer(
 | 
					 | 
				
			||||||
            "Ошибка: пожалуйста, введите положительное число для кредитного плеча."
 | 
					 | 
				
			||||||
            "\nПопробуйте снова."
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await state.update_data(size_leverage=message.text)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    data = await state.get_data()
 | 
					 | 
				
			||||||
    tg_id = message.from_user.id
 | 
					 | 
				
			||||||
    symbol = await rq.get_symbol(tg_id)
 | 
					 | 
				
			||||||
    leverage = data['size_leverage']
 | 
					 | 
				
			||||||
    client = await get_bybit_client(tg_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    instruments_resp = client.get_instruments_info(category="linear", symbol=symbol)
 | 
					 | 
				
			||||||
    info = instruments_resp.get("result", {}).get("list", [])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    max_leverage = safe_float(info[0].get("leverageFilter", {}).get("maxLeverage", 0))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if safe_float(leverage) > max_leverage:
 | 
					 | 
				
			||||||
        await message.answer(
 | 
					 | 
				
			||||||
            f"Запрошенное кредитное плечо {leverage} превышает максимальное {max_leverage} для {symbol}. "
 | 
					 | 
				
			||||||
            f"Устанавливаю максимальное.",
 | 
					 | 
				
			||||||
            reply_markup=inline_markup.back_to_main,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        logger.info(
 | 
					 | 
				
			||||||
            f"Запрошенное кредитное плечо {leverage} превышает максимальное {max_leverage} для {symbol}. Устанавливаю максимальное.")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await rq.update_size_leverange(message.from_user.id, max_leverage)
 | 
					 | 
				
			||||||
        await main_settings_message(message.from_user.id, message)
 | 
					 | 
				
			||||||
        await state.clear()
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        await message.answer(f"✅ Изменено: {leverage}")
 | 
					 | 
				
			||||||
        await rq.update_size_leverange(message.from_user.id, safe_float(leverage))
 | 
					 | 
				
			||||||
        await main_settings_message(message.from_user.id, message)
 | 
					 | 
				
			||||||
        await state.clear()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def martingale_factor_message(message, state):
 | 
					 | 
				
			||||||
    await state.set_state(update_main_settings.martingale_factor)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.edit_text("Введите <b>коэффициент Мартингейла:</b>", parse_mode='html',
 | 
					 | 
				
			||||||
                            reply_markup=inline_markup.back_btn_list_settings_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_main_settings.message(update_main_settings.martingale_factor)
 | 
					 | 
				
			||||||
async def state_martingale_factor(message: Message, state):
 | 
					 | 
				
			||||||
    await state.update_data(martingale_factor=message.text)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    data = await state.get_data()
 | 
					 | 
				
			||||||
    data_settings = await rq.get_user_main_settings(message.from_user.id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if data['martingale_factor'].isdigit() and int(data['martingale_factor']) <= 100:
 | 
					 | 
				
			||||||
        await message.answer(f"✅ Изменено: {data_settings['martingale_factor']} → {data['martingale_factor']}")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await rq.update_martingale_factor(message.from_user.id, data['martingale_factor'])
 | 
					 | 
				
			||||||
        await main_settings_message(message.from_user.id, message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await state.clear()
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        val = data['martingale_factor']
 | 
					 | 
				
			||||||
        await message.answer(
 | 
					 | 
				
			||||||
            f"⛔️ Ошибка: ваше значение ({val}) или выше лимита (100) или вы вводите неверные символы")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await main_settings_message(message.from_user.id, message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def margin_type_message(message, state):
 | 
					 | 
				
			||||||
    await state.set_state(update_main_settings.margin_type)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.edit_text("""<b>Тип маржи</b>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<b>Изолированная маржа</b>  
 | 
					 | 
				
			||||||
Этот тип маржи позволяет ограничить риск конкретной позиции. 
 | 
					 | 
				
			||||||
При использовании изолированной маржи вы выделяете определённую сумму средств только для одной позиции. 
 | 
					 | 
				
			||||||
Если позиция начинает приносить убытки, ваши потери ограничиваются этой суммой, 
 | 
					 | 
				
			||||||
и остальные средства на счёте не затрагиваются.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<b>Кросс-маржа</b>  
 | 
					 | 
				
			||||||
Кросс-маржа объединяет весь маржинальный баланс на счёте и использует все доступные средства для поддержания открытых позиций. 
 | 
					 | 
				
			||||||
В случае убытков средства с других позиций или баланса автоматически покрывают дефицит, 
 | 
					 | 
				
			||||||
снижая риск ликвидации, но увеличивая общий риск потери капитала.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<em>Выберите ниже для изменений:</em>
 | 
					 | 
				
			||||||
""", parse_mode='html', reply_markup=inline_markup.margin_type_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_main_settings.callback_query(update_main_settings.margin_type)
 | 
					 | 
				
			||||||
async def state_margin_type(callback: CallbackQuery, state):
 | 
					 | 
				
			||||||
    callback_data = callback.data
 | 
					 | 
				
			||||||
    if callback_data in ['margin_type_isolated', 'margin_type_cross']:
 | 
					 | 
				
			||||||
        tg_id = callback.from_user.id
 | 
					 | 
				
			||||||
        api_key = await rq.get_bybit_api_key(tg_id)
 | 
					 | 
				
			||||||
        secret_key = await rq.get_bybit_secret_key(tg_id)
 | 
					 | 
				
			||||||
        data_settings = await rq.get_user_main_settings(tg_id)
 | 
					 | 
				
			||||||
        symbol = await rq.get_symbol(tg_id)
 | 
					 | 
				
			||||||
        client = HTTP(api_key=api_key, api_secret=secret_key)
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            active_positions = client.get_positions(category='linear', settleCoin="USDT")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            positions = active_positions.get('result', {}).get('list', [])
 | 
					 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            logger.error("Ошибка при получении активных позиций: %s", e)
 | 
					 | 
				
			||||||
            positions = []
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for pos in positions:
 | 
					 | 
				
			||||||
            size = pos.get('size')
 | 
					 | 
				
			||||||
            if float(size) > 0:
 | 
					 | 
				
			||||||
                await callback.answer(
 | 
					 | 
				
			||||||
                    "⚠️ Маржинальный режим нельзя менять при открытой позиции"
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            match callback.data:
 | 
					 | 
				
			||||||
                case 'margin_type_isolated':
 | 
					 | 
				
			||||||
                    await callback.answer()
 | 
					 | 
				
			||||||
                    await callback.message.answer(f"✅ Изменено: {data_settings['margin_type']} → Isolated")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    await rq.update_margin_type(tg_id, 'Isolated')
 | 
					 | 
				
			||||||
                    await main_settings_message(tg_id, callback.message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    await state.clear()
 | 
					 | 
				
			||||||
                case 'margin_type_cross':
 | 
					 | 
				
			||||||
                    await callback.answer()
 | 
					 | 
				
			||||||
                    await callback.message.answer(f"✅ Изменено: {data_settings['margin_type']} → Cross")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    await rq.update_margin_type(tg_id, 'Cross')
 | 
					 | 
				
			||||||
                    await main_settings_message(tg_id, callback.message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    await state.clear()
 | 
					 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            logger.error("Ошибка при изменении типа маржи: %s", e)
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        await callback.answer()
 | 
					 | 
				
			||||||
        await main_settings_message(callback.from_user.id, callback.message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await state.clear()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def starting_quantity_message(message, state):
 | 
					 | 
				
			||||||
    await state.set_state(update_main_settings.starting_quantity)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.edit_text("Введите <b>начальную ставку:</b>", parse_mode='html',
 | 
					 | 
				
			||||||
                            reply_markup=inline_markup.back_btn_list_settings_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_main_settings.message(update_main_settings.starting_quantity)
 | 
					 | 
				
			||||||
async def state_starting_quantity(message: Message, state):
 | 
					 | 
				
			||||||
    await state.update_data(starting_quantity=message.text)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    data = await state.get_data()
 | 
					 | 
				
			||||||
    data_settings = await rq.get_user_main_settings(message.from_user.id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if data['starting_quantity'].isdigit():
 | 
					 | 
				
			||||||
        await message.answer(f"✅ Изменено: {data_settings['starting_quantity']} → {data['starting_quantity']}")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await rq.update_starting_quantity(message.from_user.id, data['starting_quantity'])
 | 
					 | 
				
			||||||
        await main_settings_message(message.from_user.id, message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await state.clear()
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        await message.answer("⛔️ Ошибка: вы вводите неверные символы")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await main_settings_message(message.from_user.id, message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def maximum_quantity_message(message, state):
 | 
					 | 
				
			||||||
    await state.set_state(update_main_settings.maximal_quantity)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.edit_text("Введите максимальное количество серии ставок:",
 | 
					 | 
				
			||||||
                            reply_markup=inline_markup.back_btn_list_settings_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_main_settings.message(update_main_settings.maximal_quantity)
 | 
					 | 
				
			||||||
async def state_maximal_quantity(message: Message, state):
 | 
					 | 
				
			||||||
    await state.update_data(maximal_quantity=message.text)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    data = await state.get_data()
 | 
					 | 
				
			||||||
    data_settings = await rq.get_user_main_settings(message.from_user.id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if data['maximal_quantity'].isdigit() and int(data['maximal_quantity']) <= 100:
 | 
					 | 
				
			||||||
        await message.answer(f"✅ Изменено: {data_settings['maximal_quantity']} → {data['maximal_quantity']}")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await rq.update_maximal_quantity(message.from_user.id, data['maximal_quantity'])
 | 
					 | 
				
			||||||
        await main_settings_message(message.from_user.id, message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await state.clear()
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        val = data['maximal_quantity']
 | 
					 | 
				
			||||||
        await message.answer(
 | 
					 | 
				
			||||||
            f'⛔️ Ошибка: ваше значение ({val}) или выше лимита (100) или вы вводите неверные символы')
 | 
					 | 
				
			||||||
        logger.error(f'⛔️ Ошибка: ваше значение ({val}) или выше лимита (100) или вы вводите неверные символы')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await main_settings_message(message.from_user.id, message)
 | 
					 | 
				
			||||||
							
								
								
									
										27
									
								
								app/telegram/functions/profile_tg.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								app/telegram/functions/profile_tg.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aiogram.types import Message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import app.telegram.keyboards.reply as kbr
 | 
				
			||||||
 | 
					import database.request as rq
 | 
				
			||||||
 | 
					from logger_helper.logger_helper import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("profile_tg")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def user_profile_tg(tg_id: int, message: Message) -> None:
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        user = await rq.get_user(tg_id)
 | 
				
			||||||
 | 
					        if user:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                text="💎Ваш профиль:\n\n" "⚖️ Баланс: 0\n", reply_markup=kbr.profile
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await rq.create_user(tg_id=tg_id, username=user.username)
 | 
				
			||||||
 | 
					            await rq.set_user_symbol(tg_id=tg_id, symbol="BTCUSDT")
 | 
				
			||||||
 | 
					            await rq.create_user_additional_settings(tg_id=tg_id)
 | 
				
			||||||
 | 
					            await rq.create_user_risk_management(tg_id=tg_id)
 | 
				
			||||||
 | 
					            await user_profile_tg(tg_id=tg_id, message=message)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error processing user profile: %s", e)
 | 
				
			||||||
@@ -1,160 +0,0 @@
 | 
				
			|||||||
from aiogram import Router
 | 
					 | 
				
			||||||
import app.telegram.Keyboards.inline_keyboards as inline_markup
 | 
					 | 
				
			||||||
import logging.config
 | 
					 | 
				
			||||||
import app.telegram.database.requests as rq
 | 
					 | 
				
			||||||
from aiogram.types import Message, CallbackQuery
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from app.states.States import update_risk_management_settings
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from logger_helper.logger_helper import LOGGING_CONFIG
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
logging.config.dictConfig(LOGGING_CONFIG)
 | 
					 | 
				
			||||||
logger = logging.getLogger("risk_management_settings")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
router_risk_management_settings = Router()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def reg_new_user_default_risk_management_settings(id, message):
 | 
					 | 
				
			||||||
    tg_id = id
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await rq.set_new_user_default_risk_management_settings(tg_id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def main_settings_message(id, message):
 | 
					 | 
				
			||||||
    data = await rq.get_user_risk_management_settings(id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    text = f"""<b>Риск менеджмент</b>,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <b>- Процент изменения цены для фиксации прибыли:</b> {data.get('price_profit', 0)}%
 | 
					 | 
				
			||||||
    <b>- Процент изменения цены для фиксации убытков:</b> {data.get('price_loss', 0)}%
 | 
					 | 
				
			||||||
    <b>- Максимальный риск на сделку (в % от баланса):</b> {data.get('max_risk_deal', 0)}%
 | 
					 | 
				
			||||||
    <b>- Комиссия биржи для расчета прибыли:</b> {data.get('commission_fee', "Да")}
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.risk_management_settings_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def price_profit_message(message, state):
 | 
					 | 
				
			||||||
    await state.set_state(update_risk_management_settings.price_profit)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    text = 'Введите число изменения цены для фиксации прибыли: '
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.cancel)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_risk_management_settings.message(update_risk_management_settings.price_profit)
 | 
					 | 
				
			||||||
async def state_price_profit(message: Message, state):
 | 
					 | 
				
			||||||
    await state.update_data(price_profit=message.text)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    data = await state.get_data()
 | 
					 | 
				
			||||||
    data_settings = await rq.get_user_risk_management_settings(message.from_user.id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if data['price_profit'].isdigit() and int(data['price_profit']) <= 100:
 | 
					 | 
				
			||||||
        await message.answer(f"✅ Изменено: {data_settings['price_profit']}% → {data['price_profit']}%")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await rq.update_price_profit(message.from_user.id, data['price_profit'])
 | 
					 | 
				
			||||||
        await main_settings_message(message.from_user.id, message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await state.clear()
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        val = data['price_profit']
 | 
					 | 
				
			||||||
        await message.answer(
 | 
					 | 
				
			||||||
            f'⛔️ Ошибка: ваше значение ({val}%) или выше лимита (100) или вы вводите неверные символы')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await main_settings_message(message.from_user.id, message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def price_loss_message(message, state):
 | 
					 | 
				
			||||||
    await state.set_state(update_risk_management_settings.price_loss)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    text = 'Введите число изменения цены для фиксации убытков: '
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.cancel)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_risk_management_settings.message(update_risk_management_settings.price_loss)
 | 
					 | 
				
			||||||
async def state_price_loss(message: Message, state):
 | 
					 | 
				
			||||||
    await state.update_data(price_loss=message.text)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    data = await state.get_data()
 | 
					 | 
				
			||||||
    data_settings = await rq.get_user_risk_management_settings(message.from_user.id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if data['price_loss'].isdigit() and int(data['price_loss']) <= 100:
 | 
					 | 
				
			||||||
        new_price_loss = int(data['price_loss'])
 | 
					 | 
				
			||||||
        old_price_loss = int(data_settings.get('price_loss', 0))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        current_price_profit = data_settings.get('price_profit')
 | 
					 | 
				
			||||||
        # Пробуем перевести price_profit в число, если это возможно
 | 
					 | 
				
			||||||
        try:
 | 
					 | 
				
			||||||
            current_price_profit_num = int(current_price_profit)
 | 
					 | 
				
			||||||
        except Exception as e:
 | 
					 | 
				
			||||||
            logger.error(e)
 | 
					 | 
				
			||||||
            current_price_profit_num = 0
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Флаг, если price_profit изначально равен 0 или совпадает со старым стоп-лоссом
 | 
					 | 
				
			||||||
        should_update_profit = (current_price_profit_num == 0) or (current_price_profit_num == abs(old_price_loss))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Обновляем стоп-лосс
 | 
					 | 
				
			||||||
        await rq.update_price_loss(message.from_user.id, new_price_loss)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        # Если нужно, меняем тейк-профит
 | 
					 | 
				
			||||||
        if should_update_profit:
 | 
					 | 
				
			||||||
            new_price_profit = abs(new_price_loss)
 | 
					 | 
				
			||||||
            await rq.update_price_profit(message.from_user.id, new_price_profit)
 | 
					 | 
				
			||||||
            await message.answer(f"✅ Стоп-лосс изменён: {old_price_loss}% → {new_price_loss}%\n"
 | 
					 | 
				
			||||||
                                 f"Тейк-профит автоматически установлен в: {new_price_profit}%")
 | 
					 | 
				
			||||||
        else:
 | 
					 | 
				
			||||||
            await message.answer(f"✅ Стоп-лосс изменён: {old_price_loss}% → {new_price_loss}%")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await main_settings_message(message.from_user.id, message)
 | 
					 | 
				
			||||||
        await state.clear()
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        val = data['price_loss']
 | 
					 | 
				
			||||||
        await message.answer(
 | 
					 | 
				
			||||||
            f'⛔️ Ошибка: ваше значение ({val}%) выше лимита (100) или содержит неверные символы')
 | 
					 | 
				
			||||||
        await main_settings_message(message.from_user.id, message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def max_risk_deal_message(message, state):
 | 
					 | 
				
			||||||
    await state.set_state(update_risk_management_settings.max_risk_deal)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    text = 'Введите число (процент от баланса) для изменения максимального риска на сделку: '
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.cancel)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_risk_management_settings.message(update_risk_management_settings.max_risk_deal)
 | 
					 | 
				
			||||||
async def state_max_risk_deal(message: Message, state):
 | 
					 | 
				
			||||||
    await state.update_data(max_risk_deal=message.text)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    data = await state.get_data()
 | 
					 | 
				
			||||||
    data_settings = await rq.get_user_risk_management_settings(message.from_user.id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if data['max_risk_deal'].isdigit() and int(data['max_risk_deal']) <= 100:
 | 
					 | 
				
			||||||
        await message.answer(f"✅ Изменено: {data_settings['max_risk_deal']}% → {data['max_risk_deal']}%")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await rq.update_max_risk_deal(message.from_user.id, data['max_risk_deal'])
 | 
					 | 
				
			||||||
        await main_settings_message(message.from_user.id, message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await state.clear()
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        val = data['max_risk_deal']
 | 
					 | 
				
			||||||
        await message.answer(
 | 
					 | 
				
			||||||
            f'⛔️ Ошибка: ваше значение ({val}%) или выше лимита (100) или вы вводите неверные символы')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await main_settings_message(message.from_user.id, message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async def commission_fee_message(message, state):
 | 
					 | 
				
			||||||
    await state.set_state(update_risk_management_settings.commission_fee)
 | 
					 | 
				
			||||||
    await message.answer(text="Хотите учитывать комиссию биржи:", parse_mode='html',
 | 
					 | 
				
			||||||
                         reply_markup=inline_markup.buttons_yes_no_markup)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router_risk_management_settings.callback_query(lambda c: c.data in ["clb_yes", "clb_no"])
 | 
					 | 
				
			||||||
async def process_commission_fee_callback(callback: CallbackQuery, state):
 | 
					 | 
				
			||||||
    val = "Да" if callback.data == "clb_yes" else "Нет"
 | 
					 | 
				
			||||||
    await rq.update_commission_fee(callback.from_user.id, val)
 | 
					 | 
				
			||||||
    await callback.message.answer(f"✅ Изменено: {val}")
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
    await main_settings_message(callback.from_user.id, callback.message)
 | 
					 | 
				
			||||||
    await state.clear()
 | 
					 | 
				
			||||||
							
								
								
									
										32
									
								
								app/telegram/handlers/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								app/telegram/handlers/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					__all__ = "router"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aiogram import Router
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from app.telegram.handlers.add_bybit_api import router_add_bybit_api
 | 
				
			||||||
 | 
					from app.telegram.handlers.changing_the_symbol import router_changing_the_symbol
 | 
				
			||||||
 | 
					from app.telegram.handlers.close_orders import router_close_orders
 | 
				
			||||||
 | 
					from app.telegram.handlers.common import router_common
 | 
				
			||||||
 | 
					from app.telegram.handlers.get_positions_handlers import router_get_positions_handlers
 | 
				
			||||||
 | 
					from app.telegram.handlers.handlers_main import router_handlers_main
 | 
				
			||||||
 | 
					from app.telegram.handlers.main_settings import router_main_settings
 | 
				
			||||||
 | 
					from app.telegram.handlers.settings import router_settings
 | 
				
			||||||
 | 
					from app.telegram.handlers.start_trading import router_start_trading
 | 
				
			||||||
 | 
					from app.telegram.handlers.stop_trading import router_stop_trading
 | 
				
			||||||
 | 
					from app.telegram.handlers.tp_sl_handlers import router_tp_sl_handlers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router = Router(name=__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router.include_router(router_handlers_main)
 | 
				
			||||||
 | 
					router.include_router(router_add_bybit_api)
 | 
				
			||||||
 | 
					router.include_router(router_settings)
 | 
				
			||||||
 | 
					router.include_router(router_main_settings)
 | 
				
			||||||
 | 
					router.include_router(router_changing_the_symbol)
 | 
				
			||||||
 | 
					router.include_router(router_get_positions_handlers)
 | 
				
			||||||
 | 
					router.include_router(router_start_trading)
 | 
				
			||||||
 | 
					router.include_router(router_stop_trading)
 | 
				
			||||||
 | 
					router.include_router(router_close_orders)
 | 
				
			||||||
 | 
					router.include_router(router_tp_sl_handlers)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Do not add anything below this router
 | 
				
			||||||
 | 
					router.include_router(router_common)
 | 
				
			||||||
							
								
								
									
										150
									
								
								app/telegram/handlers/add_bybit_api.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								app/telegram/handlers/add_bybit_api.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,150 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aiogram import F, Router
 | 
				
			||||||
 | 
					from aiogram.fsm.context import FSMContext
 | 
				
			||||||
 | 
					from aiogram.types import CallbackQuery, Message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import app.telegram.keyboards.inline as kbi
 | 
				
			||||||
 | 
					import app.telegram.keyboards.reply as kbr
 | 
				
			||||||
 | 
					import database.request as rq
 | 
				
			||||||
 | 
					from app.bybit.profile_bybit import user_profile_bybit
 | 
				
			||||||
 | 
					from app.telegram.states.states import AddBybitApiState
 | 
				
			||||||
 | 
					from logger_helper.logger_helper import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("add_bybit_api")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router_add_bybit_api = Router(name="add_bybit_api")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_add_bybit_api.callback_query(F.data == "connect_platform")
 | 
				
			||||||
 | 
					async def connect_platform(callback: CallbackQuery, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handles the callback query to initiate Bybit platform connection.
 | 
				
			||||||
 | 
					    Sends instructions on how to create and provide API keys to the bot.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param callback: CallbackQuery object triggered by user interaction.
 | 
				
			||||||
 | 
					    :param state: FSMContext object to manage state data.
 | 
				
			||||||
 | 
					    :return: None
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					        await callback.answer()
 | 
				
			||||||
 | 
					        user = await rq.get_user(tg_id=callback.from_user.id)
 | 
				
			||||||
 | 
					        if user:
 | 
				
			||||||
 | 
					            await callback.message.answer(
 | 
				
			||||||
 | 
					                text=(
 | 
				
			||||||
 | 
					                    "Подключение Bybit аккаунта \n\n"
 | 
				
			||||||
 | 
					                    "1. Зарегистрируйтесь или войдите в свой аккаунт на Bybit по ссылке: "
 | 
				
			||||||
 | 
					                    "[Перейти на Bybit](https://www.bybit.com/invite?ref=YME83OJ).\n"
 | 
				
			||||||
 | 
					                    "2. В личном кабинете выберите раздел API. \n"
 | 
				
			||||||
 | 
					                    "3. Создание нового API ключа\n"
 | 
				
			||||||
 | 
					                    "   - Нажмите кнопку Create New Key (Создать новый ключ).\n"
 | 
				
			||||||
 | 
					                    "   - Выберите системно-сгенерированный ключ.\n"
 | 
				
			||||||
 | 
					                    "   - Укажите название API ключа (любое).  \n"
 | 
				
			||||||
 | 
					                    "   - Выберите права доступа для торговли (Trade).  \n"
 | 
				
			||||||
 | 
					                    "   - Можно ограничить доступ по IP для безопасности.\n"
 | 
				
			||||||
 | 
					                    "4. Подтверждение создания\n"
 | 
				
			||||||
 | 
					                    "   - Подтвердите создание ключа.\n"
 | 
				
			||||||
 | 
					                    "   - Отправьте чат-роботу.\n\n"
 | 
				
			||||||
 | 
					                    "Важно: сохраните отдельно API Key и Secret Key в надежном месте. Secret ключ отображается только один раз."
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                parse_mode="Markdown",
 | 
				
			||||||
 | 
					                reply_markup=kbi.add_bybit_api,
 | 
				
			||||||
 | 
					                disable_web_page_preview=True,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await rq.create_user(
 | 
				
			||||||
 | 
					                tg_id=callback.from_user.id, username=callback.from_user.username
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            await rq.set_user_symbol(tg_id=callback.from_user.id, symbol="BTCUSDT")
 | 
				
			||||||
 | 
					            await rq.create_user_additional_settings(tg_id=callback.from_user.id)
 | 
				
			||||||
 | 
					            await rq.create_user_risk_management(tg_id=callback.from_user.id)
 | 
				
			||||||
 | 
					            await rq.create_user_conditional_settings(tg_id=callback.from_user.id)
 | 
				
			||||||
 | 
					            await connect_platform(callback=callback, state=state)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error adding bybit API for user %s: %s", callback.from_user.id, e)
 | 
				
			||||||
 | 
					        await callback.message.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка. Пожалуйста, попробуйте позже."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_add_bybit_api.callback_query(F.data == "add_bybit_api")
 | 
				
			||||||
 | 
					async def process_api_key(callback: CallbackQuery, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Starts the FSM flow to add Bybit API keys.
 | 
				
			||||||
 | 
					    Sets the FSM state to prompt user to enter API Key.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param callback: CallbackQuery object.
 | 
				
			||||||
 | 
					    :param state: FSMContext for managing user state.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					        await state.set_state(AddBybitApiState.api_key_state)
 | 
				
			||||||
 | 
					        await callback.answer()
 | 
				
			||||||
 | 
					        await callback.message.answer(text="Введите API Key:")
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error adding bybit API for user %s: %s", callback.from_user.id, e)
 | 
				
			||||||
 | 
					        await callback.message.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка. Пожалуйста, попробуйте позже."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_add_bybit_api.message(AddBybitApiState.api_key_state)
 | 
				
			||||||
 | 
					async def process_secret_key(message: Message, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Receives the API Key input from the user, stores it in FSM context,
 | 
				
			||||||
 | 
					    then sets state to collect Secret Key.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param message: Message object with user's input.
 | 
				
			||||||
 | 
					    :param state: FSMContext for managing user state.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        api_key = message.text
 | 
				
			||||||
 | 
					        await state.update_data(api_key=api_key)
 | 
				
			||||||
 | 
					        await state.set_state(AddBybitApiState.api_secret_state)
 | 
				
			||||||
 | 
					        await message.answer(text="Введите Secret Key:")
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error adding bybit API for user %s: %s", message.from_user.id, e)
 | 
				
			||||||
 | 
					        await message.answer(text="Произошла ошибка. Пожалуйста, попробуйте позже.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_add_bybit_api.message(AddBybitApiState.api_secret_state)
 | 
				
			||||||
 | 
					async def add_bybit_api(message: Message, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Receives the Secret Key input, stores it, saves both API keys in the database,
 | 
				
			||||||
 | 
					    clears FSM state and confirms success to the user.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :param message: Message object with user's input.
 | 
				
			||||||
 | 
					    :param state: FSMContext for managing user state.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        api_secret = message.text
 | 
				
			||||||
 | 
					        api_key = (await state.get_data()).get("api_key")
 | 
				
			||||||
 | 
					        await state.update_data(api_secret=api_secret)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not api_key or not api_secret:
 | 
				
			||||||
 | 
					            await message.answer("Введите корректные данные.")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        result = await rq.set_user_api(
 | 
				
			||||||
 | 
					            tg_id=message.from_user.id, api_key=api_key, api_secret=api_secret
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if result:
 | 
				
			||||||
 | 
					            await message.answer(text="Данные добавлены.", reply_markup=kbr.profile)
 | 
				
			||||||
 | 
					            await user_profile_bybit(
 | 
				
			||||||
 | 
					                tg_id=message.from_user.id, message=message, state=state
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "Bybit API added successfully for user: %s", message.from_user.id
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await message.answer(text="Произошла ошибка. Пожалуйста, попробуйте позже.")
 | 
				
			||||||
 | 
					            logger.error(
 | 
				
			||||||
 | 
					                "Error adding bybit API for user %s: %s", message.from_user.id, result
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error adding bybit API for user %s: %s", message.from_user.id, e)
 | 
				
			||||||
 | 
					        await message.answer(text="Произошла ошибка. Пожалуйста, попробуйте позже.")
 | 
				
			||||||
							
								
								
									
										164
									
								
								app/telegram/handlers/changing_the_symbol.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								app/telegram/handlers/changing_the_symbol.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,164 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aiogram import F, Router
 | 
				
			||||||
 | 
					from aiogram.fsm.context import FSMContext
 | 
				
			||||||
 | 
					from aiogram.types import CallbackQuery, Message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import app.telegram.keyboards.inline as kbi
 | 
				
			||||||
 | 
					import database.request as rq
 | 
				
			||||||
 | 
					from app.bybit.get_functions.get_tickers import get_tickers
 | 
				
			||||||
 | 
					from app.bybit.profile_bybit import user_profile_bybit
 | 
				
			||||||
 | 
					from app.bybit.set_functions.set_leverage import (
 | 
				
			||||||
 | 
					    set_leverage,
 | 
				
			||||||
 | 
					    set_leverage_to_buy_and_sell,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					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.telegram.states.states import ChangingTheSymbolState
 | 
				
			||||||
 | 
					from logger_helper.logger_helper import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("changing_the_symbol")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router_changing_the_symbol = Router(name="changing_the_symbol")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_changing_the_symbol.callback_query(F.data == "change_symbol")
 | 
				
			||||||
 | 
					async def change_symbol(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handler for the "change_symbol" command.
 | 
				
			||||||
 | 
					    Sends a message with available symbols to choose from.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					        await state.set_state(ChangingTheSymbolState.symbol_state)
 | 
				
			||||||
 | 
					        msg = await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					            text="Выберите название инструмента без лишних символов (например: BTCUSDT):",
 | 
				
			||||||
 | 
					            reply_markup=kbi.symbol,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        await state.update_data(prompt_message_id=msg.message_id)
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            "Command change_symbol processed successfully for user: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await callback_query.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка. Пожалуйста, попробуйте позже."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command change_symbol for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_changing_the_symbol.message(ChangingTheSymbolState.symbol_state)
 | 
				
			||||||
 | 
					async def set_symbol(message: Message, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handler for user input for setting the symbol.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Updates FSM context with the selected symbol and persists the choice in database.
 | 
				
			||||||
 | 
					    Sends an acknowledgement to user and clears FSM state afterward.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        message (Message): Incoming message from user containing the selected symbol.
 | 
				
			||||||
 | 
					        state (FSMContext): Finite State Machine context for the current user session.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Logs:
 | 
				
			||||||
 | 
					        Success or error messages with user identification.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            data = await state.get_data()
 | 
				
			||||||
 | 
					            if "prompt_message_id" in data:
 | 
				
			||||||
 | 
					                prompt_message_id = data["prompt_message_id"]
 | 
				
			||||||
 | 
					                await message.bot.delete_message(
 | 
				
			||||||
 | 
					                    chat_id=message.chat.id, message_id=prompt_message_id
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            await message.delete()
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            if "message to delete not found" in str(e).lower():
 | 
				
			||||||
 | 
					                pass  # Ignore this error
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                raise e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        symbol = message.text.upper()
 | 
				
			||||||
 | 
					        additional_settings = await rq.get_user_additional_settings(
 | 
				
			||||||
 | 
					            tg_id=message.from_user.id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not additional_settings:
 | 
				
			||||||
 | 
					            await rq.create_user_additional_settings(tg_id=message.from_user.id)
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        trade_mode = additional_settings.trade_mode or "Merged_Single"
 | 
				
			||||||
 | 
					        mode = 0 if trade_mode == "Merged_Single" else 3
 | 
				
			||||||
 | 
					        margin_type = additional_settings.margin_type or "ISOLATED_MARGIN"
 | 
				
			||||||
 | 
					        leverage = "10"
 | 
				
			||||||
 | 
					        leverage_to_buy = "10"
 | 
				
			||||||
 | 
					        leverage_to_sell = "10"
 | 
				
			||||||
 | 
					        ticker = await get_tickers(tg_id=message.from_user.id, symbol=symbol)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if ticker is None:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                text=f"Инструмент {symbol} не найден.", reply_markup=kbi.symbol
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        req = await rq.set_user_symbol(tg_id=message.from_user.id, symbol=symbol)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not req:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                text="Произошла ошибка при установке инструмента.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.symbol,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await user_profile_bybit(
 | 
				
			||||||
 | 
					            tg_id=message.from_user.id, message=message, state=state
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        res = await set_switch_position_mode(
 | 
				
			||||||
 | 
					            tg_id=message.from_user.id, symbol=symbol, mode=mode
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        if res == "You have an existing position, so position mode cannot be switched":
 | 
				
			||||||
 | 
					            if mode == 0:
 | 
				
			||||||
 | 
					                mode = 3
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                mode = 0
 | 
				
			||||||
 | 
					            await set_switch_position_mode(
 | 
				
			||||||
 | 
					                tg_id=message.from_user.id, symbol=symbol, mode=mode
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            if trade_mode == "Merged_Single":
 | 
				
			||||||
 | 
					                trade_mode = "Both_Sides"
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                trade_mode = "Merged_Single"
 | 
				
			||||||
 | 
					            await rq.set_trade_mode(tg_id=message.from_user.id, trade_mode=trade_mode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await set_margin_mode(tg_id=message.from_user.id, margin_mode=margin_type)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if margin_type == "ISOLATED_MARGIN":
 | 
				
			||||||
 | 
					            await set_leverage_to_buy_and_sell(
 | 
				
			||||||
 | 
					                tg_id=message.from_user.id,
 | 
				
			||||||
 | 
					                symbol=symbol,
 | 
				
			||||||
 | 
					                leverage_to_buy=str(leverage_to_buy),
 | 
				
			||||||
 | 
					                leverage_to_sell=str(leverage_to_sell),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await set_leverage(
 | 
				
			||||||
 | 
					                tg_id=message.from_user.id, symbol=symbol, leverage=str(leverage)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await rq.set_leverage(tg_id=message.from_user.id, leverage=str(leverage))
 | 
				
			||||||
 | 
					        await rq.set_leverage_to_buy_and_sell(
 | 
				
			||||||
 | 
					            tg_id=message.from_user.id,
 | 
				
			||||||
 | 
					            leverage_to_buy=str(leverage_to_buy),
 | 
				
			||||||
 | 
					            leverage_to_sell=str(leverage_to_sell),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        await rq.set_limit_price(tg_id=message.from_user.id, limit_price=0)
 | 
				
			||||||
 | 
					        await rq.set_trigger_price(tg_id=message.from_user.id, trigger_price=0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await message.answer(text="Произошла ошибка. Пожалуйста, попробуйте позже.")
 | 
				
			||||||
 | 
					        logger.error("Error setting symbol for user %s: %s", message.from_user.id, e)
 | 
				
			||||||
							
								
								
									
										109
									
								
								app/telegram/handlers/close_orders.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								app/telegram/handlers/close_orders.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					logger = logging.getLogger("close_orders")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router_close_orders = Router(name="close_orders")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_close_orders.callback_query(
 | 
				
			||||||
 | 
					    lambda c: c.data and c.data.startswith("close_position_")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def close_position_handler(
 | 
				
			||||||
 | 
					    callback_query: CallbackQuery, state: FSMContext
 | 
				
			||||||
 | 
					) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Close a position.
 | 
				
			||||||
 | 
					    :param callback_query: Incoming callback query from Telegram inline keyboard.
 | 
				
			||||||
 | 
					    :param state: Finite State Machine context for the current user session.
 | 
				
			||||||
 | 
					    :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,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await callback_query.answer(text="Произошла ошибка при закрытии позиции.")
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command close_position for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    finally:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_close_orders.callback_query(
 | 
				
			||||||
 | 
					    lambda c: c.data and c.data.startswith("close_order_")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def cancel_order_handler(
 | 
				
			||||||
 | 
					    callback_query: CallbackQuery, state: FSMContext
 | 
				
			||||||
 | 
					) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Cancel an order.
 | 
				
			||||||
 | 
					    :param callback_query: Incoming callback query from Telegram inline keyboard.
 | 
				
			||||||
 | 
					    :param state: Finite State Machine context for the current user session.
 | 
				
			||||||
 | 
					    :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,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await callback_query.answer(text="Произошла ошибка при закрытии ордера.")
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command close_order for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    finally:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
							
								
								
									
										50
									
								
								app/telegram/handlers/common.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								app/telegram/handlers/common.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aiogram import Router
 | 
				
			||||||
 | 
					from aiogram.fsm.context import FSMContext
 | 
				
			||||||
 | 
					from aiogram.types import Message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from logger_helper.logger_helper import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("common")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router_common = Router(name="common")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_common.message()
 | 
				
			||||||
 | 
					async def unknown_message(message: Message, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handle unexpected or unrecognized messages.
 | 
				
			||||||
 | 
					    Clears FSM state and informs the user about available commands.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        message (types.Message): Incoming message object.
 | 
				
			||||||
 | 
					        state (FSMContext): Current FSM context.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Returns:
 | 
				
			||||||
 | 
					        None
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await message.answer(
 | 
				
			||||||
 | 
					            text="Извините, я вас не понял. "
 | 
				
			||||||
 | 
					            "Пожалуйста, используйте одну из следующих команд:\n"
 | 
				
			||||||
 | 
					            "/start - Запустить бота\n"
 | 
				
			||||||
 | 
					            "/profile - Профиль\n"
 | 
				
			||||||
 | 
					            "/bybit - Панель Bybit\n"
 | 
				
			||||||
 | 
					            "/help - Получить помощь\n"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            "Received unknown message from user %s: %s",
 | 
				
			||||||
 | 
					            message.from_user.id,
 | 
				
			||||||
 | 
					            message.text,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error handling unknown message for user %s: %s", message.from_user.id, e
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        await message.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка при обработке вашего сообщения. Пожалуйста, попробуйте позже."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    finally:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
							
								
								
									
										314
									
								
								app/telegram/handlers/get_positions_handlers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										314
									
								
								app/telegram/handlers/get_positions_handlers.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,314 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aiogram import F, Router
 | 
				
			||||||
 | 
					from aiogram.fsm.context import FSMContext
 | 
				
			||||||
 | 
					from aiogram.types import CallbackQuery
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import app.telegram.keyboards.inline as kbi
 | 
				
			||||||
 | 
					from app.bybit.get_functions.get_positions import (
 | 
				
			||||||
 | 
					    get_active_orders,
 | 
				
			||||||
 | 
					    get_active_orders_by_symbol,
 | 
				
			||||||
 | 
					    get_active_positions,
 | 
				
			||||||
 | 
					    get_active_positions_by_symbol,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from logger_helper.logger_helper import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("get_positions_handlers")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router_get_positions_handlers = Router(name="get_positions_handlers")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_get_positions_handlers.callback_query(F.data == "my_deals")
 | 
				
			||||||
 | 
					async def get_positions_handlers(
 | 
				
			||||||
 | 
					    callback_query: CallbackQuery, state: FSMContext
 | 
				
			||||||
 | 
					) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Gets the user's active positions.
 | 
				
			||||||
 | 
					    :param callback_query: CallbackQuery object.
 | 
				
			||||||
 | 
					    :param state: FSMContext
 | 
				
			||||||
 | 
					    :return: None
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					        await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					            text="Выберите какие сделки вы хотите посмотреть:",
 | 
				
			||||||
 | 
					            reply_markup=kbi.change_position,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error in get_positions_handler: %s", e)
 | 
				
			||||||
 | 
					        await callback_query.answer(text="Произошла ошибка при получении сделок.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_get_positions_handlers.callback_query(F.data == "change_position")
 | 
				
			||||||
 | 
					async def get_positions_handler(
 | 
				
			||||||
 | 
					    callback_query: CallbackQuery, state: FSMContext
 | 
				
			||||||
 | 
					) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Gets the user's active positions.
 | 
				
			||||||
 | 
					    :param callback_query: CallbackQuery object.
 | 
				
			||||||
 | 
					    :param state: FSMContext
 | 
				
			||||||
 | 
					    :return: None
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        res = await get_active_positions(tg_id=callback_query.from_user.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if res is None:
 | 
				
			||||||
 | 
					            await callback_query.answer(
 | 
				
			||||||
 | 
					                text="Произошла ошибка при получении активных позиций."
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if res == ["No active positions found"]:
 | 
				
			||||||
 | 
					            await callback_query.answer(text="Нет активных позиций.")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        active_positions = [pos for pos in res if float(pos.get("size", 0)) > 0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not active_positions:
 | 
				
			||||||
 | 
					            await callback_query.answer(text="Нет активных позиций.")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        active_symbols_sides = [
 | 
				
			||||||
 | 
					            (pos.get("symbol"), pos.get("side")) for pos in active_positions
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					            text="Ваши активные позиции:",
 | 
				
			||||||
 | 
					            reply_markup=kbi.create_active_positions_keyboard(
 | 
				
			||||||
 | 
					                symbols=active_symbols_sides
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error in get_positions_handler: %s", e)
 | 
				
			||||||
 | 
					        await callback_query.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка при получении активных позиций."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    finally:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_get_positions_handlers.callback_query(
 | 
				
			||||||
 | 
					    lambda c: c.data.startswith("get_position_")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def get_position_handler(callback_query: CallbackQuery, state: FSMContext):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        data = callback_query.data
 | 
				
			||||||
 | 
					        parts = data.split("_")
 | 
				
			||||||
 | 
					        symbol = parts[2]
 | 
				
			||||||
 | 
					        get_side = parts[3]
 | 
				
			||||||
 | 
					        res = await get_active_positions_by_symbol(
 | 
				
			||||||
 | 
					            tg_id=callback_query.from_user.id, symbol=symbol
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if res is None:
 | 
				
			||||||
 | 
					            await callback_query.answer(
 | 
				
			||||||
 | 
					                text="Произошла ошибка при получении активных позиций."
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        position = next((pos for pos in res if pos.get("side") == get_side), None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if position:
 | 
				
			||||||
 | 
					            side = position.get("side")
 | 
				
			||||||
 | 
					            symbol = position.get("symbol") or "Нет данных"
 | 
				
			||||||
 | 
					            avg_price = position.get("avgPrice") or "Нет данных"
 | 
				
			||||||
 | 
					            size = position.get("size") or "Нет данных"
 | 
				
			||||||
 | 
					            take_profit = position.get("takeProfit") or "Нет данных"
 | 
				
			||||||
 | 
					            stop_loss = position.get("stopLoss") or "Нет данных"
 | 
				
			||||||
 | 
					            position_idx = position.get("positionIdx") or "Нет данных"
 | 
				
			||||||
 | 
					            liq_price = position.get("liqPrice") or "Нет данных"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            side = "Нет данных"
 | 
				
			||||||
 | 
					            symbol = "Нет данных"
 | 
				
			||||||
 | 
					            avg_price = "Нет данных"
 | 
				
			||||||
 | 
					            size = "Нет данных"
 | 
				
			||||||
 | 
					            take_profit = "Нет данных"
 | 
				
			||||||
 | 
					            stop_loss = "Нет данных"
 | 
				
			||||||
 | 
					            position_idx = "Нет данных"
 | 
				
			||||||
 | 
					            liq_price = "Нет данных"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        side_rus = (
 | 
				
			||||||
 | 
					            "Покупка"
 | 
				
			||||||
 | 
					            if side == "Buy"
 | 
				
			||||||
 | 
					            else "Продажа" if side == "Sell" else "Нет данных"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        position_idx_rus = (
 | 
				
			||||||
 | 
					            "Односторонний"
 | 
				
			||||||
 | 
					            if position_idx == 0
 | 
				
			||||||
 | 
					            else (
 | 
				
			||||||
 | 
					                "Покупка в режиме хеджирования"
 | 
				
			||||||
 | 
					                if position_idx == 1
 | 
				
			||||||
 | 
					                else (
 | 
				
			||||||
 | 
					                    "Продажа в режиме хеджирования"
 | 
				
			||||||
 | 
					                    if position_idx == 2
 | 
				
			||||||
 | 
					                    else "Нет данных"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        text_lines = [
 | 
				
			||||||
 | 
					            f"Торговая пара: {symbol}",
 | 
				
			||||||
 | 
					            f"Режим позиции: {position_idx_rus}",
 | 
				
			||||||
 | 
					            f"Цена входа: {avg_price}",
 | 
				
			||||||
 | 
					            f"Количество: {size}",
 | 
				
			||||||
 | 
					            f"Движение: {side_rus}",
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if take_profit and take_profit != "Нет данных":
 | 
				
			||||||
 | 
					            text_lines.append(f"Тейк-профит: {take_profit}")
 | 
				
			||||||
 | 
					        if stop_loss and stop_loss != "Нет данных":
 | 
				
			||||||
 | 
					            text_lines.append(f"Стоп-лосс: {stop_loss}")
 | 
				
			||||||
 | 
					        if liq_price and liq_price != "Нет данных":
 | 
				
			||||||
 | 
					            text_lines.append(f"Цена ликвидации: {liq_price}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        text = "\n".join(text_lines)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					            text=text,
 | 
				
			||||||
 | 
					            reply_markup=kbi.make_close_position_keyboard(
 | 
				
			||||||
 | 
					                symbol_pos=symbol, side=side, position_idx=position_idx, qty=size
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error in get_position_handler: %s", e)
 | 
				
			||||||
 | 
					        await callback_query.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка при получении активных позиций."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    finally:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_get_positions_handlers.callback_query(F.data == "open_orders")
 | 
				
			||||||
 | 
					async def get_open_orders_handler(
 | 
				
			||||||
 | 
					    callback_query: CallbackQuery, state: FSMContext
 | 
				
			||||||
 | 
					) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Gets the user's open orders.
 | 
				
			||||||
 | 
					    :param callback_query: CallbackQuery object.
 | 
				
			||||||
 | 
					    :param state: FSMContext
 | 
				
			||||||
 | 
					    :return: None
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        res = await get_active_orders(tg_id=callback_query.from_user.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if res is None:
 | 
				
			||||||
 | 
					            await callback_query.answer(
 | 
				
			||||||
 | 
					                text="Произошла ошибка при получении активных ордеров."
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if res == ["No active orders found"]:
 | 
				
			||||||
 | 
					            await callback_query.answer(text="Нет активных ордеров.")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        active_positions = [pos for pos in res if pos.get("orderStatus", 0) == "New"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not active_positions:
 | 
				
			||||||
 | 
					            await callback_query.answer(text="Нет активных ордеров.")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        active_orders_sides = [
 | 
				
			||||||
 | 
					            (pos.get("symbol"), pos.get("side")) for pos in active_positions
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					            text="Ваши активные ордера:",
 | 
				
			||||||
 | 
					            reply_markup=kbi.create_active_orders_keyboard(orders=active_orders_sides),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error in get_open_orders_handler: %s", e)
 | 
				
			||||||
 | 
					        await callback_query.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка при получении активных ордеров."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    finally:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_get_positions_handlers.callback_query(lambda c: c.data.startswith("get_order_"))
 | 
				
			||||||
 | 
					async def get_order_handler(callback_query: CallbackQuery, state: FSMContext):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        data = callback_query.data
 | 
				
			||||||
 | 
					        parts = data.split("_")
 | 
				
			||||||
 | 
					        symbol = parts[2]
 | 
				
			||||||
 | 
					        get_side = parts[3]
 | 
				
			||||||
 | 
					        res = await get_active_orders_by_symbol(
 | 
				
			||||||
 | 
					            tg_id=callback_query.from_user.id, symbol=symbol
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if res is None:
 | 
				
			||||||
 | 
					            await callback_query.answer(
 | 
				
			||||||
 | 
					                text="Произошла ошибка при получении активных ордеров."
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        orders = next((pos for pos in res if pos.get("side") == get_side), None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if orders:
 | 
				
			||||||
 | 
					            side = orders.get("side")
 | 
				
			||||||
 | 
					            symbol = orders.get("symbol")
 | 
				
			||||||
 | 
					            price = orders.get("price")
 | 
				
			||||||
 | 
					            qty = orders.get("qty")
 | 
				
			||||||
 | 
					            order_type = orders.get("orderType")
 | 
				
			||||||
 | 
					            trigger_price = orders.get("triggerPrice")
 | 
				
			||||||
 | 
					            take_profit = orders.get("takeProfit")
 | 
				
			||||||
 | 
					            stop_loss = orders.get("stopLoss")
 | 
				
			||||||
 | 
					            order_id = orders.get("orderId")
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            side = "Нет данных"
 | 
				
			||||||
 | 
					            symbol = "Нет данных"
 | 
				
			||||||
 | 
					            price = "Нет данных"
 | 
				
			||||||
 | 
					            qty = "Нет данных"
 | 
				
			||||||
 | 
					            order_type = "Нет данных"
 | 
				
			||||||
 | 
					            trigger_price = "Нет данных"
 | 
				
			||||||
 | 
					            take_profit = "Нет данных"
 | 
				
			||||||
 | 
					            stop_loss = "Нет данных"
 | 
				
			||||||
 | 
					            order_id = "Нет данных"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        side_rus = (
 | 
				
			||||||
 | 
					            "Покупка"
 | 
				
			||||||
 | 
					            if side == "Buy"
 | 
				
			||||||
 | 
					            else "Продажа" if side == "Sell" else "Нет данных"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        order_type_rus = (
 | 
				
			||||||
 | 
					            "Рыночный"
 | 
				
			||||||
 | 
					            if order_type == "Market"
 | 
				
			||||||
 | 
					            else "Лимитный" if order_type == "Limit" else "Нет данных"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        text_lines = [
 | 
				
			||||||
 | 
					            f"Торговая пара: {symbol}",
 | 
				
			||||||
 | 
					            f"Количество: {qty}",
 | 
				
			||||||
 | 
					            f"Движение: {side_rus}",
 | 
				
			||||||
 | 
					            f"Тип ордера: {order_type_rus}",
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        if price:
 | 
				
			||||||
 | 
					            text_lines.append(f"Цена: {price}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if trigger_price and trigger_price != "Нет данных":
 | 
				
			||||||
 | 
					            text_lines.append(f"Триггер цена: {trigger_price}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if take_profit and take_profit != "Нет данных":
 | 
				
			||||||
 | 
					            text_lines.append(f"Тейк-профит: {take_profit}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if stop_loss and stop_loss != "Нет данных":
 | 
				
			||||||
 | 
					            text_lines.append(f"Стоп-лосс: {stop_loss}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        text = "\n".join(text_lines)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					            text=text,
 | 
				
			||||||
 | 
					            reply_markup=kbi.make_close_orders_keyboard(
 | 
				
			||||||
 | 
					                symbol_order=symbol, order_id=order_id
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error in get_order_handler: %s", e)
 | 
				
			||||||
 | 
					        await callback_query.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка при получении активных ордеров."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    finally:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
@@ -1,316 +0,0 @@
 | 
				
			|||||||
import logging.config
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from aiogram import F, Router
 | 
					 | 
				
			||||||
from aiogram.filters import CommandStart, Command
 | 
					 | 
				
			||||||
from aiogram.types import Message, CallbackQuery
 | 
					 | 
				
			||||||
from aiogram.fsm.context import FSMContext
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import app.telegram.functions.functions as func
 | 
					 | 
				
			||||||
import app.telegram.functions.main_settings.settings as func_main_settings
 | 
					 | 
				
			||||||
import app.telegram.functions.risk_management_settings.settings as func_rmanagement_settings
 | 
					 | 
				
			||||||
import app.telegram.functions.condition_settings.settings as func_condition_settings
 | 
					 | 
				
			||||||
import app.telegram.functions.additional_settings.settings as func_additional_settings
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import app.telegram.database.requests as rq
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from app.services.Bybit.functions.balance import get_balance
 | 
					 | 
				
			||||||
from app.services.Bybit.functions.bybit_ws import run_ws_for_user
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
from logger_helper.logger_helper import LOGGING_CONFIG
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
logging.config.dictConfig(LOGGING_CONFIG)
 | 
					 | 
				
			||||||
logger = logging.getLogger("handlers")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
router = Router()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router.message(Command("start"))
 | 
					 | 
				
			||||||
@router.message(CommandStart())
 | 
					 | 
				
			||||||
async def start_message(message: Message) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Обработчик команды /start.
 | 
					 | 
				
			||||||
    Инициализирует нового пользователя в БД.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        message (Message): Входящее сообщение с командой /start.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await rq.set_new_user_bybit_api(message.from_user.id)
 | 
					 | 
				
			||||||
    await func.start_message(message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router.message(Command("profile"))
 | 
					 | 
				
			||||||
@router.message(F.text == "👤 Профиль")
 | 
					 | 
				
			||||||
async def profile_message(message: Message) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Обработчик кнопки 'Профиль'.
 | 
					 | 
				
			||||||
    Проверяет существование пользователя и отображает профиль.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        message (Message): Сообщение с текстом кнопки.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    user = await rq.check_user(message.from_user.id)
 | 
					 | 
				
			||||||
    tg_id = message.from_user.id
 | 
					 | 
				
			||||||
    balance = await get_balance(message.from_user.id, message)
 | 
					 | 
				
			||||||
    if user and balance:
 | 
					 | 
				
			||||||
        await run_ws_for_user(tg_id, message)
 | 
					 | 
				
			||||||
        await func.profile_message(message.from_user.username, message)
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        await rq.save_tg_id_new_user(message.from_user.id)
 | 
					 | 
				
			||||||
        await func_main_settings.reg_new_user_default_main_settings(message.from_user.id, message)
 | 
					 | 
				
			||||||
        await func_rmanagement_settings.reg_new_user_default_risk_management_settings(message.from_user.id, message)
 | 
					 | 
				
			||||||
        await func_condition_settings.reg_new_user_default_condition_settings(message.from_user.id)
 | 
					 | 
				
			||||||
        await func_additional_settings.reg_new_user_default_additional_settings(message.from_user.id, message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router.callback_query(F.data == "clb_start_chatbot_message")
 | 
					 | 
				
			||||||
async def clb_profile_msg(callback: CallbackQuery) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Обработчик колбэка 'clb_start_chatbot_message'.
 | 
					 | 
				
			||||||
    Если пользователь есть в БД — показывает профиль,
 | 
					 | 
				
			||||||
    иначе регистрирует нового пользователя и инициализирует настройки.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        callback (CallbackQuery): Полученный колбэк.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    tg_id = callback.from_user.id
 | 
					 | 
				
			||||||
    message = callback.message
 | 
					 | 
				
			||||||
    user = await rq.check_user(callback.from_user.id)
 | 
					 | 
				
			||||||
    balance = await get_balance(callback.from_user.id, callback.message)
 | 
					 | 
				
			||||||
    first_name = callback.from_user.first_name or ""
 | 
					 | 
				
			||||||
    last_name = callback.from_user.last_name or ""
 | 
					 | 
				
			||||||
    username = f"{first_name} {last_name}".strip() or callback.from_user.username or "Пользователь"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if user and balance:
 | 
					 | 
				
			||||||
        await run_ws_for_user(tg_id, message)
 | 
					 | 
				
			||||||
        await func.profile_message(callback.from_user.username, callback.message)
 | 
					 | 
				
			||||||
    else:
 | 
					 | 
				
			||||||
        await rq.save_tg_id_new_user(callback.from_user.id)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        await func_main_settings.reg_new_user_default_main_settings(callback.from_user.id, callback.message)
 | 
					 | 
				
			||||||
        await func_rmanagement_settings.reg_new_user_default_risk_management_settings(callback.from_user.id,
 | 
					 | 
				
			||||||
                                                                                      callback.message)
 | 
					 | 
				
			||||||
        await func_condition_settings.reg_new_user_default_condition_settings(callback.from_user.id)
 | 
					 | 
				
			||||||
        await func_additional_settings.reg_new_user_default_additional_settings(callback.from_user.id, callback.message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router.callback_query(F.data == "clb_settings_message")
 | 
					 | 
				
			||||||
async def clb_settings_msg(callback: CallbackQuery) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Показать главное меню настроек.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        callback (CallbackQuery): полученный колбэк.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await func.settings_message(callback.message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router.callback_query(F.data == "clb_back_to_special_settings_message")
 | 
					 | 
				
			||||||
async def clb_back_to_settings_msg(callback: CallbackQuery) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Вернуть пользователя к меню специальных настроек.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        callback (CallbackQuery): полученный колбэк.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await func.settings_message(callback.message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router.callback_query(F.data == "clb_change_main_settings")
 | 
					 | 
				
			||||||
async def clb_change_main_settings_message(callback: CallbackQuery) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Открыть меню изменения главных настроек.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        callback (CallbackQuery): полученный колбэк.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await func_main_settings.main_settings_message(callback.from_user.id, callback.message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router.callback_query(F.data == "clb_change_risk_management_settings")
 | 
					 | 
				
			||||||
async def clb_change_risk_management_message(callback: CallbackQuery) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Открыть меню изменения настроек управления рисками.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        callback (CallbackQuery): полученный колбэк.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await func_rmanagement_settings.main_settings_message(callback.from_user.id, callback.message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router.callback_query(F.data == "clb_change_condition_settings")
 | 
					 | 
				
			||||||
async def clb_change_condition_message(callback: CallbackQuery) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Открыть меню изменения настроек условий.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        callback (CallbackQuery): полученный колбэк.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await func_condition_settings.main_settings_message(callback.from_user.id, callback.message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router.callback_query(F.data == "clb_change_additional_settings")
 | 
					 | 
				
			||||||
async def clb_change_additional_message(callback: CallbackQuery) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Открыть меню изменения дополнительных настроек.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        callback (CallbackQuery): полученный колбэк.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await func_additional_settings.main_settings_message(callback.from_user.id, callback.message)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Конкретные настройки каталогов
 | 
					 | 
				
			||||||
list_main_settings = ['clb_change_trading_mode',
 | 
					 | 
				
			||||||
                      'clb_change_switch_state',
 | 
					 | 
				
			||||||
                      'clb_change_margin_type',
 | 
					 | 
				
			||||||
                      'clb_change_size_leverage',
 | 
					 | 
				
			||||||
                      'clb_change_starting_quantity',
 | 
					 | 
				
			||||||
                      'clb_change_martingale_factor',
 | 
					 | 
				
			||||||
                      'clb_change_maximum_quantity'
 | 
					 | 
				
			||||||
                      ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router.callback_query(F.data.in_(list_main_settings))
 | 
					 | 
				
			||||||
async def clb_main_settings_msg(callback: CallbackQuery, state: FSMContext) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Обработчик колбэков изменения главных настроек с dispatch через match-case.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        callback (CallbackQuery): полученный колбэк.
 | 
					 | 
				
			||||||
        state (FSMContext): текущее состояние FSM.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        match callback.data:
 | 
					 | 
				
			||||||
            case 'clb_change_trading_mode':
 | 
					 | 
				
			||||||
                await func_main_settings.trading_mode_message(callback.message, state)
 | 
					 | 
				
			||||||
            case 'clb_change_switch_state':
 | 
					 | 
				
			||||||
                await func_main_settings.switch_mode_enabled_message(callback.message, state)
 | 
					 | 
				
			||||||
            case 'clb_change_margin_type':
 | 
					 | 
				
			||||||
                await func_main_settings.margin_type_message(callback.message, state)
 | 
					 | 
				
			||||||
            case 'clb_change_size_leverage':
 | 
					 | 
				
			||||||
                await func_main_settings.size_leverage_message(callback.message, state)
 | 
					 | 
				
			||||||
            case 'clb_change_starting_quantity':
 | 
					 | 
				
			||||||
                await func_main_settings.starting_quantity_message(callback.message, state)
 | 
					 | 
				
			||||||
            case 'clb_change_martingale_factor':
 | 
					 | 
				
			||||||
                await func_main_settings.martingale_factor_message(callback.message, state)
 | 
					 | 
				
			||||||
            case 'clb_change_maximum_quantity':
 | 
					 | 
				
			||||||
                await func_main_settings.maximum_quantity_message(callback.message, state)
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        logger.error("Error callback in main_settings match-case: %s", e)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
list_risk_management_settings = ['clb_change_price_profit',
 | 
					 | 
				
			||||||
                                 'clb_change_price_loss',
 | 
					 | 
				
			||||||
                                 'clb_change_max_risk_deal',
 | 
					 | 
				
			||||||
                                 'commission_fee',
 | 
					 | 
				
			||||||
                                 ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router.callback_query(F.data.in_(list_risk_management_settings))
 | 
					 | 
				
			||||||
async def clb_risk_management_settings_msg(callback: CallbackQuery, state: FSMContext) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Обработчик изменений настроек управления рисками.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        callback (CallbackQuery): полученный колбэк.
 | 
					 | 
				
			||||||
        state (FSMContext): текущее состояние FSM.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        match callback.data:
 | 
					 | 
				
			||||||
            case 'clb_change_price_profit':
 | 
					 | 
				
			||||||
                await func_rmanagement_settings.price_profit_message(callback.message, state)
 | 
					 | 
				
			||||||
            case 'clb_change_price_loss':
 | 
					 | 
				
			||||||
                await func_rmanagement_settings.price_loss_message(callback.message, state)
 | 
					 | 
				
			||||||
            case 'clb_change_max_risk_deal':
 | 
					 | 
				
			||||||
                await func_rmanagement_settings.max_risk_deal_message(callback.message, state)
 | 
					 | 
				
			||||||
            case 'commission_fee':
 | 
					 | 
				
			||||||
                await func_rmanagement_settings.commission_fee_message(callback.message, state)
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        logger.error("Error callback in risk_management match-case: %s", e)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
list_condition_settings = ['clb_change_mode',
 | 
					 | 
				
			||||||
                           'clb_change_timer',
 | 
					 | 
				
			||||||
                           'clb_change_filter_volatility',
 | 
					 | 
				
			||||||
                           'clb_change_external_cues',
 | 
					 | 
				
			||||||
                           'clb_change_tradingview_cues',
 | 
					 | 
				
			||||||
                           'clb_change_webhook',
 | 
					 | 
				
			||||||
                           'clb_change_ai_analytics'
 | 
					 | 
				
			||||||
                           ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router.callback_query(F.data.in_(list_condition_settings))
 | 
					 | 
				
			||||||
async def clb_condition_settings_msg(callback: CallbackQuery, state: FSMContext) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Обработчик изменений настроек условий трейдинга.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        callback (CallbackQuery): полученный колбэк.
 | 
					 | 
				
			||||||
        state (FSMContext): текущее состояние FSM.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        match callback.data:
 | 
					 | 
				
			||||||
            case 'clb_change_mode':
 | 
					 | 
				
			||||||
                await func_condition_settings.trigger_message(callback.from_user.id, callback.message, state)
 | 
					 | 
				
			||||||
            case 'clb_change_timer':
 | 
					 | 
				
			||||||
                await func_condition_settings.timer_message(callback.from_user.id, callback.message, state)
 | 
					 | 
				
			||||||
            case 'clb_change_filter_volatility':
 | 
					 | 
				
			||||||
                await func_condition_settings.filter_volatility_message(callback.message, state)
 | 
					 | 
				
			||||||
            case 'clb_change_external_cues':
 | 
					 | 
				
			||||||
                await func_condition_settings.external_cues_message(callback.message, state)
 | 
					 | 
				
			||||||
            case 'clb_change_tradingview_cues':
 | 
					 | 
				
			||||||
                await func_condition_settings.trading_cues_message(callback.message, state)
 | 
					 | 
				
			||||||
            case 'clb_change_webhook':
 | 
					 | 
				
			||||||
                await func_condition_settings.webhook_message(callback.message, state)
 | 
					 | 
				
			||||||
            case 'clb_change_ai_analytics':
 | 
					 | 
				
			||||||
                await func_condition_settings.ai_analytics_message(callback.message, state)
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        logger.error("Error callback in main_settings match-case: %s", e)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
list_additional_settings = ['clb_change_save_pattern',
 | 
					 | 
				
			||||||
                            'clb_change_auto_start',
 | 
					 | 
				
			||||||
                            'clb_change_notifications',
 | 
					 | 
				
			||||||
                            ]
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@router.callback_query(F.data.in_(list_additional_settings))
 | 
					 | 
				
			||||||
async def clb_additional_settings_msg(callback: CallbackQuery, state: FSMContext) -> None:
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    Обработчик дополнительных настроек бота.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Args:
 | 
					 | 
				
			||||||
        callback (CallbackQuery): полученный колбэк.
 | 
					 | 
				
			||||||
        state (FSMContext): текущее состояние FSM.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    await callback.answer()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        match callback.data:
 | 
					 | 
				
			||||||
            case 'clb_change_save_pattern':
 | 
					 | 
				
			||||||
                await func_additional_settings.save_pattern_message(callback.message, state)
 | 
					 | 
				
			||||||
            case 'clb_change_auto_start':
 | 
					 | 
				
			||||||
                await func_additional_settings.auto_start_message(callback.message, state)
 | 
					 | 
				
			||||||
            case 'clb_change_notifications':
 | 
					 | 
				
			||||||
                await func_additional_settings.notifications_message(callback.message, state)
 | 
					 | 
				
			||||||
    except Exception as e:
 | 
					 | 
				
			||||||
        logger.error("Error callback in additional_settings match-case: %s", e)
 | 
					 | 
				
			||||||
							
								
								
									
										381
									
								
								app/telegram/handlers/handlers_main.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										381
									
								
								app/telegram/handlers/handlers_main.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,381 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aiogram import F, Router
 | 
				
			||||||
 | 
					from aiogram.filters import Command
 | 
				
			||||||
 | 
					from aiogram.fsm.context import FSMContext
 | 
				
			||||||
 | 
					from aiogram.types import CallbackQuery, Message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import app.telegram.keyboards.inline as kbi
 | 
				
			||||||
 | 
					import app.telegram.keyboards.reply as kbr
 | 
				
			||||||
 | 
					import database.request as rq
 | 
				
			||||||
 | 
					from app.bybit.profile_bybit import user_profile_bybit
 | 
				
			||||||
 | 
					from app.telegram.functions.profile_tg import user_profile_tg
 | 
				
			||||||
 | 
					from logger_helper.logger_helper import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("handlers_main")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router_handlers_main = Router(name="handlers_main")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_handlers_main.message(Command("start", "hello"))
 | 
				
			||||||
 | 
					@router_handlers_main.message(F.text.lower() == "привет")
 | 
				
			||||||
 | 
					async def cmd_start(message: Message, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handle the /start or /hello commands and the text message "привет".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Checks if the user exists in the database and sends a user profile or creates a new user
 | 
				
			||||||
 | 
					    with default settings and greeting message.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        message (Message): Incoming Telegram message object.
 | 
				
			||||||
 | 
					        state (FSMContext): FSMContext for managing user state.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Raises:
 | 
				
			||||||
 | 
					        None: Exceptions are caught and logged internally.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    tg_id = message.from_user.id
 | 
				
			||||||
 | 
					    username = message.from_user.username
 | 
				
			||||||
 | 
					    full_name = message.from_user.full_name
 | 
				
			||||||
 | 
					    user = await rq.get_user(tg_id)
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        if user:
 | 
				
			||||||
 | 
					            await user_profile_tg(tg_id=message.from_user.id, message=message)
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "Command start processed successfully for user: %s",
 | 
				
			||||||
 | 
					                message.from_user.id,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await rq.create_user(tg_id=tg_id, username=username)
 | 
				
			||||||
 | 
					            await rq.set_user_symbol(tg_id=tg_id, symbol="BTCUSDT")
 | 
				
			||||||
 | 
					            await rq.create_user_additional_settings(tg_id=tg_id)
 | 
				
			||||||
 | 
					            await rq.create_user_risk_management(tg_id=tg_id)
 | 
				
			||||||
 | 
					            await rq.create_user_conditional_settings(tg_id=tg_id)
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                text=f"Добро пожаловать, {full_name}!\n\n"
 | 
				
			||||||
 | 
					                     "PHANTOM TRADING - ваш надежный помощник для автоматизации трейдинга😉",
 | 
				
			||||||
 | 
					                reply_markup=kbi.connect_the_platform,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "Command start processed successfully for user: %s",
 | 
				
			||||||
 | 
					                message.from_user.id,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command start for user %s: %s", message.from_user.id, e
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        await message.answer(text="Произошла ошибка. Пожалуйста, попробуйте позже.")
 | 
				
			||||||
 | 
					    finally:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_handlers_main.message(Command("profile"))
 | 
				
			||||||
 | 
					@router_handlers_main.message(F.text == "Профиль")
 | 
				
			||||||
 | 
					async def cmd_to_main(message: Message, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handle the /profile command or text "Профиль".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Clears the current FSM state and sends the Telegram user profile.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        message (Message): Incoming Telegram message object.
 | 
				
			||||||
 | 
					        state (FSMContext): FSM state context.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Raises:
 | 
				
			||||||
 | 
					        None: Exceptions are caught and logged internally.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await user_profile_tg(tg_id=message.from_user.id, message=message)
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            "Command to_profile_tg processed successfully for user: %s",
 | 
				
			||||||
 | 
					            message.from_user.id,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command to_profile_tg for user %s: %s",
 | 
				
			||||||
 | 
					            message.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    finally:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_handlers_main.message(Command("bybit"))
 | 
				
			||||||
 | 
					@router_handlers_main.message(F.text == "Панель Bybit")
 | 
				
			||||||
 | 
					async def profile_bybit(message: Message, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handle the /bybit command or text "Панель Bybit".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Clears FSM state and sends Bybit trading panel profile.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        message (Message): Incoming Telegram message object.
 | 
				
			||||||
 | 
					        state (FSMContext): FSM state context.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Raises:
 | 
				
			||||||
 | 
					        None: Exceptions are caught and logged internally.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					        await user_profile_bybit(
 | 
				
			||||||
 | 
					            tg_id=message.from_user.id, message=message, state=state
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            "Command to_profile_bybit processed successfully for user: %s",
 | 
				
			||||||
 | 
					            message.from_user.id,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command to_profile_bybit for user %s: %s",
 | 
				
			||||||
 | 
					            message.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_handlers_main.callback_query(F.data == "profile_bybit")
 | 
				
			||||||
 | 
					async def profile_bybit_callback(
 | 
				
			||||||
 | 
					        callback_query: CallbackQuery, state: FSMContext
 | 
				
			||||||
 | 
					) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handle callback query with data "profile_bybit".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Clears FSM state and sends the Bybit profile in response.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        callback_query (CallbackQuery): Callback query object from Telegram.
 | 
				
			||||||
 | 
					        state (FSMContext): FSM state context.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Raises:
 | 
				
			||||||
 | 
					        None: Exceptions are caught and logged internally.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					        await user_profile_bybit(
 | 
				
			||||||
 | 
					            tg_id=callback_query.from_user.id,
 | 
				
			||||||
 | 
					            message=callback_query.message,
 | 
				
			||||||
 | 
					            state=state,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            "Callback profile_bybit processed successfully for user: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        await callback_query.answer()
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing callback profile_bybit for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_handlers_main.callback_query(F.data == "main_settings")
 | 
				
			||||||
 | 
					async def settings(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handle callback query with data "main_settings".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Clears FSM state and edits the message to show main settings options.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        callback_query (CallbackQuery): Callback query object.
 | 
				
			||||||
 | 
					        state (FSMContext): FSM state context.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Raises:
 | 
				
			||||||
 | 
					        None: Exceptions are caught and logged internally.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					        msg = await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					            text="Выберите, что вы хотите настроить:", reply_markup=kbi.main_settings
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        await state.update_data(prompt_message_id=msg.message_id)
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            "Command settings processed successfully for user: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command settings for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_handlers_main.message(Command("connect"))
 | 
				
			||||||
 | 
					@router_handlers_main.message(F.text == "Подключить платформу Bybit")
 | 
				
			||||||
 | 
					async def cmd_connect(message: Message, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handle the /connect command or text "Подключить платформу Bybit".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Clears FSM state and sends a connection message.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        message (Message): Incoming Telegram message object.
 | 
				
			||||||
 | 
					        state (FSMContext): FSM state context.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Raises:
 | 
				
			||||||
 | 
					        None: Exceptions are caught and logged internally.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					        user = await rq.get_user(tg_id=message.from_user.id)
 | 
				
			||||||
 | 
					        if user:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                text=(
 | 
				
			||||||
 | 
					                    "Подключение Bybit аккаунта \n\n"
 | 
				
			||||||
 | 
					                    "1. Зарегистрируйтесь или войдите в свой аккаунт на Bybit по ссылке: "
 | 
				
			||||||
 | 
					                    "[Перейти на Bybit](https://www.bybit.com/invite?ref=YME83OJ).\n"
 | 
				
			||||||
 | 
					                    "2. В личном кабинете выберите раздел API. \n"
 | 
				
			||||||
 | 
					                    "3. Создание нового API ключа\n"
 | 
				
			||||||
 | 
					                    "   - Нажмите кнопку Create New Key (Создать новый ключ).\n"
 | 
				
			||||||
 | 
					                    "   - Выберите системно-сгенерированный ключ.\n"
 | 
				
			||||||
 | 
					                    "   - Укажите название API ключа (любое).  \n"
 | 
				
			||||||
 | 
					                    "   - Выберите права доступа для торговли (Trade).  \n"
 | 
				
			||||||
 | 
					                    "   - Можно ограничить доступ по IP для безопасности.\n"
 | 
				
			||||||
 | 
					                    "4. Подтверждение создания\n"
 | 
				
			||||||
 | 
					                    "   - Подтвердите создание ключа.\n"
 | 
				
			||||||
 | 
					                    "   - Отправьте чат-роботу.\n\n"
 | 
				
			||||||
 | 
					                    "Важно: сохраните отдельно API Key и Secret Key в надежном месте. Secret ключ отображается только один раз."
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                parse_mode="Markdown",
 | 
				
			||||||
 | 
					                reply_markup=kbi.add_bybit_api,
 | 
				
			||||||
 | 
					                disable_web_page_preview=True,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await rq.create_user(
 | 
				
			||||||
 | 
					                tg_id=message.from_user.id, username=message.from_user.username
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            await rq.set_user_symbol(tg_id=message.from_user.id, symbol="BTCUSDT")
 | 
				
			||||||
 | 
					            await rq.create_user_additional_settings(tg_id=message.from_user.id)
 | 
				
			||||||
 | 
					            await rq.create_user_risk_management(tg_id=message.from_user.id)
 | 
				
			||||||
 | 
					            await rq.create_user_conditional_settings(tg_id=message.from_user.id)
 | 
				
			||||||
 | 
					            await cmd_connect(message=message, state=state)
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            "Command connect processed successfully for user: %s",
 | 
				
			||||||
 | 
					            message.from_user.id,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command connect for user %s: %s",
 | 
				
			||||||
 | 
					            message.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_handlers_main.message(Command("help"))
 | 
				
			||||||
 | 
					async def cmd_help(message: Message, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handle the /help command.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Clears FSM state and sends a help message with available commands and reply keyboard.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        message (Message): Incoming Telegram message object.
 | 
				
			||||||
 | 
					        state (FSMContext): FSM state context.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Raises:
 | 
				
			||||||
 | 
					        None: Exceptions are caught and logged internally.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					        await message.answer(
 | 
				
			||||||
 | 
					            text="Используйте одну из следующих команд:\n"
 | 
				
			||||||
 | 
					                 "/start - Запустить бота\n"
 | 
				
			||||||
 | 
					                 "/profile - Профиль\n"
 | 
				
			||||||
 | 
					                 "/bybit - Панель Bybit\n"
 | 
				
			||||||
 | 
					                 "/connect - Подключиться к платформе\n",
 | 
				
			||||||
 | 
					            reply_markup=kbr.profile,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            "Command help processed successfully for user: %s",
 | 
				
			||||||
 | 
					            message.from_user.id,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command help for user %s: %s", message.from_user.id, e
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        await message.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка. Пожалуйста, попробуйте позже.",
 | 
				
			||||||
 | 
					            reply_markup=kbr.profile,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_handlers_main.message(Command("cancel"))
 | 
				
			||||||
 | 
					@router_handlers_main.message(
 | 
				
			||||||
 | 
					    lambda message: message.text.casefold() in ["cancel", "отмена"]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def cmd_cancel_handler(message: Message, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handle /cancel command or text 'cancel'/'отмена'.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    If there is an active FSM state, clears it and informs the user.
 | 
				
			||||||
 | 
					    Otherwise, informs that no operation was in progress.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        message (Message): Incoming Telegram message object.
 | 
				
			||||||
 | 
					        state (FSMContext): FSM state context.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Raises:
 | 
				
			||||||
 | 
					        None: Exceptions are caught and logged internally.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    current_state = await state.get_state()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if current_state is None:
 | 
				
			||||||
 | 
					        await message.reply(
 | 
				
			||||||
 | 
					            text="Хорошо, но ничего не происходило.", reply_markup=kbr.profile
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            "Cancel command received but no active state for user %s.",
 | 
				
			||||||
 | 
					            message.from_user.id,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					        await message.reply(text="Команда отменена.", reply_markup=kbr.profile)
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            "Command cancel executed successfully. State cleared for user %s.",
 | 
				
			||||||
 | 
					            message.from_user.id,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error while cancelling command for user %s: %s", message.from_user.id, e
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        await message.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка при отмене. Пожалуйста, попробуйте позже.",
 | 
				
			||||||
 | 
					            reply_markup=kbr.profile,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_handlers_main.callback_query(F.data == "cancel")
 | 
				
			||||||
 | 
					async def cmd_cancel(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handle callback query with data "cancel".
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Clears the FSM state and sends a cancellation message.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        callback_query (CallbackQuery): Callback query object.
 | 
				
			||||||
 | 
					        state (FSMContext): FSM state context.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Raises:
 | 
				
			||||||
 | 
					        None: Exceptions are caught and logged internally.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await callback_query.message.delete()
 | 
				
			||||||
 | 
					        await user_profile_bybit(
 | 
				
			||||||
 | 
					            tg_id=callback_query.from_user.id,
 | 
				
			||||||
 | 
					            message=callback_query.message,
 | 
				
			||||||
 | 
					            state=state,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            "Command cancel processed successfully for user: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command cancel for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    finally:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
							
								
								
									
										17
									
								
								app/telegram/handlers/main_settings/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								app/telegram/handlers/main_settings/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					__all__ = "router"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aiogram import Router
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from app.telegram.handlers.main_settings.additional_settings import (
 | 
				
			||||||
 | 
					    router_additional_settings,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from app.telegram.handlers.main_settings.conditional_settings import (
 | 
				
			||||||
 | 
					    router_conditional_settings,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from app.telegram.handlers.main_settings.risk_management import router_risk_management
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router_main_settings = Router(name=__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router_main_settings.include_router(router_additional_settings)
 | 
				
			||||||
 | 
					router_main_settings.include_router(router_risk_management)
 | 
				
			||||||
 | 
					router_main_settings.include_router(router_conditional_settings)
 | 
				
			||||||
							
								
								
									
										1556
									
								
								app/telegram/handlers/main_settings/additional_settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1556
									
								
								app/telegram/handlers/main_settings/additional_settings.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										174
									
								
								app/telegram/handlers/main_settings/conditional_settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								app/telegram/handlers/main_settings/conditional_settings.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,174 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aiogram import Router
 | 
				
			||||||
 | 
					from aiogram.fsm.context import FSMContext
 | 
				
			||||||
 | 
					from aiogram.types import CallbackQuery, Message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import app.telegram.keyboards.inline as kbi
 | 
				
			||||||
 | 
					import database.request as rq
 | 
				
			||||||
 | 
					from app.helper_functions import is_int_for_timer
 | 
				
			||||||
 | 
					from app.telegram.states.states import ConditionalSettingsState
 | 
				
			||||||
 | 
					from logger_helper.logger_helper import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("conditional_settings")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router_conditional_settings = Router(name="conditional_settings")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_conditional_settings.callback_query(
 | 
				
			||||||
 | 
					    lambda c: c.data == "start_timer" or c.data == "stop_timer"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def timer(callback_query: CallbackQuery, state: FSMContext):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handles callback queries starting with 'start_timer' or 'stop_timer'.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					        if callback_query.data == "start_timer":
 | 
				
			||||||
 | 
					            await state.set_state(ConditionalSettingsState.start_timer_state)
 | 
				
			||||||
 | 
					            msg = await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					                "Введите время в минутах для старта торговли:",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_conditions,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            await state.update_data(prompt_message_id=msg.message_id)
 | 
				
			||||||
 | 
					        elif callback_query.data == "stop_timer":
 | 
				
			||||||
 | 
					            await state.set_state(ConditionalSettingsState.stop_timer_state)
 | 
				
			||||||
 | 
					            msg = await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					                "Введите время в минутах для остановки торговли:",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_conditions,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            await state.update_data(prompt_message_id=msg.message_id)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await callback_query.answer(
 | 
				
			||||||
 | 
					                text="Произошла ошибка. Пожалуйста, попробуйте позже."
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await callback_query.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка. Пожалуйста, попробуйте позже."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command timer for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_conditional_settings.message(ConditionalSettingsState.start_timer_state)
 | 
				
			||||||
 | 
					async def start_timer(message: Message, state: FSMContext):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handles the start_timer state of the Finite State Machine.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            data = await state.get_data()
 | 
				
			||||||
 | 
					            if "prompt_message_id" in data:
 | 
				
			||||||
 | 
					                prompt_message_id = data["prompt_message_id"]
 | 
				
			||||||
 | 
					                await message.bot.delete_message(
 | 
				
			||||||
 | 
					                    chat_id=message.chat.id, message_id=prompt_message_id
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            await message.delete()
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            if "message to delete not found" in str(e).lower():
 | 
				
			||||||
 | 
					                pass  # Ignore this error
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                raise e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        get_start_timer = message.text
 | 
				
			||||||
 | 
					        value = is_int_for_timer(get_start_timer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if value is False:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                "Ошибка: введите валидное число.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_conditions,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "User %s input invalid (not an valid number): %s",
 | 
				
			||||||
 | 
					                message.from_user.id,
 | 
				
			||||||
 | 
					                get_start_timer,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        req = await rq.set_start_timer(
 | 
				
			||||||
 | 
					            tg_id=message.from_user.id, timer_start=int(get_start_timer)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if req:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                "Таймер успешно установлен.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_conditions,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                "Произошла ошибка. Пожалуйста, попробуйте позже.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_conditions,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await message.answer(text="Произошла ошибка. Пожалуйста, попробуйте позже.")
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command start_timer for user %s: %s",
 | 
				
			||||||
 | 
					            message.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_conditional_settings.message(ConditionalSettingsState.stop_timer_state)
 | 
				
			||||||
 | 
					async def stop_timer(message: Message, state: FSMContext):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handles the stop_timer state of the Finite State Machine.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            data = await state.get_data()
 | 
				
			||||||
 | 
					            if "prompt_message_id" in data:
 | 
				
			||||||
 | 
					                prompt_message_id = data["prompt_message_id"]
 | 
				
			||||||
 | 
					                await message.bot.delete_message(
 | 
				
			||||||
 | 
					                    chat_id=message.chat.id, message_id=prompt_message_id
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            await message.delete()
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            if "message to delete not found" in str(e).lower():
 | 
				
			||||||
 | 
					                pass  # Ignore this error
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                raise e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        get_stop_timer = message.text
 | 
				
			||||||
 | 
					        value = is_int_for_timer(get_stop_timer)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if value is False:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                "Ошибка: введите валидное число.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_conditions,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "User %s input invalid (not an valid number): %s",
 | 
				
			||||||
 | 
					                message.from_user.id,
 | 
				
			||||||
 | 
					                get_stop_timer,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        req = await rq.set_stop_timer(
 | 
				
			||||||
 | 
					            tg_id=message.from_user.id, timer_end=int(get_stop_timer)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if req:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                "Таймер успешно установлен.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_conditions,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                "Произошла ошибка. Пожалуйста, попробуйте позже.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_conditions,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await message.answer(text="Произошла ошибка. Пожалуйста, попробуйте позже.")
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command stop_timer for user %s: %s",
 | 
				
			||||||
 | 
					            message.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
							
								
								
									
										467
									
								
								app/telegram/handlers/main_settings/risk_management.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										467
									
								
								app/telegram/handlers/main_settings/risk_management.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,467 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aiogram import F, Router
 | 
				
			||||||
 | 
					from aiogram.fsm.context import FSMContext
 | 
				
			||||||
 | 
					from aiogram.types import CallbackQuery, Message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import app.telegram.keyboards.inline as kbi
 | 
				
			||||||
 | 
					import database.request as rq
 | 
				
			||||||
 | 
					from app.helper_functions import is_int
 | 
				
			||||||
 | 
					from app.telegram.states.states import RiskManagementState
 | 
				
			||||||
 | 
					from logger_helper.logger_helper import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("risk_management")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router_risk_management = Router(name="risk_management")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_risk_management.callback_query(F.data == "take_profit_percent")
 | 
				
			||||||
 | 
					async def take_profit_percent(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handles the 'profit_price_change' callback query.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Clears the current FSM state, edits the message text to display the take profit percent options,
 | 
				
			||||||
 | 
					    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 state.set_state(RiskManagementState.take_profit_percent_state)
 | 
				
			||||||
 | 
					        msg = await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					            text="Введите процент изменения цены для фиксации прибыли: ",
 | 
				
			||||||
 | 
					            reply_markup=kbi.back_to_risk_management,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        await state.update_data(prompt_message_id=msg.message_id)
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            "Command profit_price_change processed successfully for user: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await callback_query.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка. Пожалуйста, попробуйте позже."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command profit_price_change for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_risk_management.message(RiskManagementState.take_profit_percent_state)
 | 
				
			||||||
 | 
					async def set_take_profit_percent(message: Message, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handles user input for setting the take profit percentage.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Updates FSM context with the selected percentage and persists the choice in database.
 | 
				
			||||||
 | 
					    Sends an acknowledgement to user and clears FSM state afterward.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        message (Message): Incoming message from user containing the take profit percentage.
 | 
				
			||||||
 | 
					        state (FSMContext): Finite State Machine context for the current user session.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Logs:
 | 
				
			||||||
 | 
					        Success or error messages with user identification.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            data = await state.get_data()
 | 
				
			||||||
 | 
					            if "prompt_message_id" in data:
 | 
				
			||||||
 | 
					                prompt_message_id = data["prompt_message_id"]
 | 
				
			||||||
 | 
					                await message.bot.delete_message(
 | 
				
			||||||
 | 
					                    chat_id=message.chat.id, message_id=prompt_message_id
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            await message.delete()
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            if "message to delete not found" in str(e).lower():
 | 
				
			||||||
 | 
					                pass  # Ignore this error
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                raise e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        take_profit_percent_value = message.text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not is_int(take_profit_percent_value):
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                text="Ошибка: введите валидное число.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_risk_management,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "User %s input invalid (not an valid number): %s",
 | 
				
			||||||
 | 
					                message.from_user.id,
 | 
				
			||||||
 | 
					                take_profit_percent_value,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if int(take_profit_percent_value) < 1 or int(take_profit_percent_value) > 100:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                text="Ошибка: введите число от 1 до 100.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_risk_management,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "User %s input invalid (not an valid number): %s",
 | 
				
			||||||
 | 
					                message.from_user.id,
 | 
				
			||||||
 | 
					                take_profit_percent_value,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        req = await rq.set_take_profit_percent(
 | 
				
			||||||
 | 
					            tg_id=message.from_user.id,
 | 
				
			||||||
 | 
					            take_profit_percent=int(take_profit_percent_value),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if req:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                text=f"Процент изменения цены для фиксации прибыли "
 | 
				
			||||||
 | 
					                f"установлен на {take_profit_percent_value}%.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_risk_management,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                text="Произошла ошибка при установке процента изменения цены для фиксации прибыли. "
 | 
				
			||||||
 | 
					                "Пожалуйста, попробуйте позже.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_risk_management,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command profit_price_change for user %s: %s",
 | 
				
			||||||
 | 
					            message.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_risk_management.callback_query(F.data == "stop_loss_percent")
 | 
				
			||||||
 | 
					async def stop_loss_percent(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handles the 'stop_loss_percent' callback query.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Clears the current FSM state, edits the message text to display the stop loss percentage options,
 | 
				
			||||||
 | 
					    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 state.set_state(RiskManagementState.stop_loss_percent_state)
 | 
				
			||||||
 | 
					        msg = await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					            text="Введите процент изменения цены для фиксации убытка: ",
 | 
				
			||||||
 | 
					            reply_markup=kbi.back_to_risk_management,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        await state.update_data(prompt_message_id=msg.message_id)
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            "Command stop_loss_percent processed successfully for user: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await callback_query.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка. Пожалуйста, попробуйте позже."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command stop_loss_percent for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_risk_management.message(RiskManagementState.stop_loss_percent_state)
 | 
				
			||||||
 | 
					async def set_stop_loss_percent(message: Message, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handles user input for setting the stop loss percentage.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Updates FSM context with the selected percentage and persists the choice in database.
 | 
				
			||||||
 | 
					    Sends an acknowledgement to user and clears FSM state afterward.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        message (Message): Incoming message from user containing the stop loss percentage.
 | 
				
			||||||
 | 
					        state (FSMContext): Finite State Machine context for the current user session.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Logs:
 | 
				
			||||||
 | 
					        Success or error messages with user identification.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            data = await state.get_data()
 | 
				
			||||||
 | 
					            if "prompt_message_id" in data:
 | 
				
			||||||
 | 
					                prompt_message_id = data["prompt_message_id"]
 | 
				
			||||||
 | 
					                await message.bot.delete_message(
 | 
				
			||||||
 | 
					                    chat_id=message.chat.id, message_id=prompt_message_id
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            await message.delete()
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            if "message to delete not found" in str(e).lower():
 | 
				
			||||||
 | 
					                pass  # Ignore this error
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                raise e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        stop_loss_percent_value = message.text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not is_int(stop_loss_percent_value):
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                text="Ошибка: введите валидное число.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_risk_management,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "User %s input invalid (not an valid number): %s",
 | 
				
			||||||
 | 
					                message.from_user.id,
 | 
				
			||||||
 | 
					                stop_loss_percent_value,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if int(stop_loss_percent_value) < 1 or int(stop_loss_percent_value) > 100:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                text="Ошибка: введите число от 1 до 100.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_risk_management,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "User %s input invalid (not an valid number): %s",
 | 
				
			||||||
 | 
					                message.from_user.id,
 | 
				
			||||||
 | 
					                stop_loss_percent_value,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        req = await rq.set_stop_loss_percent(
 | 
				
			||||||
 | 
					            tg_id=message.from_user.id, stop_loss_percent=int(stop_loss_percent_value)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if req:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                text=f"Процент изменения цены для фиксации убытка "
 | 
				
			||||||
 | 
					                f"установлен на {stop_loss_percent_value}%.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_risk_management,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                text="Произошла ошибка при установке процента изменения цены для фиксации убытка. "
 | 
				
			||||||
 | 
					                "Пожалуйста, попробуйте позже.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_risk_management,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await message.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка при установке процента изменения цены для фиксации убытка. "
 | 
				
			||||||
 | 
					            "Пожалуйста, попробуйте позже.",
 | 
				
			||||||
 | 
					            reply_markup=kbi.back_to_risk_management,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command stop_loss_percent for user %s: %s",
 | 
				
			||||||
 | 
					            message.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_risk_management.callback_query(F.data == "max_risk_percent")
 | 
				
			||||||
 | 
					async def max_risk_percent(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handles the 'max_risk_percent' callback query.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Clears the current FSM state, edits the message text to display the maximum risk percentage options,
 | 
				
			||||||
 | 
					    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 state.set_state(RiskManagementState.max_risk_percent_state)
 | 
				
			||||||
 | 
					        msg = await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					            text="Введите максимальный процент риска: ",
 | 
				
			||||||
 | 
					            reply_markup=kbi.back_to_risk_management,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        await state.update_data(prompt_message_id=msg.message_id)
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            "Command max_risk_percent processed successfully for user: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await callback_query.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка. Пожалуйста, попробуйте позже."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command max_risk_percent for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_risk_management.message(RiskManagementState.max_risk_percent_state)
 | 
				
			||||||
 | 
					async def set_max_risk_percent(message: Message, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handles user input for setting the maximum risk percentage.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Updates FSM context with the selected percentage and persists the choice in database.
 | 
				
			||||||
 | 
					    Sends an acknowledgement to user and clears FSM state afterward.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        message (Message): Incoming message from user containing the maximum risk percentage.
 | 
				
			||||||
 | 
					        state (FSMContext): Finite State Machine context for the current user session.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Logs:
 | 
				
			||||||
 | 
					        Success or error messages with user identification.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            data = await state.get_data()
 | 
				
			||||||
 | 
					            if "prompt_message_id" in data:
 | 
				
			||||||
 | 
					                prompt_message_id = data["prompt_message_id"]
 | 
				
			||||||
 | 
					                await message.bot.delete_message(
 | 
				
			||||||
 | 
					                    chat_id=message.chat.id, message_id=prompt_message_id
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            await message.delete()
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            if "message to delete not found" in str(e).lower():
 | 
				
			||||||
 | 
					                pass  # Ignore this error
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                raise e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        max_risk_percent_value = message.text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not is_int(max_risk_percent_value):
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                text="Ошибка: введите валидное число.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_risk_management,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "User %s input invalid (not an valid number): %s",
 | 
				
			||||||
 | 
					                message.from_user.id,
 | 
				
			||||||
 | 
					                max_risk_percent_value,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if int(max_risk_percent_value) < 1 or int(max_risk_percent_value) > 100:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                text="Ошибка: введите число от 1 до 100.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_risk_management,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "User %s input invalid (not an valid number): %s",
 | 
				
			||||||
 | 
					                message.from_user.id,
 | 
				
			||||||
 | 
					                max_risk_percent_value,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        req = await rq.set_max_risk_percent(
 | 
				
			||||||
 | 
					            tg_id=message.from_user.id, max_risk_percent=int(max_risk_percent_value)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if req:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                text=f"Максимальный процент риска установлен на {max_risk_percent_value}%.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_risk_management,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                text="Произошла ошибка при установке максимального процента риска. "
 | 
				
			||||||
 | 
					                "Пожалуйста, попробуйте позже.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.back_to_risk_management,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await message.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка при установке максимального процента риска. "
 | 
				
			||||||
 | 
					            "Пожалуйста, попробуйте позже.",
 | 
				
			||||||
 | 
					            reply_markup=kbi.back_to_risk_management,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command max_risk_percent for user %s: %s",
 | 
				
			||||||
 | 
					            message.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_risk_management.callback_query(F.data == "commission_fee")
 | 
				
			||||||
 | 
					async def commission_fee(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handles the 'commission_fee' callback query.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Clears the current FSM state, edits the message text to display the commission fee options,
 | 
				
			||||||
 | 
					    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 state.set_state(RiskManagementState.commission_fee_state)
 | 
				
			||||||
 | 
					        msg = await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					            text="Учитывать комиссию биржи для расчета прибыли?: ",
 | 
				
			||||||
 | 
					            reply_markup=kbi.commission_fee,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        await state.update_data(prompt_message_id=msg.message_id)
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            "Command commission_fee processed successfully for user: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await callback_query.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка. Пожалуйста, попробуйте позже."
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command commission_fee for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_risk_management.callback_query(
 | 
				
			||||||
 | 
					    lambda c: c.data in ["Yes_commission_fee", "No_commission_fee"]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def set_commission_fee(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handles user input for setting the commission fee.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Updates FSM context with the selected option and persists the choice in database.
 | 
				
			||||||
 | 
					    Sends an acknowledgement to user and clears FSM state afterward.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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:
 | 
				
			||||||
 | 
					        req = await rq.set_commission_fee(
 | 
				
			||||||
 | 
					            tg_id=callback_query.from_user.id, commission_fee=callback_query.data
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not req:
 | 
				
			||||||
 | 
					            await callback_query.answer(
 | 
				
			||||||
 | 
					                text="Произошла ошибка при установке комиссии биржи. Пожалуйста, попробуйте позже."
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if callback_query.data == "Yes_commission_fee":
 | 
				
			||||||
 | 
					            await callback_query.answer(text="Комиссия биржи учитывается.")
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await callback_query.answer(text="Комиссия биржи не учитывается.")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command commission_fee for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    finally:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
							
								
								
									
										278
									
								
								app/telegram/handlers/settings.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										278
									
								
								app/telegram/handlers/settings.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,278 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aiogram import F, Router
 | 
				
			||||||
 | 
					from aiogram.fsm.context import FSMContext
 | 
				
			||||||
 | 
					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.bybit.get_functions.get_tickers import get_tickers
 | 
				
			||||||
 | 
					from app.helper_functions import calculate_total_budget, get_base_currency, safe_float
 | 
				
			||||||
 | 
					from logger_helper.logger_helper import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("settings")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router_settings = Router(name="settings")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_settings.callback_query(F.data == "additional_settings")
 | 
				
			||||||
 | 
					async def additional_settings(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handler for the "additional_settings" command.
 | 
				
			||||||
 | 
					    Sends a message with additional settings options.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    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:
 | 
				
			||||||
 | 
					            await rq.create_user(
 | 
				
			||||||
 | 
					                tg_id=tg_id, username=callback_query.from_user.username
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            await rq.create_user_additional_settings(tg_id=tg_id)
 | 
				
			||||||
 | 
					            await rq.create_user_risk_management(tg_id=tg_id)
 | 
				
			||||||
 | 
					            await rq.create_user_conditional_settings(tg_id=tg_id)
 | 
				
			||||||
 | 
					            await additional_settings(callback_query=callback_query, state=state)
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        trade_mode_map = {
 | 
				
			||||||
 | 
					            "Merged_Single": "Односторонний режим",
 | 
				
			||||||
 | 
					            "Both_Sides": "Хеджирование",
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        margin_type_map = {
 | 
				
			||||||
 | 
					            "ISOLATED_MARGIN": "Изолированная",
 | 
				
			||||||
 | 
					            "REGULAR_MARGIN": "Кросс",
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        order_type_map = {"Market": "Рыночный", "Limit": "Лимитный"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        trade_mode = additional_data.trade_mode or ""
 | 
				
			||||||
 | 
					        margin_type = additional_data.margin_type or ""
 | 
				
			||||||
 | 
					        order_type = additional_data.order_type or ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        trade_mode_rus = trade_mode_map.get(trade_mode, trade_mode)
 | 
				
			||||||
 | 
					        margin_type_rus = margin_type_map.get(margin_type, margin_type)
 | 
				
			||||||
 | 
					        order_type_rus = order_type_map.get(order_type, "Условный")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def f(x):
 | 
				
			||||||
 | 
					            return safe_float(x)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        leverage = f(additional_data.leverage)
 | 
				
			||||||
 | 
					        leverage_to_buy = f(additional_data.leverage_to_buy)
 | 
				
			||||||
 | 
					        leverage_to_sell = f(additional_data.leverage_to_sell)
 | 
				
			||||||
 | 
					        martingale = f(additional_data.martingale_factor)
 | 
				
			||||||
 | 
					        max_bets = additional_data.max_bets_in_series
 | 
				
			||||||
 | 
					        quantity = f(additional_data.order_quantity)
 | 
				
			||||||
 | 
					        limit_price = f(additional_data.limit_price)
 | 
				
			||||||
 | 
					        trigger_price = f(additional_data.trigger_price) or 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        tickers = await get_tickers(tg_id=tg_id, symbol=symbol)
 | 
				
			||||||
 | 
					        price_symbol = safe_float(tickers.get("lastPrice")) or 0
 | 
				
			||||||
 | 
					        bid = f(tickers.get("bid1Price")) or 0
 | 
				
			||||||
 | 
					        ask = f(tickers.get("ask1Price")) or 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        sym = get_base_currency(symbol)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if trade_mode == "Merged_Single":
 | 
				
			||||||
 | 
					            leverage_str = f"{leverage:.2f}x"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if margin_type == "ISOLATED_MARGIN":
 | 
				
			||||||
 | 
					                leverage_str = f"{leverage_to_buy:.2f}x:{leverage_to_sell:.2f}x"
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                leverage_str = f"{leverage:.2f}x"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        conditional_order_type = additional_data.conditional_order_type or ""
 | 
				
			||||||
 | 
					        conditional_order_type_rus = (
 | 
				
			||||||
 | 
					            "Лимитный"
 | 
				
			||||||
 | 
					            if conditional_order_type == "Limit"
 | 
				
			||||||
 | 
					            else (
 | 
				
			||||||
 | 
					                "Рыночный"
 | 
				
			||||||
 | 
					                if conditional_order_type == "Market"
 | 
				
			||||||
 | 
					                else conditional_order_type
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        conditional_order_type_text = (
 | 
				
			||||||
 | 
					            f"- Тип условного ордера: {conditional_order_type_rus}\n"
 | 
				
			||||||
 | 
					            if order_type == "Conditional"
 | 
				
			||||||
 | 
					            else ""
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        limit_price_text = ""
 | 
				
			||||||
 | 
					        trigger_price_text = ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if order_type == "Limit":
 | 
				
			||||||
 | 
					            limit_price_text = f"- Цена лимитного ордера: {limit_price:.4f} USDT\n"
 | 
				
			||||||
 | 
					        elif order_type == "Conditional":
 | 
				
			||||||
 | 
					            if conditional_order_type == "Limit":
 | 
				
			||||||
 | 
					                limit_price_text = f"- Цена лимитного ордера: {limit_price:.4f} USDT\n"
 | 
				
			||||||
 | 
					            trigger_price_text = f"- Триггер цена: {trigger_price:.4f} USDT\n"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        risk_management_data = await rq.get_user_risk_management(tg_id=tg_id)
 | 
				
			||||||
 | 
					        commission_fee = risk_management_data.commission_fee
 | 
				
			||||||
 | 
					        client = await get_bybit_client(tg_id=tg_id)
 | 
				
			||||||
 | 
					        fee_info = client.get_fee_rates(category="linear", symbol=symbol)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if commission_fee == "Yes_commission_fee":
 | 
				
			||||||
 | 
					            commission_fee_percent = safe_float(
 | 
				
			||||||
 | 
					                fee_info["result"]["list"][0]["takerFeeRate"]
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            commission_fee_percent = 0.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if order_type == "Conditional":
 | 
				
			||||||
 | 
					            if conditional_order_type == "Limit":
 | 
				
			||||||
 | 
					                entry_price = limit_price
 | 
				
			||||||
 | 
					                ask_price = limit_price
 | 
				
			||||||
 | 
					                bid_price = limit_price
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                ask_price = trigger_price
 | 
				
			||||||
 | 
					                bid_price = trigger_price
 | 
				
			||||||
 | 
					                entry_price = trigger_price
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            if order_type == "Limit":
 | 
				
			||||||
 | 
					                entry_price = limit_price
 | 
				
			||||||
 | 
					                ask_price = limit_price
 | 
				
			||||||
 | 
					                bid_price = limit_price
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                entry_price = price_symbol
 | 
				
			||||||
 | 
					                ask_price = ask
 | 
				
			||||||
 | 
					                bid_price = bid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        durability_buy = quantity * bid_price
 | 
				
			||||||
 | 
					        durability_sell = quantity * ask_price
 | 
				
			||||||
 | 
					        quantity_price = quantity * entry_price
 | 
				
			||||||
 | 
					        total_commission = quantity_price * commission_fee_percent
 | 
				
			||||||
 | 
					        total_budget = await calculate_total_budget(
 | 
				
			||||||
 | 
					            quantity=durability_buy,
 | 
				
			||||||
 | 
					            martingale_factor=martingale,
 | 
				
			||||||
 | 
					            max_steps=max_bets,
 | 
				
			||||||
 | 
					            commission_fee_percent=total_commission,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        text = (
 | 
				
			||||||
 | 
					            f"Основные настройки:\n\n"
 | 
				
			||||||
 | 
					            f"- Режим позиции: {trade_mode_rus}\n"
 | 
				
			||||||
 | 
					            f"- Тип маржи: {margin_type_rus}\n"
 | 
				
			||||||
 | 
					            f"- Размер кредитного плеча: {leverage_str}\n"
 | 
				
			||||||
 | 
					            f"- Тип ордера: {order_type_rus}\n"
 | 
				
			||||||
 | 
					            f"- Количество ордера: {quantity} {sym}\n"
 | 
				
			||||||
 | 
					            f"- Коэффициент мартингейла: {martingale:.2f}\n"
 | 
				
			||||||
 | 
					            f"{conditional_order_type_text}"
 | 
				
			||||||
 | 
					            f"{trigger_price_text}"
 | 
				
			||||||
 | 
					            f"{limit_price_text}"
 | 
				
			||||||
 | 
					            f"- Максимальное кол-во ставок в серии: {max_bets}\n\n"
 | 
				
			||||||
 | 
					            f"- Стоимость: {durability_buy:.2f}/{durability_sell:.2f} USDT\n"
 | 
				
			||||||
 | 
					            f"- Рекомендуемый бюджет: {total_budget:.4f} USDT\n"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        keyboard = kbi.get_additional_settings_keyboard(
 | 
				
			||||||
 | 
					            current_order_type=order_type, conditional_order=conditional_order_type
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        await callback_query.message.edit_text(text=text, reply_markup=keyboard)
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            "Command additional_settings processed successfully for user: %s", tg_id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					            text="Произошла ошибка. Пожалуйста, попробуйте ещё раз.",
 | 
				
			||||||
 | 
					            reply_markup=kbi.profile_bybit,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command additional_settings for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_settings.callback_query(F.data == "risk_management")
 | 
				
			||||||
 | 
					async def risk_management(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handler for the "risk_management" command.
 | 
				
			||||||
 | 
					    Sends a message with risk management options.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					        risk_management_data = await rq.get_user_risk_management(
 | 
				
			||||||
 | 
					            tg_id=callback_query.from_user.id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        if risk_management_data:
 | 
				
			||||||
 | 
					            take_profit_percent = risk_management_data.take_profit_percent or ""
 | 
				
			||||||
 | 
					            stop_loss_percent = risk_management_data.stop_loss_percent or ""
 | 
				
			||||||
 | 
					            max_risk_percent = risk_management_data.max_risk_percent or ""
 | 
				
			||||||
 | 
					            commission_fee = risk_management_data.commission_fee or ""
 | 
				
			||||||
 | 
					            commission_fee_rus = (
 | 
				
			||||||
 | 
					                "Да" if commission_fee == "Yes_commission_fee" else "Нет"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					                text=f"Риск-менеджмент:\n\n"
 | 
				
			||||||
 | 
					                f"- Процент изменения цены для фиксации прибыли: {take_profit_percent}%\n"
 | 
				
			||||||
 | 
					                f"- Процент изменения цены для фиксации убытка: {stop_loss_percent}%\n\n"
 | 
				
			||||||
 | 
					                f"- Максимальный риск на сделку (в % от баланса): {max_risk_percent}%\n\n"
 | 
				
			||||||
 | 
					                f"- Комиссия биржи для расчета прибыли: {commission_fee_rus}\n\n",
 | 
				
			||||||
 | 
					                reply_markup=kbi.risk_management,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "Command main_settings processed successfully for user: %s",
 | 
				
			||||||
 | 
					                callback_query.from_user.id,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await rq.create_user(
 | 
				
			||||||
 | 
					                tg_id=callback_query.from_user.id,
 | 
				
			||||||
 | 
					                username=callback_query.from_user.username,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            await rq.create_user_additional_settings(tg_id=callback_query.from_user.id)
 | 
				
			||||||
 | 
					            await rq.create_user_risk_management(tg_id=callback_query.from_user.id)
 | 
				
			||||||
 | 
					            await rq.create_user_conditional_settings(tg_id=callback_query.from_user.id)
 | 
				
			||||||
 | 
					            await risk_management(callback_query=callback_query, state=state)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command main_settings for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_settings.callback_query(F.data == "conditional_settings")
 | 
				
			||||||
 | 
					async def conditions(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handler for the "conditions" command.
 | 
				
			||||||
 | 
					    Sends a message with trading conditions options.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					        conditional_settings_data = await rq.get_user_conditional_settings(
 | 
				
			||||||
 | 
					            tg_id=callback_query.from_user.id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        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",
 | 
				
			||||||
 | 
					                reply_markup=kbi.conditions,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "Command main_settings processed successfully for user: %s",
 | 
				
			||||||
 | 
					                callback_query.from_user.id,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await rq.create_user(
 | 
				
			||||||
 | 
					                tg_id=callback_query.from_user.id,
 | 
				
			||||||
 | 
					                username=callback_query.from_user.username,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            await rq.create_user_additional_settings(tg_id=callback_query.from_user.id)
 | 
				
			||||||
 | 
					            await rq.create_user_risk_management(tg_id=callback_query.from_user.id)
 | 
				
			||||||
 | 
					            await rq.create_user_conditional_settings(tg_id=callback_query.from_user.id)
 | 
				
			||||||
 | 
					            await conditions(callback_query=callback_query, state=state)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command main_settings for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
							
								
								
									
										414
									
								
								app/telegram/handlers/start_trading.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										414
									
								
								app/telegram/handlers/start_trading.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,414 @@
 | 
				
			|||||||
 | 
					import asyncio
 | 
				
			||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aiogram import F, Router
 | 
				
			||||||
 | 
					from aiogram.fsm.context import FSMContext
 | 
				
			||||||
 | 
					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.open_positions import start_trading_cycle
 | 
				
			||||||
 | 
					from app.helper_functions import safe_float
 | 
				
			||||||
 | 
					from app.telegram.tasks.tasks import (
 | 
				
			||||||
 | 
					    add_start_task_merged,
 | 
				
			||||||
 | 
					    add_start_task_switch,
 | 
				
			||||||
 | 
					    cancel_start_task_merged,
 | 
				
			||||||
 | 
					    cancel_start_task_switch,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					from logger_helper.logger_helper import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("start_trading")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router_start_trading = Router(name="start_trading")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_start_trading.callback_query(F.data == "start_trading")
 | 
				
			||||||
 | 
					async def start_trading(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handles the "start_trading" callback query.
 | 
				
			||||||
 | 
					    Clears the FSM state and sends a message to the user to select the trading mode.
 | 
				
			||||||
 | 
					    :param callback_query: Message
 | 
				
			||||||
 | 
					    :param state: FSMContext
 | 
				
			||||||
 | 
					    :return: None
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					        additional_data = await rq.get_user_additional_settings(
 | 
				
			||||||
 | 
					            tg_id=callback_query.from_user.id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        trade_mode = additional_data.trade_mode
 | 
				
			||||||
 | 
					        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)
 | 
				
			||||||
 | 
					            position_idx = position.get("positionIdx")
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            size = 0
 | 
				
			||||||
 | 
					            position_idx = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if position_idx != 0 and safe_float(size) > 0 and trade_mode == "Merged_Single":
 | 
				
			||||||
 | 
					            await callback_query.answer(
 | 
				
			||||||
 | 
					                text="У вас есть активная позиция в режиме хеджирования. "
 | 
				
			||||||
 | 
					                "Открытие сделки в одностороннем режиме невозможно.",
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if position_idx == 0 and safe_float(size) > 0 and trade_mode == "Both_Sides":
 | 
				
			||||||
 | 
					            await callback_query.answer(
 | 
				
			||||||
 | 
					                text="У вас есть активная позиция в одностороннем режиме. "
 | 
				
			||||||
 | 
					                "Открытие сделки в режиме хеджирования невозможно.",
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if trade_mode == "Merged_Single":
 | 
				
			||||||
 | 
					            await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					                text="Выберите режим торговли:\n\n"
 | 
				
			||||||
 | 
					                "Лонг - все сделки серии открываются на покупку.\n"
 | 
				
			||||||
 | 
					                "Шорт - все сделки серии открываются на продажу.\n"
 | 
				
			||||||
 | 
					                "Свитч - направление каждой сделки серии меняется по переменно.\n",
 | 
				
			||||||
 | 
					                reply_markup=kbi.merged_start_trading,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:  # trade_mode == "Both_Sides":
 | 
				
			||||||
 | 
					            await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					                text="Выберите режим торговли:\n\n"
 | 
				
			||||||
 | 
					                "Лонг - все сделки открываются на покупку.\n"
 | 
				
			||||||
 | 
					                "Шорт - все сделки открываются на продажу.\n",
 | 
				
			||||||
 | 
					                reply_markup=kbi.both_start_trading,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            "Command start_trading processed successfully for user: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await callback_query.answer(text="Произошла ошибка при запуске торговли")
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command start_trading for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_start_trading.callback_query(lambda c: c.data == "long" or c.data == "short")
 | 
				
			||||||
 | 
					async def start_trading_long(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handles the "long" or "short" callback query.
 | 
				
			||||||
 | 
					    Clears the FSM state and starts the trading cycle.
 | 
				
			||||||
 | 
					    :param callback_query: Message
 | 
				
			||||||
 | 
					    :param state: FSMContext
 | 
				
			||||||
 | 
					    :return: None
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        if callback_query.data == "long":
 | 
				
			||||||
 | 
					            side = "Buy"
 | 
				
			||||||
 | 
					        elif callback_query.data == "short":
 | 
				
			||||||
 | 
					            side = "Sell"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await callback_query.answer(text="Произошла ошибка при запуске торговли")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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)
 | 
				
			||||||
 | 
					            position_idx = position.get("positionIdx")
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            size = 0
 | 
				
			||||||
 | 
					            position_idx = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if position_idx == 0 and safe_float(size) > 0:
 | 
				
			||||||
 | 
					            await callback_query.answer(
 | 
				
			||||||
 | 
					                text="Торговля уже запущена в одностороннем режиме для данного инструмента"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        conditional_data = await rq.get_user_conditional_settings(
 | 
				
			||||||
 | 
					            tg_id=callback_query.from_user.id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        timer_start = conditional_data.timer_start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cancel_start_task_merged(user_id=callback_query.from_user.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        async def delay_start():
 | 
				
			||||||
 | 
					            if timer_start > 0:
 | 
				
			||||||
 | 
					                await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					                    text=f"Торговля будет запущена с задержкой {timer_start} мин.",
 | 
				
			||||||
 | 
					                    reply_markup=kbi.cancel_timer_merged,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                await rq.set_start_timer(
 | 
				
			||||||
 | 
					                    tg_id=callback_query.from_user.id, timer_start=0
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                await asyncio.sleep(timer_start * 60)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await rq.set_auto_trading(
 | 
				
			||||||
 | 
					                tg_id=callback_query.from_user.id,
 | 
				
			||||||
 | 
					                symbol=symbol,
 | 
				
			||||||
 | 
					                auto_trading=True,
 | 
				
			||||||
 | 
					                side=side,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            res = await start_trading_cycle(
 | 
				
			||||||
 | 
					                tg_id=callback_query.from_user.id,
 | 
				
			||||||
 | 
					                side=side,
 | 
				
			||||||
 | 
					                switch_side_mode=False,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            error_messages = {
 | 
				
			||||||
 | 
					                "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": "️️Количество контрактов превышает допустимое максимальное количество контрактов",
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if res == "OK":
 | 
				
			||||||
 | 
					                await callback_query.message.edit_text(text="Торговля запущена")
 | 
				
			||||||
 | 
					                await state.clear()
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                await rq.set_auto_trading(
 | 
				
			||||||
 | 
					                    tg_id=callback_query.from_user.id,
 | 
				
			||||||
 | 
					                    symbol=symbol,
 | 
				
			||||||
 | 
					                    auto_trading=False,
 | 
				
			||||||
 | 
					                    side=side,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                text = error_messages.get(res, "Произошла ошибка при запуске торговли")
 | 
				
			||||||
 | 
					                await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					                    text=text, reply_markup=kbi.profile_bybit
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await callback_query.message.edit_text("Запуск торговли...")
 | 
				
			||||||
 | 
					        task = asyncio.create_task(delay_start())
 | 
				
			||||||
 | 
					        await add_start_task_merged(user_id=callback_query.from_user.id, task=task)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await callback_query.answer(text="Произошла ошибка при запуске торговли")
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command long for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except asyncio.CancelledError:
 | 
				
			||||||
 | 
					        logger.error("Cancelled timer for user %s", callback_query.from_user.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_start_trading.callback_query(lambda c: c.data == "switch")
 | 
				
			||||||
 | 
					async def start_trading_switch(
 | 
				
			||||||
 | 
					    callback_query: CallbackQuery, state: FSMContext
 | 
				
			||||||
 | 
					) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handles the "switch" callback query.
 | 
				
			||||||
 | 
					    Clears the FSM state and sends a message to the user to select the switch side.
 | 
				
			||||||
 | 
					    :param callback_query: Message
 | 
				
			||||||
 | 
					    :param state: FSMContext
 | 
				
			||||||
 | 
					    :return: None
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					        await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					            text="Выберите направление первой сделки серии:\n\n"
 | 
				
			||||||
 | 
					            "Лонг - открывается первая сделка на покупку.\n"
 | 
				
			||||||
 | 
					            "Шорт - открывается первая сделка на продажу.\n"
 | 
				
			||||||
 | 
					            "По направлению - сделка открывается в направлении последней сделки предыдущей серии.\n"
 | 
				
			||||||
 | 
					            "Противоположно - сделка открывается в противоположном направлении последней сделки предыдущей серии.\n",
 | 
				
			||||||
 | 
					            reply_markup=kbi.switch_side,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await callback_query.answer(text="Произошла ошибка при запуске торговли")
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command start trading switch for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_start_trading.callback_query(
 | 
				
			||||||
 | 
					    lambda c: c.data
 | 
				
			||||||
 | 
					    in {"switch_long", "switch_short", "switch_direction", "switch_opposite"}
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def start_switch(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Starts the trading cycle with the selected side.
 | 
				
			||||||
 | 
					    :param callback_query:
 | 
				
			||||||
 | 
					    :param state:
 | 
				
			||||||
 | 
					    :return:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        symbol = await rq.get_user_symbol(tg_id=callback_query.from_user.id)
 | 
				
			||||||
 | 
					        user_deals_data = await rq.get_user_deal_by_symbol(
 | 
				
			||||||
 | 
					            tg_id=callback_query.from_user.id, symbol=symbol
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        get_side = "Buy"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if user_deals_data:
 | 
				
			||||||
 | 
					            get_side = user_deals_data.last_side or "Buy"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if callback_query.data == "switch_long":
 | 
				
			||||||
 | 
					            side = "Buy"
 | 
				
			||||||
 | 
					        elif callback_query.data == "switch_short":
 | 
				
			||||||
 | 
					            side = "Sell"
 | 
				
			||||||
 | 
					        elif callback_query.data == "switch_direction":
 | 
				
			||||||
 | 
					            side = get_side
 | 
				
			||||||
 | 
					        elif callback_query.data == "switch_opposite":
 | 
				
			||||||
 | 
					            if get_side == "Buy":
 | 
				
			||||||
 | 
					                side = "Sell"
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                side = "Buy"
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await callback_query.answer(text="Произошла ошибка при запуске торговли")
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        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)
 | 
				
			||||||
 | 
					            position_idx = position.get("positionIdx")
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            size = 0
 | 
				
			||||||
 | 
					            position_idx = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if position_idx == 1 and safe_float(size) > 0 and side == "Buy":
 | 
				
			||||||
 | 
					            await callback_query.answer(
 | 
				
			||||||
 | 
					                text="Торговля уже запущена в режиме хеджирования на покупку для данного инструмента"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if position_idx == 2 and safe_float(size) > 0 and side == "Sell":
 | 
				
			||||||
 | 
					            await callback_query.answer(
 | 
				
			||||||
 | 
					                text="Торговля уже запущена в режиме хеджирования на продажу для данного инструмента"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        conditional_data = await rq.get_user_conditional_settings(
 | 
				
			||||||
 | 
					            tg_id=callback_query.from_user.id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        timer_start = conditional_data.timer_start
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cancel_start_task_switch(user_id=callback_query.from_user.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        async def delay_start():
 | 
				
			||||||
 | 
					            if timer_start > 0:
 | 
				
			||||||
 | 
					                await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					                    text=f"Торговля будет запущена с задержкой {timer_start} мин.",
 | 
				
			||||||
 | 
					                    reply_markup=kbi.cancel_timer_switch,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                await rq.set_start_timer(
 | 
				
			||||||
 | 
					                    tg_id=callback_query.from_user.id, timer_start=0
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                await asyncio.sleep(timer_start * 60)
 | 
				
			||||||
 | 
					            await rq.set_auto_trading(
 | 
				
			||||||
 | 
					                tg_id=callback_query.from_user.id,
 | 
				
			||||||
 | 
					                symbol=symbol,
 | 
				
			||||||
 | 
					                auto_trading=True,
 | 
				
			||||||
 | 
					                side=side,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            if side == "Buy":
 | 
				
			||||||
 | 
					                r_side = "Sell"
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                r_side = "Buy"
 | 
				
			||||||
 | 
					            await rq.set_auto_trading(
 | 
				
			||||||
 | 
					                tg_id=callback_query.from_user.id,
 | 
				
			||||||
 | 
					                symbol=symbol,
 | 
				
			||||||
 | 
					                auto_trading=True,
 | 
				
			||||||
 | 
					                side=r_side,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            res = await start_trading_cycle(
 | 
				
			||||||
 | 
					                tg_id=callback_query.from_user.id,
 | 
				
			||||||
 | 
					                side=side,
 | 
				
			||||||
 | 
					                switch_side_mode=True,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            error_messages = {
 | 
				
			||||||
 | 
					                "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": "️ ️️Количество контрактов превышает допустимое максимальное количество контрактов",
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if res == "OK":
 | 
				
			||||||
 | 
					                await callback_query.message.edit_text(text="Торговля запущена")
 | 
				
			||||||
 | 
					                await state.clear()
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                await rq.set_auto_trading(
 | 
				
			||||||
 | 
					                    tg_id=callback_query.from_user.id,
 | 
				
			||||||
 | 
					                    symbol=symbol,
 | 
				
			||||||
 | 
					                    auto_trading=False,
 | 
				
			||||||
 | 
					                    side=side,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                if side == "Buy":
 | 
				
			||||||
 | 
					                    r_side = "Sell"
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    r_side = "Buy"
 | 
				
			||||||
 | 
					                await rq.set_auto_trading(
 | 
				
			||||||
 | 
					                    tg_id=callback_query.from_user.id,
 | 
				
			||||||
 | 
					                    symbol=symbol,
 | 
				
			||||||
 | 
					                    auto_trading=False,
 | 
				
			||||||
 | 
					                    side=r_side,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                text = error_messages.get(res, "Произошла ошибка при запуске торговли")
 | 
				
			||||||
 | 
					                await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					                    text=text, reply_markup=kbi.profile_bybit
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await callback_query.message.edit_text("Запуск торговли...")
 | 
				
			||||||
 | 
					        task = asyncio.create_task(delay_start())
 | 
				
			||||||
 | 
					        await add_start_task_switch(user_id=callback_query.from_user.id, task=task)
 | 
				
			||||||
 | 
					    except asyncio.CancelledError:
 | 
				
			||||||
 | 
					        logger.error("Cancelled timer for user %s", callback_query.from_user.id)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await callback_query.answer(text="Произошла ошибка при запуске торговли")
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command start switch for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_start_trading.callback_query(
 | 
				
			||||||
 | 
					    lambda c: c.data == "cancel_timer_merged" or c.data == "cancel_timer_switch"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					async def cancel_start_trading(
 | 
				
			||||||
 | 
					    callback_query: CallbackQuery, state: FSMContext
 | 
				
			||||||
 | 
					) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handles the "cancel_timer" callback query.
 | 
				
			||||||
 | 
					    Clears the FSM state and sends a message to the user to cancel the start trading process.
 | 
				
			||||||
 | 
					    :param callback_query: Message
 | 
				
			||||||
 | 
					    :param state: FSMContext
 | 
				
			||||||
 | 
					    :return: None
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					        if callback_query.data == "cancel_timer_merged":
 | 
				
			||||||
 | 
					            cancel_start_task_merged(user_id=callback_query.from_user.id)
 | 
				
			||||||
 | 
					        elif callback_query.data == "cancel_timer_switch":
 | 
				
			||||||
 | 
					            cancel_start_task_switch(user_id=callback_query.from_user.id)
 | 
				
			||||||
 | 
					        await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					            text="Запуск торговли отменен", reply_markup=kbi.profile_bybit
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await callback_query.answer("Произошла ошибка при отмене запуска торговли")
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command cancel_timer for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
							
								
								
									
										99
									
								
								app/telegram/handlers/stop_trading.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								app/telegram/handlers/stop_trading.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,99 @@
 | 
				
			|||||||
 | 
					import asyncio
 | 
				
			||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aiogram import F, Router
 | 
				
			||||||
 | 
					from aiogram.fsm.context import FSMContext
 | 
				
			||||||
 | 
					from aiogram.types import CallbackQuery
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import app.telegram.keyboards.inline as kbi
 | 
				
			||||||
 | 
					import database.request as rq
 | 
				
			||||||
 | 
					from app.telegram.tasks.tasks import add_stop_task, cancel_stop_task
 | 
				
			||||||
 | 
					from logger_helper.logger_helper import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("stop_trading")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router_stop_trading = Router(name="stop_trading")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_stop_trading.callback_query(F.data == "stop_trading")
 | 
				
			||||||
 | 
					async def stop_all_trading(callback_query: CallbackQuery, state: FSMContext):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cancel_stop_task(callback_query.from_user.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        conditional_data = await rq.get_user_conditional_settings(
 | 
				
			||||||
 | 
					            tg_id=callback_query.from_user.id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        timer_end = conditional_data.timer_end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        async def delay_start():
 | 
				
			||||||
 | 
					            if timer_end > 0:
 | 
				
			||||||
 | 
					                await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					                    text=f"Торговля будет остановлена с задержкой {timer_end} мин.",
 | 
				
			||||||
 | 
					                    reply_markup=kbi.cancel_timer_stop,
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                await rq.set_stop_timer(tg_id=callback_query.from_user.id, timer_end=0)
 | 
				
			||||||
 | 
					                await asyncio.sleep(timer_end * 60)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            user_auto_trading_list = await rq.get_all_user_auto_trading(
 | 
				
			||||||
 | 
					                tg_id=callback_query.from_user.id
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            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
 | 
				
			||||||
 | 
					                        get_side = active_auto_trading.side
 | 
				
			||||||
 | 
					                        req = await rq.set_auto_trading(
 | 
				
			||||||
 | 
					                            tg_id=callback_query.from_user.id,
 | 
				
			||||||
 | 
					                            symbol=symbol,
 | 
				
			||||||
 | 
					                            auto_trading=False,
 | 
				
			||||||
 | 
					                            side=get_side,
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                        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
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        logger.debug(
 | 
				
			||||||
 | 
					            "Command stop_trading processed successfully for user: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await callback_query.answer(text="Произошла ошибка при остановке торговли")
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command stop_trading for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_stop_trading.callback_query(F.data == "cancel_timer_stop")
 | 
				
			||||||
 | 
					async def cancel_stop_trading(callback_query: CallbackQuery, state: FSMContext):
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					        cancel_stop_task(callback_query.from_user.id)
 | 
				
			||||||
 | 
					        await callback_query.message.edit_text(
 | 
				
			||||||
 | 
					            text="Таймер отменён.", reply_markup=kbi.profile_bybit
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await callback_query.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка при отмене остановки торговли"
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        logger.error(
 | 
				
			||||||
 | 
					            "Error processing command cancel_timer_stop for user %s: %s",
 | 
				
			||||||
 | 
					            callback_query.from_user.id,
 | 
				
			||||||
 | 
					            e,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
							
								
								
									
										168
									
								
								app/telegram/handlers/tp_sl_handlers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								app/telegram/handlers/tp_sl_handlers.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,168 @@
 | 
				
			|||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aiogram import Router
 | 
				
			||||||
 | 
					from aiogram.fsm.context import FSMContext
 | 
				
			||||||
 | 
					from aiogram.types import CallbackQuery, Message
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import app.telegram.keyboards.inline as kbi
 | 
				
			||||||
 | 
					from app.bybit.set_functions.set_tp_sl import set_tp_sl_for_position
 | 
				
			||||||
 | 
					from app.helper_functions import is_number
 | 
				
			||||||
 | 
					from app.telegram.states.states import SetTradingStopState
 | 
				
			||||||
 | 
					from logger_helper.logger_helper import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("tp_sl_handlers")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router_tp_sl_handlers = Router(name="tp_sl_handlers")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_tp_sl_handlers.callback_query(lambda c: c.data.startswith("pos_tp_sl_"))
 | 
				
			||||||
 | 
					async def set_tp_sl_handler(callback_query: CallbackQuery, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handles the 'pos_tp_sl' callback query.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Clears the current FSM state, sets the state to 'take_profit', and prompts the user to enter the take-profit.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        callback_query (CallbackQuery): Incoming callback query from Telegram inline keyboard.
 | 
				
			||||||
 | 
					        state (FSMContext): Finite State Machine context for the current user session.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					        data = callback_query.data
 | 
				
			||||||
 | 
					        parts = data.split("_")
 | 
				
			||||||
 | 
					        symbol = parts[3]
 | 
				
			||||||
 | 
					        position_idx = int(parts[4])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await state.set_state(SetTradingStopState.take_profit_state)
 | 
				
			||||||
 | 
					        await state.update_data(symbol=symbol)
 | 
				
			||||||
 | 
					        await state.update_data(position_idx=position_idx)
 | 
				
			||||||
 | 
					        msg = await callback_query.message.answer(
 | 
				
			||||||
 | 
					            text="Введите тейк-профит:", reply_markup=kbi.cancel
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        await state.update_data(prompt_message_id=msg.message_id)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error in set_tp_sl_handler: %s", e)
 | 
				
			||||||
 | 
					        await callback_query.answer(text="Произошла ошибка, попробуйте позже")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_tp_sl_handlers.message(SetTradingStopState.take_profit_state)
 | 
				
			||||||
 | 
					async def set_take_profit_handler(message: Message, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handles the 'take_profit' state.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Clears the current FSM state, sets the state to 'stop_loss', and prompts the user to enter the stop-loss.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        message (Message): Incoming message from Telegram.
 | 
				
			||||||
 | 
					        state (FSMContext): Finite State Machine context for the current user session.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Returns:
 | 
				
			||||||
 | 
					        None
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            data = await state.get_data()
 | 
				
			||||||
 | 
					            if "prompt_message_id" in data:
 | 
				
			||||||
 | 
					                prompt_message_id = data["prompt_message_id"]
 | 
				
			||||||
 | 
					                await message.bot.delete_message(
 | 
				
			||||||
 | 
					                    chat_id=message.chat.id, message_id=prompt_message_id
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            await message.delete()
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            if "message to delete not found" in str(e).lower():
 | 
				
			||||||
 | 
					                pass  # Ignore this error
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                raise e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        take_profit = message.text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not is_number(take_profit):
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                "Ошибка: введите валидное число.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.profile_bybit,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "User %s input invalid (not an valid number): %s",
 | 
				
			||||||
 | 
					                message.from_user.id,
 | 
				
			||||||
 | 
					                take_profit,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await state.update_data(take_profit=take_profit)
 | 
				
			||||||
 | 
					        await state.set_state(SetTradingStopState.stop_loss_state)
 | 
				
			||||||
 | 
					        msg = await message.answer(text="Введите стоп-лосс:", reply_markup=kbi.cancel)
 | 
				
			||||||
 | 
					        await state.update_data(prompt_message_id=msg.message_id)
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        logger.error("Error in set_take_profit_handler: %s", e)
 | 
				
			||||||
 | 
					        await message.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка, попробуйте позже", reply_markup=kbi.profile_bybit
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@router_tp_sl_handlers.message(SetTradingStopState.stop_loss_state)
 | 
				
			||||||
 | 
					async def set_stop_loss_handler(message: Message, state: FSMContext) -> None:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Handles the 'stop_loss' state.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Clears the current FSM state, sets the state to 'take_profit', and prompts the user to enter the take-profit.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        message (Message): Incoming message from Telegram.
 | 
				
			||||||
 | 
					        state (FSMContext): Finite State Machine context for the current user session.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Returns:
 | 
				
			||||||
 | 
					        None
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            data = await state.get_data()
 | 
				
			||||||
 | 
					            if "prompt_message_id" in data:
 | 
				
			||||||
 | 
					                prompt_message_id = data["prompt_message_id"]
 | 
				
			||||||
 | 
					                await message.bot.delete_message(
 | 
				
			||||||
 | 
					                    chat_id=message.chat.id, message_id=prompt_message_id
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            await message.delete()
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            if "message to delete not found" in str(e).lower():
 | 
				
			||||||
 | 
					                pass  # Ignore this error
 | 
				
			||||||
 | 
					            else:
 | 
				
			||||||
 | 
					                raise e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        stop_loss = message.text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if not is_number(stop_loss):
 | 
				
			||||||
 | 
					            await message.answer(
 | 
				
			||||||
 | 
					                "Ошибка: введите валидное число.",
 | 
				
			||||||
 | 
					                reply_markup=kbi.profile_bybit,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            logger.debug(
 | 
				
			||||||
 | 
					                "User %s input invalid (not an valid number): %s",
 | 
				
			||||||
 | 
					                message.from_user.id,
 | 
				
			||||||
 | 
					                stop_loss,
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await state.update_data(stop_loss=stop_loss)
 | 
				
			||||||
 | 
					        data = await state.get_data()
 | 
				
			||||||
 | 
					        symbol = data["symbol"]
 | 
				
			||||||
 | 
					        take_profit = data["take_profit"]
 | 
				
			||||||
 | 
					        position_idx = data["position_idx"]
 | 
				
			||||||
 | 
					        res = await set_tp_sl_for_position(
 | 
				
			||||||
 | 
					            tg_id=message.from_user.id,
 | 
				
			||||||
 | 
					            symbol=symbol,
 | 
				
			||||||
 | 
					            take_profit_price=float(take_profit),
 | 
				
			||||||
 | 
					            stop_loss_price=float(stop_loss),
 | 
				
			||||||
 | 
					            position_idx=position_idx,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if res:
 | 
				
			||||||
 | 
					            await message.answer(text="Тейк-профит и стоп-лосс установлены.")
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            await message.answer(text="Тейк-профит и стоп-лосс не установлены.")
 | 
				
			||||||
 | 
					        await state.clear()
 | 
				
			||||||
 | 
					    except Exception as e:
 | 
				
			||||||
 | 
					        await message.answer(
 | 
				
			||||||
 | 
					            text="Произошла ошибка, попробуйте позже", reply_markup=kbi.profile_bybit
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        logger.error("Error in set_stop_loss_handler: %s", e)
 | 
				
			||||||
							
								
								
									
										484
									
								
								app/telegram/keyboards/inline.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										484
									
								
								app/telegram/keyboards/inline.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,484 @@
 | 
				
			|||||||
 | 
					from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
 | 
				
			||||||
 | 
					from aiogram.utils.keyboard import InlineKeyboardBuilder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					connect_the_platform = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Подключить платформу", callback_data="connect_platform"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					add_bybit_api = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [InlineKeyboardButton(text="Добавить API", callback_data="add_bybit_api")]
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					profile_bybit = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [InlineKeyboardButton(text="На главную", callback_data="profile_bybit")]
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cancel = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[[InlineKeyboardButton(text="Отменить", callback_data="cancel")]]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					main_menu = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [InlineKeyboardButton(text="Настройки", callback_data="main_settings")],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Сменить торговую пару", callback_data="change_symbol"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [InlineKeyboardButton(text="Мои сделки", callback_data="my_deals")],
 | 
				
			||||||
 | 
					        [InlineKeyboardButton(text="Начать торговлю", callback_data="start_trading")],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Остановить торговлю", callback_data="stop_trading"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# MAIN SETTINGS
 | 
				
			||||||
 | 
					main_settings = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Основные настройки", callback_data="additional_settings"
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Риск-менеджмент", callback_data="risk_management"
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Условия запуска", callback_data="conditional_settings"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [InlineKeyboardButton(text="Назад", callback_data="profile_bybit")],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# additional_settings
 | 
				
			||||||
 | 
					def get_additional_settings_keyboard(
 | 
				
			||||||
 | 
					    current_order_type: str, conditional_order: str
 | 
				
			||||||
 | 
					) -> InlineKeyboardMarkup:
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Create keyboard for additional settings
 | 
				
			||||||
 | 
					    :param current_order_type: Market, Limit or Conditional
 | 
				
			||||||
 | 
					    :param conditional_order: Market or Limit
 | 
				
			||||||
 | 
					    :return: InlineKeyboardMarkup
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    buttons = [
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Режим позиции", callback_data="trade_mode"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Тип маржи", callback_data="margin_type"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Размер кредитного плеча", callback_data="leverage"
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Тип ордера", callback_data="order_type"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Количество ордера", callback_data="order_quantity"
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Коэффициент мартингейла", callback_data="martingale_factor"
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if current_order_type == "Conditional":
 | 
				
			||||||
 | 
					        buttons.append(
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                InlineKeyboardButton(
 | 
				
			||||||
 | 
					                    text="Тип условного ордера", callback_data="conditional_order_type"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        buttons.append(
 | 
				
			||||||
 | 
					            [InlineKeyboardButton(text="Триггер цена", callback_data="trigger_price")]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        if conditional_order == "Limit":
 | 
				
			||||||
 | 
					            buttons.append(
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    InlineKeyboardButton(
 | 
				
			||||||
 | 
					                        text="Цена лимитного ордера", callback_data="limit_price"
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					    elif current_order_type == "Limit":
 | 
				
			||||||
 | 
					        buttons.append(
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                InlineKeyboardButton(
 | 
				
			||||||
 | 
					                    text="Цена лимитного ордера", callback_data="limit_price"
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    buttons.append(
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Максимальное кол-во ставок в серии",
 | 
				
			||||||
 | 
					                callback_data="max_bets_in_series",
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    buttons.append(
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Назад", callback_data="main_settings"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return InlineKeyboardMarkup(inline_keyboard=buttons)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					order_type = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Рыночный", callback_data="Market"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Лимитный", callback_data="Limit"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [InlineKeyboardButton(text="Условный", callback_data="Conditional")],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Назад", callback_data="additional_settings"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					conditional_order_type = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Рыночный", callback_data="set_market"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Лимитный", callback_data="set_limit"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Назад", callback_data="additional_settings"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					trade_mode = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Односторонний режим", callback_data="Merged_Single"
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Хеджирование", callback_data="Both_Sides"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Назад", callback_data="additional_settings"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					margin_type = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Изолированная", callback_data="ISOLATED_MARGIN"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Кросс", callback_data="REGULAR_MARGIN"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Назад", callback_data="additional_settings"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					back_to_additional_settings = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Назад", callback_data="additional_settings"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					change_limit_price = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Установить цену", callback_data="set_limit_price"
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Последняя цена", callback_data="last_price"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Назад", callback_data="additional_settings"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					back_to_change_limit_price = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Назад", callback_data="limit_price"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Основные настройки", callback_data="additional_settings"
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [InlineKeyboardButton(text="На главную", callback_data="profile_bybit")],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# risk_management
 | 
				
			||||||
 | 
					risk_management = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Изм. цены прибыли", callback_data="take_profit_percent"
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Изм. цены убытка", callback_data="stop_loss_percent"
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Максимальный риск", callback_data="max_risk_percent"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [InlineKeyboardButton(text="Комиссия биржи", callback_data="commission_fee")],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Назад", callback_data="main_settings"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					back_to_risk_management = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Назад", callback_data="risk_management"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					commission_fee = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Да", callback_data="Yes_commission_fee"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Нет", callback_data="No_commission_fee"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Назад", callback_data="risk_management"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# conditions
 | 
				
			||||||
 | 
					conditions = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Таймер для старта", callback_data="start_timer"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Таймер для остановки", callback_data="stop_timer"
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Назад", callback_data="main_settings"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					back_to_conditions = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Назад", callback_data="conditional_settings"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# SYMBOL
 | 
				
			||||||
 | 
					symbol = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Назад", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# POSITION
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					change_position = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Позиции", callback_data="change_position"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Открытые ордера", callback_data="open_orders"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [InlineKeyboardButton(text="Назад", callback_data="profile_bybit")],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def create_active_positions_keyboard(symbols: list):
 | 
				
			||||||
 | 
					    builder = InlineKeyboardBuilder()
 | 
				
			||||||
 | 
					    for sym, side in symbols:
 | 
				
			||||||
 | 
					        builder.button(text=f"{sym}:{side}", callback_data=f"get_position_{sym}_{side}")
 | 
				
			||||||
 | 
					    builder.button(text="Назад", callback_data="my_deals")
 | 
				
			||||||
 | 
					    builder.button(text="На главную", callback_data="profile_bybit")
 | 
				
			||||||
 | 
					    builder.adjust(2)
 | 
				
			||||||
 | 
					    return builder.as_markup()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def make_close_position_keyboard(
 | 
				
			||||||
 | 
					    symbol_pos: str, side: str, position_idx: int, qty: int
 | 
				
			||||||
 | 
					):
 | 
				
			||||||
 | 
					    return InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					        inline_keyboard=[
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                InlineKeyboardButton(
 | 
				
			||||||
 | 
					                    text="Закрыть позицию",
 | 
				
			||||||
 | 
					                    callback_data=f"close_position_{symbol_pos}_{side}_{position_idx}_{qty}",
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                InlineKeyboardButton(
 | 
				
			||||||
 | 
					                    text="Установить TP/SL",
 | 
				
			||||||
 | 
					                    callback_data=f"pos_tp_sl_{symbol_pos}_{position_idx}",
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                InlineKeyboardButton(text="Назад", callback_data="change_position"),
 | 
				
			||||||
 | 
					                InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def create_active_orders_keyboard(orders: list):
 | 
				
			||||||
 | 
					    builder = InlineKeyboardBuilder()
 | 
				
			||||||
 | 
					    for order, side in orders:
 | 
				
			||||||
 | 
					        builder.button(text=f"{order}", callback_data=f"get_order_{order}_{side}")
 | 
				
			||||||
 | 
					    builder.button(text="Назад", callback_data="my_deals")
 | 
				
			||||||
 | 
					    builder.button(text="На главную", callback_data="profile_bybit")
 | 
				
			||||||
 | 
					    builder.adjust(2)
 | 
				
			||||||
 | 
					    return builder.as_markup()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def make_close_orders_keyboard(symbol_order: str, order_id: str):
 | 
				
			||||||
 | 
					    return InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					        inline_keyboard=[
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                InlineKeyboardButton(
 | 
				
			||||||
 | 
					                    text="Закрыть ордер",
 | 
				
			||||||
 | 
					                    callback_data=f"close_order_{symbol_order}_{order_id}",
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            [
 | 
				
			||||||
 | 
					                InlineKeyboardButton(text="Назад", callback_data="open_orders"),
 | 
				
			||||||
 | 
					                InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# START TRADING
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					merged_start_trading = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Лонг", callback_data="long"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Шорт", callback_data="short"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [InlineKeyboardButton(text="Свитч", callback_data="switch")],
 | 
				
			||||||
 | 
					        [InlineKeyboardButton(text="Назад", callback_data="profile_bybit")],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					both_start_trading = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Лонг", callback_data="long"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Шорт", callback_data="short"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [InlineKeyboardButton(text="Назад", callback_data="profile_bybit")],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					switch_side = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Лонг", callback_data="switch_long"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Шорт", callback_data="switch_short"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="По направлению", callback_data="switch_direction"
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Противоположно", callback_data="switch_opposite"
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Назад", callback_data="start_trading"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					back_to_start_trading = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="Назад", callback_data="start_trading"),
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cancel_timer_merged = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Отменить таймер", callback_data="cancel_timer_merged"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cancel_timer_switch = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Отменить таймер", callback_data="cancel_timer_switch"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# STOP TRADING
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cancel_timer_stop = InlineKeyboardMarkup(
 | 
				
			||||||
 | 
					    inline_keyboard=[
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(
 | 
				
			||||||
 | 
					                text="Отменить таймер", callback_data="cancel_timer_stop"
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            InlineKeyboardButton(text="На главную", callback_data="profile_bybit"),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										9
									
								
								app/telegram/keyboards/reply.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/telegram/keyboards/reply.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					from aiogram.types import KeyboardButton, ReplyKeyboardMarkup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					profile = ReplyKeyboardMarkup(
 | 
				
			||||||
 | 
					    keyboard=[[KeyboardButton(text="Панель Bybit"), KeyboardButton(text="Профиль")],
 | 
				
			||||||
 | 
					              [KeyboardButton(text="Подключить платформу Bybit")]],
 | 
				
			||||||
 | 
					    resize_keyboard=True,
 | 
				
			||||||
 | 
					    one_time_keyboard=True,
 | 
				
			||||||
 | 
					    input_field_placeholder="Выберите пункт меню...",
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
							
								
								
									
										0
									
								
								app/telegram/states/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								app/telegram/states/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										51
									
								
								app/telegram/states/states.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								app/telegram/states/states.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					from aiogram.fsm.state import State, StatesGroup
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AddBybitApiState(StatesGroup):
 | 
				
			||||||
 | 
					    """States for adding Bybit API keys."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    api_key_state = State()
 | 
				
			||||||
 | 
					    api_secret_state = State()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AdditionalSettingsState(StatesGroup):
 | 
				
			||||||
 | 
					    """States for additional settings."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    leverage_state = State()
 | 
				
			||||||
 | 
					    leverage_to_buy_state = State()
 | 
				
			||||||
 | 
					    leverage_to_sell_state = State()
 | 
				
			||||||
 | 
					    quantity_state = State()
 | 
				
			||||||
 | 
					    martingale_factor_state = State()
 | 
				
			||||||
 | 
					    max_bets_in_series_state = State()
 | 
				
			||||||
 | 
					    limit_price_state = State()
 | 
				
			||||||
 | 
					    trigger_price_state = State()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RiskManagementState(StatesGroup):
 | 
				
			||||||
 | 
					    """States for risk management."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    take_profit_percent_state = State()
 | 
				
			||||||
 | 
					    stop_loss_percent_state = State()
 | 
				
			||||||
 | 
					    max_risk_percent_state = State()
 | 
				
			||||||
 | 
					    commission_fee_state = State()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ConditionalSettingsState(StatesGroup):
 | 
				
			||||||
 | 
					    """States for conditional settings."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    start_timer_state = State()
 | 
				
			||||||
 | 
					    stop_timer_state = State()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ChangingTheSymbolState(StatesGroup):
 | 
				
			||||||
 | 
					    """States for changing the symbol."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    symbol_state = State()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class SetTradingStopState(StatesGroup):
 | 
				
			||||||
 | 
					    """States for setting a trading stop."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    symbol_state = State()
 | 
				
			||||||
 | 
					    take_profit_state = State()
 | 
				
			||||||
 | 
					    stop_loss_state = State()
 | 
				
			||||||
							
								
								
									
										0
									
								
								app/telegram/tasks/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								app/telegram/tasks/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										77
									
								
								app/telegram/tasks/tasks.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								app/telegram/tasks/tasks.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
				
			|||||||
 | 
					import asyncio
 | 
				
			||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from logger_helper.logger_helper import LOGGING_CONFIG
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
 | 
					logger = logging.getLogger("tasks")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					user_start_tasks_merged = {}
 | 
				
			||||||
 | 
					user_start_tasks_switch = {}
 | 
				
			||||||
 | 
					user_stop_tasks = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def add_start_task_merged(user_id: int, task: asyncio.Task):
 | 
				
			||||||
 | 
					    """Add task to user_start_tasks dict"""
 | 
				
			||||||
 | 
					    if user_id in user_start_tasks_merged:
 | 
				
			||||||
 | 
					        old_task = user_start_tasks_merged[user_id]
 | 
				
			||||||
 | 
					        if not old_task.done():
 | 
				
			||||||
 | 
					            old_task.cancel()
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                await old_task
 | 
				
			||||||
 | 
					            except asyncio.CancelledError:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					    user_start_tasks_merged[user_id] = task
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def add_start_task_switch(user_id: int, task: asyncio.Task):
 | 
				
			||||||
 | 
					    """Add task to user_start_tasks dict"""
 | 
				
			||||||
 | 
					    if user_id in user_start_tasks_switch:
 | 
				
			||||||
 | 
					        old_task = user_start_tasks_switch[user_id]
 | 
				
			||||||
 | 
					        if not old_task.done():
 | 
				
			||||||
 | 
					            old_task.cancel()
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                await old_task
 | 
				
			||||||
 | 
					            except asyncio.CancelledError:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					    user_start_tasks_switch[user_id] = task
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def add_stop_task(user_id: int, task: asyncio.Task):
 | 
				
			||||||
 | 
					    """Add task to user_stop_tasks dict"""
 | 
				
			||||||
 | 
					    if user_id in user_stop_tasks:
 | 
				
			||||||
 | 
					        old_task = user_stop_tasks[user_id]
 | 
				
			||||||
 | 
					        if not old_task.done():
 | 
				
			||||||
 | 
					            old_task.cancel()
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                await old_task
 | 
				
			||||||
 | 
					            except asyncio.CancelledError:
 | 
				
			||||||
 | 
					                pass
 | 
				
			||||||
 | 
					    user_stop_tasks[user_id] = task
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def cancel_start_task_merged(user_id: int):
 | 
				
			||||||
 | 
					    """Cancel task from user_start_tasks dict"""
 | 
				
			||||||
 | 
					    if user_id in user_start_tasks_merged:
 | 
				
			||||||
 | 
					        task = user_start_tasks_merged[user_id]
 | 
				
			||||||
 | 
					        if not task.done():
 | 
				
			||||||
 | 
					            task.cancel()
 | 
				
			||||||
 | 
					        del user_start_tasks_merged[user_id]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def cancel_start_task_switch(user_id: int):
 | 
				
			||||||
 | 
					    """Cancel task from user_start_tasks dict"""
 | 
				
			||||||
 | 
					    if user_id in user_start_tasks_switch:
 | 
				
			||||||
 | 
					        task = user_start_tasks_switch[user_id]
 | 
				
			||||||
 | 
					        if not task.done():
 | 
				
			||||||
 | 
					            task.cancel()
 | 
				
			||||||
 | 
					        del user_start_tasks_switch[user_id]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def cancel_stop_task(user_id: int):
 | 
				
			||||||
 | 
					    """Cancel task from user_stop_tasks dict"""
 | 
				
			||||||
 | 
					    if user_id in user_stop_tasks:
 | 
				
			||||||
 | 
					        task = user_stop_tasks[user_id]
 | 
				
			||||||
 | 
					        if not task.done():
 | 
				
			||||||
 | 
					            task.cancel()
 | 
				
			||||||
 | 
					        del user_stop_tasks[user_id]
 | 
				
			||||||
							
								
								
									
										34
									
								
								config.py
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								config.py
									
									
									
									
									
								
							@@ -1,9 +1,31 @@
 | 
				
			|||||||
from dotenv import load_dotenv, find_dotenv
 | 
					 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
 | 
					from dotenv import load_dotenv, find_dotenv
 | 
				
			||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
env_file = find_dotenv()
 | 
					from logger_helper.logger_helper import LOGGING_CONFIG
 | 
				
			||||||
load_dotenv(env_file)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
TOKEN_TG_BOT_1 = os.getenv('TOKEN_TELEGRAM_BOT_1')
 | 
					logging.config.dictConfig(LOGGING_CONFIG)
 | 
				
			||||||
TOKEN_TG_BOT_2 = os.getenv('TOKEN_TELEGRAM_BOT_2')
 | 
					logger = logging.getLogger("config")
 | 
				
			||||||
TOKEN_TG_BOT_3 = os.getenv('TOKEN_TELEGRAM_BOT_3')
 | 
					
 | 
				
			||||||
 | 
					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}"
 | 
				
			||||||
							
								
								
									
										24
									
								
								database/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								database/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					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 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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async_session = async_sessionmaker(async_engine, class_=AsyncSession, expire_on_commit=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def init_db():
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        async with async_engine.begin() as conn:
 | 
				
			||||||
 | 
					            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)
 | 
				
			||||||
							
								
								
									
										190
									
								
								database/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										190
									
								
								database/models.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,190 @@
 | 
				
			|||||||
 | 
					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.orm import relationship
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Base = declarative_base(cls=AsyncAttrs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class User(Base):
 | 
				
			||||||
 | 
					    """User model."""
 | 
				
			||||||
 | 
					    __tablename__ = "users"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    id = Column(Integer, primary_key=True, autoincrement=True)
 | 
				
			||||||
 | 
					    tg_id = Column(BigInteger, nullable=False, unique=True)
 | 
				
			||||||
 | 
					    username = Column(String, nullable=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    user_api = relationship("UserApi",
 | 
				
			||||||
 | 
					                            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_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_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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					    api_key = Column(String, nullable=False)
 | 
				
			||||||
 | 
					    api_secret = Column(String, nullable=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    user = relationship("User", back_populates="user_api")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					    symbol = Column(String, nullable=False, default="BTCUSDT")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    user = relationship("User", back_populates="user_symbol")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					    trade_mode = Column(String, nullable=False, default="Merged_Single")
 | 
				
			||||||
 | 
					    order_type = Column(String, nullable=False, default="Market")
 | 
				
			||||||
 | 
					    conditional_order_type = Column(String, nullable=False, default="Market")
 | 
				
			||||||
 | 
					    limit_price = Column(Float, nullable=False, default=0.0)
 | 
				
			||||||
 | 
					    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")
 | 
				
			||||||
 | 
					    leverage_to_buy = Column(String, nullable=False, default="10")
 | 
				
			||||||
 | 
					    leverage_to_sell = Column(String, nullable=False, default="10")
 | 
				
			||||||
 | 
					    order_quantity = Column(Float, nullable=False, default=5.0)
 | 
				
			||||||
 | 
					    martingale_factor = Column(Float, nullable=False, default=1.0)
 | 
				
			||||||
 | 
					    max_bets_in_series = Column(Integer, nullable=False, default=1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    user = relationship("User", back_populates="user_additional_settings")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					    take_profit_percent = Column(Integer, nullable=False, default=1)
 | 
				
			||||||
 | 
					    stop_loss_percent = Column(Integer, nullable=False, default=1)
 | 
				
			||||||
 | 
					    max_risk_percent = Column(Integer, nullable=False, default=100)
 | 
				
			||||||
 | 
					    commission_fee = Column(String, nullable=False, default="Yes_commission_fee")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    user = relationship("User", back_populates="user_risk_management")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    timer_start = Column(Integer, nullable=False, default=0)
 | 
				
			||||||
 | 
					    timer_end = Column(Integer, nullable=False, default=0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    user = relationship("User", back_populates="user_conditional_settings")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					    current_step = Column(Integer, nullable=True)
 | 
				
			||||||
 | 
					    symbol = Column(String, nullable=True)
 | 
				
			||||||
 | 
					    trade_mode = Column(String, nullable=True)
 | 
				
			||||||
 | 
					    trading_type = Column(String, nullable=True)
 | 
				
			||||||
 | 
					    margin_type = Column(String, nullable=True)
 | 
				
			||||||
 | 
					    order_type = Column(String, nullable=True)
 | 
				
			||||||
 | 
					    conditional_order_type = Column(String, nullable=True)
 | 
				
			||||||
 | 
					    leverage = Column(String, nullable=True)
 | 
				
			||||||
 | 
					    leverage_to_buy = Column(String, nullable=True)
 | 
				
			||||||
 | 
					    leverage_to_sell = Column(String, nullable=True)
 | 
				
			||||||
 | 
					    last_side = Column(String, nullable=True)
 | 
				
			||||||
 | 
					    closed_side = Column(String, nullable=True)
 | 
				
			||||||
 | 
					    order_quantity = Column(Float, nullable=True)
 | 
				
			||||||
 | 
					    martingale_factor = Column(Float, nullable=True)
 | 
				
			||||||
 | 
					    max_bets_in_series = Column(Integer, nullable=True)
 | 
				
			||||||
 | 
					    take_profit_percent = Column(Integer, nullable=True)
 | 
				
			||||||
 | 
					    stop_loss_percent = Column(Integer, nullable=True)
 | 
				
			||||||
 | 
					    max_risk_percent = Column(Integer, nullable=True)
 | 
				
			||||||
 | 
					    switch_side_mode = Column(Boolean, nullable=True)
 | 
				
			||||||
 | 
					    limit_price = Column(Float, nullable=True)
 | 
				
			||||||
 | 
					    trigger_price = Column(Float, nullable=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    user = relationship("User", back_populates="user_deals")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    __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)
 | 
				
			||||||
 | 
					    symbol = Column(String, nullable=True)
 | 
				
			||||||
 | 
					    auto_trading = Column(Boolean, nullable=True)
 | 
				
			||||||
 | 
					    side = Column(String, nullable=True)
 | 
				
			||||||
 | 
					    fee = Column(Float, nullable=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    user = relationship("User", back_populates="user_auto_trading")
 | 
				
			||||||
							
								
								
									
										1366
									
								
								database/request.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1366
									
								
								database/request.py
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										0
									
								
								logger_helper/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								logger_helper/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -2,15 +2,18 @@ import os
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
current_directory = os.path.dirname(os.path.abspath(__file__))
 | 
					current_directory = os.path.dirname(os.path.abspath(__file__))
 | 
				
			||||||
log_directory = os.path.join(current_directory, 'loggers')
 | 
					log_directory = os.path.join(current_directory, 'loggers')
 | 
				
			||||||
 | 
					error_log_directory = os.path.join(log_directory, 'errors')
 | 
				
			||||||
os.makedirs(log_directory, exist_ok=True)
 | 
					os.makedirs(log_directory, exist_ok=True)
 | 
				
			||||||
 | 
					os.makedirs(error_log_directory, exist_ok=True)
 | 
				
			||||||
log_filename = os.path.join(log_directory, 'app.log')
 | 
					log_filename = os.path.join(log_directory, 'app.log')
 | 
				
			||||||
 | 
					error_log_filename = os.path.join(error_log_directory, 'error.log')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
LOGGING_CONFIG = {
 | 
					LOGGING_CONFIG = {
 | 
				
			||||||
    "version": 1,
 | 
					    "version": 1,
 | 
				
			||||||
    "disable_existing_loggers": False,
 | 
					    "disable_existing_loggers": False,
 | 
				
			||||||
    "formatters": {
 | 
					    "formatters": {
 | 
				
			||||||
        "default": {
 | 
					        "default": {
 | 
				
			||||||
            "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
 | 
					            "format": "TELEGRAM: %(asctime)s - %(name)s - %(levelname)s - %(message)s",
 | 
				
			||||||
            "datefmt": "%Y-%m-%d %H:%M:%S",  # Формат даты
 | 
					            "datefmt": "%Y-%m-%d %H:%M:%S",  # Формат даты
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
@@ -23,90 +26,122 @@ LOGGING_CONFIG = {
 | 
				
			|||||||
            "backupCount": 7,  # Количество сохраняемых архивов (0 - не сохранять)
 | 
					            "backupCount": 7,  # Количество сохраняемых архивов (0 - не сохранять)
 | 
				
			||||||
            "formatter": "default",
 | 
					            "formatter": "default",
 | 
				
			||||||
            "encoding": "utf-8",
 | 
					            "encoding": "utf-8",
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "error_file": {
 | 
				
			||||||
 | 
					            "class": "logging.handlers.TimedRotatingFileHandler",
 | 
				
			||||||
 | 
					            "filename": error_log_filename,
 | 
				
			||||||
 | 
					            "when": "midnight",
 | 
				
			||||||
 | 
					            "interval": 1,
 | 
				
			||||||
 | 
					            "backupCount": 30,
 | 
				
			||||||
 | 
					            "formatter": "default",
 | 
				
			||||||
 | 
					            "encoding": "utf-8",
 | 
				
			||||||
 | 
					            "level": "ERROR",
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "console": {
 | 
					        "console": {
 | 
				
			||||||
            "class": "logging.StreamHandler",
 | 
					            "class": "logging.StreamHandler",
 | 
				
			||||||
            "formatter": "default",
 | 
					            "formatter": "default",
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "loggers": {
 | 
					    "loggers": {
 | 
				
			||||||
        "main": {
 | 
					        "run": {
 | 
				
			||||||
            "handlers": ["console", "timed_rotating_file"],
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "config": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "common": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "handlers_main": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "database": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
 | 
					            "level": "DEBUG",
 | 
				
			||||||
 | 
					            "propagate": False,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "request": {
 | 
				
			||||||
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
            "level": "DEBUG",
 | 
					            "level": "DEBUG",
 | 
				
			||||||
            "propagate": False,
 | 
					            "propagate": False,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "add_bybit_api": {
 | 
					        "add_bybit_api": {
 | 
				
			||||||
            "handlers": ["console", "timed_rotating_file"],
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
            "level": "DEBUG",
 | 
					            "level": "DEBUG",
 | 
				
			||||||
            "propagate": False,
 | 
					            "propagate": False,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "balance": {
 | 
					        "profile_tg": {
 | 
				
			||||||
            "handlers": ["console", "timed_rotating_file"],
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
            "level": "DEBUG",
 | 
					            "level": "DEBUG",
 | 
				
			||||||
            "propagate": False,
 | 
					            "propagate": False,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "functions": {
 | 
					        "settings": {
 | 
				
			||||||
            "handlers": ["console", "timed_rotating_file"],
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
            "level": "DEBUG",
 | 
					            "level": "DEBUG",
 | 
				
			||||||
            "propagate": False,
 | 
					            "propagate": False,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "futures": {
 | 
					        "additional_settings": {
 | 
				
			||||||
            "handlers": ["console", "timed_rotating_file"],
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
            "level": "DEBUG",
 | 
					            "level": "DEBUG",
 | 
				
			||||||
            "propagate": False,
 | 
					            "propagate": False,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "get_valid_symbol": {
 | 
					        "helper_functions": {
 | 
				
			||||||
            "handlers": ["console", "timed_rotating_file"],
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
            "level": "DEBUG",
 | 
					            "level": "DEBUG",
 | 
				
			||||||
            "propagate": False,
 | 
					            "propagate": False,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "min_qty": {
 | 
					        "risk_management": {
 | 
				
			||||||
            "handlers": ["console", "timed_rotating_file"],
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
            "level": "DEBUG",
 | 
					            "level": "DEBUG",
 | 
				
			||||||
            "propagate": False,
 | 
					            "propagate": False,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "price_symbol": {
 | 
					        "start_trading": {
 | 
				
			||||||
            "handlers": ["console", "timed_rotating_file"],
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
            "level": "DEBUG",
 | 
					            "level": "DEBUG",
 | 
				
			||||||
            "propagate": False,
 | 
					            "propagate": False,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "requests": {
 | 
					        "stop_trading": {
 | 
				
			||||||
            "handlers": ["console", "timed_rotating_file"],
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
            "level": "DEBUG",
 | 
					            "level": "DEBUG",
 | 
				
			||||||
            "propagate": False,
 | 
					            "propagate": False,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "handlers": {
 | 
					        "changing_the_symbol": {
 | 
				
			||||||
            "handlers": ["console", "timed_rotating_file"],
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
            "level": "DEBUG",
 | 
					            "level": "DEBUG",
 | 
				
			||||||
            "propagate": False,
 | 
					            "propagate": False,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "condition_settings": {
 | 
					        "conditional_settings": {
 | 
				
			||||||
            "handlers": ["console", "timed_rotating_file"],
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
            "level": "DEBUG",
 | 
					            "level": "DEBUG",
 | 
				
			||||||
            "propagate": False,
 | 
					            "propagate": False,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "main_settings": {
 | 
					        "get_positions_handlers": {
 | 
				
			||||||
            "handlers": ["console", "timed_rotating_file"],
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
            "level": "DEBUG",
 | 
					            "level": "DEBUG",
 | 
				
			||||||
            "propagate": False,
 | 
					            "propagate": False,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "risk_management_settings": {
 | 
					        "close_orders": {
 | 
				
			||||||
            "handlers": ["console", "timed_rotating_file"],
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
            "level": "DEBUG",
 | 
					            "level": "DEBUG",
 | 
				
			||||||
            "propagate": False,
 | 
					            "propagate": False,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "models": {
 | 
					        "tp_sl_handlers": {
 | 
				
			||||||
            "handlers": ["console", "timed_rotating_file"],
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
            "level": "DEBUG",
 | 
					 | 
				
			||||||
            "propagate": False,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "bybit_ws": {
 | 
					 | 
				
			||||||
            "handlers": ["console", "timed_rotating_file"],
 | 
					 | 
				
			||||||
            "level": "DEBUG",
 | 
					            "level": "DEBUG",
 | 
				
			||||||
            "propagate": False,
 | 
					            "propagate": False,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "tasks": {
 | 
					        "tasks": {
 | 
				
			||||||
            "handlers": ["console", "timed_rotating_file"],
 | 
					            "handlers": ["console", "timed_rotating_file", "error_file"],
 | 
				
			||||||
            "level": "DEBUG",
 | 
					            "level": "DEBUG",
 | 
				
			||||||
            "propagate": False,
 | 
					            "propagate": False,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								logger_helper/loggers/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								logger_helper/loggers/.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1 +0,0 @@
 | 
				
			|||||||
*.log
 | 
					 | 
				
			||||||
@@ -4,7 +4,9 @@ aiohappyeyeballs==2.6.1
 | 
				
			|||||||
aiohttp==3.12.15
 | 
					aiohttp==3.12.15
 | 
				
			||||||
aiosignal==1.4.0
 | 
					aiosignal==1.4.0
 | 
				
			||||||
aiosqlite==0.21.0
 | 
					aiosqlite==0.21.0
 | 
				
			||||||
 | 
					alembic==1.16.5
 | 
				
			||||||
annotated-types==0.7.0
 | 
					annotated-types==0.7.0
 | 
				
			||||||
 | 
					asyncpg==0.30.0
 | 
				
			||||||
attrs==25.3.0
 | 
					attrs==25.3.0
 | 
				
			||||||
black==25.1.0
 | 
					black==25.1.0
 | 
				
			||||||
certifi==2025.8.3
 | 
					certifi==2025.8.3
 | 
				
			||||||
@@ -20,7 +22,9 @@ greenlet==3.2.4
 | 
				
			|||||||
idna==3.10
 | 
					idna==3.10
 | 
				
			||||||
isort==6.0.1
 | 
					isort==6.0.1
 | 
				
			||||||
magic-filter==1.0.12
 | 
					magic-filter==1.0.12
 | 
				
			||||||
 | 
					Mako==1.3.10
 | 
				
			||||||
mando==0.7.1
 | 
					mando==0.7.1
 | 
				
			||||||
 | 
					MarkupSafe==3.0.2
 | 
				
			||||||
mccabe==0.7.0
 | 
					mccabe==0.7.0
 | 
				
			||||||
multidict==6.6.4
 | 
					multidict==6.6.4
 | 
				
			||||||
mypy_extensions==1.1.0
 | 
					mypy_extensions==1.1.0
 | 
				
			||||||
@@ -29,10 +33,12 @@ packaging==25.0
 | 
				
			|||||||
pathspec==0.12.1
 | 
					pathspec==0.12.1
 | 
				
			||||||
platformdirs==4.4.0
 | 
					platformdirs==4.4.0
 | 
				
			||||||
propcache==0.3.2
 | 
					propcache==0.3.2
 | 
				
			||||||
 | 
					psycopg==3.2.10
 | 
				
			||||||
 | 
					psycopg-binary==3.2.10
 | 
				
			||||||
pybit==5.11.0
 | 
					pybit==5.11.0
 | 
				
			||||||
pycodestyle==2.14.0
 | 
					pycodestyle==2.14.0
 | 
				
			||||||
pycryptodome==3.23.0
 | 
					pycryptodome==3.23.0
 | 
				
			||||||
pydantic==2.11.7
 | 
					pydantic==2.11.9
 | 
				
			||||||
pydantic_core==2.33.2
 | 
					pydantic_core==2.33.2
 | 
				
			||||||
pyflakes==3.4.0
 | 
					pyflakes==3.4.0
 | 
				
			||||||
python-dotenv==1.1.1
 | 
					python-dotenv==1.1.1
 | 
				
			||||||
@@ -42,7 +48,8 @@ requests==2.32.5
 | 
				
			|||||||
six==1.17.0
 | 
					six==1.17.0
 | 
				
			||||||
SQLAlchemy==2.0.43
 | 
					SQLAlchemy==2.0.43
 | 
				
			||||||
typing-inspection==0.4.1
 | 
					typing-inspection==0.4.1
 | 
				
			||||||
typing_extensions==4.14.1
 | 
					typing_extensions==4.15.0
 | 
				
			||||||
 | 
					uliweb-alembic==0.6.9
 | 
				
			||||||
urllib3==2.5.0
 | 
					urllib3==2.5.0
 | 
				
			||||||
websocket-client==1.8.0
 | 
					websocket-client==1.8.0
 | 
				
			||||||
yarl==1.20.1
 | 
					yarl==1.20.1
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										56
									
								
								run.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								run.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					import asyncio
 | 
				
			||||||
 | 
					import contextlib
 | 
				
			||||||
 | 
					import logging.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from aiogram import Bot, Dispatcher
 | 
				
			||||||
 | 
					from aiogram.fsm.storage.redis import RedisStorage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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)
 | 
				
			||||||
 | 
					logger = logging.getLogger("run")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async def main():
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    The main function of launching the bot.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Performs database initialization, creation of bot and dispatcher objects,
 | 
				
			||||||
 | 
					    then it triggers the long polling event.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Logs important events and errors.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    try:
 | 
				
			||||||
 | 
					        await init_db()
 | 
				
			||||||
 | 
					        bot = Bot(token=BOT_TOKEN)
 | 
				
			||||||
 | 
					        storage = RedisStorage.from_url("redis://localhost:6379")
 | 
				
			||||||
 | 
					        dp = Dispatcher(storage=storage)
 | 
				
			||||||
 | 
					        dp.include_router(router)
 | 
				
			||||||
 | 
					        web_socket = WebSocketBot(telegram_bot=bot)
 | 
				
			||||||
 | 
					        await web_socket.clear_user_sockets()
 | 
				
			||||||
 | 
					        ws_task = asyncio.create_task(web_socket.run_user_check_loop())
 | 
				
			||||||
 | 
					        tg_task = asyncio.create_task(dp.start_polling(bot))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            logger.info("Bot started")
 | 
				
			||||||
 | 
					            await asyncio.gather(ws_task, tg_task)
 | 
				
			||||||
 | 
					        except Exception as e:
 | 
				
			||||||
 | 
					            logger.error("Bot stopped with error: %s", e)
 | 
				
			||||||
 | 
					        finally:
 | 
				
			||||||
 | 
					            for task in (ws_task, tg_task):
 | 
				
			||||||
 | 
					                task.cancel()
 | 
				
			||||||
 | 
					            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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
					    asyncio.run(main())
 | 
				
			||||||
		Reference in New Issue
	
	Block a user