1
0
forked from kodorvan/stcs

Compare commits

...

36 Commits

Author SHA1 Message Date
algizn97
dd63f4c015 added 2025-08-23 14:41:23 +05:00
algizn97
6267663015 Added States.py 2025-08-23 14:34:38 +05:00
algizn97
89ea511072 Update 2025-08-23 14:34:19 +05:00
algizn97
afe61ea7d6 Added documentation 2025-08-23 14:33:33 +05:00
algizn97
2da06481f7 Update 2025-08-23 14:32:11 +05:00
algizn97
6c3f13f372 Update 2025-08-23 14:32:04 +05:00
algizn97
1ec9732607 Update loggers 2025-08-23 14:31:45 +05:00
algizn97
c7da20d577 Update 2025-08-23 14:31:09 +05:00
algizn97
8a2497bcac Added logger_helper.py 2025-08-23 14:30:48 +05:00
algizn97
0746490786 Delete func_min_qty.py and logs.py 2025-08-23 14:30:24 +05:00
algizn97
f895c19b14 fixed the method of getting the timer value. 2025-08-22 16:57:59 +05:00
algizn97
c4b35be053 Updated the risk management function 2025-08-22 16:49:17 +05:00
algizn97
54667db29b Added a martingale step 2025-08-22 16:48:17 +05:00
algizn97
12a00a1f3a Added a martingale step 2025-08-22 16:48:09 +05:00
algizn97
6ec99dc9a7 Update functions 2025-08-22 16:47:33 +05:00
algizn97
812920f46d The timer function has been updated 2025-08-22 13:56:56 +05:00
algizn97
cd7180c3d7 Added the function of getting and updating data in the database for the UserTimer table 2025-08-22 13:55:50 +05:00
algizn97
fc381ae6c2 Added timer 2025-08-22 13:54:13 +05:00
algizn97
8ab308d4b9 Added keyboards 2025-08-22 13:53:48 +05:00
algizn97
0c4204fb6e Added UserTimer 2025-08-22 13:53:17 +05:00
algizn97
c9c6a5b7f0 Fixed filename 2025-08-22 13:52:50 +05:00
algizn97
4ca6e1fb2c Added dp 2025-08-22 13:52:31 +05:00
algizn97
1a20c1a9d2 Added keyboard 2025-08-22 13:52:20 +05:00
algizn97
eaf8458835 Update 2025-08-21 16:10:01 +05:00
algizn97
f8cedf4cb4 Fixed 2025-08-21 16:09:32 +05:00
algizn97
d0577f163b Added get user trades 2025-08-21 14:22:54 +05:00
algizn97
8293c44864 Added USER DEALS 2025-08-21 14:22:34 +05:00
algizn97
c1a9f16faa Added commission_fee 2025-08-21 13:47:19 +05:00
algizn97
99b51d4cc0 Added limit and market orders functions 2025-08-21 13:46:12 +05:00
algizn97
b46d8d7af9 Update handlers.py 2025-08-21 13:41:58 +05:00
algizn97
c8b0dad7c2 Added commission_fee and order_type functions 2025-08-21 13:39:43 +05:00
algizn97
7f53caaac6 Added commission_fee and entry_order_type 2025-08-21 13:38:50 +05:00
algizn97
fe1c0b16ce Added reply keyboard (base_buttons_markup) 2025-08-21 13:37:58 +05:00
algizn97
4ebe7399ba Update function 2025-08-21 13:36:11 +05:00
algizn97
2dc639d59a Added market order and limit order 2025-08-21 13:35:42 +05:00
algizn97
a6ba949061 Added buttons To the main page, Open a deal, My Deals and select the input type 2025-08-21 13:34:56 +05:00
35 changed files with 2019 additions and 483 deletions

View File

@@ -1 +1,3 @@
TOKEN_TELEGRAM_BOT= TOKEN_TELEGRAM_BOT_1=
TOKEN_TELEGRAM_BOT_2=
TOKEN_TELEGRAM_BOT_3=

3
.gitignore vendored
View File

@@ -7,6 +7,3 @@ __pycache__/
env/ env/
venv/ venv/
.venv/ .venv/
requirements.txt

5
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

610
.idea/dbnavigator.xml generated Normal file
View File

