2
0
forked from kodorvan/stcs

28 Commits

Author SHA1 Message Date
82d875136b deleted deprecated 2025-09-12 09:33:16 +07:00
28ead61112 Обновить README.md 2025-09-12 09:32:27 +07:00
688fc7a8ab Merge pull request 'develop' (#4) from Alex/stcs:develop into stable
Reviewed-on: kodorvan/stcs#4
2025-09-12 09:28:08 +07:00
07666fa984 Merge branch 'stable' into develop 2025-09-12 09:27:52 +07:00
algizn97
b3119c6ee1 Fixed martingale step 2025-09-11 12:50:43 +05:00
algizn97
da16a267e4 Fixed 2025-09-11 12:15:19 +05:00
algizn97
babbcbd1fc The timer start function has been updated 2025-09-11 12:02:47 +05:00
algizn97
f42940f847 Added timer deletion button 2025-09-11 10:57:21 +05:00
algizn97
3ff146a1b9 Fixed 2025-09-10 20:30:51 +05:00
algizn97
93865a1b16 Fixed 2025-09-10 17:34:41 +05:00
algizn97
44f9b05001 Fixed 2025-09-10 15:28:08 +05:00
algizn97
02279d19ae Fixed 2025-09-09 11:40:51 +05:00
algizn97
cf581dc485 Fixed 2025-09-09 10:24:01 +05:00
15e248d7d7 Ещё исправления markdown 2025-09-07 20:57:25 +07:00
cdb745d55a Исправление markdown 2025-09-07 20:56:42 +07:00
c46a4cb0b7 Merge pull request 'develop' (#3) from Alex/stcs:develop into stable
Reviewed-on: kodorvan/stcs#3
2025-09-07 20:55:40 +07:00
algizn97
058ba09c03 Fixed 2025-09-03 21:33:08 +05:00
algizn97
dd53e5a14a Fixed 2025-09-03 21:28:12 +05:00
algizn97
3bd6b7363c Fixed 2025-08-31 11:47:13 +05:00
algizn97
2ee8c9916f Fixed 2025-08-30 16:29:56 +05:00
algizn97
3462078a47 Fixed 2025-08-29 11:44:24 +05:00
algizn97
8715b32139 Fixed 2025-08-29 11:43:52 +05:00
algizn97
4245e165bf Updated 2025-08-29 11:43:11 +05:00
algizn97
f4ff128236 Added trigger function 2025-08-29 11:42:55 +05:00
algizn97
f09fe1d70b Added new request 2025-08-29 11:42:03 +05:00
algizn97
4f774160b3 Updated States.py 2025-08-29 11:41:37 +05:00
algizn97
f6130c0b8c Deleted tasks.py 2025-08-29 11:41:18 +05:00
algizn97
e05b214a8a Added the ability to get a list of open trades and limit orders, as well as their closures (previously it was possible only for the selected pair) 2025-08-28 11:25:48 +05:00
32 changed files with 1440 additions and 1699 deletions

2
.gitignore vendored
View File

@@ -9,3 +9,5 @@ venv/
.venv/ .venv/
.idea .idea
/.idea /.idea
/myenv
myenv

5
.idea/.gitignore generated vendored
View File

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

610
.idea/dbnavigator.xml generated
View File

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

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

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

7
.idea/misc.xml generated
View File

@@ -1,7 +0,0 @@
<?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
View File

@@ -1,8 +0,0 @@
<?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
View File

@@ -1,8 +0,0 @@
<?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
View File

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

View File

@@ -1,8 +1,8 @@
import asyncio import asyncio
import logging.config import logging.config
from aiogram import Bot, Dispatcher from aiogram import Bot, Dispatcher
import app.services.Bybit.functions.bybit_ws as bybit_ws from aiogram.fsm.storage.redis import RedisStorage
import app.telegram.database.requests as rq from app.services.Bybit.functions.bybit_ws import get_or_create_event_loop, set_event_loop
from app.telegram.database.models import async_main from app.telegram.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
@@ -17,41 +17,17 @@ from config import TOKEN_TG_BOT_1
logging.config.dictConfig(LOGGING_CONFIG) logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("main") logger = logging.getLogger("main")
storage = RedisStorage.from_url("redis://localhost:6379/0")
bot = Bot(token=TOKEN_TG_BOT_1) bot = Bot(token=TOKEN_TG_BOT_1)
dp = Dispatcher() dp = Dispatcher(storage=storage)
def get_or_create_event_loop() -> asyncio.AbstractEventLoop:
"""
Возвращает текущий активный цикл событий asyncio или создает новый, если его нет.
"""
try:
return asyncio.get_running_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
return loop
loop = get_or_create_event_loop()
bybit_ws.set_event_loop(loop)
async def run_ws_for_user(tg_id, message) -> None:
"""
Запускает WebSocket Bybit для пользователя с указанным tg_id.
"""
api_key = await rq.get_bybit_api_key(tg_id)
api_secret = await rq.get_bybit_secret_key(tg_id)
await bybit_ws.start_execution_ws(api_key, api_secret, message)
async def main() -> None: async def main() -> None:
""" """
Основная асинхронная функция запуска бота: Основная асинхронная функция запуска бота:
""" """
loop = get_or_create_event_loop()
set_event_loop(loop)
await async_main() await async_main()
@@ -62,7 +38,10 @@ async def main() -> None:
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)
await dp.start_polling(bot) try:
await dp.start_polling(bot)
except asyncio.CancelledError:
logger.info("Bot is off")
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -2,7 +2,7 @@ Crypto Trading Telegram Bot
Этот бот — автоматизированный торговый помощник для работы с криптовалютной биржей Bybit на основе стратегии мартингейла. Он позволяет торговать бессрочными контрактами с управлением рисками, тейк-профитами, стоп-лоссами и кредитным плечом. Этот бот — автоматизированный торговый помощник для работы с криптовалютной биржей Bybit на основе стратегии мартингейла. Он позволяет торговать бессрочными контрактами с управлением рисками, тейк-профитами, стоп-лоссами и кредитным плечом.
##Основные возможности ## Основные возможности
- Поддержка работы с биржей Bybit через официальный API. - Поддержка работы с биржей Bybit через официальный API.
@@ -27,28 +27,37 @@ Crypto Trading Telegram Bot
- Хранение пользовательских настроек и статистики в базе данных. - Хранение пользовательских настроек и статистики в базе данных.
##Установка и запуск ## Установка и запуск
1. Клонируйте репозиторий: 1. Клонируйте репозиторий:
git clone <URL_репозитория> ```bash
git clone https://git.svoboda.works/kodorvan/stcs
```
2. Установите зависимости: 2. Установите зависимости:
```bash
pip install -r requirements.txt pip install -r requirements.txt
```
3. Зарегистрируйте чат-робота и сгенерируйте ключ авторизации<br>
[@BotFather](https://t.me/BotFather)
3. Создайте файл .env и настройте переменные окружения. 4. Создайте файл .env и настройте переменные окружения
```bash
cp .env.sample .env
nvim .env
```
5. Запустите бота:
4. Запустите бота: ```bash
python BybitBot_API.py python BybitBot_API.py
```
## Настройки пользователя
##Настройки пользователя
- Кредитное плечо (например, 15x) - Кредитное плечо (например, 15x)
@@ -67,7 +76,7 @@ python BybitBot_API.py
- Таймеры для старта и закрытия сделок - Таймеры для старта и закрытия сделок
##Безопасность и риски ## Безопасность и риски
- Бот требует аккуратной настройки параметров риска. - Бот требует аккуратной настройки параметров риска.

View File

@@ -1,9 +1,16 @@
from aiogram import F, Router from aiogram import F, Router
import logging.config import logging.config
from app.services.Bybit.functions.functions import start_bybit_trade_message
from logger_helper.logger_helper 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.Keyboards.reply_keyboards as reply_markup
import app.telegram.functions.main_settings.settings as func_main_settings
import app.telegram.functions.risk_management_settings.settings as func_rmanagement_settings
import app.telegram.functions.condition_settings.settings as func_condition_settings
import app.telegram.functions.additional_settings.settings as func_additional_settings
import app.telegram.database.requests as rq import app.telegram.database.requests as rq
from aiogram.types import Message, CallbackQuery from aiogram.types import Message, CallbackQuery
@@ -81,12 +88,24 @@ async def add_secret_key(message: Message, state: FSMContext) -> None:
await state.update_data(secret_key=message.text) await state.update_data(secret_key=message.text)
data = await state.get_data() data = await state.get_data()
user = await rq.check_user(message.from_user.id)
await rq.update_api_key(message.from_user.id, data['api_key']) await rq.upsert_api_keys(message.from_user.id, data['api_key'], data['secret_key'])
await rq.update_secret_key(message.from_user.id, data['secret_key'])
await rq.set_new_user_symbol(message.from_user.id) await rq.set_new_user_symbol(message.from_user.id)
await state.clear() await state.clear()
await message.answer('Данные добавлены, нажмите на профиль и начните торговлю!', await message.answer('Данные добавлены.',
reply_markup=reply_markup.base_buttons_markup) reply_markup=reply_markup.base_buttons_markup)
if user:
await start_bybit_trade_message(message)
else:
await rq.save_tg_id_new_user(message.from_user.id)
await func_main_settings.reg_new_user_default_main_settings(message.from_user.id, message)
await func_rmanagement_settings.reg_new_user_default_risk_management_settings(message.from_user.id,
message)
await func_condition_settings.reg_new_user_default_condition_settings(message.from_user.id)
await func_additional_settings.reg_new_user_default_additional_settings(message.from_user.id, message)
await start_bybit_trade_message(message)

File diff suppressed because it is too large Load Diff

View File

@@ -32,9 +32,9 @@ async def get_balance(tg_id: int, message) -> float:
api_secret=secret_key api_secret=secret_key
) )
if api_key == 'None' or secret_key == 'None': if api_key is None or secret_key is None:
await message.answer('⚠️ Подключите платформу для торговли', await message.answer('⚠️ Подключите платформу для торговли',
reply_markup=inline_markup.connect_bybit_api_markup) reply_markup=inline_markup.connect_bybit_api_message)
return 0 return 0
try: try:
@@ -48,5 +48,5 @@ async def get_balance(tg_id: int, message) -> float:
return 0 return 0
except Exception as e: except Exception as e:
logger.error(f"Ошибка при получении общего баланса: {e}") logger.error(f"Ошибка при получении общего баланса: {e}")
await message.answer('⚠️ Ошибка при получении баланса') await message.answer('Ошибка при подключении, повторите попытку', reply_markup=inline_markup.connect_bybit_api_message)
return 0 return 0

View File

@@ -1,13 +1,51 @@
import asyncio import asyncio
import logging.config import logging.config
from pybit.unified_trading import WebSocket from pybit.unified_trading import WebSocket
from websocket import WebSocketConnectionClosedException from websocket import WebSocketConnectionClosedException
from logger_helper.logger_helper import LOGGING_CONFIG from logger_helper.logger_helper import LOGGING_CONFIG
import app.telegram.database.requests as rq
logging.config.dictConfig(LOGGING_CONFIG) logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("bybit_ws") logger = logging.getLogger("bybit_ws")
event_loop = None # Сюда нужно будет установить event loop из основного приложения event_loop = None # Сюда нужно будет установить event loop из основного приложения
active_ws_tasks = {}
def on_ws_error(ws, error):
logger.error(f"WebSocket internal error: {error}")
# Запланировать переподключение через event loop
if event_loop:
asyncio.run_coroutine_threadsafe(reconnect_ws(ws), event_loop)
def on_ws_close(ws, close_status_code, close_msg):
logger.warning(f"WebSocket closed: {close_status_code} - {close_msg}")
# Запланировать переподключение через event loop
if event_loop:
asyncio.run_coroutine_threadsafe(reconnect_ws(ws), event_loop)
async def reconnect_ws(ws):
logger.info("Запускаем переподключение WebSocket...")
await asyncio.sleep(5)
try:
await ws.run_forever()
except WebSocketConnectionClosedException:
logger.info("WebSocket переподключение успешно завершено.")
def get_or_create_event_loop() -> asyncio.AbstractEventLoop:
"""
Возвращает текущий активный цикл событий asyncio или создает новый, если его нет.
"""
try:
return asyncio.get_running_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
return loop
def set_event_loop(loop: asyncio.AbstractEventLoop): def set_event_loop(loop: asyncio.AbstractEventLoop):
@@ -15,14 +53,34 @@ def set_event_loop(loop: asyncio.AbstractEventLoop):
event_loop = loop event_loop = loop
def on_execution_callback(message, msg): async def run_ws_for_user(tg_id, message) -> None:
""" """
Callback на событие исполнения сделки. Запускает WebSocket Bybit для пользователя с указанным tg_id.
Безопасно запускает асинхронный обработчик из sync callback.
""" """
if tg_id not in active_ws_tasks or active_ws_tasks[tg_id].done():
api_key = await rq.get_bybit_api_key(tg_id)
api_secret = await rq.get_bybit_secret_key(tg_id)
# Запускаем WebSocket как асинхронную задачу
active_ws_tasks[tg_id] = asyncio.create_task(
start_execution_ws(api_key, api_secret, message)
)
logger.info(f"WebSocket для пользователя {tg_id} запущен.")
def on_order_callback(message, msg):
if event_loop is not None: if event_loop is not None:
from app.services.Bybit.functions.Futures import handle_execution_message # Импорт внутри, чтобы избежать циклических импортов from app.services.Bybit.functions.Futures import handle_order_message
asyncio.run_coroutine_threadsafe(handle_execution_message(message, msg), event_loop) asyncio.run_coroutine_threadsafe(handle_order_message(message, msg), event_loop)
logger.info("Callback выполнен.")
else:
logger.error("Event loop не установлен, callback пропущен.")
def on_execution_callback(message, ws_msg):
if event_loop is not None:
from app.services.Bybit.functions.Futures import handle_execution_message
asyncio.run_coroutine_threadsafe(handle_execution_message(message, ws_msg), event_loop)
logger.info("Callback выполнен.")
else: else:
logger.error("Event loop не установлен, callback пропущен.") logger.error("Event loop не установлен, callback пропущен.")
@@ -35,8 +93,18 @@ async def start_execution_ws(api_key: str, api_secret: str, message):
reconnect_delay = 5 reconnect_delay = 5
while True: while True:
try: try:
if not api_key or not api_secret:
logger.error("API_KEY и API_SECRET должны быть указаны для подключения к приватным каналам.")
await asyncio.sleep(reconnect_delay)
continue
ws = WebSocket(api_key=api_key, api_secret=api_secret, testnet=False, channel_type="private") ws = WebSocket(api_key=api_key, api_secret=api_secret, testnet=False, channel_type="private")
ws.execution_stream(lambda msg: on_execution_callback(message, msg))
ws.on_error = on_ws_error
ws.on_close = on_ws_close
ws.subscribe("order", lambda ws_msg: on_order_callback(message, ws_msg))
ws.subscribe("execution", lambda ws_msg: on_execution_callback(message, ws_msg))
while True: while True:
await asyncio.sleep(1) # Поддержание активности await asyncio.sleep(1) # Поддержание активности
except WebSocketConnectionClosedException: except WebSocketConnectionClosedException:

