From 363719cc8e8739d36d74136728eebfbd879e616d Mon Sep 17 00:00:00 2001 From: Sergey Date: Mon, 11 May 2026 11:22:10 +0300 Subject: [PATCH] 07.4.4.1.4 Entry Decision Diagnostics Layer --- app/src/core/event_titles.py | 3 + app/src/telegram/handlers/auto/ui.py | 31 +- app/src/trading/auto/service.py | 45 +++ app/src/trading/auto/state.py | 8 +- app/src/trading/strategies/trend.py | 2 + docs/roadmap/master-roadmap.md | 16 + docs/roadmap/stage-07-auto-trading-roadmap.md | 16 + ..._4_1_4-entry_decision_diagnostics_layer.md | 342 ++++++++++++++++++ 8 files changed, 456 insertions(+), 7 deletions(-) create mode 100644 docs/stages/stage-07_4_4_1_4-entry_decision_diagnostics_layer.md diff --git a/app/src/core/event_titles.py b/app/src/core/event_titles.py index 6182fa3..0a09ec9 100644 --- a/app/src/core/event_titles.py +++ b/app/src/core/event_titles.py @@ -31,6 +31,9 @@ EVENT_TITLES = { "market_stream_disconnected": "Рынок", "market_symbol_changed": "Рынок", + # Мониторинг позиций + "entry_blocked": "Вход в позицию", + # Журнал "journal_exported": "Журнал", "journal_export_error": "Журнал", diff --git a/app/src/telegram/handlers/auto/ui.py b/app/src/telegram/handlers/auto/ui.py index dcfa6c5..08de59e 100644 --- a/app/src/telegram/handlers/auto/ui.py +++ b/app/src/telegram/handlers/auto/ui.py @@ -155,16 +155,23 @@ def _build_waiting_text(state) -> str: entry_price_override=price, ) + signal_lines = [ + _signal_line(state), + _market_state_line(state), + _entry_block_line(state), + *_signal_confidence_lines(state), + *_execution_block_lines(state), + ] + + signal_lines = [line for line in signal_lines if line] + parts = [ f"🤖 Автоторговля {_status_text(state.status)}", _account_mode_line(), "", f"Доступно · $ {_format_money_compact(available)}", "", - _signal_line(state), - _market_state_line(state), - *_signal_confidence_lines(state), - *_execution_block_lines(state), + *signal_lines, "", "🧾 Подготовка ордера", "", @@ -253,16 +260,28 @@ def _market_state_line(state) -> str: return labels.get(market_state, "⏳ Рынок · Идёт анализ") +def _entry_block_line(state) -> str: + message = getattr(state, "entry_block_message", None) + + if not message: + return "" + + return f"Вход в позицию · {message}" + + def _execution_block_lines(state) -> list[str]: lines: list[str] = [] reason = getattr(state, "execution_block_reason", None) if reason: - lines.append(f"Blocked · {reason}") + lines.append(f"Исполнение · {reason}") adjustment = getattr(state, "execution_size_adjustment_reason", None) + if adjustment == "MARGIN_LIMIT": - lines.append("Size adjusted by Max Reserved") + lines.append( + "Позиция ограничена настройкой Max Reserved." + ) return lines diff --git a/app/src/trading/auto/service.py b/app/src/trading/auto/service.py index 068d674..4fbb717 100644 --- a/app/src/trading/auto/service.py +++ b/app/src/trading/auto/service.py @@ -35,6 +35,7 @@ class AutoTradeService: _last_logged_market_state: str | None = None _last_logged_market_trend: str | None = None _last_logged_market_volatility: str | None = None + _last_logged_entry_block_reason: str | None = None _same_signal_count = 0 # debug: принудительно выставить сигнал и decision @@ -663,6 +664,8 @@ class AutoTradeService: state.market_volatility = payload.get("market_volatility") state.market_analysis_interval = payload.get("market_analysis_interval") state.market_analysis_reason = payload.get("market_analysis_reason") + state.entry_block_reason = payload.get("entry_block_reason") + state.entry_block_message = payload.get("entry_block_message") self._log_market_state_if_changed( state=state, @@ -672,6 +675,48 @@ class AutoTradeService: previous_market_volatility=previous_market_volatility, ) + self._log_entry_block_if_changed( + state=state, + payload=payload, + ) + + def _log_entry_block_if_changed( + self, + *, + state: AutoTradeState, + payload: dict, + ) -> None: + reason = state.entry_block_reason + message = state.entry_block_message + + if not reason or not message: + return + + key = f"{state.status}:{state.symbol}:{state.strategy}:{reason}:{message}" + + if key == type(self)._last_logged_entry_block_reason: + return + + type(self)._last_logged_entry_block_reason = key + + try: + JournalService().log_ui_info( + event_type="entry_blocked", + message=f"Вход в позицию не выполнен: {message}.", + screen="auto", + action="entry_diagnostics", + payload={ + **payload, + "entry_block_reason": reason, + "entry_block_message": message, + "symbol": state.symbol, + "strategy": state.strategy, + "status": state.status, + }, + ) + except Exception: + pass + def _log_market_state_if_changed( self, *, diff --git a/app/src/trading/auto/state.py b/app/src/trading/auto/state.py index 46154eb..60c07aa 100644 --- a/app/src/trading/auto/state.py +++ b/app/src/trading/auto/state.py @@ -119,4 +119,10 @@ class AutoTradeState: market_analysis_interval: str | None = None # объяснение последнего анализа рынка - market_analysis_reason: str | None = None \ No newline at end of file + market_analysis_reason: str | None = None + + # код причины, почему вход в позицию сейчас не выполнен + entry_block_reason: str | None = None + + # человекочитаемое объяснение причины не входа + entry_block_message: str | None = None \ No newline at end of file diff --git a/app/src/trading/strategies/trend.py b/app/src/trading/strategies/trend.py index a15940a..244cf5d 100644 --- a/app/src/trading/strategies/trend.py +++ b/app/src/trading/strategies/trend.py @@ -92,6 +92,8 @@ class TrendStrategy: payload={ **base_payload, "market_filter_blocked": True, + "entry_block_reason": "MARKET_FILTER_BLOCKED", + "entry_block_message": "рынок сейчас не подходит для входа", }, ) diff --git a/docs/roadmap/master-roadmap.md b/docs/roadmap/master-roadmap.md index 17ae59c..3ee9742 100644 --- a/docs/roadmap/master-roadmap.md +++ b/docs/roadmap/master-roadmap.md @@ -451,6 +451,22 @@ - CSV / XLSX export очищен от эмодзи - журнал подготовлен к централизованному event_titles.py и future filters/search layer +#### 07.4.4.1.4 ✅ Entry Decision Diagnostics Layer +- добавлен диагностический слой причин не входа в позицию +- AutoTradeState расширен entry_block_reason и entry_block_message +- TrendStrategy начала передавать причины HOLD в payload +- добавлены entry_block_reason к market filter / live data / weak impulse сценариям +- AutoTradeService синхронизирует entry diagnostics в runtime state +- добавлено событие entry_blocked для журнала +- журнал пишет причины не входа только при изменении причины +- добавлена защита от spam logging одинаковых HOLD-причин +- Auto UI показывает строку Вход в позицию · причина +- strategy diagnostics отделены от execution diagnostics +- execution UI приведён к human-readable стилю +- добавлен EVENT_TITLES mapping для entry_blocked +- подготовлена база для анализа частоты причин отказа от входа +- подготовлена база для adaptive thresholds и настройки чувствительности стратегии + --- ### 07.4.5 diff --git a/docs/roadmap/stage-07-auto-trading-roadmap.md b/docs/roadmap/stage-07-auto-trading-roadmap.md index 738be68..5dfb1ee 100644 --- a/docs/roadmap/stage-07-auto-trading-roadmap.md +++ b/docs/roadmap/stage-07-auto-trading-roadmap.md @@ -427,6 +427,22 @@ - CSV / XLSX export очищен от эмодзи - журнал подготовлен к централизованному event_titles.py и future filters/search layer +#### 07.4.4.1.4 ✅ Entry Decision Diagnostics Layer +- добавлен диагностический слой причин не входа в позицию +- AutoTradeState расширен entry_block_reason и entry_block_message +- TrendStrategy начала передавать причины HOLD в payload +- добавлены entry_block_reason к market filter / live data / weak impulse сценариям +- AutoTradeService синхронизирует entry diagnostics в runtime state +- добавлено событие entry_blocked для журнала +- журнал пишет причины не входа только при изменении причины +- добавлена защита от spam logging одинаковых HOLD-причин +- Auto UI показывает строку Вход в позицию · причина +- strategy diagnostics отделены от execution diagnostics +- execution UI приведён к human-readable стилю +- добавлен EVENT_TITLES mapping для entry_blocked +- подготовлена база для анализа частоты причин отказа от входа +- подготовлена база для adaptive thresholds и настройки чувствительности стратегии + --- ### 07.4.5 diff --git a/docs/stages/stage-07_4_4_1_4-entry_decision_diagnostics_layer.md b/docs/stages/stage-07_4_4_1_4-entry_decision_diagnostics_layer.md new file mode 100644 index 0000000..52c74d3 --- /dev/null +++ b/docs/stages/stage-07_4_4_1_4-entry_decision_diagnostics_layer.md @@ -0,0 +1,342 @@ +# 07.4.4.1.4 — Entry Decision Diagnostics Layer + +## Цель этапа + +Добавить диагностический слой, который объясняет, почему автоторговля не входит в позицию, даже если рынок движется. + +До этого этапа пользователь видел в интерфейсе в основном `HOLD`, но не всегда понимал причину: + +- рынок не подходит для входа +- live-импульс слишком слабый +- недостаточно live-данных +- сигнал ещё не подтверждён +- execution не готов открыть позицию +- размер позиции ограничен настройками защиты + +Этап сделал поведение автоторговли более прозрачным и подготовил систему к дальнейшей настройке стратегии. + +--- + +# Что было реализовано + +## 1. Добавлен Entry Decision Diagnostics Layer + +В систему добавлен отдельный слой диагностики входа в позицию. + +Теперь стратегия не просто возвращает `HOLD`, а дополнительно передаёт причину, почему вход не выполнен. + +Это важно, потому что `HOLD` может означать разные ситуации: + +- безопасное ожидание +- рынок без выраженного направления +- слабый импульс +- недостаточно live-данных +- неподходящая волатильность +- техническая ошибка получения рыночных данных + +После этапа эти причины стали видимыми в runtime state, UI и журнале. + +--- + +# 2. Добавлены поля диагностики входа в AutoTradeState + +В `AutoTradeState` добавлены новые поля: + +- `entry_block_reason` +- `entry_block_message` + +`entry_block_reason` хранит технический код причины. + +Примеры: + +- `MARKET_FILTER_BLOCKED` +- `LIVE_DATA_WAITING` +- `WEAK_UP_IMPULSE` +- `WEAK_DOWN_IMPULSE` +- `INVALID_PRICE` +- `SNAPSHOT_ERROR` + +`entry_block_message` хранит человекочитаемое объяснение. + +Примеры: + +- «рынок сейчас не подходит для входа» +- «недостаточно live-данных для подтверждения» +- «слабый импульс вверх» +- «слабый импульс вниз» + +--- + +# 3. TrendStrategy начала отдавать причины отказа от входа + +В `TrendStrategy` причины HOLD были расширены диагностическими payload-полями. + +Теперь, когда стратегия не даёт BUY или SELL, она передаёт: + +- почему вход не разрешён +- какой market state был в момент решения +- какой live-импульс был рассчитан +- сколько live-цен накоплено +- какой threshold не был пройден +- какой direction ratio был получен + +Это особенно важно для анализа ситуаций, когда визуально рынок движется, но бот всё равно не входит в позицию. + +Например, рынок может расти последние несколько свечей, но стратегия всё ещё может не входить, если: + +- общий market state не подтверждает тренд +- live-импульс ниже порога +- движение недостаточно устойчивое +- подтверждение сигнала ещё не накоплено + +--- + +# 4. AutoTradeService синхронизирует entry diagnostics + +`AutoTradeService` теперь забирает `entry_block_reason` и `entry_block_message` из payload стратегии и сохраняет их в текущем состоянии автоторговли. + +Это позволило использовать одну и ту же диагностику сразу в нескольких местах: + +- Auto UI +- журнал +- future analytics layer +- future filters/search layer +- future AI explanation layer + +--- + +# 5. Добавлено журналирование причин не входа + +Добавлено событие: + +- `entry_blocked` + +Журнал теперь фиксирует не только готовые сигналы и открытие позиций, но и важные причины, почему позиция не была открыта. + +Формат сообщения: + +```text +[DEMO] Вход в позицию не выполнен: слабый импульс вверх. +``` + +или: + +```text +[DEMO] Вход в позицию не выполнен: рынок сейчас не подходит для входа. +``` + +Это позволяет оставить автоторговлю работать долго и потом понять, почему за период не было сделок. + +--- + +# 6. Добавлена защита от spam logging + +Причины не входа не пишутся в журнал на каждом цикле. + +Система пишет событие только тогда, когда причина изменилась. + +Например, если бот 30 минут подряд не входит из-за слабого импульса, журнал не будет получать одинаковые записи каждые 5 секунд. + +Если причина изменилась, например: + +- было: слабый импульс вверх +- стало: рынок без выраженного направления + +тогда будет записано новое событие. + +Это сохраняет журнал полезным и не превращает его в поток повторяющихся HOLD-сообщений. + +--- + +# 7. Auto UI начал показывать причину не входа + +На экране автоторговли добавлена строка диагностики: + +```text +Вход в позицию · слабый импульс вверх +``` + +или: + +```text +Вход в позицию · рынок сейчас не подходит для входа +``` + +Теперь пользователь видит не просто `HOLD`, а понимает, почему бот ждёт. + +--- + +# 8. Strategy diagnostics отделены от execution diagnostics + +В UI теперь логически разделены два разных типа причин. + +## Entry diagnostics + +Отвечает на вопрос: + +```text +Почему стратегия не хочет входить в позицию? +``` + +Примеры: + +- рынок не подходит +- слабый импульс +- недостаточно данных + +## Execution diagnostics + +Отвечает на вопрос: + +```text +Почему позиция не может быть технически открыта? +``` + +Примеры: + +- ограничение Max Reserved +- execution block +- невозможность рассчитать размер позиции + +Это разделение важно, потому что раньше разные причины могли восприниматься как одна проблема. + +--- + +# 9. UI-тексты приведены к единому human-readable стилю + +Устаревшие технические строки были приведены к более понятному виду. + +Например: + +- `Blocked · ...` заменено на `Исполнение · ...` +- `Size adjusted by Max Reserved` заменено на `Позиция ограничена настройкой Max Reserved.` +- `Вход не выполнен` уточнено до `Вход в позицию не выполнен` + +Это делает интерфейс понятнее для пользователя, который не обязан знать внутреннюю архитектуру execution layer. + +--- + +# 10. Добавлен event title для entry diagnostics + +В общий mapping заголовков событий добавлен event title: + +```python +"entry_blocked": "Вход" +``` + +Так журнал сохраняет короткую структуру: + +```text +Вход | [DEMO] Вход в позицию не выполнен: слабый импульс вверх. +``` + +Заголовок остаётся коротким, а смысл раскрывается в сообщении. + +--- + +# Изменения в архитектуре + +## Strategy Layer + +`TrendStrategy` теперь не только формирует торговый сигнал, но и объясняет причины отказа от входа. + +Добавлены диагностические payload-поля: + +- `entry_block_reason` +- `entry_block_message` + +--- + +## Auto Runtime Layer + +`AutoTradeService` теперь синхронизирует entry diagnostics в runtime state и журнал. + +Добавлено антиспам-логирование причин не входа. + +--- + +## Auto State Layer + +`AutoTradeState` расширен полями диагностики входа: + +- `entry_block_reason` +- `entry_block_message` + +--- + +## Auto UI Layer + +На экран автоторговли добавлено отображение причины не входа. + +Также execution diagnostics приведён к более понятному стилю. + +--- + +## Journal Layer + +Добавлено событие: + +- `entry_blocked` + +Событие пишет только изменение причины отказа от входа, а не каждый HOLD-цикл. + +--- + +# Что изменилось для пользователя + +Пользователь теперь видит не просто: + +```text +Сигнал HOLD +``` + +А получает объяснение: + +```text +Сигнал HOLD +Вход в позицию · слабый импульс вверх +``` + +или: + +```text +Сигнал HOLD +Вход в позицию · рынок сейчас не подходит для входа +``` + +Также пользователь может открыть журнал и увидеть, почему бот не входил в позицию в течение длительного времени. + +Это особенно важно для paper trading и подготовки к реальной торговле. + +--- + +# Почему это важно для стратегии + +До этого этапа было трудно отличить нормальное ожидание от проблемы. + +Теперь можно понять: + +- стратегия слишком строгая +- market filter слишком часто блокирует вход +- live threshold слишком высокий +- direction ratio слишком требовательный +- не хватает накопленных live-данных +- execution layer блокирует уже готовый сигнал + +Это создаёт основу для дальнейшей настройки торговой стратегии не на ощущениях, а по фактическим причинам отказа от входа. + +--- + +# Что подготовлено дальше + +Этап подготовил основу для следующих работ: + +- анализ частоты причин отказа от входа +- adaptive thresholds +- настройка чувствительности TrendStrategy +- очистка `_price_window` при смене актива +- signal aging / signal reset +- multi-symbol runtime isolation +- entry diagnostics filters в журнале +- статистика HOLD-причин +- AI-комментарии по причинам не входа