@@ -0,0 +1,610 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DBNavigator.Project.DDLFileAttachmentManager">
<mappings />
<preferences />
</component>
<component name="DBNavigator.Project.DataEditorManager">
<record-view-column-sorting-type value="BY_INDEX" />
<value-preview-text-wrapping value="false" />
<value-preview-pinned value="false" />
</component>
<component name="DBNavigator.Project.DatabaseAssistantManager">
<assistants>
<assistant-state connection-id="539567a8-9e28-4dbe-b015-5c44ee3cfda5" default-profile-name="" selected-conversation-id="" assistant-type="GENERIC" availability="UNAVAILABLE" acknowledgement="NONE">
<conversations />
</assistant-state>
<assistant-state connection-id="0f4bac5e-2797-4db3-bad2-53cbf8cd2b20" default-profile-name="" selected-conversation-id="" assistant-type="GENERIC" availability="UNAVAILABLE" acknowledgement="NONE">
<conversations />
</assistant-state>
<assistant-state connection-id="be96bc1e-ca0e-4a7f-a163-9127fdee15c6" default-profile-name="" selected-conversation-id="" assistant-type="GENERIC" availability="UNAVAILABLE" acknowledgement="NONE">
<conversations />
</assistant-state>
<assistant-state connection-id="8d8de23e-fc36-46b8-a593-c47740aca91b" default-profile-name="" selected-conversation-id="" assistant-type="GENERIC" availability="UNAVAILABLE" acknowledgement="NONE">
<conversations />
</assistant-state>
<assistant-state connection-id="d0ab0625-5cb0-46d3-9aa4-4d4b02bd8890" default-profile-name="" selected-conversation-id="" assistant-type="GENERIC" availability="UNAVAILABLE" acknowledgement="NONE">
<conversations />
</assistant-state>
<assistant-state connection-id="4a35d891-e512-4ca8-85c4-6f6647deefbc" default-profile-name="" selected-conversation-id="" assistant-type="GENERIC" availability="UNAVAILABLE" acknowledgement="NONE">
<conversations />
</assistant-state>
<assistant-state connection-id="431aa026-563e-40e3-a8f7-50e01f1f1af7" default-profile-name="" selected-conversation-id="" assistant-type="GENERIC" availability="UNAVAILABLE" acknowledgement="NONE">
<conversations />
</assistant-state>
<assistant-state connection-id="f5fd2325-996b-4e52-a9f8-b1dcee5d149d" default-profile-name="" selected-conversation-id="" assistant-type="GENERIC" availability="UNAVAILABLE" acknowledgement="NONE">
<conversations />
</assistant-state>
</assistants>
</component>
<component name="DBNavigator.Project.DatabaseBrowserManager">
<autoscroll-to-editor value="false" />
<autoscroll-from-editor value="true" />
<show-object-properties value="false" />
<loaded-nodes />
</component>
<component name="DBNavigator.Project.DatabaseConsoleManager">
<connection id="0f4bac5e-2797-4db3-bad2-53cbf8cd2b20">
<console name="Connection" type="STANDARD" schema="main" session="Main" />
</connection>
</component>
<component name="DBNavigator.Project.DatabaseEditorStateManager">
<last-used-providers />
</component>
<component name="DBNavigator.Project.DatabaseFileManager">
<open-files />
</component>
<component name="DBNavigator.Project.DatabaseSessionManager">
<connection id="0f4bac5e-2797-4db3-bad2-53cbf8cd2b20" />
</component>
<component name="DBNavigator.Project.DatasetFilterManager">
<filter-actions connection-id="0f4bac5e-2797-4db3-bad2-53cbf8cd2b20" dataset="main.user_bybit_api" active-filter-id="EMPTY_FILTER" />
</component>
<component name="DBNavigator.Project.ExecutionManager">
<retain-sticky-names value="false" />
</component>
<component name="DBNavigator.Project.MethodExecutionManager">
<method-browser />
<execution-history>
<group-entries value="true" />
<execution-inputs />
</execution-history>
<execution-variables-cache />
</component>
<component name="DBNavigator.Project.ObjectQuickFilterManager">
<last-used-operator value="EQUAL" />
<filters />
</component>
<component name="DBNavigator.Project.Settings">
<connections>
<connection source-id="" id="0f4bac5e-2797-4db3-bad2-53cbf8cd2b20" active="true" signed="true">
<database>
<name value="Connection" />
<description value="" />
<database-type value="SQLITE" />
<config-type value="BASIC" />
<database-version value="3.5" />
<driver-source value="BUNDLED" />
<driver-library value="" />
<driver value="" />
<url-type value="FILE" />
<host value="" />
<port value="" />
<database value="" />
<tns-folder value="" />
<tns-profile value="" />
<server-type value="" />
<protocol value="" />
<url-parameters />
<files>
<file path="C:\Users\Algiz_N\PycharmProjects\stcs\db.sqlite3" schema="main" />
</files>
<type value="NONE" />
<user value="" />
<token-type value="" />
<token-config-file value="" />
<token-profile value="" />
<session-user value="" />
</database>
<properties>
<auto-commit value="true" />
</properties>
<ssh-settings>
<active value="false" />
<proxy-host value="" />
<proxy-port value="22" />
<proxy-user value="" />
<auth-type value="PASSWORD" />
<key-file value="" />
</ssh-settings>
<ssl-settings>
<active value="false" />
<certificate-authority-file value="" />
<client-certificate-file value="" />
<client-key-file value="" />
</ssl-settings>
<details>
<charset value="UTF-8" />
<session-management value="true" />
<ddl-file-binding value="true" />
<database-logging value="true" />
<connect-automatically value="true" />
<restore-workspace value="true" />
<restore-workspace-deep value="false" />
<environment-type value="default" />
<connectivity-timeout value="30" />
<idle-time-to-disconnect value="30" />
<idle-time-to-disconnect-pool value="5" />
<credential-expiry-time value="10" />
<max-connection-pool-size value="7" />
<alternative-statement-delimiter value="" />
</details>
<debugger>
<compile-dependencies value="true" />
<tcp-driver-tunneling value="false" />
<tcp-host-address value="" />
<tcp-port-from value="4000" />
<tcp-port-to value="4999" />
<debugger-type value="JDBC" />
</debugger>
<object-filters hide-empty-schemas="false" hide-pseudo-columns="false" hide-audit-columns="false">
<object-filters />
<object-type-filter use-master-settings="true">
<object-type name="SCHEMA" enabled="true" />
<object-type name="USER" enabled="true" />
<object-type name="ROLE" enabled="true" />
<object-type name="PRIVILEGE" enabled="true" />
<object-type name="CHARSET" enabled="true" />
<object-type name="TABLE" enabled="true" />
<object-type name="VIEW" enabled="true" />
<object-type name="JSON_VIEW" enabled="true" />
<object-type name="MATERIALIZED_VIEW" enabled="true" />
<object-type name="NESTED_TABLE" enabled="true" />
<object-type name="COLUMN" enabled="true" />
<object-type name="INDEX" enabled="true" />
<object-type name="CONSTRAINT" enabled="true" />
<object-type name="DATASET_TRIGGER" enabled="true" />
<object-type name="DATABASE_TRIGGER" enabled="true" />
<object-type name="SYNONYM" enabled="true" />
<object-type name="SEQUENCE" enabled="true" />
<object-type name="PROCEDURE" enabled="true" />
<object-type name="FUNCTION" enabled="true" />
<object-type name="PACKAGE" enabled="true" />
<object-type name="TYPE" enabled="true" />
<object-type name="TYPE_ATTRIBUTE" enabled="true" />
<object-type name="ARGUMENT" enabled="true" />
<object-type name="JAVA_CLASS" enabled="true" />
<object-type name="JAVA_FIELD" enabled="true" />
<object-type name="JAVA_METHOD" enabled="true" />
<object-type name="JAVA_RESOURCE" enabled="true" />
<object-type name="DIMENSION" enabled="true" />
<object-type name="CLUSTER" enabled="true" />
<object-type name="DBLINK" enabled="true" />
<object-type name="CREDENTIAL" enabled="true" />
<object-type name="AI_PROFILE" enabled="true" />
</object-type-filter>
</object-filters>
</connection>
</connections>
<browser-settings>
<general>
<display-mode value="TABBED" />
<navigation-history-size value="100" />
<show-object-details value="false" />
<enable-sticky-paths value="true" />
<enable-quick-filters value="false" />
</general>
<filters>
<object-type-filter>
<object-type name="SCHEMA" enabled="true" />
<object-type name="USER" enabled="true" />
<object-type name="ROLE" enabled="true" />
<object-type name="PRIVILEGE" enabled="true" />
<object-type name="CHARSET" enabled="true" />
<object-type name="TABLE" enabled="true" />
<object-type name="VIEW" enabled="true" />
<object-type name="JSON_VIEW" enabled="true" />
<object-type name="MATERIALIZED_VIEW" enabled="true" />
<object-type name="NESTED_TABLE" enabled="true" />
<object-type name="COLUMN" enabled="true" />
<object-type name="INDEX" enabled="true" />
<object-type name="CONSTRAINT" enabled="true" />
<object-type name="DATASET_TRIGGER" enabled="true" />
<object-type name="DATABASE_TRIGGER" enabled="true" />
<object-type name="SYNONYM" enabled="true" />
<object-type name="SEQUENCE" enabled="true" />
<object-type name="PROCEDURE" enabled="true" />
<object-type name="FUNCTION" enabled="true" />
<object-type name="PACKAGE" enabled="true" />
<object-type name="TYPE" enabled="true" />
<object-type name="TYPE_ATTRIBUTE" enabled="true" />
<object-type name="ARGUMENT" enabled="true" />
<object-type name="JAVA_CLASS" enabled="true" />
<object-type name="JAVA_FIELD" enabled="true" />
<object-type name="JAVA_METHOD" enabled="true" />
<object-type name="JAVA_RESOURCE" enabled="true" />
<object-type name="DIMENSION" enabled="true" />
<object-type name="CLUSTER" enabled="true" />
<object-type name="DBLINK" enabled="true" />
<object-type name="CREDENTIAL" enabled="true" />
<object-type name="AI_PROFILE" enabled="true" />
</object-type-filter>
</filters>
<sorting>
<object-type name="COLUMN" sorting-type="NAME" />
<object-type name="FUNCTION" sorting-type="NAME" />
<object-type name="PROCEDURE" sorting-type="NAME" />
<object-type name="ARGUMENT" sorting-type="POSITION" />
<object-type name="TYPE ATTRIBUTE" sorting-type="POSITION" />
</sorting>
<default-editors>
<object-type name="VIEW" editor-type="SELECTION" />
<object-type name="PACKAGE" editor-type="SELECTION" />
<object-type name="TYPE" editor-type="SELECTION" />
</default-editors>
</browser-settings>
<navigation-settings>
<lookup-filters>
<lookup-objects>
<object-type name="SCHEMA" enabled="true" />
<object-type name="USER" enabled="false" />
<object-type name="ROLE" enabled="false" />
<object-type name="PRIVILEGE" enabled="false" />
<object-type name="CHARSET" enabled="false" />
<object-type name="TABLE" enabled="true" />
<object-type name="VIEW" enabled="true" />
<object-type name="JSON VIEW" enabled="true" />
<object-type name="MATERIALIZED VIEW" enabled="true" />
<object-type name="INDEX" enabled="true" />
<object-type name="CONSTRAINT" enabled="true" />
<object-type name="DATASET TRIGGER" enabled="true" />
<object-type name="DATABASE TRIGGER" enabled="true" />
<object-type name="SYNONYM" enabled="false" />
<object-type name="SEQUENCE" enabled="true" />
<object-type name="PROCEDURE" enabled="true" />
<object-type name="FUNCTION" enabled="true" />
<object-type name="PACKAGE" enabled="true" />
<object-type name="TYPE" enabled="true" />
<object-type name="JAVA CLASS" enabled="true" />
<object-type name="INNER CLASS" enabled="true" />
<object-type name="JAVA FIELD" enabled="true" />
<object-type name="JAVA METHOD" enabled="true" />
<object-type name="JAVA PARAMETER" enabled="true" />
<object-type name="JAVA RESOURCE" enabled="true" />
<object-type name="DIMENSION" enabled="false" />
<object-type name="CLUSTER" enabled="false" />
<object-type name="DBLINK" enabled="false" />
<object-type name="CREDENTIAL" enabled="false" />
</lookup-objects>
<force-database-load value="false" />
<prompt-connection-selection value="true" />
<prompt-schema-selection value="true" />
</lookup-filters>
</navigation-settings>
<dataset-grid-settings>
<general>
<enable-zooming value="true" />
<enable-column-tooltip value="true" />
</general>
<sorting>
<nulls-first value="true" />
<max-sorting-columns value="4" />
</sorting>
<audit-columns>
<column-names value="" />
<visible value="true" />
<editable value="false" />
</audit-columns>
</dataset-grid-settings>
<dataset-editor-settings>
<text-editor-popup>
<active value="false" />
<active-if-empty value="false" />
<data-length-threshold value="100" />
<popup-delay value="1000" />
</text-editor-popup>
<values-actions-popup>
<show-popup-button value="true" />
<element-count-threshold value="1000" />
<data-length-threshold value="250" />
</values-actions-popup>
<general>
<fetch-block-size value="100" />
<fetch-timeout value="30" />
<trim-whitespaces value="true" />
<convert-empty-strings-to-null value="true" />
<select-content-on-cell-edit value="true" />
<large-value-preview-active value="true" />
</general>
<filters>
<prompt-filter-dialog value="true" />
<default-filter-type value="BASIC" />
</filters>
<qualified-text-editor text-length-threshold="300">
<content-types>
<content-type name="Text" enabled="true" />
<content-type name="Properties" enabled="true" />
<content-type name="XML" enabled="true" />
<content-type name="DTD" enabled="true" />
<content-type name="HTML" enabled="true" />
<content-type name="XHTML" enabled="true" />
<content-type name="SQL" enabled="true" />
<content-type name="PL/SQL" enabled="true" />
<content-type name="JSON" enabled="true" />
<content-type name="JSON5" enabled="true" />
<content-type name="YAML" enabled="true" />
</content-types>
</qualified-text-editor>
<record-navigation>
<navigation-target value="VIEWER" />
</record-navigation>
</dataset-editor-settings>
<code-editor-settings>
<general>
<show-object-navigation-gutter value="false" />
<show-spec-declaration-navigation-gutter value="true" />
<enable-spellchecking value="true" />
<enable-reference-spellchecking value="false" />
</general>
<confirmations>
<save-changes value="false" />
<revert-changes value="true" />
<exit-on-changes value="ASK" />
</confirmations>
</code-editor-settings>
<code-completion-settings>
<filters>
<basic-filter>
<filter-element type="RESERVED_WORD" id="keyword" selected="true" />
<filter-element type="RESERVED_WORD" id="function" selected="true" />
<filter-element type="RESERVED_WORD" id="parameter" selected="true" />
<filter-element type="RESERVED_WORD" id="datatype" selected="true" />
<filter-element type="RESERVED_WORD" id="exception" selected="true" />
<filter-element type="OBJECT" id="schema" selected="true" />
<filter-element type="OBJECT" id="role" selected="true" />
<filter-element type="OBJECT" id="user" selected="true" />
<filter-element type="OBJECT" id="privilege" selected="true" />
<user-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="json view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="false" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</user-schema>
<public-schema>
<filter-element type="OBJECT" id="table" selected="false" />
<filter-element type="OBJECT" id="view" selected="false" />
<filter-element type="OBJECT" id="json view" selected="false" />
<filter-element type="OBJECT" id="materialized view" selected="false" />
<filter-element type="OBJECT" id="index" selected="false" />
<filter-element type="OBJECT" id="constraint" selected="false" />
<filter-element type="OBJECT" id="trigger" selected="false" />
<filter-element type="OBJECT" id="synonym" selected="false" />
<filter-element type="OBJECT" id="sequence" selected="false" />
<filter-element type="OBJECT" id="procedure" selected="false" />
<filter-element type="OBJECT" id="function" selected="false" />
<filter-element type="OBJECT" id="package" selected="false" />
<filter-element type="OBJECT" id="type" selected="false" />
<filter-element type="OBJECT" id="dimension" selected="false" />
<filter-element type="OBJECT" id="cluster" selected="false" />
<filter-element type="OBJECT" id="dblink" selected="false" />
</public-schema>
<any-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="json view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</any-schema>
</basic-filter>
<extended-filter>
<filter-element type="RESERVED_WORD" id="keyword" selected="true" />
<filter-element type="RESERVED_WORD" id="function" selected="true" />
<filter-element type="RESERVED_WORD" id="parameter" selected="true" />
<filter-element type="RESERVED_WORD" id="datatype" selected="true" />
<filter-element type="RESERVED_WORD" id="exception" selected="true" />
<filter-element type="OBJECT" id="schema" selected="true" />
<filter-element type="OBJECT" id="user" selected="true" />
<filter-element type="OBJECT" id="role" selected="true" />
<filter-element type="OBJECT" id="privilege" selected="true" />
<user-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="json view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</user-schema>
<public-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="json view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</public-schema>
<any-schema>
<filter-element type="OBJECT" id="table" selected="true" />
<filter-element type="OBJECT" id="view" selected="true" />
<filter-element type="OBJECT" id="json view" selected="true" />
<filter-element type="OBJECT" id="materialized view" selected="true" />
<filter-element type="OBJECT" id="index" selected="true" />
<filter-element type="OBJECT" id="constraint" selected="true" />
<filter-element type="OBJECT" id="trigger" selected="true" />
<filter-element type="OBJECT" id="synonym" selected="true" />
<filter-element type="OBJECT" id="sequence" selected="true" />
<filter-element type="OBJECT" id="procedure" selected="true" />
<filter-element type="OBJECT" id="function" selected="true" />
<filter-element type="OBJECT" id="package" selected="true" />
<filter-element type="OBJECT" id="type" selected="true" />
<filter-element type="OBJECT" id="dimension" selected="true" />
<filter-element type="OBJECT" id="cluster" selected="true" />
<filter-element type="OBJECT" id="dblink" selected="true" />
</any-schema>
</extended-filter>
</filters>
<sorting enabled="true">
<sorting-element type="RESERVED_WORD" id="keyword" />
<sorting-element type="RESERVED_WORD" id="datatype" />
<sorting-element type="OBJECT" id="column" />
<sorting-element type="OBJECT" id="table" />
<sorting-element type="OBJECT" id="view" />
<sorting-element type="OBJECT" id="json view" />
<sorting-element type="OBJECT" id="materialized view" />
<sorting-element type="OBJECT" id="index" />
<sorting-element type="OBJECT" id="constraint" />
<sorting-element type="OBJECT" id="trigger" />
<sorting-element type="OBJECT" id="synonym" />
<sorting-element type="OBJECT" id="sequence" />
<sorting-element type="OBJECT" id="procedure" />
<sorting-element type="OBJECT" id="function" />
<sorting-element type="OBJECT" id="package" />
<sorting-element type="OBJECT" id="type" />
<sorting-element type="OBJECT" id="dimension" />
<sorting-element type="OBJECT" id="cluster" />
<sorting-element type="OBJECT" id="dblink" />
<sorting-element type="OBJECT" id="schema" />
<sorting-element type="OBJECT" id="role" />
<sorting-element type="OBJECT" id="user" />
<sorting-element type="RESERVED_WORD" id="function" />
<sorting-element type="RESERVED_WORD" id="parameter" />
</sorting>
<format>
<enforce-code-style-case value="true" />
</format>
</code-completion-settings>
<execution-engine-settings>
<statement-execution>
<fetch-block-size value="100" />
<execution-timeout value="20" />
<debug-execution-timeout value="600" />
<focus-result value="false" />
<prompt-execution value="false" />
</statement-execution>
<script-execution>
<command-line-interfaces />
<execution-timeout value="300" />
</script-execution>
<method-execution>
<execution-timeout value="30" />
<debug-execution-timeout value="600" />
<parameter-history-size value="10" />
</method-execution>
</execution-engine-settings>
<operation-settings>
<transactions>
<uncommitted-changes>
<on-project-close value="ASK" />
<on-disconnect value="ASK" />
<on-autocommit-toggle value="ASK" />
</uncommitted-changes>
<multiple-uncommitted-changes>
<on-commit value="ASK" />
<on-rollback value="ASK" />
</multiple-uncommitted-changes>
</transactions>
<session-browser>
<disconnect-session value="ASK" />
<kill-session value="ASK" />
<reload-on-filter-change value="false" />
</session-browser>
<compiler>
<compile-type value="KEEP" />
<compile-dependencies value="ASK" />
<always-show-controls value="false" />
</compiler>
</operation-settings>
<ddl-file-settings>
<extensions>
<mapping file-type-id="VIEW" extensions="vw" />
<mapping file-type-id="TRIGGER" extensions="trg" />
<mapping file-type-id="PROCEDURE" extensions="prc" />
<mapping file-type-id="FUNCTION" extensions="fnc" />
<mapping file-type-id="PACKAGE" extensions="pkg" />
<mapping file-type-id="PACKAGE_SPEC" extensions="pks" />
<mapping file-type-id="PACKAGE_BODY" extensions="pkb" />
<mapping file-type-id="TYPE" extensions="tpe" />
<mapping file-type-id="TYPE_SPEC" extensions="tps" />
<mapping file-type-id="TYPE_BODY" extensions="tpb" />
<mapping file-type-id="JAVA_SOURCE" extensions="sql" />
</extensions>
<general>
<lookup-ddl-files value="true" />
<create-ddl-files value="false" />
<synchronize-ddl-files value="true" />
<use-qualified-names value="false" />
<make-scripts-rerunnable value="true" />
</general>
</ddl-file-settings>
<assistant-settings>
<credential-settings>
<credentials />
</credential-settings>
</assistant-settings>
<general-settings>
<regional-settings>
<date-format value="MEDIUM" />
<number-format value="UNGROUPED" />
<locale value="SYSTEM_DEFAULT" />
<use-custom-formats value="false" />
</regional-settings>
<environment>
<environment-types>
<environment-type id="development" name="Development" description="Development environment" color="-2430209/-12296320" readonly-code="false" readonly-data="false" />
<environment-type id="integration" name="Integration" description="Integration environment" color="-2621494/-12163514" readonly-code="true" readonly-data="false" />
<environment-type id="production" name="Production" description="Productive environment" color="-11574/-10271420" readonly-code="true" readonly-data="true" />
<environment-type id="other" name="Other" description="" color="-1576/-10724543" readonly-code="false" readonly-data="false" />
</environment-types>
<visibility-settings>
<connection-tabs value="true" />
<dialog-headers value="true" />
<object-editor-tabs value="true" />
<script-editor-tabs value="false" />
<execution-result-tabs value="true" />
</visibility-settings>
</environment>
</general-settings>
</component>
</project>

View File

@@ -0,0 +1,44 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
<option name="myValues">
<value>
<list size="7">
<item index="0" class="java.lang.String" itemvalue="nobr" />
<item index="1" class="java.lang.String" itemvalue="noembed" />
<item index="2" class="java.lang.String" itemvalue="comment" />
<item index="3" class="java.lang.String" itemvalue="noscript" />
<item index="4" class="java.lang.String" itemvalue="embed" />
<item index="5" class="java.lang.String" itemvalue="script" />
<item index="6" class="java.lang.String" itemvalue="span" />
</list>
</value>
</option>
<option name="myCustomValuesEnabled" value="true" />
</inspection_tool>
<inspection_tool class="PyCompatibilityInspection" enabled="false" level="WARNING" enabled_by_default="false">
<option name="ourVersions">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="3.13" />
</list>
</value>
</option>
</inspection_tool>
<inspection_tool class="PyShadowingBuiltinsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredNames">
<list>
<option value="format" />
</list>
</option>
</inspection_tool>
<inspection_tool class="PyUnresolvedReferencesInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredIdentifiers">
<list>
<option value="type.*" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View File

