From 9ba1297c467c0db908ead28bfc5536a2dca0cc00 Mon Sep 17 00:00:00 2001 From: Sergey Date: Mon, 11 May 2026 20:39:13 +0300 Subject: [PATCH] =?UTF-8?q?07.4.4.1.8.1=20=E2=80=94=20Spread=20Hysteresis?= =?UTF-8?q?=20Layer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/trading/auto/service.py | 73 ++++- docs/roadmap/master-roadmap.md | 22 ++ docs/roadmap/stage-07-auto-trading-roadmap.md | 22 ++ ...ge-07_4_4_1_8_1-spread_hysteresis_layer.md | 295 ++++++++++++++++++ 4 files changed, 396 insertions(+), 16 deletions(-) create mode 100644 docs/stages/stage-07_4_4_1_8_1-spread_hysteresis_layer.md diff --git a/app/src/trading/auto/service.py b/app/src/trading/auto/service.py index 0db3b30..f8d8056 100644 --- a/app/src/trading/auto/service.py +++ b/app/src/trading/auto/service.py @@ -45,10 +45,50 @@ class AutoTradeService: _max_snapshot_age_seconds = 5.0 _warning_snapshot_age_seconds = 2.0 - _max_spread_percent = 0.15 - _warning_spread_percent = 0.08 + _spread_warning_enter_percent = 0.08 + _spread_warning_exit_percent = 0.06 + _spread_block_enter_percent = 0.15 + _spread_block_exit_percent = 0.12 _last_logged_execution_quality_key: str | None = None + def _spread_execution_quality( + self, + *, + state: AutoTradeState, + spread_percent: float | None, + ) -> tuple[str | None, str | None, str | None, bool]: + if spread_percent is None: + return None, None, None, False + + previous_quality = state.execution_quality + previous_reason = state.execution_quality_reason + + if previous_quality == "BLOCKED" and previous_reason == "HIGH_SPREAD": + if spread_percent > self._spread_block_exit_percent: + return "BLOCKED", "HIGH_SPREAD", "высокий spread", False + + if spread_percent > self._spread_warning_exit_percent: + return "WARNING", "WIDE_SPREAD", "spread повышен", False + + return "GOOD", "MARKET_OK", "рынок готов", False + + if previous_quality == "WARNING" and previous_reason == "WIDE_SPREAD": + if spread_percent >= self._spread_block_enter_percent: + return "BLOCKED", "HIGH_SPREAD", "высокий spread", False + + if spread_percent > self._spread_warning_exit_percent: + return "WARNING", "WIDE_SPREAD", "spread повышен", False + + return "GOOD", "MARKET_OK", "рынок готов", False + + if spread_percent >= self._spread_block_enter_percent: + return "BLOCKED", "HIGH_SPREAD", "высокий spread", False + + if spread_percent >= self._spread_warning_enter_percent: + return "WARNING", "WIDE_SPREAD", "spread повышен", False + + return "GOOD", "MARKET_OK", "рынок готов", False + # debug: принудительно выставить сигнал и decision def debug_force_signal( self, @@ -995,7 +1035,7 @@ class AutoTradeService: else: state.execution_quality = "BLOCKED" state.execution_quality_reason = "SNAPSHOT_ERROR" - state.execution_quality_message = "нет market data" + state.execution_quality_message = "нет данных рынка" state.market_runtime_degraded = True self._log_execution_quality_if_changed( @@ -1027,23 +1067,22 @@ class AutoTradeService: state.execution_quality_message = "snapshot устарел" state.market_runtime_degraded = True - elif state.spread_percent is not None and state.spread_percent > self._max_spread_percent: - state.execution_quality = "BLOCKED" - state.execution_quality_reason = "HIGH_SPREAD" - state.execution_quality_message = "высокий spread" - state.market_runtime_degraded = False - elif age_seconds is not None and age_seconds > self._warning_snapshot_age_seconds: state.execution_quality = "WARNING" state.execution_quality_reason = "AGING_SNAPSHOT" state.execution_quality_message = "snapshot стареет" state.market_runtime_degraded = not is_fresh - elif state.spread_percent is not None and state.spread_percent > self._warning_spread_percent: - state.execution_quality = "WARNING" - state.execution_quality_reason = "WIDE_SPREAD" - state.execution_quality_message = "spread повышен" - state.market_runtime_degraded = False + elif state.spread_percent is not None: + ( + state.execution_quality, + state.execution_quality_reason, + state.execution_quality_message, + state.market_runtime_degraded, + ) = self._spread_execution_quality( + state=state, + spread_percent=state.spread_percent, + ) else: state.execution_quality = "GOOD" @@ -1075,8 +1114,10 @@ class AutoTradeService: "market_runtime_degraded": state.market_runtime_degraded, "max_snapshot_age_seconds": self._max_snapshot_age_seconds, "warning_snapshot_age_seconds": self._warning_snapshot_age_seconds, - "max_spread_percent": self._max_spread_percent, - "warning_spread_percent": self._warning_spread_percent, + "spread_warning_enter_percent": self._spread_warning_enter_percent, + "spread_warning_exit_percent": self._spread_warning_exit_percent, + "spread_block_enter_percent": self._spread_block_enter_percent, + "spread_block_exit_percent": self._spread_block_exit_percent, }, ) diff --git a/docs/roadmap/master-roadmap.md b/docs/roadmap/master-roadmap.md index ddecc9d..49447e7 100644 --- a/docs/roadmap/master-roadmap.md +++ b/docs/roadmap/master-roadmap.md @@ -613,6 +613,28 @@ - выявлена необходимость Spread Hysteresis Layer - подготовлен следующий этап 07.4.4.1.8.1 Spread Hysteresis Layer +#### 07.4.4.1.8.1 ✅ Spread Hysteresis Layer +- добавлен hysteresis layer для spread quality +- одиночные spread thresholds заменены на enter / exit thresholds +- добавлен порог входа в WARNING spread state +- добавлен порог выхода из WARNING spread state +- добавлен порог входа в BLOCKED spread state +- добавлен порог выхода из BLOCKED spread state +- добавлен helper `_spread_execution_quality` +- spread quality теперь учитывает предыдущее execution_quality состояние +- предотвращено мигание GOOD / WARNING около warning spread threshold +- предотвращено мигание WARNING / BLOCKED около block spread threshold +- execution gate стал устойчивее к микроколебаниям bid/ask +- Telegram UI стал стабильнее при пограничном spread +- состояние `Вход · высокий spread` теперь снимается только после нормализации spread +- состояние `Рынок · spread` теперь не исчезает от единичного тика +- снижено количество шумных execution_quality_changed событий +- исправлена типизация return value для market_runtime_degraded +- подтверждена стабильная работа на LTC +- подготовлена база для symbol-specific spread profiles +- подготовлена база для volatility-aware spread thresholds +- подготовлена база для adaptive execution quality model + --- ### 07.4.5 diff --git a/docs/roadmap/stage-07-auto-trading-roadmap.md b/docs/roadmap/stage-07-auto-trading-roadmap.md index 074a558..32f93f5 100644 --- a/docs/roadmap/stage-07-auto-trading-roadmap.md +++ b/docs/roadmap/stage-07-auto-trading-roadmap.md @@ -589,6 +589,28 @@ - выявлена необходимость Spread Hysteresis Layer - подготовлен следующий этап 07.4.4.1.8.1 Spread Hysteresis Layer +#### 07.4.4.1.8.1 ✅ Spread Hysteresis Layer +- добавлен hysteresis layer для spread quality +- одиночные spread thresholds заменены на enter / exit thresholds +- добавлен порог входа в WARNING spread state +- добавлен порог выхода из WARNING spread state +- добавлен порог входа в BLOCKED spread state +- добавлен порог выхода из BLOCKED spread state +- добавлен helper `_spread_execution_quality` +- spread quality теперь учитывает предыдущее execution_quality состояние +- предотвращено мигание GOOD / WARNING около warning spread threshold +- предотвращено мигание WARNING / BLOCKED около block spread threshold +- execution gate стал устойчивее к микроколебаниям bid/ask +- Telegram UI стал стабильнее при пограничном spread +- состояние `Вход · высокий spread` теперь снимается только после нормализации spread +- состояние `Рынок · spread` теперь не исчезает от единичного тика +- снижено количество шумных execution_quality_changed событий +- исправлена типизация return value для market_runtime_degraded +- подтверждена стабильная работа на LTC +- подготовлена база для symbol-specific spread profiles +- подготовлена база для volatility-aware spread thresholds +- подготовлена база для adaptive execution quality model + --- ### 07.4.5 diff --git a/docs/stages/stage-07_4_4_1_8_1-spread_hysteresis_layer.md b/docs/stages/stage-07_4_4_1_8_1-spread_hysteresis_layer.md new file mode 100644 index 0000000..d4f03ba --- /dev/null +++ b/docs/stages/stage-07_4_4_1_8_1-spread_hysteresis_layer.md @@ -0,0 +1,295 @@ +# 07.4.4.1.8.1 — Spread Hysteresis Layer + +## Статус +✅ Завершено + +## Цель этапа + +Этап 07.4.4.1.8.1 добавляет hysteresis-механику для spread quality layer, чтобы убрать дребезг состояния рынка около пороговых значений spread. + +До этого execution quality могла быстро переключаться между состояниями `GOOD`, `WARNING` и `BLOCKED`, если spread колебался рядом с границами `0.08%` и `0.15%`. Это приводило к визуальному миганию Telegram UI и нестабильному поведению execution gate. + +После внедрения hysteresis состояние меняется не по одной границе, а по отдельным порогам входа и выхода. + +--- + +## Что было сделано + +### 1. Заменены одиночные spread-пороги на hysteresis-пары + +Ранее использовались два порога: + +```python +_warning_spread_percent = 0.08 +_max_spread_percent = 0.15 +``` + +Теперь spread quality использует четыре значения: + +```python +_spread_warning_enter_percent = 0.08 +_spread_warning_exit_percent = 0.06 + +_spread_block_enter_percent = 0.15 +_spread_block_exit_percent = 0.12 +``` + +Это позволяет системе отдельно понимать: + +- когда нужно перейти из `GOOD` в `WARNING`; +- когда можно вернуться из `WARNING` в `GOOD`; +- когда нужно перейти из `WARNING`/`GOOD` в `BLOCKED`; +- когда можно выйти из `BLOCKED` обратно в `WARNING`. + +--- + +## Новая логика spread hysteresis + +### GOOD → WARNING + +Если рынок был нормальным, предупреждение появляется только когда spread достигает warning enter threshold: + +```text +spread >= 0.08% → WARNING +``` + +### WARNING → GOOD + +Если рынок уже был в состоянии предупреждения, обратно в норму он возвращается только когда spread опускается ниже exit threshold: + +```text +spread <= 0.06% → GOOD +``` + +Это защищает UI от частого переключения около `0.08%`. + +--- + +### WARNING → BLOCKED + +Execution block включается только когда spread достигает block enter threshold: + +```text +spread >= 0.15% → BLOCKED +``` + +### BLOCKED → WARNING + +После блокировки вход не разблокируется сразу при снижении spread до `0.149%`. Для выхода из BLOCKED spread должен снизиться до block exit threshold: + +```text +spread <= 0.12% → WARNING +``` + +Это защищает execution pipeline от дребезга около `0.15%`. + +--- + +## Добавлен helper `_spread_execution_quality` + +В `AutoTradeService` добавлен отдельный метод: + +```python +def _spread_execution_quality( + self, + *, + state: AutoTradeState, + spread_percent: float | None, +) -> tuple[str | None, str | None, str | None, bool]: +``` + +Он отвечает только за spread-based quality decision и учитывает предыдущее состояние: + +- `state.execution_quality` +- `state.execution_quality_reason` + +Это важно, потому что hysteresis не может работать только от текущего spread. Ему нужно знать, в каком состоянии система была до текущего цикла. + +--- + +## Поведение helper-а + +### Если предыдущий статус был `BLOCKED / HIGH_SPREAD` + +Система остаётся в `BLOCKED`, пока spread выше exit-порога: + +```text +spread > 0.12% → BLOCKED +``` + +Если spread снизился ниже block exit, но всё ещё выше warning exit: + +```text +0.06% < spread <= 0.12% → WARNING +``` + +Если spread снизился достаточно сильно: + +```text +spread <= 0.06% → GOOD +``` + +--- + +### Если предыдущий статус был `WARNING / WIDE_SPREAD` + +Система переходит в `BLOCKED`, только если spread дошёл до block enter: + +```text +spread >= 0.15% → BLOCKED +``` + +Остаётся в `WARNING`, пока spread выше warning exit: + +```text +spread > 0.06% → WARNING +``` + +Возвращается в `GOOD`, только если spread опустился до нормального уровня: + +```text +spread <= 0.06% → GOOD +``` + +--- + +### Если предыдущего spread-состояния не было + +Для первого расчёта используется обычная enter-логика: + +```text +spread >= 0.15% → BLOCKED +spread >= 0.08% → WARNING +spread < 0.08% → GOOD +``` + +--- + +## Обновлена `_sync_execution_quality_state` + +Вместо прямой проверки: + +```python +spread > _max_spread_percent +spread > _warning_spread_percent +``` + +теперь используется: + +```python +self._spread_execution_quality( + state=state, + spread_percent=state.spread_percent, +) +``` + +Это сохранило существующую архитектуру execution quality, но добавило устойчивость к рыночному шуму. + +--- + +## Что осталось без изменений + +Этап не менял: + +- `AutoTradeState` структуру; +- Telegram UI layout; +- execution quality labels; +- market snapshot age logic; +- stale snapshot logic; +- execution block behavior; +- `ExecutionEngine`; +- market data runner; +- strategy logic. + +Правка была локализована в `AutoTradeService`. + +--- + +## Почему это важно для торговли + +Spread — это разница между лучшей ценой покупки и лучшей ценой продажи. Чем spread выше, тем дороже входить в позицию и тем больше риск получить плохую цену. + +Без hysteresis возможна ситуация: + +```text +0.149% → WARNING +0.151% → BLOCKED +0.148% → WARNING +0.152% → BLOCKED +``` + +Для трейдинга это плохо, потому что: + +- UI мигает; +- journal получает больше шумных событий; +- execution gate может часто открываться и закрываться; +- пользователь видит нестабильное состояние; +- runtime принимает решения по микроколебаниям, а не по устойчивому состоянию рынка. + +После hysteresis поведение становится стабильнее: + +```text +BLOCKED включился при 0.15% +BLOCKED держится до снижения spread к 0.12% +WARNING держится до снижения spread к 0.06% +``` + +То есть система теперь реагирует не на единичный тик, а на устойчивое изменение качества рынка. + +--- + +## UI-поведение после внедрения + +Telegram UI стал стабильнее: + +```text +⚠️ Рынок · spread 0.10% +``` + +не мигает туда-сюда около `0.08%`. + +А состояние: + +```text +⛔ Вход · высокий spread 0.17% +``` + +не исчезает сразу при минимальном откате spread ниже `0.15%`. + +--- + +## Проверка этапа + +Проверено: + +- `python -m compileall src` должен проходить без ошибок; +- spread warning больше не мигает около `0.08%`; +- spread block больше не мигает около `0.15%`; +- LTC показывает более стабильное состояние рынка; +- UI стал визуально спокойнее; +- execution gate стал устойчивее; +- состояние `Вход · высокий spread` выходит из блокировки только после заметного снижения spread; +- Pylance type warning исправлен через корректный bool return type. + +--- + +## Итог + +Этап 07.4.4.1.8.1 стабилизировал spread-aware execution quality layer. + +Система теперь не переключает качество исполнения при каждом микроколебании spread. Это улучшает UX, снижает шум в runtime diagnostics и делает execution gate ближе к production-grade поведению. + +--- + +## Подготовлена база для следующих этапов + +Этап подготовил основу для: + +- symbol-specific spread profiles; +- volatility-aware spread thresholds; +- adaptive execution quality model; +- smart re-entry after spread normalization; +- cooldown windows после execution block; +- advanced liquidity diagnostics; +- execution quality scoring; +- risk-adjusted order execution.