diff --git a/app/src/core/constants.py b/app/src/core/constants.py
index dadea16..c023bec 100644
--- a/app/src/core/constants.py
+++ b/app/src/core/constants.py
@@ -1,2 +1,4 @@
+# app/src/core/constants.py
+
APP_NAME = "Dzentra Bot"
-APP_VERSION = "2.0.0"
+APP_VERSION = "2.0.0"
\ No newline at end of file
diff --git a/app/src/core/system_status.py b/app/src/core/system_status.py
index 82ac7ef..0fa6bed 100644
--- a/app/src/core/system_status.py
+++ b/app/src/core/system_status.py
@@ -183,8 +183,8 @@ def build_system_text(*, include_updated_at: bool = False) -> str:
text = (
"🖥️ Система\n"
- f"🔸 {snapshot.mode_label}\n"
- f"⏱️ {snapshot.timezone_name}\n\n"
+ f"🔸 {snapshot.mode_label}\n\n"
+ # f"⏱️ {snapshot.timezone_name}\n\n"
f"{components_block}"
)
diff --git a/app/src/telegram/handlers/auto.py b/app/src/telegram/handlers/auto.py
index 0708641..0d1e5d7 100644
--- a/app/src/telegram/handlers/auto.py
+++ b/app/src/telegram/handlers/auto.py
@@ -1,8 +1,11 @@
# app/src/telegram/handlers/auto.py
+from __future__ import annotations
+
from aiogram import F, Router
from aiogram.fsm.context import FSMContext
-from aiogram.types import Message
+from aiogram.types import CallbackQuery, InlineKeyboardMarkup, Message
+from aiogram.utils.keyboard import InlineKeyboardBuilder
from src.telegram.menus import AUTO_TEXT
@@ -10,8 +13,37 @@ from src.telegram.menus import AUTO_TEXT
router = Router(name="auto")
-@router.message(F.text == "🤖 Авто")
+def _auto_keyboard() -> InlineKeyboardMarkup:
+ builder = InlineKeyboardBuilder()
+ builder.button(text="🛠️ Настройки", callback_data="settings:auto")
+ builder.adjust(1)
+ return builder.as_markup()
+
+
+async def _render_auto_screen(
+ target_message: Message,
+ *,
+ edit_mode: bool,
+) -> None:
+ if edit_mode:
+ await target_message.edit_text(AUTO_TEXT, reply_markup=_auto_keyboard())
+ else:
+ await target_message.answer(AUTO_TEXT, reply_markup=_auto_keyboard())
+
+
+@router.message(F.text.in_({"🤖 Автоторговля", "🤖 Авто"}))
async def open_auto(message: Message, state: FSMContext) -> None:
- # Глобальный экран: всегда выходим из текущего FSM-сценария.
await state.clear()
- await message.answer(AUTO_TEXT)
\ No newline at end of file
+ await _render_auto_screen(message, edit_mode=False)
+
+
+@router.callback_query(F.data == "auto:home")
+async def open_auto_from_callback(callback: CallbackQuery, state: FSMContext) -> None:
+ await state.clear()
+
+ if callback.message is None:
+ await callback.answer("Сообщение не найдено", show_alert=True)
+ return
+
+ await _render_auto_screen(callback.message, edit_mode=True)
+ await callback.answer()
\ No newline at end of file
diff --git a/app/src/telegram/handlers/system.py b/app/src/telegram/handlers/system.py
index 45b3d93..0a5a037 100644
--- a/app/src/telegram/handlers/system.py
+++ b/app/src/telegram/handlers/system.py
@@ -8,6 +8,8 @@ from aiogram.types import CallbackQuery, InlineKeyboardMarkup, Message
from aiogram.utils.keyboard import InlineKeyboardBuilder
from src.core.system_status import build_system_text, get_system_snapshot, has_system_alerts
+from src.core.config import load_settings
+from src.core.constants import APP_NAME, APP_VERSION
from src.trading.journal.service import JournalService
@@ -161,6 +163,77 @@ async def open_system_management(callback: CallbackQuery) -> None:
await callback.answer()
+@router.callback_query(F.data == "settings:auto")
+async def open_auto_settings(callback: CallbackQuery) -> None:
+ if callback.message is None:
+ await callback.answer("Сообщение не найдено", show_alert=True)
+ return
+
+ text = (
+ "🤖 Автоторговля\n\n"
+ "СИСТЕМА · Настройки\n\n"
+ "Статус: —\n"
+ "Стратегия: —\n"
+ "Риск: —\n\n"
+ "В разработке."
+ )
+
+ builder = InlineKeyboardBuilder()
+ builder.button(text="⬅️ Назад", callback_data="system:management")
+ builder.button(text="🤖 Автоторговля", callback_data="auto:home")
+ builder.adjust(2)
+
+ await callback.message.edit_text(text, reply_markup=builder.as_markup())
+ await callback.answer()
+
+
+@router.callback_query(F.data == "settings:trade")
+async def open_trade_settings(callback: CallbackQuery) -> None:
+ if callback.message is None:
+ await callback.answer("Сообщение не найдено", show_alert=True)
+ return
+
+ text = (
+ "📊 Торговля\n\n"
+ "СИСТЕМА · Настройки\n\n"
+ "Инструмент: —\n"
+ "Тип ордера по умолчанию: —\n"
+ "Пресеты количества: —\n\n"
+ "В разработке."
+ )
+
+ builder = InlineKeyboardBuilder()
+ builder.button(text="⬅️ Назад", callback_data="system:management")
+ builder.button(text="📊 Торговля", callback_data="trade:home")
+ builder.adjust(2)
+
+ await callback.message.edit_text(text, reply_markup=builder.as_markup())
+ await callback.answer()
+
+
+@router.callback_query(F.data == "settings:general")
+async def open_general_settings(callback: CallbackQuery) -> None:
+ if callback.message is None:
+ await callback.answer("Сообщение не найдено", show_alert=True)
+ return
+
+ text = (
+ "🌍 Общие\n\n"
+ "СИСТЕМА · Настройки\n\n"
+ "Режим аккаунта: —\n"
+ "Часовой пояс: —\n"
+ "Язык интерфейса: ru\n\n"
+ "В разработке."
+ )
+
+ builder = InlineKeyboardBuilder()
+ builder.button(text="⬅️ Назад", callback_data="system:management")
+ builder.adjust(1)
+
+ await callback.message.edit_text(text, reply_markup=builder.as_markup())
+ await callback.answer()
+
+
@router.callback_query(F.data == "settings:journal")
async def open_journal_settings(callback: CallbackQuery) -> None:
if callback.message is None:
@@ -184,7 +257,7 @@ async def open_journal_settings(callback: CallbackQuery) -> None:
builder.button(text="🗄 Архив", callback_data="settings:journal_archive")
builder.button(text="📦 Лимит", callback_data="settings:journal_limit")
builder.button(text="⏳ Хранение", callback_data="settings:journal_retention")
- builder.button(text="⬅️ Назад", callback_data="system:control")
+ builder.button(text="⬅️ Назад", callback_data="system:management")
builder.button(text="📒 Журнал", callback_data="journal:1")
builder.adjust(2, 2, 2)
@@ -195,6 +268,88 @@ async def open_journal_settings(callback: CallbackQuery) -> None:
await callback.answer()
+@router.callback_query(F.data == "settings:journal_archive")
+async def open_journal_archive_settings(callback: CallbackQuery) -> None:
+ if callback.message is None:
+ await callback.answer("Сообщение не найдено", show_alert=True)
+ return
+
+ text = (
+ "🗄 Архив\n\n"
+ "СИСТЕМА · Настройки · Журнал\n\n"
+ "Автоматический архив журнала: —\n"
+ "Формат архива: —\n"
+ "Периодичность: —\n\n"
+ "В разработке."
+ )
+
+ builder = InlineKeyboardBuilder()
+ builder.button(text="⬅️ Назад", callback_data="settings:journal")
+ builder.button(text="📒 Журнал", callback_data="journal:1")
+ builder.adjust(2)
+
+ await callback.message.edit_text(text, reply_markup=builder.as_markup())
+ await callback.answer()
+
+
+@router.callback_query(F.data == "settings:journal_limit")
+async def open_journal_limit_settings(callback: CallbackQuery) -> None:
+ if callback.message is None:
+ await callback.answer("Сообщение не найдено", show_alert=True)
+ return
+
+ text = (
+ "📦 Лимит\n\n"
+ "СИСТЕМА · Настройки · Журнал\n\n"
+ "Текущий лимит: —\n\n"
+ "Доступные варианты:"
+ )
+
+ builder = InlineKeyboardBuilder()
+ builder.button(text="1 000", callback_data="settings:journal_limit_stub")
+ builder.button(text="5 000", callback_data="settings:journal_limit_stub")
+ builder.button(text="10 000", callback_data="settings:journal_limit_stub")
+ builder.button(text="∞", callback_data="settings:journal_limit_stub")
+ builder.button(text="⬅️ Назад", callback_data="settings:journal")
+ builder.adjust(2, 2, 1)
+
+ await callback.message.edit_text(text, reply_markup=builder.as_markup())
+ await callback.answer()
+
+
+@router.callback_query(F.data == "settings:journal_retention")
+async def open_journal_retention_settings(callback: CallbackQuery) -> None:
+ if callback.message is None:
+ await callback.answer("Сообщение не найдено", show_alert=True)
+ return
+
+ text = (
+ "⏳ Хранение\n\n"
+ "СИСТЕМА · Настройки · Журнал\n\n"
+ "Текущий срок хранения: —\n\n"
+ "Доступные варианты:"
+ )
+
+ builder = InlineKeyboardBuilder()
+ builder.button(text="7 дней", callback_data="settings:journal_retention_stub")
+ builder.button(text="30 дней", callback_data="settings:journal_retention_stub")
+ builder.button(text="90 дней", callback_data="settings:journal_retention_stub")
+ builder.button(text="∞", callback_data="settings:journal_retention_stub")
+ builder.button(text="⬅️ Назад", callback_data="settings:journal")
+ builder.adjust(2, 2, 1)
+
+ await callback.message.edit_text(text, reply_markup=builder.as_markup())
+ await callback.answer()
+
+
+@router.callback_query(F.data.in_({
+ "settings:journal_limit_stub",
+ "settings:journal_retention_stub",
+}))
+async def journal_settings_stub(callback: CallbackQuery) -> None:
+ await callback.answer("Настройка скоро появится", show_alert=True)
+
+
@router.callback_query(F.data == "system:back")
async def back_to_system(callback: CallbackQuery) -> None:
if callback.message is None:
@@ -216,4 +371,39 @@ async def back_to_system(callback: CallbackQuery) -> None:
@router.callback_query(F.data == "system:about")
async def open_system_about(callback: CallbackQuery) -> None:
- await callback.answer("О продукте скоро появится")
\ No newline at end of file
+ if callback.message is None:
+ await callback.answer("Сообщение не найдено", show_alert=True)
+ return
+
+ settings = load_settings()
+ journal = JournalService()
+
+ journal.log_ui_info(
+ event_type="system_about_opened",
+ message="Открыта информация о продукте.",
+ screen="system",
+ action="about",
+ user_id=callback.from_user.id if callback.from_user else None,
+ chat_id=callback.message.chat.id if callback.message.chat else None,
+ )
+
+ text = (
+ "ℹ️ Информация\n\n"
+ "СИСТЕМА\n\n"
+ f"{APP_NAME}\n"
+ f"Версия: {APP_VERSION}\n"
+ f"Режим: {'DEMO' if 'demo' in settings.exchange_base_url.lower() else 'LIVE'}\n"
+ f"Часовой пояс: {settings.tz}\n\n"
+ "Торговый Telegram-бот для контроля рынка, портфеля, журнала событий "
+ "и будущей автоторговли."
+ )
+
+ builder = InlineKeyboardBuilder()
+ builder.button(text="⬅️ Назад", callback_data="system:back")
+ builder.adjust(1)
+
+ await callback.message.edit_text(
+ text,
+ reply_markup=builder.as_markup(),
+ )
+ await callback.answer()
\ No newline at end of file
diff --git a/app/src/telegram/handlers/trade/main.py b/app/src/telegram/handlers/trade/main.py
index c05a897..eacb78c 100644
--- a/app/src/telegram/handlers/trade/main.py
+++ b/app/src/telegram/handlers/trade/main.py
@@ -33,7 +33,7 @@ def _trade_home_keyboard() -> InlineKeyboardMarkup:
builder.button(text="📝 Ордер", callback_data="trade:new_order")
builder.button(text="📂 Ордера", callback_data="trade:orders")
builder.button(text="📜 История", callback_data="trade:history")
- builder.button(text="⚙️ Настройки", callback_data="trade:settings")
+ builder.button(text="🛠️ Настройки", callback_data="settings:trade")
builder.adjust(2, 2)
return builder.as_markup()
diff --git a/docs/decisions/0014-system-navigation-and-journal-settings.md b/docs/decisions/0014-system-navigation-and-journal-settings.md
deleted file mode 100644
index 8041ad9..0000000
--- a/docs/decisions/0014-system-navigation-and-journal-settings.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# 0014 — System Navigation and Journal Settings
-
-## Решение
-Вынести журнал и системные настройки в отдельный раздел “Система” с многоуровневой навигацией.
-
-## Причины
-- разгрузить главное меню
-- разделить operational UI и settings UI
-- упростить масштабирование новых системных экранов
-
-## Последствия
-- появляется единый navigation pattern
-- журнал становится отдельным subsystem
-- проще расширять настройки
\ No newline at end of file
diff --git a/docs/decisions/0014-system-settings-navigation.md b/docs/decisions/0014-system-settings-navigation.md
new file mode 100644
index 0000000..7dbe7fa
--- /dev/null
+++ b/docs/decisions/0014-system-settings-navigation.md
@@ -0,0 +1,17 @@
+# 0014 — System settings as centralized navigation hub
+
+## Решение
+
+Все настройки продукта централизуются через экран Система → Настройки.
+
+## Причины
+
+- единая точка входа
+
+- масштабируемость
+
+- понятная UX-структура
+
+## Последствия
+
+- все разделы получают связку экран ↔ настройки
\ No newline at end of file
diff --git a/docs/roadmap/master-roadmap.md b/docs/roadmap/master-roadmap.md
index 46d12c4..309b727 100644
--- a/docs/roadmap/master-roadmap.md
+++ b/docs/roadmap/master-roadmap.md
@@ -76,7 +76,9 @@
✔ system navigation redesign
### 06.2
-⏳ archive / backup
+✔ system settings navigation
+✔ product information screen
+✔ settings stubs
### 06.3
⏳ retention / limits
diff --git a/docs/stages/stage-06_2-system-settings-navigation-and-info.md b/docs/stages/stage-06_2-system-settings-navigation-and-info.md
new file mode 100644
index 0000000..d04211d
--- /dev/null
+++ b/docs/stages/stage-06_2-system-settings-navigation-and-info.md
@@ -0,0 +1,75 @@
+# Stage 06.2 — System Settings Navigation and Product Information
+
+## Что сделано
+
+### 1. Экран ℹ️ Информация
+Добавлен полноценный экран информации о продукте.
+
+Показывает:
+- название продукта
+- версия
+- режим работы (DEMO / LIVE)
+- timezone
+- описание продукта
+
+---
+
+### 2. Новый navigation pattern
+
+Добавлена двусторонняя навигация:
+
+СИСТЕМА ↔ НАСТРОЙКИ ↔ ЭКРАНЫ
+
+Реализовано для:
+
+- 🤖 Автоторговля
+- 📊 Торговля
+- 📒 Журнал
+
+---
+
+### 3. Настройки Журнала
+
+Добавлены отдельные экраны:
+
+- 🗑 Очистка
+- 🗄 Архив
+- 📦 Лимит
+- ⏳ Хранение
+
+---
+
+### 4. Заглушки настроек
+
+Созданы шаблонные экраны:
+
+#### 🤖 Автоторговля
+- статус
+- стратегия
+- риск
+
+#### 📊 Торговля
+- инструмент
+- тип ордера
+- пресеты количества
+
+#### 🌍 Общие
+- режим
+- timezone
+- язык
+
+---
+
+### 5. Обновление UI
+
+Убран timezone с главного экрана Система.
+
+Экран стал компактнее.
+
+---
+
+## Commit
+
+```bash
+git add .
+git commit -m "Stage 06.2 - system settings navigation and product information screens"
\ No newline at end of file