Stage 07.3.4 - monitoring screen and journal migration
This commit is contained in:
@@ -15,6 +15,7 @@ from src.telegram.handlers.journal_ui import (
|
||||
build_actions_keyboard,
|
||||
render_actions,
|
||||
)
|
||||
from src.telegram.live.runner import ScreenRegistry, StaticScreen
|
||||
from src.trading.journal.service import JournalService
|
||||
from src.trading.auto.runner import AutoTradeRunner
|
||||
|
||||
@@ -60,8 +61,24 @@ async def _show_journal_page(
|
||||
|
||||
if edit_mode:
|
||||
await target_message.edit_text(text, reply_markup=kb)
|
||||
ScreenRegistry.register_screen(
|
||||
StaticScreen(
|
||||
screen="journal",
|
||||
bot=target_message.bot,
|
||||
chat_id=target_message.chat.id,
|
||||
message_id=target_message.message_id,
|
||||
)
|
||||
)
|
||||
else:
|
||||
await target_message.answer(text, reply_markup=kb)
|
||||
sent_message = await target_message.answer(text, reply_markup=kb)
|
||||
ScreenRegistry.register_screen(
|
||||
StaticScreen(
|
||||
screen="journal",
|
||||
bot=sent_message.bot,
|
||||
chat_id=sent_message.chat.id,
|
||||
message_id=sent_message.message_id,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "journal:actions")
|
||||
@@ -82,6 +99,12 @@ async def journal_actions(callback: CallbackQuery) -> None:
|
||||
async def open_journal(message: Message, state: FSMContext) -> None:
|
||||
await state.clear()
|
||||
|
||||
await ScreenRegistry.delete_screen(
|
||||
screen="journal",
|
||||
bot=message.bot,
|
||||
chat_id=message.chat.id,
|
||||
)
|
||||
|
||||
JournalService().log_ui_info(
|
||||
event_type="journal_open_requested",
|
||||
message="Запрошено открытие журнала.",
|
||||
@@ -98,6 +121,37 @@ async def open_journal(message: Message, state: FSMContext) -> None:
|
||||
)
|
||||
|
||||
|
||||
@router.callback_query(F.data == "monitoring:journal")
|
||||
async def open_journal_from_monitoring(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
await state.clear()
|
||||
|
||||
if callback.message is None:
|
||||
await callback.answer("Сообщение не найдено", show_alert=True)
|
||||
return
|
||||
|
||||
await ScreenRegistry.delete_screen(
|
||||
screen="journal",
|
||||
bot=callback.message.bot,
|
||||
chat_id=callback.message.chat.id,
|
||||
)
|
||||
|
||||
JournalService().log_ui_info(
|
||||
event_type="journal_open_requested",
|
||||
message="Запрошено открытие журнала из мониторинга.",
|
||||
screen="journal",
|
||||
action="open_from_monitoring",
|
||||
user_id=_user_id_from_callback(callback),
|
||||
chat_id=_chat_id_from_callback(callback),
|
||||
)
|
||||
|
||||
await _show_journal_page(
|
||||
callback.message,
|
||||
page=1,
|
||||
edit_mode=True,
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data == "journal:noop")
|
||||
async def journal_noop(callback: CallbackQuery) -> None:
|
||||
await callback.answer()
|
||||
|
||||
@@ -103,7 +103,7 @@ def build_keyboard(page: int, total_pages: int) -> InlineKeyboardMarkup:
|
||||
|
||||
kb.button(text="📤 Экспорт", callback_data="journal:actions")
|
||||
kb.button(text="🛠️ Настройки", callback_data="settings:journal")
|
||||
kb.button(text="⬅️ Назад", callback_data="system:back")
|
||||
kb.button(text="📊 К мониторингу", callback_data="monitoring:home")
|
||||
|
||||
nav_count = 1
|
||||
if page > 1:
|
||||
@@ -127,7 +127,7 @@ def build_actions_keyboard() -> InlineKeyboardMarkup:
|
||||
def render_actions() -> str:
|
||||
return (
|
||||
"<b>📤 Экспорт</b>\n\n"
|
||||
"<b>СИСТЕМА · Журнал</b>\n\n"
|
||||
"<b>МОНИТОРИНГ · Журнал</b>\n\n"
|
||||
"Выберите формат:"
|
||||
)
|
||||
|
||||
@@ -193,7 +193,7 @@ def render(events, page, total_pages):
|
||||
lines = [
|
||||
"<b>📒 Журнал</b>",
|
||||
"",
|
||||
"<b>СИСТЕМА</b>",
|
||||
"<b>МОНИТОРИНГ</b>",
|
||||
"",
|
||||
"<b>Последние события:</b>",
|
||||
"",
|
||||
|
||||
@@ -29,7 +29,7 @@ _last_market_directions: dict[str, str] = {}
|
||||
# клавиатура экрана рынка
|
||||
def _market_keyboard() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🏠 К торговле", callback_data="trade:home")
|
||||
builder.button(text="📊 К мониторингу", callback_data="monitoring:home")
|
||||
builder.adjust(1)
|
||||
return builder.as_markup()
|
||||
|
||||
@@ -257,6 +257,55 @@ async def open_market(message: Message, state: FSMContext) -> None:
|
||||
)
|
||||
|
||||
|
||||
# открыть рынок из экрана мониторинга
|
||||
@router.callback_query(F.data == "monitoring:market")
|
||||
async def open_market_from_monitoring(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
await state.clear()
|
||||
|
||||
if callback.message is None:
|
||||
await callback.answer("Сообщение не найдено", show_alert=True)
|
||||
return
|
||||
|
||||
await LiveScreenRunner.delete_screen(
|
||||
screen="market",
|
||||
bot=callback.message.bot,
|
||||
chat_id=callback.message.chat.id,
|
||||
)
|
||||
|
||||
user_id = callback.from_user.id if callback.from_user else None
|
||||
chat_id = callback.message.chat.id if callback.message.chat else None
|
||||
|
||||
try:
|
||||
await _render_market_screen(
|
||||
callback.message,
|
||||
user_id=user_id,
|
||||
chat_id=chat_id,
|
||||
edit_mode=True,
|
||||
action="open_from_monitoring",
|
||||
)
|
||||
await callback.answer()
|
||||
except ExchangeError as exc:
|
||||
JournalService().log_ui_error(
|
||||
event_type="market_open_error",
|
||||
message="Не удалось загрузить экран рынка из мониторинга.",
|
||||
screen="market",
|
||||
action="open_from_monitoring",
|
||||
user_id=user_id,
|
||||
chat_id=chat_id,
|
||||
error_type=classify_exchange_error(exc),
|
||||
raw_error=str(exc),
|
||||
)
|
||||
|
||||
await show_callback_exchange_error(
|
||||
callback,
|
||||
title="<b>📈 Рынок</b>",
|
||||
exc=exc,
|
||||
network_details="Рыночные данные недоступны.\nОбнови экран.",
|
||||
auth_details="Не удалось получить рыночные данные.\nПроверь API ключи.",
|
||||
retry_callback_data="market:retry",
|
||||
)
|
||||
|
||||
|
||||
# обновить рынок вручную
|
||||
@router.callback_query(F.data == "market:retry")
|
||||
async def retry_market(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
|
||||
66
app/src/telegram/handlers/monitoring.py
Normal file
66
app/src/telegram/handlers/monitoring.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# app/src/telegram/handlers/monitoring.py
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from aiogram import F, Router
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.types import CallbackQuery, InlineKeyboardMarkup, Message
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from src.trading.auto.runner import AutoTradeRunner
|
||||
|
||||
|
||||
router = Router(name="monitoring")
|
||||
|
||||
|
||||
# клавиатура экрана мониторинга
|
||||
def _monitoring_keyboard() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="💼 Портфель", callback_data="monitoring:portfolio")
|
||||
builder.button(text="📈 Рынок", callback_data="monitoring:market")
|
||||
builder.button(text="📒 Журнал", callback_data="monitoring:journal")
|
||||
builder.adjust(2, 1)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
# текст экрана мониторинга
|
||||
def _monitoring_text() -> str:
|
||||
return (
|
||||
"<b>📊 Мониторинг</b>\n\n"
|
||||
"Выберите раздел:"
|
||||
)
|
||||
|
||||
|
||||
# открыть мониторинг из главного меню
|
||||
@router.message(F.text == "📊 Мониторинг")
|
||||
async def open_monitoring(message: Message, state: FSMContext) -> None:
|
||||
await state.clear()
|
||||
AutoTradeRunner.set_current_screen("monitoring")
|
||||
|
||||
await message.answer(
|
||||
_monitoring_text(),
|
||||
reply_markup=_monitoring_keyboard(),
|
||||
)
|
||||
|
||||
|
||||
# вернуться на экран мониторинга из callback
|
||||
@router.callback_query(F.data == "monitoring:home")
|
||||
async def open_monitoring_callback(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
await state.clear()
|
||||
AutoTradeRunner.set_current_screen("monitoring")
|
||||
|
||||
if callback.message is None:
|
||||
await callback.answer("Сообщение не найдено", show_alert=True)
|
||||
return
|
||||
|
||||
await callback.message.edit_text(
|
||||
_monitoring_text(),
|
||||
reply_markup=_monitoring_keyboard(),
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
# переход к портфелю из мониторинга
|
||||
|
||||
|
||||
# переход к рынку из мониторинга
|
||||
@@ -68,7 +68,7 @@ def _compact_amount(currency: str, value: float) -> str:
|
||||
# клавиатура портфеля
|
||||
def _portfolio_keyboard() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🏠 К торговле", callback_data="trade:home")
|
||||
builder.button(text="📊 К мониторингу", callback_data="monitoring:home")
|
||||
builder.adjust(1)
|
||||
return builder.as_markup()
|
||||
|
||||
@@ -77,7 +77,7 @@ def _portfolio_keyboard() -> InlineKeyboardMarkup:
|
||||
def _portfolio_warning_keyboard() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🔁 Обновить", callback_data="portfolio:retry")
|
||||
builder.button(text="🏠 К торговле", callback_data="trade:home")
|
||||
builder.button(text="📊 К мониторингу", callback_data="monitoring:home")
|
||||
builder.adjust(1, 1)
|
||||
return builder.as_markup()
|
||||
|
||||
@@ -296,6 +296,55 @@ async def open_portfolio(message: Message, state: FSMContext) -> None:
|
||||
)
|
||||
|
||||
|
||||
# открыть портфель из экрана мониторинга
|
||||
@router.callback_query(F.data == "monitoring:portfolio")
|
||||
async def open_portfolio_from_monitoring(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
await state.clear()
|
||||
|
||||
if callback.message is None:
|
||||
await callback.answer("Сообщение не найдено", show_alert=True)
|
||||
return
|
||||
|
||||
await LiveScreenRunner.delete_screen(
|
||||
screen="portfolio",
|
||||
bot=callback.message.bot,
|
||||
chat_id=callback.message.chat.id,
|
||||
)
|
||||
|
||||
user_id = callback.from_user.id if callback.from_user else None
|
||||
chat_id = callback.message.chat.id if callback.message.chat else None
|
||||
|
||||
try:
|
||||
await _render_portfolio_screen(
|
||||
callback.message,
|
||||
user_id=user_id,
|
||||
chat_id=chat_id,
|
||||
edit_mode=True,
|
||||
action="open_from_monitoring",
|
||||
)
|
||||
await callback.answer()
|
||||
except ExchangeError as exc:
|
||||
JournalService().log_ui_error(
|
||||
event_type="portfolio_open_error",
|
||||
message="Не удалось загрузить портфель из мониторинга.",
|
||||
screen="portfolio",
|
||||
action="open_from_monitoring",
|
||||
user_id=user_id,
|
||||
chat_id=chat_id,
|
||||
error_type=classify_exchange_error(exc),
|
||||
raw_error=str(exc),
|
||||
)
|
||||
|
||||
await show_callback_exchange_error(
|
||||
callback,
|
||||
title="<b>💼 Портфель</b>",
|
||||
exc=exc,
|
||||
network_details="Не загружен баланс аккаунта.\nОбнови экран.",
|
||||
auth_details="Не загружен баланс аккаунта.\nПроверь API ключи.",
|
||||
retry_callback_data="portfolio:retry",
|
||||
)
|
||||
|
||||
|
||||
# обновить портфель вручную
|
||||
@router.callback_query(F.data == "portfolio:retry")
|
||||
async def retry_portfolio(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
|
||||
@@ -20,20 +20,18 @@ router = Router(name="system")
|
||||
|
||||
def _system_keyboard() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="📒 Журнал", callback_data="journal:1")
|
||||
builder.button(text="🛠️ Настройки", callback_data="system:management")
|
||||
builder.button(text="ℹ️ Информация", callback_data="system:about")
|
||||
builder.adjust(2, 1)
|
||||
builder.adjust(2)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def _system_alert_keyboard() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🔁 Обновить", callback_data="system:retry")
|
||||
builder.button(text="📒 Журнал", callback_data="journal:1")
|
||||
builder.button(text="🛠️ Настройки", callback_data="system:management")
|
||||
builder.button(text="ℹ️ Информация", callback_data="system:about")
|
||||
builder.adjust(1, 2, 1)
|
||||
builder.adjust(1, 2)
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
@@ -102,7 +100,7 @@ async def _render_system_screen(
|
||||
await target_message.answer(text, reply_markup=reply_markup)
|
||||
|
||||
|
||||
@router.message(F.text.in_({"🖥️ Система", "⚙️ Система", "⚙ Система"}))
|
||||
@router.message(F.text.in_({"🖥️ Система"}))
|
||||
async def open_system(message: Message, state: FSMContext) -> None:
|
||||
await state.clear()
|
||||
|
||||
@@ -153,7 +151,7 @@ async def open_system_management(callback: CallbackQuery) -> None:
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🤖 Автоторговля", callback_data="settings:auto")
|
||||
builder.button(text="📊 Торговля", callback_data="settings:trade")
|
||||
builder.button(text="💹 Торговля", callback_data="settings:trade")
|
||||
builder.button(text="🌍 Общие", callback_data="settings:general")
|
||||
builder.button(text="📒 Журнал", callback_data="settings:journal")
|
||||
builder.button(text="⬅️ Назад", callback_data="system:back")
|
||||
@@ -322,7 +320,7 @@ async def open_trade_settings(callback: CallbackQuery) -> None:
|
||||
return
|
||||
|
||||
text = (
|
||||
"<b>📊 Торговля</b>\n\n"
|
||||
"<b>💹 Торговля</b>\n\n"
|
||||
"<b>СИСТЕМА</b> · Настройки\n\n"
|
||||
"Инструмент: —\n"
|
||||
"Тип ордера по умолчанию: —\n"
|
||||
@@ -332,7 +330,7 @@ async def open_trade_settings(callback: CallbackQuery) -> None:
|
||||
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="⬅️ Назад", callback_data="system:management")
|
||||
builder.button(text="📊 Торговля", callback_data="trade:home")
|
||||
builder.button(text="💹 Торговля", callback_data="trade:home")
|
||||
builder.adjust(2)
|
||||
|
||||
await callback.message.edit_text(text, reply_markup=builder.as_markup())
|
||||
|
||||
@@ -19,7 +19,7 @@ router = Router(name="trade_main")
|
||||
|
||||
def _trade_screen(title: str) -> str:
|
||||
return (
|
||||
f"<b>📊 Торговля — {title}</b>\n"
|
||||
f"<b>💹 Торговля — {title}</b>\n"
|
||||
f"{mode_line()}"
|
||||
"Выбери раздел"
|
||||
)
|
||||
@@ -41,14 +41,14 @@ def _trade_home_keyboard() -> InlineKeyboardMarkup:
|
||||
|
||||
def _trade_home_button() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🏠 К торговле", callback_data="trade:home")
|
||||
builder.button(text="💹 К торговле", callback_data="trade:home")
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
def _orders_menu_keyboard() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="📂 Черновики", callback_data="trade:orders:drafts")
|
||||
builder.button(text="🏠 К торговле", callback_data="trade:home")
|
||||
builder.button(text="💹 К торговле", callback_data="trade:home")
|
||||
builder.adjust(2)
|
||||
return builder.as_markup()
|
||||
|
||||
@@ -57,7 +57,7 @@ def _history_menu_keyboard() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="✅ Исполненные", callback_data="trade:history:filled")
|
||||
builder.button(text="🚫 Отменённые", callback_data="trade:history:canceled")
|
||||
builder.button(text="🏠 К торговле", callback_data="trade:home")
|
||||
builder.button(text="💹 К торговле", callback_data="trade:home")
|
||||
builder.adjust(2, 1)
|
||||
return builder.as_markup()
|
||||
|
||||
@@ -67,7 +67,7 @@ def _settings_menu_keyboard() -> InlineKeyboardMarkup:
|
||||
builder.button(text="⚙️ Параметры", callback_data="trade:settings:params")
|
||||
builder.button(text="🔁 Режим", callback_data="trade:settings:mode")
|
||||
builder.button(text="ℹ️ Справка", callback_data="trade:settings:help")
|
||||
builder.button(text="🏠 К торговле", callback_data="trade:home")
|
||||
builder.button(text="💹 К торговле", callback_data="trade:home")
|
||||
builder.adjust(2, 2)
|
||||
return builder.as_markup()
|
||||
|
||||
@@ -95,7 +95,7 @@ def _trade_settings_text() -> str:
|
||||
# ENTRY
|
||||
# =========================
|
||||
|
||||
@router.message(F.text.in_({"📊 Торговля", "⚡ Торговля", "Торговля"}))
|
||||
@router.message(F.text.in_({"💹 Торговля"}))
|
||||
async def open_trade(message: Message) -> None:
|
||||
AutoTradeRunner.set_current_screen("trade")
|
||||
|
||||
@@ -180,7 +180,7 @@ async def open_filled_history(callback: CallbackQuery) -> None:
|
||||
await callback.answer()
|
||||
if callback.message is not None:
|
||||
await callback.message.edit_text(
|
||||
"<b>📊 Торговля — История</b>\n\n"
|
||||
"<b>💹 Торговля — История</b>\n\n"
|
||||
"Шаг 1/1: Исполненные\n"
|
||||
"Раздел в разработке.",
|
||||
reply_markup=_trade_home_button(),
|
||||
@@ -192,7 +192,7 @@ async def open_canceled_history(callback: CallbackQuery) -> None:
|
||||
await callback.answer()
|
||||
if callback.message is not None:
|
||||
await callback.message.edit_text(
|
||||
"<b>📊 Торговля — История</b>\n\n"
|
||||
"<b>💹 Торговля — История</b>\n\n"
|
||||
"Шаг 1/1: Отменённые\n"
|
||||
"Раздел в разработке.",
|
||||
reply_markup=_trade_home_button(),
|
||||
@@ -220,7 +220,7 @@ async def open_trade_settings_params(callback: CallbackQuery) -> None:
|
||||
await callback.answer()
|
||||
if callback.message is not None:
|
||||
await callback.message.edit_text(
|
||||
"<b>📊 Торговля — Настройки</b>\n\n"
|
||||
"<b>💹 Торговля — Настройки</b>\n\n"
|
||||
"Шаг 1/1: Параметры ордера\n"
|
||||
"Раздел в разработке.",
|
||||
reply_markup=_trade_home_button(),
|
||||
@@ -232,7 +232,7 @@ async def open_trade_settings_mode(callback: CallbackQuery) -> None:
|
||||
await callback.answer()
|
||||
if callback.message is not None:
|
||||
await callback.message.edit_text(
|
||||
"<b>📊 Торговля — Настройки</b>\n\n"
|
||||
"<b>💹 Торговля — Настройки</b>\n\n"
|
||||
"Шаг 1/1: Режим работы\n"
|
||||
"Текущий режим: <b>demo</b>",
|
||||
reply_markup=_trade_home_button(),
|
||||
@@ -244,7 +244,7 @@ async def open_trade_settings_help(callback: CallbackQuery) -> None:
|
||||
await callback.answer()
|
||||
if callback.message is not None:
|
||||
await callback.message.edit_text(
|
||||
"<b>📊 Торговля — Справка</b>\n\n"
|
||||
"<b>💹 Торговля — Справка</b>\n\n"
|
||||
"Шаг 1/1: Информация\n"
|
||||
"Раздел в разработке.",
|
||||
reply_markup=_trade_home_button(),
|
||||
|
||||
@@ -51,13 +51,10 @@ MAIN_MENU_BUTTONS = {
|
||||
"🏠 Главная",
|
||||
"📈 Рынок",
|
||||
"💼 Портфель",
|
||||
"📊 Торговля",
|
||||
"⚡ Торговля",
|
||||
"Торговля",
|
||||
"💹 Торговля",
|
||||
"🤖 Авто",
|
||||
"📒 Журнал",
|
||||
"⚙️ Система",
|
||||
"⚙ Система",
|
||||
"🖥️ Система",
|
||||
"Меню",
|
||||
}
|
||||
|
||||
@@ -275,7 +272,7 @@ async def cancel_order_builder(message: Message, state: FSMContext) -> None:
|
||||
)
|
||||
|
||||
await message.answer(
|
||||
"<b>📊 Торговля — Новый ордер</b>\n"
|
||||
"<b>💹 Торговля — Новый ордер</b>\n"
|
||||
f"{mode_line()}"
|
||||
"<b>⛔ Создание черновика отменено</b>",
|
||||
reply_markup=_trade_back_home_keyboard(),
|
||||
@@ -298,7 +295,7 @@ async def start_new_order_draft(
|
||||
context = service.get_entry_context(side="BUY", order_type="MARKET")
|
||||
|
||||
text = (
|
||||
"<b>📊 Торговля — Новый ордер</b>\n"
|
||||
"<b>💹 Торговля — Новый ордер</b>\n"
|
||||
f"{mode_line()}"
|
||||
f"{context.symbol}\n\n"
|
||||
"Шаг 1/4. Выбери сторону"
|
||||
@@ -331,7 +328,7 @@ async def start_new_order_draft(
|
||||
)
|
||||
await show_message_exchange_error(
|
||||
message,
|
||||
title="<b>📊 Торговля — Новый ордер</b>",
|
||||
title="<b>💹 Торговля — Новый ордер</b>",
|
||||
exc=exc,
|
||||
retry_callback_data="trade:new_order_retry",
|
||||
)
|
||||
@@ -357,7 +354,7 @@ async def process_order_side_callback(
|
||||
context = service.get_entry_context(side=side, order_type="MARKET")
|
||||
|
||||
text = (
|
||||
"<b>📊 Торговля — Новый ордер</b>\n"
|
||||
"<b>💹 Торговля — Новый ордер</b>\n"
|
||||
f"{mode_line()}"
|
||||
f"{context.symbol}\n\n"
|
||||
f"{path}\n\n"
|
||||
@@ -390,7 +387,7 @@ async def process_order_side_callback(
|
||||
)
|
||||
await show_callback_exchange_error(
|
||||
callback,
|
||||
title="<b>📊 Торговля — Новый ордер</b>",
|
||||
title="<b>💹 Торговля — Новый ордер</b>",
|
||||
exc=exc,
|
||||
retry_callback_data=callback.data,
|
||||
)
|
||||
@@ -1251,7 +1248,7 @@ async def confirm_order(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
|
||||
await show_callback_exchange_error(
|
||||
callback,
|
||||
title="<b>📊 Торговля — Подтверждение черновика</b>",
|
||||
title="<b>💹 Торговля — Подтверждение черновика</b>",
|
||||
exc=exc,
|
||||
retry_callback_data=callback.data,
|
||||
drafts_page=data.get("draft_edit_page"),
|
||||
|
||||
@@ -38,7 +38,7 @@ async def _return_to_draft_detail(
|
||||
|
||||
if not draft:
|
||||
await callback.message.edit_text(
|
||||
"<b>📊 Торговля</b>\n\n"
|
||||
"<b>💹 Торговля</b>\n\n"
|
||||
"Черновик не найден.",
|
||||
reply_markup=_trade_back_home_keyboard(),
|
||||
)
|
||||
@@ -94,7 +94,7 @@ async def go_back_to_side(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
|
||||
await state.set_state(NewOrderDraftStates.waiting_side)
|
||||
text = (
|
||||
"<b>📊 Торговля — Новый ордер</b>\n"
|
||||
"<b>💹 Торговля — Новый ордер</b>\n"
|
||||
f"{mode_line()}"
|
||||
f"{context.symbol}\n\n"
|
||||
"Шаг 1/4. Выбери сторону"
|
||||
@@ -104,7 +104,7 @@ async def go_back_to_side(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
except ExchangeError as exc:
|
||||
await _show_navigation_exchange_error(
|
||||
callback,
|
||||
title="<b>📊 Торговля — Новый ордер</b>",
|
||||
title="<b>💹 Торговля — Новый ордер</b>",
|
||||
exc=exc,
|
||||
)
|
||||
|
||||
@@ -133,7 +133,7 @@ async def go_back_to_type(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
|
||||
await state.set_state(NewOrderDraftStates.waiting_type)
|
||||
text = (
|
||||
"<b>📊 Торговля — Новый ордер</b>\n"
|
||||
"<b>💹 Торговля — Новый ордер</b>\n"
|
||||
f"{mode_line()}"
|
||||
f"{context.symbol}\n\n"
|
||||
f"{path}\n\n"
|
||||
@@ -144,7 +144,7 @@ async def go_back_to_type(callback: CallbackQuery, state: FSMContext) -> None:
|
||||
except ExchangeError as exc:
|
||||
await _show_navigation_exchange_error(
|
||||
callback,
|
||||
title="<b>📊 Торговля — Новый ордер</b>",
|
||||
title="<b>💹 Торговля — Новый ордер</b>",
|
||||
exc=exc,
|
||||
)
|
||||
|
||||
@@ -213,7 +213,7 @@ async def go_back_from_confirm(callback: CallbackQuery, state: FSMContext) -> No
|
||||
if not confirm_draft:
|
||||
await state.clear()
|
||||
await callback.message.edit_text(
|
||||
"<b>📊 Торговля</b>\n\n"
|
||||
"<b>💹 Торговля</b>\n\n"
|
||||
"Не удалось восстановить шаг подтверждения.",
|
||||
reply_markup=_trade_back_home_keyboard(),
|
||||
)
|
||||
|
||||
@@ -159,7 +159,7 @@ def _side_keyboard() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🟢 BUY", callback_data="order_side:BUY")
|
||||
builder.button(text="🔴 SELL", callback_data="order_side:SELL")
|
||||
builder.button(text="🏠 К торговле", callback_data="trade:home")
|
||||
builder.button(text="💹 К торговле", callback_data="trade:home")
|
||||
builder.adjust(2, 1)
|
||||
return builder.as_markup()
|
||||
|
||||
@@ -169,7 +169,7 @@ def _type_keyboard() -> InlineKeyboardMarkup:
|
||||
builder.button(text="⚡ MARKET", callback_data="order_type:MARKET")
|
||||
builder.button(text="🎯 LIMIT", callback_data="order_type:LIMIT")
|
||||
builder.button(text="⬅️ Назад", callback_data="order_back:side")
|
||||
builder.button(text="🏠 К торговле", callback_data="trade:home")
|
||||
builder.button(text="💹 К торговле", callback_data="trade:home")
|
||||
builder.adjust(2, 2)
|
||||
return builder.as_markup()
|
||||
|
||||
@@ -192,7 +192,7 @@ def _quantity_keyboard(
|
||||
if drafts_page is not None:
|
||||
builder.button(text="📚 К черновикам", callback_data=f"drafts:{drafts_page}")
|
||||
else:
|
||||
builder.button(text="🏠 К торговле", callback_data="trade:home")
|
||||
builder.button(text="💹 К торговле", callback_data="trade:home")
|
||||
|
||||
if len(presets) == 0:
|
||||
builder.adjust(1, 2)
|
||||
@@ -215,7 +215,7 @@ def _quantity_manual_keyboard(
|
||||
if drafts_page is not None:
|
||||
builder.button(text="📚 К черновикам", callback_data=f"drafts:{drafts_page}")
|
||||
else:
|
||||
builder.button(text="🏠 К торговле", callback_data="trade:home")
|
||||
builder.button(text="💹 К торговле", callback_data="trade:home")
|
||||
|
||||
builder.adjust(2)
|
||||
return builder.as_markup()
|
||||
@@ -237,7 +237,7 @@ def _price_keyboard(
|
||||
if drafts_page is not None:
|
||||
builder.button(text="📚 К черновикам", callback_data=f"drafts:{drafts_page}")
|
||||
else:
|
||||
builder.button(text="🏠 К торговле", callback_data="trade:home")
|
||||
builder.button(text="💹 К торговле", callback_data="trade:home")
|
||||
|
||||
builder.adjust(2, 2, 2)
|
||||
return builder.as_markup()
|
||||
@@ -252,7 +252,7 @@ def _price_manual_keyboard(
|
||||
if drafts_page is not None:
|
||||
builder.button(text="📚 К черновикам", callback_data=f"drafts:{drafts_page}")
|
||||
else:
|
||||
builder.button(text="🏠 К торговле", callback_data="trade:home")
|
||||
builder.button(text="💹 К торговле", callback_data="trade:home")
|
||||
|
||||
builder.adjust(2)
|
||||
return builder.as_markup()
|
||||
@@ -268,7 +268,7 @@ def _confirm_keyboard(
|
||||
if drafts_page is not None:
|
||||
builder.button(text="📚 К черновикам", callback_data=f"drafts:{drafts_page}")
|
||||
else:
|
||||
builder.button(text="🏠 К торговле", callback_data="trade:home")
|
||||
builder.button(text="💹 К торговле", callback_data="trade:home")
|
||||
|
||||
builder.adjust(1, 2)
|
||||
return builder.as_markup()
|
||||
@@ -276,7 +276,7 @@ def _confirm_keyboard(
|
||||
|
||||
def _trade_back_home_keyboard() -> InlineKeyboardMarkup:
|
||||
builder = InlineKeyboardBuilder()
|
||||
builder.button(text="🏠 К торговле", callback_data="trade:home")
|
||||
builder.button(text="💹 К торговле", callback_data="trade:home")
|
||||
return builder.as_markup()
|
||||
|
||||
|
||||
@@ -304,7 +304,7 @@ def _drafts_pagination_keyboard(page: int, total_pages: int) -> InlineKeyboardMa
|
||||
if page < total_pages:
|
||||
first_row_count += 1
|
||||
|
||||
builder.button(text="🏠 К торговле", callback_data="trade:home")
|
||||
builder.button(text="💹 К торговле", callback_data="trade:home")
|
||||
builder.adjust(first_row_count, 1)
|
||||
|
||||
return builder.as_markup()
|
||||
@@ -334,7 +334,7 @@ def _exchange_error_keyboard(
|
||||
if drafts_page is not None:
|
||||
builder.button(text="📚 К черновикам", callback_data=f"drafts:{drafts_page}")
|
||||
else:
|
||||
builder.button(text="🏠 К торговле", callback_data="trade:home")
|
||||
builder.button(text="💹 К торговле", callback_data="trade:home")
|
||||
|
||||
builder.adjust(2 if back_callback_data else 1)
|
||||
return builder.as_markup()
|
||||
@@ -386,7 +386,7 @@ def _render_draft_summary(
|
||||
success_text = "✅ <b>Черновик изменён</b>" if is_edit_mode else "✅ <b>Черновик создан</b>"
|
||||
|
||||
lines = [
|
||||
"<b>📊 Торговля — Черновик ордера</b>",
|
||||
"<b>💹 Торговля — Черновик ордера</b>",
|
||||
mode_line().rstrip(),
|
||||
"",
|
||||
f"{symbol}",
|
||||
@@ -468,7 +468,7 @@ def _render_confirm(
|
||||
|
||||
def _render_validation_error(errors: list[str]) -> str:
|
||||
lines = [
|
||||
"<b>📊 Торговля — Ошибка валидации</b>",
|
||||
"<b>💹 Торговля — Ошибка валидации</b>",
|
||||
mode_line().rstrip(),
|
||||
"Шаг 4/4. Проверь параметры черновика",
|
||||
"",
|
||||
@@ -603,7 +603,7 @@ def _render_draft_detail(
|
||||
order_type = str(draft["order_type"]).upper()
|
||||
|
||||
lines = [
|
||||
"<b>📊 Торговля — Черновик</b>",
|
||||
"<b>💹 Торговля — Черновик</b>",
|
||||
mode_line().rstrip(),
|
||||
"",
|
||||
f"<b>{draft['symbol']}</b>",
|
||||
@@ -642,8 +642,8 @@ def _format_draft_quantity(value: str) -> str:
|
||||
|
||||
def _screen_title(is_edit_mode: bool) -> str:
|
||||
if is_edit_mode:
|
||||
return "<b>📊 Торговля — Редактирование черновика</b>"
|
||||
return "<b>📊 Торговля — Новый ордер</b>"
|
||||
return "<b>💹 Торговля — Редактирование черновика</b>"
|
||||
return "<b>💹 Торговля — Новый ордер</b>"
|
||||
|
||||
|
||||
# Рендерит экран выбора количества.
|
||||
@@ -908,7 +908,7 @@ async def show_recent_drafts(
|
||||
|
||||
if not drafts:
|
||||
text = (
|
||||
"<b>📊 Торговля — Черновики</b>\n"
|
||||
"<b>💹 Торговля — Черновики</b>\n"
|
||||
f"{mode_line()}"
|
||||
"<b>Список пуст</b>\n\n"
|
||||
"Черновиков пока нет."
|
||||
@@ -920,7 +920,7 @@ async def show_recent_drafts(
|
||||
return
|
||||
|
||||
lines = [
|
||||
"<b>📊 Торговля — Черновики</b>",
|
||||
"<b>💹 Торговля — Черновики</b>",
|
||||
mode_line().rstrip(),
|
||||
"",
|
||||
]
|
||||
|
||||
@@ -8,13 +8,10 @@ def build_main_menu_keyboard() -> ReplyKeyboardMarkup:
|
||||
keyboard=[
|
||||
[
|
||||
KeyboardButton(text="🤖 Автоторговля"),
|
||||
KeyboardButton(text="📊 Торговля"),
|
||||
],
|
||||
[
|
||||
KeyboardButton(text="💼 Портфель"),
|
||||
KeyboardButton(text="📈 Рынок"),
|
||||
KeyboardButton(text="💹 Торговля"),
|
||||
],
|
||||
[
|
||||
KeyboardButton(text="📊 Мониторинг"),
|
||||
KeyboardButton(text="🖥️ Система"),
|
||||
],
|
||||
],
|
||||
|
||||
@@ -7,11 +7,12 @@ from dataclasses import dataclass
|
||||
from typing import Callable
|
||||
|
||||
from aiogram import Bot
|
||||
from aiogram.exceptions import TelegramBadRequest
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class LiveScreen:
|
||||
# имя live-экрана: market / portfolio / journal
|
||||
# имя live-экрана: market / portfolio
|
||||
screen: str
|
||||
|
||||
# Telegram bot instance
|
||||
@@ -33,6 +34,71 @@ class LiveScreen:
|
||||
interval_seconds: int = 5
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class StaticScreen:
|
||||
# имя статичного экрана: journal / monitoring / etc
|
||||
screen: str
|
||||
|
||||
# Telegram bot instance
|
||||
bot: Bot
|
||||
|
||||
# чат, где находится экран
|
||||
chat_id: int
|
||||
|
||||
# сообщение экрана
|
||||
message_id: int
|
||||
|
||||
|
||||
class ScreenRegistry:
|
||||
_screens: dict[str, list[StaticScreen]] = {}
|
||||
|
||||
# зарегистрировать статичный экран
|
||||
@classmethod
|
||||
def register_screen(cls, static_screen: StaticScreen) -> None:
|
||||
screens = cls._screens.setdefault(static_screen.screen, [])
|
||||
|
||||
screens[:] = [
|
||||
item
|
||||
for item in screens
|
||||
if not (
|
||||
item.chat_id == static_screen.chat_id
|
||||
and item.message_id == static_screen.message_id
|
||||
)
|
||||
]
|
||||
|
||||
screens.append(static_screen)
|
||||
|
||||
# удалить старые статичные экраны указанного типа
|
||||
@classmethod
|
||||
async def delete_screen(
|
||||
cls,
|
||||
*,
|
||||
screen: str,
|
||||
bot: Bot,
|
||||
chat_id: int,
|
||||
) -> None:
|
||||
screens = cls._screens.get(screen, [])
|
||||
remaining: list[StaticScreen] = []
|
||||
|
||||
for static_screen in screens:
|
||||
if static_screen.chat_id != chat_id:
|
||||
remaining.append(static_screen)
|
||||
continue
|
||||
|
||||
try:
|
||||
await bot.delete_message(
|
||||
chat_id=static_screen.chat_id,
|
||||
message_id=static_screen.message_id,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if remaining:
|
||||
cls._screens[screen] = remaining
|
||||
else:
|
||||
cls._screens.pop(screen, None)
|
||||
|
||||
|
||||
class LiveScreenRunner:
|
||||
_screens: dict[str, list[LiveScreen]] = {}
|
||||
_tasks: dict[str, asyncio.Task] = {}
|
||||
@@ -63,7 +129,6 @@ class LiveScreenRunner:
|
||||
chat_id: int,
|
||||
) -> None:
|
||||
screens = cls._screens.get(screen, [])
|
||||
|
||||
remaining: list[LiveScreen] = []
|
||||
|
||||
for live_screen in screens:
|
||||
@@ -139,6 +204,12 @@ class LiveScreenRunner:
|
||||
reply_markup=live_screen.render_markup(),
|
||||
)
|
||||
alive_screens.append(live_screen)
|
||||
|
||||
except TelegramBadRequest as exc:
|
||||
if "message is not modified" in str(exc).lower():
|
||||
alive_screens.append(live_screen)
|
||||
continue
|
||||
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ HOME_TEXT = (
|
||||
)
|
||||
|
||||
SYSTEM_TEXT = (
|
||||
"<b>⚙️ Система</b>\n\n"
|
||||
"<b>🖥️ Система</b>\n\n"
|
||||
"Системный экран.\n\n"
|
||||
"<b>Справка</b>\n"
|
||||
"/start — запуск\n"
|
||||
@@ -27,6 +27,6 @@ SYSTEM_TEXT = (
|
||||
|
||||
MARKET_TEXT = "<b>📈 Рынок</b>\n\nРаздел пока в разработке."
|
||||
PORTFOLIO_TEXT = "<b>💼 Портфель</b>\n\nРаздел пока в разработке."
|
||||
TRADE_TEXT = "<b>📊 Торговля</b>\n\nВыберите действие:\n<i>DRAFT режим</i>"
|
||||
TRADE_TEXT = "<b>💹 Торговля</b>\n\nВыберите действие:\n<i>DRAFT режим</i>"
|
||||
AUTO_TEXT = "<b>🤖 Авто</b>\n\nРаздел пока в разработке."
|
||||
JOURNAL_TEXT = "<b>📒 Журнал</b>\n\nРаздел пока в разработке."
|
||||
@@ -1,9 +1,12 @@
|
||||
# app/src/telegram/routers.py
|
||||
|
||||
from aiogram import Dispatcher
|
||||
|
||||
from src.telegram.handlers.auto import router as auto_router
|
||||
from src.telegram.handlers.home import router as home_router
|
||||
from src.telegram.handlers.journal import router as journal_router
|
||||
from src.telegram.handlers.market import router as market_router
|
||||
from src.telegram.handlers.monitoring import router as monitoring_router
|
||||
from src.telegram.handlers.portfolio import router as portfolio_router
|
||||
from src.telegram.handlers.start import router as start_router
|
||||
from src.telegram.handlers.system import router as system_router
|
||||
@@ -14,10 +17,11 @@ from src.telegram.handlers.trade.new_order import router as trade_new_order_rout
|
||||
def setup_routers(dispatcher: Dispatcher) -> None:
|
||||
dispatcher.include_router(start_router)
|
||||
dispatcher.include_router(home_router)
|
||||
dispatcher.include_router(monitoring_router)
|
||||
dispatcher.include_router(market_router)
|
||||
dispatcher.include_router(portfolio_router)
|
||||
dispatcher.include_router(trade_main_router)
|
||||
dispatcher.include_router(trade_new_order_router)
|
||||
dispatcher.include_router(auto_router)
|
||||
dispatcher.include_router(journal_router)
|
||||
dispatcher.include_router(system_router)
|
||||
dispatcher.include_router(system_router)
|
||||
@@ -0,0 +1,100 @@
|
||||
# Stage 07.3.4 — Monitoring Screen and Journal Migration
|
||||
|
||||
## Цель
|
||||
|
||||
Добавить единый экран мониторинга и перенести Журнал из раздела Система.
|
||||
|
||||
---
|
||||
|
||||
## Что реализовано
|
||||
|
||||
### Экран Мониторинг
|
||||
|
||||
Добавлен новый раздел:
|
||||
|
||||
- 📊 Мониторинг
|
||||
|
||||
Экран содержит быстрые переходы:
|
||||
|
||||
- 💼 Портфель
|
||||
- 📈 Рынок
|
||||
- 📒 Журнал
|
||||
|
||||
---
|
||||
|
||||
### Навигация
|
||||
|
||||
Обновлена структура главного меню:
|
||||
|
||||
```
|
||||
🤖 Автоторговля 📊 Торговля
|
||||
📊 Мониторинг 🖥️ Система
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Интеграция live-экранов
|
||||
|
||||
Портфель и Рынок теперь работают как дочерние экраны Мониторинга:
|
||||
|
||||
* кнопка «📊 К мониторингу»
|
||||
* возврат через callback
|
||||
* сохраняется live-refresh
|
||||
|
||||
---
|
||||
|
||||
### Журнал перенесён
|
||||
|
||||
Журнал убран из:
|
||||
|
||||
* экрана Система
|
||||
* клавиатуры Система
|
||||
|
||||
Журнал теперь относится к Monitoring / Observability.
|
||||
|
||||
---
|
||||
|
||||
### LiveScreenRunner improvements
|
||||
|
||||
Улучшена логика live-экранов:
|
||||
|
||||
* поддержка нескольких live-сообщений одного типа;
|
||||
* обновление всех активных экземпляров;
|
||||
* обработка TelegramBadRequest:
|
||||
* message is not modified больше не убивает экран.
|
||||
|
||||
---
|
||||
|
||||
## Архитектурный результат
|
||||
|
||||
Теперь разделы выглядят логично:
|
||||
|
||||
🤖 Автоторговля
|
||||
|
||||
управление стратегией
|
||||
|
||||
📊 Торговля
|
||||
|
||||
ручное создание ордеров
|
||||
|
||||
📊 Мониторинг
|
||||
|
||||
наблюдение за системой торговли:
|
||||
|
||||
* рынок
|
||||
* портфель
|
||||
* журнал
|
||||
|
||||
🖥️ Система
|
||||
|
||||
администрирование и настройки
|
||||
|
||||
---
|
||||
|
||||
## Следующий этап
|
||||
|
||||
Stage 07.3.5 — WebSocket Market Stream
|
||||
|
||||
Цель:
|
||||
|
||||
убрать polling REST для рынка и перейти на realtime stream.
|
||||
Reference in New Issue
Block a user