07.4.4.1.3 — Journal Runtime Cleanup & Event Titles Layer
This commit is contained in:
85
app/src/core/event_titles.py
Normal file
85
app/src/core/event_titles.py
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# app/src/core/event_titles.py
|
||||||
|
|
||||||
|
# app/src/core/event_titles.py
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
EVENT_TITLES = {
|
||||||
|
# Сигналы
|
||||||
|
"signal_summary": "Сигнал",
|
||||||
|
"signal_ready": "Сигнал",
|
||||||
|
|
||||||
|
# Execution
|
||||||
|
"position_opened": "Позиция",
|
||||||
|
"position_closed": "Позиция",
|
||||||
|
"position_flipped": "Позиция",
|
||||||
|
"position_flip_blocked": "Позиция",
|
||||||
|
|
||||||
|
# Настройки
|
||||||
|
"auto_settings_updated": "Автоторговля",
|
||||||
|
"risk_settings_updated": "Защита",
|
||||||
|
|
||||||
|
# Аналитика рынка
|
||||||
|
"market_state_changed": "Рынок",
|
||||||
|
"market_volatility_changed": "Рынок",
|
||||||
|
|
||||||
|
# Мониторинг рынка
|
||||||
|
"market_monitor_started": "Рынок",
|
||||||
|
"market_monitor_stopped": "Рынок",
|
||||||
|
"market_stream_connected": "Рынок",
|
||||||
|
"market_stream_disconnected": "Рынок",
|
||||||
|
"market_symbol_changed": "Рынок",
|
||||||
|
|
||||||
|
# Журнал
|
||||||
|
"journal_exported": "Журнал",
|
||||||
|
"journal_export_error": "Журнал",
|
||||||
|
"journal_cleared": "Журнал",
|
||||||
|
|
||||||
|
# Уведомления
|
||||||
|
"notification_sent": "Уведомление",
|
||||||
|
"notification_error": "Уведомление",
|
||||||
|
|
||||||
|
# Приложение
|
||||||
|
"app_started": "Приложение",
|
||||||
|
"app_bootstrap_failed": "Приложение",
|
||||||
|
|
||||||
|
# Legacy
|
||||||
|
"app_start": "Приложение",
|
||||||
|
|
||||||
|
"journal_open_requested": "Журнал",
|
||||||
|
"journal_export_csv_success": "Журнал",
|
||||||
|
"journal_export_csv_error": "Журнал",
|
||||||
|
"journal_export_xlsx_success": "Журнал",
|
||||||
|
"journal_export_xlsx_error": "Журнал",
|
||||||
|
"journal_cleared_old": "Журнал",
|
||||||
|
|
||||||
|
"system_open_requested": "Система",
|
||||||
|
"system_open_alert": "Система",
|
||||||
|
"system_open_success": "Система",
|
||||||
|
"system_retry": "Система",
|
||||||
|
"system_about_opened": "Система",
|
||||||
|
|
||||||
|
"market_open_requested": "Рынок",
|
||||||
|
"market_open_success": "Рынок",
|
||||||
|
"market_open_error": "Рынок",
|
||||||
|
|
||||||
|
"portfolio_open_requested": "Портфель",
|
||||||
|
"portfolio_open_success": "Портфель",
|
||||||
|
"portfolio_open_error": "Портфель",
|
||||||
|
"portfolio_partial_estimate": "Портфель",
|
||||||
|
|
||||||
|
"exchange_request_error": "Биржа",
|
||||||
|
|
||||||
|
"balance_summary_loaded": "Баланс",
|
||||||
|
"balance_summary_error": "Баланс",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def event_title(event_type: object) -> str:
|
||||||
|
value = str(event_type or "").strip()
|
||||||
|
|
||||||
|
if not value:
|
||||||
|
return "Событие"
|
||||||
|
|
||||||
|
return EVENT_TITLES.get(value, "Событие")
|
||||||
@@ -120,7 +120,7 @@ class MarketDataRunner:
|
|||||||
cls._log_info(
|
cls._log_info(
|
||||||
context,
|
context,
|
||||||
"market_symbol_changed",
|
"market_symbol_changed",
|
||||||
f"Инструмент автоторговли изменён на {cache_symbol}.",
|
f"Инструмент автоторговли изменён: {cache_symbol}.",
|
||||||
{
|
{
|
||||||
"previous_symbol": previous_symbol,
|
"previous_symbol": previous_symbol,
|
||||||
"symbol": symbol,
|
"symbol": symbol,
|
||||||
@@ -128,7 +128,6 @@ class MarketDataRunner:
|
|||||||
"ws_symbol": ws_symbol,
|
"ws_symbol": ws_symbol,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await cls._run_websocket(context, symbol)
|
await cls._run_websocket(context, symbol)
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
@@ -307,9 +306,6 @@ class MarketDataRunner:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _message(cls, context: MarketRuntimeContext, message: str) -> str:
|
def _message(cls, context: MarketRuntimeContext, message: str) -> str:
|
||||||
if context.runtime_label:
|
|
||||||
return f"{context.runtime_label} {message}"
|
|
||||||
|
|
||||||
return message
|
return message
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
@@ -32,6 +32,17 @@ class TelegramNotificationChannel:
|
|||||||
text=message.text,
|
text=message.text,
|
||||||
parse_mode=message.parse_mode,
|
parse_mode=message.parse_mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
JournalService().log_info(
|
||||||
|
"notification_sent",
|
||||||
|
"Telegram-уведомление отправлено.",
|
||||||
|
{
|
||||||
|
"title": message.title,
|
||||||
|
"priority": message.priority,
|
||||||
|
"dedupe_key": message.dedupe_key,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except TelegramRetryAfter as exc:
|
except TelegramRetryAfter as exc:
|
||||||
|
|||||||
@@ -208,7 +208,12 @@ def _log_risk_updated(action: str) -> None:
|
|||||||
try:
|
try:
|
||||||
JournalService().log_ui_info(
|
JournalService().log_ui_info(
|
||||||
event_type="risk_settings_updated",
|
event_type="risk_settings_updated",
|
||||||
message="Параметры защиты позиции обновлены.",
|
message=(
|
||||||
|
"Параметры защиты позиции изменены: "
|
||||||
|
f"SL={_format_percent(state.stop_loss_percent)}, "
|
||||||
|
f"TP={_format_percent(state.take_profit_percent)}, "
|
||||||
|
f"ML={_format_usd(state.max_loss_usd)}."
|
||||||
|
),
|
||||||
screen="auto",
|
screen="auto",
|
||||||
action=action,
|
action=action,
|
||||||
payload={
|
payload={
|
||||||
|
|||||||
@@ -241,16 +241,16 @@ def _market_state_line(state) -> str:
|
|||||||
market_state = getattr(state, "market_state", None)
|
market_state = getattr(state, "market_state", None)
|
||||||
|
|
||||||
labels = {
|
labels = {
|
||||||
"TREND_UP": "📈 Рынок · Рост",
|
"TREND_UP": "📈 Тренд · Восходящий",
|
||||||
"TREND_DOWN": "📉 Рынок · Падение",
|
"TREND_DOWN": "📉 Тренд · Нисходящий",
|
||||||
"RANGE": "🟰 Рынок · Без направления",
|
"RANGE": "🟰 Тренд · Нет выраженного направления",
|
||||||
"HIGH_VOLATILITY": "⚠️ Рынок · Волатильность",
|
"HIGH_VOLATILITY": "⚠️ Рынок · Высокая волатильность",
|
||||||
"LOW_VOLATILITY": "🟰 Рынок · Спокойный",
|
"LOW_VOLATILITY": "🟰 Рынок · Низкая активность",
|
||||||
"UNKNOWN": "⏳ Рынок · Анализ",
|
"UNKNOWN": "⏳ Рынок · Идёт анализ",
|
||||||
None: "⏳ Рынок · Анализ",
|
None: "⏳ Рынок · Идёт анализ",
|
||||||
}
|
}
|
||||||
|
|
||||||
return labels.get(market_state, "⏳ Рынок · Анализ")
|
return labels.get(market_state, "⏳ Рынок · Идёт анализ")
|
||||||
|
|
||||||
|
|
||||||
def _execution_block_lines(state) -> list[str]:
|
def _execution_block_lines(state) -> list[str]:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from aiogram.types import InlineKeyboardMarkup
|
|||||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||||
|
|
||||||
from src.core.config import load_settings
|
from src.core.config import load_settings
|
||||||
|
from src.core.event_titles import event_title
|
||||||
|
|
||||||
|
|
||||||
PAGE_SIZE = 5
|
PAGE_SIZE = 5
|
||||||
@@ -20,28 +21,6 @@ LEVEL_ICONS = {
|
|||||||
"CRITICAL": "🆘",
|
"CRITICAL": "🆘",
|
||||||
}
|
}
|
||||||
|
|
||||||
EVENT_TITLES = {
|
|
||||||
"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": "Журнал очищен",
|
|
||||||
"notification_sent": "Уведомление отправлено",
|
|
||||||
"notification_error": "Ошибка уведомления",
|
|
||||||
"app_started": "Приложение запущено",
|
|
||||||
"app_bootstrap_failed": "Ошибка запуска приложения",
|
|
||||||
}
|
|
||||||
|
|
||||||
TECH_TO_HUMAN_MESSAGES = {
|
TECH_TO_HUMAN_MESSAGES = {
|
||||||
"invalid api key": "Неверный API Key.",
|
"invalid api key": "Неверный API Key.",
|
||||||
"unauthorized": "Нет доступа к аккаунту.",
|
"unauthorized": "Нет доступа к аккаунту.",
|
||||||
@@ -164,7 +143,7 @@ def _time_label(dt: datetime | None, raw_value: str) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def _event_title(event_type: str) -> str:
|
def _event_title(event_type: str) -> str:
|
||||||
return EVENT_TITLES.get(event_type, event_type)
|
return event_title(event_type)
|
||||||
|
|
||||||
|
|
||||||
def _humanize_message(message: str) -> str:
|
def _humanize_message(message: str) -> str:
|
||||||
|
|||||||
@@ -97,15 +97,6 @@ async def _render_system_screen(
|
|||||||
) -> None:
|
) -> None:
|
||||||
journal = JournalService()
|
journal = JournalService()
|
||||||
|
|
||||||
journal.log_ui_info(
|
|
||||||
event_type="system_open_requested",
|
|
||||||
message="Запрошено открытие экрана системы.",
|
|
||||||
screen="system",
|
|
||||||
action=action,
|
|
||||||
user_id=user_id,
|
|
||||||
chat_id=chat_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
snapshot = get_system_snapshot()
|
snapshot = get_system_snapshot()
|
||||||
is_alert = has_system_alerts(snapshot)
|
is_alert = has_system_alerts(snapshot)
|
||||||
|
|
||||||
@@ -363,6 +354,22 @@ def _log_auto_setting_updated(
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _human_symbol(symbol: str | None) -> str:
|
||||||
|
if not symbol:
|
||||||
|
return "—"
|
||||||
|
|
||||||
|
base = symbol.split("_", 1)[0].upper()
|
||||||
|
|
||||||
|
if "/" in base:
|
||||||
|
return base.split("/", 1)[0]
|
||||||
|
|
||||||
|
for suffix in ("USDT", "USD", "EUR", "BTC"):
|
||||||
|
if base.endswith(suffix) and len(base) > len(suffix):
|
||||||
|
return base[: -len(suffix)]
|
||||||
|
|
||||||
|
return base
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith("settings:auto_strategy:"))
|
@router.callback_query(F.data.startswith("settings:auto_strategy:"))
|
||||||
async def set_auto_strategy(callback: CallbackQuery) -> None:
|
async def set_auto_strategy(callback: CallbackQuery) -> None:
|
||||||
strategy = callback.data.split(":", 2)[2].upper()
|
strategy = callback.data.split(":", 2)[2].upper()
|
||||||
@@ -375,7 +382,7 @@ async def set_auto_strategy(callback: CallbackQuery) -> None:
|
|||||||
|
|
||||||
if previous_strategy != strategy:
|
if previous_strategy != strategy:
|
||||||
_log_auto_setting_updated(
|
_log_auto_setting_updated(
|
||||||
message=f"Стратегия автоторговли изменена на {strategy}.",
|
message=f"Стратегия изменена: {strategy}.",
|
||||||
action="set_strategy",
|
action="set_strategy",
|
||||||
payload={
|
payload={
|
||||||
"previous_strategy": previous_strategy,
|
"previous_strategy": previous_strategy,
|
||||||
@@ -423,7 +430,7 @@ async def set_auto_symbol(callback: CallbackQuery) -> None:
|
|||||||
|
|
||||||
if previous_symbol != symbol:
|
if previous_symbol != symbol:
|
||||||
_log_auto_setting_updated(
|
_log_auto_setting_updated(
|
||||||
message=f"Актив автоторговли изменён на {symbol}.",
|
message=f"Актив изменён: {_human_symbol(symbol)}.",
|
||||||
action="set_symbol",
|
action="set_symbol",
|
||||||
payload={
|
payload={
|
||||||
"previous_symbol": previous_symbol,
|
"previous_symbol": previous_symbol,
|
||||||
@@ -470,7 +477,7 @@ async def set_auto_risk(callback: CallbackQuery) -> None:
|
|||||||
|
|
||||||
if previous_risk != risk:
|
if previous_risk != risk:
|
||||||
_log_auto_setting_updated(
|
_log_auto_setting_updated(
|
||||||
message=f"Риск на сделку изменён на {risk:g}%.",
|
message=f"Риск на сделку изменён: {risk:g}%.",
|
||||||
action="set_risk_percent",
|
action="set_risk_percent",
|
||||||
payload={
|
payload={
|
||||||
"previous_risk_percent": previous_risk,
|
"previous_risk_percent": previous_risk,
|
||||||
@@ -520,7 +527,7 @@ async def set_auto_leverage(callback: CallbackQuery) -> None:
|
|||||||
|
|
||||||
if previous_leverage != leverage:
|
if previous_leverage != leverage:
|
||||||
_log_auto_setting_updated(
|
_log_auto_setting_updated(
|
||||||
message=f"Плечо автоторговли изменено на x{leverage:g}.",
|
message=f"Плечо изменено: x{leverage:g}.",
|
||||||
action="set_leverage",
|
action="set_leverage",
|
||||||
payload={
|
payload={
|
||||||
"previous_leverage": previous_leverage,
|
"previous_leverage": previous_leverage,
|
||||||
@@ -572,7 +579,7 @@ async def set_auto_max_reserved(callback: CallbackQuery) -> None:
|
|||||||
value_text = "off" if value is None else f"{value:g}%"
|
value_text = "off" if value is None else f"{value:g}%"
|
||||||
|
|
||||||
_log_auto_setting_updated(
|
_log_auto_setting_updated(
|
||||||
message=f"Лимит на сделку изменён на {value_text}.",
|
message=f"Лимит на сделку изменён: {value_text}.",
|
||||||
action="set_max_reserved_balance_percent",
|
action="set_max_reserved_balance_percent",
|
||||||
payload={
|
payload={
|
||||||
"previous_max_reserved_balance_percent": previous_value,
|
"previous_max_reserved_balance_percent": previous_value,
|
||||||
|
|||||||
@@ -582,7 +582,7 @@ class AutoTradeService:
|
|||||||
JournalService().log_ui_info(
|
JournalService().log_ui_info(
|
||||||
event_type="signal_summary",
|
event_type="signal_summary",
|
||||||
message=(
|
message=(
|
||||||
f"🟡 HOLD {duration_text} завершён сигналом {next_signal}."
|
f"HOLD длился {duration_text} и завершился сигналом {next_signal}."
|
||||||
),
|
),
|
||||||
screen="auto",
|
screen="auto",
|
||||||
action="signal_summary",
|
action="signal_summary",
|
||||||
@@ -622,7 +622,9 @@ class AutoTradeService:
|
|||||||
try:
|
try:
|
||||||
JournalService().log_ui_info(
|
JournalService().log_ui_info(
|
||||||
event_type="signal_ready",
|
event_type="signal_ready",
|
||||||
message=f"Сигнал {normalized_signal} готов к исполнению.",
|
message=(
|
||||||
|
f"Сигнал {normalized_signal} подтверждён и готов к исполнению."
|
||||||
|
),
|
||||||
screen="auto",
|
screen="auto",
|
||||||
action="signal_ready",
|
action="signal_ready",
|
||||||
payload={
|
payload={
|
||||||
@@ -691,28 +693,15 @@ class AutoTradeService:
|
|||||||
and market_state != type(self)._last_logged_market_state
|
and market_state != type(self)._last_logged_market_state
|
||||||
)
|
)
|
||||||
|
|
||||||
trend_changed = (
|
|
||||||
market_trend is not None
|
|
||||||
and market_trend != previous_market_trend
|
|
||||||
and market_trend != type(self)._last_logged_market_trend
|
|
||||||
)
|
|
||||||
|
|
||||||
volatility_changed = (
|
volatility_changed = (
|
||||||
market_volatility is not None
|
market_volatility is not None
|
||||||
and market_volatility != previous_market_volatility
|
and market_volatility != previous_market_volatility
|
||||||
and market_volatility != type(self)._last_logged_market_volatility
|
and market_volatility != type(self)._last_logged_market_volatility
|
||||||
)
|
)
|
||||||
|
|
||||||
if not state_changed and not trend_changed and not volatility_changed:
|
if not state_changed and not volatility_changed:
|
||||||
return
|
return
|
||||||
|
|
||||||
type(self)._last_logged_market_state = market_state
|
|
||||||
type(self)._last_logged_market_trend = market_trend
|
|
||||||
type(self)._last_logged_market_volatility = market_volatility
|
|
||||||
|
|
||||||
level = self._market_journal_level(market_state)
|
|
||||||
message = self._market_state_message(market_state)
|
|
||||||
|
|
||||||
journal_payload = {
|
journal_payload = {
|
||||||
**payload,
|
**payload,
|
||||||
"previous_market_state": previous_market_state,
|
"previous_market_state": previous_market_state,
|
||||||
@@ -724,25 +713,64 @@ class AutoTradeService:
|
|||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if level == "WARNING":
|
if state_changed:
|
||||||
JournalService().log_ui_warning(
|
self._write_market_journal_event(
|
||||||
event_type="market_state_changed",
|
event_type="market_state_changed",
|
||||||
message=message,
|
market_state=market_state,
|
||||||
screen="auto",
|
message=self._market_state_message(market_state),
|
||||||
action="market_analysis",
|
|
||||||
payload=journal_payload,
|
payload=journal_payload,
|
||||||
)
|
)
|
||||||
return
|
|
||||||
|
|
||||||
JournalService().log_ui_info(
|
if volatility_changed:
|
||||||
event_type="market_state_changed",
|
self._write_market_journal_event(
|
||||||
|
event_type="market_volatility_changed",
|
||||||
|
market_state=market_state,
|
||||||
|
message=self._market_volatility_message(market_volatility),
|
||||||
|
payload=journal_payload,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
type(self)._last_logged_market_state = market_state
|
||||||
|
type(self)._last_logged_market_trend = market_trend
|
||||||
|
type(self)._last_logged_market_volatility = market_volatility
|
||||||
|
|
||||||
|
def _write_market_journal_event(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
event_type: str,
|
||||||
|
market_state: str,
|
||||||
|
message: str,
|
||||||
|
payload: dict,
|
||||||
|
) -> None:
|
||||||
|
level = self._market_journal_level(market_state)
|
||||||
|
|
||||||
|
if level == "WARNING":
|
||||||
|
JournalService().log_ui_warning(
|
||||||
|
event_type=event_type,
|
||||||
message=message,
|
message=message,
|
||||||
screen="auto",
|
screen="auto",
|
||||||
action="market_analysis",
|
action="market_analysis",
|
||||||
payload=journal_payload,
|
payload=payload,
|
||||||
)
|
)
|
||||||
except Exception:
|
return
|
||||||
pass
|
|
||||||
|
JournalService().log_ui_info(
|
||||||
|
event_type=event_type,
|
||||||
|
message=message,
|
||||||
|
screen="auto",
|
||||||
|
action="market_analysis",
|
||||||
|
payload=payload,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _market_volatility_message(self, market_volatility: str | None) -> str:
|
||||||
|
messages = {
|
||||||
|
"LOW": "Волатильность изменена: низкая.",
|
||||||
|
"NORMAL": "Волатильность изменена: нормальная.",
|
||||||
|
"HIGH": "Волатильность изменена: высокая.",
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages.get(str(market_volatility or ""), "Волатильность не определена.")
|
||||||
|
|
||||||
def _market_journal_level(self, market_state: str) -> str:
|
def _market_journal_level(self, market_state: str) -> str:
|
||||||
if market_state == "HIGH_VOLATILITY":
|
if market_state == "HIGH_VOLATILITY":
|
||||||
@@ -752,14 +780,14 @@ class AutoTradeService:
|
|||||||
|
|
||||||
def _market_state_message(self, market_state: str) -> str:
|
def _market_state_message(self, market_state: str) -> str:
|
||||||
messages = {
|
messages = {
|
||||||
"TREND_UP": "📈 Рынок перешёл в рост.",
|
"TREND_UP": "Состояние рынка изменено: рост.",
|
||||||
"TREND_DOWN": "📉 Рынок перешёл в снижение.",
|
"TREND_DOWN": "Состояние рынка изменено: снижение.",
|
||||||
"RANGE": "🟰 На рынке нет выраженного направления.",
|
"RANGE": "Состояние рынка изменено: нет выраженного направления.",
|
||||||
"HIGH_VOLATILITY": "⚠️ Рынок стал слишком волатильным.",
|
"HIGH_VOLATILITY": "Состояние рынка изменено: высокая волатильность.",
|
||||||
"LOW_VOLATILITY": "💤 Рынок почти не движется.",
|
"LOW_VOLATILITY": "Состояние рынка изменено: низкая активность.",
|
||||||
}
|
}
|
||||||
|
|
||||||
return messages.get(market_state, "⏳ Состояние рынка анализируется.")
|
return messages.get(market_state, "Состояние рынка анализируется.")
|
||||||
|
|
||||||
def run_cycle(self) -> AutoTradeState:
|
def run_cycle(self) -> AutoTradeState:
|
||||||
state = self.get_state()
|
state = self.get_state()
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ class ExecutionEngine:
|
|||||||
state.execution_block_reason = None
|
state.execution_block_reason = None
|
||||||
state.last_flip_block_reason = None
|
state.last_flip_block_reason = None
|
||||||
state.last_execution_action = action
|
state.last_execution_action = action
|
||||||
state.last_execution_reason = f"Paper ENTRY {side} открыта."
|
state.last_execution_reason = f"Позиция {side} открыта."
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"execution_type": "ENTRY",
|
"execution_type": "ENTRY",
|
||||||
@@ -157,7 +157,7 @@ class ExecutionEngine:
|
|||||||
|
|
||||||
EventBus.emit("paper_position_opened", payload)
|
EventBus.emit("paper_position_opened", payload)
|
||||||
|
|
||||||
return ExecutionDecision(action, True, f"Paper ENTRY {side} открыта.")
|
return ExecutionDecision(action, True, f"Позиция {side} открыта.")
|
||||||
|
|
||||||
def _flip_position(self, state: AutoTradeState) -> ExecutionDecision:
|
def _flip_position(self, state: AutoTradeState) -> ExecutionDecision:
|
||||||
position = type(self)._position
|
position = type(self)._position
|
||||||
@@ -227,7 +227,7 @@ class ExecutionEngine:
|
|||||||
state.execution_block_reason = None
|
state.execution_block_reason = None
|
||||||
state.last_flip_block_reason = None
|
state.last_flip_block_reason = None
|
||||||
state.last_execution_action = f"FLIP_{old_side}_TO_{new_side}"
|
state.last_execution_action = f"FLIP_{old_side}_TO_{new_side}"
|
||||||
state.last_execution_reason = "Paper FLIP выполнен."
|
state.last_execution_reason = "Направление позиции изменено."
|
||||||
state.last_flip_at = now
|
state.last_flip_at = now
|
||||||
type(self)._last_flip_block_key = None
|
type(self)._last_flip_block_key = None
|
||||||
|
|
||||||
@@ -278,7 +278,7 @@ class ExecutionEngine:
|
|||||||
return ExecutionDecision(
|
return ExecutionDecision(
|
||||||
f"FLIP_{old_side}_TO_{new_side}",
|
f"FLIP_{old_side}_TO_{new_side}",
|
||||||
True,
|
True,
|
||||||
f"Paper FLIP выполнен: {old_side} → {new_side}.",
|
f"Направление позиции изменено: {old_side} → {new_side}.",
|
||||||
)
|
)
|
||||||
|
|
||||||
def _close_position(
|
def _close_position(
|
||||||
@@ -359,9 +359,9 @@ class ExecutionEngine:
|
|||||||
else "CLOSE"
|
else "CLOSE"
|
||||||
)
|
)
|
||||||
state.last_execution_reason = (
|
state.last_execution_reason = (
|
||||||
f"Paper EXIT выполнена по риску: {forced_reason}."
|
f"Позиция закрыта по правилу защиты: {forced_reason}."
|
||||||
if forced_reason is not None
|
if forced_reason is not None
|
||||||
else "Paper EXIT выполнена."
|
else "Позиция закрыта."
|
||||||
)
|
)
|
||||||
type(self)._last_flip_block_key = None
|
type(self)._last_flip_block_key = None
|
||||||
|
|
||||||
@@ -369,10 +369,10 @@ class ExecutionEngine:
|
|||||||
return ExecutionDecision(
|
return ExecutionDecision(
|
||||||
f"FORCE_CLOSE_{forced_reason}",
|
f"FORCE_CLOSE_{forced_reason}",
|
||||||
True,
|
True,
|
||||||
f"Paper EXIT выполнена по риску: {forced_reason}.",
|
f"Позиция закрыта по правилу защиты: {forced_reason}.",
|
||||||
)
|
)
|
||||||
|
|
||||||
return ExecutionDecision("CLOSE", True, "Paper EXIT выполнена.")
|
return ExecutionDecision("CLOSE", True, "Позиция закрыта.")
|
||||||
|
|
||||||
def _risk_close_decision(self, state: AutoTradeState) -> ExecutionDecision | None:
|
def _risk_close_decision(self, state: AutoTradeState) -> ExecutionDecision | None:
|
||||||
position = type(self)._position
|
position = type(self)._position
|
||||||
@@ -472,26 +472,26 @@ class ExecutionEngine:
|
|||||||
|
|
||||||
if confidence < self._min_flip_confidence:
|
if confidence < self._min_flip_confidence:
|
||||||
return (
|
return (
|
||||||
"Flip blocked: signal confidence "
|
"уверенность сигнала ниже порога "
|
||||||
f"{confidence:.2f} < {self._min_flip_confidence:.2f}."
|
f"({confidence:.2f} < {self._min_flip_confidence:.2f})"
|
||||||
)
|
)
|
||||||
|
|
||||||
if repeat_count < self._min_flip_repeat_count:
|
if repeat_count < self._min_flip_repeat_count:
|
||||||
return (
|
return (
|
||||||
"Flip blocked: repeat count "
|
"сигнал ещё не подтверждён нужным количеством повторов "
|
||||||
f"{repeat_count} < {self._min_flip_repeat_count}."
|
f"({repeat_count} < {self._min_flip_repeat_count})"
|
||||||
)
|
)
|
||||||
|
|
||||||
if hold_seconds is not None and hold_seconds < self._min_flip_hold_seconds:
|
if hold_seconds is not None and hold_seconds < self._min_flip_hold_seconds:
|
||||||
return (
|
return (
|
||||||
"Flip blocked: position hold time "
|
"позиция открыта слишком недавно "
|
||||||
f"{hold_seconds}s < {self._min_flip_hold_seconds}s."
|
f"({hold_seconds}с < {self._min_flip_hold_seconds}с)"
|
||||||
)
|
)
|
||||||
|
|
||||||
if unrealized_pnl < 0 and confidence < self._loss_flip_confidence:
|
if unrealized_pnl < 0 and confidence < self._loss_flip_confidence:
|
||||||
return (
|
return (
|
||||||
"Flip blocked: position is negative and signal is not strong enough "
|
"позиция сейчас в минусе, а сигнал недостаточно сильный "
|
||||||
f"({confidence:.2f} < {self._loss_flip_confidence:.2f})."
|
f"({confidence:.2f} < {self._loss_flip_confidence:.2f})"
|
||||||
)
|
)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
@@ -535,7 +535,7 @@ class ExecutionEngine:
|
|||||||
|
|
||||||
JournalService().log_ui_warning(
|
JournalService().log_ui_warning(
|
||||||
event_type="position_flip_blocked",
|
event_type="position_flip_blocked",
|
||||||
message=f"Смена направления позиции заблокирована: {reason}",
|
message=f"Смена направления позиции заблокирована: {reason}.",
|
||||||
screen="auto",
|
screen="auto",
|
||||||
action="paper_execution",
|
action="paper_execution",
|
||||||
payload=payload,
|
payload=payload,
|
||||||
|
|||||||
@@ -13,30 +13,7 @@ from openpyxl import Workbook
|
|||||||
from openpyxl.styles import Font
|
from openpyxl.styles import Font
|
||||||
|
|
||||||
from src.core.config import load_settings
|
from src.core.config import load_settings
|
||||||
|
from src.core.event_titles import event_title
|
||||||
|
|
||||||
EVENT_TITLES = {
|
|
||||||
"app_start": "Запуск приложения",
|
|
||||||
"journal_open_requested": "Открытие журнала",
|
|
||||||
"journal_export_csv_success": "Экспорт CSV",
|
|
||||||
"journal_export_csv_error": "Ошибка экспорта CSV",
|
|
||||||
"journal_export_xlsx_success": "Экспорт Excel",
|
|
||||||
"journal_export_xlsx_error": "Ошибка экспорта Excel",
|
|
||||||
"journal_cleared": "Журнал очищен",
|
|
||||||
"journal_cleared_old": "Очистка старых записей",
|
|
||||||
"system_open_alert": "Система загружена с предупреждениями",
|
|
||||||
"system_open_success": "Система загружена",
|
|
||||||
"market_open_requested": "Открытие рынка",
|
|
||||||
"market_open_success": "Рынок загружен",
|
|
||||||
"market_open_error": "Ошибка открытия рынка",
|
|
||||||
"portfolio_open_requested": "Открытие портфеля",
|
|
||||||
"portfolio_open_success": "Портфель загружен",
|
|
||||||
"portfolio_open_error": "Ошибка открытия портфеля",
|
|
||||||
"portfolio_partial_estimate": "Частичная оценка портфеля",
|
|
||||||
"exchange_request_error": "Ошибка запроса к бирже",
|
|
||||||
"balance_summary_loaded": "Баланс загружен",
|
|
||||||
"balance_summary_error": "Ошибка загрузки баланса",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_EMOJI_RE = re.compile(
|
_EMOJI_RE = re.compile(
|
||||||
@@ -81,8 +58,7 @@ def _format_datetime(value: object) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def _event_title(event_type: object) -> str:
|
def _event_title(event_type: object) -> str:
|
||||||
value = str(event_type or "").strip()
|
return event_title(event_type)
|
||||||
return EVENT_TITLES.get(value, value.replace("_", " ").strip().capitalize())
|
|
||||||
|
|
||||||
|
|
||||||
def _payload(row: dict) -> dict:
|
def _payload(row: dict) -> dict:
|
||||||
|
|||||||
@@ -434,6 +434,23 @@
|
|||||||
- подготовлена база для future AI market commentary
|
- подготовлена база для future AI market commentary
|
||||||
- журнал подготовлен к market filters/search layer
|
- журнал подготовлен к market filters/search layer
|
||||||
|
|
||||||
|
#### 07.4.4.1.3 ✅ Journal Runtime Cleanup & Event Titles Layer
|
||||||
|
- унифицирована модель журнала: event_type / заголовок / сообщение
|
||||||
|
- заголовки событий сокращены до понятных сущностей
|
||||||
|
- подготовлен общий EVENT_TITLES mapping для journal UI и export layer
|
||||||
|
- убрано дублирование market_state_changed и market_trend_changed
|
||||||
|
- market_trend_changed исключён из обычного журналирования
|
||||||
|
- market trend сохранён в payload для аналитики и debug
|
||||||
|
- market-analysis сообщения приведены к единому формату
|
||||||
|
- Auto UI wording уточнён: падение заменено на нисходящий тренд
|
||||||
|
- сообщения настроек автоторговли приведены к короткому стилю
|
||||||
|
- символы активов очищены в journal messages: BTC вместо BTC/USD_LEVERAGE
|
||||||
|
- risk-control logging стал показывать SL / TP / ML в одном сообщении
|
||||||
|
- notification logging унифицирован через notification_sent / notification_error
|
||||||
|
- из market runtime сообщений убран лишний [AUTO]
|
||||||
|
- CSV / XLSX export очищен от эмодзи
|
||||||
|
- журнал подготовлен к централизованному event_titles.py и future filters/search layer
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 07.4.5
|
### 07.4.5
|
||||||
|
|||||||
@@ -410,6 +410,23 @@
|
|||||||
- подготовлена база для future AI market commentary
|
- подготовлена база для future AI market commentary
|
||||||
- журнал подготовлен к market filters/search layer
|
- журнал подготовлен к market filters/search layer
|
||||||
|
|
||||||
|
#### 07.4.4.1.3 ✅ Journal Runtime Cleanup & Event Titles Layer
|
||||||
|
- унифицирована модель журнала: event_type / заголовок / сообщение
|
||||||
|
- заголовки событий сокращены до понятных сущностей
|
||||||
|
- подготовлен общий EVENT_TITLES mapping для journal UI и export layer
|
||||||
|
- убрано дублирование market_state_changed и market_trend_changed
|
||||||
|
- market_trend_changed исключён из обычного журналирования
|
||||||
|
- market trend сохранён в payload для аналитики и debug
|
||||||
|
- market-analysis сообщения приведены к единому формату
|
||||||
|
- Auto UI wording уточнён: падение заменено на нисходящий тренд
|
||||||
|
- сообщения настроек автоторговли приведены к короткому стилю
|
||||||
|
- символы активов очищены в journal messages: BTC вместо BTC/USD_LEVERAGE
|
||||||
|
- risk-control logging стал показывать SL / TP / ML в одном сообщении
|
||||||
|
- notification logging унифицирован через notification_sent / notification_error
|
||||||
|
- из market runtime сообщений убран лишний [AUTO]
|
||||||
|
- CSV / XLSX export очищен от эмодзи
|
||||||
|
- журнал подготовлен к централизованному event_titles.py и future filters/search layer
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 07.4.5
|
### 07.4.5
|
||||||
|
|||||||
@@ -0,0 +1,381 @@
|
|||||||
|
# 07.4.4.1.3 — Journal Runtime Cleanup & Event Titles Layer
|
||||||
|
|
||||||
|
## Цель этапа
|
||||||
|
|
||||||
|
Привести журнал runtime-событий к единому, читаемому и пригодному для анализа виду.
|
||||||
|
|
||||||
|
После этапов Market State Engine и Market State Journal Events журнал уже начал фиксировать состояние рынка, сигналы, настройки и runtime-события. Но в экспорте и UI оставались проблемы:
|
||||||
|
|
||||||
|
- заголовки часто дублировали сообщения;
|
||||||
|
- часть сообщений была слишком длинной;
|
||||||
|
- часть event_type отображалась технически;
|
||||||
|
- market_state_changed и market_trend_changed часто писали один и тот же смысл;
|
||||||
|
- в экспорт попадали эмодзи;
|
||||||
|
- в сообщениях встречались лишние технические маркеры вроде [AUTO];
|
||||||
|
- торговые инструменты отображались в сыром виде, например BTC/USD_LEVERAGE;
|
||||||
|
- EVENT_TITLES дублировались в разных файлах.
|
||||||
|
|
||||||
|
Этап 07.4.4.1.3 сделал журнал более чистым, компактным и пригодным для дальнейшей аналитики.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Что было реализовано
|
||||||
|
|
||||||
|
## 1. Унифицирована логика заголовков журнала
|
||||||
|
|
||||||
|
В журнале была закреплена новая модель:
|
||||||
|
|
||||||
|
- `event_type` — технический код события;
|
||||||
|
- `Заголовок` — короткая сущность события;
|
||||||
|
- `Сообщение` — конкретное действие или факт.
|
||||||
|
|
||||||
|
Теперь заголовок больше не повторяет текст сообщения.
|
||||||
|
|
||||||
|
Пример итогового формата:
|
||||||
|
|
||||||
|
- `auto_settings_updated` → `Автоторговля` → `Плечо изменено: x3.`
|
||||||
|
- `risk_settings_updated` → `Защита` → `Параметры защиты позиции изменены: SL=1%, TP=1%, ML=off.`
|
||||||
|
- `market_state_changed` → `Рынок` → `Состояние рынка изменено: снижение.`
|
||||||
|
- `market_stream_connected` → `Рынок` → `Поток рыночных данных подключён.`
|
||||||
|
|
||||||
|
Такой формат стал понятнее и для пользователя, и для будущих фильтров / поиска / аналитики.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. EVENT_TITLES подготовлены к централизации
|
||||||
|
|
||||||
|
Была согласована новая компактная карта заголовков событий.
|
||||||
|
|
||||||
|
Ключевые группы:
|
||||||
|
|
||||||
|
- `Сигнал`
|
||||||
|
- `Позиция`
|
||||||
|
- `Автоторговля`
|
||||||
|
- `Защита`
|
||||||
|
- `Рынок`
|
||||||
|
- `Журнал`
|
||||||
|
- `Уведомление`
|
||||||
|
- `Приложение`
|
||||||
|
- `Система`
|
||||||
|
- `Портфель`
|
||||||
|
- `Биржа`
|
||||||
|
- `Баланс`
|
||||||
|
|
||||||
|
Также принято архитектурное решение вынести `EVENT_TITLES` в отдельный общий файл:
|
||||||
|
|
||||||
|
```text
|
||||||
|
app/src/core/event_titles.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Это правильнее, чем хранить заголовки в `trading/journal`, потому что события относятся не только к торговле, но и к системе, журналу, рынку, уведомлениям, портфелю и бирже.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Убрано дублирование market_state_changed и market_trend_changed
|
||||||
|
|
||||||
|
До правки одно изменение рынка могло писать сразу два события:
|
||||||
|
|
||||||
|
- `market_state_changed` — «Рынок перешёл в рост»
|
||||||
|
- `market_trend_changed` — «Направление рынка изменилось на рост»
|
||||||
|
|
||||||
|
Для пользователя это выглядело как дублирование.
|
||||||
|
|
||||||
|
Теперь `market_trend_changed` исключён из обычного журналирования, потому что направление уже включено в market state:
|
||||||
|
|
||||||
|
- `TREND_UP` уже означает рост;
|
||||||
|
- `TREND_DOWN` уже означает снижение;
|
||||||
|
- `RANGE` уже означает отсутствие выраженного направления.
|
||||||
|
|
||||||
|
В журнале остаётся одно основное событие:
|
||||||
|
|
||||||
|
```text
|
||||||
|
market_state_changed → Рынок → Состояние рынка изменено: снижение.
|
||||||
|
```
|
||||||
|
|
||||||
|
Это сделало market-analysis feed значительно чище.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Аналитические market-сообщения приведены к единому стилю
|
||||||
|
|
||||||
|
Market-analysis сообщения были переписаны в одинаковом формате:
|
||||||
|
|
||||||
|
- `Состояние рынка изменено: рост.`
|
||||||
|
- `Состояние рынка изменено: снижение.`
|
||||||
|
- `Состояние рынка изменено: нет выраженного направления.`
|
||||||
|
- `Состояние рынка изменено: высокая волатильность.`
|
||||||
|
- `Состояние рынка изменено: низкая активность.`
|
||||||
|
|
||||||
|
Сообщения волатильности также приведены к компактному виду:
|
||||||
|
|
||||||
|
- `Волатильность изменена: низкая.`
|
||||||
|
- `Волатильность изменена: нормальная.`
|
||||||
|
- `Волатильность изменена: высокая.`
|
||||||
|
|
||||||
|
Это важно для дальнейшей аналитики: сообщения теперь имеют одинаковую структуру и легче читаются в CSV / XLSX.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Market trend оставлен в payload, но убран из отдельного журнала
|
||||||
|
|
||||||
|
Поле `market_trend` не удалялось из состояния и payload.
|
||||||
|
|
||||||
|
Оно по-прежнему может использоваться:
|
||||||
|
|
||||||
|
- в UI;
|
||||||
|
- в стратегии;
|
||||||
|
- в debug;
|
||||||
|
- в будущей аналитике;
|
||||||
|
- в explainability layer.
|
||||||
|
|
||||||
|
Но отдельное событие `market_trend_changed` больше не нужно для обычного журнала, потому что оно дублировало `market_state_changed`.
|
||||||
|
|
||||||
|
Итог:
|
||||||
|
|
||||||
|
- данные не потеряны;
|
||||||
|
- журнал стал чище;
|
||||||
|
- future analytics сохранила нужный контекст.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Улучшено отображение состояния рынка в Auto UI
|
||||||
|
|
||||||
|
Формулировка `Рынок · Падение` была признана неоднозначной.
|
||||||
|
|
||||||
|
Она могла восприниматься как «цена прямо сейчас падает», хотя фактически отображался более широкий режим рынка по анализатору.
|
||||||
|
|
||||||
|
Поэтому UI wording был уточнён:
|
||||||
|
|
||||||
|
- `TREND_UP` → `Тренд · Восходящий`
|
||||||
|
- `TREND_DOWN` → `Тренд · Нисходящий`
|
||||||
|
- `RANGE` → `Тренд · Нет выраженного направления`
|
||||||
|
- `HIGH_VOLATILITY` → `Рынок · Высокая волатильность`
|
||||||
|
- `LOW_VOLATILITY` → `Рынок · Низкая активность`
|
||||||
|
- `UNKNOWN` → `Рынок · Идёт анализ`
|
||||||
|
|
||||||
|
Это снизило риск неправильной интерпретации графика пользователем.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Сообщения настроек автоторговли приведены к единому стилю
|
||||||
|
|
||||||
|
Сообщения настроек были сокращены и стандартизированы.
|
||||||
|
|
||||||
|
Теперь используется формат:
|
||||||
|
|
||||||
|
- `Стратегия изменена: TREND.`
|
||||||
|
- `Актив изменён: BTC.`
|
||||||
|
- `Риск на сделку изменён: 1%.`
|
||||||
|
- `Плечо изменено: x3.`
|
||||||
|
- `Лимит на сделку изменён: 50%.`
|
||||||
|
|
||||||
|
Ранее сообщения были длиннее и местами дублировали заголовок.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Символы активов очищены для journal messages
|
||||||
|
|
||||||
|
В пользовательских сообщениях журнала убраны технические торговые символы вида:
|
||||||
|
|
||||||
|
```text
|
||||||
|
BTC/USD_LEVERAGE
|
||||||
|
ETH/USD_LEVERAGE
|
||||||
|
LTC/USD_LEVERAGE
|
||||||
|
XRP/USD_LEVERAGE
|
||||||
|
```
|
||||||
|
|
||||||
|
Теперь в сообщениях отображаются короткие имена активов:
|
||||||
|
|
||||||
|
```text
|
||||||
|
BTC
|
||||||
|
ETH
|
||||||
|
LTC
|
||||||
|
XRP
|
||||||
|
```
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[DEMO] Актив изменён: BTC.
|
||||||
|
```
|
||||||
|
|
||||||
|
В payload при этом сохраняется полный технический symbol, поэтому аналитика и отладка не теряют точность.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Risk-control logging приведён к единому формату
|
||||||
|
|
||||||
|
Сообщение `Параметры защиты позиции обновлены` было заменено на более информативное:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Параметры защиты позиции изменены: SL=1%, TP=1%, ML=25 USD.
|
||||||
|
```
|
||||||
|
|
||||||
|
Теперь в одном сообщении видно состояние всех ключевых protective rules:
|
||||||
|
|
||||||
|
- Stop Loss;
|
||||||
|
- Take Profit;
|
||||||
|
- Max Loss.
|
||||||
|
|
||||||
|
Это удобно для просмотра истории настроек в журнале и экспорте.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Уведомления приведены к единой модели событий
|
||||||
|
|
||||||
|
Notification layer был очищен от legacy event_type.
|
||||||
|
|
||||||
|
Теперь используется единая схема:
|
||||||
|
|
||||||
|
- `notification_sent`
|
||||||
|
- `notification_error`
|
||||||
|
|
||||||
|
После успешной отправки Telegram-уведомления добавлено логирование:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Telegram-уведомление отправлено.
|
||||||
|
```
|
||||||
|
|
||||||
|
Ошибки отправки также пишутся в один event_type `notification_error`, а детали уходят в payload.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Убран лишний runtime-префикс [AUTO]
|
||||||
|
|
||||||
|
Сообщения market runtime раньше выглядели так:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[DEMO] [AUTO] Поток рыночных данных подключён.
|
||||||
|
```
|
||||||
|
|
||||||
|
Префикс `[AUTO]` был признан избыточным, потому что контекст уже хранится в колонках `Экран`, `Действие` и payload.
|
||||||
|
|
||||||
|
Теперь сообщение выглядит чище:
|
||||||
|
|
||||||
|
```text
|
||||||
|
[DEMO] Поток рыночных данных подключён.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Экспорт очищен от эмодзи
|
||||||
|
|
||||||
|
CSV / XLSX экспорт был очищен от эмодзи.
|
||||||
|
|
||||||
|
Эмодзи могут быть удобны в Telegram UI, но в экспортных файлах они мешают:
|
||||||
|
|
||||||
|
- сортировке;
|
||||||
|
- фильтрации;
|
||||||
|
- чтению в Excel;
|
||||||
|
- будущей BI-аналитике;
|
||||||
|
- машинной обработке.
|
||||||
|
|
||||||
|
Теперь экспорт содержит чистые текстовые значения.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Изменения в архитектуре
|
||||||
|
|
||||||
|
## Core Layer
|
||||||
|
|
||||||
|
Подготовлено выделение общего файла:
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/core/event_titles.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Он должен стать единым источником event-title mapping для:
|
||||||
|
|
||||||
|
- journal UI;
|
||||||
|
- CSV export;
|
||||||
|
- XLSX export;
|
||||||
|
- будущих filters/search layer.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Journal UI Layer
|
||||||
|
|
||||||
|
`journal_ui.py` подготовлен к использованию централизованного `event_title()`.
|
||||||
|
|
||||||
|
Локальный `EVENT_TITLES` больше не должен дублироваться после вынесения в core.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Journal Export Layer
|
||||||
|
|
||||||
|
`exporter.py` подготовлен к использованию того же `event_title()`.
|
||||||
|
|
||||||
|
Это гарантирует, что UI и экспорт будут показывать одинаковые заголовки.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Auto Runtime Layer
|
||||||
|
|
||||||
|
В `AutoTradeService` улучшена логика market logging:
|
||||||
|
|
||||||
|
- убрано избыточное `market_trend_changed`;
|
||||||
|
- сохранено `market_state_changed`;
|
||||||
|
- сохранено `market_volatility_changed`;
|
||||||
|
- market trend оставлен в payload;
|
||||||
|
- сообщения приведены к единому стилю.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Market UI Layer
|
||||||
|
|
||||||
|
Auto UI стал точнее отображать market state:
|
||||||
|
|
||||||
|
- вместо «Падение» используется «Нисходящий тренд»;
|
||||||
|
- вместо «Рост» используется «Восходящий тренд»;
|
||||||
|
- вместо «Без направления» используется «Нет выраженного направления».
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Что изменилось для пользователя
|
||||||
|
|
||||||
|
Журнал стал выглядеть как нормальная событийная лента:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Рынок | Состояние рынка изменено: снижение.
|
||||||
|
Рынок | Волатильность изменена: нормальная.
|
||||||
|
Автоторговля | Плечо изменено: x3.
|
||||||
|
Защита | Параметры защиты позиции изменены: SL=1%, TP=1%, ML=off.
|
||||||
|
Журнал | Журнал очищен.
|
||||||
|
```
|
||||||
|
|
||||||
|
Пользователь теперь быстрее понимает:
|
||||||
|
|
||||||
|
- что изменилось;
|
||||||
|
- где изменилось;
|
||||||
|
- относится ли событие к рынку, настройкам, защите, позиции или журналу;
|
||||||
|
- какой был конкретный результат.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Что подготовлено дальше
|
||||||
|
|
||||||
|
Этап подготовил основу для следующих работ:
|
||||||
|
|
||||||
|
- централизованный `event_titles.py`;
|
||||||
|
- фильтры журнала по сущностям;
|
||||||
|
- поиск по event_type;
|
||||||
|
- группировка событий по доменам;
|
||||||
|
- журнал действий пользователя;
|
||||||
|
- market transition statistics;
|
||||||
|
- аналитика частоты смены market state;
|
||||||
|
- AI-комментарии к журналу;
|
||||||
|
- подготовка runtime-журнала к реальной торговле.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Итог этапа
|
||||||
|
|
||||||
|
Этап 07.4.4.1.3 завершил важную часть подготовки журнала к реальной эксплуатации.
|
||||||
|
|
||||||
|
Журнал больше не выглядит как набор технических сообщений. Он стал ближе к операционной ленте автоторговли, где каждая строка имеет понятную структуру:
|
||||||
|
|
||||||
|
```text
|
||||||
|
что произошло → где произошло → конкретное действие
|
||||||
|
```
|
||||||
|
|
||||||
|
Это важный шаг перед переходом к более серьёзной аналитике стратегий и дальнейшей подготовке к real trading.
|
||||||
Reference in New Issue
Block a user