# app/src/trading/diagnostics/formatter.py

from __future__ import annotations

from typing import Any

from src.core.numbers import safe_float
from src.core.types import JsonDict, NumericLike
from src.integrations.exchange.runtime_ui import format_runtime_exchange_alert


class SemanticDiagnosticFormatter:
    def format(self, snapshot: JsonDict) -> str:
        status = snapshot.get("status", {})
        signal = snapshot.get("signal", {})
        market = snapshot.get("market", {})
        execution = snapshot.get("execution", {})
        runtime = snapshot.get("runtime_health", {})
        exchange_statuses = runtime.get("exchange_statuses") or []
        exchange_status = runtime.get("exchange_status")

        if not exchange_statuses and exchange_status:
            exchange_statuses = [exchange_status]

        position = snapshot.get("position", {})
        has_position = self._has_position(position)

        if str(status.get("status") or "").upper() == "OFF":
            return self._diagnostics_title(status)

        header_lines = [self._diagnostics_title(status)]

        live_warning = self._live_stream_warning(runtime)
        if live_warning:
            header_lines.append(live_warning)

        sections = ["\n".join(header_lines)]

        for item in exchange_statuses:
            sections.append(self._runtime_exchange_block(item))

        if has_position:
            # При открытой позиции сначала показываем саму позицию:
            # сторона, вход, текущая цена, размер, объём, SL/TP.
            position_block = self._position_block(position)
            if position_block:
                sections.append(position_block)

            # Затем показываем здоровье позиции.
            position_health_block = self._position_health_block(position)
            if position_health_block:
                sections.append(position_health_block)

            sections.append(self._market_block(market))
        else:
            sections.extend([
                self._execution_block(execution, market=market),
                self._signal_block(signal, market=market),
                self._market_block(market),
            ])

        return "\n\n".join(
            section.strip()
            for section in sections
            if section and section.strip()
        ).strip()

    def _diagnostics_title(self, status: JsonDict) -> str:
        symbol = self._asset_symbol(status.get("symbol"))
        strategy = self._strategy_title(status.get("strategy"))

        if strategy == "—":
            return f"📊 Анализ рынка · <b>{symbol}</b>"

        return f"📊 Анализ рынка · <b>{symbol}</b> · {strategy}"

    def build_notification_reason_lines(self, snapshot: JsonDict, *, limit: int = 2) -> list[str]:
        signal = snapshot.get("signal", {})
        market = snapshot.get("market", {})
        summary = snapshot.get("summary", {})

        lines: list[str] = []

        def add_many(text: str) -> None:
            for line in str(text or "").splitlines():
                cleaned = line.strip().rstrip(".")
                if cleaned and cleaned not in lines:
                    lines.append(cleaned)

        add_many(self._signal_explanation(signal))

        has_breakout = any(
            "Пробой вверх" in line or "Пробой вниз" in line
            for line in lines
        )

        if len(lines) < limit and not has_breakout:
            add_many(self._market_explanation(market))

        if len(lines) < limit:
            blockers = summary.get("blockers") or []
            for blocker in blockers:
                add_many(self._short_reason(str(blocker)))

        return lines[:limit]

    def _headline_block(
        self,
        data: JsonDict,
        status: JsonDict,
        position: JsonDict | None = None,
        *,
        market: JsonDict | None = None,
        momentum: JsonDict | None = None,
    ) -> str:
        severity = data.get("severity")
        assessment = data.get("assessment") or self._human(severity)
        headline_mode = str(data.get("headline_mode") or "ENTRY")

        if headline_mode == "POSITION":
            return self._position_headline(
                status=status,
                position=position or {},
            )

        return self._entry_headline(
            data=data,
            status=status,
            assessment=assessment,
            market=market or {},
            momentum=momentum or {},
        )

    def _entry_headline(
        self,
        *,
        data: JsonDict,
        status: JsonDict,
        assessment: str,
        market: JsonDict | None = None,
        momentum: JsonDict | None = None,
    ) -> str:
        blockers = data.get("blockers") or []

        lines = [
            self._diagnostics_title(status),
            self._headline_status_line(
                severity=data.get("severity"),
                assessment=assessment,
                data=data,
            ),
        ]

        reasons = self._headline_reasons(
            blockers=blockers,
            summary=data,
            market=market or {},
            momentum=momentum or {},
        )

        if not reasons:
            reasons = ["Ограничений нет"]

        lines.extend(reasons[:2])

        return "\n".join(lines)

    def _headline_status_line(
        self,
        *,
        severity: object,
        assessment: str,
        data: JsonDict | None = None,
    ) -> str:
        text = str(severity or "")
        data = data or {}

        if text == "RED":
            return "⛔️ Вход заблокирован"

        if text == "WAITING":
            return self._waiting_headline_status(data)

        if text == "YELLOW":
            execution = str(data.get("execution") or "")

            if execution == "WAITING_SIGNAL":
                return "🟡 Сигнал проверяется"

            return "🟡 Осторожно"

        if text == "GREEN":
            return "🟢 Готово"

        return f"{self._severity_icon(severity)} {assessment.capitalize()}"
    
    def _waiting_headline_status(self, data: JsonDict) -> str:
        execution = str(data.get("execution") or "")
        blockers_text = self._blockers_text(data)

        if execution == "WAITING_SIGNAL":
            return "🟡 Сигнал проверяется"

        if (
            "подтвержд" in blockers_text
            or "сигнал проверяется" in blockers_text
            or "идёт подтверждение" in blockers_text
        ):
            return "🟡 Сигнал проверяется"

        if (
            "бирж" in blockers_text
            or "перерыв" in blockers_text
            or "рынок закрыт" in blockers_text
            or "торги временно" in blockers_text
        ):
            return "⛔️ Вход заблокирован"

        return "🟡 Ожидание сигнала"
    
    def _blockers_text(self, data: JsonDict) -> str:
        blockers = data.get("blockers") or []

        return " ".join(
            str(item).strip().lower()
            for item in blockers
            if str(item).strip()
        )
    
    def _headline_reasons(
        self,
        *,
        blockers: list[object],
        summary: JsonDict,
        market: JsonDict,
        momentum: JsonDict,
    ) -> list[str]:
        reasons: list[str] = []

        def add(text: str) -> None:
            normalized = text.strip().rstrip(".")
            if normalized and normalized not in reasons:
                reasons.append(normalized)

        market_state = str(market.get("state") or summary.get("market") or "").upper()
        market_phase = str(market.get("phase") or summary.get("phase") or "").upper()
        market_quality = str(market.get("trend_quality") or "").upper()
        market_strength = str(market.get("trend_strength") or "").upper()
        market_volatility = str(market.get("volatility") or "").upper()
        market_open = market.get("market_is_open")

        momentum_state = str(momentum.get("state") or summary.get("momentum") or "").upper()
        momentum_direction = str(momentum.get("direction") or "").upper()
        momentum_strength = safe_float(momentum.get("strength"))

        execution = str(
            summary.get("execution")
            or summary.get("semantic_status")
            or ""
        ).upper()

        if market_open is False:
            add(str(market.get("market_status_message") or "Рынок закрыт"))
            return reasons[:2]

        if execution == "READY":
            add("Условия входа готовы")

        if market_state == "HIGH_VOLATILITY" or market_volatility in {"HIGH", "HIGH_VOLATILITY"}:
            add("Рынок перегрет")

        elif market_state == "LOW_VOLATILITY" or market_volatility in {"LOW", "LOW_VOLATILITY"}:
            add("Движения мало")

        elif market_state == "RANGE" or market_phase == "RANGE":
            add("Рынок во флэте")

        elif market_phase == "PULLBACK":
            add("Рынок в откате")

        elif market_phase == "SQUEEZE":
            add("Рынок в сжатии")

        elif market_quality == "NOISY":
            add("Рынок шумный")

        elif market_strength == "WEAK":
            add("Тренд слабый")

        elif market_state == "TREND_UP":
            add("Рынок растёт")

        elif market_state == "TREND_DOWN":
            add("Рынок снижается")

        if (
            momentum_state in {"BREAKOUT_UP", "BREAKOUT_DOWN"}
            and market_state not in {"RANGE"}
            and market_phase not in {"RANGE", "PULLBACK", "SQUEEZE"}
            and market_quality != "NOISY"
        ):
            add("Пробой вверх" if momentum_state == "BREAKOUT_UP" else "Пробой вниз")

        elif (
            momentum_state in {"MOMENTUM_UP", "MOMENTUM_DOWN"}
            and market_strength != "WEAK"
            and market_quality != "NOISY"
            and market_phase not in {"RANGE", "SQUEEZE"}
        ):
            add("Импульс вверх" if momentum_state == "MOMENTUM_UP" else "Импульс вниз")

        elif (
            momentum_state in {"NONE", "NO_SIGNIFICANT_MOMENTUM"}
            or momentum_direction in {"FLAT", "РОВНО"}
        ):
            if momentum_strength is not None and momentum_strength >= 1.0:
                add("Импульс затухает")
            else:
                add("Сильного импульса нет")

        generic_blockers = {
            "market_filter_blocked",
            "рынок не готов",
            "рынок не подходит",
            "рынок не подходит для входа",
        }

        for blocker in blockers:
            raw = str(blocker).strip()
            normalized = raw.lower()

            if not raw or normalized in generic_blockers:
                continue

            text = self._short_reason(raw).strip()

            if not text or text.lower() in generic_blockers:
                continue

            add(text[:1].upper() + text[1:])

            if len(reasons) >= 2:
                break

        return reasons[:2]
    
    def _position_headline(
        self,
        *,
        status: JsonDict,
        position: JsonDict,
    ) -> str:
        pnl_value = safe_float(position.get("unrealized_pnl_usd")) or 0.0

        icon = "🟢" if pnl_value >= 0 else "🔴"
        sign = "+" if pnl_value >= 0 else "−"

        return "\n".join([
            self._diagnostics_title(status),
            f"Позиция {icon} {sign}$ {self._money(abs(pnl_value))}",
        ])

    def _analytics_block(self, summary: JsonDict, runtime: JsonDict, execution: JsonDict) -> str:
        market_age = runtime.get("market_age_seconds")
        signal_age = runtime.get("signal_age_seconds")

        market_state = self._market_live_state(market_age)
        execution_state = self._execution_data_state(
            execution.get("snapshot_age_seconds")
        )

        if market_state == "live" and execution_state == "live":
            icon = "🟢"
        elif market_state == "устарели" or execution_state == "устарели":
            icon = "⛔️"
        else:
            icon = "🟡"

        lines = [
            f"{icon} Runtime",
            f"• Рынок: {market_state}",
            f"• Стакан: {execution_state}",
            f"• Сигнал: {self._duration(signal_age)}",
        ]

        expired_reason = runtime.get("runtime_expired_reason")
        if expired_reason:
            lines.append(self._human(expired_reason))

        return "\n".join(lines)

    def _execution_block(
        self,
        data: JsonDict,
        *,
        market: JsonDict | None = None,
    ) -> str:
        market = market or {}

        quality = str(data.get("quality") or "")
        reason = str(
            data.get("quality_reason")
            or data.get("semantic_reason")
            or ""
        )

        lines = [
            self._entry_conditions_title(
                quality=quality,
                semantic_status=data.get("semantic_status"),
                reason=reason,
            )
        ]

        lines.append(
            "• Данные: "
            f"{self._execution_data_state(data.get('snapshot_age_seconds'))}"
        )

        spread_line = self._spread_status_line(
            reason=reason,
            spread_percent=data.get("spread_percent"),
        )

        if spread_line:
            lines.append(spread_line)

        timing_line = self._entry_timing_line(
            execution=data,
            market=market,
        )

        if timing_line:
            lines.append(timing_line)

        return "\n".join(lines)

    def _entry_timing_line(
        self,
        *,
        execution: JsonDict,
        market: JsonDict,
    ) -> str:
        state = str(
            market.get("entry_timing_state")
            or market.get("entry_timing")
            or ""
        ).upper()

        label_map = {
            "EARLY": "ранний",
            "NORMAL": "нормальный",
            "LATE": "поздний",
            "CHASING": "погоня за движением",
            "UNKNOWN": "неясный",
        }

        label = label_map.get(state)

        if not label:
            semantic_status = str(execution.get("semantic_status") or "").upper()
            quality = str(execution.get("quality") or "").upper()

            if semantic_status == "READY":
                label = "готов"
            elif quality == "BLOCKED":
                label = "плохой"
            elif quality == "WARNING":
                label = "рискованный"
            elif semantic_status in {"IDLE", "WAITING_SIGNAL"}:
                label = "подтверждение"
            else:
                label = "ожидание"

        return f"• Момент входа: {label}"

    def _entry_conditions_title(
        self,
        *,
        quality: str,
        semantic_status: str | None = None,
        reason: str | None = None,
    ) -> str:
        reason_text = str(reason or "").upper()

        if semantic_status == "POSITION_OPEN":
            return "🟢 Условия входа · выполнен"

        if semantic_status == "READY":
            return "🟢 Условия входа · готовы"

        if reason_text == "MARKET_CLOSED":
            return "⛔️ Биржа · перерыв"

        if semantic_status in {
            "IDLE",
            "WAITING_SIGNAL",
        }:
            return "🟡 Условия входа · подтверждение"

        if quality == "BLOCKED":
            return "⛔️ Условия входа · заблокированы"

        if quality == "WARNING":
            return "🟡 Условия входа · риск"

        if quality == "GOOD":
            return "🟢 Условия входа · нормальные"

        return "🟡 Условия входа · ожидание"

    def _spread_status_line(
        self,
        *,
        reason: str,
        spread_percent: NumericLike | None,
    ) -> str:
        if reason == "HIGH_SPREAD":
            return "• Спред: блокирует вход"

        if reason == "WIDE_SPREAD":
            return "• Спред: повышен"

        if spread_percent is None:
            return ""

        spread = safe_float(spread_percent)

        if spread is None:
            return ""

        if spread < 0.1:
            return "• Спред: низкий"

        return f"• Спред: {spread:.3f}%"

    def _execution_data_state(self, value: NumericLike | None) -> str:
        seconds_float = safe_float(value)

        if seconds_float is None:
            return "нет данных"

        seconds = int(seconds_float)

        if seconds <= 2:
            return "Live-поток"

        if seconds <= 10:
            return f"задержка {seconds}с"

        return "устарели"

    def _signal_block(
        self,
        data: JsonDict,
        *,
        market: JsonDict | None = None,
    ) -> str:
        market = market or {}

        signal = str(data.get("signal") or "").upper()

        progress = safe_float(data.get("confirmation_progress")) or 0.0

        title = self._signal_status_title(
            signal=signal,
            progress=progress,
        )

        lines = [title]

        direction_line = self._signal_direction_line(
            signal=signal,
            market=market,
        )

        if direction_line:
            lines.append(direction_line)

        readiness_line = self._signal_readiness_line(data)

        if readiness_line:
            lines.append(readiness_line)

        lines.append(
            f"• Длительность: {self._duration(data.get('age_seconds'))}"
        )

        return "\n".join(lines)

    def _signal_direction_line(
        self,
        *,
        signal: str,
        market: JsonDict,
    ) -> str:
        long_score = safe_float(market.get("market_long_score"))
        short_score = safe_float(market.get("market_short_score"))

        if signal == "BUY":
            return "• Направление: Long"

        if signal == "SELL":
            return "• Направление: Short"

        if long_score is None and short_score is None:
            return ""

        if short_score is not None and (long_score is None or short_score > long_score):
            return "• Потенциал: Short"

        return "• Потенциал: Long"

    def _signal_readiness_line(self, data: JsonDict) -> str:
        signal = str(data.get("signal") or "").upper()

        if signal not in {"BUY", "SELL"}:
            return ""

        progress = safe_float(data.get("confirmation_progress"))

        if progress is None:
            return ""

        return f"• Готовность: {self._score_percent(progress)}"

    def _score_percent(self, value: NumericLike | None) -> str:
        number = safe_float(value)

        if number is None:
            return ""

        if 0 <= number <= 1:
            number *= 100

        return f"{number:.0f}%"

    def _signal_status_title(
        self,
        *,
        signal: str,
        progress: float,
    ) -> str:
        if signal == "BUY":
            if progress >= 1.0:
                return "🟢 Сигнал · Long"

            return "🟡 Сигнал · подтверждение"

        if signal == "SELL":
            if progress >= 1.0:
                return "🔴 Сигнал · Short"

            return "🟡 Сигнал · подтверждение"

        return "🟡 Сигнал · ожидание"
    
    def _momentum_title(
        self,
        *,
        momentum_state: str,
        strength: NumericLike | None = None,
    ) -> str:
        try:
            strength_value = float(strength or 0.0)
        except Exception:
            strength_value = 0.0

        if momentum_state == "BREAKOUT_UP":
            return "🚀 Пробой вверх"

        if momentum_state == "BREAKOUT_DOWN":
            return "🚀 Пробой вниз"

        if momentum_state == "MOMENTUM_UP":
            return "⚡ Импульс вверх"

        if momentum_state == "MOMENTUM_DOWN":
            return "⚡ Импульс вниз"

        if momentum_state == "EXHAUSTED":
            return "⚠️ Импульс затухает"

        if momentum_state in {"NONE", "NO_SIGNIFICANT_MOMENTUM"}:
            if strength_value >= 1.0:
                return "⚠️ Импульс затухает"

            return "🟡 Импульса нет"

        return "🟡 Импульс анализируется"

    def _position_side_title(self, side: str) -> str:
        if side == "LONG":
            return "Long"

        if side == "SHORT":
            return "Short"

        return side
    
    def _signal_explanation(self, data: JsonDict) -> str:
        signal = str(data.get("signal") or "").upper()
        reason = str(data.get("reason") or "").strip()
        reason_upper = reason.upper()
        progress = data.get("confirmation_progress")

        reasons: list[str] = []

        def add(text: str) -> None:
            if text not in reasons:
                reasons.append(text)

        if not reason:
            if signal == "BUY":
                return "Есть сигнал вверх."
            if signal == "SELL":
                return "Есть сигнал вниз."
            if signal == "HOLD":
                return "Точки входа нет."
            return "Сигнал не определён."

        # 1. Критичные причины
        if "NOT_ENOUGH_LIVE_DATA" in reason_upper or "МАЛО" in reason_upper:
            add("Недостаточно live-данных")

        if "SIGNAL_TTL_EXPIRED" in reason_upper or "УСТАР" in reason_upper:
            add("Сигнал устарел")

        if "MARKET_ANALYSIS_TTL_EXPIRED" in reason_upper:
            add("Анализ рынка устарел")

        if "HIGH_SPREAD" in reason_upper or "СПРЕД" in reason_upper:
            add("Спред мешает входу")

        if "MARKET_FILTER_BLOCKED" in reason_upper:
            add("Рынок не готов")

        if "MARKET_STRUCTURE_CONFLICT" in reason_upper or "СТРУКТУРА РЫНКА ПРОТИВ" in reason_upper:
            add("Структура против входа")

        if "MARKET_STRUCTURE_MIXED" in reason_upper or "СТРУКТУРА РЫНКА СМЕШАН" in reason_upper:
            add("Структура не подтверждает вход")

        if "MOMENTUM_NOT_CONFIRMED" in reason_upper:
            if signal == "BUY":
                add("Нет уверенного движения вверх")
            elif signal == "SELL":
                add("Нет уверенного движения вниз")
            else:
                add("Движение не подтверждает вход")
                
        # 2. Рыночный контекст
        if (
            "WEAK_MARKET_TREND" in reason_upper
            or ("СЛАБ" in reason_upper and "ТРЕНД" in reason_upper)
        ):
            add("Тренд слабый")

        if "NOISY_MARKET_TREND" in reason_upper or "ШУМ" in reason_upper:
            add("Рынок шумный")

        if "MARKET_PULLBACK" in reason_upper or "ОТКАТ" in reason_upper:
            add("Рынок в откате")

        if "RANGE" in reason_upper or "ФЛЭТ" in reason_upper:
            add("Нет понятного направления")

        if "SQUEEZE" in reason_upper or "СЖАТ" in reason_upper:
            add("Рынок в сжатии")

        # 3. Импульс
        if (
            "WEAK_UP_IMPULSE" in reason_upper
            or "LIVE-ИМПУЛЬС ВВЕРХ НЕДОСТАТОЧНО" in reason_upper
            or ("ИМПУЛЬС ВВЕРХ" in reason_upper and "СЛАБ" in reason_upper)
        ):
            add("Импульс вверх слабый")

        if (
            "WEAK_DOWN_IMPULSE" in reason_upper
            or "LIVE-ИМПУЛЬС ВНИЗ НЕДОСТАТОЧНО" in reason_upper
            or ("ИМПУЛЬС ВНИЗ" in reason_upper and "СЛАБ" in reason_upper)
        ):
            add("Импульс вниз слабый")

        if "NO_SIGNIFICANT_MOMENTUM" in reason_upper:
            add("Сильного импульса нет")

        if "BREAKOUT_UP" in reason_upper or "ПРОБОЙ ВВЕРХ" in reason_upper:
            add("Пробой вверх")

        if "BREAKOUT_DOWN" in reason_upper or "ПРОБОЙ ВНИЗ" in reason_upper:
            add("Пробой вниз")

        if "FAST_UP_MOVE" in reason_upper or "БЫСТРЫЙ РОСТ" in reason_upper:
            add("Быстрый рост")

        if "FAST_DOWN_MOVE" in reason_upper or "БЫСТРОЕ СНИЖЕНИЕ" in reason_upper:
            add("Быстрое снижение")

        # 4. Тренд
        if "TREND_UP" in reason_upper:
            if signal == "BUY":
                add("Тренд вверх подтверждает покупку")
            elif signal == "SELL":
                add("Рост против продажи")
            else:
                add("Рост есть, вход не подтверждён")

        if "TREND_DOWN" in reason_upper:
            if signal == "SELL":
                add("Тренд вниз подтверждает продажу")
            elif signal == "BUY":
                add("Снижение против покупки")
            else:
                add("Снижение есть, вход не подтверждён")

        # 5. Fallback по состоянию сигнала
        if not reasons:
            if signal in {"BUY", "SELL"}:
                try:
                    progress_value = float(progress or 0.0)
                except Exception:
                    progress_value = 0.0

                if progress_value >= 1.0:
                    add("Сигнал подтверждён")
                elif progress_value > 0:
                    add("Сигнал подтверждается")
                elif signal == "BUY":
                    add("Есть сигнал вверх")
                else:
                    add("Есть сигнал вниз")

            elif signal == "HOLD":
                add("Точки входа нет")
            else:
                add(self._human(reason))

        return "\n".join(reasons[:2])
    
    def _market_block(self, data: JsonDict) -> str:
        market_closed = data.get("market_is_open") is False

        lines = [
            (
                f"{self._market_icon(data)} "
                f"Рынок · "
                f"{self._market_title(data)}"
            ),
        ]

        directional_lines = self._directional_market_score_lines(data)
        if directional_lines:
            lines.extend(directional_lines)

        if market_closed:
            lines.append("• Биржа · перерыв")
            lines.append(
                str(
                    data.get("market_status_message")
                    or "Торги временно остановлены"
                )
            )
            return "\n".join(lines)

        # Метрики рынка выводим по важности:
        # 1. старший тренд;
        # 2. рабочий тренд;
        # 3. последняя закрытая свеча;
        # 4. текущая свеча;
        # 5. структура;
        # 6. волатильность;
        # 7. качество движения.
        for line in [
            self._htf_context_line(data),
            self._market_trend_line(
                trend=data.get("trend"),
                strength=data.get("trend_strength"),
                quality=data.get("trend_quality"),
                volatility=data.get("volatility"),
                interval=data.get("interval") or data.get("current_interval_label") or "5m",
            ),
            self._last_closed_candle_line(data),
            self._current_candle_line(data),
            self._market_structure_line(data),
            self._market_volatility_line(data.get("volatility")),
            self._market_quality_line(
                data.get("trend_quality")
                or data.get("market_trend_quality")
            ),
        ]:
            if line:
                lines.append(line)

        return "\n".join(lines)

    def _market_structure_line(self, data: JsonDict) -> str:
        structure = str(
            data.get("market_structure")
            or data.get("structure")
            or ""
        )

        mapping = {
            "HH_HL": "• Структура: рост · HH/HL",
            "LH_LL": "• Структура: снижение · LH/LL",
            "MIXED": "• Структура: смешанная",
            "UNKNOWN": "",
            "": "",
        }

        return mapping.get(structure, "")

    def _market_title(self, data: JsonDict) -> str:
        if data.get("market_is_open") is False:
            return "перерыв"

        score = safe_float(data.get("market_score"))
        label = str(data.get("market_score_label") or "").strip()

        if score is not None:
            if not label:
                label = self._market_score_label(score)

            return f"{label.lower()} · {score:.0f}%"

        state = str(data.get("state") or "")
        phase = str(data.get("phase") or "")
        trend = str(data.get("trend") or "")
        quality = str(data.get("trend_quality") or "")
        strength = str(data.get("trend_strength") or "")

        # Fallback для старых snapshot или если market_score ещё не рассчитан.
        if state == "RANGE" or phase == "RANGE":
            return "флэт"

        if phase == "PULLBACK":
            return "откат"

        if quality == "NOISY":
            if trend == "UP":
                return "шумный рост"

            if trend == "DOWN":
                return "шумное снижение"

            return "шум"

        if strength == "WEAK":
            if trend == "UP":
                return "слабый рост"

            if trend == "DOWN":
                return "слабое снижение"

        if phase == "IMPULSE":
            if trend == "UP":
                return "рост"

            if trend == "DOWN":
                return "снижение"

            return "импульс"

        if trend == "UP":
            return "рост"

        if trend == "DOWN":
            return "снижение"

        return self._human(state)
    
    def _directional_market_score_lines(self, data: JsonDict) -> list[str]:
        long_score = safe_float(data.get("market_long_score"))
        short_score = safe_float(data.get("market_short_score"))

        if long_score is None and short_score is None:
            return []

        lines: list[str] = []

        long_is_best = (
            long_score is not None
            and (
                short_score is None
                or long_score >= short_score
            )
        )

        short_is_best = (
            short_score is not None
            and (
                long_score is None
                or short_score > long_score
            )
        )

        if long_score is not None:
            mark = " ✅" if long_is_best else ""
            lines.append(f"• Long: {long_score:.0f}%{mark}")

        if short_score is not None:
            mark = " ✅" if short_is_best else ""
            lines.append(f"• Short: {short_score:.0f}%{mark}")

        return lines

    def _market_score_label(self, score: NumericLike | None) -> str:
        # Общая оценка рынка:
        # 90-100 — отличный рынок
        # 75-89  — благоприятный
        # 55-74  — нейтральный
        # 35-54  — сложный
        # 0-34   — неблагоприятный
        value = safe_float(score)

        if value is None:
            return "оценка недоступна"

        if value >= 90:
            return "отличный"

        if value >= 75:
            return "благоприятный"

        if value >= 55:
            return "нейтральный"

        if value >= 35:
            return "сложный"

        return "неблагоприятный"

    def _market_trend_line(
        self,
        *,
        trend: object,
        strength: object,
        quality: object | None = None,
        volatility: object | None = None,
        interval: object | None = None,
    ) -> str:
        interval_text = str(interval or "5m")
        trend_text = self._human(trend)
        strength_text = self._human(strength)

        quality_text = str(quality or "").upper()
        volatility_text = str(volatility or "").upper()

        if trend_text in {"—", "нет", "неясно"}:
            return f"• Рабочий тренд ({interval_text}): неясно"

        if trend_text in {"ровно"}:
            return f"• Рабочий тренд ({interval_text}): флэт"
        
        if (
            quality_text == "NOISY"
            or volatility_text in {"HIGH", "HIGH_VOLATILITY"}
        ):
            return f"• Рабочий тренд ({interval_text}): {trend_text}"

        if strength_text in {"—", "нет", "неясно"}:
            return f"• Рабочий тренд ({interval_text}): {trend_text}"

        return f"• Рабочий тренд ({interval_text}): {trend_text} · {strength_text}"

    def _last_closed_candle_line(self, data: JsonDict) -> str:
        change = safe_float(data.get("last_closed_candle_change_percent"))
        direction = str(data.get("last_closed_candle_direction") or "").upper()
        interval_label = str(data.get("current_interval_label") or "5m").strip()

        if change is None:
            return ""

        if direction == "UP" or change > 0:
            arrow = "▲"
        elif direction == "DOWN" or change < 0:
            arrow = "▼"
        else:
            arrow = "→"

        return f"• Последняя свеча ({interval_label}): {arrow} {change:+.2f}%"

    def _current_candle_line(self, data: JsonDict) -> str:
        change = safe_float(data.get("current_interval_change_percent"))
        interval_label = str(data.get("current_interval_label") or "5m").strip()

        if change is None:
            return ""

        if change > 0:
            arrow = "▲"
        elif change < 0:
            arrow = "▼"
        else:
            arrow = "→"

        return f"• Текущая свеча ({interval_label}): {arrow} {change:+.2f}%"
    
    def _market_volatility_line(self, value: object) -> str:
        text = str(value or "")

        if text in {"HIGH", "HIGH_VOLATILITY"}:
            return "• Волатильность: высокая"

        if text in {"LOW", "LOW_VOLATILITY"}:
            return "• Волатильность: низкая"

        if text == "NORMAL":
            return "• Волатильность: нормальная"

        return ""

    def _market_quality_line(self, value: object) -> str:
        text = str(value or "")

        if text == "NOISY":
            return "• Качество: шум"

        if text == "NORMAL":
            return "• Качество: нормальное"

        if text == "CLEAN":
            return "• Качество: чистое"

        return ""
    
    def _momentum_block(self, data: JsonDict) -> str:
        momentum_state = str(data.get("state") or "")
        direction = self._human(data.get("direction"))

        has_live_data = data.get("has_live_data")
        change_percent = data.get("change_percent")
        strength = data.get("strength")

        if (
            has_live_data is False
            or change_percent is None
            or strength is None
        ):
            lines = [
                "⚪️ Импульс · нет данных",
                "• Недостаточно live-данных",
            ]

            reason = data.get("reason") or data.get("message")

            if reason:
                lines.append(
                    f"• {self._short_reason(str(reason))}"
                )

            return "\n".join(lines)

        lines = [
            self._momentum_title(
                momentum_state=momentum_state,
                strength=strength,
            ),
            f"• Направление: {direction}",
            f"• Пробой: {self._bool(data.get('is_breakout'))}",
        ]

        breakout_level = self._float(
            data.get("breakout_level")
        )
        if breakout_level != "—":
            formatted_breakout_level = self._price_value(
                breakout_level,
                is_usd=True,
            )

            lines.append(
                f"• Цель пробоя: {formatted_breakout_level}"
            )

        breakout_distance = self._percent_value(
            data.get("breakout_distance_percent")
        )
        if breakout_distance != "—":
            lines.append(
                f"• Дистанция: {breakout_distance}"
            )

        lines.extend([
            f"• Сила: {self._momentum_strength(strength, momentum_state=momentum_state, direction=data.get('direction'))}",
            f"• Движение: {self._percent_value(change_percent)}",
        ])

        return "\n".join(lines)

    def _has_position(self, data: JsonDict) -> bool:
        return str(data.get("side") or "NONE").upper() != "NONE"

    def _position_block(self, data: JsonDict) -> str:
        side = str(data.get("side") or "NONE").upper()

        entry_price = data.get("entry_price")
        current_price = data.get("current_price") or entry_price
        size = data.get("size")
        unrealized_pnl = data.get("unrealized_pnl_usd")
        leverage = data.get("leverage") or 2

        adaptive_multiplier = data.get("adaptive_size_multiplier")
        adaptive_warning = ""

        adaptive_value = safe_float(adaptive_multiplier)

        if adaptive_value is not None and abs(adaptive_value - 1.0) > 0.001:
            adaptive_warning = " ⚠️"

        lines = [
            (
                f"{self._position_icon(side)} "
                f"Позиция · {self._position_side_title(side)} x{leverage}"
            ),
            self._pnl_line(unrealized_pnl),
            f"Вход · $ {self._money(entry_price)}",
            f"Цена · $ {self._money(current_price)}",
            f"Размер{adaptive_warning} {self._size_value(size)}",
            f"Объём · {self._position_notional(current_price, size)}",
        ]

        risk_line = self._position_risk_line(data)

        if risk_line:
            lines.append(risk_line)

        return "\n".join(lines).strip()

    def _position_health_block(self, data: JsonDict) -> str:
        health_state = str(data.get("health_state") or "")
        health_message = str(data.get("health_message") or "").strip()
        pressure_state = str(data.get("pressure_state") or "")
        trend_alignment = str(data.get("trend_alignment") or "")
        adverse_momentum = bool(data.get("adverse_momentum"))
        price_move = safe_float(data.get("price_move_percent"))
        opened_age = data.get("opened_age_seconds")
        pnl = safe_float(data.get("unrealized_pnl_usd"))

        if not health_state or health_state in {"NONE", "UNKNOWN"}:
            return ""

        lines = [
            (
                f"{self._position_health_icon(health_state)} "
                f"Здоровье позиции · {self._position_health_title(health_state)}"
            ),
        ]

        pnl_line = self._position_health_pnl_line(pnl)
        if pnl_line:
            lines.append(pnl_line)

        if price_move is not None:
            lines.append(f"• Движение цены: {price_move:+.3f}%")

        alignment_line = self._position_alignment_line(trend_alignment)
        if alignment_line:
            lines.append(alignment_line)

        pressure_line = self._position_pressure_line(pressure_state)
        if pressure_line:
            lines.append(pressure_line)

        if adverse_momentum:
            lines.append("• Импульс: против позиции")

        hold_time = self._duration(opened_age)
        if hold_time != "—":
            lines.append(f"• В позиции: {hold_time}")

        if health_message:
            lines.append(health_message)

        return "\n".join(lines)

    def _position_health_icon(self, value: object) -> str:
        text = str(value or "")

        if text == "HEALTHY":
            return "🟢"

        if text == "WATCH":
            return "🟡"

        if text == "PRESSURE":
            return "🟠"

        if text == "DANGER":
            return "🔴"

        return "⚪️"

    def _position_health_title(self, value: object) -> str:
        text = str(value or "")

        mapping = {
            "HEALTHY": "устойчива",
            "WATCH": "под наблюдением",
            "PRESSURE": "под давлением",
            "DANGER": "высокий риск",
        }

        return mapping.get(text, "неизвестно")

    def _position_alignment_line(self, value: object) -> str:
        text = str(value or "")

        if text == "ALIGNED":
            return "• Рынок: по позиции"

        if text == "AGAINST":
            return "• Рынок: против позиции"

        if text == "NEUTRAL":
            return "• Рынок: нейтрально"

        return ""

    def _position_pressure_line(self, value: object) -> str:
        text = str(value or "")

        mapping = {
            "STRONG_PROFIT": "• Давление: нет, сильная прибыль",
            "PROFIT": "• Давление: нет",
            "PROFIT_UNDER_PRESSURE": "• Давление: прибыль под риском",
            "FLAT": "• Давление: нейтральное",
            "LOSS": "• Давление: умеренное",
            "HIGH_LOSS": "• Давление: высокое",
            "PRESSURE": "• Давление: повышенное",
            "DANGER": "• Давление: критическое",
        }

        return mapping.get(text, "")

    def _position_risk_line(self, data: JsonDict) -> str:
        items: list[str] = []

        sl = data.get("stop_loss_usd")
        tp = data.get("take_profit_usd")
        ml = data.get("max_loss_usd")

        if sl is not None:
            items.append(f"SL -$ {self._money(sl)}")

        if tp is not None:
            items.append(f"TP +$ {self._money(tp)}")

        if ml is not None:
            should_show_ml = True

            if sl is not None:
                try:
                    should_show_ml = abs(float(ml) - float(sl)) > 1.0
                except Exception:
                    should_show_ml = True

            if should_show_ml:
                items.append(f"ML -$ {self._money(ml)}")

        if not items:
            return ""

        return " · ".join(items)

    def _has_closed_pnl(self, value: NumericLike | None) -> bool:
        number = safe_float(value)

        if number is None:
            return False

        return abs(number) > 0.0001
    
    def _has_adaptive_size(self, data: JsonDict) -> bool:
        multiplier_raw = data.get("multiplier")
        value = safe_float(multiplier_raw)

        if value is None:
            return False

        if value < 0.95:
            return True

        if value > 1.05:
            return True

        reason = str(data.get("reason") or "").lower()

        return "margin" in reason

    def _adaptive_block(self, data: JsonDict) -> str:
        multiplier = safe_float(data.get("multiplier"))

        if multiplier is None:
            return ""

        return (
            f"{self._adaptive_icon(multiplier)} "
            f"Размер позиции · {self._adaptive_title(multiplier)}\n"
            f"• Риск: {self._float(data.get('effective_risk_percent'))}% депозита\n"
            f"• Потеря при SL: $ {self._float(data.get('effective_target_risk_usd'))}\n"
            f"• Коррекция: x{self._float(multiplier)}\n"
            f"{self._adaptive_reason_line(data)}"
        ).strip()

    def _status_block(self, data: JsonDict) -> str:
        status = str(data.get("status") or "").upper()

        if status == "RUNNING":
            icon = "🟢"
            title = "работает"
        elif status == "OBSERVING":
            icon = "👀"
            title = "под наблюдением"
        elif status == "OFF":
            icon = "⚪️"
            title = "остановлена"
        else:
            icon = "⛔️"
            title = "не готова"

        return (
            f"{icon} Автоторговля {title}\n"
            f"• Актив: {self._format_system_symbol(data.get('symbol'))}\n"
            f"• Стратегия: {data.get('strategy') or '—'}\n"
            f"• Настроено: {self._bool(data.get('is_configured'))}"
        )

    def _execution_explanation(self, data: JsonDict) -> str:
        quality = str(data.get("quality") or "")
        reason = str(
            data.get("quality_reason")
            or data.get("semantic_reason")
            or ""
        )
        semantic_status = str(data.get("semantic_status") or "")

        if semantic_status in {
            "IDLE",
            "WAITING_SIGNAL",
        }:
            return "Ожидание подходящего входа."

        if semantic_status in {"POSITION_OPEN", "READY"}:
            return ""

        if reason == "MARKET_CLOSED":
            return "Биржа временно недоступна для торговли."

        if quality == "GOOD":
            return ""

        if reason == "HIGH_SPREAD":
            return "Спред слишком широкий для входа."

        if reason == "WIDE_SPREAD":
            return "Цена входа может ухудшиться."

        if reason in {
            "STALE_SNAPSHOT",
            "AGING_SNAPSHOT",
        }:
            return "Данные исполнения устарели."

        if reason in {
            "SNAPSHOT_ERROR",
            "SNAPSHOT_UNAVAILABLE",
        }:
            return "Недостаточно данных для входа."

        if quality == "BLOCKED":
            return "Вход временно невозможен."

        if quality == "WARNING":
            return "Условия входа нестабильны."

        return "Условия входа ещё не сформированы."

    def _market_explanation(self, data: JsonDict) -> str:
        state = str(data.get("state") or "")
        trend = str(data.get("trend") or "")
        strength = str(data.get("trend_strength") or "")
        quality = str(data.get("trend_quality") or "")
        phase = str(data.get("phase") or "")
        phase_direction = str(data.get("phase_direction") or "")
        volatility = str(data.get("volatility") or "")
        entry_block = str(
            data.get("entry_block_reason")
            or data.get("entry_block_message")
            or ""
        )
        age = data.get("age_seconds")

        is_range = state == "RANGE" or phase == "RANGE"
        is_pullback = phase == "PULLBACK"
        is_impulse = phase == "IMPULSE"
        is_squeeze = phase == "SQUEEZE"

        reasons: list[str] = []

        def add(text: str) -> None:
            if text not in reasons:
                reasons.append(text)

        try:
            age_seconds = int(float(age)) if age is not None else None
        except Exception:
            age_seconds = None

        if age_seconds is not None and age_seconds > 60:
            add("Данные рынка устарели")

        if state == "HIGH_VOLATILITY" or volatility in {"HIGH", "HIGH_VOLATILITY"}:
            add("Рынок перегрет")

        if state == "LOW_VOLATILITY" or volatility in {"LOW", "LOW_VOLATILITY"}:
            add("Движения мало")

        if "MARKET_FILTER_BLOCKED" in entry_block:
            if state == "HIGH_VOLATILITY" or volatility in {"HIGH", "HIGH_VOLATILITY"}:
                add("Слишком резкое движение — вход рискованный")
            elif state == "LOW_VOLATILITY" or volatility in {"LOW", "LOW_VOLATILITY"}:
                add("Слишком слабое движение")
            elif quality == "NOISY":
                add("Движение шумное")
            elif strength == "WEAK":
                add("Тренд слабый")
            elif is_pullback:
                add("Рынок в откате")
            elif is_squeeze:
                add("Рынок сжат")
            elif is_range:
                add("Нет понятного направления")
            else:
                add("Условия для входа ещё не совпали")

        elif entry_block:
            normalized_block = entry_block.strip().lower()

            if "слаб" in normalized_block and "тренд" in normalized_block:
                if not is_range:
                    add("Тренд слабый")
            elif "откат" in normalized_block:
                add("Рынок в откате")
            elif "шум" in normalized_block:
                add("Движение шумное")
            elif "волат" in normalized_block:
                add("Волатильность мешает входу")
            elif "данн" in normalized_block:
                add("Недостаточно данных")
            else:
                short_reason = self._short_reason(entry_block)

                if not (is_range and "тренд слаб" in short_reason.lower()):
                    add(short_reason)

        market_title = self._market_title(data)

        if is_range:
            if (
                "флэт" not in market_title.lower()
                and not any("флэт" in r.lower() for r in reasons)
            ):
                add("Флэт")

        if is_squeeze:
            add("Рынок в сжатии")

        if is_pullback:
            add("Рынок в откате")

            if phase_direction == "UP" and trend == "DOWN":
                add("Откат против снижения")

            if phase_direction == "DOWN" and trend == "UP":
                add("Откат против роста")

        if quality == "NOISY":
            add("Движение шумное")

        if strength == "WEAK" and not is_range:
            add("Тренд слабый")

        has_weak_impulse = entry_block in {
            "WEAK_UP_IMPULSE",
            "WEAK_DOWN_IMPULSE",
        }

        if is_impulse and strength != "WEAK" and not has_weak_impulse:
            if phase_direction == "UP" and trend == "DOWN":
                add("Импульс против снижения")
            elif phase_direction == "DOWN" and trend == "UP":
                add("Импульс против роста")
            elif phase_direction == "UP":
                if not any("Пробой вверх" in r for r in reasons):
                    add("Есть направленное движение вверх")
            elif phase_direction == "DOWN":
                if not any("Пробой вниз" in r for r in reasons):
                    add("Есть направленное движение вниз")
            elif trend == "UP":
                if not any("Пробой вверх" in r for r in reasons):
                    add("Есть направленное движение вверх")
            elif trend == "DOWN":
                if not any("Пробой вниз" in r for r in reasons):
                    add("Есть направленное движение вниз")
            else:
                if not any("Пробой" in r for r in reasons):
                    add("Есть направленное движение")

        if trend == "UP" and not is_impulse and not is_range:
            add("Рынок растёт")

        if trend == "DOWN" and not is_impulse and not is_range:
            add("Рынок снижается")

        if not reasons:
            if is_range:
                return "Нет понятного направления"
            if is_squeeze:
                return "Рынок в сжатии"
            if is_pullback:
                return "Рынок в откате"
            return "Рынок анализируется"

        return "\n".join(reasons[:2])

    def _human(self, value: Any) -> str:
        if value is None:
            return "—"

        text = str(value)

        mapping = {
            # === ADAPTIVE SIZE ===
            "ADAPTIVE_SIZE_INCREASED": "размер входа увеличен",
            "ADAPTIVE_SIZE_REDUCED": "размер входа уменьшен",
            "ADAPTIVE_SIZE_ZERO": "размер входа заблокирован",

            # === EXECUTION / SNAPSHOT ===
            "AGING_SNAPSHOT": "snapshot стареет",
            "BLOCKED": "заблокировано",
            "GOOD": "норма",
            "HIGH_SPREAD": "высокий спред",
            "SNAPSHOT_ERROR": "нет данных",
            "SNAPSHOT_UNAVAILABLE": "нет стакана",
            "STALE_SNAPSHOT": "старый snapshot",
            "WARNING": "внимание",
            "WIDE_SPREAD": "спред повышен",

            # === MARKET ===
            "BREAKOUT_DOWN": "пробой вниз",
            "BREAKOUT_UP": "пробой вверх",
            "CLEAN": "чисто",
            "COUNTER_TREND_BREAKOUT": "пробой против тренда",
            "FAST_DOWN_MOVE": "быстрое снижение",
            "FAST_UP_MOVE": "быстрый рост",
            "FLAT": "ровно",
            "HIGH_VOLATILITY": "перегрев",
            "IMPULSE": "импульс",
            "LOW_VOLATILITY": "сжатие",
            "MARKET_ANALYSIS_TTL_EXPIRED": "анализ рынка устарел",
            "MARKET_FILTER_BLOCKED": "рынок не готов",
            "MARKET_OK": "рынок готов",
            "MARKET_PULLBACK": "откат",
            "MARKET_STATE_NOT_TREND": "нет понятного направления",
            "HH_HL": "структура роста",
            "LH_LL": "структура снижения",
            "MIXED": "структура смешанная",
            "MARKET_STRUCTURE_CONFLICT": "структура против входа",
            "MARKET_STRUCTURE_MIXED": "структура не подтверждает вход",
            "HIGHER_HIGH_HIGHER_LOW": "выше хай и выше лой",
            "LOWER_HIGH_LOWER_LOW": "ниже хай и ниже лой",
            "MIXED_MARKET_STRUCTURE": "смешанная структура",
            "NOISY": "шум",
            "NOISY_MARKET_TREND": "рынок шумный",
            "NORMAL": "норма",
            "PRICE_ABOVE_LOOKBACK_HIGH": "выше локального хая",
            "PRICE_BELOW_LOOKBACK_LOW": "ниже локального лоя",
            "PULLBACK": "откат",
            "RANGE": "флэт",
            "SQUEEZE": "сжатие",
            "TREND_DOWN": "снижение",
            "TREND_UP": "рост",
            "UNKNOWN": "неясно",
            "WEAK_MARKET_TREND": "слабый тренд",
            "CHASING": "chasing",
            "COMPRESSED": "EMA сжаты",
            "EARLY": "ранний вход",
            "EMA_COMPRESSED": "EMA сжаты",
            "EMA_OVEREXTENDED": "EMA перерастянуты",
            "EXTENDED": "расширен",
            "HEALTHY": "здоровая дистанция",
            "LATE": "поздний вход",
            "OVEREXTENDED": "перерастянут",
            "BREAKOUT_ALREADY_EXTENDED": "пробой уже растянут",
            "HEALTHY_TREND_DISTANCE": "здоровая EMA-дистанция",
            "PULLBACK_ENTRY_ZONE": "зона входа на откате",
            "ENTRY_TIMING_UNKNOWN": "тайминг неясен",

            # === MOMENTUM ===
            "DOWN": "вниз",
            "EXHAUSTED": "выдохся",
            "MOMENTUM_DOWN": "импульс вниз",
            "MOMENTUM_UP": "импульс вверх",
            "MOMENTUM_NOT_CONFIRMED": "движение не подтверждает вход",
            "NO_SIGNIFICANT_MOMENTUM": "импульс слабый",
            "STRONG": "сильная",
            "UP": "вверх",
            "WEAK": "слабая",
            "WEAK_DOWN_IMPULSE": "слабый импульс вниз",
            "WEAK_UP_IMPULSE": "слабый импульс вверх",

            # === RUNTIME / DATA ===
            "CONFIRMING": "подтверждение",
            "EXPANDED": "подробно",
            "GREEN": "стабильно",
            "HIGH": "высокая",
            "LOW": "низкая",
            "NOT_ENOUGH_LIVE_DATA": "мало live-данных",
            "OFF": "выключено",
            "OBSERVING": "наблюдение",
            "READY": "готово",
            "RED": "вход нежелателен",
            "RUNNING": "работает",
            "SIGNAL_TTL_EXPIRED": "сигнал устарел",
            "WAITING": "ожидание",
            "YELLOW": "осторожно",

            # === SIGNAL ===
            "BUY": "покупка",
            "HOLD": "ожидание",
            "SELL": "продажа",
            "WAITING_SIGNAL": "ждёт сигнал",

            # === SYSTEM / MODE ===
            "COMPACT": "кратко",
            "IDLE": "пауза",

            # === POSITION ===
            "LONG": "Long",
            "NONE": "нет",
            "POSITION_OPEN": "позиция открыта",
            "SHORT": "Short",

            # === HTF / GLOBAL TREND ===
            "HTF_TREND_AGAINST": "старший тренд против входа",
            "HTF_TREND_NOT_CONFIRMED": "старший тренд не подтвердил вход",
            "HTF_NOT_CONFIRMED": "старший тренд не подтвердил вход",
            "HTF_ALIGNMENT_AGAINST": "старший тренд против входа",
            "HTF_ALIGNMENT_UNKNOWN": "старший тренд неясен",
            "AGAINST": "против входа",
            "ALIGNED": "подтверждает вход",
            "SAME_INTERVAL": "тот же таймфрейм",
            "NEUTRAL": "нейтрально",
        }

        return mapping.get(text, text)

    def _bool(self, value: object) -> str:
        if isinstance(value, bool):
            return "да" if value else "нет"

        text = str(value or "").strip().lower()

        if text in {"true", "1", "yes", "да"}:
            return "да"

        if text in {"false", "0", "no", "нет", "", "none", "null"}:
            return "нет"

        return "да" if bool(value) else "нет"

    def _float(self, value: NumericLike | None) -> str:
        number = safe_float(value)

        if number is None:
            return "—"

        return f"{number:.4f}"


    def _percent(self, value: NumericLike | None) -> str:
        number = safe_float(value)

        if number is None:
            return "—"

        return f"{number * 100:.0f}%"


    def _percent_value(self, value: NumericLike | None) -> str:
        number = safe_float(value)

        if number is None:
            return "—"

        return f"{number:.3f}%"


    def _money(self, value: NumericLike | None) -> str:
        number = safe_float(value)

        if number is None:
            return "—"

        return f"{number:.2f}"


    def _price_value(
        self,
        value: NumericLike | None,
        *,
        is_usd: bool = False,
    ) -> str:
        number = safe_float(value)

        if number is None:
            return "—"

        formatted = f"{number:,.2f}".replace(",", " ")

        if is_usd:
            return f"${formatted}"

        return formatted


    def _size_value(self, value: NumericLike | None) -> str:
        number = safe_float(value)

        if number is None:
            return "—"

        return f"{number:.5f}".rstrip("0").rstrip(".")

    def _severity_icon(self, value: object) -> str:
        text = str(value or "")

        if text == "GREEN":
            return "🟢"

        if text in {"YELLOW", "WAITING"}:
            return "🟡"

        if text == "RED":
            return "⛔️"

        return "⚪"

    def _signal_icon(self, value: object) -> str:
        text = str(value or "").upper()

        if text == "BUY":
            return "🟢"

        if text == "SELL":
            return "🔴"

        if text == "HOLD":
            return "🟡"

        return "⚪️"
    
    def _position_icon(self, value: object) -> str:
        text = str(value or "")

        if text == "LONG":
            return "🟢"

        if text == "SHORT":
            return "🔴"

        if text == "NONE":
            return "⚪"

        return "⚪"
    
    def _system_icon(self, value: object) -> str:
        text = str(value or "")

        if text == "RUNNING":
            return "🟢"

        if text == "OBSERVING":
            return "🟡"

        if text == "OFF":
            return "⚪"

        return "⚪"

    def _position_notional(
        self,
        entry_price: NumericLike | None,
        size: NumericLike | None,
    ) -> str:
        price = safe_float(entry_price)
        amount = safe_float(size)

        if price is None or amount is None:
            return "$ —"

        return f"$ {price * amount:.1f}"

    def _pnl_value(self, value: NumericLike | None) -> str:
        pnl = safe_float(value)

        if pnl is None:
            return "—"

        if pnl > 0:
            return f"🟢 +$ {abs(pnl):.2f}"

        if pnl < 0:
            return f"🔴 −$ {abs(pnl):.2f}"

        return "$ 0.00"

    def _pnl_line(self, value: NumericLike | None) -> str:
        pnl = safe_float(value)

        if pnl is None:
            return "PnL: —"

        if pnl > 0:
            return f"Прибыль 🟢 +$ {abs(pnl):.2f}"

        if pnl < 0:
            return f"Убыток 🔴 −$ {abs(pnl):.2f}"

        return "PnL: $ 0.00"

    def _position_action_line(self, action: str) -> str:
        if action in {"OPEN_LONG", "OPEN_SHORT"}:
            return "Новая позиция открыта"

        if action.startswith("FLIP_"):
            return "Сделка развернута"

        if action == "CLOSE":
            return "Сделка закрыта"

        return ""

    def _adaptive_icon(self, multiplier: NumericLike | None) -> str:
        value = safe_float(multiplier)

        if value is None:
            return "⚠️"

        if value == 1:
            return "🟢"

        return "⚠️"

    def _adaptive_title(self, multiplier: NumericLike | None) -> str:
        value = safe_float(multiplier)

        if value is None:
            return "неясен"

        if value < 1:
            return "уменьшен"

        if value > 1:
            return "увеличен"

        return "по настройкам"

    def _adaptive_reason_line(self, data: JsonDict) -> str:
        reason = self._human(data.get("reason"))

        if reason in {"—", "нет"}:
            return ""

        return reason[:1].upper() + reason[1:]

    def _market_icon(self, data: JsonDict) -> str:
        if data.get("market_is_open") is False:
            return "⛔️"
        
        score = safe_float(data.get("market_score"))

        if score is not None:
            if score >= 75:
                return "🟢"

            if score >= 35:
                return "🟡"

            return "⛔️"

        state = str(data.get("state") or "").upper()
        strength = str(data.get("trend_strength") or "").upper()
        quality = str(data.get("trend_quality") or "").upper()
        phase = str(data.get("phase") or "").upper()
        volatility = str(data.get("volatility") or "").upper()

        entry_block_reason = str(data.get("entry_block_reason") or "").upper()
        entry_block_message = str(data.get("entry_block_message") or "").lower()

        hard_block_reasons = {
            "MARKET_CLOSED",
            "STALE_SNAPSHOT",
            "SNAPSHOT_ERROR",
            "SNAPSHOT_UNAVAILABLE",
            "HIGH_SPREAD",
            # Структура рынка против входа — это hard-block.
            "MARKET_STRUCTURE_CONFLICT",
            "MARKET_STRUCTURE_MIXED",
        }

        hard_block_texts = {
            "бирж",
            "перерыв",
            "рынок закрыт",
            "торги временно",
            "нет данных рынка",
            "устарел",
            "snapshot",
            "спред",
            "spread",
            # Русские сообщения по structure-block.
            "структура рынка против входа",
            "структура рынка не подтверждает вход",
        }

        if entry_block_reason in hard_block_reasons:
            return "⛔️"

        if any(text in entry_block_message for text in hard_block_texts):
            return "⛔️"

        if state in {"UNKNOWN", "NONE"}:
            return "⚪️"

        if (
            entry_block_reason
            or state in {"HIGH_VOLATILITY", "CHAOTIC", "LIQUIDITY_VOID", "RANGE"}
            or phase in {"RANGE", "SQUEEZE", "PULLBACK"}
            or volatility in {"HIGH", "HIGH_VOLATILITY", "LOW", "LOW_VOLATILITY"}
            or strength == "WEAK"
            or quality == "NOISY"
        ):
            return "🟡"

        if state in {"TREND_UP", "TREND_DOWN"}:
            return "🟢"

        return "🟡"

    def _momentum_strength(
        self,
        value: NumericLike | None,
        *,
        momentum_state: object | None = None,
        direction: object | None = None,
    ) -> str:
        strength = safe_float(value)

        if strength is None:
            return "—"

        state_text = str(momentum_state or "").upper()
        direction_text = str(direction or "").upper()

        if (
            state_text in {"NONE", "NO_SIGNIFICANT_MOMENTUM"}
            or direction_text in {"FLAT", "РОВНО"}
        ):
            if strength >= 1.0:
                return "затухание"

            return "слабая"

        if strength < 0.3:
            label = "слабая"
        elif strength < 0.6:
            label = "средняя"
        elif strength < 1.0:
            label = "сильная"
        else:
            label = "резкая"

        return f"{label} · x{strength:.1f}"

    def _short_reason(self, value: str) -> str:
        text = value.strip()
        normalized = text.lower()

        mapping = {
            "анализ рынка устарел": "Анализ рынка устарел",
            "высокий spread": "Спред мешает входу",
            "низкая совокупная уверенность входа": "Вход рискованный",
            "откат": "Рынок в откате",
            "рынок сейчас не подходит для входа": "Рынок не готов",
            "сигнал устарел и был сброшен": "Сигнал устарел",
            "слабый импульс": "Слабый импульс",
            "слабый тренд": "Тренд слабый",
            "снимок устарел": "Данные рынка устарели",
            "snapshot устарел": "Данные рынка устарели",
            "spread повышен": "Спред повышен",
            "шумный тренд": "Рынок шумный",
            "market_structure_conflict": "Структура против входа",
            "market_structure_mixed": "Структура не подтверждает вход",
            "структура рынка против входа": "Структура против входа",
            "структура рынка не подтверждает вход": "Структура не подтверждает вход",
            "counter_trend_breakout": "Пробой против тренда",
            "market_filter_blocked": "Рынок не подходит для входа",
            "market_pullback": "Рынок в откате",
            "market_state_not_trend": "Нет понятного направления",
            "noisy_market_trend": "Движение шумное",
            "weak_down_impulse": "Импульс вниз слабый",
            "weak_market_trend": "Тренд слабый",
            "weak_up_impulse": "Импульс вверх слабый",
            "momentum_not_confirmed": "Движение не подтверждает вход",
            "htf_trend_against": "Старший тренд против входа",
            "htf_trend_not_confirmed": "Старший тренд не подтвердил вход",
            "htf_not_confirmed": "Старший тренд не подтвердил вход",
            "htf_alignment_against": "Старший тренд против входа",
            "htf_alignment_unknown": "Старший тренд неясен",
            "against": "Старший тренд против входа",
        }

        if normalized in mapping:
            return mapping[normalized]

        human = self._human(text)

        if human != text:
            return human[:1].upper() + human[1:]

        return text

    def _market_live_state(self, value: NumericLike | None) -> str:
        seconds_float = safe_float(value)

        if seconds_float is None:
            return "—"

        seconds = int(seconds_float)

        if seconds <= 10:
            return "live"

        if seconds <= 60:
            return f"задержка {seconds}с"

        return "устарели"

    def _duration(self, value: NumericLike | None) -> str:
        seconds_float = safe_float(value)

        if seconds_float is None:
            return "—"

        total_seconds = max(0, int(seconds_float))

        hours = total_seconds // 3600
        minutes = (total_seconds % 3600) // 60
        seconds = total_seconds % 60

        if hours > 0:
            return f"{hours}ч {minutes:02d}м"

        if minutes > 0:
            return f"{minutes}м {seconds:02d}с"

        return f"{seconds}с"
    
    def _asset_symbol(self, symbol: object) -> str:
        if symbol is None:
            return "—"

        text = str(symbol).split("_", 1)[0].upper()

        if "/" in text:
            return text.split("/", 1)[0]

        return text
    
    def _format_system_symbol(self, symbol: object) -> str:
        if symbol is None:
            return "—"

        text = str(symbol).split("_", 1)[0].upper()

        if "/" in text:
            base, quote = text.split("/", 1)
            return f"{base} / {quote}"

        return text
    
    def _advanced_trend_quality_line(self, data: JsonDict) -> str:
        efficiency = safe_float(data.get("trend_efficiency"))
        ema_distance = safe_float(data.get("ema_distance_atr_ratio"))
        consistency = safe_float(data.get("trend_consistency"))
        quality_score = safe_float(data.get("trend_quality_score"))

        ema_distance_state = str(data.get("ema_distance_state") or "")
        entry_timing_state = str(data.get("entry_timing_state") or "")

        items: list[str] = []

        if quality_score is not None:
            items.append(f"score {quality_score:.2f}")

        if consistency is not None:
            items.append(f"стабильность {consistency:.2f}")

        if efficiency is not None:
            items.append(f"эффективность {efficiency:.2f}")

        if ema_distance is not None:
            items.append(f"EMA/ATR {ema_distance:.2f}")

        if ema_distance_state:
            items.append(self._human(ema_distance_state))

        if entry_timing_state:
            items.append(self._human(entry_timing_state))

        if not items:
            return ""

        return "• Структура: " + " · ".join(items[:4])
    
    def _runtime_exchange_block(
        self,
        data: JsonDict,
    ) -> str:
        return format_runtime_exchange_alert(data)
    
    def _htf_context_line(self, data: JsonDict) -> str:
        htf_trend = str(data.get("htf_trend") or "")
        htf_interval = str(data.get("htf_interval") or "1h").upper()

        if not htf_trend or htf_trend in {"UNKNOWN", "NONE"}:
            return ""

        trend_text = self._human(htf_trend)

        return f"• Старший тренд ({htf_interval}): {trend_text}"

    def _human_htf_alignment(self, value: object) -> str:
        text = str(value or "").upper()

        mapping = {
            "ALIGNED": "подтверждает",
            "AGAINST": "против входа",
            "NEUTRAL": "нейтрально",
            "UNKNOWN": "неясно",
            "SAME_INTERVAL": "тот же ТФ",
        }

        return mapping.get(text, text.lower() or "—")
    
    def _position_pnl_icon(self, pnl: float | None) -> str:
        if pnl is None:
            return "⚪️"

        if pnl > 0:
            return "🟢"

        if pnl < 0:
            return "🔴"

        return "🟡"


    def _position_health_pnl_line(self, pnl: float | None) -> str:
        if pnl is None:
            return "• PnL · —"

        if pnl > 0:
            return f"• Прибыль · +$ {abs(pnl):.2f}"

        if pnl < 0:
            return f"• Убыток · −$ {abs(pnl):.2f}"

        return "• PnL · $ 0.00"

    def _live_stream_warning(self, runtime: JsonDict) -> str:
        market_data_runtime = runtime.get("market_data_runtime")

        if not isinstance(market_data_runtime, dict):
            return "⚠️ Live-поток недоступен"

        stream_state = str(market_data_runtime.get("stream_state") or "").upper()

        if stream_state == "CONNECTED":
            return ""

        return "⚠️ Live-поток недоступен"

    def _strategy_title(self, value: object) -> str:
        text = str(value or "").upper()

        mapping = {
            "TREND": "Trend",
            "GRID": "Grid",
            "SCALP": "Scalp",
        }

        return mapping.get(text, text.title() if text else "—")