@@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.13" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 virtualenv at C:\Users\Algiz_N\PycharmProjects\stcs\.venv" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/stcs.iml" filepath="$PROJECT_DIR$/.idea/stcs.iml" />
</modules>
</component>
</project>

8
.idea/stcs.iml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 3.13 virtualenv at C:\Users\Algiz_N\PycharmProjects\stcs\.venv" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -1,5 +1,5 @@
import asyncio import asyncio
import logging.config
from aiogram import Bot, Dispatcher from aiogram import Bot, Dispatcher
from aiogram.filters import Command, CommandStart from aiogram.filters import Command, CommandStart
from aiogram.types import Message from aiogram.types import Message
@@ -9,14 +9,17 @@ from app.telegram.database.models import async_main
from app.telegram.handlers.handlers import router from app.telegram.handlers.handlers import router
from app.telegram.functions.main_settings.settings import router_main_settings 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.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.Add_Bybit_API import router_register_bybit_api
from app.services.Bybit.functions.functions import router_functions_bybit_trade from app.services.Bybit.functions.functions import router_functions_bybit_trade
from config import TOKEN_TG_BOT from logger_helper.logger_helper import LOGGING_CONFIG
from config import TOKEN_TG_BOT_1, TOKEN_TG_BOT_2, TOKEN_TG_BOT_3
from app.telegram.logs import logger logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("main")
bot = Bot(token=TOKEN_TG_BOT) bot = Bot(token=TOKEN_TG_BOT_1)
dp = Dispatcher() dp = Dispatcher()
async def main(): async def main():
@@ -25,6 +28,7 @@ async def main():
dp.include_router(router) dp.include_router(router)
dp.include_router(router_main_settings) dp.include_router(router_main_settings)
dp.include_router(router_risk_management_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_register_bybit_api)
dp.include_router(router_functions_bybit_trade) dp.include_router(router_functions_bybit_trade)
@@ -32,6 +36,7 @@ async def main():
if __name__ == '__main__': if __name__ == '__main__':
try: try:
logger.info("Bot is on")
asyncio.run(main()) asyncio.run(main())
except KeyboardInterrupt: except KeyboardInterrupt:
print("Bot is off") logger.info("Bot is off")

View File

@@ -1,6 +1,8 @@
from aiogram import F, Router from aiogram import F, Router
import logging.config
from logger_helper.logger_helper import LOGGING_CONFIG
import app.telegram.Keyboards.inline_keyboards as inline_markup import app.telegram.Keyboards.inline_keyboards as inline_markup
import app.telegram.Keyboards.reply_keyboards as reply_markup
import app.telegram.database.requests as rq import app.telegram.database.requests as rq
from aiogram.types import Message, CallbackQuery from aiogram.types import Message, CallbackQuery
@@ -9,6 +11,9 @@ from aiogram.types import Message, CallbackQuery
from aiogram.fsm.state import State, StatesGroup from aiogram.fsm.state import State, StatesGroup
from aiogram.fsm.context import FSMContext 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 = Router()
class state_reg_bybit_api(StatesGroup): class state_reg_bybit_api(StatesGroup):
@@ -68,6 +73,6 @@ async def add_secret_key(message: Message, state: FSMContext):
await state.clear() await state.clear()
await message.answer('Данные добавлены, нажмите на профиль и начните торговлю!') await message.answer('Данные добавлены, нажмите на профиль и начните торговлю!', reply_markup=reply_markup.base_buttons_markup)

View File

@@ -1,254 +1,246 @@
import time import asyncio
import time
from typing import Optional import logging.config
from asyncio import Handle
from annotated_types import T
from pybit import exceptions from pybit import exceptions
from pybit.unified_trading import HTTP from pybit.unified_trading import HTTP
from pybit.unified_trading import WebSocket from logger_helper.logger_helper import LOGGING_CONFIG
import app.services.Bybit.functions.price_symbol as price_symbol
from app.services.Bybit.functions import price_symbol
import app.services.Bybit.functions.balance as balance_g import app.services.Bybit.functions.balance as balance_g
import app.telegram.database.requests as rq import app.telegram.database.requests as rq
import logging logging.config.dictConfig(LOGGING_CONFIG)
logging.basicConfig(level=logging.DEBUG) logger = logging.getLogger("futures")
def handle_message(message):
print(message)
async def info_access_open_deal(message, symbol, trade_mode, margin_mode, leverage, qty): async def info_access_open_deal(message, symbol, trade_mode, margin_mode, leverage, qty):
match margin_mode: human_margin_mode = 'Isolated' if margin_mode == 'ISOLATED_MARGIN' else 'Cross'
case 'ISOLATED_MARGIN':
margin_mode = 'Isolated'
case 'REGULAR_MARGIN':
margin_mode = 'Cross'
text = f'''Позиция была успешна открыта! text = f'''Позиция была успешна открыта!
Торговая пара: {symbol} Торговая пара: {symbol}
Движение: {trade_mode} Движение: {trade_mode}
Тип-маржи: {margin_mode} Тип-маржи: {human_margin_mode}
Кредитное плечо: {leverage} Кредитное плечо: {leverage}
Количество: {qty} Количество: {qty}
''' '''
await message.answer(text=text, parse_mode='html') await message.answer(text=text, parse_mode='html')
async def error_max_step(message): async def error_max_step(message):
await message.answer('Сделка не была совершена, превышен лимит максимального количества ставок') await message.answer('Сделка не была совершена, превышен лимит максимального количества ставок в серии.')
async def error_max_risk(message): async def error_max_risk(message):
await message.answer('Сделка не была совершена, слишком высокий риск') await message.answer('Сделка не была совершена, риск убытка превышает допустимый лимит.')
async def open_position(tg_id, message, side: str, margin_mode: str):
"""
Открытие позиции (торговля с мартингейлом и управлением рисками)
:param tg_id: Telegram ID пользователя
:param message: объект сообщения Telegram для ответов
:param side: 'Buy' для Long, 'Sell' для Short
:param margin_mode: 'Isolated' или 'Cross'
"""
async def contract_long(tg_id, message, margin_mode):
api_key = await rq.get_bybit_api_key(tg_id) api_key = await rq.get_bybit_api_key(tg_id)
secret_key = await rq.get_bybit_secret_key(tg_id) secret_key = await rq.get_bybit_secret_key(tg_id)
SYMBOL = await rq.get_symbol(tg_id) symbol = await rq.get_symbol(tg_id)
data_main_stgs = await rq.get_user_main_settings(tg_id) data_main_stgs = await rq.get_user_main_settings(tg_id)
data_risk_management_stgs = await rq.get_user_risk_management_settings(tg_id) order_type = data_main_stgs.get('entry_order_type', 'Market')
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)
match margin_mode: bybit_margin_mode = 'ISOLATED_MARGIN' if margin_mode == 'Isolated' else 'REGULAR_MARGIN'
case 'Isolated':
margin_mode = 'ISOLATED_MARGIN' client = HTTP(api_key=api_key, api_secret=secret_key)
case 'Cross':
margin_mode = 'REGULAR_MARGIN' try:
balance = await balance_g.get_balance(tg_id, message)
price = await price_symbol.get_price(tg_id)
# Установка маржинального режима
client.set_margin_mode(setMarginMode=bybit_margin_mode)
martingale_factor = float(data_main_stgs['martingale_factor'])
max_martingale_steps = int(data_main_stgs['maximal_quantity'])
starting_quantity = float(data_main_stgs['starting_quantity'])
max_risk_percent = float(data_risk_stgs['max_risk_deal'])
loss_profit = float(data_risk_stgs['price_loss'])
takeprofit = float(data_risk_stgs['price_profit'])
commission_fee = float(data_risk_stgs.get('commission_fee', 0))
takeProfit = max(takeprofit - commission_fee, 0)
current_martingale_step = 0
next_quantity = starting_quantity
last_quantity = starting_quantity
realised_pnl = 0.0
# Получаем текущие открытые позиции по символу
positions_resp = client.get_positions(category='linear', symbol=symbol)
positions_list = positions_resp.get('result', {}).get('list', [])
current_martingale_step = await rq.get_martingale_step(tg_id)
if positions_list:
position = positions_list[0]
realised_pnl = float(position.get('unrealisedPnl', 0.0))
if realised_pnl > 0:
current_martingale_step = 0
next_quantity = starting_quantity
else:
current_martingale_step += 1
if current_martingale_step > max_martingale_steps:
await error_max_step(message)
return
next_quantity = starting_quantity * (martingale_factor ** current_martingale_step)
else:
# Позиция не открыта — начинаем с начальной ставки
next_quantity = starting_quantity
current_martingale_step = 0
# Проверяем риск убытка
potential_loss = next_quantity * price * (loss_profit / 100)
allowed_loss = balance * (max_risk_percent / 100)
if potential_loss > allowed_loss:
await error_max_risk(message)
return
# Отправляем запрос на открытие ордера
response = client.place_order(
category='linear',
symbol=symbol,
side=side,
orderType=order_type,
qty=next_quantity,
leverage=int(data_main_stgs['size_leverage']),
price=limit_price if order_type == 'Limit' else None,
takeProfit=takeProfit,
stopLoss=loss_profit,
orderLinkId=f"deal_{symbol}_{int(time.time())}"
)
if response.get('ret_code', -1) == 0:
await info_access_open_deal(message, symbol, data_main_stgs['trading_mode'], bybit_margin_mode,
data_main_stgs['size_leverage'], next_quantity)
await rq.update_martingale_step(tg_id, current_martingale_step)
else:
await message.answer(f"Ошибка открытия ордера: {response.get('ret_msg', 'неизвестная ошибка')}")
except exceptions.InvalidRequestError as e:
logger.error(f"InvalidRequestError: {e}")
await message.answer('Ошибка: неверно указана торговая пара или параметры.')
except Exception as e:
logger.error(f"Ошибка при совершении сделки: {e}")
async def trading_cycle(tg_id, message):
start_time = time.time()
timer_min = await rq.get_user_timer(tg_id)
timer_sec = timer_min * 60 if timer_min else 0
while True:
elapsed = time.time() - start_time
if timer_sec > 0 and elapsed > timer_sec:
await message.answer("Время работы по таймеру истекло. Торговля остановлена.")
await rq.update_martingale_step(tg_id, 0)
break
# Проверяем позиции
data_main_stgs = await rq.get_user_main_settings(tg_id)
side = 'Buy' if data_main_stgs['trading_mode'] == 'Long' else 'Sell'
margin_mode = data_main_stgs.get('margin_type', 'Isolated')
# Можно добавлять логику по PNL, стоп-лоссам, тейк-профитам
await open_position(tg_id, message, side=side, margin_mode=margin_mode)
await asyncio.sleep(10)
async def get_active_positions(message, api_key, secret_key, symbol):
client = HTTP( client = HTTP(
api_key=api_key, api_key=api_key,
api_secret=secret_key api_secret=secret_key
) )
instruments_resp = client.get_instruments_info(category='linear')
if instruments_resp.get('ret_code') != 0:
return []
symbols = [item['symbol'] for item in instruments_resp.get('result', {}).get('list', [])]
active_positions = []
async def fetch_positions(symbol):
try: try:
balance = 0 resp = client.get_positions(category='linear', symbol=symbol)
price = 0 if resp.get('ret_code') == 0:
positions = resp.get('result', {}).get('list', [])
balance = await balance_g.get_balance(tg_id) for pos in positions:
price = await price_symbol.get_price(tg_id, message) if pos.get('size') and float(pos['size']) > 0:
active_positions.append(pos)
client.set_margin_mode(
setMarginMode=margin_mode # margin_type
)
martingale_factor = float(data_main_stgs['martingale_factor']) # Исправлено: было maximal_quantity
max_martingale_steps = int(data_main_stgs['maximal_quantity'])
starting_quantity = float(data_main_stgs['starting_quantity'])
max_risk_percent = float(data_risk_management_stgs['max_risk_deal'])
loss_profit = float(data_risk_management_stgs['price_loss'])
takeProfit= float(data_risk_management_stgs['price_profit'])
# Инициализация переменных
next_quantity = starting_quantity
last_quantity = starting_quantity
realised_pnl = 0.0
current_martingale_step = 0 # Текущая ставка в серии
next_quantity = 0
realised_pnl = 0
last_quantity = starting_quantity
# Пример расчёта следующего размера позиции
try:
position_info = client.get_positions(category='linear', symbol=SYMBOL)
position = position_info['result']['list'][0] # или другой нужный индекс
realised_pnl = float(position['unrealisedPnl'])
if realised_pnl > 0:
starting_quantity = next_quantity
current_martingale_step = 0
elif not realised_pnl:
next_quantity = starting_quantity
current_martingale_step += 1
else:
current_martingale_step += 1
next_quantity = last_quantity * martingale_factor
starting_quantity = next_quantity
except Exception as e: except Exception as e:
print("Не получены позиции") logger.error(f"Ошибка при получении позиций: {e}")
next_quantity = starting_quantity await message.answer('⚠️ Ошибка при получении позиций')
potential_loss = (next_quantity * float(price)) * (loss_profit / 100) for sym in symbols:
allowed_loss = float(balance) * (max_risk_percent / 100) await fetch_positions(sym)
if current_martingale_step >= max_martingale_steps: return active_positions
print("Достигнут максимум ставок в серии (8)!")
print("Торговля не продолжится")
await error_max_step(message)
else:
if potential_loss > allowed_loss:
print(f"ОШИБКА: Риск превышен!")
print(f"Ручной qty = {next_quantity} → Убыток = {potential_loss} USDT")
print(f"Разрешено = {allowed_loss} USDT (1% от баланса)")
await error_max_risk(message) async def close_user_trade(tg_id: int, symbol: str) -> bool:
else:
print(f"Риск в допустимых пределах. Qty = {next_quantity}")
r = client.place_order(
category='linear',
symbol=SYMBOL,
side='Buy',
orderType="Market",
leverage=int(data_main_stgs['size_leverage']),
qty=next_quantity,
takeProfit=takeProfit, # TP - закрывает позицию, когда цена достигает нужного уровня
stopProfit=float(data_risk_management_stgs['price_loss']), # SL - закрывает позицию, когда убыток достигает нужного уровня
orderLinkId=f"deal_{SYMBOL}_{time.time()}"
)
await info_access_open_deal(message, SYMBOL, data_main_stgs['trading_mode'], margin_mode, data_main_stgs['size_leverage'], next_quantity)
except exceptions.InvalidRequestError as e:
await message.answer('Недостаточно баланса')
except Exception as e:
await message.answer('Непредвиденная оишбка')
async def contract_short(tg_id, message, margin_mode):
api_key = await rq.get_bybit_api_key(tg_id) api_key = await rq.get_bybit_api_key(tg_id)
secret_key = await rq.get_bybit_secret_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)
data_main_stgs = await rq.get_user_main_settings(tg_id) # Получаем текущие открытые позиции по символу (пример для linear фьючерсов)
data_risk_management_stgs = await rq.get_user_risk_management_settings(tg_id) positions_resp = client.get_positions(category="linear", symbol=symbol)
match margin_mode: ret_code = positions_resp.get('ret_code')
case 'Isolated': result = positions_resp.get('result')
margin_mode = 'ISOLATED_MARGIN'
case 'Cross':
margin_mode = 'REGULAR_MARGIN'
client = HTTP( if ret_code != 0 or not result or not result.get('list'):
api_key=api_key, return False
api_secret=secret_key
) positions_list = result['list']
if not positions_list:
return False
position = positions_list[0]
qty = abs(float(position['size']))
side = position['side']
if qty == 0:
return False
# Определяем сторону закрытия — противоположная открытой позиции
close_side = "Sell" if side == "Buy" else "Buy"
try: try:
balance = 0 response = client.place_order(
price = 0 category="linear",
symbol=symbol,
balance = await balance_g.get_balance(tg_id) side=close_side,
price = await price_symbol.get_price(tg_id, message)
client.set_margin_mode(
setMarginMode=margin_mode # margin_type
)
martingale_factor = float(data_main_stgs['martingale_factor']) # Исправлено: было maximal_quantity
max_martingale_steps = int(data_main_stgs['maximal_quantity'])
starting_quantity = float(data_main_stgs['starting_quantity'])
max_risk_percent = float(data_risk_management_stgs['max_risk_deal'])
loss_profit = float(data_risk_management_stgs['price_loss'])
takeProfit = float(data_risk_management_stgs['price_profit'])
# Инициализация переменных
next_quantity = starting_quantity
last_quantity = starting_quantity
realised_pnl = 0.0
current_martingale_step = 0 # Текущая ставка в серии
next_quantity = 0
realised_pnl = 0
last_quantity = starting_quantity
# Пример расчёта следующего размера позиции
try:
position_info = client.get_positions(category='linear', symbol=SYMBOL)
position = position_info['result']['list'][0] # или другой нужный индекс
realised_pnl = float(position['unrealisedPnl'])
if realised_pnl > 0:
starting_quantity = next_quantity
current_martingale_step = 0
elif not realised_pnl:
next_quantity = starting_quantity
current_martingale_step += 1
else:
current_martingale_step += 1
next_quantity = last_quantity * martingale_factor
starting_quantity = next_quantity
except Exception as e:
print("Не получены позиции")
next_quantity = starting_quantity
potential_loss = (next_quantity * float(price)) * (loss_profit / 100)
allowed_loss = float(balance) * (max_risk_percent / 100)
if current_martingale_step >= max_martingale_steps:
print("Достигнут максимум ставок в серии (8)!")
print("Торговля не продолжится")
await error_max_step(message)
else:
if potential_loss > allowed_loss:
print(f"ОШИБКА: Риск превышен!")
print(f"Ручной qty = {next_quantity} → Убыток = {potential_loss} USDT")
print(f"Разрешено = {allowed_loss} USDT (1% от баланса)")
await error_max_risk(message)
else:
print(f"Риск в допустимых пределах. Qty = {next_quantity}")
r = client.place_order(
category='linear',
symbol=SYMBOL,
side='Sell',
orderType="Market", orderType="Market",
leverage=int(data_main_stgs['size_leverage']), qty=str(qty),
qty=next_quantity, timeInForce="GoodTillCancel",
orderLinkId=f"deal_{SYMBOL}_{time.time()}" reduceOnly=True
) )
return response['ret_code'] == 0
await info_access_open_deal(message, SYMBOL, data_main_stgs['trading_mode'], margin_mode, data_main_stgs['size_leverage'], next_quantity)
except exceptions.InvalidRequestError as e:
await message.answer('Недостаточно баланса')
except Exception as e: except Exception as e:
await message.answer('Непредвиденная оишбка') logger.error(f"Ошибка закрытия сделки {symbol} для пользователя {tg_id}: {e}")
return False
def get_positive_percent(negative_percent: float, manual_positive_percent: float | None) -> float:
if manual_positive_percent and manual_positive_percent > 0:
return manual_positive_percent
return abs(negative_percent)