View File

@@ -2,16 +2,18 @@
import logging.config import logging.config
from aiogram import F, Router from aiogram import F, Router
from app.tasks.tasks import handle_stop_close_trade, handle_start_close_trade, handle_stop_trading, handle_start_trading from app.services.Bybit.functions.bybit_ws import run_ws_for_user
from app.telegram.functions.main_settings.settings import main_settings_message
from logger_helper.logger_helper import LOGGING_CONFIG from logger_helper.logger_helper import LOGGING_CONFIG
from app.services.Bybit.functions.Futures import (close_user_trade, open_position, set_take_profit_stop_loss, \ from app.services.Bybit.functions.Futures import (close_user_trade, set_take_profit_stop_loss, \
get_active_positions_by_symbol, get_active_orders_by_symbol, get_active_positions_by_symbol, get_active_orders_by_symbol,
get_active_positions, get_active_orders, cancel_all_tp_sl_orders,
open_position, close_trade_after_delay, safe_float,
) )
from app.services.Bybit.functions.balance import get_balance 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
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
from app.services.Bybit.functions.price_symbol import get_price from app.services.Bybit.functions.price_symbol import get_price
@@ -27,6 +29,8 @@ logger = logging.getLogger("functions")
router_functions_bybit_trade = Router() router_functions_bybit_trade = Router()
user_trade_tasks = {}
@router_functions_bybit_trade.callback_query(F.data.in_(['clb_start_trading', 'clb_back_to_main', 'back_to_main'])) @router_functions_bybit_trade.callback_query(F.data.in_(['clb_start_trading', 'clb_back_to_main', 'back_to_main']))
async def clb_start_bybit_trade_message(callback: CallbackQuery) -> None: async def clb_start_bybit_trade_message(callback: CallbackQuery) -> None:
@@ -36,10 +40,10 @@ async def clb_start_bybit_trade_message(callback: CallbackQuery) -> None:
""" """
user_id = callback.from_user.id user_id = callback.from_user.id
balance = await get_balance(user_id, callback.message) balance = await get_balance(user_id, callback.message)
price = await get_price(user_id)
if balance: if balance:
symbol = await rq.get_symbol(user_id) symbol = await rq.get_symbol(user_id)
price = await get_price(user_id, symbol=symbol)
text = ( text = (
f"💎 Торговля на Bybit\n\n" f"💎 Торговля на Bybit\n\n"
@@ -49,7 +53,7 @@ async def clb_start_bybit_trade_message(callback: CallbackQuery) -> None:
"Как начать торговлю?\n\n" "Как начать торговлю?\n\n"
"1⃣ Проверьте и тщательно настройте все параметры в вашем профиле.\n" "1⃣ Проверьте и тщательно настройте все параметры в вашем профиле.\n"
"2⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару, без лишних символов (например: BTCUSDT).\n" "2⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару, без лишних символов (например: BTCUSDT).\n"
"3⃣ Нажмите кнопку 'Выбрать тип входа' и после нажмите начать торговлю.\n" "3⃣ Нажмите кнопку 'Начать торговать'.\n"
) )
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)
@@ -60,10 +64,12 @@ async def start_bybit_trade_message(message: Message) -> None:
вместе с инструкциями по началу торговли. вместе с инструкциями по началу торговли.
""" """
balance = await get_balance(message.from_user.id, message) balance = await get_balance(message.from_user.id, message)
price = await get_price(message.from_user.id) tg_id = message.from_user.id
if balance: if balance:
await run_ws_for_user(tg_id, message)
symbol = await rq.get_symbol(message.from_user.id) symbol = await rq.get_symbol(message.from_user.id)
price = await get_price(message.from_user.id, symbol=symbol)
text = ( text = (
f"💎 Торговля на Bybit\n\n" f"💎 Торговля на Bybit\n\n"
@@ -73,7 +79,7 @@ async def start_bybit_trade_message(message: Message) -> None:
"Как начать торговлю?\n\n" "Как начать торговлю?\n\n"
"1⃣ Проверьте и тщательно настройте все параметры в вашем профиле.\n" "1⃣ Проверьте и тщательно настройте все параметры в вашем профиле.\n"
"2⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару, без лишних символов (например: BTCUSDT).\n" "2⃣ Нажмите ниже кнопку 'Указать торговую пару' и введите торговую пару, без лишних символов (например: BTCUSDT).\n"
"3⃣ Нажмите кнопку 'Выбрать тип входа' и после нажмите начать торговлю.\n" "3⃣ Нажмите кнопку 'Начать торговать'.\n"
) )
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)
@@ -85,6 +91,7 @@ async def update_symbol_for_trade_message(callback: CallbackQuery, state: FSMCon
Начинает процедуру обновления торговой пары, переводит пользователя в состояние ожидания пары. Начинает процедуру обновления торговой пары, переводит пользователя в состояние ожидания пары.
""" """
await state.set_state(state_update_symbol.symbol) await state.set_state(state_update_symbol.symbol)
await callback.answer()
await callback.message.answer( await callback.message.answer(
text='Укажите торговую пару заглавными буквами без пробелов и лишних символов (пример: BTCUSDT): ', text='Укажите торговую пару заглавными буквами без пробелов и лишних символов (пример: BTCUSDT): ',
@@ -98,7 +105,6 @@ async def update_symbol_for_trade(message: Message, state: FSMContext) -> None:
При успешном обновлении сохранит пару и отправит обновлённую информацию. При успешном обновлении сохранит пару и отправит обновлённую информацию.
""" """
user_input = message.text.strip().upper() user_input = message.text.strip().upper()
exists = await get_valid_symbols(message.from_user.id, user_input) exists = await get_valid_symbols(message.from_user.id, user_input)
if not exists: if not exists:
@@ -186,60 +192,37 @@ async def start_trading_process(callback: CallbackQuery) -> None:
Проверяет API-ключи, режим торговли, маржинальный режим и открытые позиции, Проверяет API-ключи, режим торговли, маржинальный режим и открытые позиции,
затем запускает торговый цикл с задержкой или без неё. затем запускает торговый цикл с задержкой или без неё.
""" """
await callback.answer()
tg_id = callback.from_user.id tg_id = callback.from_user.id
message = callback.message message = callback.message
data_main_stgs = await rq.get_user_main_settings(tg_id) 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) symbol = await rq.get_symbol(tg_id)
margin_mode = data_main_stgs.get('margin_type', 'Isolated') margin_mode = data_main_stgs.get('margin_type', 'Isolated')
trading_mode = data_main_stgs.get('trading_mode') trading_mode = data_main_stgs.get('trading_mode')
starting_quantity = safe_float(data_main_stgs.get('starting_quantity'))
switch_state = data_main_stgs.get("switch_state", "По направлению")
if not api_key or not secret_key: if trading_mode == 'Switch':
await message.answer("❗️ У вас не настроены API ключи для Bybit.") if switch_state == "По направлению":
await callback.answer() side = data_main_stgs.get("last_side")
return else:
side = data_main_stgs.get("last_side")
if trading_mode not in ['Long', 'Short', 'Smart', 'Switch']: if side.lower() == "buy":
await message.answer(f"❗️ Некорректный торговый режим: {trading_mode}") side = "Sell"
await callback.answer() else:
return side = "Buy"
else:
if margin_mode not in ['Isolated', 'Cross']: if trading_mode == 'Long':
margin_mode = 'Isolated' side = 'Buy'
elif trading_mode == 'Short':
client = HTTP(api_key=api_key, api_secret=secret_key) side = 'Sell'
else:
try: await message.answer(f"Режим торговли '{trading_mode}' пока не поддерживается.",
positions_resp = client.get_positions(category='linear', symbol=symbol) reply_markup=inline_markup.back_to_main)
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 return
if trading_mode == 'Long':
side = 'Buy'
elif trading_mode == 'Short':
side = 'Sell'
else:
await message.answer(f"Режим торговли '{trading_mode}' пока не поддерживается.",
reply_markup=inline_markup.back_to_main)
await callback.answer()
return
await message.answer("Начинаю торговлю с использованием текущих настроек...") await message.answer("Начинаю торговлю с использованием текущих настроек...")
timer_data = await rq.get_user_timer(tg_id) timer_data = await rq.get_user_timer(tg_id)
if isinstance(timer_data, dict): if isinstance(timer_data, dict):
timer_minute = timer_data.get('timer_minutes', 0) timer_minute = timer_data.get('timer_minutes', 0)
@@ -247,14 +230,39 @@ async def start_trading_process(callback: CallbackQuery) -> None:
timer_minute = timer_data or 0 timer_minute = timer_data or 0
if timer_minute > 0: if timer_minute > 0:
await handle_start_trading(tg_id, message, side=side, margin_mode=margin_mode, use_timer=True) await message.answer(f"Торговля начнётся через {timer_minute} мин.", reply_markup=inline_markup.cancel_start)
await message.answer(f"Торговля начнётся через {timer_minute} мин. Для отмены нажмите кнопку ниже.",
reply_markup=inline_markup.cancel_start_markup)
await rq.update_user_timer(tg_id, minutes=0)
else:
await handle_start_trading(tg_id, message, side=side, margin_mode=margin_mode, use_timer=False)
await callback.answer() async def delay_start():
try:
await asyncio.sleep(timer_minute * 60)
await open_position(tg_id, message, side, margin_mode, symbol=symbol, quantity=starting_quantity)
await rq.update_user_timer(tg_id, minutes=0)
except asyncio.exceptions.CancelledError:
logger.exception(f"Торговый цикл для пользователя {tg_id} был отменён.")
raise
task = asyncio.create_task(delay_start())
user_trade_tasks[tg_id] = task
else:
await open_position(tg_id, message, side, margin_mode, symbol=symbol, quantity=starting_quantity)
@router_functions_bybit_trade.callback_query(F.data == "clb_cancel_start")
async def cancel_start_trading(callback: CallbackQuery):
tg_id = callback.from_user.id
task = user_trade_tasks.get(tg_id)
if task and not task.done():
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
user_trade_tasks.pop(tg_id, None)
await rq.update_user_timer(tg_id, minutes=0)
await callback.message.answer("Запуск торговли отменён.", reply_markup=inline_markup.back_to_main)
await callback.message.edit_reply_markup(reply_markup=None)
else:
await callback.answer("Нет запланированной задачи запуска.", show_alert=True)
@router_functions_bybit_trade.callback_query(F.data == "clb_my_deals") @router_functions_bybit_trade.callback_query(F.data == "clb_my_deals")
@@ -264,8 +272,7 @@ async def show_my_trades(callback: CallbackQuery) -> None:
""" """
await callback.answer() await callback.answer()
try: try:
symbol = await rq.get_symbol(callback.from_user.id) await callback.message.answer(f"Выберите тип сделки:",
await callback.message.answer(f"Выберите тип сделки для пары {symbol}:",
reply_markup=inline_markup.my_deals_select_markup) reply_markup=inline_markup.my_deals_select_markup)
except Exception as e: except Exception as e:
logger.error(f"Произошла ошибка при выборе типа сделки: {e}") logger.error(f"Произошла ошибка при выборе типа сделки: {e}")
@@ -274,17 +281,34 @@ async def show_my_trades(callback: CallbackQuery) -> None:
@router_functions_bybit_trade.callback_query(F.data == "clb_open_deals") @router_functions_bybit_trade.callback_query(F.data == "clb_open_deals")
async def show_my_trades_callback(callback: CallbackQuery): async def show_my_trades_callback(callback: CallbackQuery):
""" """
Показывает открытые позиции пользователя по символу. Показывает открытые позиции пользователя.
""" """
await callback.answer() await callback.answer()
try: try:
await get_active_positions_by_symbol(callback.from_user.id, message=callback.message) await get_active_positions(callback.from_user.id, message=callback.message)
except Exception as e: except Exception as e:
logger.error(f"Произошла ошибка при выборе сделки: {e}") logger.error(f"Произошла ошибка при выборе сделки: {e}")
await callback.message.answer("Произошла ошибка при выборе сделки", reply_markup=inline_markup.back_to_main) await callback.message.answer("Произошла ошибка при выборе сделки", reply_markup=inline_markup.back_to_main)
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("show_deal_"))
async def show_deal_callback(callback_query: CallbackQuery) -> None:
"""
Показывает сделку пользователя по символу.
"""
await callback_query.answer()
try:
symbol = callback_query.data[len("show_deal_"):]
await rq.update_symbol(callback_query.from_user.id, symbol)
tg_id = callback_query.from_user.id
await get_active_positions_by_symbol(tg_id, symbol, message=callback_query.message)
except Exception as e:
logger.error(f"Произошла ошибка при выборе сделки: {e}")
await callback_query.message.answer("Произошла ошибка при выборе сделки",
reply_markup=inline_markup.back_to_main)
@router_functions_bybit_trade.callback_query(F.data == "clb_open_orders") @router_functions_bybit_trade.callback_query(F.data == "clb_open_orders")
async def show_my_orders_callback(callback: CallbackQuery) -> None: async def show_my_orders_callback(callback: CallbackQuery) -> None:
""" """
@@ -293,16 +317,33 @@ async def show_my_orders_callback(callback: CallbackQuery) -> None:
await callback.answer() await callback.answer()
try: try:
await get_active_orders_by_symbol(callback.from_user.id, message=callback.message) await get_active_orders(callback.from_user.id, message=callback.message)
except Exception as e: except Exception as e:
logger.error(f"Произошла ошибка при выборе ордера: {e}") logger.error(f"Произошла ошибка при выборе ордера: {e}")
await callback.message.answer("Произошла ошибка при выборе ордера", reply_markup=inline_markup.back_to_main) await callback.message.answer("Произошла ошибка при выборе ордера", reply_markup=inline_markup.back_to_main)
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("show_limit_"))
async def show_limit_callback(callback_query: CallbackQuery) -> None:
"""
Показывает сделку пользователя по символу.
"""
await callback_query.answer()
try:
symbol = callback_query.data[len("show_limit_"):]
await rq.update_symbol(callback_query.from_user.id, symbol)
tg_id = callback_query.from_user.id
await get_active_orders_by_symbol(tg_id, symbol, message=callback_query.message)
except Exception as e:
logger.error(f"Произошла ошибка при выборе сделки: {e}")
await callback_query.message.answer("Произошла ошибка при выборе сделки",
reply_markup=inline_markup.back_to_main)
@router_functions_bybit_trade.callback_query(F.data == "clb_set_tp_sl") @router_functions_bybit_trade.callback_query(F.data == "clb_set_tp_sl")
async def set_tp_sl(callback: CallbackQuery, state: FSMContext) -> None: async def set_tp_sl(callback: CallbackQuery, state: FSMContext) -> None:
""" """
Показывает активные ордера пользователя. Запускает процесс установки Take Profit и Stop Loss.
""" """
await callback.answer() await callback.answer()
await state.set_state(SetTP_SL_State.waiting_for_take_profit) await state.set_state(SetTP_SL_State.waiting_for_take_profit)
@@ -362,18 +403,6 @@ async def process_stop_loss(message: Message, state: FSMContext) -> None:
await state.clear() await state.clear()
@router_functions_bybit_trade.callback_query(F.data == "clb_stop_timer")
async def cancel_start_callback(callback: CallbackQuery) -> None:
"""
Отменяет задачу старта торговли по таймеру, если она активна.
"""
tg_id = callback.from_user.id
await handle_stop_close_trade(tg_id)
await callback.message.answer("Торговля по таймеру отменена.", reply_markup=inline_markup.back_to_main)
await callback.answer()
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_deal:")) @router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_deal:"))
async def close_trade_callback(callback: CallbackQuery) -> None: async def close_trade_callback(callback: CallbackQuery) -> None:
""" """
@@ -382,10 +411,9 @@ async def close_trade_callback(callback: CallbackQuery) -> None:
symbol = callback.data.split(':')[1] symbol = callback.data.split(':')[1]
tg_id = callback.from_user.id tg_id = callback.from_user.id
result = await close_user_trade(tg_id, symbol, message=callback.message) result = await close_user_trade(tg_id, symbol)
if result: if result:
await handle_stop_trading(tg_id)
logger.info(f"Сделка {symbol} успешно закрыта.") logger.info(f"Сделка {symbol} успешно закрыта.")
else: else:
logger.error(f"Не удалось закрыть сделку {symbol}.") logger.error(f"Не удалось закрыть сделку {symbol}.")
@@ -394,6 +422,24 @@ async def close_trade_callback(callback: CallbackQuery) -> None:
await callback.answer() await callback.answer()
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_limit:"))
async def close_trade_callback(callback: CallbackQuery) -> None:
"""
Закрывает ордера пользователя по символу.
"""
symbol = callback.data.split(':')[1]
tg_id = callback.from_user.id
result = await cancel_all_tp_sl_orders(tg_id, symbol)
if result:
logger.info(f"Ордер {result} успешно закрыт.")
else:
await callback.message.answer(f"Не удалось закрыть ордер {result}.")
await callback.answer()
@router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_deal_by_timer:")) @router_functions_bybit_trade.callback_query(lambda c: c.data and c.data.startswith("close_deal_by_timer:"))
async def ask_close_delay(callback: CallbackQuery, state: FSMContext) -> None: async def ask_close_delay(callback: CallbackQuery, state: FSMContext) -> None:
""" """
@@ -402,7 +448,7 @@ async def ask_close_delay(callback: CallbackQuery, state: FSMContext) -> None:
symbol = callback.data.split(":")[1] symbol = callback.data.split(":")[1]
await state.update_data(symbol=symbol) await state.update_data(symbol=symbol)
await state.set_state(CloseTradeTimerState.waiting_for_delay) await state.set_state(CloseTradeTimerState.waiting_for_delay)
await callback.message.answer("Введите задержку в минутах до закрытия сделки (например, 60):", await callback.message.answer("Введите задержку в минутах до закрытия сделки:",
reply_markup=inline_markup.cancel) reply_markup=inline_markup.cancel)
await callback.answer() await callback.answer()
@@ -410,7 +456,7 @@ async def ask_close_delay(callback: CallbackQuery, state: FSMContext) -> None:
@router_functions_bybit_trade.message(CloseTradeTimerState.waiting_for_delay) @router_functions_bybit_trade.message(CloseTradeTimerState.waiting_for_delay)
async def process_close_delay(message: Message, state: FSMContext) -> None: async def process_close_delay(message: Message, state: FSMContext) -> None:
""" """
Обрабатывает ввод задержки и запускает задачу закрытия сделки с задержкой. Обрабатывает ввод закрытия сделки с задержкой.
""" """
try: try:
delay_minutes = int(message.text.strip()) delay_minutes = int(message.text.strip())
@@ -423,14 +469,11 @@ async def process_close_delay(message: Message, state: FSMContext) -> None:
data = await state.get_data() data = await state.get_data()
symbol = data.get("symbol") symbol = data.get("symbol")
tg_id = message.from_user.id
delay = delay_minutes * 60 delay = delay_minutes * 60
await message.answer(f"Закрытие сделки {symbol} запланировано через {delay_minutes} мин.",
await handle_start_close_trade(tg_id, message, symbol, delay) reply_markup=inline_markup.back_to_main)
await close_trade_after_delay(message.from_user.id, message, symbol, delay)
await message.answer(f"Закрытие сделки {symbol} запланировано через {delay} секунд.",
reply_markup=inline_markup.cancel_start_markup)
await state.clear() await state.clear()
@@ -440,8 +483,9 @@ async def reset_martingale(callback: CallbackQuery) -> None:
Сбрасывает шаги мартингейла пользователя. Сбрасывает шаги мартингейла пользователя.
""" """
tg_id = callback.from_user.id tg_id = callback.from_user.id
await rq.update_martingale_step(tg_id, 0) await rq.update_martingale_step(tg_id, 1)
await callback.answer("Сброс шагов выполнен.") await callback.answer("Сброс шагов выполнен.")
await main_settings_message(tg_id, callback.message)
@router_functions_bybit_trade.callback_query(F.data == "clb_stop_trading") @router_functions_bybit_trade.callback_query(F.data == "clb_stop_trading")
@@ -454,6 +498,7 @@ async def confirm_stop_trading(callback: CallbackQuery):
) )
await callback.answer() await callback.answer()
@router_functions_bybit_trade.callback_query(F.data == "stop_immediately") @router_functions_bybit_trade.callback_query(F.data == "stop_immediately")
async def stop_immediately(callback: CallbackQuery): async def stop_immediately(callback: CallbackQuery):
""" """
@@ -461,21 +506,24 @@ async def stop_immediately(callback: CallbackQuery):
""" """
tg_id = callback.from_user.id tg_id = callback.from_user.id
await handle_stop_trading(tg_id, use_timer=False) await rq.update_trigger(tg_id, "Ручной")
await callback.message.answer("Торговля остановлена.", reply_markup=inline_markup.back_to_main) await callback.message.answer("Автоматическая торговля остановлена.", reply_markup=inline_markup.back_to_main)
await callback.answer() await callback.answer()
@router_functions_bybit_trade.callback_query(F.data == "stop_with_timer") @router_functions_bybit_trade.callback_query(F.data == "stop_with_timer")
async def stop_with_timer_start(callback: CallbackQuery, state: FSMContext): async def stop_with_timer_start(callback: CallbackQuery, state: FSMContext):
""" """
Запускает диалог с пользователем для задания задержки перед остановкой торговли. Запускает диалог с пользователем для задания задержки до остановки торговли.
""" """
await state.set_state(CloseTradeTimerState.waiting_for_delay) await state.set_state(CloseTradeTimerState.waiting_for_trade)
await callback.message.answer("Введите задержку в минутах перед остановкой торговли:", reply_markup=inline_markup.cancel) await callback.message.answer("Введите задержку в минутах до остановки торговли:",
reply_markup=inline_markup.cancel)
await callback.answer() await callback.answer()
@router_functions_bybit_trade.message(CloseTradeTimerState.waiting_for_delay)
@router_functions_bybit_trade.message(CloseTradeTimerState.waiting_for_trade)
async def process_stop_delay(message: Message, state: FSMContext): async def process_stop_delay(message: Message, state: FSMContext):
""" """
Обрабатывает ввод задержки и запускает задачу остановки торговли с задержкой. Обрабатывает ввод задержки и запускает задачу остановки торговли с задержкой.
@@ -492,16 +540,12 @@ async def process_stop_delay(message: Message, state: FSMContext):
tg_id = message.from_user.id tg_id = message.from_user.id
delay_seconds = delay_minutes * 60 delay_seconds = delay_minutes * 60
# Остановка задачи с таймером через заданную задержку await message.answer(f"Торговля будет остановлена через {delay_minutes} минут.",
# Можно реализовать через запуск отдельной асинхронной задачи, которая через delay_seconds отменит торговый цикл reply_markup=inline_markup.back_to_main)
async def delayed_stop(): await asyncio.sleep(delay_seconds)
await asyncio.sleep(delay_seconds) await rq.update_trigger(tg_id, "Ручной")
await handle_stop_trading(tg_id, use_timer=True) await message.answer("Автоматическая торговля остановлена.", reply_markup=inline_markup.back_to_main)
await message.answer("Торговля по таймеру остановлена.")
asyncio.create_task(delayed_stop())
await message.answer(f"Торговля будет остановлена через {delay_minutes} минут.", reply_markup=inline_markup.back_to_main)
await state.clear() await state.clear()

