07.4.4.1.4 Entry Decision Diagnostics Layer
This commit is contained in:
@@ -31,6 +31,9 @@ EVENT_TITLES = {
|
||||
"market_stream_disconnected": "Рынок",
|
||||
"market_symbol_changed": "Рынок",
|
||||
|
||||
# Мониторинг позиций
|
||||
"entry_blocked": "Вход в позицию",
|
||||
|
||||
# Журнал
|
||||
"journal_exported": "Журнал",
|
||||
"journal_export_error": "Журнал",
|
||||
|
||||
@@ -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"<b>Доступно</b> · $ {_format_money_compact(available)}",
|
||||
"",
|
||||
_signal_line(state),
|
||||
_market_state_line(state),
|
||||
*_signal_confidence_lines(state),
|
||||
*_execution_block_lines(state),
|
||||
*signal_lines,
|
||||
"",
|
||||
"🧾 <b>Подготовка ордера</b>",
|
||||
"",
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
*,
|
||||
|
||||
@@ -119,4 +119,10 @@ class AutoTradeState:
|
||||
market_analysis_interval: str | None = None
|
||||
|
||||
# объяснение последнего анализа рынка
|
||||
market_analysis_reason: str | None = None
|
||||
market_analysis_reason: str | None = None
|
||||
|
||||
# код причины, почему вход в позицию сейчас не выполнен
|
||||
entry_block_reason: str | None = None
|
||||
|
||||
# человекочитаемое объяснение причины не входа
|
||||
entry_block_message: str | None = None
|
||||
@@ -92,6 +92,8 @@ class TrendStrategy:
|
||||
payload={
|
||||
**base_payload,
|
||||
"market_filter_blocked": True,
|
||||
"entry_block_reason": "MARKET_FILTER_BLOCKED",
|
||||
"entry_block_message": "рынок сейчас не подходит для входа",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
342
docs/stages/stage-07_4_4_1_4-entry_decision_diagnostics_layer.md
Normal file
342
docs/stages/stage-07_4_4_1_4-entry_decision_diagnostics_layer.md
Normal file
@@ -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-комментарии по причинам не входа
|
||||
Reference in New Issue
Block a user