View File

@@ -1,10 +1,29 @@
import app.telegram.database.requests as rq 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 from pybit.unified_trading import HTTP
client = HTTP() logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("balance")
async def get_balance(tg_id, message):
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) api_key = await rq.get_bybit_api_key(tg_id)
secret_key = await rq.get_bybit_secret_key(tg_id) secret_key = await rq.get_bybit_secret_key(tg_id)
@@ -14,20 +33,20 @@ async def get_balance(tg_id, message):
) )
if api_key == 'None' or secret_key == 'None': if api_key == 'None' or secret_key == 'None':
await message.answer('⚠️ Подключите платформу для торговли') await message.answer('⚠️ Подключите платформу для торговли',
reply_markup=inline_markup.connect_bybit_api_markup)
return 0 return 0
try: try:
check_user = client.get_wallet_balance() response = client.get_wallet_balance(accountType='UNIFIED')
if response['retCode'] == 0:
if check_user: total_balance = response['result']['list'][0].get('totalWalletBalance', '0')
try: return total_balance
balance = client.get_wallet_balance(accountType='UNIFIED', coin='USDT')['result']['list'][0]['coin'][0]['walletBalance'] else:
logger.error(f"Ошибка API: {response.get('retMsg')}")
return balance await message.answer(f"⚠️ Ошибка API: {response.get('retMsg')}")
except Exception as e:
await message.answer('⚠️ Ошибка при получении баланса пользователя')
return 0 return 0
except Exception as e: except Exception as e:
await message.answer('⚠️ Неверные данные API, перепроверьте их') logger.error(f"Ошибка при получении общего баланса: {e}")
await message.answer('⚠️ Ошибка при получении баланса')
return 0 return 0

View File

@@ -1,21 +0,0 @@
import app.telegram.database.requests as rq
import app.services.Bybit.functions.price_symbol as price_s
from pybit.unified_trading import HTTP
client = HTTP()
async def get_min_qty(tg_id, message):
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 price_s.get_price(tg_id, message)
min_qty = int(5 / price * 1.1)
return min_qty

View File

@@ -1,9 +1,13 @@
from aiogram import F, Router import asyncio
import logging.config
from app.services.Bybit.functions import Futures, func_min_qty from aiogram import F, Router
from logger_helper.logger_helper import LOGGING_CONFIG
from app.services.Bybit.functions import Futures, min_qty
from app.services.Bybit.functions.Futures import open_position, close_user_trade, get_active_positions, trading_cycle
from app.services.Bybit.functions.balance import get_balance from app.services.Bybit.functions.balance import get_balance
import app.telegram.Keyboards.inline_keyboards as inline_markup import app.telegram.Keyboards.inline_keyboards as inline_markup
import app.telegram.Keyboards.reply_keyboards as reply_markup
from pybit.unified_trading import HTTP
import app.telegram.database.requests as rq import app.telegram.database.requests as rq
from aiogram.types import Message, CallbackQuery from aiogram.types import Message, CallbackQuery
@@ -11,12 +15,32 @@ from aiogram.types import Message, CallbackQuery
from aiogram.fsm.state import State, StatesGroup from aiogram.fsm.state import State, StatesGroup
from aiogram.fsm.context import FSMContext 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() router_functions_bybit_trade = Router()
class state_update_symbol(StatesGroup): class state_update_symbol(StatesGroup):
symbol = State() symbol = State()
@router_functions_bybit_trade.callback_query(F.data == 'clb_start_trading')
class state_update_entry_type(StatesGroup):
entry_type = State()
class TradeSetup(StatesGroup):
waiting_for_timer = State()
waiting_for_positive_percent = State()
class state_limit_price(StatesGroup):
price = State()
@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, state: FSMContext): async def clb_start_bybit_trade_message(callback: CallbackQuery, state: FSMContext):
api = await rq.get_bybit_api_key(callback.from_user.id) api = await rq.get_bybit_api_key(callback.from_user.id)
secret = await rq.get_bybit_secret_key(callback.from_user.id) secret = await rq.get_bybit_secret_key(callback.from_user.id)
@@ -33,10 +57,12 @@ async def clb_start_bybit_trade_message(callback: CallbackQuery, state: FSMConte
Как начать торговлю? Как начать торговлю?
1⃣ Проверьте и тщательно настройте все параметры в вашем профиле. 1⃣ Проверьте и тщательно настройте все параметры в вашем профиле.
2⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару заглавными буквами, без лишних символов (например: BTCUSDT). 2⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару, без лишних символов (например: BTCUSDT).
3⃣ Нажмите кнопку 'Выбрать тип входа' и после нажмите начать торговлю.
''' '''
await callback.message.edit_text(text=text, parse_mode='html', reply_markup=inline_markup.trading_markup) await callback.message.edit_text(text=text, parse_mode='html', reply_markup=inline_markup.trading_markup)
async def start_bybit_trade_message(message, state): async def start_bybit_trade_message(message, state):
api = await rq.get_bybit_api_key(message.from_user.id) api = await rq.get_bybit_api_key(message.from_user.id)
secret = await rq.get_bybit_secret_key(message.from_user.id) secret = await rq.get_bybit_secret_key(message.from_user.id)
@@ -53,47 +79,254 @@ async def start_bybit_trade_message(message, state):
Как начать торговлю? Как начать торговлю?
1⃣ Проверьте и тщательно настройте все параметры в вашем профиле. 1⃣ Проверьте и тщательно настройте все параметры в вашем профиле.
2⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару заглавными буквами, без лишних символов (например: BTCUSDT). 2⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару, без лишних символов (например: BTCUSDT).
3⃣ Нажмите кнопку 'Выбрать тип входа' и после нажмите начать торговлю.
''' '''
await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.trading_markup) 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') @router_functions_bybit_trade.callback_query(F.data == 'clb_update_trading_pair')
async def update_symbol_for_trade_message(callback: CallbackQuery, state: FSMContext): async def update_symbol_for_trade_message(callback: CallbackQuery, state: FSMContext):
await state.set_state(state_update_symbol.symbol) await state.set_state(state_update_symbol.symbol)
await callback.message.answer(text='Укажите торговую пару заглавными буквами без пробелов и лишних символов (пример: BTCUSDT): ') await callback.message.answer(
text='Укажите торговую пару заглавными буквами без пробелов и лишних символов (пример: BTCUSDT): ',
reply_markup=inline_markup.cancel)
@router_functions_bybit_trade.message(state_update_symbol.symbol) @router_functions_bybit_trade.message(state_update_symbol.symbol)
async def update_symbol_for_trade(message: Message, state: FSMContext): async def update_symbol_for_trade(message: Message, state: FSMContext):
await state.update_data(symbol = message.text) user_input = message.text.strip().upper()
data = await state.get_data() 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 message.answer('Пара была успешно обновлена')
await rq.update_symbol(message.from_user.id, data['symbol']) await rq.update_symbol(message.from_user.id, user_input)
await start_bybit_trade_message(message, state) await start_bybit_trade_message(message, state)
await state.clear() await state.clear()
@router_functions_bybit_trade.callback_query(F.data == 'clb_open_deal')
async def make_deal_bybit (callback: CallbackQuery):
data_main_stgs = await rq.get_user_main_settings(callback.from_user.id)
trade_mode = data_main_stgs['trading_mode'] @router_functions_bybit_trade.callback_query(F.data == 'clb_update_entry_type')
qty = data_main_stgs['starting_quantity'] async def update_entry_type_message(callback: CallbackQuery, state: FSMContext):
margin_mode = data_main_stgs['margin_type'] await state.set_state(state_update_entry_type.entry_type)
qty_min = await func_min_qty.get_min_qty(callback.from_user.id, callback.message) await callback.message.answer("Выберите тип входа в позицию:", reply_markup=inline_markup.entry_order_type_markup)
await callback.answer()
if qty < qty_min:
await callback.message.edit_text(f"Количество вашей ставки ({qty}) меньше минимального количества ({qty_min}) для данной торговой пары") @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):
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(f"Произошла ошибка при обновлении типа входа в позицию: {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):
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)
data = await state.get_data()
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_my_deals")
async def show_my_trades_callback(callback: CallbackQuery):
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)
symbol = await rq.get_symbol(tg_id)
trades = await get_active_positions(callback.message, api_key, secret_key, symbol)
if not trades:
await callback.message.answer("Нет активных позиций.")
await callback.answer()
return
keyboard = inline_markup.create_trades_inline_keyboard(trades)
await callback.message.answer(
"Выберите сделку из списка:",
reply_markup=keyboard
)
await callback.answer()
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith('select_trade:'))
async def on_trade_selected(callback: CallbackQuery):
symbol = callback.data.split(':')[1]
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)
positions = await get_active_positions(callback.message, api_key, secret_key, symbol)
# Если несколько позиций по символу, можно выбрать нужную или взять первую
if not positions:
await callback.message.answer("Позиция не найдена")
await callback.answer()
return
pos = positions[0]
symbol = pos.get('symbol')
side = pos.get('side')
entry_price = pos.get('entryPrice') # Цена открытия позиции
current_price = pos.get('price') # Текущая цена (если есть)
text = (f"Информация по позиции:\n"
f"Название: {symbol}\n"
f"Направление: {side}\n"
f"Цена покупки: {entry_price}\n"
f"Текущая цена: {current_price if current_price else 'N/A'}")
keyboard = inline_markup.create_close_deal_markup(symbol)
await callback.message.answer(text, reply_markup=keyboard)
await callback.answer()
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_deal:"))
async def close_trade_callback(callback: CallbackQuery):
symbol = callback.data.split(':')[1]
tg_id = callback.from_user.id
result = await close_user_trade(tg_id, symbol)
if result:
await callback.message.answer(f"Сделка {symbol} успешно закрыта.")
else: else:
match trade_mode: await callback.message.answer(f"Не удалось закрыть сделку {symbol}.")
case 'Long':
await Futures.contract_long(callback.from_user.id, callback.message, margin_mode) await callback.answer()
case 'Short':
await Futures.contract_short(callback.from_user.id, callback.message, margin_mode)
case 'Switch': @router_functions_bybit_trade.callback_query(F.data == "clb_start_chatbot_trading")
await callback.message.edit_text('Режим Switch пока недоступен') async def start_trading_process(callback: CallbackQuery, state: FSMContext):
case 'Smart': tg_id = callback.from_user.id
await callback.message.edit_text('Режим Smart пока недоступен') message = callback.message
# Получаем настройки пользователя
data_main_stgs = await rq.get_user_main_settings(tg_id)
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)
margin_mode = data_main_stgs.get('margin_type', 'Isolated')
trading_mode = data_main_stgs.get('trading_mode')
# Проверка API ключей
if not api_key or not secret_key:
await message.answer("❗️ У вас не настроены API ключи для Bybit.")
await callback.answer()
return
# Проверка режима торговли
if trading_mode not in ['Long', 'Short', 'Smart', 'Switch']:
await message.answer(f"❗️ Некорректный торговый режим: {trading_mode}")
await callback.answer()
return
# Проверка допустимости маржинального режима
if margin_mode not in ['Isolated', 'Cross']:
margin_mode = 'Isolated'
# Проверяем открытые позиции и маржинальный режим
client = HTTP(api_key=api_key, api_secret=secret_key)
try:
positions_resp = client.get_positions(category='linear', symbol=symbol)
positions = positions_resp.get('result', {}).get('list', [])
except Exception as e:
logger.error(f"Ошибка при получении позиций: {e}")
positions = []
for pos in positions:
size = pos.get('size')
existing_margin_mode = pos.get('margin_mode')
if size and float(size) > 0 and existing_margin_mode and existing_margin_mode != margin_mode:
await callback.answer(
f"⚠️ Маржинальный режим нельзя менять при открытой позиции "
f"(текущий режим: {existing_margin_mode})", show_alert=True)
return
# Определяем сторону для открытия позиции
if trading_mode == 'Long':
side = 'Buy'
elif trading_mode == 'Short':
side = 'Sell'
else:
await message.answer(f"Режим торговли '{trading_mode}' пока не поддерживается.")
await callback.answer()
return
# Сообщаем о начале торговли
await message.answer("Начинаю торговлю с использованием текущих настроек...")
# Открываем позицию (вызывает Futures.open_position)
success = await open_position(tg_id, message, side=side, margin_mode=margin_mode)
if not success:
await message.answer('⚠️ Ошибка при совершении сделки', reply_markup=inline_markup.back_to_main)
return
# Проверяем таймер и информируем пользователя
timer_data = await rq.get_user_timer(tg_id)
timer_minutes = timer_data.get('timer') if isinstance(timer_data, dict) else timer_data
if timer_minutes and timer_minutes > 0:
await message.answer(f"Торговля будет работать по таймеру: {timer_minutes} мин.")
asyncio.create_task(trading_cycle(tg_id, message))
else:
await message.answer(
"Торговля начата без ограничения по времени. Для остановки нажмите кнопку 'Закрыть сделку'.",
reply_markup=inline_markup.create_close_deal_markup(symbol)
)
await callback.answer()
@router_functions_bybit_trade.callback_query(F.data == "clb_cancel")
async def cancel(callback: CallbackQuery, state: FSMContext):
await state.clear()
await callback.message.answer("Отменено!", reply_markup=inline_markup.back_to_main)
await callback.answer()