View File

@@ -28,7 +28,7 @@ async def get_min_qty(tg_id: int) -> float:
client = HTTP(api_key=api_key, api_secret=secret_key) client = HTTP(api_key=api_key, api_secret=secret_key)
price = await get_price(tg_id) price = await get_price(tg_id, symbol=symbol)
response = client.get_instruments_info(symbol=symbol, category='linear') response = client.get_instruments_info(symbol=symbol, category='linear')

View File

@@ -8,7 +8,7 @@ logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("price_symbol") logger = logging.getLogger("price_symbol")
async def get_price(tg_id: int) -> float: async def get_price(tg_id: int, symbol: str) -> float:
""" """
Асинхронно получает текущую цену символа пользователя на Bybit. Асинхронно получает текущую цену символа пользователя на Bybit.
@@ -17,7 +17,6 @@ async def get_price(tg_id: int) -> 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)
client = HTTP( client = HTTP(
api_key=api_key, api_key=api_key,

View File

@@ -25,6 +25,7 @@ class state_limit_price(StatesGroup):
class CloseTradeTimerState(StatesGroup): class CloseTradeTimerState(StatesGroup):
"""FSM состояние ожидания задержки перед закрытием сделки.""" """FSM состояние ожидания задержки перед закрытием сделки."""
waiting_for_delay = State() waiting_for_delay = State()
waiting_for_trade = State()
class SetTP_SL_State(StatesGroup): class SetTP_SL_State(StatesGroup):
@@ -45,3 +46,24 @@ class state_reg_bybit_api(StatesGroup):
"""FSM состояние для регистрации API Bybit.""" """FSM состояние для регистрации API Bybit."""
api_key = State() api_key = State()
secret_key = State() secret_key = State()
class condition_settings(StatesGroup):
"""FSM состояние для настройки условий трейдинга."""
trigger = State()
timer = State()
volatilty = State()
volume = State()
integration = State()
use_tv_signal = State()
class update_main_settings(StatesGroup):
"""FSM состояние для обновления основных настройок."""
trading_mode = State()
size_leverage = State()
margin_type = State()
martingale_factor = State()
starting_quantity = State()
maximal_quantity = State()
switch_mode_enabled = State()

View File

@@ -1,125 +0,0 @@
import asyncio
import logging.config
from typing import Optional
from app.services.Bybit.functions.Futures import close_trade_after_delay, trading_cycle, open_position
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("tasks")
active_start_tasks = {}
active_close_tasks = {}
active_start_tasks_timer = {}
lock_start_tasks = asyncio.Lock()
lock_close_tasks = asyncio.Lock()
def start_trading_cycle(tg_id, message, side: str, margin_mode: str, tpsl_mode='Full') -> None:
"""
Запускает асинхронную задачу торгового цикла для пользователя с указанным tg_id.
"""
task = asyncio.create_task(open_position(tg_id, message, side, margin_mode, tpsl_mode))
active_start_tasks[tg_id] = task
def stop_trading_cycle(tg_id) -> None:
"""
Останавливает (отменяет) задачу торгового цикла для пользователя с указанным tg_id.
"""
task: Optional[asyncio.Task] = active_start_tasks.pop(tg_id, None)
if task:
task.cancel()
def start_trading_for_timer(tg_id, message, side: str, margin_mode: str, tpsl_mode='Full') -> None:
# Старт с задержкой (trading_cycle)
task = asyncio.create_task(trading_cycle(tg_id, message))
active_start_tasks_timer[tg_id] = task
def stop_trading_for_timer(tg_id) -> None:
task: Optional[asyncio.Task] = active_start_tasks_timer.pop(tg_id, None)
if task:
task.cancel()
def start_close_trade_task(tg_id, message, symbol, delay_sec) -> None:
"""
Запускает асинхронную задачу автоматического закрытия сделки после задержки.
"""
task = asyncio.create_task(close_trade_after_delay(tg_id, message, symbol, delay_sec))
active_close_tasks[tg_id] = task
def stop_close_trade_task(tg_id) -> None:
"""
Останавливает (отменяет) задачу автоматического закрытия сделки для пользователя.
"""
task: Optional[asyncio.Task] = active_close_tasks.pop(tg_id, None)
if task:
task.cancel()
async def handle_start_trading(tg_id: int, message, side: str, margin_mode: str, tpsl_mode='Full', use_timer=False):
"""
Запускает торговый цикл. Если уже есть запущенная задача, отменяет её.
"""
async with lock_start_tasks:
if use_timer:
old_task = active_start_tasks_timer.get(tg_id)
if old_task and not old_task.done():
old_task.cancel()
try:
await old_task
except asyncio.CancelledError:
logger.info(f"Старая задача торговли с таймером для пользователя {tg_id} отменена")
start_trading_for_timer(tg_id, message, side, margin_mode, tpsl_mode)
logger.info(f"Новая задача торговли с таймером запущена для пользователя {tg_id}")
else:
old_task = active_start_tasks.get(tg_id)
if old_task and not old_task.done():
old_task.cancel()
try:
await old_task
except asyncio.CancelledError:
logger.info(f"Старая задача торговли для пользователя {tg_id} отменена")
start_trading_cycle(tg_id, message, side, margin_mode, tpsl_mode)
logger.info(f"Новая задача торговли запущена для пользователя {tg_id}")
async def handle_stop_trading(tg_id: int, use_timer=False):
"""
Останавливает торговую задачу пользователя, если она активна.
"""
async with lock_start_tasks:
if use_timer:
stop_trading_for_timer(tg_id)
logger.info(f"Задача торговли с таймером остановлена для пользователя {tg_id}")
else:
stop_trading_cycle(tg_id)
logger.info(f"Задача торговли остановлена для пользователя {tg_id}")
async def handle_start_close_trade(tg_id: int, message, symbol: str, delay_sec: int):
"""
Запускает задачу закрытия сделки с задержкой. Отменяет старую задачу, если есть.
"""
async with lock_close_tasks:
old_task = active_close_tasks.get(tg_id)
if old_task and not old_task.done():
old_task.cancel()
try:
await old_task
except asyncio.CancelledError:
logger.info(f"Старая задача закрытия сделки пользователя {tg_id} отменена")
start_close_trade_task(tg_id, message, symbol, delay_sec)
logger.info(f"Задача закрытия сделки для {symbol} запущена с задержкой {delay_sec}s для пользователя {tg_id}")
async def handle_stop_close_trade(tg_id: int):
"""
Отменяет задачу закрытия сделки пользователя, если она есть.
"""
async with lock_close_tasks:
stop_close_trade_task(tg_id)
logger.info(f"Задача закрытия сделки отменена для пользователя {tg_id}")

View File

@@ -6,24 +6,37 @@ start_markup = InlineKeyboardMarkup(inline_keyboard=[
]) ])
settings_markup = InlineKeyboardMarkup(inline_keyboard=[ settings_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Настройки", callback_data='clb_settings_message')],
[InlineKeyboardButton(text="Запуск", callback_data='clb_start_trading')] [InlineKeyboardButton(text="Запуск", callback_data='clb_start_trading')]
]) ])
cancel_start = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Отменить запуск", callback_data="clb_cancel_start")]
])
back_btn_list_settings = [InlineKeyboardButton(text="Назад",
callback_data='clb_back_to_special_settings_message')] # Кнопка для возврата к списку каталога настроек
back_btn_list_settings_markup = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Назад",
callback_data='clb_back_to_special_settings_message')]]) # Клавиатура для возврата к списку каталога настроек
back_btn_to_main = [InlineKeyboardButton(text="На главную", callback_data='clb_back_to_main')]
back_btn_profile = [InlineKeyboardButton(text="Назад", callback_data='clb_start_chatbot_message')] back_btn_profile = [InlineKeyboardButton(text="Назад", callback_data='clb_start_chatbot_message')]
connect_bybit_api_message = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Подключить Bybit", callback_data='clb_new_user_connect_bybit_api_message')]
])
special_settings_markup = InlineKeyboardMarkup(inline_keyboard=[ special_settings_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Основные настройки", callback_data='clb_change_main_settings'), [InlineKeyboardButton(text="Основные настройки", callback_data='clb_change_main_settings'),
InlineKeyboardButton(text="Риск-менеджмент", callback_data='clb_change_risk_management_settings')], InlineKeyboardButton(text="Риск-менеджмент", callback_data='clb_change_risk_management_settings')],
[InlineKeyboardButton(text="Условия запуска", callback_data='clb_change_condition_settings'), [InlineKeyboardButton(text="Условия запуска", callback_data='clb_change_condition_settings')],
InlineKeyboardButton(text="Дополнительные параметры", callback_data='clb_change_additional_settings')], # InlineKeyboardButton(text="Дополнительные параметры", callback_data='clb_change_additional_settings')],
[InlineKeyboardButton(text="Подключить Bybit", callback_data='clb_new_user_connect_bybit_api_message')], [InlineKeyboardButton(text="Подключить Bybit", callback_data='clb_new_user_connect_bybit_api_message')],
back_btn_to_main
back_btn_profile
]) ])
connect_bybit_api_markup = InlineKeyboardMarkup(inline_keyboard=[ connect_bybit_api_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Подключить Bybit", callback_data='clb_new_user_connect_bybit_api')] [InlineKeyboardButton(text="Подключить Bybit", callback_data='clb_new_user_connect_bybit_api')]
]) ])
@@ -32,15 +45,13 @@ trading_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Настройки", callback_data='clb_settings_message')], [InlineKeyboardButton(text="Настройки", callback_data='clb_settings_message')],
[InlineKeyboardButton(text="Мои сделки", callback_data='clb_my_deals')], [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_stop_trading')], [InlineKeyboardButton(text="Остановить торговлю", callback_data='clb_stop_trading')],
]) ])
start_trading_markup = InlineKeyboardMarkup(inline_keyboard=[ start_trading_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="На главную", callback_data='back_to_main')],
[InlineKeyboardButton(text="Начать торговлю", callback_data="clb_start_chatbot_trading")], [InlineKeyboardButton(text="Начать торговлю", callback_data="clb_start_chatbot_trading")],
[InlineKeyboardButton(text="Установить таймер", callback_data="clb_set_timer")], [InlineKeyboardButton(text="На главную", callback_data='back_to_main')],
]) ])
cancel = InlineKeyboardMarkup(inline_keyboard=[ cancel = InlineKeyboardMarkup(inline_keyboard=[
@@ -52,15 +63,11 @@ entry_order_type_markup = InlineKeyboardMarkup(
[ [
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_to_main
] ]
) )
back_btn_list_settings = [InlineKeyboardButton(text="Назад",
callback_data='clb_back_to_special_settings_message')] # Кнопка для возврата к списку каталога настроек
back_btn_list_settings_markup = InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Назад",
callback_data='clb_back_to_special_settings_message')]]) # Клавиатура для возврата к списку каталога настроек
back_btn_to_main = [InlineKeyboardButton(text="На главную", callback_data='clb_back_to_main')]
back_to_main = InlineKeyboardMarkup(inline_keyboard=[ back_to_main = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="На главную", callback_data='back_to_main')], [InlineKeyboardButton(text="На главную", callback_data='back_to_main')],
@@ -68,6 +75,7 @@ back_to_main = InlineKeyboardMarkup(inline_keyboard=[
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_switch_state'),
InlineKeyboardButton(text='Тип маржи', callback_data='clb_change_margin_type')], InlineKeyboardButton(text='Тип маржи', callback_data='clb_change_margin_type')],
[InlineKeyboardButton(text='Размер кредитного плеча', callback_data='clb_change_size_leverage'), [InlineKeyboardButton(text='Размер кредитного плеча', callback_data='clb_change_size_leverage'),
@@ -93,16 +101,16 @@ 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_mode'),
InlineKeyboardButton(text='Таймер', callback_data='clb_change_timer')], 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')],
#
[InlineKeyboardButton(text='Сигналы TradingView', callback_data='clb_change_tradingview_cues'), # [InlineKeyboardButton(text='Сигналы TradingView', callback_data='clb_change_tradingview_cues'),
InlineKeyboardButton(text='Webhook URL', callback_data='clb_change_webhook')], # InlineKeyboardButton(text='Webhook URL', callback_data='clb_change_webhook')],
#
[InlineKeyboardButton(text='AI - аналитика', callback_data='clb_change_ai_analytics')], # [InlineKeyboardButton(text='AI - аналитика', callback_data='clb_change_ai_analytics')],
back_btn_list_settings, back_btn_list_settings,
back_btn_to_main back_btn_to_main
@@ -120,10 +128,9 @@ additional_settings_markup = InlineKeyboardMarkup(inline_keyboard=[
trading_mode_markup = InlineKeyboardMarkup(inline_keyboard=[ trading_mode_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Лонг", callback_data="trade_mode_long"), [InlineKeyboardButton(text="Лонг", callback_data="trade_mode_long"),
InlineKeyboardButton(text="Шорт", callback_data="trade_mode_short")], InlineKeyboardButton(text="Шорт", callback_data="trade_mode_short"),
InlineKeyboardButton(text="Свитч", callback_data="trade_mode_switch")],
[InlineKeyboardButton(text="Свитч", callback_data="trade_mode_switch"), # InlineKeyboardButton(text="Смарт", callback_data="trade_mode_smart")],
InlineKeyboardButton(text="Смарт", callback_data="trade_mode_smart")],
back_btn_list_settings, back_btn_list_settings,
back_btn_to_main back_btn_to_main
@@ -133,14 +140,15 @@ margin_type_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Изолированный", callback_data="margin_type_isolated"), [InlineKeyboardButton(text="Изолированный", callback_data="margin_type_isolated"),
InlineKeyboardButton(text="Кросс", callback_data="margin_type_cross")], InlineKeyboardButton(text="Кросс", callback_data="margin_type_cross")],
back_btn_list_settings, back_btn_list_settings
back_btn_to_main
]) ])
trigger_markup = InlineKeyboardMarkup(inline_keyboard=[ # ИЗМЕНИТЬ НА INLINE trigger_markup = InlineKeyboardMarkup(inline_keyboard=[ # ИЗМЕНИТЬ НА INLINE
[InlineKeyboardButton(text='Ручной', callback_data="clb_trigger_ruchnoy"), [InlineKeyboardButton(text='Ручной', callback_data="clb_trigger_manual")],
InlineKeyboardButton(text='TradingView', callback_data="clb_trigger_tradingview")], # [InlineKeyboardButton(text='TradingView', callback_data="clb_trigger_tradingview")],
[InlineKeyboardButton(text="Автоматический", callback_data="clb_trigger_auto")] [InlineKeyboardButton(text="Автоматический", callback_data="clb_trigger_auto")],
back_btn_list_settings,
back_btn_to_main
]) ])
buttons_yes_no_markup = InlineKeyboardMarkup(inline_keyboard=[ buttons_yes_no_markup = InlineKeyboardMarkup(inline_keyboard=[
@@ -155,15 +163,21 @@ buttons_on_off_markup = InlineKeyboardMarkup(inline_keyboard=[ # ИЗМЕНИТ
my_deals_select_markup = InlineKeyboardMarkup(inline_keyboard=[ my_deals_select_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='Открытые сделки', callback_data="clb_open_deals"), [InlineKeyboardButton(text='Открытые сделки', callback_data="clb_open_deals"),
InlineKeyboardButton(text='Открытые ордера', callback_data="clb_open_orders")], InlineKeyboardButton(text='Лимитные ордера', callback_data="clb_open_orders")],
back_btn_to_main back_btn_to_main
]) ])
def create_trades_inline_keyboard(trades): def create_trades_inline_keyboard(trades):
builder = InlineKeyboardBuilder() builder = InlineKeyboardBuilder()
for trade in trades: for trade in trades:
symbol = trade['symbol'] if isinstance(trade, dict) else trade.symbol builder.button(text=trade, callback_data=f"show_deal_{trade}")
builder.button(text=symbol, callback_data=f"show_deal_{symbol}") builder.adjust(2)
return builder.as_markup()
def create_trades_inline_keyboard_limits(trades):
builder = InlineKeyboardBuilder()
for trade in trades:
builder.button(text=trade, callback_data=f"show_limit_{trade}")
builder.adjust(2) builder.adjust(2)
return builder.as_markup() return builder.as_markup()
@@ -176,16 +190,18 @@ def create_close_deal_markup(symbol: str) -> InlineKeyboardMarkup:
back_btn_to_main back_btn_to_main
]) ])
def create_close_limit_markup(symbol: str) -> InlineKeyboardMarkup:
return InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Закрыть лимитный ордер", callback_data=f"close_limit:{symbol}")],
back_btn_to_main
])
timer_markup = InlineKeyboardMarkup(inline_keyboard=[ timer_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Установить таймер", callback_data="clb_set_timer")], [InlineKeyboardButton(text="Установить таймер", callback_data="clb_set_timer")],
[InlineKeyboardButton(text="Удалить таймер", callback_data="clb_delete_timer")],
back_btn_to_main back_btn_to_main
]) ])
cancel_start_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text="Отменить таймер", callback_data="clb_stop_timer")]
])
stop_choice_markup = InlineKeyboardMarkup( stop_choice_markup = InlineKeyboardMarkup(
inline_keyboard=[ inline_keyboard=[
[ [
@@ -193,4 +209,9 @@ stop_choice_markup = InlineKeyboardMarkup(
InlineKeyboardButton(text="Остановить по таймеру", callback_data="stop_with_timer"), InlineKeyboardButton(text="Остановить по таймеру", callback_data="stop_with_timer"),
] ]
] ]
) )
switch_state_markup = InlineKeyboardMarkup(inline_keyboard=[
[InlineKeyboardButton(text='По направлению', callback_data="clb_long_switch"),
InlineKeyboardButton(text='Против направления', callback_data="clb_short_switch")],
])

View File

@@ -1,6 +1,6 @@
from datetime import datetime from datetime import datetime
import logging.config import logging.config
from sqlalchemy.sql.sqltypes import DateTime from sqlalchemy.sql.sqltypes import DateTime, Numeric
from sqlalchemy import BigInteger, Boolean, Integer, String, ForeignKey from sqlalchemy import BigInteger, Boolean, Integer, String, ForeignKey
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
@@ -54,10 +54,10 @@ class User_Bybit_API(Base):
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id")) tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True)
api_key = mapped_column(String(18), default='None') api_key = mapped_column(String(18), unique=True, nullable=True)
secret_key = mapped_column(String(36), default='None') secret_key = mapped_column(String(36), unique=True, nullable=True)
class User_Symbol(Base): class User_Symbol(Base):
@@ -65,25 +65,12 @@ class User_Symbol(Base):
Модель таблицы user_main_settings. Модель таблицы user_main_settings.
Хранит основные настройки торговли для пользователя. Хранит основные настройки торговли для пользователя.
Атрибуты:
id (int): Внутренний первичный ключ записи.
tg_id (int): Внешний ключ на Telegram пользователя.
trading_mode (str): Режим торговли (ForeignKey на trading_modes.mode).
margin_type (str): Тип маржи (ForeignKey на margin_types.type).
size_leverage (int): Кредитное плечо.
starting_quantity (int): Начальный объём позиции.
martingale_factor (int): Коэффициент мартингейла.
martingale_step (int): Текущий шаг мартингейла.
maximal_quantity (int): Максимальное количество шагов мартингейла.
entry_order_type (str): Тип входа (Market или Limit).
limit_order_price (str, optional): Цена лимитного ордера, если используется.
""" """
__tablename__ = 'user_symbols' __tablename__ = 'user_symbols'
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id")) tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True)
symbol = mapped_column(String(18), default='PENGUUSDT') symbol = mapped_column(String(18), default='PENGUUSDT')
@@ -94,7 +81,7 @@ class Trading_Mode(Base):
Атрибуты: Атрибуты:
id (int): Первичный ключ. id (int): Первичный ключ.
mode (str): Уникальный режим (например, 'Long', 'Short'). mode (str): Уникальный режим (например, 'Long', 'Short', 'Switch).
""" """
__tablename__ = 'trading_modes' __tablename__ = 'trading_modes'
@@ -120,17 +107,16 @@ class Margin_type(Base):
class Trigger(Base): class Trigger(Base):
""" """
Справочник видов триггеров для сделок. Справочник триггеров для сделок.
Атрибуты: Атрибуты:
id (int): Первичный ключ. id (int): Первичный ключ.
trigger (str): Название триггера (например, 'Ручной', 'Автоматический').
""" """
__tablename__ = 'triggers' __tablename__ = 'triggers'
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
trigger = mapped_column(String(15), unique=True) trigger_price = mapped_column(Integer(), default=0)
class User_Main_Settings(Base): class User_Main_Settings(Base):
@@ -149,22 +135,25 @@ class User_Main_Settings(Base):
maximal_quantity (int): Максимальное число шагов мартингейла. maximal_quantity (int): Максимальное число шагов мартингейла.
entry_order_type (str): Тип ордера входа (Market/Limit). entry_order_type (str): Тип ордера входа (Market/Limit).
limit_order_price (Optional[str]): Цена лимитного ордера, если есть. limit_order_price (Optional[str]): Цена лимитного ордера, если есть.
last_side (str): Последняя сторона ордера.
""" """
__tablename__ = 'user_main_settings' __tablename__ = 'user_main_settings'
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id")) tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True)
trading_mode = mapped_column(ForeignKey("trading_modes.mode")) trading_mode = mapped_column(ForeignKey("trading_modes.mode"))
margin_type = mapped_column(ForeignKey("margin_types.type")) margin_type = mapped_column(ForeignKey("margin_types.type"))
switch_state = mapped_column(String(10), default='По направлению')
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) 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') entry_order_type = mapped_column(String(10), default='Market')
limit_order_price = mapped_column(String(20), nullable=True) limit_order_price = mapped_column(Numeric(18, 15), nullable=True)
last_side = mapped_column(String(10), default='Buy')
class User_Risk_Management_Settings(Base): class User_Risk_Management_Settings(Base):
@@ -183,7 +172,7 @@ class User_Risk_Management_Settings(Base):
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id")) tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True)
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)
@@ -210,9 +199,9 @@ class User_Condition_Settings(Base):
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id")) tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True)
trigger = mapped_column(ForeignKey("triggers.trigger")) trigger = mapped_column(String(15), default='Автоматический')
filter_time = mapped_column(String(25), default='???') filter_time = mapped_column(String(25), default='???')
filter_volatility = mapped_column(Boolean, default=False) filter_volatility = mapped_column(Boolean, default=False)
external_cues = mapped_column(Boolean, default=False) external_cues = mapped_column(Boolean, default=False)
@@ -236,7 +225,7 @@ class User_Additional_Settings(Base):
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id")) tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True)
pattern_save = mapped_column(Boolean, default=False) pattern_save = mapped_column(Boolean, default=False)
autostart = mapped_column(Boolean, default=False) autostart = mapped_column(Boolean, default=False)
@@ -259,7 +248,7 @@ class USER_DEALS(Base):
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id")) tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True)
symbol = mapped_column(String(18), default='PENGUUSDT') symbol = mapped_column(String(18), default='PENGUUSDT')
side = mapped_column(String(10), nullable=False) side = mapped_column(String(10), nullable=False)
@@ -281,7 +270,7 @@ class UserTimer(Base):
__tablename__ = 'user_timers' __tablename__ = 'user_timers'
id: Mapped[int] = mapped_column(primary_key=True) id: Mapped[int] = mapped_column(primary_key=True)
tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id")) tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id"), unique=True, nullable=True)
timer_minutes = mapped_column(Integer, nullable=False, default=0) timer_minutes = mapped_column(Integer, nullable=False, default=0)
timer_start = mapped_column(DateTime, default=datetime.utcnow) timer_start = mapped_column(DateTime, default=datetime.utcnow)
timer_end = mapped_column(DateTime, nullable=True) timer_end = mapped_column(DateTime, nullable=True)
@@ -309,9 +298,9 @@ async def async_main():
logger.info("Заполение таблицы типов маржи") logger.info("Заполение таблицы типов маржи")
await conn.execute(Margin_type.__table__.insert().values(type=type)) await conn.execute(Margin_type.__table__.insert().values(type=type))
triggers = ['Ручной', 'Автоматический', 'TradingView'] last_side = ['Buy', 'Sell']
for trigger in triggers: for side in last_side:
result = await conn.execute(select(Trigger).where(Trigger.trigger == trigger)) result = await conn.execute(select(User_Main_Settings).where(User_Main_Settings.last_side == side))
if not result.first(): if not result.first():
logger.info("Заполение таблицы триггеров") logger.info("Заполение таблицы последнего направления")
await conn.execute(Trigger.__table__.insert().values(trigger=trigger)) await conn.execute(User_Main_Settings.__table__.insert().values(last_side=side))

