From 14088503eab0412932134a40cb54cbb5dbcf33b5 Mon Sep 17 00:00:00 2001 From: Kirill Strelnikov Date: Thu, 10 Jul 2025 18:16:29 +0700 Subject: [PATCH] add files project --- .env.sample | 1 + .gitignore | 12 + BibytBot_API.py | 31 +++ BibytBot_API.pyproj | 59 +++++ BibytBot_API.sln | 23 ++ app/telegram/Keyboards/inline_keyboards.py | 88 ++++++++ app/telegram/Keyboards/reply_keyboards.py | 19 ++ app/telegram/database/models.py | 120 ++++++++++ app/telegram/database/requests.py | 174 +++++++++++++++ .../functions/additional_settings/settings.py | 41 ++++ .../functions/condition_settings/settings.py | 82 +++++++ app/telegram/functions/functions.py | 34 +++ .../functions/main_settings/settings.py | 211 ++++++++++++++++++ .../risk_management_settings/settings.py | 37 +++ app/telegram/handlers/handlers.py | 195 ++++++++++++++++ app/telegram/logs.py | 8 + config.py | 6 + 17 files changed, 1141 insertions(+) create mode 100644 .env.sample create mode 100644 .gitignore create mode 100644 BibytBot_API.py create mode 100644 BibytBot_API.pyproj create mode 100644 BibytBot_API.sln create mode 100644 app/telegram/Keyboards/inline_keyboards.py create mode 100644 app/telegram/Keyboards/reply_keyboards.py create mode 100644 app/telegram/database/models.py create mode 100644 app/telegram/database/requests.py create mode 100644 app/telegram/functions/additional_settings/settings.py create mode 100644 app/telegram/functions/condition_settings/settings.py create mode 100644 app/telegram/functions/functions.py create mode 100644 app/telegram/functions/main_settings/settings.py create mode 100644 app/telegram/functions/risk_management_settings/settings.py create mode 100644 app/telegram/handlers/handlers.py create mode 100644 app/telegram/logs.py create mode 100644 config.py diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..f5fde1c --- /dev/null +++ b/.env.sample @@ -0,0 +1 @@ +TOKEN_TELEGRAM_BOT= \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a4caf9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +.env +!*.sample + +__pycache__/ +*.pyc + +env/ +venv/ +.venv/ + +requirements.txt + diff --git a/BibytBot_API.py b/BibytBot_API.py new file mode 100644 index 0000000..3cb2ffd --- /dev/null +++ b/BibytBot_API.py @@ -0,0 +1,31 @@ +import asyncio + +from aiogram import Bot, Dispatcher +from aiogram.filters import Command, CommandStart +from aiogram.types import Message + +from app.telegram.database.models import async_main + +from app.telegram.handlers.handlers import router # Для вызова событий +from app.telegram.functions.main_settings.settings import router_main_settings # Для вызова событий + +from config import TOKEN_TG_BOT + +from app.telegram.logs import logger + +bot = Bot(token=TOKEN_TG_BOT) +dp = Dispatcher() + +async def main(): + await async_main() + + dp.include_router(router) + dp.include_router(router_main_settings) + + await dp.start_polling(bot) + +if __name__ == '__main__': + try: + asyncio.run(main()) + except KeyboardInterrupt: + print("Bot is off") diff --git a/BibytBot_API.pyproj b/BibytBot_API.pyproj new file mode 100644 index 0000000..d6c1cc1 --- /dev/null +++ b/BibytBot_API.pyproj @@ -0,0 +1,59 @@ + + + Debug + 2.0 + bc1d7460-d8ca-4977-a249-0f6d6cc2375a + . + BibytBot_API.py + + + . + . + BibytBot_API + BibytBot_API + + + true + false + + + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/BibytBot_API.sln b/BibytBot_API.sln new file mode 100644 index 0000000..e90ff6b --- /dev/null +++ b/BibytBot_API.sln @@ -0,0 +1,23 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35825.156 d17.13 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "BibytBot_API", "BibytBot_API.pyproj", "{BC1D7460-D8CA-4977-A249-0F6D6CC2375A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BC1D7460-D8CA-4977-A249-0F6D6CC2375A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BC1D7460-D8CA-4977-A249-0F6D6CC2375A}.Release|Any CPU.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9AF00E9A-19FB-4146-96C0-B86C8B1E02C0} + EndGlobalSection +EndGlobal diff --git a/app/telegram/Keyboards/inline_keyboards.py b/app/telegram/Keyboards/inline_keyboards.py new file mode 100644 index 0000000..4748f3d --- /dev/null +++ b/app/telegram/Keyboards/inline_keyboards.py @@ -0,0 +1,88 @@ +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup + +start_markup = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Зарегистрироваться", callback_data="callback_registration")], + [InlineKeyboardButton(text="Авторизоваться", callback_data="callback_autorisation")] +]) + +settings_markup = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Настройки", callback_data='clb_settings_message')] +]) + +back_btn_profile = [InlineKeyboardButton(text="Назад", callback_data='callback_autorisation')] + +special_settings_markup = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Основные настройки", callback_data='clb_change_main_settings'), + InlineKeyboardButton(text="Риск-менеджмент", callback_data='clb_change_risk_management_settings')], + + [InlineKeyboardButton(text="Условия запуска", callback_data='clb_change_condition_settings'), + InlineKeyboardButton(text="Дополнительные параметры", callback_data='clb_change_additional_settings')], + + back_btn_profile +]) + +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')]]) # Клавиатура для возврата к списку каталога настроек + +main_settings_markup = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text='Режим торговли', callback_data='clb_change_trading_mode'), + InlineKeyboardButton(text='Тип маржи', callback_data='clb_change_margin_type')], + + [InlineKeyboardButton(text='Размер кредитного плеча', callback_data='clb_change_size_leverage'), + InlineKeyboardButton(text='Начальная ставка', callback_data='clb_change_starting_quantity')], + + [InlineKeyboardButton(text='Коэффициент Мартингейла', callback_data='clb_change_martingale_factor'), + InlineKeyboardButton(text='Максимльное кол-во ставок', callback_data='clb_change_maximum_quantity')], + + back_btn_list_settings +]) + +risk_management_settings_markup = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text='Изм. цены прибыли', callback_data='clb_change_price_profit'), + InlineKeyboardButton(text='Изм. цены убытков', callback_data='clb_change_price_loss')], + + [InlineKeyboardButton(text='Иакс. риск на сделку', callback_data='clb_change_max_risk_deal')], + + back_btn_list_settings +]) + +condition_settings_markup = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text='Триггер', callback_data='clb_change_trigger'), + InlineKeyboardButton(text='Фильтр времени', callback_data='clb_change_filter_time')], + + [InlineKeyboardButton(text='Фильтр волатильности', callback_data='clb_change_filter_volatility'), + InlineKeyboardButton(text='Внешние сигналы', callback_data='clb_change_external_cues')], + + [InlineKeyboardButton(text='Сигналы TradingView', callback_data='clb_change_tradingview_cues'), + InlineKeyboardButton(text='Webhook URL', callback_data='clb_change_webhook')], + + [InlineKeyboardButton(text='AI - аналитика', callback_data='clb_change_ai_analytics')], + + back_btn_list_settings +]) + +additional_settings_markup = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text='Сохранить шаблон', callback_data='clb_change_save_pattern'), + InlineKeyboardButton(text='Автозапуск', callback_data='clb_change_auto_start')], + + [InlineKeyboardButton(text='Уведомления', callback_data='clb_change_notifications')], + + back_btn_list_settings +]) + +trading_mode_markup = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Лонг", callback_data="trade_mode_long"), + InlineKeyboardButton(text="Шорт", callback_data="trade_mode_short")], + + [InlineKeyboardButton(text="Свитч", callback_data="trade_mode_switch"), + InlineKeyboardButton(text="Смарт", callback_data="trade_mode_smart")], + + back_btn_list_settings +]) + +margin_type_markup = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Изолированный", callback_data="margin_type_isolated"), + InlineKeyboardButton(text="Кросс", callback_data="margin_type_cross")], + + back_btn_list_settings +]) \ No newline at end of file diff --git a/app/telegram/Keyboards/reply_keyboards.py b/app/telegram/Keyboards/reply_keyboards.py new file mode 100644 index 0000000..e0fae5c --- /dev/null +++ b/app/telegram/Keyboards/reply_keyboards.py @@ -0,0 +1,19 @@ +from aiogram.types import ReplyKeyboardMarkup, KeyboardButton + +base_buttons_markup = ReplyKeyboardMarkup(keyboard=[ + [KeyboardButton(text="👤 Профиль")], + # [KeyboardButton(text="Настройки")] +], resize_keyboard=True) + +trigger_markup = ReplyKeyboardMarkup(keyboard=[ # ИЗМЕНИТЬ НА INLINE + [KeyboardButton(text='Ручной'), KeyboardButton(text='TradingView')], + [KeyboardButton(text="Автоматический")] +], resize_keyboard=True) + +buttons_yes_no_markup = ReplyKeyboardMarkup(keyboard=[ # ИЗМЕНИТЬ НА INLINE + [KeyboardButton(text='Да'), KeyboardButton(text='Нет')] +], resize_keyboard=True) + +buttons_on_off_markup = ReplyKeyboardMarkup(keyboard=[ # ИЗМЕНИТЬ НА INLINE + [KeyboardButton(text='Включить'), KeyboardButton(text='Выключить')] +], resize_keyboard=True) \ No newline at end of file diff --git a/app/telegram/database/models.py b/app/telegram/database/models.py new file mode 100644 index 0000000..655f61c --- /dev/null +++ b/app/telegram/database/models.py @@ -0,0 +1,120 @@ +import logging +logger = logging.getLogger(__name__) + +from sqlalchemy import BigInteger, Boolean, Integer, String, ForeignKey +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column +from sqlalchemy.ext.asyncio import AsyncAttrs, async_sessionmaker, create_async_engine + +from sqlalchemy import select, insert + +engine = create_async_engine(url='sqlite+aiosqlite:///db.sqlite3') + +async_session = async_sessionmaker(engine) + +class Base(AsyncAttrs, DeclarativeBase): + pass + +class User_Telegram_Id(Base): + __tablename__ = 'user_telegram_id' + + id: Mapped[int] = mapped_column(primary_key=True) + + tg_id = mapped_column(BigInteger) + +class Trading_Mode(Base): + __tablename__ = 'trading_modes' + + id: Mapped[int] = mapped_column(primary_key=True) + + mode = mapped_column(String(10), unique=True) + +class Margin_type(Base): + __tablename__ = 'margin_types' + + id: Mapped[int] = mapped_column(primary_key=True) + + type = mapped_column(String(15), unique=True) + +class Trigger(Base): + __tablename__ = 'triggers' + + id: Mapped[int] = mapped_column(primary_key=True) + + trigger = mapped_column(String(15), unique=True) + +class User_Main_Settings(Base): + __tablename__ = 'user_main_settings' + + id: Mapped[int] = mapped_column(primary_key=True) + + tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id")) + + trading_mode = mapped_column(ForeignKey("trading_modes.mode")) + margin_type = mapped_column(ForeignKey("margin_types.type")) + size_leverage = mapped_column(Integer(), default=1) + starting_quantity = mapped_column(Integer(), default=1) + martingale_factor = mapped_column(Integer(), default=1) + maximal_quantity = mapped_column(Integer(), default=10) + +class User_Risk_Management_Settings(Base): + __tablename__ = 'user_risk_management_settings' + + id: Mapped[int] = mapped_column(primary_key=True) + + tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id")) + + price_profit = mapped_column(Integer(), default=1) + price_loss = mapped_column(Integer(), default=1) + max_risk_deal = mapped_column(Integer(), default=1) + +class User_Condition_Settings(Base): + __tablename__ = 'user_condition_settings' + + id: Mapped[int] = mapped_column(primary_key=True) + + tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id")) + + trigger = mapped_column(ForeignKey("triggers.trigger")) + filter_time = mapped_column(String(25), default='???') + filter_volatility = mapped_column(Boolean, default=False) + external_cues = mapped_column(Boolean, default=False) + tradingview_cues = mapped_column(Boolean, default=False) + webhook = mapped_column(String(40), default='') + ai_analytics = mapped_column(Boolean, default=False) + +class User_Additional_Settings(Base): + __tablename__ = 'user_additional_settings' + + id: Mapped[int] = mapped_column(primary_key=True) + + tg_id = mapped_column(ForeignKey("user_telegram_id.tg_id")) + + pattern_save = mapped_column(Boolean, default=False) + autostart = mapped_column(Boolean, default=False) + notifications = mapped_column(Boolean, default=False) + +async def async_main(): + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + + # Заполнение таблиц + modes = ['Long', 'Short', 'Switch', 'Smart'] + for mode in modes: + result = await conn.execute(select(Trading_Mode).where(Trading_Mode.mode == mode)) + if not result.first(): + logger.info("Заполение таблицы режима торговли") + await conn.execute(Trading_Mode.__table__.insert().values(mode=mode)) + + types = ['Изолированный', 'Кросс'] + for type in types: + result = await conn.execute(select(Margin_type).where(Margin_type.type == type)) + if not result.first(): + logger.info("Заполение таблицы типов маржи") + await conn.execute(Margin_type.__table__.insert().values(type=type)) + + triggers = ['Ручной', 'Автоматический', 'TradingView'] + for trigger in triggers: + result = await conn.execute(select(Trigger).where(Trigger.trigger == trigger)) + if not result.first(): + logger.info("Заполение таблицы триггеров") + await conn.execute(Trigger.__table__.insert().values(trigger=trigger)) \ No newline at end of file diff --git a/app/telegram/database/requests.py b/app/telegram/database/requests.py new file mode 100644 index 0000000..6cd01a0 --- /dev/null +++ b/app/telegram/database/requests.py @@ -0,0 +1,174 @@ +import logging +logger = logging.getLogger(__name__) + +from app.telegram.database.models import async_session +from app.telegram.database.models import User_Telegram_Id as UTi +from app.telegram.database.models import User_Main_Settings as UMS +from app.telegram.database.models import User_Risk_Management_Settings as URMS +from app.telegram.database.models import User_Condition_Settings as UCS +from app.telegram.database.models import User_Additional_Settings as UAS +from app.telegram.database.models import Trading_Mode +from app.telegram.database.models import Margin_type +from app.telegram.database.models import Trigger + +import app.telegram.functions.functions as func # functions + +from sqlalchemy import select, delete, update + +# SET_DB +async def save_tg_id_new_user(tg_id): + async with async_session() as session: + user = await session.scalar(select(UTi).where(UTi.tg_id == tg_id)) + + if not user: + session.add(UTi(tg_id=tg_id)) + + logger.info("Новый пользователь был добавлен в бд") + + await session.commit() + +async def set_new_user_default_main_settings(tg_id, trading_mode, margin_type) -> None: + async with async_session() as session: + settings = await session.scalar(select(UMS).where(UMS.tg_id == tg_id)) + + if not settings: + session.add(UMS( + tg_id=tg_id, + trading_mode=trading_mode, + margin_type=margin_type, + )) + + logger.info("Основные настройки нового пользователя были заполнены") + + await session.commit() + +async def set_new_user_default_risk_management_settings(tg_id) -> None: + async with async_session() as session: + settings = await session.scalar(select(URMS).where(URMS.tg_id == tg_id)) + + if not settings: + session.add(URMS( + tg_id=tg_id + )) + + logger.info("Риск-Менеджмент настройки нового пользователя были заполнены") + + await session.commit() + +async def set_new_user_default_condition_settings(tg_id, trigger) -> None: + async with async_session() as session: + settings = await session.scalar(select(UCS).where(UCS.tg_id == tg_id)) + + if not settings: + session.add(UCS( + tg_id=tg_id, + trigger=trigger + )) + + logger.info("Условные настройки нового пользователя были заполнены") + + await session.commit() + +async def set_new_user_default_additional_settings(tg_id) -> None: + async with async_session() as session: + settings = await session.scalar(select(UAS).where(UAS.tg_id == tg_id)) + + if not settings: + session.add(UAS( + tg_id=tg_id, + )) + + logger.info("Дополнительные настройки нового пользователя были заполнены") + + await session.commit() + +# GET_DB +async def check_user(tg_id): + async with async_session() as session: + user = await session.scalar(select(UTi).where(UTi.tg_id == tg_id)) + return user + +async def get_for_registration_trading_mode(): + async with async_session() as session: + mode = await session.scalar(select(Trading_Mode.mode).where(Trading_Mode.id == 1)) + return mode + +async def get_for_registration_margin_type(): + async with async_session() as session: + type = await session.scalar(select(Margin_type.type).where(Margin_type.id == 1)) + return type + +async def get_for_registration_trigger(): + async with async_session() as session: + trigger = await session.scalar(select(Trigger.trigger).where(Trigger.id == 1)) + return trigger + +async def get_user_main_settings(tg_id): + async with async_session() as session: + user = await session.scalar(select(UMS).where(UMS.tg_id == tg_id)) + + if user: + logger.info("Получение основных настроек пользователя") + + 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)) + + data = { + 'trading_mode': trading_mode, + 'margin_type': margin_mode, + 'size_leverage': size_leverage, + 'starting_quantity': starting_quantity, + 'martingale_factor': martingale_factor, + 'maximal_quantity': maximal_quantity + } + + return data + +# UPDATE_DB +async def update_trade_mode_user(tg_id, trading_mode) -> None: + async with async_session() as session: + mode = await session.scalar(select(Trading_Mode.mode).where(Trading_Mode.mode == trading_mode)) + + if mode: + logger.info("Изменен трейд мод") + await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(trading_mode = mode)) + + await session.commit() + +async def update_margin_type(tg_id, margin_type) -> None: + async with async_session() as session: + type = await session.scalar(select(Margin_type.type).where(Margin_type.type == margin_type)) + + if type: + logger.info("Изменен тип маржи") + await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(margin_type = type)) + + await session.commit() + +async def update_size_leverange(tg_id, num): + async with async_session() as session: + await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(size_leverage = num)) + + await session.commit() + +async def update_starting_quantity(tg_id, num): + async with async_session() as session: + await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(starting_quantity = num)) + + await session.commit() + +async def update_martingale_factor(tg_id, num): + async with async_session() as session: + await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(martingale_factor = num)) + + await session.commit() + +async def update_maximal_quantity(tg_id, num): + async with async_session() as session: + await session.execute(update(UMS).where(UMS.tg_id == tg_id).values(maximal_quantity = num)) + + await session.commit() \ No newline at end of file diff --git a/app/telegram/functions/additional_settings/settings.py b/app/telegram/functions/additional_settings/settings.py new file mode 100644 index 0000000..c1f5069 --- /dev/null +++ b/app/telegram/functions/additional_settings/settings.py @@ -0,0 +1,41 @@ +import app.telegram.Keyboards.inline_keyboards as inline_markup +import app.telegram.Keyboards.reply_keyboards as reply_markup + +import app.telegram.database.requests as rq + +async def reg_new_user_default_additional_settings(id, message): + tg_id = id + + await rq.set_new_user_default_additional_settings(tg_id) + +async def main_settings_message(message): + text = '''Дополнительные параметры + +Сохранить как шаблон стратегии: да / нет + +Автозапуск после сохранения: да / нет + +Уведомления в Telegram: включено / отключено ''' + + await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.additional_settings_markup) + +async def save_pattern_message(message, state): + text = '''Сохранение шаблона + + Описание... ''' + + await message.answer(text=text, parse_mode='html', reply_markup=reply_markup.buttons_yes_no_markup) + +async def auto_start_message(message, state): + text = '''Автозапуск + + Описание... ''' + + await message.answer(text=text, parse_mode='html', reply_markup=reply_markup.buttons_yes_no_markup) + +async def notifications_message(message, state): + text = '''Уведомления + + Описание... ''' + + await message.answer(text=text, parse_mode='html', reply_markup=reply_markup.buttons_on_off_markup) \ No newline at end of file diff --git a/app/telegram/functions/condition_settings/settings.py b/app/telegram/functions/condition_settings/settings.py new file mode 100644 index 0000000..1f98a02 --- /dev/null +++ b/app/telegram/functions/condition_settings/settings.py @@ -0,0 +1,82 @@ +import app.telegram.Keyboards.inline_keyboards as inline_markup +import app.telegram.Keyboards.reply_keyboards as reply_markup + +import app.telegram.database.requests as rq + +async def reg_new_user_default_condition_settings(id, message): + tg_id = id + + trigger = await rq.get_for_registration_trigger() + + await rq.set_new_user_default_condition_settings(tg_id, trigger) + +async def main_settings_message(message): + text = """ Условия запуска + +Триггер: Ручной запуск / Сигнал TradingView / Полностью автоматический + +Фильтр времени: диапазон по дням недели и времени суток + +Фильтр волатильности / объёма: включить/отключить + +Интеграции и внешние сигналы: + +Использовать сигналы TradingView: да / нет + +Использовать AI-аналитику от ChatGPT: да / нет + +Webhook URL для сигналов (если используется TradingView). + +""" + + await message.answer(text=text, parse_mode='html', reply_markup=inline_markup.condition_settings_markup) + +async def trigger_message(message, state): + text = '''Триггер + + Описание ручного запуска, сигналов, автоматического режима ''' + + await message.answer(text=text, parse_mode='html', reply_markup=reply_markup.trigger_markup) + +async def filter_time_message(message, state): + text = '''Фильтр времени + + ??? + ''' + + await message.answer(text=text) + +async def filter_volatility_message(message, state): + text = '''Фильтр волатильности + + Описание... ''' + + await message.answer(text=text, parse_mode='html', reply_markup=reply_markup.buttons_on_off_markup) + +async def external_cues_message(message, state): + text = '''Внешние сигналы + + Описание... ''' + + await message.answer(text=text, parse_mode='html', reply_markup=None) + +async def trading_cues_message(message, state): + text = '''Использование сигналов + + Описание... ''' + + await message.answer(text=text, parse_mode='html', reply_markup=reply_markup.buttons_yes_no_markup) + +async def webhook_message(message, state): + text = '''Скиньте ссылку на webhook (если есть trading view): ''' + + await message.answer(text=text, parse_mode='html') + +async def ai_analytics_message(message, state): + text = '''ИИ - Аналитика + + Описание... ''' + + await message.answer(text=text, parse_mode='html', reply_markup=reply_markup.buttons_yes_no_markup) + + diff --git a/app/telegram/functions/functions.py b/app/telegram/functions/functions.py new file mode 100644 index 0000000..8b35365 --- /dev/null +++ b/app/telegram/functions/functions.py @@ -0,0 +1,34 @@ +import app.telegram.Keyboards.inline_keyboards as inline_markup +import app.telegram.Keyboards.reply_keyboards as reply_markup + +async def start_message(message): + await message.answer(f""" Привет {message.from_user.username}! 👋 + +Добро пожаловать в бот по трейдингу на Bibyt — вашего надежного помощника для анализа рынка и принятия взвешенных решений. Здесь вы получите: + +📊 Анализ текущих трендов +📈 Инструменты для прогнозирования и оценки рисков +⚡️ Сигналы и рекомендации по сделкам +🔔 Уведомления о важных изменениях и новостях + +Просто отправляйте интересующий вас инструмент или команду, и бот быстро предоставит актуальную информацию и аналитику. + +Начнем торговать умно и эффективно вместе! 🚀 + +""", parse_mode='html', reply_markup=inline_markup.start_markup) + +async def profile_message(username, message): + await message.answer(f""" {username} + +Баланс: +⭐️ 0 + +Описание: +Активный трейдер на платформе Bibyt с индивидуальной стратегией и аналитикой в реальном времени. Постоянно улучшает навыки и следит за рыночными тенденциями для максимальной прибыли. +""", parse_mode='html', reply_markup=inline_markup.settings_markup) + +async def check_profile_message(message): + await message.answer(f'Добро пожаловать {message.from_user.username}!', reply_markup=reply_markup.base_buttons_markup) + +async def settings_message(message): + await message.edit_text("Выберите что настроить", reply_markup=inline_markup.special_settings_markup) \ No newline at end of file diff --git a/app/telegram/functions/main_settings/settings.py b/app/telegram/functions/main_settings/settings.py new file mode 100644 index 0000000..13c1a5d --- /dev/null +++ b/app/telegram/functions/main_settings/settings.py @@ -0,0 +1,211 @@ +from aiogram import Router + +import app.telegram.Keyboards.inline_keyboards as inline_markup +import app.telegram.Keyboards.reply_keyboards as reply_markup + +import app.telegram.database.requests as rq +from aiogram.types import Message, CallbackQuery + +# FSM - Механизм состояния +from aiogram.fsm.state import State, StatesGroup + +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): + tg_id = id + + trading_mode = await rq.get_for_registration_trading_mode() + margin_type = await rq.get_for_registration_margin_type() + + await rq.set_new_user_default_main_settings(tg_id, trading_mode, margin_type) + + +async def main_settings_message(id, message, state): + data = await rq.get_user_main_settings(id) + + await message.answer(f"""Основные настройки +Режим торговли: {data['trading_mode']} + +Тип маржи: {data['margin_type']} + +Размер кредитного плеча: х{data['size_leverage']} + +Начальная ставка: {data['starting_quantity']} + +Коэффициент мартингейла: {data['martingale_factor']} + +Максимальное количесиво ставок в серии: {data['maximal_quantity']} +""", parse_mode='html', reply_markup=inline_markup.main_settings_markup) + +async def trading_mode_message(message, state): + await state.set_state(update_main_settings.trading_mode) + + await message.edit_text("""Режим торговли + +Лонг — стратегия, ориентированная на покупку актива с целью заработать на повышении его стоимости. + +Шорт — метод продажи активов, взятых в кредит, чтобы получить прибыль от снижения цены. + +Смарт — автоматизированный режим, который подбирает оптимальную стратегию в зависимости от текущих рыночных условий. + +Свитч — динамическое переключение между торговыми режимами для максимизации эффективности. + +Выберите ниже для изменений: +""", parse_mode='html', reply_markup=inline_markup.trading_mode_markup) + +@router_main_settings.callback_query(update_main_settings.trading_mode) +async def state_trading_mode(callback: CallbackQuery, state): + await callback.answer() + + id = callback.from_user.id + print(f"sdljfngdjklfg ## {callback.data}") + + try: + match callback.data: + case 'trade_mode_long': + await rq.update_trade_mode_user(id, 'Long') + await main_settings_message(id, callback.message, state) + + await state.clear() + case 'trade_mode_short': + await rq.update_trade_mode_user(id, 'Short') + await main_settings_message(id, callback.message, state) + + await state.clear() + case 'trade_mode_switch': + await rq.update_trade_mode_user(id, 'Switch') + await main_settings_message(id, callback.message, state) + + await state.clear() + case 'trade_mode_smart': + await rq.update_trade_mode_user(id, 'Smart') + await main_settings_message(id, callback.message, state) + + await state.clear() + except Exception as e: + print(f"error: {e}") + +async def size_leverage_message (message, state): + await state.set_state(update_main_settings.size_leverage) + + await message.edit_text("Введите размер кредитного плеча (от 1 до 100): ", parse_mode='html', reply_markup=inline_markup.back_btn_list_settings_markup) + +@router_main_settings.message(update_main_settings.size_leverage) +async def state_size_leverage(message: Message, state): + await state.update_data(size_leverage = message.text) + + data = await state.get_data() + + if data['size_leverage'].isdigit() and int(data['size_leverage']) <= 100: + await rq.update_size_leverange(message.from_user.id, data['size_leverage']) + await main_settings_message(message.from_user.id, message, state) + + await state.clear() + else: + await main_settings_message(message.from_user.id, message, state) + +async def martingale_factor_message(message, state): + await state.set_state(update_main_settings.martingale_factor) + + await message.edit_text("Введите коэффициент Мартингейла:", parse_mode='html', reply_markup=inline_markup.back_btn_list_settings_markup) + +@router_main_settings.message(update_main_settings.martingale_factor) +async def state_martingale_factor(message: Message, state): + await state.update_data(martingale_factor = message.text) + + data = await state.get_data() + + if data['martingale_factor'].isdigit() and int(data['martingale_factor']) <= 100: + await rq.update_martingale_factor(message.from_user.id, data['martingale_factor']) + await main_settings_message(message.from_user.id, message, state) + + await state.clear() + else: + await main_settings_message(message.from_user.id, message, state) + +async def margin_type_message(message, state): + await state.set_state(update_main_settings.margin_type) + + await message.edit_text("""Тип маржи + +Изолированная маржа +Этот тип маржи позволяет ограничить риск конкретной позиции. +При использовании изолированной маржи вы выделяете определённую сумму средств только для одной позиции. +Если позиция начинает приносить убытки, ваши потери ограничиваются этой суммой, +и остальные средства на счёте не затрагиваются. + +Кросс-маржа +Кросс-маржа объединяет весь маржинальный баланс на счёте и использует все доступные средства для поддержания открытых позиций. +В случае убытков средства с других позиций или баланса автоматически покрывают дефицит, +снижая риск ликвидации, но увеличивая общий риск потери капитала. + +Выберите ниже для изменений: +""", parse_mode='html', reply_markup=inline_markup.margin_type_markup) + +@router_main_settings.callback_query(update_main_settings.margin_type) +async def state_margin_type(callback: CallbackQuery, state): + await callback.answer() + + id = callback.from_user.id + print(f"sdljfngdjklfg ## {callback.data}") + + try: + match callback.data: + case 'margin_type_isolated': + await rq.update_margin_type(id, 'Изолированный') + await main_settings_message(id, callback.message, state) + + await state.clear() + case 'margin_type_cross': + await rq.update_margin_type(id, 'Кросс') + await main_settings_message(id, callback.message, state) + + await state.clear() + except Exception as e: + print(f"error: {e}") + +async def starting_quantity_message (message, state): + await state.set_state(update_main_settings.starting_quantity) + + await message.edit_text("Введите началаьную ставку:", parse_mode='html', reply_markup=inline_markup.back_btn_list_settings_markup) + +@router_main_settings.message(update_main_settings.starting_quantity) +async def state_starting_quantity(message: Message, state): + await state.update_data(starting_quantity = message.text) + + data = await state.get_data() + + if data['starting_quantity'].isdigit() and int(data['starting_quantity']) <= 100: + await rq.update_starting_quantity(message.from_user.id, data['starting_quantity']) + await main_settings_message(message.from_user.id, message, state) + + await state.clear() + else: + await main_settings_message(message.from_user.id, message, state) + +async def maximum_quantity_message(message, state): + await state.set_state(update_main_settings.maximal_quantity) + + await message.edit_text("Введите максимальное количество ставок:", parse_mode='html', reply_markup=inline_markup.back_btn_list_settings_markup) + +@router_main_settings.message(update_main_settings.maximal_quantity) +async def state_maximal_quantity(message: Message, state): + await state.update_data(maximal_quantity = message.text) + + data = await state.get_data() + + if data['maximal_quantity'].isdigit() and int(data['maximal_quantity']) <= 100: + await rq.update_maximal_quantity(message.from_user.id, data['maximal_quantity']) + await main_settings_message(message.from_user.id, message, state) + + await state.clear() + else: + await main_settings_message(message.from_user.id, message, state) \ No newline at end of file diff --git a/app/telegram/functions/risk_management_settings/settings.py b/app/telegram/functions/risk_management_settings/settings.py new file mode 100644 index 0000000..541672f --- /dev/null +++ b/app/telegram/functions/risk_management_settings/settings.py @@ -0,0 +1,37 @@ +import app.telegram.Keyboards.inline_keyboards as inline_markup +import app.telegram.Keyboards.reply_keyboards as reply_markup + +import app.telegram.database.requests as rq + +async def reg_new_user_default_risk_management_settings(id, message): + tg_id = id + + await rq.set_new_user_default_risk_management_settings(tg_id) + + +async def main_settings_message(message): + text = f"""Риск менеджмент, + + Процент изменения цены для фиксации прибыли: 0 + + Процент изменения цены для фиксации убытков: 0 + + Максимальный риск на сделку (в % от баланса): 0 + """ + + await message.edit_text(text=text, parse_mode='html', reply_markup=inline_markup.risk_management_settings_markup) + +async def price_profit_message(message, state): + text = 'Введите число изменения цены для фиксации прибыли: ' + + await message.answer(text=text, parse_mode='html', reply_markup=None) + +async def price_loss_message(message, state): + text = 'Введите число изменения цены для фиксации убытков: ' + + await message.answer(text=text, parse_mode='html', reply_markup=None) + +async def max_risk_deal_message(message, state): + text = 'Введите число (процент от баланса) для изменения максимального риска на сделку: ' + + await message.answer(text=text, parse_mode='html', reply_markup=None) \ No newline at end of file diff --git a/app/telegram/handlers/handlers.py b/app/telegram/handlers/handlers.py new file mode 100644 index 0000000..a5b6533 --- /dev/null +++ b/app/telegram/handlers/handlers.py @@ -0,0 +1,195 @@ +import logging + +from aiogram import F, Router +from aiogram.filters import CommandStart, Command +from aiogram.types import Message, CallbackQuery +from aiogram.fsm.context import FSMContext + +import app.telegram.functions.functions as func # functions +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.Keyboards.inline_keyboards as inline_markup +import app.telegram.Keyboards.reply_keyboards as reply_markup + +router = Router() + +@router.message(CommandStart()) +async def start_message(message: Message): + await func.start_message(message) + +@router.message(F.text == "👤 Профиль") +async def profile_message(message: Message): + user = await rq.check_user(message.from_user.id) + + if user: + await func.profile_message(message.from_user.username, message) + +@router.message(F.text == "Настройки") +async def settings_msg(message: Message): + user = await rq.check_user(message.from_user.id) + + if user: + await func.settings_message(message) + +@router.callback_query(F.data == "callback_registration") +async def clb_func_reg (callback: CallbackQuery): + await rq.save_tg_id_new_user(callback.from_user.id) + print(callback.from_user.id) + + await func_main_settings.reg_new_user_default_main_settings(callback.from_user.id, callback.message) + await func_rmanagement_settings.reg_new_user_default_risk_management_settings(callback.from_user.id, callback.message) + await func_condition_settings.reg_new_user_default_condition_settings(callback.from_user.id, callback.message) + await func_additional_settings.reg_new_user_default_additional_settings(callback.from_user.id, callback.message) + + await callback.message.answer(f'Регистрация прошла успешно, здравствуйте {callback.from_user.username}!', reply_markup=reply_markup.base_buttons_markup) + + await callback.answer() + +@router.callback_query(F.data == "callback_autorisation") +async def clb_func_reg (callback: CallbackQuery): + user = await rq.check_user(callback.from_user.id) + + if user: + await func.profile_message(callback.from_user.username, callback.message) + + # Настройки торговли +@router.callback_query(F.data == "clb_settings_message") +async def clb_settings_msg (callback: CallbackQuery): + await func.settings_message(callback.message) + + await callback.answer() + +@router.callback_query(F.data == "clb_back_to_special_settings_message") +async def clb_back_to_settings_msg(callback: CallbackQuery): + await func.settings_message(callback.message) + + await callback.answer() + +@router.callback_query(F.data == "clb_change_main_settings") +async def clb_change_main_settings_message(callback: CallbackQuery, state: FSMContext): + await func_main_settings.main_settings_message(callback.from_user.id, callback.message, state) + + await callback.answer() + +@router.callback_query(F.data == "clb_change_risk_management_settings") +async def clb_change_risk_management_message(callback: CallbackQuery, state: FSMContext): + await func_rmanagement_settings.main_settings_message(callback.from_user.id, callback.message, state) + + await callback.answer() + +@router.callback_query(F.data == "clb_change_condition_settings") +async def clb_change_condition_message(callback: CallbackQuery, state: FSMContext): + await func_condition_settings.main_settings_message(callback.from_user.id, callback.message, state) + + await callback.answer() + +@router.callback_query(F.data == "clb_change_additional_settings") +async def clb_change_additional_message(callback: CallbackQuery, state: FSMContext): + await func_additional_settings.main_settings_message(callback.from_user.id, callback.message, state) + + await callback.answer() + + # Конкретные настройки каталогов +list_main_settings = ['clb_change_trading_mode', + 'clb_change_margin_type', + 'clb_change_size_leverage', + 'clb_change_starting_quantity', + 'clb_change_martingale_factor', + 'clb_change_maximum_quantity' +] +@router.callback_query(F.data.in_(list_main_settings)) +async def clb_main_settings_msg(callback: CallbackQuery, state: FSMContext): + await callback.answer() + + try: + match callback.data: + case 'clb_change_trading_mode': + await func_main_settings.trading_mode_message(callback.message, state) + case 'clb_change_margin_type': + await func_main_settings.margin_type_message(callback.message, state) + case 'clb_change_size_leverage': + await func_main_settings.size_leverage_message(callback.message, state) + case 'clb_change_starting_quantity': + await func_main_settings.starting_quantity_message(callback.message, state) + case 'clb_change_martingale_factor': + await func_main_settings.martingale_factor_message(callback.message, state) + case 'clb_change_maximum_quantity': + await func_main_settings.maximum_quantity_message(callback.message, state) + except Exception as e: + logging.error(f"Error callback in main_settings match-case: {e}") + + +list_risk_management_settings = ['clb_change_price_profit', + 'clb_change_price_loss', + 'clb_change_max_risk_deal', +] +@router.callback_query(F.data.in_(list_risk_management_settings)) +async def clb_risk_management_settings_msg(callback: CallbackQuery, state: FSMContext): + await callback.answer() + + try: + match callback.data: + case 'clb_change_price_profit': + await func_rmanagement_settings.price_profit_message(callback.message, state) + case 'clb_change_price_loss': + await func_rmanagement_settings.price_loss_message(callback.message, state) + case 'clb_change_max_risk_deal': + await func_rmanagement_settings.max_risk_deal_message(callback.message, state) + except Exception as e: + logging.error(f"Error callback in risk_management match-case: {e}") + + +list_condition_settings = ['clb_change_trigger', + 'clb_change_filter_time', + 'clb_change_filter_volatility', + 'clb_change_external_cues', + 'clb_change_tradingview_cues', + 'clb_change_webhook', + 'clb_change_ai_analytics' +] +@router.callback_query(F.data.in_(list_condition_settings)) +async def clb_condition_settings_msg(callback: CallbackQuery, state: FSMContext): + await callback.answer() + + try: + match callback.data: + case 'clb_change_trigger': + await func_condition_settings.trigger_message(callback.message, state) + case 'clb_change_filter_time': + await func_condition_settings.filter_time_message(callback.message, state) + case 'clb_change_filter_volatility': + await func_condition_settings.filter_volatility_message(callback.message, state) + case 'clb_change_external_cues': + await func_condition_settings.external_cues_message(callback.message, state) + case 'clb_change_tradingview_cues': + await func_condition_settings.trading_cues_message(callback.message, state) + case 'clb_change_webhook': + await func_condition_settings.webhook_message(callback.message, state) + case 'clb_change_ai_analytics': + await func_condition_settings.ai_analytics_message(callback.message, state) + except Exception as e: + logging.error(f"Error callback in main_settings match-case: {e}") + + +list_additional_settings = ['clb_change_save_pattern', + 'clb_change_auto_start', + 'clb_change_notifications', +] +@router.callback_query(F.data.in_(list_additional_settings)) +async def clb_additional_settings_msg(callback: CallbackQuery, state: FSMContext): + await callback.answer() + + try: + match callback.data: + case 'clb_change_save_pattern': + await func_additional_settings.save_pattern_message(callback.message, state) + case 'clb_change_auto_start': + await func_additional_settings.auto_start_message(callback.message, state) + case 'clb_change_notifications': + await func_additional_settings.notifications_message(callback.message, state) + except Exception as e: + logging.error(f"Error callback in additional_settings match-case: {e}") \ No newline at end of file diff --git a/app/telegram/logs.py b/app/telegram/logs.py new file mode 100644 index 0000000..eaf7fa3 --- /dev/null +++ b/app/telegram/logs.py @@ -0,0 +1,8 @@ +import logging + +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) + +logger = logging.getLogger(__name__) \ No newline at end of file diff --git a/config.py b/config.py new file mode 100644 index 0000000..230e0c2 --- /dev/null +++ b/config.py @@ -0,0 +1,6 @@ +from dotenv import load_dotenv +import os + +load_dotenv('.env') + +TOKEN_TG_BOT = os.getenv('TOKEN_TELEGRAM_BOT') \ No newline at end of file -- 2.34.1