07.4.4.1.8.1 — Spread Hysteresis Layer
This commit is contained in:
@@ -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,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
295
docs/stages/stage-07_4_4_1_8_1-spread_hysteresis_layer.md
Normal file
295
docs/stages/stage-07_4_4_1_8_1-spread_hysteresis_layer.md
Normal 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.
|
||||||
Reference in New Issue
Block a user