View File

@@ -1,4 +1,5 @@
import logging.config import logging.config
from logger_helper.logger_helper import LOGGING_CONFIG from logger_helper.logger_helper import LOGGING_CONFIG
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any from typing import Any
@@ -238,17 +239,21 @@ async def update_symbol(tg_id: int, symbol: str) -> None:
await session.commit() await session.commit()
async def update_api_key(tg_id: int, api: str) -> None: async def upsert_api_keys(tg_id: int, api_key: str, secret_key: str) -> None:
"""Обновить API ключ пользователя.""" """Обновить API ключ пользователя."""
async with async_session() as session: async with async_session() as session:
await session.execute(update(UBA).where(UBA.tg_id == tg_id).values(api_key=api)) result = await session.execute(select(UBA).where(UBA.tg_id == tg_id))
await session.commit() user = result.scalars().first()
if user:
if api_key is not None:
async def update_secret_key(tg_id: int, api: str) -> None: user.api_key = api_key
"""Обновить секретный ключ пользователя.""" if secret_key is not None:
async with async_session() as session: user.secret_key = secret_key
await session.execute(update(UBA).where(UBA.tg_id == tg_id).values(secret_key=api)) logger.info(f"Обновлены ключи для пользователя {tg_id}")
else:
new_user = UBA(tg_id=tg_id, api_key=api_key, secret_key=secret_key)
session.add(new_user)
logger.info(f"Добавлен новый пользователь {tg_id} с ключами")
await session.commit() await session.commit()
@@ -291,10 +296,10 @@ async def get_for_registration_margin_type():
return type return type
async def get_for_registration_trigger(): async def get_for_registration_trigger(tg_id):
"""Получить триггер по умолчанию.""" """Получить триггер по умолчанию."""
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(UCS.trigger).where(tg_id == tg_id))
return trigger return trigger
@@ -302,32 +307,20 @@ 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("Получение основных настроек пользователя %s", 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))
size_leverage = await session.scalar(select(UMS.size_leverage).where(UMS.tg_id == tg_id))
starting_quantity = await session.scalar(select(UMS.starting_quantity).where(UMS.tg_id == tg_id))
martingale_factor = await session.scalar(select(UMS.martingale_factor).where(UMS.tg_id == tg_id))
maximal_quantity = await session.scalar(select(UMS.maximal_quantity).where(UMS.tg_id == tg_id))
entry_order_type = await session.scalar(select(UMS.entry_order_type).where(UMS.tg_id == tg_id))
limit_order_price = await session.scalar(select(UMS.limit_order_price).where(UMS.tg_id == tg_id))
martingale_step = await session.scalar(select(UMS.martingale_step).where(UMS.tg_id == tg_id))
data = { data = {
'trading_mode': trading_mode, 'trading_mode': user.trading_mode,
'margin_type': margin_mode, 'margin_type': user.margin_type,
'size_leverage': size_leverage, 'switch_state': user.switch_state,
'starting_quantity': starting_quantity, 'size_leverage': user.size_leverage,
'martingale_factor': martingale_factor, 'starting_quantity': user.starting_quantity,
'maximal_quantity': maximal_quantity, 'martingale_factor': user.martingale_factor,
'entry_order_type': entry_order_type, 'maximal_quantity': user.maximal_quantity,
'limit_order_price': limit_order_price, 'entry_order_type': user.entry_order_type,
'martingale_step': martingale_step, 'limit_order_price': user.limit_order_price,
'martingale_step': user.martingale_step,
'last_side': user.last_side,
} }
return data return data
@@ -547,3 +540,46 @@ async def update_martingale_step(tg_id, step):
await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(martingale_step=step)) await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(martingale_step=step))
await session.commit() await session.commit()
async def update_switch_mode_enabled(tg_id, switch_mode):
"""Обновить режим переключения пользователя."""
async with async_session() as session:
await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(switch_mode_enabled=switch_mode))
await session.commit()
async def update_switch_state(tg_id, switch_state):
"""Обновить состояние переключения пользователя."""
async with async_session() as session:
await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(switch_state=switch_state))
await session.commit()
async def update_trigger(tg_id, trigger):
"""Обновить триггер пользователя."""
async with async_session() as session:
await session.execute(update(UCS).where(UCS.tg_id == tg_id).values(trigger=trigger))
await session.commit()
async def set_last_series_info(tg_id: int, last_side: str):
async with async_session() as session:
async with session.begin():
# Обновляем запись
result = await session.execute(
update(UMS)
.where(UMS.tg_id == tg_id)
.values(last_side=last_side)
)
if result.rowcount == 0:
# Если запись не существует, создаём новую
new_entry = UMS(
tg_id=tg_id,
last_side=last_side,
)
session.add(new_entry)
await session.commit()

