diff --git a/app/src/bootstrap/app_factory.py b/app/src/bootstrap/app_factory.py
index facaaaf..b67fa57 100644
--- a/app/src/bootstrap/app_factory.py
+++ b/app/src/bootstrap/app_factory.py
@@ -39,8 +39,8 @@ def create_app() -> tuple[Bot, Dispatcher]:
try:
journal.log_info(
- "app_start",
- "Приложение запущено.",
+ "app_started",
+ "Приложение запущено",
{
"env": settings.app_env,
"exchange_name": settings.exchange_name,
diff --git a/app/src/integrations/exchange/market_data_runner.py b/app/src/integrations/exchange/market_data_runner.py
index b635594..1579dad 100644
--- a/app/src/integrations/exchange/market_data_runner.py
+++ b/app/src/integrations/exchange/market_data_runner.py
@@ -46,13 +46,6 @@ class MarketDataRunner:
existing.screen = screen
existing.action = action
existing.runtime_label = runtime_label
-
- cls._log_info(
- existing,
- "market_runner_context_updated",
- "MarketDataRunner context updated.",
- {"interval_seconds": interval_seconds},
- )
return
context = MarketRuntimeContext(
@@ -69,8 +62,8 @@ class MarketDataRunner:
cls._log_info(
context,
- "market_runner_started",
- "MarketDataRunner started.",
+ "market_monitor_started",
+ "Мониторинг рынка запущен.",
{"interval_seconds": interval_seconds},
)
@@ -93,8 +86,8 @@ class MarketDataRunner:
cls._log_info(
context,
- "market_runner_stopped",
- "MarketDataRunner stopped.",
+ "market_monitor_stopped",
+ "Мониторинг рынка остановлен.",
)
cls._runtimes.pop(runtime_key, None)
@@ -107,11 +100,6 @@ class MarketDataRunner:
symbol = context.symbol_provider()
if not symbol:
- cls._log_warning(
- context,
- "market_runner_no_symbol",
- "MarketDataRunner has no symbol.",
- )
await asyncio.sleep(context.interval_seconds)
continue
@@ -129,8 +117,8 @@ class MarketDataRunner:
cls._log_info(
context,
- "market_runner_symbol_changed",
- "MarketDataRunner symbol changed.",
+ "market_symbol_changed",
+ f"Инструмент автоторговли изменён на {cache_symbol}.",
{
"symbol": symbol,
"cache_symbol": cache_symbol,
@@ -143,10 +131,10 @@ class MarketDataRunner:
except asyncio.CancelledError:
raise
except Exception as exc:
- cls._log_error(
+ cls._log_warning(
context,
- "market_ws_error_fallback",
- "WebSocket market data failed. Falling back to REST.",
+ "market_stream_disconnected",
+ "Поток рыночных данных отключён. Используется резервный REST-режим.",
{
"symbol": symbol,
"cache_symbol": cache_symbol,
@@ -165,17 +153,6 @@ class MarketDataRunner:
cache_symbol = cls._cache_symbol(symbol)
ws_symbol = cls._ws_symbol(symbol)
- cls._log_info(
- context,
- "market_ws_connecting",
- "Connecting market WebSocket.",
- {
- "requested_symbol": symbol,
- "cache_symbol": cache_symbol,
- "ws_symbol": ws_symbol,
- },
- )
-
payload_count = 0
async for payload in ExchangeWebSocketClient().stream_depth(
@@ -185,8 +162,8 @@ class MarketDataRunner:
if payload_count == 0:
cls._log_info(
context,
- "market_ws_connected",
- "Market WebSocket connected and first payload received.",
+ "market_stream_connected",
+ "Поток рыночных данных подключён.",
{
"requested_symbol": symbol,
"cache_symbol": cache_symbol,
@@ -200,33 +177,12 @@ class MarketDataRunner:
current_symbol = context.symbol_provider()
if current_symbol and current_symbol != symbol:
- cls._log_info(
- context,
- "market_ws_symbol_switch",
- "Market WebSocket stopped because symbol changed.",
- {
- "old_symbol": symbol,
- "new_symbol": current_symbol,
- },
- )
break
best_bid = cls._extract_best_price(payload, "bids")
best_ask = cls._extract_best_price(payload, "asks")
if best_bid is None or best_ask is None:
- cls._log_warning(
- context,
- "market_ws_payload_unrecognized",
- "Market WebSocket payload does not contain recognizable bids/asks.",
- {
- "requested_symbol": symbol,
- "cache_symbol": cache_symbol,
- "ws_symbol": ws_symbol,
- "payload_keys": list(payload.keys()),
- "payload_preview": cls._safe_payload_preview(payload),
- },
- )
continue
MarketPriceCache.set_price(
@@ -241,30 +197,16 @@ class MarketDataRunner:
@classmethod
async def _rest_fallback_once(cls, context: MarketRuntimeContext, symbol: str) -> None:
try:
- snapshot = await asyncio.to_thread(
+ await asyncio.to_thread(
ExchangeService().refresh_market_snapshot_cache,
symbol,
runtime_key=context.runtime_key,
)
-
- cls._log_warning(
- context,
- "market_rest_fallback_success",
- "REST fallback market snapshot loaded.",
- {
- "symbol": symbol,
- "snapshot_symbol": snapshot.get("symbol"),
- "source": snapshot.get("source"),
- "last_price": snapshot.get("last_price"),
- "bid_price": snapshot.get("bid_price"),
- "ask_price": snapshot.get("ask_price"),
- },
- )
except Exception as exc:
cls._log_error(
context,
- "market_rest_fallback_error",
- "REST fallback market snapshot failed.",
+ "market_stream_disconnected",
+ "Поток рыночных данных отключён. Резервный REST-режим недоступен.",
{
"symbol": symbol,
"error": str(exc),
diff --git a/app/src/notifications/channels/telegram.py b/app/src/notifications/channels/telegram.py
index cefea86..78370af 100644
--- a/app/src/notifications/channels/telegram.py
+++ b/app/src/notifications/channels/telegram.py
@@ -16,8 +16,8 @@ class TelegramNotificationChannel:
if bot is None or chat_id is None:
JournalService().log_warning(
- "notification_target_missing",
- "Telegram notification target is not registered.",
+ "notification_error",
+ "Не удалось отправить Telegram-уведомление: получатель не настроен.",
{
"title": message.title,
"priority": message.priority,
@@ -36,8 +36,8 @@ class TelegramNotificationChannel:
except TelegramRetryAfter as exc:
JournalService().log_warning(
- "notification_telegram_retry_after",
- "Telegram notification delayed by retry-after.",
+ "notification_error",
+ "Не удалось отправить Telegram-уведомление: Telegram ограничил частоту отправки.",
{
"title": message.title,
"retry_after": exc.retry_after,
@@ -49,8 +49,8 @@ class TelegramNotificationChannel:
except Exception as exc:
JournalService().log_error(
- "notification_telegram_error",
- "Telegram notification failed.",
+ "notification_error",
+ "Не удалось отправить Telegram-уведомление.",
{
"title": message.title,
"error": str(exc),
diff --git a/app/src/notifications/service.py b/app/src/notifications/service.py
index 73cf853..d07a6de 100644
--- a/app/src/notifications/service.py
+++ b/app/src/notifications/service.py
@@ -7,9 +7,7 @@ from src.notifications.dedupe import NotificationDedupe
from src.notifications.models import NotificationMessage
from src.notifications.templates.execution import build_execution_notification
from src.notifications.templates.signal import build_signal_notification
-from src.runtime_events.event_types import RuntimeEventType
from src.runtime_events.models import RuntimeEvent
-from src.trading.journal.service import JournalService
class NotificationService:
@@ -17,112 +15,15 @@ class NotificationService:
message = self._build_message(event)
if message is None:
- JournalService().log_info(
- "runtime_event_ignored",
- "Runtime event has no notification template.",
- {
- "event_type": event.event_type.value,
- "source": event.source,
- "title": event.title,
- "priority": event.priority,
- "dedupe_key": event.dedupe_key,
- },
- )
return
if not NotificationDedupe.should_send(message.dedupe_key):
- self._log_suppressed(event, message)
return
- sent = await TelegramNotificationChannel().send(message)
-
- if sent:
- self._log_sent(event, message)
+ await TelegramNotificationChannel().send(message)
def _build_message(self, event: RuntimeEvent) -> NotificationMessage | None:
return (
build_signal_notification(event)
or build_execution_notification(event)
- )
-
- def _log_sent(self, event: RuntimeEvent, message: NotificationMessage) -> None:
- if event.event_type == RuntimeEventType.AUTO_SIGNAL_READY:
- signal = str(event.payload.get("signal") or "—").upper()
-
- JournalService().log_ui_info(
- event_type="auto_strong_signal_alert_sent",
- message=f"Отправлено уведомление о сильном сигнале {signal}.",
- screen="auto",
- action="strong_signal_alert",
- payload={
- **event.payload,
- "runtime_event_type": event.event_type.value,
- "runtime_source": event.source,
- "priority": message.priority.upper(),
- "dedupe_key": message.dedupe_key,
- },
- )
- return
-
- if event.event_type in {
- RuntimeEventType.POSITION_OPENED,
- RuntimeEventType.POSITION_CLOSED,
- RuntimeEventType.POSITION_FLIPPED,
- }:
- JournalService().log_ui_info(
- event_type="auto_execution_alert_sent",
- message="Отправлено Telegram-уведомление по paper execution.",
- screen="auto",
- action="execution_alert",
- payload={
- **event.payload,
- "runtime_event_type": event.event_type.value,
- "runtime_source": event.source,
- "priority": message.priority.upper(),
- "dedupe_key": message.dedupe_key,
- },
- )
- return
-
- JournalService().log_info(
- "notification_sent",
- "Runtime notification sent.",
- {
- "event_type": event.event_type.value,
- "source": event.source,
- "title": event.title,
- "priority": event.priority,
- "dedupe_key": message.dedupe_key,
- },
- )
-
- def _log_suppressed(self, event: RuntimeEvent, message: NotificationMessage) -> None:
- if event.event_type == RuntimeEventType.AUTO_SIGNAL_READY:
- signal = str(event.payload.get("signal") or "—").upper()
-
- JournalService().log_ui_info(
- event_type="auto_strong_signal_alert_suppressed",
- message=f"Повторное уведомление о сильном сигнале {signal} подавлено.",
- screen="auto",
- action="strong_signal_alert",
- payload={
- **event.payload,
- "runtime_event_type": event.event_type.value,
- "runtime_source": event.source,
- "priority": message.priority.upper(),
- "dedupe_key": message.dedupe_key,
- },
- )
- return
-
- JournalService().log_info(
- "notification_suppressed_duplicate",
- "Duplicate notification suppressed.",
- {
- "event_type": event.event_type.value,
- "source": event.source,
- "title": event.title,
- "priority": event.priority,
- "dedupe_key": message.dedupe_key,
- },
)
\ No newline at end of file
diff --git a/app/src/telegram/handlers/auto/risk.py b/app/src/telegram/handlers/auto/risk.py
index a3b4d58..d4722ee 100644
--- a/app/src/telegram/handlers/auto/risk.py
+++ b/app/src/telegram/handlers/auto/risk.py
@@ -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={
diff --git a/app/src/telegram/handlers/journal.py b/app/src/telegram/handlers/journal.py
index 1b4f5a8..639f684 100644
--- a/app/src/telegram/handlers/journal.py
+++ b/app/src/telegram/handlers/journal.py
@@ -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)
diff --git a/app/src/telegram/handlers/journal_ui.py b/app/src/telegram/handlers/journal_ui.py
index d347977..1565113 100644
--- a/app/src/telegram/handlers/journal_ui.py
+++ b/app/src/telegram/handlers/journal_ui.py
@@ -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} AUTO · {signal}",
- f"{created_time} · {strategy} · {symbol}",
+ f"{icon} {level} · {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))
diff --git a/app/src/trading/auto/service.py b/app/src/trading/auto/service.py
index 551bbc1..ce583a7 100644
--- a/app/src/trading/auto/service.py
+++ b/app/src/trading/auto/service.py
@@ -573,9 +573,9 @@ class AutoTradeService:
try:
JournalService().log_ui_info(
- event_type="auto_signal_summary",
+ event_type="signal_summary",
message=(
- f"🟡 HOLD {duration_text} завершён сигналом {next_signal}"
+ f"🟡 HOLD {duration_text} завершён сигналом {next_signal}."
),
screen="auto",
action="signal_summary",
@@ -614,12 +614,8 @@ class AutoTradeService:
try:
JournalService().log_ui_info(
- event_type="auto_signal_ready",
- message=(
- f"Сигнал {normalized_signal} готов: "
- f"{signal_intent}, confidence={confidence:.2f}, "
- f"repeats={state.last_signal_repeat_count}"
- ),
+ event_type="signal_ready",
+ message=f"Сигнал {normalized_signal} готов к исполнению.",
screen="auto",
action="signal_ready",
payload={
diff --git a/app/src/trading/execution/engine.py b/app/src/trading/execution/engine.py
index 1d2c5f2..a24c957 100644
--- a/app/src/trading/execution/engine.py
+++ b/app/src/trading/execution/engine.py
@@ -148,8 +148,8 @@ class ExecutionEngine:
}
JournalService().log_ui_info(
- event_type="paper_position_opened",
- message=f"Paper ENTRY открыта: {side} {state.symbol}",
+ event_type="position_opened",
+ message=f"Позиция {side} открыта: {state.symbol}.",
screen="auto",
action="paper_execution",
payload=payload,
@@ -266,8 +266,8 @@ class ExecutionEngine:
}
JournalService().log_ui_info(
- event_type="paper_position_flipped",
- message=f"Paper FLIP выполнен: {old_side} → {new_side} {state.symbol}",
+ event_type="position_flipped",
+ message=f"Направление позиции изменено: {old_side} → {new_side}.",
screen="auto",
action="paper_execution",
payload=payload,
@@ -337,13 +337,11 @@ class ExecutionEngine:
"price_updated_at": exit_execution.updated_at if exit_execution else None,
}
+ close_reason = forced_reason or "MANUAL"
+
JournalService().log_ui_info(
- event_type="paper_position_closed",
- message=(
- f"Paper EXIT закрыта по риску {forced_reason}: {position.side} {state.symbol}"
- if forced_reason is not None
- else f"Paper EXIT закрыта: {position.side} {state.symbol}"
- ),
+ event_type="position_closed",
+ message=f"Позиция {position.side} закрыта: {close_reason}.",
screen="auto",
action="paper_execution",
payload=payload,
@@ -535,9 +533,9 @@ class ExecutionEngine:
"updated_at": position.updated_at,
}
- JournalService().log_ui_info(
- event_type="paper_flip_blocked",
- message=f"Paper FLIP заблокирован: {reason}",
+ JournalService().log_ui_warning(
+ event_type="position_flip_blocked",
+ message=f"Смена направления позиции заблокирована: {reason}",
screen="auto",
action="paper_execution",
payload=payload,
diff --git a/app/src/trading/journal/service.py b/app/src/trading/journal/service.py
index 8d0be19..aa821ff 100644
--- a/app/src/trading/journal/service.py
+++ b/app/src/trading/journal/service.py
@@ -279,8 +279,8 @@ class JournalService:
deleted_count = self.repository.delete_older_than_days(days)
self.log_ui_warning(
- event_type="journal_cleared_old",
- message=f"Журнал очищен старше {days} дней.",
+ event_type="journal_cleared",
+ message=f"Журнал очищен: удалены записи старше {days} дней.",
screen="journal",
action="clear_old",
payload={
diff --git a/docs/roadmap/master-roadmap.md b/docs/roadmap/master-roadmap.md
index af7f530..152ac4d 100644
--- a/docs/roadmap/master-roadmap.md
+++ b/docs/roadmap/master-roadmap.md
@@ -384,6 +384,18 @@
- добавлено отдельное событие готового сигнала
- подготовлена база для стандартизации журнала в 07.4.3.19.4
+#### 07.4.3.19.4 ✅ Journal Runtime Standardization & Export Layer
+- унифицированы execution event_type
+- удалены legacy paper_* события
+- execution logging переведён в единый human-readable стиль
+- унифицированы market runtime events
+- стандартизирован export logging
+- добавлены account-aware export filename
+- добавлены [DEMO]/[LIVE] runtime prefixes
+- унифицированы risk-control journal events
+- централизован EVENT_TITLES mapping
+- журнал подготовлен к filters/search layer
+
### 07.4.4
⏳ Grid Strategy
diff --git a/docs/roadmap/stage-07-auto-trading-roadmap.md b/docs/roadmap/stage-07-auto-trading-roadmap.md
index 3ceb6d5..a7f72f5 100644
--- a/docs/roadmap/stage-07-auto-trading-roadmap.md
+++ b/docs/roadmap/stage-07-auto-trading-roadmap.md
@@ -360,6 +360,18 @@
- добавлено отдельное событие готового сигнала
- подготовлена база для стандартизации журнала в 07.4.3.19.4
+#### 07.4.3.19.4 ✅ Journal Runtime Standardization & Export Layer
+- унифицированы execution event_type
+- удалены legacy paper_* события
+- execution logging переведён в единый human-readable стиль
+- унифицированы market runtime events
+- стандартизирован export logging
+- добавлены account-aware export filename
+- добавлены [DEMO]/[LIVE] runtime prefixes
+- унифицированы risk-control journal events
+- централизован EVENT_TITLES mapping
+- журнал подготовлен к filters/search layer
+
---
### 07.4.4
diff --git a/docs/stages/stage-07_4_3_19_4-journal_runtime_standardization.md b/docs/stages/stage-07_4_3_19_4-journal_runtime_standardization.md
new file mode 100644
index 0000000..1bffd22
--- /dev/null
+++ b/docs/stages/stage-07_4_3_19_4-journal_runtime_standardization.md
@@ -0,0 +1,242 @@
+# 07.4.3.19.4 — Journal Runtime Standardization & Export Layer
+
+## Статус
+
+Этап завершён.
+
+## Цель этапа
+
+Цель этапа — завершить стандартизацию runtime-журнала, унифицировать execution/runtime/event logging, очистить legacy-style события и подготовить журнал к следующему этапу фильтрации, поиска и аналитики.
+
+После этапа 07.4.3.19.3 журнал уже содержал signal intent layer и noise filtering, но оставались:
+
+- разные стили event naming
+- legacy paper_* event_type
+- смешанные runtime/runtime-ui сообщения
+- несогласованные execution-сообщения
+- разные стили export/runtime notifications
+
+Этап 07.4.3.19.4 завершает переход к единому audit/runtime journal.
+
+---
+
+## Что изменено
+
+### 1. Унифицированы execution event_type
+
+Старые paper-style event_type заменены на единый runtime-style:
+
+Было:
+
+- paper_position_opened
+- paper_position_closed
+- paper_position_flipped
+- paper_flip_blocked
+
+Стало:
+
+- position_opened
+- position_closed
+- position_flipped
+- position_flip_blocked
+
+Это упрощает дальнейшую фильтрацию и экспорт журнала.
+
+---
+
+### 2. Execution-сообщения приведены к единому стилю
+
+Execution runtime-сообщения теперь используют единый human-readable стиль.
+
+Примеры:
+
+- Позиция LONG открыта.
+- Позиция SHORT закрыта.
+- Направление позиции изменено: LONG → SHORT.
+- Смена направления позиции заблокирована.
+
+Убраны:
+
+- Paper ENTRY
+- Paper EXIT
+- FLIP выполнен
+- flip blocked technical text
+
+---
+
+### 3. Унифицированы runtime market events
+
+Market runtime logging переведён в единый monitoring-style.
+
+Добавлены стандартизированные события:
+
+- market_monitor_started
+- market_monitor_stopped
+- market_stream_connected
+- market_stream_disconnected
+- market_symbol_changed
+
+Runtime payload теперь содержит:
+
+- runtime_key
+- runtime_screen
+- runtime_label
+- cache_symbol
+- ws_symbol
+
+---
+
+### 4. Унифицирован журнал экспорта
+
+Экспорт журнала переведён в unified export layer.
+
+Добавлены:
+
+- journal_exported
+- journal_export_error
+
+Экспорт теперь использует единый account-aware filename:
+
+- journal_demo_info_plus_YYYY-MM-DD_HH-MM-SS.csv
+- journal_live_info_plus_YYYY-MM-DD_HH-MM-SS.xlsx
+
+---
+
+### 5. Добавлен account-mode prefix
+
+Все UI/runtime journal-сообщения теперь автоматически получают account-mode prefix:
+
+- [DEMO]
+- [LIVE]
+
+Prefix формируется через JournalService.
+
+Это подготавливает систему к multi-runtime и multi-account support.
+
+---
+
+### 6. Унифицированы risk-control события
+
+Risk settings logging переведён в user-oriented формат.
+
+Добавлены:
+
+- risk_settings_updated
+
+Убраны технические debug-style risk messages.
+
+---
+
+### 7. Унифицированы journal UI titles
+
+journal_ui.py переведён на централизованный EVENT_TITLES mapping.
+
+Теперь journal renderer отображает:
+
+- понятные runtime titles
+- human-readable execution names
+- единый visual style
+
+---
+
+## Что больше не пишется в журнал
+
+Из runtime journal удалены:
+
+- legacy paper_* event_type
+- flip technical spam
+- execution debug text
+- duplicate runtime-notification messages
+- mixed runtime/export wording
+- raw monitoring tool messages
+
+---
+
+## Что остаётся в журнале
+
+После этапа журнал содержит только полезные runtime-аудит события:
+
+- signal summary
+- READY signals
+- execution events
+- blocked flip
+- market runtime events
+- export events
+- notification errors
+- journal/system critical events
+
+---
+
+## Основные изменённые файлы
+
+- app/src/trading/execution/engine.py
+- app/src/trading/auto/service.py
+- app/src/integrations/exchange/market_data_runner.py
+- app/src/telegram/handlers/journal_ui.py
+- app/src/telegram/handlers/journal.py
+- app/src/trading/journal/service.py
+- app/src/telegram/handlers/auto/risk.py
+- app/src/notifications/service.py
+- app/src/notifications/channels/telegram.py
+
+---
+
+## Проверка
+
+После правок необходимо выполнить:
+
+ python -m compileall src
+ python -m src.main
+
+После запуска проверить:
+
+1. В журнале больше нет paper_* event_type.
+2. Все execution events отображаются единообразно.
+3. Flip-blocked отображается как user-readable событие.
+4. Export CSV/XLSX работает.
+5. Journal filename содержит account mode.
+6. Market runtime использует unified titles.
+7. [DEMO]/[LIVE] prefix отображается корректно.
+8. Старые debug-style runtime messages больше не появляются.
+
+---
+
+## Roadmap Update
+
+#### 07.4.3.19.3 ✅ Strategy Noise Filter & Signal Intent Layer
+- убрано журналирование одиночных BUY / SELL без серии
+- HOLD-серии переведены с repeat-count на duration формат
+- добавлен формат 🟡 HOLD 5м 36с завершён сигналом SELL
+- добавлен signal_intent в payload сигналов
+- добавлены intent-типы ENTRY_CANDIDATE, REVERSAL_CANDIDATE, REINFORCE_POSITION, HOLD_MARKET, NOISE
+- добавлена position-aware интерпретация сигналов
+- добавлено отдельное событие готового сигнала
+- подготовлена база для стандартизации журнала в 07.4.3.19.4
+
+#### 07.4.3.19.4 ✅ Journal Runtime Standardization & Export Layer
+- унифицированы execution event_type
+- удалены legacy paper_* события
+- execution logging переведён в единый human-readable стиль
+- унифицированы market runtime events
+- стандартизирован export logging
+- добавлены account-aware export filename
+- добавлены [DEMO]/[LIVE] runtime prefixes
+- унифицированы risk-control journal events
+- централизован EVENT_TITLES mapping
+- журнал подготовлен к filters/search layer
+
+---
+
+## Итог
+
+Этап завершил переход журнала от debug/runtime telemetry к полноценному runtime audit layer.
+
+Система получила:
+
+- единый execution logging
+- account-aware runtime journal
+- стандартизированные runtime events
+- unified export layer
+- human-readable execution events
+- подготовку к следующему этапу journal filters/search/analytics.
+