07.4.4.1.8.1 — Spread Hysteresis Layer

This commit is contained in:
2026-05-11 20:39:13 +03:00
parent eb40ecc4dd
commit 9ba1297c46
4 changed files with 396 additions and 16 deletions

View File

@@ -45,10 +45,50 @@ class AutoTradeService:
_max_snapshot_age_seconds = 5.0 _max_snapshot_age_seconds = 5.0
_warning_snapshot_age_seconds = 2.0 _warning_snapshot_age_seconds = 2.0
_max_spread_percent = 0.15 _spread_warning_enter_percent = 0.08
_warning_spread_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 _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 # debug: принудительно выставить сигнал и decision
def debug_force_signal( def debug_force_signal(
self, self,
@@ -995,7 +1035,7 @@ class AutoTradeService:
else: else:
state.execution_quality = "BLOCKED" state.execution_quality = "BLOCKED"
state.execution_quality_reason = "SNAPSHOT_ERROR" state.execution_quality_reason = "SNAPSHOT_ERROR"
state.execution_quality_message = "нет market data" state.execution_quality_message = "нет данных рынка"
state.market_runtime_degraded = True state.market_runtime_degraded = True
self._log_execution_quality_if_changed( self._log_execution_quality_if_changed(
@@ -1027,23 +1067,22 @@ class AutoTradeService:
state.execution_quality_message = "snapshot устарел" state.execution_quality_message = "snapshot устарел"
state.market_runtime_degraded = True 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: elif age_seconds is not None and age_seconds > self._warning_snapshot_age_seconds:
state.execution_quality = "WARNING" state.execution_quality = "WARNING"
state.execution_quality_reason = "AGING_SNAPSHOT" state.execution_quality_reason = "AGING_SNAPSHOT"
state.execution_quality_message = "snapshot стареет" state.execution_quality_message = "snapshot стареет"
state.market_runtime_degraded = not is_fresh state.market_runtime_degraded = not is_fresh
elif state.spread_percent is not None and state.spread_percent > self._warning_spread_percent: elif state.spread_percent is not None:
state.execution_quality = "WARNING" (
state.execution_quality_reason = "WIDE_SPREAD" state.execution_quality,
state.execution_quality_message = "spread повышен" state.execution_quality_reason,
state.market_runtime_degraded = False state.execution_quality_message,
state.market_runtime_degraded,
) = self._spread_execution_quality(
state=state,
spread_percent=state.spread_percent,
)
else: else:
state.execution_quality = "GOOD" state.execution_quality = "GOOD"
@@ -1075,8 +1114,10 @@ class AutoTradeService:
"market_runtime_degraded": state.market_runtime_degraded, "market_runtime_degraded": state.market_runtime_degraded,
"max_snapshot_age_seconds": self._max_snapshot_age_seconds, "max_snapshot_age_seconds": self._max_snapshot_age_seconds,
"warning_snapshot_age_seconds": self._warning_snapshot_age_seconds, "warning_snapshot_age_seconds": self._warning_snapshot_age_seconds,
"max_spread_percent": self._max_spread_percent, "spread_warning_enter_percent": self._spread_warning_enter_percent,
"warning_spread_percent": self._warning_spread_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,
}, },
) )

View File

@@ -613,6 +613,28 @@
- выявлена необходимость Spread Hysteresis Layer - выявлена необходимость Spread Hysteresis Layer
- подготовлен следующий этап 07.4.4.1.8.1 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 ### 07.4.5

View File

@@ -589,6 +589,28 @@
- выявлена необходимость Spread Hysteresis Layer - выявлена необходимость Spread Hysteresis Layer
- подготовлен следующий этап 07.4.4.1.8.1 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 ### 07.4.5

View File

@@ -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.