View File

@@ -7,7 +7,7 @@ async def reg_new_user_default_additional_settings(id, message):
await rq.set_new_user_default_additional_settings(tg_id) await rq.set_new_user_default_additional_settings(tg_id)
async def main_settings_message(id, message, state): async def main_settings_message(id, message):
text = '''<b>Дополнительные параметры</b> text = '''<b>Дополнительные параметры</b>
<b>- Сохранить как шаблон стратегии:</b> да / нет <b>- Сохранить как шаблон стратегии:</b> да / нет

View File

@@ -1,13 +1,10 @@
import asyncio import logging.config
import logging.config
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 import Router, F
from aiogram.types import Message, CallbackQuery from aiogram.types import Message, CallbackQuery
from aiogram.fsm.context import FSMContext 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 from app.states.States import condition_settings
from app.services.Bybit.functions.Futures import trading_cycle
from logger_helper.logger_helper import LOGGING_CONFIG from logger_helper.logger_helper import LOGGING_CONFIG
@@ -17,45 +14,51 @@ logger = logging.getLogger("condition_settings")
condition_settings_router = Router() condition_settings_router = Router()
class condition_settings(StatesGroup): async def reg_new_user_default_condition_settings(id):
trigger = State()
timer = State()
volatilty = State()
volume = State()
integration = State()
use_tv_signal = State()
async def reg_new_user_default_condition_settings(id, message):
tg_id = id tg_id = id
trigger = await rq.get_for_registration_trigger() trigger = await rq.get_for_registration_trigger(tg_id)
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):
text = """ <b>Условия запуска</b>
<b>- Триггер:</b> Ручной запуск / Сигнал TradingView / Полностью автоматический tg_id = id
<b>- Таймер: </b> установить таймер / остановить таймер trigger = await rq.get_for_registration_trigger(tg_id)
<b>- Фильтр волатильности / объёма: </b> включить/отключить text = f""" <b>Условия запуска</b>
<b>- Интеграции и внешние сигналы: </b>
<b>- Использовать сигналы TradingView:</b> да / нет <b>- Режим торговли:</b> {trigger}
<b>- Использовать AI-аналитику от ChatGPT:</b> да / не <b>- Таймер: </b> установить таймер / удалить таймер
<b>- Webhook URL для сигналов (если используется TradingView): </b>
""" """
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(id, message, state: FSMContext):
text = '''Триггер await state.set_state(condition_settings.trigger)
text = '''
Описание ручного запуска, сигналов, автоматического режима ''' <b>- Автоматический:</b> торговля будет происходить в рамках серии ставок.
<b>- Ручной:</b> торговля будет происходить только в ручном режиме.
<em>- Выберите тип триггера:</em>'''
await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.trigger_markup) await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.trigger_markup)
@condition_settings_router.callback_query(F.data == "clb_trigger_manual")
async def trigger_manual_callback(callback: CallbackQuery, state: FSMContext):
await state.set_state(condition_settings.trigger)
await rq.update_trigger(tg_id=callback.from_user.id, trigger="Ручной")
await main_settings_message(callback.from_user.id, callback.message)
await callback.answer()
@condition_settings_router.callback_query(F.data == "clb_trigger_auto")
async def trigger_manual_callback(callback: CallbackQuery, state: FSMContext):
await state.set_state(condition_settings.trigger)
await rq.update_trigger(tg_id=callback.from_user.id, trigger="Автоматический")
await main_settings_message(callback.from_user.id, callback.message)
await callback.answer()
async def timer_message(id, message: Message, state: FSMContext): async def timer_message(id, message: Message, state: FSMContext):
await state.set_state(condition_settings.timer) await state.set_state(condition_settings.timer)
@@ -65,7 +68,7 @@ async def timer_message(id, message: Message, state: FSMContext):
return return
await message.answer( await message.answer(
f"Таймер: {timer_info['timer_minutes']} мин\n", f"Таймер установлен на: {timer_info['timer_minutes']} мин\n",
reply_markup=inline_markup.timer_markup reply_markup=inline_markup.timer_markup
) )
@@ -73,7 +76,7 @@ async def timer_message(id, message: Message, state: FSMContext):
@condition_settings_router.callback_query(F.data == "clb_set_timer") @condition_settings_router.callback_query(F.data == "clb_set_timer")
async def set_timer_callback(callback: CallbackQuery, state: FSMContext): async def set_timer_callback(callback: CallbackQuery, state: FSMContext):
await state.set_state(condition_settings.timer) # состояние для ввода времени await state.set_state(condition_settings.timer) # состояние для ввода времени
await callback.message.answer("Введите время работы в минутах (например, 60):") await callback.message.answer("Введите время работы в минутах (например, 60):", reply_markup=inline_markup.cancel)
await callback.answer() await callback.answer()
@@ -86,14 +89,21 @@ async def process_timer_input(message: Message, state: FSMContext):
return return
await rq.update_user_timer(message.from_user.id, minutes) await rq.update_user_timer(message.from_user.id, minutes)
await message.answer(f"Таймер установлен на {minutes} минут.\nНажмите кнопку 'Начать торговлю' для запуска.", logger.info("Timer set for user %s: %s minutes", message.from_user.id, minutes)
reply_markup=inline_markup.start_trading_markup) await timer_message(message.from_user.id, message, state)
await state.clear() await state.clear()
except ValueError: except ValueError:
await message.reply("Пожалуйста, введите корректное число.") await message.reply("Пожалуйста, введите корректное число.")
@condition_settings_router.callback_query(F.data == "clb_delete_timer")
async def delete_timer_callback(callback: CallbackQuery, state: FSMContext):
await state.clear()
await rq.update_user_timer(callback.from_user.id, 0)
logger.info("Timer deleted for user %s", callback.from_user.id)
await timer_message(callback.from_user.id, callback.message, state)
await callback.answer()
async def filter_volatility_message(message, state): async def filter_volatility_message(message, state):
text = '''Фильтр волатильности text = '''Фильтр волатильности
@@ -130,4 +140,4 @@ async def ai_analytics_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)

