07.4.3.19.4 — Journal Runtime Standardization & Export Layer

This commit is contained in:
2026-05-10 15:26:49 +03:00
parent 1692cb4d81
commit 8024cd9d9a
13 changed files with 343 additions and 325 deletions

View File

@@ -207,13 +207,8 @@ def _log_risk_updated(action: str) -> None:
try:
JournalService().log_ui_info(
event_type="auto_risk_settings_updated",
message=(
"Risk settings updated: "
f"SL={state.stop_loss_percent}, "
f"TP={state.take_profit_percent}, "
f"ML={state.max_loss_usd}"
),
event_type="risk_settings_updated",
message="Параметры защиты позиции обновлены.",
screen="auto",
action=action,
payload={

View File

@@ -126,15 +126,6 @@ async def open_journal(message: Message, state: FSMContext) -> None:
chat_id=message.chat.id,
)
JournalService().log_ui_info(
event_type="journal_open_requested",
message="Запрошено открытие журнала.",
screen="journal",
action="open",
user_id=_user_id_from_message(message),
chat_id=_chat_id_from_message(message),
)
await _show_journal_page(
message,
page=1,
@@ -157,15 +148,6 @@ async def open_journal_from_monitoring(callback: CallbackQuery, state: FSMContex
keep_message_id=callback.message.message_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,
@@ -195,7 +177,7 @@ async def export_journal_csv(callback: CallbackQuery) -> None:
await callback.message.answer_document(document=document)
service.log_ui_info(
event_type="journal_export_csv_success",
event_type="journal_exported",
message="Журнал экспортирован в CSV.",
screen="journal",
action="export_csv",
@@ -207,12 +189,13 @@ async def export_journal_csv(callback: CallbackQuery) -> None:
await callback.answer("CSV экспортирован")
except Exception as exc:
service.log_ui_error(
event_type="journal_export_csv_error",
event_type="journal_export_error",
message="Не удалось экспортировать журнал в CSV.",
screen="journal",
action="export_csv",
user_id=_user_id_from_callback(callback),
chat_id=_chat_id_from_callback(callback),
payload={"format": "csv"},
raw_error=str(exc),
)
await callback.answer("Не удалось экспортировать CSV", show_alert=True)
@@ -233,8 +216,8 @@ async def export_journal_xlsx(callback: CallbackQuery) -> None:
await callback.message.answer_document(document=document)
service.log_ui_info(
event_type="journal_export_xlsx_success",
message="Журнал экспортирован в Excel.",
event_type="journal_exported",
message="Журнал экспортирован в XLSX.",
screen="journal",
action="export_xlsx",
user_id=_user_id_from_callback(callback),
@@ -245,12 +228,13 @@ async def export_journal_xlsx(callback: CallbackQuery) -> None:
await callback.answer("Excel экспортирован")
except Exception as exc:
service.log_ui_error(
event_type="journal_export_xlsx_error",
message="Не удалось экспортировать журнал в Excel.",
event_type="journal_export_error",
message="Не удалось экспортировать журнал в XLSX.",
screen="journal",
action="export_xlsx",
user_id=_user_id_from_callback(callback),
chat_id=_chat_id_from_callback(callback),
payload={"format": "xlsx"},
raw_error=str(exc),
)
await callback.answer("Не удалось экспортировать Excel", show_alert=True)

View File

@@ -21,65 +21,25 @@ LEVEL_ICONS = {
}
EVENT_TITLES = {
"auto_signal_generated": "Сигнал автоторговли",
"auto_signal_summary": "Итог серии сигналов",
"app_start": "Запуск приложения",
"system_open_alert": "Система загружена с предупреждениями",
"system_open_requested": "Открытие системы",
"system_open_success": "Система загружена",
"system_retry": "Система обновлена",
"market_open_requested": "Открытие рынка",
"market_open_success": "Рынок загружен",
"market_open_error": "Ошибка открытия рынка",
"market_retry_error": "Ошибка обновления рынка",
"market_symbol_invalid": "Некорректный инструмент",
"market_price_error": "Ошибка загрузки цены",
"portfolio_open_requested": "Открытие портфеля",
"portfolio_open_success": "Портфель загружен",
"portfolio_open_error": "Ошибка открытия портфеля",
"portfolio_retry_error": "Ошибка обновления портфеля",
"portfolio_empty": "Портфель пуст",
"portfolio_zero_balances": "Нет активов с балансом",
"portfolio_partial_estimate": "Частичная оценка портфеля",
"balance_summary_loaded": "Баланс загружен",
"balance_summary_empty": "Баланс пуст",
"balance_summary_error": "Ошибка загрузки баланса",
"exchange_request_error": "Ошибка запроса к бирже",
"trade_drafts_open": "Открытие списка черновиков",
"trade_drafts_paginate": "Переключение страницы черновиков",
"trade_draft_open_success": "Черновик открыт",
"trade_draft_open_not_found": "Черновик не найден",
"trade_draft_edit_start": "Начато редактирование черновика",
"trade_draft_edit_error": "Ошибка редактирования черновика",
"trade_draft_edit_not_found": "Черновик не найден",
"trade_order_create_start": "Начато создание ордера",
"trade_order_create_start_error": "Ошибка создания ордера",
"trade_order_create_cancelled": "Создание ордера отменено",
"trade_order_side_selected": "Выбрана сторона ордера",
"trade_order_side_select_error": "Ошибка выбора стороны",
"trade_order_type_selected": "Выбран тип ордера",
"trade_order_type_select_error": "Ошибка выбора типа",
"trade_order_quantity_selected": "Выбрано количество",
"trade_order_quantity_select_error": "Ошибка выбора количества",
"trade_order_quantity_manual_open": "Ручной ввод количества",
"trade_order_quantity_manual_success": "Количество введено",
"trade_order_quantity_manual_error": "Ошибка ввода количества",
"trade_order_price_selected": "Выбрана цена",
"trade_order_price_select_error": "Ошибка выбора цены",
"trade_order_price_manual_open": "Ручной ввод цены",
"trade_order_price_manual_success": "Цена введена",
"trade_order_price_manual_error": "Ошибка ввода цены",
"trade_order_confirm_success": "Черновик сохранён",
"trade_order_confirm_error": "Ошибка сохранения",
"trade_order_confirm_validation_error": "Ошибка проверки",
"trade_order_confirm_state_error": "Ошибка состояния",
"signal_summary": "Сводка сигнала",
"signal_ready": "Сигнал готов",
"position_opened": "Позиция открыта",
"position_closed": "Позиция закрыта",
"position_flipped": "Направление позиции изменено",
"position_flip_blocked": "Смена направления позиции заблокирована",
"risk_settings_updated": "Настройки защиты обновлены",
"market_monitor_started": "Мониторинг рынка запущен",
"market_monitor_stopped": "Мониторинг рынка остановлен",
"market_stream_connected": "Поток рынка подключён",
"market_stream_disconnected": "Поток рынка отключён",
"market_symbol_changed": "Инструмент рынка изменён",
"journal_export_error": "Ошибка экспорта журнала",
"journal_exported": "Журнал экспортирован",
"journal_cleared": "Журнал очищен",
"journal_cleared_old": "Журнал очищен по сроку",
"journal_export_csv_success": "Экспорт CSV",
"journal_export_csv_error": "Ошибка экспорта CSV",
"journal_export_xlsx_success": "Экспорт Excel",
"journal_export_xlsx_error": "Ошибка экспорта Excel",
"journal_open_requested": "Открытие журнала",
"notification_sent": "Уведомление отправлено",
"notification_error": "Ошибка уведомления",
"app_started": "Приложение запущено",
"app_bootstrap_failed": "Ошибка запуска приложения",
}
TECH_TO_HUMAN_MESSAGES = {
@@ -221,42 +181,18 @@ def _payload(event: dict) -> dict:
def _render_auto_signal(event: dict, created_time: str) -> list[str]:
payload = _payload(event)
signal = str(payload.get("signal", "HOLD")).upper()
strategy = str(payload.get("strategy", "AUTO")).upper()
symbol = str(payload.get("symbol", ""))
reason = str(payload.get("reason", ""))
confidence = float(payload.get("confidence", 0.0) or 0.0)
repeat_count = int(payload.get("repeat_count", 1) or 1)
is_strong_signal = bool(payload.get("is_strong_signal", False))
is_aggregated = bool(payload.get("is_aggregated", False))
signal_icon = {
"BUY": "🟢",
"SELL": "🔴",
"HOLD": "🟡",
}.get(signal, "")
prefix = ""
if is_strong_signal:
prefix += "📈 "
if is_aggregated:
prefix += "🧠 "
level = str(event.get("level", "INFO")).upper()
icon = LEVEL_ICONS.get(level, "")
title = _event_title(str(event.get("event_type", "")))
message = _humanize_message(str(event.get("message", "")))
lines = [
f"{prefix}{signal_icon} <b>AUTO · {signal}</b>",
f"{created_time} · {strategy} · {symbol}",
f"{icon} <b>{level}</b> · {title}",
f"{created_time}",
]
if is_aggregated:
lines.append(f"{repeat_count} {signal} подряд")
if confidence > 0:
lines.append(f"Уверенность: {confidence:.2f}")
if reason:
lines.append(f"Причина: {reason}")
if message:
lines.append(message)
return lines
@@ -305,7 +241,7 @@ def render(events, page, total_pages):
event_type = str(event.get("event_type", ""))
if event_type in {"auto_signal_generated", "auto_signal_summary"}:
if event_type in {"signal_summary", "signal_ready"}:
lines.extend(_render_auto_signal(event, created_time))
else:
lines.extend(_render_default_event(event, created_time))