07.4.4.1.2 — Market State Journal Events

This commit is contained in:
2026-05-11 00:28:26 +03:00
parent ef7cec68cc
commit c07a1a4dff
9 changed files with 532 additions and 73 deletions

View File

@@ -107,6 +107,7 @@ class MarketDataRunner:
ws_symbol = cls._ws_symbol(symbol) ws_symbol = cls._ws_symbol(symbol)
if symbol != last_symbol: if symbol != last_symbol:
previous_symbol = last_symbol
last_symbol = symbol last_symbol = symbol
if not cls._is_cache_symbol_used_by_other_runtime( if not cls._is_cache_symbol_used_by_other_runtime(
@@ -115,16 +116,18 @@ class MarketDataRunner:
): ):
MarketPriceCache.clear(cache_symbol) MarketPriceCache.clear(cache_symbol)
cls._log_info( if previous_symbol is not None:
context, cls._log_info(
"market_symbol_changed", context,
f"Инструмент автоторговли изменён на {cache_symbol}.", "market_symbol_changed",
{ f"Инструмент автоторговли изменён на {cache_symbol}.",
"symbol": symbol, {
"cache_symbol": cache_symbol, "previous_symbol": previous_symbol,
"ws_symbol": ws_symbol, "symbol": symbol,
}, "cache_symbol": cache_symbol,
) "ws_symbol": ws_symbol,
},
)
try: try:
await cls._run_websocket(context, symbol) await cls._run_websocket(context, symbol)

View File