View File

@@ -10,7 +10,7 @@ async def start_message(message):
username = message.from_user.first_name username = message.from_user.first_name
else: else:
username = f'{message.from_user.first_name} {message.from_user.last_name}' username = f'{message.from_user.first_name} {message.from_user.last_name}'
await message.answer(f""" Привет <b>{username}</b>! 👋""", parse_mode='html', reply_markup=reply_markup.base_buttons_markup) await message.answer(f""" Привет <b>{username}</b>! 👋""", parse_mode='html')
await message.answer("Добро пожаловать в чат-робот для автоматизации трейдинга — вашего надежного помощника для анализа рынка и принятия взвешенных решений.", await message.answer("Добро пожаловать в чат-робот для автоматизации трейдинга — вашего надежного помощника для анализа рынка и принятия взвешенных решений.",
parse_mode='html', reply_markup=inline_markup.start_markup) parse_mode='html', reply_markup=inline_markup.start_markup)

View File

@@ -1,23 +1,21 @@
from aiogram import Router from aiogram import Router
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
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
# FSM - Механизм состояния from app.services.Bybit.functions.price_symbol import get_price
from aiogram.fsm.state import State, StatesGroup from app.services.Bybit.functions.Futures import safe_float, calculate_total_budget, get_bybit_client
from app.states.States import update_main_settings
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("main_settings")
router_main_settings = Router() router_main_settings = Router()
class update_main_settings(StatesGroup):
trading_mode = State()
size_leverage = State()
margin_type = State()
martingale_factor = State()
starting_quantity = State()
maximal_quantity = State()
async def reg_new_user_default_main_settings(id, message): async def reg_new_user_default_main_settings(id, message):
tg_id = id tg_id = id
@@ -26,21 +24,58 @@ async def reg_new_user_default_main_settings(id, message):
margin_type = await rq.get_for_registration_margin_type() margin_type = await rq.get_for_registration_margin_type()
await rq.set_new_user_default_main_settings(tg_id, trading_mode, margin_type) await rq.set_new_user_default_main_settings(tg_id, trading_mode, margin_type)
async def main_settings_message(id, message):
try:
data = await rq.get_user_main_settings(id)
tg_id = id
data_main_stgs = await rq.get_user_main_settings(id)
data_risk_stgs = await rq.get_user_risk_management_settings(id)
client = await get_bybit_client(tg_id)
symbol = await rq.get_symbol(tg_id)
max_martingale_steps = (data_main_stgs or {}).get('maximal_quantity', 0)
commission_fee = (data_risk_stgs or {}).get('commission_fee')
starting_quantity = safe_float((data_main_stgs or {}).get('starting_quantity'))
martingale_factor = safe_float((data_main_stgs or {}).get('martingale_factor'))
fee_info = client.get_fee_rates(category='linear', symbol=symbol)
leverage = safe_float((data_main_stgs or {}).get('size_leverage'))
price = await get_price(tg_id, symbol=symbol)
entry_price = safe_float(price)
if commission_fee == "Да":
commission_fee_percent = safe_float(fee_info['result']['list'][0]['takerFeeRate'])
else:
commission_fee_percent = 0.0
total_budget = await calculate_total_budget(
starting_quantity=starting_quantity,
martingale_factor=martingale_factor,
max_steps=max_martingale_steps,
commission_fee_percent=commission_fee_percent,
leverage=leverage,
current_price=entry_price,
)
await message.answer(f"""<b>Основные настройки</b>
<b>- Режим торговли:</b> {data['trading_mode']}
<b>- Состояние свитча:</b> {data['switch_state']}
<b>- Направление последней сделки:</b> {data['last_side']}
<b>- Тип маржи:</b> {data['margin_type']}
<b>- Размер кредитного плеча:</b> х{data['size_leverage']}
<b>- Начальная ставка:</b> {data['starting_quantity']}
<b>- Коэффициент мартингейла:</b> {data['martingale_factor']}
<b>- Текущий шаг:</b> {data['martingale_step']}
<b>- Максимальное количество ставок в серии:</b> {data['maximal_quantity']}
<b>- Требуемый бюджет:</b> {total_budget:.2f} USDT
""", parse_mode='html', reply_markup=inline_markup.main_settings_markup)
except PermissionError as e:
logger.error("Authenticated endpoints require keys: %s", e)
await message.answer("Вы не авторизованы.", reply_markup=inline_markup.connect_bybit_api_message)
async def main_settings_message(id, message, state):
data = await rq.get_user_main_settings(id)
await message.answer(f"""<b>Основные настройки</b>
<b>- Режим торговли:</b> {data['trading_mode']}
<b>- Тип маржи:</b> {data['margin_type']}
<b>- Размер кредитного плеча:</b> х{data['size_leverage']}
<b>- Начальная ставка:</b> {data['starting_quantity']}
<b>- Коэффициент мартингейла:</b> {data['martingale_factor']}
<b>- Количество ставок в серии:</b> {data['martingale_step']}
<b>- Максимальное количество ставок в серии:</b> {data['maximal_quantity']}
""", parse_mode='html', reply_markup=inline_markup.main_settings_markup)
async def trading_mode_message(message, state): async def trading_mode_message(message, state):
await state.set_state(update_main_settings.trading_mode) await state.set_state(update_main_settings.trading_mode)
@@ -51,81 +86,143 @@ async def trading_mode_message(message, state):
<b>Шорт</b> — метод продажи активов, взятых в кредит, чтобы получить прибыль от снижения цены. <b>Шорт</b> — метод продажи активов, взятых в кредит, чтобы получить прибыль от снижения цены.
<b>Смарт</b> — автоматизированный режим, который подбирает оптимальную стратегию в зависимости от текущих рыночных условий.
<b>Свитч</b> — динамическое переключение между торговыми режимами для максимизации эффективности. <b>Свитч</b> — динамическое переключение между торговыми режимами для максимизации эффективности.
<em>Выберите ниже для изменений:</em> <em>Выберите ниже для изменений:</em>
""", parse_mode='html', reply_markup=inline_markup.trading_mode_markup) """, parse_mode='html', reply_markup=inline_markup.trading_mode_markup)
@router_main_settings.callback_query(update_main_settings.trading_mode) @router_main_settings.callback_query(update_main_settings.trading_mode)
async def state_trading_mode(callback: CallbackQuery, state): async def state_trading_mode(callback: CallbackQuery, state):
await callback.answer() await callback.answer()
id = callback.from_user.id id = callback.from_user.id
data_settings = await rq.get_user_main_settings(id) data_settings = await rq.get_user_main_settings(id)
try: try:
match callback.data: match callback.data:
case 'trade_mode_long': case 'trade_mode_long':
await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Long") await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Long")
await rq.update_trade_mode_user(id, 'Long') await rq.update_trade_mode_user(id, 'Long')
await main_settings_message(id, callback.message, state) await main_settings_message(id, callback.message)
await state.clear() await state.clear()
case 'trade_mode_short': case 'trade_mode_short':
await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Short") await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Short")
await rq.update_trade_mode_user(id, 'Short') await rq.update_trade_mode_user(id, 'Short')
await main_settings_message(id, callback.message, state) await main_settings_message(id, callback.message)
await state.clear() await state.clear()
case 'trade_mode_switch':
case 'trade_mode_switch':
await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Switch") await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Switch")
await rq.update_trade_mode_user(id, 'Switch') await rq.update_trade_mode_user(id, 'Switch')
await main_settings_message(id, callback.message, state) await main_settings_message(id, callback.message)
await state.clear() await state.clear()
case 'trade_mode_smart':
case 'trade_mode_smart':
await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Smart") await callback.message.answer(f"✅ Изменено: {data_settings['trading_mode']} → Smart")
await rq.update_trade_mode_user(id, 'Smart') await rq.update_trade_mode_user(id, 'Smart')
await main_settings_message(id, callback.message, state) await main_settings_message(id, callback.message)
await state.clear() await state.clear()
except Exception as e: except Exception as e:
print(f"error: {e}") logger.error(e)
async def size_leverage_message (message, state):
async def switch_mode_enabled_message(message, state):
await state.set_state(update_main_settings.switch_mode_enabled)
await message.edit_text(
f"""<b> Состояние свитча</b>
<b>По направлению</b> - по направлению последней сделки предыдущей серии
<b>Против направления</b> - против направления последней сделки предыдущей серии
<em>По умолчанию при первом запуске бота, направление сделки установлено на "Buy".</em>
<em>Выберите ниже для изменений:</em>""", parse_mode='html',
reply_markup=inline_markup.switch_state_markup)
@router_main_settings.callback_query(lambda c: c.data in ["clb_long_switch", "clb_short_switch"])
async def state_switch_mode_enabled(callback: CallbackQuery, state):
await callback.answer()
tg_id = callback.from_user.id
val = "По направлению" if callback.data == "clb_long_switch" else "Против направления"
if val == "По направлению":
await rq.update_switch_state(tg_id, "По направлению")
await callback.message.answer(f"Состояние свитча: {val}")
await main_settings_message(tg_id, callback.message)
else:
await rq.update_switch_state(tg_id, "Против направления")
await callback.message.answer(f"Состояние свитча: {val}")
await main_settings_message(tg_id, callback.message)
await state.clear()
async def size_leverage_message(message, state):
await state.set_state(update_main_settings.size_leverage) await state.set_state(update_main_settings.size_leverage)
await message.edit_text("Введите размер <b>кредитного плеча</b> (от 1 до 100): ", parse_mode='html', reply_markup=inline_markup.back_btn_list_settings_markup) await message.edit_text("Введите размер <b>кредитного плеча</b> (от 1 до 100): ", parse_mode='html',
reply_markup=inline_markup.back_btn_list_settings_markup)
@router_main_settings.message(update_main_settings.size_leverage) @router_main_settings.message(update_main_settings.size_leverage)
async def state_size_leverage(message: Message, state): async def state_size_leverage(message: Message, state):
await state.update_data(size_leverage = message.text) try:
leverage = float(message.text)
if leverage <= 0:
raise ValueError("Неверное значение")
except ValueError:
await message.answer(
"Ошибка: пожалуйста, введите положительное число для кредитного плеча."
"\nПопробуйте снова."
)
return
await state.update_data(size_leverage=message.text)
data = await state.get_data() data = await state.get_data()
data_settings = await rq.get_user_main_settings(message.from_user.id) tg_id = message.from_user.id
symbol = await rq.get_symbol(tg_id)
leverage = data['size_leverage']
client = await get_bybit_client(tg_id)
if data['size_leverage'].isdigit() and int(data['size_leverage']) <= 100: instruments_resp = client.get_instruments_info(category="linear", symbol=symbol)
await message.answer(f"✅ Изменено: {data_settings['size_leverage']}{data['size_leverage']}") info = instruments_resp.get("result", {}).get("list", [])
await rq.update_size_leverange(message.from_user.id, data['size_leverage']) max_leverage = safe_float(info[0].get("leverageFilter", {}).get("maxLeverage", 0))
await main_settings_message(message.from_user.id, message, state)
if safe_float(leverage) > max_leverage:
await message.answer(
f"Запрошенное кредитное плечо {leverage} превышает максимальное {max_leverage} для {symbol}. "
f"Устанавливаю максимальное.",
reply_markup=inline_markup.back_to_main,
)
logger.info(
f"Запрошенное кредитное плечо {leverage} превышает максимальное {max_leverage} для {symbol}. Устанавливаю максимальное.")
await rq.update_size_leverange(message.from_user.id, max_leverage)
await main_settings_message(message.from_user.id, message)
await state.clear() await state.clear()
else: else:
await message.answer(f'⛔️ Ошибка: ваше значение ({data['size_leverage']}) или выше лимита (100) или вы вводите неверные символы') await message.answer(f"✅ Изменено: {leverage}")
await rq.update_size_leverange(message.from_user.id, safe_float(leverage))
await main_settings_message(message.from_user.id, message)
await state.clear()
await main_settings_message(message.from_user.id, message, state)
async def martingale_factor_message(message, state): async def martingale_factor_message(message, state):
await state.set_state(update_main_settings.martingale_factor) await state.set_state(update_main_settings.martingale_factor)
await message.edit_text("Введите <b>коэффициент Мартингейла:</b>", parse_mode='html', reply_markup=inline_markup.back_btn_list_settings_markup) await message.edit_text("Введите <b>коэффициент Мартингейла:</b>", parse_mode='html',
reply_markup=inline_markup.back_btn_list_settings_markup)
@router_main_settings.message(update_main_settings.martingale_factor) @router_main_settings.message(update_main_settings.martingale_factor)
async def state_martingale_factor(message: Message, state): async def state_martingale_factor(message: Message, state):
await state.update_data(martingale_factor = message.text) await state.update_data(martingale_factor=message.text)
data = await state.get_data() data = await state.get_data()
data_settings = await rq.get_user_main_settings(message.from_user.id) data_settings = await rq.get_user_main_settings(message.from_user.id)
@@ -134,14 +231,16 @@ async def state_martingale_factor(message: Message, state):
await message.answer(f"✅ Изменено: {data_settings['martingale_factor']}{data['martingale_factor']}") await message.answer(f"✅ Изменено: {data_settings['martingale_factor']}{data['martingale_factor']}")
await rq.update_martingale_factor(message.from_user.id, data['martingale_factor']) await rq.update_martingale_factor(message.from_user.id, data['martingale_factor'])
await main_settings_message(message.from_user.id, message, state) await main_settings_message(message.from_user.id, message)
await state.clear() await state.clear()
else: else:
await message.answer(f'⛔️ Ошибка: ваше значение ({data['martingale_factor']}) или выше лимита (100) или вы вводите неверные символы') await message.answer(
f'⛔️ Ошибка: ваше значение ({data['martingale_factor']}) или выше лимита (100) или вы вводите неверные символы')
await main_settings_message(message.from_user.id, message)
await main_settings_message(message.from_user.id, message, state)
async def margin_type_message(message, state): async def margin_type_message(message, state):
await state.set_state(update_main_settings.margin_type) await state.set_state(update_main_settings.margin_type)
@@ -161,40 +260,70 @@ async def margin_type_message(message, state):
<em>Выберите ниже для изменений:</em> <em>Выберите ниже для изменений:</em>
""", parse_mode='html', reply_markup=inline_markup.margin_type_markup) """, parse_mode='html', reply_markup=inline_markup.margin_type_markup)
@router_main_settings.callback_query(update_main_settings.margin_type) @router_main_settings.callback_query(update_main_settings.margin_type)
async def state_margin_type(callback: CallbackQuery, state): async def state_margin_type(callback: CallbackQuery, state):
await callback.answer() callback_data = callback.data
if callback_data in ['margin_type_isolated', 'margin_type_cross']:
tg_id = callback.from_user.id
api_key = await rq.get_bybit_api_key(tg_id)
secret_key = await rq.get_bybit_secret_key(tg_id)
data_settings = await rq.get_user_main_settings(tg_id)
symbol = await rq.get_symbol(tg_id)
client = HTTP(api_key=api_key, api_secret=secret_key)
try:
active_positions = client.get_positions(category='linear', settleCoin="USDT")
id = callback.from_user.id positions = active_positions.get('result', {}).get('list', [])
data_settings = await rq.get_user_main_settings(id) except Exception as e:
logger.error(f"error: {e}")
positions = []
try: for pos in positions:
match callback.data: size = pos.get('size')
case 'margin_type_isolated': if float(size) > 0:
await callback.message.answer(f"✅ Изменено: {data_settings['margin_type']} → Isolated") await callback.answer(
"⚠️ Маржинальный режим нельзя менять при открытой позиции"
)
return
await rq.update_margin_type(id, 'Isolated') try:
await main_settings_message(id, callback.message, state) match callback.data:
case 'margin_type_isolated':
await callback.answer()
await callback.message.answer(f"✅ Изменено: {data_settings['margin_type']} → Isolated")
await state.clear() await rq.update_margin_type(tg_id, 'Isolated')
case 'margin_type_cross': await main_settings_message(tg_id, callback.message)
await callback.message.answer(f"✅ Изменено: {data_settings['margin_type']} → Cross")
await rq.update_margin_type(id, 'Cross') await state.clear()
await main_settings_message(id, callback.message, state) case 'margin_type_cross':
await callback.answer()
await callback.message.answer(f"✅ Изменено: {data_settings['margin_type']} → Cross")
await state.clear() await rq.update_margin_type(tg_id, 'Cross')
except Exception as e: await main_settings_message(tg_id, callback.message)
print(f"error: {e}")
async def starting_quantity_message (message, state): await state.clear()
except Exception as e:
logger.error(f"error: {e}")
else:
await callback.answer()
await main_settings_message(callback.from_user.id, callback.message)
await state.clear()
async def starting_quantity_message(message, state):
await state.set_state(update_main_settings.starting_quantity) await 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):
await state.update_data(starting_quantity = message.text) await state.update_data(starting_quantity=message.text)
data = await state.get_data() data = await state.get_data()
data_settings = await rq.get_user_main_settings(message.from_user.id) data_settings = await rq.get_user_main_settings(message.from_user.id)
@@ -203,22 +332,25 @@ async def state_starting_quantity(message: Message, state):
await message.answer(f"✅ Изменено: {data_settings['starting_quantity']}{data['starting_quantity']}") await message.answer(f"✅ Изменено: {data_settings['starting_quantity']}{data['starting_quantity']}")
await rq.update_starting_quantity(message.from_user.id, data['starting_quantity']) await rq.update_starting_quantity(message.from_user.id, data['starting_quantity'])
await main_settings_message(message.from_user.id, message, state) await main_settings_message(message.from_user.id, message)
await state.clear() await state.clear()
else: else:
await message.answer(f'⛔️ Ошибка: вы вводите неверные символы') await message.answer(f'⛔️ Ошибка: вы вводите неверные символы')
await main_settings_message(message.from_user.id, message, state) await main_settings_message(message.from_user.id, message)
async def maximum_quantity_message(message, state): async def maximum_quantity_message(message, state):
await state.set_state(update_main_settings.maximal_quantity) await state.set_state(update_main_settings.maximal_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.maximal_quantity) @router_main_settings.message(update_main_settings.maximal_quantity)
async def state_maximal_quantity(message: Message, state): async def state_maximal_quantity(message: Message, state):
await state.update_data(maximal_quantity = message.text) await state.update_data(maximal_quantity=message.text)
data = await state.get_data() data = await state.get_data()
data_settings = await rq.get_user_main_settings(message.from_user.id) data_settings = await rq.get_user_main_settings(message.from_user.id)
@@ -227,10 +359,11 @@ async def state_maximal_quantity(message: Message, state):
await message.answer(f"✅ Изменено: {data_settings['maximal_quantity']}{data['maximal_quantity']}") await message.answer(f"✅ Изменено: {data_settings['maximal_quantity']}{data['maximal_quantity']}")
await rq.update_maximal_quantity(message.from_user.id, data['maximal_quantity']) await rq.update_maximal_quantity(message.from_user.id, data['maximal_quantity'])
await main_settings_message(message.from_user.id, message, state) await main_settings_message(message.from_user.id, message)
await state.clear() await state.clear()
else: else:
await message.answer(f'⛔️ Ошибка: ваше значение ({data['maximal_quantity']}) или выше лимита (100) или вы вводите неверные символы') await message.answer(
f'⛔️ Ошибка: ваше значение ({data['maximal_quantity']}) или выше лимита (100) или вы вводите неверные символы')
await main_settings_message(message.from_user.id, message, state)
await main_settings_message(message.from_user.id, message)