View File

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

View File

@@ -1,23 +1,52 @@
from app.services.Bybit.functions import price_symbol import math
import logging.config
from app.services.Bybit.functions.price_symbol import get_price
import app.telegram.database.requests as rq import app.telegram.database.requests as rq
from logger_helper.logger_helper import LOGGING_CONFIG
from pybit.unified_trading import HTTP from pybit.unified_trading import HTTP
client = HTTP() logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("min_qty")
async def get_min_qty(tg_id): 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) api_key = await rq.get_bybit_api_key(tg_id)
secret_key = await rq.get_bybit_secret_key(tg_id) secret_key = await rq.get_bybit_secret_key(tg_id)
SYMBOL = await rq.get_symbol(tg_id) symbol = await rq.get_symbol(tg_id)
client = HTTP( client = HTTP(api_key=api_key, api_secret=secret_key)
api_key=api_key,
api_secret=secret_key
)
price = await price_symbol(tg_id) price = await get_price(tg_id)
json_data = client.get_instruments_info(symbol=SYMBOL, category='linear')
min_qty = int(5 / price * 1.1) # <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 1% <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD> 5 USDT response = client.get_instruments_info(symbol=symbol, category='linear')
return min_qty 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

View File

@@ -1,14 +1,23 @@
import app.telegram.database.requests as rq import app.telegram.database.requests as rq
import logging.config
from logger_helper.logger_helper import LOGGING_CONFIG
from pybit import exceptions from pybit import exceptions
from pybit.unified_trading import HTTP from pybit.unified_trading import HTTP
client = HTTP() logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("price_symbol")
async def get_price(tg_id, message):
async def get_price(tg_id: int) -> float:
"""
Асинхронно получает текущую цену символа пользователя на Bybit.
:param tg_id: int - ID пользователя Telegram
:return: float - текущая цена символа
"""
api_key = await rq.get_bybit_api_key(tg_id) api_key = await rq.get_bybit_api_key(tg_id)
secret_key = await rq.get_bybit_secret_key(tg_id) secret_key = await rq.get_bybit_secret_key(tg_id)
SYMBOL = await rq.get_symbol(tg_id) symbol = await rq.get_symbol(tg_id)
client = HTTP( client = HTTP(
api_key=api_key, api_key=api_key,
@@ -16,9 +25,9 @@ async def get_price(tg_id, message):
) )
try: try:
price = float(client.get_tickers(category='linear', symbol=SYMBOL).get('result').get('list')[0].get('ask1Price')) price = float(
client.get_tickers(category='linear', symbol=symbol).get('result').get('list')[0].get('ask1Price'))
return price return price
except exceptions.InvalidRequestError as e: except exceptions.InvalidRequestError as e:
await message.answer('Неверно указана торговая пара') logger.error(f"Ошибка при получении цены: {e}")
return 1.0 return 1.0

0
app/states/States.py Normal file
View File

View File

@@ -28,30 +28,41 @@ connect_bybit_api_markup = InlineKeyboardMarkup(inline_keyboard=[
]) ])
trading_markup = InlineKeyboardMarkup(inline_keyboard=[ 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_trading_pair')],
[InlineKeyboardButton(text="Выбрать тип входа", callback_data='clb_update_entry_type')], [InlineKeyboardButton(text="Выбрать тип входа", callback_data='clb_update_entry_type')],
# [InlineKeyboardButton(text="Совершить сделку", callback_data='clb_open_deal')]
]) ])
open_deal_markup = InlineKeyboardMarkup(inline_keyboard=[ start_trading_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Открыть сделку", callback_data="clb_open_deal")], [InlineKeyboardButton(text="На главную", callback_data='back_to_main')],
[InlineKeyboardButton(text="Начать торговлю", callback_data="clb_start_chatbot_trading")],
]) ])
cancel = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Отменить", callback_data="clb_cancel")]
])
entry_order_type_markup = InlineKeyboardMarkup( entry_order_type_markup = InlineKeyboardMarkup(
inline_keyboard=[ inline_keyboard=[
[ [
InlineKeyboardButton(text="Market (текущая цена)", callback_data="entry_order_type:Market"), InlineKeyboardButton(text="Market (текущая цена)", callback_data="entry_order_type:Market"),
InlineKeyboardButton(text="Limit (фиксированная цена)", callback_data="entry_order_type:Limit"), InlineKeyboardButton(text="Limit (фиксированная цена)", callback_data="entry_order_type:Limit"),
] ],
] ]
) )
back_btn_list_settings = [InlineKeyboardButton(text="Назад", callback_data='clb_back_to_special_settings_message')] # Кнопка для возврата к списку каталога настроек back_btn_list_settings = [InlineKeyboardButton(text="Назад",
back_btn_list_settings_markup = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Назад", callback_data='clb_back_to_special_settings_message')]]) # Клавиатура для возврата к списку каталога настроек 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_to_main = [InlineKeyboardButton(text="На главную", callback_data='clb_back_to_main')]
back_to_main = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="На главную", callback_data='back_to_main')],
])
main_settings_markup = InlineKeyboardMarkup(inline_keyboard=[ main_settings_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='Режим торговли', callback_data='clb_change_trading_mode'), [InlineKeyboardButton(text='Режим торговли', callback_data='clb_change_trading_mode'),
InlineKeyboardButton(text='Тип маржи', callback_data='clb_change_margin_type')], InlineKeyboardButton(text='Тип маржи', callback_data='clb_change_margin_type')],
@@ -60,7 +71,7 @@ main_settings_markup = InlineKeyboardMarkup(inline_keyboard=[
InlineKeyboardButton(text='Начальная ставка', callback_data='clb_change_starting_quantity')], InlineKeyboardButton(text='Начальная ставка', callback_data='clb_change_starting_quantity')],
[InlineKeyboardButton(text='Коэффициент Мартингейла', callback_data='clb_change_martingale_factor'), [InlineKeyboardButton(text='Коэффициент Мартингейла', callback_data='clb_change_martingale_factor'),
InlineKeyboardButton(text='Максимльное кол-во ставок', callback_data='clb_change_maximum_quantity')], InlineKeyboardButton(text='Максимальное кол-во ставок', callback_data='clb_change_maximum_quantity')],
back_btn_list_settings, back_btn_list_settings,
back_btn_to_main back_btn_to_main
@@ -79,7 +90,7 @@ risk_management_settings_markup = InlineKeyboardMarkup(inline_keyboard=[
condition_settings_markup = InlineKeyboardMarkup(inline_keyboard=[ condition_settings_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='Триггер', callback_data='clb_change_trigger'), [InlineKeyboardButton(text='Триггер', callback_data='clb_change_trigger'),
InlineKeyboardButton(text='Фильтр времени', callback_data='clb_change_filter_time')], InlineKeyboardButton(text='Таймер', callback_data='clb_change_timer')],
[InlineKeyboardButton(text='Фильтр волатильности', callback_data='clb_change_filter_volatility'), [InlineKeyboardButton(text='Фильтр волатильности', callback_data='clb_change_filter_volatility'),
InlineKeyboardButton(text='Внешние сигналы', callback_data='clb_change_external_cues')], InlineKeyboardButton(text='Внешние сигналы', callback_data='clb_change_external_cues')],
@@ -123,14 +134,41 @@ margin_type_markup = InlineKeyboardMarkup(inline_keyboard=[
]) ])
trigger_markup = InlineKeyboardMarkup(inline_keyboard=[ # ИЗМЕНИТЬ НА INLINE trigger_markup = InlineKeyboardMarkup(inline_keyboard=[ # ИЗМЕНИТЬ НА INLINE
[InlineKeyboardButton(text='Ручной', callback_data="clb_trigger_ruchnoy"), InlineKeyboardButton(text='TradingView', callback_data="clb_trigger_tradingview")], [InlineKeyboardButton(text='Ручной', callback_data="clb_trigger_ruchnoy"),
InlineKeyboardButton(text='TradingView', callback_data="clb_trigger_tradingview")],
[InlineKeyboardButton(text="Автоматический", callback_data="clb_trigger_auto")] [InlineKeyboardButton(text="Автоматический", callback_data="clb_trigger_auto")]
]) ])
buttons_yes_no_markup = InlineKeyboardMarkup(inline_keyboard=[ # ИЗМЕНИТЬ НА INLINE buttons_yes_no_markup = InlineKeyboardMarkup(inline_keyboard=[ # ИЗМЕНИТЬ НА INLINE
[InlineKeyboardButton(text='Да', callback_data="clb_yes"), InlineKeyboardButton(text='Нет', callback_data="clb_yes")] [InlineKeyboardButton(text='Да', callback_data="clb_yes"),
InlineKeyboardButton(text='Нет', callback_data="clb_yes")]
]) ])
buttons_on_off_markup = InlineKeyboardMarkup(inline_keyboard=[ # ИЗМЕНИТЬ НА INLINE buttons_on_off_markup = InlineKeyboardMarkup(inline_keyboard=[ # ИЗМЕНИТЬ НА INLINE
[InlineKeyboardButton(text='Включить', callback_data="clb_on"), InlineKeyboardButton(text='Выключить', callback_data="clb_off")] [InlineKeyboardButton(text='Включить', callback_data="clb_on"),
InlineKeyboardButton(text='Выключить', callback_data="clb_off")]
])
def create_trades_inline_keyboard(trades):
buttons = []
for trade in trades:
symbol = trade['symbol'] if isinstance(trade, dict) else trade.symbol
buttons.append([
InlineKeyboardButton(text=f"{symbol}", callback_data=f"show_deal_{symbol}")
])
return InlineKeyboardMarkup(inline_keyboard=buttons)
def create_close_deal_markup(symbol: str) -> InlineKeyboardMarkup:
return InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Закрыть сделку", callback_data=f"close_deal:{symbol}")],
back_btn_to_main
])
timer_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Установить таймер", callback_data="clb_set_timer")],
[InlineKeyboardButton(text="Остановить таймер", callback_data="clb_stop_timer")],
back_btn_to_main
]) ])

View File

