07.4.4.1.5 — Runtime Window Cleanup & Symbol Lifecycle Isolation

This commit is contained in:
2026-05-11 12:06:00 +03:00
parent 363719cc8e
commit e17f847603
10 changed files with 323 additions and 14 deletions

View File

@@ -116,18 +116,18 @@ class MarketDataRunner:
): ):
MarketPriceCache.clear(cache_symbol) MarketPriceCache.clear(cache_symbol)
if previous_symbol is not None: #if previous_symbol is not None:
cls._log_info( # cls._log_info(
context, # context,
"market_symbol_changed", # "market_symbol_changed",
f"Инструмент автоторговли изменён: {cache_symbol}.", # f"Инструмент автоторговли изменён: {cache_symbol}.",
{ # {
"previous_symbol": previous_symbol, # "previous_symbol": previous_symbol,
"symbol": symbol, # "symbol": symbol,
"cache_symbol": cache_symbol, # "cache_symbol": cache_symbol,
"ws_symbol": ws_symbol, # "ws_symbol": ws_symbol,
}, # },
) # )
try: try:
await cls._run_websocket(context, symbol) await cls._run_websocket(context, symbol)
except asyncio.CancelledError: except asyncio.CancelledError:

View File

@@ -229,15 +229,28 @@ class AutoTradeService:
# установить инструмент # установить инструмент
def set_symbol(self, symbol: str) -> AutoTradeState: def set_symbol(self, symbol: str) -> AutoTradeState:
state = self.get_state() state = self.get_state()
previous_symbol = state.symbol
state.symbol = symbol state.symbol = symbol
self._reset_signal_tracking() self._reset_signal_tracking()
StrategyRegistry.reset_runtime(symbol=previous_symbol)
StrategyRegistry.reset_runtime(symbol=symbol)
return state return state
# установить стратегию # установить стратегию
def set_strategy(self, strategy: str) -> AutoTradeState: def set_strategy(self, strategy: str) -> AutoTradeState:
state = self.get_state() state = self.get_state()
state.strategy = strategy.strip().upper() previous_strategy = state.strategy
normalized_strategy = strategy.strip().upper()
state.strategy = normalized_strategy
self._reset_signal_tracking() self._reset_signal_tracking()
StrategyRegistry.reset_runtime(previous_strategy)
StrategyRegistry.reset_runtime(normalized_strategy)
return state return state
# установить риск # установить риск
@@ -297,6 +310,13 @@ class AutoTradeService:
state.is_signal_ready = False state.is_signal_ready = False
state.execution_block_reason = None state.execution_block_reason = None
state.signal_started_at = None state.signal_started_at = None
state.market_state = None
state.market_trend = None
state.market_volatility = None
state.market_analysis_interval = None
state.market_analysis_reason = None
state.entry_block_reason = None
state.entry_block_message = None
# собрать контекст для стратегии # собрать контекст для стратегии
def _build_strategy_context(self) -> StrategyContext: def _build_strategy_context(self) -> StrategyContext:

View File

@@ -26,4 +26,8 @@ class BaseStrategy(Protocol):
# выполнить анализ и вернуть торговый сигнал # выполнить анализ и вернуть торговый сигнал
def analyze(self, context: StrategyContext) -> SignalResult: def analyze(self, context: StrategyContext) -> SignalResult:
...
# сбросить runtime-память стратегии
def reset_runtime(self, symbol: str | None = None) -> None:
... ...

View File

@@ -20,4 +20,7 @@ class HoldStrategy:
"status": context.status, "status": context.status,
"strategy": self.name, "strategy": self.name,
}, },
) )
def reset_runtime(self, symbol: str | None = None) -> None:
return

View File

@@ -17,6 +17,27 @@ class StrategyRegistry:
"SCALP": ScalpStrategy(), "SCALP": ScalpStrategy(),
} }
# сбросить runtime-память одной стратегии
@classmethod
def reset_runtime(
cls,
name: str | None = None,
*,
symbol: str | None = None,
) -> None:
if name:
strategy = cls.get(name)
strategy.reset_runtime(symbol)
return
for strategy in cls._strategies.values():
strategy.reset_runtime(symbol)
# сбросить runtime-память всех стратегий
@classmethod
def reset_all_runtime(cls) -> None:
cls.reset_runtime()
# получить стратегию по имени # получить стратегию по имени
@classmethod @classmethod
def get(cls, name: str | None) -> BaseStrategy: def get(cls, name: str | None) -> BaseStrategy:

View File

@@ -21,6 +21,20 @@ class ScalpStrategy:
# для scalp допускаем чуть больше шума # для scalp допускаем чуть больше шума
_min_direction_ratio = 0.55 _min_direction_ratio = 0.55
def reset_runtime(self, symbol: str | None = None) -> None:
if symbol is None:
self._price_window.clear()
return
normalized_symbol = symbol.upper()
keys_to_delete = [
key for key in self._price_window.keys()
if key.upper() == normalized_symbol
]
for key in keys_to_delete:
self._price_window.pop(key, None)
def analyze(self, context: StrategyContext) -> SignalResult: def analyze(self, context: StrategyContext) -> SignalResult:
try: try:
ticker = ExchangeService().get_price(context.symbol) ticker = ExchangeService().get_price(context.symbol)

View File

@@ -22,6 +22,20 @@ class TrendStrategy:
# основной таймфрейм анализа рынка # основной таймфрейм анализа рынка
_market_interval = "5m" _market_interval = "5m"
def reset_runtime(self, symbol: str | None = None) -> None:
if symbol is None:
self._price_window.clear()
return
normalized_symbol = symbol.upper()
keys_to_delete = [
key for key in self._price_window.keys()
if key.upper() == normalized_symbol
]
for key in keys_to_delete:
self._price_window.pop(key, None)
def analyze(self, context: StrategyContext) -> SignalResult: def analyze(self, context: StrategyContext) -> SignalResult:
market = MarketAnalysisService().analyze( market = MarketAnalysisService().analyze(
context.symbol, context.symbol,

View File

@@ -467,6 +467,32 @@
- подготовлена база для анализа частоты причин отказа от входа - подготовлена база для анализа частоты причин отказа от входа
- подготовлена база для adaptive thresholds и настройки чувствительности стратегии - подготовлена база для adaptive thresholds и настройки чувствительности стратегии
#### 07.4.4.1.5 ✅ Runtime Window Cleanup & Symbol Lifecycle Isolation
- внедрён lifecycle cleanup runtime-окон стратегий
- BaseStrategy расширен методом reset_runtime()
- TrendStrategy и ScalpStrategy получили runtime cleanup API
- StrategyRegistry получил reset_runtime() и reset_all_runtime()
- устранено смешивание _price_window между разными активами
- устранено смешивание runtime между TREND и SCALP
- runtime окна теперь изолированы по symbol lifecycle
- runtime окна теперь изолированы по strategy lifecycle
- при смене symbol очищается runtime старого и нового актива
- при смене strategy очищается runtime обеих стратегий
- устранены ложные BUY/SELL после переключения актива
- устранены ложные сигналы после смены стратегии
- AutoTradeService теперь полностью сбрасывает market diagnostics при reset
- очищаются market_state / market_trend / market_volatility
- очищаются market_analysis_interval / market_analysis_reason
- очищаются entry_block_reason / entry_block_message
- устранено визуальное залипание TREND_UP / TREND_DOWN в UI
- после смены актива UI возвращается в состояние “⏳ Идёт анализ”
- удалено дублирующее journal событие market_symbol_changed
- journal приведён к single-result event модели
- runtime lifecycle отделён от user-facing settings events
- подготовлена база для multi-symbol runtime engine
- подготовлена база для signal aging/reset system
- подготовлена база для adaptive runtime memory management
--- ---
### 07.4.5 ### 07.4.5

View File

@@ -443,6 +443,33 @@
- подготовлена база для анализа частоты причин отказа от входа - подготовлена база для анализа частоты причин отказа от входа
- подготовлена база для adaptive thresholds и настройки чувствительности стратегии - подготовлена база для adaptive thresholds и настройки чувствительности стратегии
#### 07.4.4.1.5 ✅ Runtime Window Cleanup & Symbol Lifecycle Isolation
- внедрён lifecycle cleanup runtime-окон стратегий
- BaseStrategy расширен методом reset_runtime()
- TrendStrategy и ScalpStrategy получили runtime cleanup API
- StrategyRegistry получил reset_runtime() и reset_all_runtime()
- устранено смешивание _price_window между разными активами
- устранено смешивание runtime между TREND и SCALP
- runtime окна теперь изолированы по symbol lifecycle
- runtime окна теперь изолированы по strategy lifecycle
- при смене symbol очищается runtime старого и нового актива
- при смене strategy очищается runtime обеих стратегий
- устранены ложные BUY/SELL после переключения актива
- устранены ложные сигналы после смены стратегии
- AutoTradeService теперь полностью сбрасывает market diagnostics при reset
- очищаются market_state / market_trend / market_volatility
- очищаются market_analysis_interval / market_analysis_reason
- очищаются entry_block_reason / entry_block_message
- устранено визуальное залипание TREND_UP / TREND_DOWN в UI
- после смены актива UI возвращается в состояние “⏳ Идёт анализ”
- удалено дублирующее journal событие market_symbol_changed
- journal приведён к single-result event модели
- runtime lifecycle отделён от user-facing settings events
- подготовлена база для multi-symbol runtime engine
- подготовлена база для signal aging/reset system
- подготовлена база для adaptive runtime memory management
--- ---
### 07.4.5 ### 07.4.5

View File

@@ -0,0 +1,180 @@
# 07.4.4.1.5 — Runtime Window Cleanup & Symbol Lifecycle Isolation
## Цель этапа
Устранить смешивание runtime-состояния между:
- разными активами,
- разными стратегиями,
- разными lifecycle-сессиями автоторговли.
Этап стабилизирует:
- `_price_window`,
- signal tracking,
- market diagnostics,
- runtime memory.
---
# Проблема до внедрения
До этапа `07.4.4.1.5` runtime-окна стратегий жили бесконечно:
```python
_price_window: dict[str, list[float]]
```
Из-за этого возникали проблемы:
## 1. Смешивание активов
```text
BTC → накопилось 8 цен
переключение на ETH
TREND уже имеет готовое окно
ложный BUY/SELL
```
## 2. Смешивание стратегий
```text
TREND накопил runtime
SCALP получил старый runtime
моментальный сигнал
```
## 3. Залипание market state
После смены актива UI мог показывать:
```text
📉 Тренд · Нисходящий
```
хотя новый актив ещё не анализировался.
## 4. Дублирование journal-событий
Одно изменение актива вызывало:
- runtime market event,
- user settings event.
Журнал становился шумным.
---
# Что внедрено
## 1. Runtime lifecycle API стратегий
Добавлен lifecycle hook:
```python
def reset_runtime(self, symbol: str | None = None) -> None
```
## 2. Runtime cleanup в TREND и SCALP
Теперь стратегии умеют:
- очищать live runtime,
- сбрасывать окна,
- изолировать symbol runtime.
## 3. Runtime cleanup registry
В `StrategyRegistry` добавлены:
```python
reset_runtime(...)
reset_all_runtime()
```
## 4. Symbol lifecycle isolation
При смене symbol:
```python
StrategyRegistry.reset_runtime(previous_symbol)
StrategyRegistry.reset_runtime(new_symbol)
```
### Результат
```text
BTC runtime уничтожается
ETH runtime стартует с нуля
```
## 5. Strategy lifecycle isolation
При смене стратегии:
```python
StrategyRegistry.reset_runtime(previous_strategy)
StrategyRegistry.reset_runtime(new_strategy)
```
### Результат
- не наследуются старые окна,
- не переносятся старые сигналы,
- не переносится momentum.
## 6. Signal lifecycle cleanup
Теперь очищаются:
```python
market_state
market_trend
market_volatility
market_analysis_interval
market_analysis_reason
entry_block_reason
entry_block_message
```
### Результат
После смены актива UI показывает:
```text
⏳ Рынок · Идёт анализ
```
## 7. Journal cleanup
Удалено runtime событие:
```text
market_symbol_changed
```
Пользователю остаётся только итоговое событие:
```text
Автоторговля | Актив изменён: ETH
```
---
# Итог архитектуры
## До этапа
```text
Strategy runtime = бесконечная память
```
## После этапа
```text
Runtime привязан к:
- symbol
- strategy
- lifecycle session
```