View File

@@ -1,11 +1,16 @@
from aiogram import Router from aiogram import Router
import app.telegram.Keyboards.inline_keyboards as inline_markup import app.telegram.Keyboards.inline_keyboards as inline_markup
import logging.config
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
from app.states.States import update_risk_management_settings from app.states.States import update_risk_management_settings
from logger_helper.logger_helper import LOGGING_CONFIG
logging.config.dictConfig(LOGGING_CONFIG)
logger = logging.getLogger("risk_management_settings")
router_risk_management_settings = Router() router_risk_management_settings = Router()
@@ -33,7 +38,7 @@ async def price_profit_message(message, state):
text = 'Введите число изменения цены для фиксации прибыли: ' text = 'Введите число изменения цены для фиксации прибыли: '
await message.answer(text=text, parse_mode='html', reply_markup=None) await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.cancel)
@router_risk_management_settings.message(update_risk_management_settings.price_profit) @router_risk_management_settings.message(update_risk_management_settings.price_profit)
@@ -62,7 +67,7 @@ async def price_loss_message(message, state):
text = 'Введите число изменения цены для фиксации убытков: ' text = 'Введите число изменения цены для фиксации убытков: '
await message.answer(text=text, parse_mode='html', reply_markup=None) await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.cancel)
@router_risk_management_settings.message(update_risk_management_settings.price_loss) @router_risk_management_settings.message(update_risk_management_settings.price_loss)
@@ -80,7 +85,8 @@ async def state_price_loss(message: Message, state):
# Пробуем перевести price_profit в число, если это возможно # Пробуем перевести price_profit в число, если это возможно
try: try:
current_price_profit_num = int(current_price_profit) current_price_profit_num = int(current_price_profit)
except Exception: except Exception as e:
logger.error(e)
current_price_profit_num = 0 current_price_profit_num = 0
# Флаг, если price_profit изначально равен 0 или совпадает со старым стоп-лоссом # Флаг, если price_profit изначально равен 0 или совпадает со старым стоп-лоссом
@@ -111,7 +117,7 @@ async def max_risk_deal_message(message, state):
text = 'Введите число (процент от баланса) для изменения максимального риска на сделку: ' text = 'Введите число (процент от баланса) для изменения максимального риска на сделку: '
await message.answer(text=text, parse_mode='html', reply_markup=None) await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.cancel)
@router_risk_management_settings.message(update_risk_management_settings.max_risk_deal) @router_risk_management_settings.message(update_risk_management_settings.max_risk_deal)