@@ -1,4 +1,8 @@
import logging import logging
from datetime import datetime
from sqlalchemy.sql.sqltypes import DateTime
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
from sqlalchemy import BigInteger, Boolean, Integer, String, ForeignKey from sqlalchemy import BigInteger, Boolean, Integer, String, ForeignKey
@@ -73,7 +77,11 @@ class User_Main_Settings(Base):
size_leverage = mapped_column(Integer(), default=1) size_leverage = mapped_column(Integer(), default=1)
starting_quantity = mapped_column(Integer(), default=1) starting_quantity = mapped_column(Integer(), default=1)
martingale_factor = 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) maximal_quantity = mapped_column(Integer(), default=10)
entry_order_type = mapped_column(String(10), default='Market')
limit_order_price = mapped_column(String(20), nullable=True)
class User_Risk_Management_Settings(Base): class User_Risk_Management_Settings(Base):
__tablename__ = 'user_risk_management_settings' __tablename__ = 'user_risk_management_settings'
@@ -85,6 +93,7 @@ class User_Risk_Management_Settings(Base):
price_profit = mapped_column(Integer(), default=1) price_profit = mapped_column(Integer(), default=1)
price_loss = mapped_column(Integer(), default=1) price_loss = mapped_column(Integer(), default=1)
max_risk_deal = mapped_column(Integer(), default=100) max_risk_deal = mapped_column(Integer(), default=100)
commission_fee = mapped_column(Integer(), default=0)
class User_Condition_Settings(Base): class User_Condition_Settings(Base):
__tablename__ = 'user_condition_settings' __tablename__ = 'user_condition_settings'
@@ -137,3 +146,26 @@ async def async_main():
if not result.first(): if not result.first():
logger.info("Заполение таблицы триггеров") logger.info("Заполение таблицы триггеров")
await conn.execute(Trigger.__table__.insert().values(trigger=trigger)) await conn.execute(Trigger.__table__.insert().values(trigger=trigger))
class USER_DEALS(Base):
__tablename__ = 'user_deals'
id: Mapped[int] = mapped_column(primary_key=True)
tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"))
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):
__tablename__ = 'user_timers'
id: Mapped[int] = mapped_column(primary_key=True)
tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"))
timer_minutes = mapped_column(Integer, nullable=False, default=0)
timer_start = mapped_column(DateTime, default=datetime.utcnow)
timer_end = mapped_column(DateTime, nullable=True)

View File

@@ -1,5 +1,10 @@
import logging import logging.config
logger = logging.getLogger(__name__) from logger_helper.logger_helper import LOGGING_CONFIG
from datetime import datetime, timedelta
from typing import Any
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("requests")
from app.telegram.database.models import async_session from app.telegram.database.models import async_session
from app.telegram.database.models import User_Telegram_Id as UTi from app.telegram.database.models import User_Telegram_Id as UTi
@@ -12,11 +17,13 @@ from app.telegram.database.models import User_Additional_Settings as UAS
from app.telegram.database.models import Trading_Mode from app.telegram.database.models import Trading_Mode
from app.telegram.database.models import Margin_type from app.telegram.database.models import Margin_type
from app.telegram.database.models import Trigger from app.telegram.database.models import Trigger
from app.telegram.database.models import USER_DEALS, UserTimer
import app.telegram.functions.functions as func # functions import app.telegram.functions.functions as func # functions
from sqlalchemy import select, delete, update from sqlalchemy import select, delete, update
# SET_DB # SET_DB
async def save_tg_id_new_user(tg_id): async def save_tg_id_new_user(tg_id):
async with async_session() as session: async with async_session() as session:
@@ -25,10 +32,11 @@ async def save_tg_id_new_user(tg_id):
if not user: if not user:
session.add(UTi(tg_id=tg_id)) session.add(UTi(tg_id=tg_id))
logger.info("Новый пользователь был добавлен в бд") logger.info("Новый пользователь был добавлен в бд %s", tg_id)
await session.commit() await session.commit()
async def set_new_user_bybit_api(tg_id): async def set_new_user_bybit_api(tg_id):
async with async_session() as session: async with async_session() as session:
user = await session.scalar(select(UBA).where(UBA.tg_id == tg_id)) user = await session.scalar(select(UBA).where(UBA.tg_id == tg_id))
@@ -38,10 +46,9 @@ async def set_new_user_bybit_api(tg_id):
tg_id=tg_id, tg_id=tg_id,
)) ))
logger.info(f"Bybit был успешно подключен")
await session.commit() await session.commit()
async def set_new_user_symbol(tg_id): async def set_new_user_symbol(tg_id):
async with async_session() as session: async with async_session() as session:
user = await session.scalar(select(User_Symbol).where(User_Symbol.tg_id == tg_id)) user = await session.scalar(select(User_Symbol).where(User_Symbol.tg_id == tg_id))
@@ -51,10 +58,11 @@ async def set_new_user_symbol(tg_id):
tg_id=tg_id tg_id=tg_id
)) ))
logger.info(f"Symbol был успешно добавлен") logger.info(f"Symbol был успешно добавлен %s", tg_id)
await session.commit() await session.commit()
async def set_new_user_default_main_settings(tg_id, trading_mode, margin_type) -> None: async def set_new_user_default_main_settings(tg_id, trading_mode, margin_type) -> None:
async with async_session() as session: async with async_session() as session:
settings = await session.scalar(select(UMS).where(UMS.tg_id == tg_id)) settings = await session.scalar(select(UMS).where(UMS.tg_id == tg_id))
@@ -66,10 +74,11 @@ async def set_new_user_default_main_settings(tg_id, trading_mode, margin_type) -
margin_type=margin_type, margin_type=margin_type,
)) ))
logger.info("Основные настройки нового пользователя были заполнены") logger.info("Основные настройки нового пользователя были заполнены%s", tg_id)
await session.commit() await session.commit()
async def set_new_user_default_risk_management_settings(tg_id) -> None: async def set_new_user_default_risk_management_settings(tg_id) -> None:
async with async_session() as session: async with async_session() as session:
settings = await session.scalar(select(URMS).where(URMS.tg_id == tg_id)) settings = await session.scalar(select(URMS).where(URMS.tg_id == tg_id))
@@ -79,10 +88,11 @@ async def set_new_user_default_risk_management_settings(tg_id) -> None:
tg_id=tg_id tg_id=tg_id
)) ))
logger.info("Риск-Менеджмент настройки нового пользователя были заполнены") logger.info("Риск-Менеджмент настройки нового пользователя были заполнены %s", tg_id)
await session.commit() await session.commit()
async def set_new_user_default_condition_settings(tg_id, trigger) -> None: async def set_new_user_default_condition_settings(tg_id, trigger) -> None:
async with async_session() as session: async with async_session() as session:
settings = await session.scalar(select(UCS).where(UCS.tg_id == tg_id)) settings = await session.scalar(select(UCS).where(UCS.tg_id == tg_id))
@@ -93,10 +103,11 @@ async def set_new_user_default_condition_settings(tg_id, trigger) -> None:
trigger=trigger trigger=trigger
)) ))
logger.info("Условные настройки нового пользователя были заполнены") logger.info("Условные настройки нового пользователя были заполнены %s", tg_id)
await session.commit() await session.commit()
async def set_new_user_default_additional_settings(tg_id) -> None: async def set_new_user_default_additional_settings(tg_id) -> None:
async with async_session() as session: async with async_session() as session:
settings = await session.scalar(select(UAS).where(UAS.tg_id == tg_id)) settings = await session.scalar(select(UAS).where(UAS.tg_id == tg_id))
@@ -106,52 +117,85 @@ async def set_new_user_default_additional_settings(tg_id) -> None:
tg_id=tg_id, tg_id=tg_id,
)) ))
logger.info("Дополнительные настройки нового пользователя были заполнены") logger.info("Дополнительные настройки нового пользователя были заполнены %s", tg_id)
await session.commit() await session.commit()
# GET_DB # GET_DB
async def check_user(tg_id): async def check_user(tg_id):
async with async_session() as session: async with async_session() as session:
user = await session.scalar(select(UTi).where(UTi.tg_id == tg_id)) user = await session.scalar(select(UTi).where(UTi.tg_id == tg_id))
return user return user
async def get_bybit_api_key(tg_id): async def get_bybit_api_key(tg_id):
async with async_session() as session: async with async_session() as session:
api_key = await session.scalar(select(UBA.api_key).where(UBA.tg_id == tg_id)) api_key = await session.scalar(select(UBA.api_key).where(UBA.tg_id == tg_id))
return api_key return api_key
async def get_bybit_secret_key(tg_id): async def get_bybit_secret_key(tg_id):
async with async_session() as session: async with async_session() as session:
secret_key = await session.scalar(select(UBA.secret_key).where(UBA.tg_id == tg_id)) secret_key = await session.scalar(select(UBA.secret_key).where(UBA.tg_id == tg_id))
return secret_key return secret_key
async def get_symbol(tg_id): async def get_symbol(tg_id):
async with async_session() as session: async with async_session() as session:
symbol = await session.scalar(select(User_Symbol.symbol).where(User_Symbol.tg_id == tg_id)) symbol = await session.scalar(select(User_Symbol.symbol).where(User_Symbol.tg_id == tg_id))
return symbol 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 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 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 def get_for_registration_trading_mode():
async with async_session() as session: async with async_session() as session:
mode = await session.scalar(select(Trading_Mode.mode).where(Trading_Mode.id == 1)) mode = await session.scalar(select(Trading_Mode.mode).where(Trading_Mode.id == 1))
return mode return mode
async def get_for_registration_margin_type(): async def get_for_registration_margin_type():
async with async_session() as session: async with async_session() as session:
type = await session.scalar(select(Margin_type.type).where(Margin_type.id == 1)) type = await session.scalar(select(Margin_type.type).where(Margin_type.id == 1))
return type return type
async def get_for_registration_trigger(): async def get_for_registration_trigger():
async with async_session() as session: async with async_session() as session:
trigger = await session.scalar(select(Trigger.trigger).where(Trigger.id == 1)) trigger = await session.scalar(select(Trigger.trigger).where(Trigger.id == 1))
return trigger return trigger
async def get_user_main_settings(tg_id): async def get_user_main_settings(tg_id):
async with async_session() as session: async with async_session() as session:
user = await session.scalar(select(UMS).where(UMS.tg_id == tg_id)) user = await session.scalar(select(UMS).where(UMS.tg_id == tg_id))
if user: if user:
logger.info("Получение основных настроек пользователя") logger.info("Получение основных настроек пользователя %s", tg_id)
trading_mode = await session.scalar(select(UMS.trading_mode).where(UMS.tg_id == tg_id)) trading_mode = await session.scalar(select(UMS.trading_mode).where(UMS.tg_id == tg_id))
margin_mode = await session.scalar(select(UMS.margin_type).where(UMS.tg_id == tg_id)) margin_mode = await session.scalar(select(UMS.margin_type).where(UMS.tg_id == tg_id))
@@ -171,105 +215,247 @@ async def get_user_main_settings(tg_id):
return data return data
async def get_user_risk_management_settings(tg_id): async def get_user_risk_management_settings(tg_id):
async with async_session() as session: async with async_session() as session:
user = await session.scalar(select(URMS).where(URMS.tg_id == tg_id)) user = await session.scalar(select(URMS).where(URMS.tg_id == tg_id))
if user: if user:
logger.info("Получение риск-менеджмента настроек пользователя") logger.info("Получение риск-менеджмента настроек пользователя %s", tg_id)
price_profit = await session.scalar(select(URMS.price_profit).where(URMS.tg_id == 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)) 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)) 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 = { data = {
'price_profit': price_profit, 'price_profit': price_profit,
'price_loss': price_loss, 'price_loss': price_loss,
'max_risk_deal': max_risk_deal 'max_risk_deal': max_risk_deal,
'commission_fee': commission_fee,
} }
return data return data
#UPDATE_SYMBOL
# UPDATE_SYMBOL
async def update_symbol(tg_id, symbol) -> None: async def update_symbol(tg_id, symbol) -> None:
async with async_session() as session: async with async_session() as session:
await session.execute(update(User_Symbol).where(User_Symbol.tg_id == tg_id).values(symbol = symbol)) await session.execute(update(User_Symbol).where(User_Symbol.tg_id == tg_id).values(symbol=symbol))
await session.commit() await session.commit()
async def update_api_key(tg_id, api): async def update_api_key(tg_id, api):
async with async_session() as session: async with async_session() as session:
api_key = await session.execute(update(UBA).where(UBA.tg_id == tg_id).values(api_key = api)) api_key = await session.execute(update(UBA).where(UBA.tg_id == tg_id).values(api_key=api))
await session.commit() await session.commit()
async def update_secret_key(tg_id, api): async def update_secret_key(tg_id, api):
async with async_session() as session: async with async_session() as session:
secret_key = await session.execute(update(UBA).where(UBA.tg_id == tg_id).values(secret_key = api)) secret_key = await session.execute(update(UBA).where(UBA.tg_id == tg_id).values(secret_key=api))
await session.commit() await session.commit()
# UPDATE_MAIN_SETTINGS_DB # UPDATE_MAIN_SETTINGS_DB
async def update_trade_mode_user(tg_id, trading_mode) -> None: async def update_trade_mode_user(tg_id, trading_mode) -> None:
async with async_session() as session: async with async_session() as session:
mode = await session.scalar(select(Trading_Mode.mode).where(Trading_Mode.mode == trading_mode)) mode = await session.scalar(select(Trading_Mode.mode).where(Trading_Mode.mode == trading_mode))
if mode: if mode:
logger.info("Изменен трейд мод") logger.info("Изменен трейд мод %s", tg_id)
await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(trading_mode = mode)) await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(trading_mode=mode))
await session.commit() await session.commit()
async def update_margin_type(tg_id, margin_type) -> None: async def update_margin_type(tg_id, margin_type) -> None:
async with async_session() as session: async with async_session() as session:
type = await session.scalar(select(Margin_type.type).where(Margin_type.type == margin_type)) type = await session.scalar(select(Margin_type.type).where(Margin_type.type == margin_type))
if type: if type:
logger.info("Изменен тип маржи") logger.info("Изменен тип маржи %s", tg_id)
await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(margin_type = type)) await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(margin_type=type))
await session.commit() await session.commit()
async def update_size_leverange(tg_id, num): async def update_size_leverange(tg_id, num):
async with async_session() as session: async with async_session() as session:
await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(size_leverage = num)) await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(size_leverage=num))
await session.commit() await session.commit()
async def update_starting_quantity(tg_id, num): async def update_starting_quantity(tg_id, num):
async with async_session() as session: async with async_session() as session:
await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(starting_quantity = num)) await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(starting_quantity=num))
await session.commit() await session.commit()
async def update_martingale_factor(tg_id, num): async def update_martingale_factor(tg_id, num):
async with async_session() as session: async with async_session() as session:
await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(martingale_factor = num)) await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(martingale_factor=num))
await session.commit() await session.commit()
async def update_maximal_quantity(tg_id, num): async def update_maximal_quantity(tg_id, num):
async with async_session() as session: async with async_session() as session:
await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(maximal_quantity = num)) await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(maximal_quantity=num))
await session.commit() await session.commit()
# UPDATE_RISK_MANAGEMENT_SETTINGS_DB # UPDATE_RISK_MANAGEMENT_SETTINGS_DB
async def update_price_profit(tg_id, num): async def update_price_profit(tg_id, num):
async with async_session() as session: async with async_session() as session:
await session.execute(update(URMS).where(URMS.tg_id == tg_id).values(price_profit = num)) await session.execute(update(URMS).where(URMS.tg_id == tg_id).values(price_profit=num))
await session.commit() await session.commit()
async def update_price_loss(tg_id, num): async def update_price_loss(tg_id, num):
async with async_session() as session: async with async_session() as session:
await session.execute(update(URMS).where(URMS.tg_id == tg_id).values(price_loss = num)) await session.execute(update(URMS).where(URMS.tg_id == tg_id).values(price_loss=num))
await session.commit() await session.commit()
async def update_max_risk_deal(tg_id, num): async def update_max_risk_deal(tg_id, num):
async with async_session() as session: async with async_session() as session:
await session.execute(update(URMS).where(URMS.tg_id == tg_id).values(max_risk_deal = num)) 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_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 get_limit_price(tg_id):
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:
async with async_session() as session:
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() await session.commit()