@@ -243,7 +243,7 @@ def _market_state_line(state) -> str:
labels = { labels = {
"TREND_UP": "📈 Рынок · Рост", "TREND_UP": "📈 Рынок · Рост",
"TREND_DOWN": "📉 Рынок · Падение", "TREND_DOWN": "📉 Рынок · Падение",
"RANGE": "🟰 Рынок · Флэт", "RANGE": "🟰 Рынок · Без направления",
"HIGH_VOLATILITY": "⚠️ Рынок · Волатильность", "HIGH_VOLATILITY": "⚠️ Рынок · Волатильность",
"LOW_VOLATILITY": "🟰 Рынок · Спокойный", "LOW_VOLATILITY": "🟰 Рынок · Спокойный",
"UNKNOWN": "⏳ Рынок · Анализ", "UNKNOWN": "⏳ Рынок · Анализ",

View File

@@ -344,10 +344,44 @@ async def open_auto_strategy_settings(callback: CallbackQuery) -> None:
await callback.answer() await callback.answer()
def _log_auto_setting_updated(
*,
event_type: str = "auto_settings_updated",
message: str,
action: str,
payload: dict,
) -> None:
try:
JournalService().log_ui_info(
event_type=event_type,
message=message,
screen="settings_auto",
action=action,
payload=payload,
)
except Exception:
pass
@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] strategy = callback.data.split(":", 2)[2].upper()
AutoTradeService().set_strategy(strategy.upper())
service = AutoTradeService()
state = service.get_state()
previous_strategy = state.strategy
service.set_strategy(strategy)
if previous_strategy != strategy:
_log_auto_setting_updated(
message=f"Стратегия автоторговли изменена на {strategy}.",
action="set_strategy",
payload={
"previous_strategy": previous_strategy,
"strategy": strategy,
},
)
await open_auto_settings(callback) await open_auto_settings(callback)
await callback.answer("Стратегия обновлена") await callback.answer("Стратегия обновлена")
@@ -380,7 +414,22 @@ async def open_auto_symbol_settings(callback: CallbackQuery) -> None:
@router.callback_query(F.data.startswith("settings:auto_symbol:")) @router.callback_query(F.data.startswith("settings:auto_symbol:"))
async def set_auto_symbol(callback: CallbackQuery) -> None: async def set_auto_symbol(callback: CallbackQuery) -> None:
symbol = callback.data.split(":", 2)[2] symbol = callback.data.split(":", 2)[2]
AutoTradeService().set_symbol(symbol)
service = AutoTradeService()
state = service.get_state()
previous_symbol = state.symbol
service.set_symbol(symbol)
if previous_symbol != symbol:
_log_auto_setting_updated(
message=f"Актив автоторговли изменён на {symbol}.",
action="set_symbol",
payload={
"previous_symbol": previous_symbol,
"symbol": symbol,
},
)
await open_auto_settings(callback) await open_auto_settings(callback)
await callback.answer("Актив обновлён") await callback.answer("Актив обновлён")
@@ -412,7 +461,22 @@ async def open_auto_risk_settings(callback: CallbackQuery) -> None:
@router.callback_query(F.data.startswith("settings:auto_risk:")) @router.callback_query(F.data.startswith("settings:auto_risk:"))
async def set_auto_risk(callback: CallbackQuery) -> None: async def set_auto_risk(callback: CallbackQuery) -> None:
risk = float(callback.data.split(":", 2)[2]) risk = float(callback.data.split(":", 2)[2])
AutoTradeService().set_risk_percent(risk)
service = AutoTradeService()
state = service.get_state()
previous_risk = state.risk_percent
service.set_risk_percent(risk)
if previous_risk != risk:
_log_auto_setting_updated(
message=f"Риск на сделку изменён на {risk:g}%.",
action="set_risk_percent",
payload={
"previous_risk_percent": previous_risk,
"risk_percent": risk,
},
)
await open_auto_settings(callback) await open_auto_settings(callback)
await callback.answer("Риск обновлён") await callback.answer("Риск обновлён")
@@ -447,12 +511,79 @@ async def open_auto_leverage_settings(callback: CallbackQuery) -> None:
@router.callback_query(F.data.startswith("settings:auto_leverage:")) @router.callback_query(F.data.startswith("settings:auto_leverage:"))
async def set_auto_leverage(callback: CallbackQuery) -> None: async def set_auto_leverage(callback: CallbackQuery) -> None:
leverage = float(callback.data.split(":", 2)[2]) leverage = float(callback.data.split(":", 2)[2])
AutoTradeService().set_leverage(leverage)
service = AutoTradeService()
state = service.get_state()
previous_leverage = state.leverage
service.set_leverage(leverage)
if previous_leverage != leverage:
_log_auto_setting_updated(
message=f"Плечо автоторговли изменено на x{leverage:g}.",
action="set_leverage",
payload={
"previous_leverage": previous_leverage,
"leverage": leverage,
},
)
await open_auto_settings(callback) await open_auto_settings(callback)
await callback.answer("Плечо обновлено") await callback.answer("Плечо обновлено")
@router.callback_query(F.data == "settings:auto_max_reserved")
async def open_auto_max_reserved_settings(callback: CallbackQuery) -> None:
if not await _prepare_system_from_callback(callback, screen="settings_auto"):
return
text = (
"<b>🏦 Лимит на сделку</b>\n\n"
"<b>СИСТЕМА</b> · Настройки · Автоторговля\n\n"
"Максимальная доля баланса, которую можно зарезервировать под позицию:"
)
builder = InlineKeyboardBuilder()
builder.button(text="25%", callback_data="settings:auto_max_reserved:25")
builder.button(text="50%", callback_data="settings:auto_max_reserved:50")
builder.button(text="75%", callback_data="settings:auto_max_reserved:75")
builder.button(text="100%", callback_data="settings:auto_max_reserved:100")
builder.button(text="off", callback_data="settings:auto_max_reserved:off")
builder.button(text="⬅️ Назад", callback_data="settings:auto")
builder.adjust(2, 2, 1, 1)
await callback.message.edit_text(text, reply_markup=builder.as_markup())
_register_system_screen(callback.message, screen="settings_auto")
await callback.answer()
@router.callback_query(F.data.startswith("settings:auto_max_reserved:"))
async def set_auto_max_reserved(callback: CallbackQuery) -> None:
raw_value = callback.data.split(":", 2)[2]
value = None if raw_value == "off" else float(raw_value)
service = AutoTradeService()
state = service.get_state()
previous_value = state.max_reserved_balance_percent
service.set_max_reserved_balance_percent(value)
if previous_value != value:
value_text = "off" if value is None else f"{value:g}%"
_log_auto_setting_updated(
message=f"Лимит на сделку изменён на {value_text}.",
action="set_max_reserved_balance_percent",
payload={
"previous_max_reserved_balance_percent": previous_value,
"max_reserved_balance_percent": value,
},
)
await open_auto_settings(callback)
await callback.answer("Лимит обновлён")
@router.callback_query(F.data == "settings:trade") @router.callback_query(F.data == "settings:trade")
async def open_trade_settings(callback: CallbackQuery) -> None: async def open_trade_settings(callback: CallbackQuery) -> None:
if not await _prepare_system_from_callback(callback, screen="settings_trade"): if not await _prepare_system_from_callback(callback, screen="settings_trade"):
@@ -665,40 +796,4 @@ async def open_system_about(callback: CallbackQuery) -> None:
await callback.message.edit_text(text, reply_markup=builder.as_markup()) await callback.message.edit_text(text, reply_markup=builder.as_markup())
_register_system_screen(callback.message, screen="system_about") _register_system_screen(callback.message, screen="system_about")
await callback.answer() await callback.answer()
@router.callback_query(F.data == "settings:auto_max_reserved")
async def open_auto_max_reserved_settings(callback: CallbackQuery) -> None:
if not await _prepare_system_from_callback(callback, screen="settings_auto"):
return
text = (
"<b>🏦 Лимит на сделку</b>\n\n"
"<b>СИСТЕМА</b> · Настройки · Автоторговля\n\n"
"Максимальная доля баланса, которую можно зарезервировать под позицию:"
)
builder = InlineKeyboardBuilder()
builder.button(text="25%", callback_data="settings:auto_max_reserved:25")
builder.button(text="50%", callback_data="settings:auto_max_reserved:50")
builder.button(text="75%", callback_data="settings:auto_max_reserved:75")
builder.button(text="100%", callback_data="settings:auto_max_reserved:100")
builder.button(text="off", callback_data="settings:auto_max_reserved:off")
builder.button(text="⬅️ Назад", callback_data="settings:auto")
builder.adjust(2, 2, 1, 1)
await callback.message.edit_text(text, reply_markup=builder.as_markup())
_register_system_screen(callback.message, screen="settings_auto")
await callback.answer()
@router.callback_query(F.data.startswith("settings:auto_max_reserved:"))
async def set_auto_max_reserved(callback: CallbackQuery) -> None:
raw_value = callback.data.split(":", 2)[2]
value = None if raw_value == "off" else float(raw_value)
AutoTradeService().set_max_reserved_balance_percent(value)
await open_auto_settings(callback)
await callback.answer("Max Reserved обновлён")

View File

@@ -32,6 +32,9 @@ class AutoTradeService:
_last_signal_confidence: float = 0.0 _last_signal_confidence: float = 0.0
_last_signal_payload: dict | None = None _last_signal_payload: dict | None = None
_last_signal_started_at: float | None = None _last_signal_started_at: float | None = None
_last_logged_market_state: str | None = None
_last_logged_market_trend: str | None = None
_last_logged_market_volatility: str | None = None
_same_signal_count = 0 _same_signal_count = 0
# debug: принудительно выставить сигнал и decision # debug: принудительно выставить сигнал и decision
@@ -649,12 +652,115 @@ class AutoTradeService:
if not isinstance(payload, dict): if not isinstance(payload, dict):
return return
previous_market_state = state.market_state
previous_market_trend = state.market_trend
previous_market_volatility = state.market_volatility
state.market_state = payload.get("market_state") state.market_state = payload.get("market_state")
state.market_trend = payload.get("market_trend") state.market_trend = payload.get("market_trend")
state.market_volatility = payload.get("market_volatility") state.market_volatility = payload.get("market_volatility")
state.market_analysis_interval = payload.get("market_analysis_interval") state.market_analysis_interval = payload.get("market_analysis_interval")
state.market_analysis_reason = payload.get("market_analysis_reason") state.market_analysis_reason = payload.get("market_analysis_reason")
self._log_market_state_if_changed(
state=state,
payload=payload,
previous_market_state=previous_market_state,
previous_market_trend=previous_market_trend,
previous_market_volatility=previous_market_volatility,
)
def _log_market_state_if_changed(
self,
*,
state: AutoTradeState,
payload: dict,
previous_market_state: str | None,
previous_market_trend: str | None,
previous_market_volatility: str | None,
) -> None:
market_state = state.market_state
market_trend = state.market_trend
market_volatility = state.market_volatility
if not market_state or market_state == "UNKNOWN":
return
state_changed = (
market_state != previous_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 = (
market_volatility is not None
and market_volatility != previous_market_volatility
and market_volatility != type(self)._last_logged_market_volatility
)
if not state_changed and not trend_changed and not volatility_changed:
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 = {
**payload,
"previous_market_state": previous_market_state,
"previous_market_trend": previous_market_trend,
"previous_market_volatility": previous_market_volatility,
"current_market_state": market_state,
"current_market_trend": market_trend,
"current_market_volatility": market_volatility,
}
try:
if level == "WARNING":
JournalService().log_ui_warning(
event_type="market_state_changed",
message=message,
screen="auto",
action="market_analysis",
payload=journal_payload,
)
return
JournalService().log_ui_info(
event_type="market_state_changed",
message=message,
screen="auto",
action="market_analysis",
payload=journal_payload,
)
except Exception:
pass
def _market_journal_level(self, market_state: str) -> str:
if market_state == "HIGH_VOLATILITY":
return "WARNING"
return "INFO"
def _market_state_message(self, market_state: str) -> str:
messages = {
"TREND_UP": "📈 Рынок перешёл в рост.",
"TREND_DOWN": "📉 Рынок перешёл в снижение.",
"RANGE": "🟰 На рынке нет выраженного направления.",
"HIGH_VOLATILITY": "⚠️ Рынок стал слишком волатильным.",
"LOW_VOLATILITY": "💤 Рынок почти не движется.",
}
return messages.get(market_state, "⏳ Состояние рынка анализируется.")
def run_cycle(self) -> AutoTradeState: def run_cycle(self) -> AutoTradeState:
state = self.get_state() state = self.get_state()

View File

@@ -4,6 +4,7 @@ from __future__ import annotations
import csv import csv
import json import json
import re
from datetime import datetime from datetime import datetime
from io import BytesIO, StringIO from io import BytesIO, StringIO
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
@@ -38,6 +39,22 @@ EVENT_TITLES = {
} }
_EMOJI_RE = re.compile(
"["
"\U0001F300-\U0001FAFF"
"\U00002700-\U000027BF"
"\U00002600-\U000026FF"
"\U0001F1E6-\U0001F1FF"
"]+",
flags=re.UNICODE,
)
def _strip_emoji(value: object) -> str:
text = str(value or "")
return _EMOJI_RE.sub("", text).strip()
def _now_local() -> datetime: def _now_local() -> datetime:
settings = load_settings() settings = load_settings()
try: try:
@@ -76,7 +93,9 @@ def _payload(row: dict) -> dict:
def _payload_json(payload: dict) -> str: def _payload_json(payload: dict) -> str:
if not payload: if not payload:
return "" return ""
return json.dumps(payload, ensure_ascii=False, sort_keys=True)
text = json.dumps(payload, ensure_ascii=False, sort_keys=True)
return _strip_emoji(text)
def _export_row(row: dict) -> list[str]: def _export_row(row: dict) -> list[str]:
@@ -84,15 +103,15 @@ def _export_row(row: dict) -> list[str]:
return [ return [
_format_datetime(row.get("created_at")), _format_datetime(row.get("created_at")),
str(row.get("level") or ""), _strip_emoji(row.get("level")),
str(row.get("event_type") or ""), _strip_emoji(row.get("event_type")),
_event_title(row.get("event_type")), _strip_emoji(_event_title(row.get("event_type"))),
str(row.get("message") or ""), _strip_emoji(row.get("message")),
str(payload.get("account_mode") or "").upper(), _strip_emoji(str(payload.get("account_mode") or "").upper()),
str(payload.get("screen") or ""), _strip_emoji(payload.get("screen")),
str(payload.get("action") or ""), _strip_emoji(payload.get("action")),
str(payload.get("error_type") or ""), _strip_emoji(payload.get("error_type")),
str(payload.get("raw_error") or ""), _strip_emoji(payload.get("raw_error")),
_payload_json(payload), _payload_json(payload),
] ]

View File

@@ -204,19 +204,19 @@ class MarketAnalysisService:
rsi_text = f", RSI={rsi_value:.2f}" if rsi_value is not None else "" rsi_text = f", RSI={rsi_value:.2f}" if rsi_value is not None else ""
if state == MarketState.TREND_UP: if state == MarketState.TREND_UP:
return f"Рынок в восходящем тренде. ATR={atr_percent:.2f}%{rsi_text}." return f"Рынок перешёл в рост. ATR={atr_percent:.2f}%{rsi_text}."
if state == MarketState.TREND_DOWN: if state == MarketState.TREND_DOWN:
return f"Рынок в нисходящем тренде. ATR={atr_percent:.2f}%{rsi_text}." return f"Рынок перешёл в снижение. ATR={atr_percent:.2f}%{rsi_text}."
if state == MarketState.RANGE: if state == MarketState.RANGE:
return f"Рынок в боковике. Тренд не подтверждён. ATR={atr_percent:.2f}%{rsi_text}." return f"На рынке нет выраженного направления. ATR={atr_percent:.2f}%{rsi_text}."
if state == MarketState.HIGH_VOLATILITY: if state == MarketState.HIGH_VOLATILITY:
return f"Рынок слишком волатильный. ATR={atr_percent:.2f}%{rsi_text}." return f"Рынок стал слишком волатильным. ATR={atr_percent:.2f}%{rsi_text}."
if state == MarketState.LOW_VOLATILITY: if state == MarketState.LOW_VOLATILITY:
return f"Рынок слишком спокойный. ATR={atr_percent:.2f}%{rsi_text}." return f"Рынок почти не движется. ATR={atr_percent:.2f}%{rsi_text}."
return f"Состояние рынка не определено. Trend={trend}, volatility={volatility}." return f"Состояние рынка не определено. Trend={trend}, volatility={volatility}."

View File

@@ -396,6 +396,10 @@
- централизован EVENT_TITLES mapping - централизован EVENT_TITLES mapping
- журнал подготовлен к filters/search layer - журнал подготовлен к filters/search layer
---
### 07.4.4
#### 07.4.4.1.1 ✅ Market State Human UI + HOLD Lifecycle Fix #### 07.4.4.1.1 ✅ Market State Human UI + HOLD Lifecycle Fix
- добавлено короткое human-readable отображение состояния рынка - добавлено короткое human-readable отображение состояния рынка
- технические market_state значения скрыты из основного Auto UI - технические market_state значения скрыты из основного Auto UI
@@ -413,8 +417,24 @@
- HOLD summary теперь пишется только при реальной смене сигнала - HOLD summary теперь пишется только при реальной смене сигнала
- этап подготовил основу для Market State Journal Events и BTC/ETH Relative Strength Layer - этап подготовил основу для Market State Journal Events и BTC/ETH Relative Strength Layer
### 07.4.4 #### 07.4.4.1.2 ✅ Market State Journal Events
⏳ Grid Strategy - добавлено journal logging изменений состояния рынка
- реализован market-state transition tracking
- добавлены market_state_changed события
- добавлены market_trend_changed события
- добавлены market_volatility_changed события
- market-analysis интегрирован в auto runtime
- устранён spam logging market-analysis циклов
- реализовано logging только при реальной смене состояния
- добавлены human-readable market messages
- убраны raw enum/state значения из UI-журнала
- журнал переведён на explainable market-analysis стиль
- добавлена фиксация отсутствия выраженного направления рынка
- подготовлена база для market analytics layer
- подготовлена база для future AI market commentary
- журнал подготовлен к market filters/search layer
---
### 07.4.5 ### 07.4.5
⏳ Scalping Strategy ⏳ Scalping Strategy

View File

@@ -372,6 +372,10 @@
- централизован EVENT_TITLES mapping - централизован EVENT_TITLES mapping
- журнал подготовлен к filters/search layer - журнал подготовлен к filters/search layer
---
### 07.4.4
#### 07.4.4.1.1 ✅ Market State Human UI + HOLD Lifecycle Fix #### 07.4.4.1.1 ✅ Market State Human UI + HOLD Lifecycle Fix
- добавлено короткое human-readable отображение состояния рынка - добавлено короткое human-readable отображение состояния рынка
- технические market_state значения скрыты из основного Auto UI - технические market_state значения скрыты из основного Auto UI
@@ -389,10 +393,24 @@
- HOLD summary теперь пишется только при реальной смене сигнала - HOLD summary теперь пишется только при реальной смене сигнала
- этап подготовил основу для Market State Journal Events и BTC/ETH Relative Strength Layer - этап подготовил основу для Market State Journal Events и BTC/ETH Relative Strength Layer
--- #### 07.4.4.1.2 ✅ Market State Journal Events
- добавлено journal logging изменений состояния рынка
- реализован market-state transition tracking
- добавлены market_state_changed события
- добавлены market_trend_changed события
- добавлены market_volatility_changed события
- market-analysis интегрирован в auto runtime
- устранён spam logging market-analysis циклов
- реализовано logging только при реальной смене состояния
- добавлены human-readable market messages
- убраны raw enum/state значения из UI-журнала
- журнал переведён на explainable market-analysis стиль
- добавлена фиксация отсутствия выраженного направления рынка
- подготовлена база для market analytics layer
- подготовлена база для future AI market commentary
- журнал подготовлен к market filters/search layer
### 07.4.4 ---
⏳ Grid strategy
### 07.4.5 ### 07.4.5
⏳ Scalping strategy ⏳ Scalping strategy

View File

@@ -0,0 +1,198 @@
# 07.4.4.1.2 — Market State Journal Events
## Цель этапа
Добавить полноценное журналирование состояния рынка и результатов market-analysis слоя, чтобы автоторговля фиксировала не только BUY / SELL / HOLD сигналы, но и изменения самого состояния рынка:
- тренд вверх
- тренд вниз
- отсутствие выраженного направления
- повышенная волатильность
- пониженная волатильность
- неизвестное состояние
Этап подготовил инфраструктуру для дальнейшей аналитики execution layer, risk layer и explainable trading logic.
---
# Что было реализовано
## 1. Добавлен journal-layer для Market Analysis
В систему внедрено отдельное журналирование market-analysis событий.
Теперь журнал фиксирует:
- смену состояния рынка
- смену тренда
- изменение волатильности
- переход рынка в режим без выраженного направления
- возврат рынка в тренд
- переход рынка в высокую волатильность
- переход рынка в низкую волатильность
---
# 2. Реализовано отслеживание изменений market state
Добавлен state-tracking между циклами анализа рынка.
Теперь система сравнивает:
- прошлое состояние рынка
- текущее состояние рынка
и пишет событие только при реальном изменении состояния.
Это устранило spam logging на каждом цикле автоторговли.
---
# 3. Добавлены отдельные event_type для аналитики рынка
В журнал внедрены новые event_type:
- market_state_changed
- market_trend_changed
- market_volatility_changed
Это подготовило журнал к:
- filters/search layer
- аналитике поведения рынка
- future BI/export
- explainable AI logging
---
# 4. Реализованы human-readable market messages
Технические market-state значения были преобразованы в понятные сообщения.
Вместо:
- TREND_UP
- TREND_DOWN
- RANGE
пользователь теперь видит:
- «Рынок перешёл в рост»
- «Рынок перешёл в снижение»
- «На рынке нет выраженного направления»
---
# 5. Удалён технический стиль market-analysis сообщений
Из journal UI убраны:
- raw enum values
- технические обозначения state
- служебные market constants
Журнал стал ориентирован на пользователя, а не на внутренние enum системы.
---
# 6. Market analysis интегрирован в auto runtime
Market-analysis теперь стал полноценной частью runtime автоторговли.
События рынка начали синхронизироваться с:
- auto runtime
- signal runtime
- execution runtime
- monitoring runtime
---
# 7. Улучшен explainability layer
Теперь journal способен объяснять:
- почему стратегия вошла в HOLD
- почему execution заблокирован
- почему рынок считается опасным
- почему направление не подтверждено
Это критически важно для:
- debugging
- future AI-assistant layer
- user trust
- explainable autotrading
---
# 8. Подготовлена основа для future analytics
Этап подготовил систему к следующим задачам:
- market heatmaps
- market statistics
- market transition analytics
- trend persistence analysis
- volatility tracking
- AI market commentary
- advanced journal filters
---
# Изменения в архитектуре
## Market Analysis Layer
Расширены:
- MarketAnalysisService
- MarketAnalysisResult
- market-state tracking logic
---
## Auto Runtime Layer
Добавлено:
- сохранение предыдущего market state
- сравнение market transitions
- event emission при изменении рынка
---
## Journal Layer
Добавлены:
- market-analysis event_type
- human-readable market messages
- runtime-aware market events
- unified UI logging
---
# Что изменилось для пользователя
Пользователь начал видеть в журнале:
- реальные изменения рынка
- понятные описания состояния
- объяснение поведения стратегии
- причину HOLD-сигналов
Вместо технического spam logging журнал стал выполнять роль explainable trading feed.
---
# Что подготовлено дальше
Этап подготовил основу для:
- 07.4.4.1.3 — Market Transition Analytics
- volatility persistence tracking
- trend strength scoring
- market regime detection
- AI commentary layer
- unified monitoring analytics