View File

@@ -1,7 +1,7 @@
import logging.config import logging.config
import asyncio
from aiogram import F, Router from aiogram import F, Router
from aiogram.filters import CommandStart from aiogram.filters import CommandStart, Command
from aiogram.types import Message, CallbackQuery from aiogram.types import Message, CallbackQuery
from aiogram.fsm.context import FSMContext from aiogram.fsm.context import FSMContext
@@ -12,7 +12,9 @@ import app.telegram.functions.condition_settings.settings as func_condition_sett
import app.telegram.functions.additional_settings.settings as func_additional_settings import app.telegram.functions.additional_settings.settings as func_additional_settings
import app.telegram.database.requests as rq import app.telegram.database.requests as rq
import app.telegram.Keyboards.reply_keyboards as reply_markup
from app.services.Bybit.functions.balance import get_balance
from app.services.Bybit.functions.bybit_ws import run_ws_for_user
from logger_helper.logger_helper import LOGGING_CONFIG from logger_helper.logger_helper import LOGGING_CONFIG
@@ -21,25 +23,21 @@ logger = logging.getLogger("handlers")
router = Router() router = Router()
@router.message(Command("start"))
@router.message(CommandStart()) @router.message(CommandStart())
async def start_message(message: Message) -> None: async def start_message(message: Message) -> None:
""" """
Обработчик команды /start. Обработчик команды /start.
Запускает WebSocket для пользователя и инициализирует нового пользователя в БД. Инициализирует нового пользователя в БД.
Args: Args:
message (Message): Входящее сообщение с командой /start. message (Message): Входящее сообщение с командой /start.
""" """
from BybitBot_API import run_ws_for_user
tg_id = message.from_user.id
asyncio.create_task(run_ws_for_user(tg_id, message))
logger.info(f"Получение event loop")
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(Command("profile"))
@router.message(F.text == "👤 Профиль") @router.message(F.text == "👤 Профиль")
async def profile_message(message: Message) -> None: async def profile_message(message: Message) -> None:
""" """
@@ -50,24 +48,17 @@ async def profile_message(message: Message) -> None:
message (Message): Сообщение с текстом кнопки. message (Message): Сообщение с текстом кнопки.
""" """
user = await rq.check_user(message.from_user.id) user = await rq.check_user(message.from_user.id)
tg_id = message.from_user.id
if user: balance = await get_balance(message.from_user.id, message)
if user and balance:
await run_ws_for_user(tg_id, message)
await func.profile_message(message.from_user.username, message) await func.profile_message(message.from_user.username, message)
else:
await rq.save_tg_id_new_user(message.from_user.id)
@router.message(F.text == "Настройки") await func_main_settings.reg_new_user_default_main_settings(message.from_user.id, message)
async def settings_msg(message: Message) -> None: await func_rmanagement_settings.reg_new_user_default_risk_management_settings(message.from_user.id, message)
""" await func_condition_settings.reg_new_user_default_condition_settings(message.from_user.id)
Обработчик кнопки 'Настройки'. await func_additional_settings.reg_new_user_default_additional_settings(message.from_user.id, message)
Проверяет пользователя и отображает меню настроек.
Args:
message (Message): Сообщение с текстом кнопки.
"""
user = await rq.check_user(message.from_user.id)
if user:
await func.settings_message(message)
@router.callback_query(F.data == "clb_start_chatbot_message") @router.callback_query(F.data == "clb_start_chatbot_message")
@@ -80,13 +71,16 @@ async def clb_profile_msg(callback: CallbackQuery) -> None:
Args: Args:
callback (CallbackQuery): Полученный колбэк. callback (CallbackQuery): Полученный колбэк.
""" """
tg_id = callback.from_user.id
message = callback.message
user = await rq.check_user(callback.from_user.id) user = await rq.check_user(callback.from_user.id)
balance = await get_balance(callback.from_user.id, callback.message)
first_name = callback.from_user.first_name or "" first_name = callback.from_user.first_name or ""
last_name = callback.from_user.last_name or "" last_name = callback.from_user.last_name or ""
username = f"{first_name} {last_name}".strip() or callback.from_user.username or "Пользователь" username = f"{first_name} {last_name}".strip() or callback.from_user.username or "Пользователь"
if user: if user and balance:
await run_ws_for_user(tg_id, message)
await func.profile_message(callback.from_user.username, callback.message) await func.profile_message(callback.from_user.username, callback.message)
else: else:
await rq.save_tg_id_new_user(callback.from_user.id) await rq.save_tg_id_new_user(callback.from_user.id)
@@ -94,13 +88,9 @@ async def clb_profile_msg(callback: CallbackQuery) -> None:
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, await func_rmanagement_settings.reg_new_user_default_risk_management_settings(callback.from_user.id,
callback.message) 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)
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)
await callback.message.answer(f'Здравствуйте, {username}!', reply_markup=reply_markup.base_buttons_markup)
await func.profile_message(username, callback.message)
await callback.answer() await callback.answer()
@@ -131,15 +121,14 @@ async def clb_back_to_settings_msg(callback: CallbackQuery) -> None:
@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) -> None: async def clb_change_main_settings_message(callback: CallbackQuery) -> None:
""" """
Открыть меню изменения главных настроек. Открыть меню изменения главных настроек.
Args: Args:
callback (CallbackQuery): полученный колбэк. callback (CallbackQuery): полученный колбэк.
state (FSMContext): текущее состояние FSM.
""" """
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)
await callback.answer() await callback.answer()
@@ -158,35 +147,34 @@ async def clb_change_risk_management_message(callback: CallbackQuery) -> None:
@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) -> None: async def clb_change_condition_message(callback: CallbackQuery) -> None:
""" """
Открыть меню изменения настроек условий. Открыть меню изменения настроек условий.
Args: Args:
callback (CallbackQuery): полученный колбэк. callback (CallbackQuery): полученный колбэк.
state (FSMContext): текущее состояние FSM.
""" """
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)
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) -> None: async def clb_change_additional_message(callback: CallbackQuery) -> None:
""" """
Открыть меню изменения дополнительных настроек. Открыть меню изменения дополнительных настроек.
Args: Args:
callback (CallbackQuery): полученный колбэк. callback (CallbackQuery): полученный колбэк.
state (FSMContext): текущее состояние FSM.
""" """
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)
await callback.answer() await callback.answer()
# Конкретные настройки каталогов # Конкретные настройки каталогов
list_main_settings = ['clb_change_trading_mode', list_main_settings = ['clb_change_trading_mode',
'clb_change_switch_state',
'clb_change_margin_type', 'clb_change_margin_type',
'clb_change_size_leverage', 'clb_change_size_leverage',
'clb_change_starting_quantity', 'clb_change_starting_quantity',
@@ -210,6 +198,8 @@ async def clb_main_settings_msg(callback: CallbackQuery, state: FSMContext) -> N
match callback.data: match callback.data:
case 'clb_change_trading_mode': case 'clb_change_trading_mode':
await func_main_settings.trading_mode_message(callback.message, state) await func_main_settings.trading_mode_message(callback.message, state)
case 'clb_change_switch_state':
await func_main_settings.switch_mode_enabled_message(callback.message, state)
case 'clb_change_margin_type': case 'clb_change_margin_type':
await func_main_settings.margin_type_message(callback.message, state) await func_main_settings.margin_type_message(callback.message, state)
case 'clb_change_size_leverage': case 'clb_change_size_leverage':
@@ -256,7 +246,7 @@ async def clb_risk_management_settings_msg(callback: CallbackQuery, state: FSMCo
logger.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_mode',
'clb_change_timer', 'clb_change_timer',
'clb_change_filter_volatility', 'clb_change_filter_volatility',
'clb_change_external_cues', 'clb_change_external_cues',
@@ -279,8 +269,8 @@ async def clb_condition_settings_msg(callback: CallbackQuery, state: FSMContext)
try: try:
match callback.data: match callback.data:
case 'clb_change_trigger': case 'clb_change_mode':
await func_condition_settings.trigger_message(callback.message, state) await func_condition_settings.trigger_message(callback.from_user.id, callback.message, state)
case 'clb_change_timer': case 'clb_change_timer':
await func_condition_settings.timer_message(callback.from_user.id, 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':

Binary file not shown.

View File

@@ -80,7 +80,17 @@ LOGGING_CONFIG = {
"level": "DEBUG", "level": "DEBUG",
"propagate": False, "propagate": False,
}, },
"conditions_settings": { "condition_settings": {
"handlers": ["console", "timed_rotating_file"],
"level": "DEBUG",
"propagate": False,
},
"main_settings": {
"handlers": ["console", "timed_rotating_file"],
"level": "DEBUG",
"propagate": False,
},
"risk_management_settings": {
"handlers": ["console", "timed_rotating_file"], "handlers": ["console", "timed_rotating_file"],
"level": "DEBUG", "level": "DEBUG",
"propagate": False, "propagate": False,

View File

@@ -6,22 +6,40 @@ aiosignal==1.4.0
aiosqlite==0.21.0 aiosqlite==0.21.0
annotated-types==0.7.0 annotated-types==0.7.0
attrs==25.3.0 attrs==25.3.0
black==25.1.0
certifi==2025.8.3 certifi==2025.8.3
charset-normalizer==3.4.3 charset-normalizer==3.4.3
click==8.2.1
colorama==0.4.6
dotenv==0.9.9 dotenv==0.9.9
flake8==7.3.0
flake8-bugbear==24.12.12
flake8-pie==0.16.0
frozenlist==1.7.0 frozenlist==1.7.0
greenlet==3.2.4 greenlet==3.2.4
idna==3.10 idna==3.10
isort==6.0.1
magic-filter==1.0.12 magic-filter==1.0.12
mando==0.7.1
mccabe==0.7.0
multidict==6.6.4 multidict==6.6.4
mypy_extensions==1.1.0
nest-asyncio==1.6.0 nest-asyncio==1.6.0
packaging==25.0
pathspec==0.12.1
platformdirs==4.4.0
propcache==0.3.2 propcache==0.3.2
pybit==5.11.0 pybit==5.11.0
pycodestyle==2.14.0
pycryptodome==3.23.0 pycryptodome==3.23.0
pydantic==2.11.7 pydantic==2.11.7
pydantic_core==2.33.2 pydantic_core==2.33.2
pyflakes==3.4.0
python-dotenv==1.1.1 python-dotenv==1.1.1
radon==6.0.1
redis==6.4.0
requests==2.32.5 requests==2.32.5
six==1.17.0
SQLAlchemy==2.0.43 SQLAlchemy==2.0.43
typing-inspection==0.4.1 typing-inspection==0.4.1
typing_extensions==4.14.1 typing_extensions==4.14.1