View File

@@ -1,6 +1,21 @@
import app.telegram.Keyboards.inline_keyboards as inline_markup 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 import app.telegram.database.requests as rq
from aiogram.fsm.state import State, StatesGroup
condition_settings_router = Router()
class condition_settings(StatesGroup):
trigger = State()
timer = State()
volatilty = State()
volume = State()
integration = State()
use_tv_signal = State()
async def reg_new_user_default_condition_settings(id, message): async def reg_new_user_default_condition_settings(id, message):
tg_id = id tg_id = id
@@ -9,11 +24,12 @@ async def reg_new_user_default_condition_settings(id, message):
await rq.set_new_user_default_condition_settings(tg_id, trigger) await rq.set_new_user_default_condition_settings(tg_id, trigger)
async def main_settings_message(id, message, state): async def main_settings_message(id, message, state):
text = """ <b>Условия запуска</b> text = """ <b>Условия запуска</b>
<b>- Триггер:</b> Ручной запуск / Сигнал TradingView / Полностью автоматический <b>- Триггер:</b> Ручной запуск / Сигнал TradingView / Полностью автоматический
<b>- Фильтр времени: </b> диапазон по дням недели и времени суток <b>- Таймер: </b> установить таймер / остановить таймер
<b>- Фильтр волатильности / объёма: </b> включить/отключить <b>- Фильтр волатильности / объёма: </b> включить/отключить
<b>- Интеграции и внешние сигналы: </b> <b>- Интеграции и внешние сигналы: </b>
<b>- Использовать сигналы TradingView:</b> да / нет <b>- Использовать сигналы TradingView:</b> да / нет
@@ -22,6 +38,7 @@ async def main_settings_message(id, message, state):
""" """
await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.condition_settings_markup) await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.condition_settings_markup)
async def trigger_message(message, state): async def trigger_message(message, state):
text = '''Триггер text = '''Триггер
@@ -29,13 +46,52 @@ async def trigger_message(message, state):
await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.trigger_markup) await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.trigger_markup)
async def filter_time_message(message, state):
text = '''Фильтр времени
??? 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"
f"Осталось: {timer_info['remaining_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):")
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)
await message.answer(f"Таймер установлен на {minutes} минут.", reply_markup=inline_markup.back_to_main)
await state.clear()
except ValueError:
await message.reply("Пожалуйста, введите корректное число.")
@condition_settings_router.callback_query(F.data == "clb_stop_timer")
async def stop_timer_callback(callback: CallbackQuery):
await rq.update_user_timer(callback.from_user.id, 0) # обнуляем таймер
await callback.message.answer("Таймер остановлен.", reply_markup=inline_markup.back_to_main)
await callback.answer()
await message.answer(text=text)
async def filter_volatility_message(message, state): async def filter_volatility_message(message, state):
text = '''Фильтр волатильности text = '''Фильтр волатильности
@@ -44,6 +100,7 @@ async def filter_volatility_message(message, state):
await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.buttons_on_off_markup) await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.buttons_on_off_markup)
async def external_cues_message(message, state): async def external_cues_message(message, state):
text = '''<b>Внешние сигналы</b> text = '''<b>Внешние сигналы</b>
@@ -51,6 +108,7 @@ async def external_cues_message(message, state):
await message.answer(text=text, parse_mode='html', reply_markup=None) await message.answer(text=text, parse_mode='html', reply_markup=None)
async def trading_cues_message(message, state): async def trading_cues_message(message, state):
text = '''<b>Использование сигналов</b> text = '''<b>Использование сигналов</b>
@@ -58,16 +116,16 @@ async def trading_cues_message(message, state):
await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.buttons_yes_no_markup) await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.buttons_yes_no_markup)
async def webhook_message(message, state): async def webhook_message(message, state):
text = '''Скиньте ссылку на <b>webhook</b> (если есть trading view): ''' text = '''Скиньте ссылку на <b>webhook</b> (если есть trading view): '''
await message.answer(text=text, parse_mode='html') await message.answer(text=text, parse_mode='html')
async def ai_analytics_message(message, state): async def ai_analytics_message(message, state):
text = '''<b>ИИ - Аналитика</b> text = '''<b>ИИ - Аналитика</b>
Описание... ''' Описание... '''
await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.buttons_yes_no_markup) await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.buttons_yes_no_markup)

View File

@@ -38,7 +38,7 @@ async def main_settings_message(id, message, state):
<b>- Размер кредитного плеча:</b> х{data['size_leverage']} <b>- Размер кредитного плеча:</b> х{data['size_leverage']}
<b>- Начальная ставка:</b> {data['starting_quantity']} <b>- Начальная ставка:</b> {data['starting_quantity']}
<b>- Коэффициент мартингейла:</b> {data['martingale_factor']} <b>- Коэффициент мартингейла:</b> {data['martingale_factor']}
<b>- Максимальное количесиво ставок в серии:</b> {data['maximal_quantity']} <b>- Максимальное количество ставок в серии:</b> {data['maximal_quantity']}
""", parse_mode='html', reply_markup=inline_markup.main_settings_markup) """, parse_mode='html', reply_markup=inline_markup.main_settings_markup)
async def trading_mode_message(message, state): async def trading_mode_message(message, state):
@@ -189,7 +189,7 @@ async def state_margin_type(callback: CallbackQuery, state):
async def starting_quantity_message (message, state): async def starting_quantity_message (message, state):
await state.set_state(update_main_settings.starting_quantity) 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) 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) @router_main_settings.message(update_main_settings.starting_quantity)
async def state_starting_quantity(message: Message, state): async def state_starting_quantity(message: Message, state):

View File

@@ -14,6 +14,7 @@ class update_risk_management_settings(StatesGroup):
price_profit = State() price_profit = State()
price_loss = State() price_loss = State()
max_risk_deal = State() max_risk_deal = State()
commission_fee = State()
async def reg_new_user_default_risk_management_settings(id, message): async def reg_new_user_default_risk_management_settings(id, message):
tg_id = id tg_id = id
@@ -25,10 +26,11 @@ async def main_settings_message(id, message, state):
text = f"""<b>Риск менеджмент</b>, text = f"""<b>Риск менеджмент</b>,
<b>- Процент изменения цены для фиксации прибыли:</b> {data['price_profit']}% <b>- Процент изменения цены для фиксации прибыли:</b> {data.get('price_profit', 0)}%
<b>- Процент изменения цены для фиксации убытков:</b> {data['price_loss']}% <b>- Процент изменения цены для фиксации убытков:</b> {data.get('price_loss', 0)}%
<b>- Максимальный риск на сделку (в % от баланса):</b> {data['max_risk_deal']}% <b>- Максимальный риск на сделку (в % от баланса):</b> {data.get('max_risk_deal', 0)}%
""" <b>- Комиссия биржи для расчета процента фиксации прибыли:</b> {data.get('commission_fee', 0)}%
"""
await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.risk_management_settings_markup) await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.risk_management_settings_markup)
async def price_profit_message(message, state): async def price_profit_message(message, state):
@@ -72,15 +74,36 @@ async def state_price_loss(message: Message, state):
data_settings = await rq.get_user_risk_management_settings(message.from_user.id) data_settings = await rq.get_user_risk_management_settings(message.from_user.id)
if data['price_loss'].isdigit() and int(data['price_loss']) <= 100: if data['price_loss'].isdigit() and int(data['price_loss']) <= 100:
await message.answer(f"✅ Изменено: {data_settings['price_loss']}% → {data['price_loss']}%") 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:
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 rq.update_price_loss(message.from_user.id, data['price_loss'])
await main_settings_message(message.from_user.id, message, state) await main_settings_message(message.from_user.id, message, state)
await state.clear() await state.clear()
else: else:
await message.answer(f'⛔️ Ошибка: ваше значение ({data['price_loss']}%) или выше лимита (100) или вы вводите неверные символы') await message.answer(
f'⛔️ Ошибка: ваше значение ({data["price_loss"]}%) выше лимита (100) или содержит неверные символы')
await main_settings_message(message.from_user.id, message, state) await main_settings_message(message.from_user.id, message, state)
async def max_risk_deal_message(message, state): async def max_risk_deal_message(message, state):
@@ -108,3 +131,27 @@ async def state_max_risk_deal(message: Message, state):
await message.answer(f'⛔️ Ошибка: ваше значение ({data['max_risk_deal']}%) или выше лимита (100) или вы вводите неверные символы') await message.answer(f'⛔️ Ошибка: ваше значение ({data['max_risk_deal']}%) или выше лимита (100) или вы вводите неверные символы')
await main_settings_message(message.from_user.id, message, state) await main_settings_message(message.from_user.id, message, state)
async def commission_fee_message(message, state):
await state.set_state(update_risk_management_settings.commission_fee)
await message.answer(text="Введите процент комиссии биржи (например, 0.1):", parse_mode='html', reply_markup=None)
@router_risk_management_settings.message(update_risk_management_settings.commission_fee)
async def state_commission_fee(message: Message, state):
await state.update_data(commission_fee=message.text)
data = await state.get_data()
data_settings = await rq.get_user_risk_management_settings(message.from_user.id)
try:
val = float(data['commission_fee'])
if val < 0 or val > 100:
raise ValueError()
except Exception:
await message.answer("⛔️ Ошибка: введите корректный процент комиссии от 0 до 100")
return await commission_fee_message(message, state)
await rq.update_commission_fee(message.from_user.id, val)
await message.answer(f"✅ Изменено: {data_settings['commission_fee']}% → {data['commission_fee']}%")
await main_settings_message(message.from_user.id, message, state)
await state.clear()

View File

@@ -1,4 +1,4 @@
import logging import logging.config
from aiogram import F, Router from aiogram import F, Router
from aiogram.filters import CommandStart, Command from aiogram.filters import CommandStart, Command
@@ -14,14 +14,21 @@ import app.telegram.functions.additional_settings.settings as func_additional_se
import app.telegram.database.requests as rq import app.telegram.database.requests as rq
import app.telegram.Keyboards.inline_keyboards as inline_markup import app.telegram.Keyboards.inline_keyboards as inline_markup
import app.telegram.Keyboards.reply_keyboards as reply_markup import app.telegram.Keyboards.reply_keyboards as reply_markup
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("handlers")
router = Router() router = Router()
@router.message(CommandStart()) @router.message(CommandStart())
async def start_message(message: Message): async def start_message(message: Message):
await rq.set_new_user_bybit_api(message.from_user.id) await rq.set_new_user_bybit_api(message.from_user.id)
await func.start_message(message) await func.start_message(message)
@router.message(F.text == "👤 Профиль") @router.message(F.text == "👤 Профиль")
async def profile_message(message: Message): async def profile_message(message: Message):
user = await rq.check_user(message.from_user.id) user = await rq.check_user(message.from_user.id)
@@ -29,6 +36,7 @@ async def profile_message(message: Message):
if user: if user:
await func.profile_message(message.from_user.username, message) await func.profile_message(message.from_user.username, message)
@router.message(F.text == "Настройки") @router.message(F.text == "Настройки")
async def settings_msg(message: Message): async def settings_msg(message: Message):
user = await rq.check_user(message.from_user.id) user = await rq.check_user(message.from_user.id)
@@ -36,8 +44,9 @@ async def settings_msg(message: Message):
if user: if user:
await func.settings_message(message) await func.settings_message(message)
@router.callback_query(F.data == "clb_start_chatbot_message") @router.callback_query(F.data == "clb_start_chatbot_message")
async def clb_profile_msg (callback: CallbackQuery): async def clb_profile_msg(callback: CallbackQuery):
user = await rq.check_user(callback.from_user.id) user = await rq.check_user(callback.from_user.id)
username = '' username = ''
@@ -55,7 +64,8 @@ async def clb_profile_msg (callback: CallbackQuery):
await rq.save_tg_id_new_user(callback.from_user.id) 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_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_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, callback.message) await func_condition_settings.reg_new_user_default_condition_settings(callback.from_user.id, callback.message)
await func_additional_settings.reg_new_user_default_additional_settings(callback.from_user.id, callback.message) await func_additional_settings.reg_new_user_default_additional_settings(callback.from_user.id, callback.message)
@@ -65,51 +75,60 @@ async def clb_profile_msg (callback: CallbackQuery):
await callback.answer() await callback.answer()
# Настройки торговли
# Настройки торговли
@router.callback_query(F.data == "clb_settings_message") @router.callback_query(F.data == "clb_settings_message")
async def clb_settings_msg (callback: CallbackQuery): async def clb_settings_msg(callback: CallbackQuery):
await func.settings_message(callback.message) await func.settings_message(callback.message)
await callback.answer() await callback.answer()
@router.callback_query(F.data == "clb_back_to_special_settings_message") @router.callback_query(F.data == "clb_back_to_special_settings_message")
async def clb_back_to_settings_msg(callback: CallbackQuery): async def clb_back_to_settings_msg(callback: CallbackQuery):
await func.settings_message(callback.message) await func.settings_message(callback.message)
await callback.answer() await callback.answer()
@router.callback_query(F.data == "clb_change_main_settings") @router.callback_query(F.data == "clb_change_main_settings")
async def clb_change_main_settings_message(callback: CallbackQuery, state: FSMContext): async def clb_change_main_settings_message(callback: CallbackQuery, state: FSMContext):
await func_main_settings.main_settings_message(callback.from_user.id, callback.message, state) await func_main_settings.main_settings_message(callback.from_user.id, callback.message, state)
await callback.answer() await callback.answer()
@router.callback_query(F.data == "clb_change_risk_management_settings") @router.callback_query(F.data == "clb_change_risk_management_settings")
async def clb_change_risk_management_message(callback: CallbackQuery, state: FSMContext): async def clb_change_risk_management_message(callback: CallbackQuery, state: FSMContext):
await func_rmanagement_settings.main_settings_message(callback.from_user.id, callback.message, state) await func_rmanagement_settings.main_settings_message(callback.from_user.id, callback.message, state)
await callback.answer() await callback.answer()
@router.callback_query(F.data == "clb_change_condition_settings") @router.callback_query(F.data == "clb_change_condition_settings")
async def clb_change_condition_message(callback: CallbackQuery, state: FSMContext): async def clb_change_condition_message(callback: CallbackQuery, state: FSMContext):
await func_condition_settings.main_settings_message(callback.from_user.id, callback.message, state) await func_condition_settings.main_settings_message(callback.from_user.id, callback.message, state)
await callback.answer() await callback.answer()
@router.callback_query(F.data == "clb_change_additional_settings") @router.callback_query(F.data == "clb_change_additional_settings")
async def clb_change_additional_message(callback: CallbackQuery, state: FSMContext): async def clb_change_additional_message(callback: CallbackQuery, state: FSMContext):
await func_additional_settings.main_settings_message(callback.from_user.id, callback.message, state) await func_additional_settings.main_settings_message(callback.from_user.id, callback.message, state)
await callback.answer() await callback.answer()
# Конкретные настройки каталогов
# Конкретные настройки каталогов
list_main_settings = ['clb_change_trading_mode', list_main_settings = ['clb_change_trading_mode',
'clb_change_margin_type', 'clb_change_margin_type',
'clb_change_size_leverage', 'clb_change_size_leverage',
'clb_change_starting_quantity', 'clb_change_starting_quantity',
'clb_change_martingale_factor', 'clb_change_martingale_factor',
'clb_change_maximum_quantity' 'clb_change_maximum_quantity'
] ]
@router.callback_query(F.data.in_(list_main_settings)) @router.callback_query(F.data.in_(list_main_settings))
async def clb_main_settings_msg(callback: CallbackQuery, state: FSMContext): async def clb_main_settings_msg(callback: CallbackQuery, state: FSMContext):
await callback.answer() await callback.answer()
@@ -129,13 +148,16 @@ async def clb_main_settings_msg(callback: CallbackQuery, state: FSMContext):
case 'clb_change_maximum_quantity': case 'clb_change_maximum_quantity':
await func_main_settings.maximum_quantity_message(callback.message, state) await func_main_settings.maximum_quantity_message(callback.message, state)
except Exception as e: except Exception as e:
logging.error(f"Error callback in main_settings match-case: {e}") logger.error(f"Error callback in main_settings match-case: {e}")
list_risk_management_settings = ['clb_change_price_profit', list_risk_management_settings = ['clb_change_price_profit',
'clb_change_price_loss', 'clb_change_price_loss',
'clb_change_max_risk_deal', 'clb_change_max_risk_deal',
] 'commission_fee',
]
@router.callback_query(F.data.in_(list_risk_management_settings)) @router.callback_query(F.data.in_(list_risk_management_settings))
async def clb_risk_management_settings_msg(callback: CallbackQuery, state: FSMContext): async def clb_risk_management_settings_msg(callback: CallbackQuery, state: FSMContext):
await callback.answer() await callback.answer()
@@ -148,18 +170,22 @@ async def clb_risk_management_settings_msg(callback: CallbackQuery, state: FSMCo
await func_rmanagement_settings.price_loss_message(callback.message, state) await func_rmanagement_settings.price_loss_message(callback.message, state)
case 'clb_change_max_risk_deal': case 'clb_change_max_risk_deal':
await func_rmanagement_settings.max_risk_deal_message(callback.message, state) 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: except Exception as e:
logging.error(f"Error callback in risk_management match-case: {e}") logger.error(f"Error callback in risk_management match-case: {e}")
list_condition_settings = ['clb_change_trigger', list_condition_settings = ['clb_change_trigger',
'clb_change_filter_time', 'clb_change_timer',
'clb_change_filter_volatility', 'clb_change_filter_volatility',
'clb_change_external_cues', 'clb_change_external_cues',
'clb_change_tradingview_cues', 'clb_change_tradingview_cues',
'clb_change_webhook', 'clb_change_webhook',
'clb_change_ai_analytics' 'clb_change_ai_analytics'
] ]
@router.callback_query(F.data.in_(list_condition_settings)) @router.callback_query(F.data.in_(list_condition_settings))
async def clb_condition_settings_msg(callback: CallbackQuery, state: FSMContext): async def clb_condition_settings_msg(callback: CallbackQuery, state: FSMContext):
await callback.answer() await callback.answer()
@@ -168,8 +194,8 @@ async def clb_condition_settings_msg(callback: CallbackQuery, state: FSMContext)
match callback.data: match callback.data:
case 'clb_change_trigger': case 'clb_change_trigger':
await func_condition_settings.trigger_message(callback.message, state) await func_condition_settings.trigger_message(callback.message, state)
case 'clb_change_filter_time': case 'clb_change_timer':
await func_condition_settings.filter_time_message(callback.message, state) await func_condition_settings.timer_message(callback.from_user.id, callback.message, state)
case 'clb_change_filter_volatility': case 'clb_change_filter_volatility':
await func_condition_settings.filter_volatility_message(callback.message, state) await func_condition_settings.filter_volatility_message(callback.message, state)
case 'clb_change_external_cues': case 'clb_change_external_cues':
@@ -181,13 +207,15 @@ async def clb_condition_settings_msg(callback: CallbackQuery, state: FSMContext)
case 'clb_change_ai_analytics': case 'clb_change_ai_analytics':
await func_condition_settings.ai_analytics_message(callback.message, state) await func_condition_settings.ai_analytics_message(callback.message, state)
except Exception as e: except Exception as e:
logging.error(f"Error callback in main_settings match-case: {e}") logger.error(f"Error callback in main_settings match-case: {e}")
list_additional_settings = ['clb_change_save_pattern', list_additional_settings = ['clb_change_save_pattern',
'clb_change_auto_start', 'clb_change_auto_start',
'clb_change_notifications', 'clb_change_notifications',
] ]
@router.callback_query(F.data.in_(list_additional_settings)) @router.callback_query(F.data.in_(list_additional_settings))
async def clb_additional_settings_msg(callback: CallbackQuery, state: FSMContext): async def clb_additional_settings_msg(callback: CallbackQuery, state: FSMContext):
await callback.answer() await callback.answer()
@@ -201,4 +229,4 @@ async def clb_additional_settings_msg(callback: CallbackQuery, state: FSMContext
case 'clb_change_notifications': case 'clb_change_notifications':
await func_additional_settings.notifications_message(callback.message, state) await func_additional_settings.notifications_message(callback.message, state)
except Exception as e: except Exception as e:
logging.error(f"Error callback in additional_settings match-case: {e}") logger.error(f"Error callback in additional_settings match-case: {e}")

View File

@@ -1,8 +0,0 @@
import logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

View File

@@ -1,6 +1,9 @@
from dotenv import load_dotenv from dotenv import load_dotenv, find_dotenv
import os import os
load_dotenv('.env') env_file = find_dotenv()
load_dotenv(env_file)
TOKEN_TG_BOT = os.getenv('TOKEN_TELEGRAM_BOT') TOKEN_TG_BOT_1 = os.getenv('TOKEN_TELEGRAM_BOT_1')
TOKEN_TG_BOT_2 = os.getenv('TOKEN_TELEGRAM_BOT_2')
TOKEN_TG_BOT_3 = os.getenv('TOKEN_TELEGRAM_BOT_3')

BIN
db.sqlite3 Normal file

Binary file not shown.

View File

@@ -0,0 +1,84 @@
import os
current_directory = os.path.dirname(os.path.abspath(__file__))
log_directory = os.path.join(current_directory, 'loggers')
os.makedirs(log_directory, exist_ok=True)
log_filename = os.path.join(log_directory, 'app.log')
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"format": "%(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",
},
"console": {
"class": "logging.StreamHandler",
"formatter": "default",
},
},
"loggers": {
"main": {
"handlers": ["console", "timed_rotating_file"],
"level": "DEBUG",
"propagate": False,
},
"add_bybit_api": {
"handlers": ["console", "timed_rotating_file"],
"level": "DEBUG",
"propagate": False,
},
"balance": {
"handlers": ["console", "timed_rotating_file"],
"level": "DEBUG",
"propagate": False,
},
"functions": {
"handlers": ["console", "timed_rotating_file"],
"level": "DEBUG",
"propagate": False,
},
"futures": {
"handlers": ["console", "timed_rotating_file"],
"level": "DEBUG",
"propagate": False,
},
"get_valid_symbol": {
"handlers": ["console", "timed_rotating_file"],
"level": "DEBUG",
"propagate": False,
},
"min_qty": {
"handlers": ["console", "timed_rotating_file"],
"level": "DEBUG",
"propagate": False,
},
"price_symbol": {
"handlers": ["console", "timed_rotating_file"],
"level": "DEBUG",
"propagate": False,
},
"requests": {
"handlers": ["console", "timed_rotating_file"],
"level": "DEBUG",
"propagate": False,
},
"handlers": {
"handlers": ["console", "timed_rotating_file"],
"level": "DEBUG",
"propagate": False,
},
},
}

View File

@@ -0,0 +1,35 @@
2025-08-23 12:57:26 - main - INFO - Bot is off
2025-08-23 13:04:01 - main - INFO - Bot is off
2025-08-23 13:25:04 - main - INFO - Bot is off
2025-08-23 13:26:24 - main - INFO - Bot is off
2025-08-23 13:28:36 - main - INFO - Bot is off
2025-08-23 13:29:29 - main - INFO - Bot is off
2025-08-23 13:30:48 - main - INFO - Bot is off
2025-08-23 13:31:43 - main - INFO - Bot is off
2025-08-23 13:33:10 - main - INFO - Bot is off
2025-08-23 13:34:59 - main - INFO - Bot is off
2025-08-23 13:36:15 - main - INFO - Bot is off
2025-08-23 13:49:17 - main - INFO - Bot is off
2025-08-23 13:50:22 - main - INFO - Bot is on
2025-08-23 13:51:30 - main - INFO - Bot is off
2025-08-23 13:51:37 - main - INFO - Bot is on
2025-08-23 13:52:12 - main - INFO - Bot is off
2025-08-23 13:57:48 - main - INFO - Bot is on
2025-08-23 14:05:36 - main - INFO - Bot is off
2025-08-23 14:05:43 - main - INFO - Bot is on
2025-08-23 14:06:03 - main - INFO - Bot is off
2025-08-23 14:06:46 - main - INFO - Bot is on
2025-08-23 14:07:04 - requests - INFO - Bybit был успешно подключен
2025-08-23 14:07:43 - requests - INFO - Новый пользователь был добавлен в бд
2025-08-23 14:07:43 - requests - INFO - Основные настройки нового пользователя были заполнены
2025-08-23 14:07:43 - requests - INFO - Риск-Менеджмент настройки нового пользователя были заполнены
2025-08-23 14:07:43 - requests - INFO - Условные настройки нового пользователя были заполнены
2025-08-23 14:07:43 - requests - INFO - Дополнительные настройки нового пользователя были заполнены
2025-08-23 14:23:31 - main - INFO - Bot is off
2025-08-23 14:23:39 - main - INFO - Bot is on
2025-08-23 14:28:13 - main - INFO - Bot is off
2025-08-23 14:28:19 - main - INFO - Bot is on
2025-08-23 14:28:26 - requests - INFO - Получение риск-менеджмента настроек пользователя 899674724
2025-08-23 14:28:26 - requests - INFO - Получение риск-менеджмента настроек пользователя 899674724
2025-08-23 14:29:12 - requests - INFO - Получение риск-менеджмента настроек пользователя 899674724
2025-08-23 14:29:34 - main - INFO - Bot is off

29
requirements.txt Normal file
View File

@@ -0,0 +1,29 @@
aiofiles==24.1.0
aiogram==3.22.0
aiohappyeyeballs==2.6.1
aiohttp==3.12.15
aiosignal==1.4.0
aiosqlite==0.21.0
annotated-types==0.7.0
attrs==25.3.0
certifi==2025.8.3
charset-normalizer==3.4.3
dotenv==0.9.9
frozenlist==1.7.0
greenlet==3.2.4
idna==3.10
magic-filter==1.0.12
multidict==6.6.4
propcache==0.3.2
pybit==5.11.0
pycryptodome==3.23.0
pydantic==2.11.7
pydantic_core==2.33.2
python-dotenv==1.1.1
requests==2.32.5
SQLAlchemy==2.0.43
typing-inspection==0.4.1
typing_extensions==4.14.1
urllib3==2.5.0
websocket-client==1.8.0
yarl==1.20.1