From e17f8476031b73a77b694cc91ca8052ef96fe606 Mon Sep 17 00:00:00 2001 From: Sergey Date: Mon, 11 May 2026 12:06:00 +0300 Subject: [PATCH] =?UTF-8?q?07.4.4.1.5=20=E2=80=94=20Runtime=20Window=20Cle?= =?UTF-8?q?anup=20&=20Symbol=20Lifecycle=20Isolation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exchange/market_data_runner.py | 24 +-- app/src/trading/auto/service.py | 22 ++- app/src/trading/strategies/base.py | 4 + app/src/trading/strategies/hold.py | 5 +- app/src/trading/strategies/registry.py | 21 ++ app/src/trading/strategies/scalp.py | 14 ++ app/src/trading/strategies/trend.py | 14 ++ docs/roadmap/master-roadmap.md | 26 +++ docs/roadmap/stage-07-auto-trading-roadmap.md | 27 +++ ..._cleanup_and_symbol_lifecycle_isolation.md | 180 ++++++++++++++++++ 10 files changed, 323 insertions(+), 14 deletions(-) create mode 100644 docs/stages/stage-07_4_4_1_5-runtime_window_cleanup_and_symbol_lifecycle_isolation.md diff --git a/app/src/integrations/exchange/market_data_runner.py b/app/src/integrations/exchange/market_data_runner.py index f94cf95..7c59f18 100644 --- a/app/src/integrations/exchange/market_data_runner.py +++ b/app/src/integrations/exchange/market_data_runner.py @@ -116,18 +116,18 @@ class MarketDataRunner: ): MarketPriceCache.clear(cache_symbol) - if previous_symbol is not None: - cls._log_info( - context, - "market_symbol_changed", - f"Инструмент автоторговли изменён: {cache_symbol}.", - { - "previous_symbol": previous_symbol, - "symbol": symbol, - "cache_symbol": cache_symbol, - "ws_symbol": ws_symbol, - }, - ) + #if previous_symbol is not None: + # cls._log_info( + # context, + # "market_symbol_changed", + # f"Инструмент автоторговли изменён: {cache_symbol}.", + # { + # "previous_symbol": previous_symbol, + # "symbol": symbol, + # "cache_symbol": cache_symbol, + # "ws_symbol": ws_symbol, + # }, + # ) try: await cls._run_websocket(context, symbol) except asyncio.CancelledError: diff --git a/app/src/trading/auto/service.py b/app/src/trading/auto/service.py index 4fbb717..03990e3 100644 --- a/app/src/trading/auto/service.py +++ b/app/src/trading/auto/service.py @@ -229,15 +229,28 @@ class AutoTradeService: # установить инструмент def set_symbol(self, symbol: str) -> AutoTradeState: state = self.get_state() + previous_symbol = state.symbol + state.symbol = symbol self._reset_signal_tracking() + + StrategyRegistry.reset_runtime(symbol=previous_symbol) + StrategyRegistry.reset_runtime(symbol=symbol) + return state # установить стратегию def set_strategy(self, strategy: str) -> AutoTradeState: 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() + + StrategyRegistry.reset_runtime(previous_strategy) + StrategyRegistry.reset_runtime(normalized_strategy) + return state # установить риск @@ -297,6 +310,13 @@ class AutoTradeService: state.is_signal_ready = False state.execution_block_reason = 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: diff --git a/app/src/trading/strategies/base.py b/app/src/trading/strategies/base.py index d615ea4..6c48bc8 100644 --- a/app/src/trading/strategies/base.py +++ b/app/src/trading/strategies/base.py @@ -26,4 +26,8 @@ class BaseStrategy(Protocol): # выполнить анализ и вернуть торговый сигнал def analyze(self, context: StrategyContext) -> SignalResult: + ... + + # сбросить runtime-память стратегии + def reset_runtime(self, symbol: str | None = None) -> None: ... \ No newline at end of file diff --git a/app/src/trading/strategies/hold.py b/app/src/trading/strategies/hold.py index 695ebaf..dee944c 100644 --- a/app/src/trading/strategies/hold.py +++ b/app/src/trading/strategies/hold.py @@ -20,4 +20,7 @@ class HoldStrategy: "status": context.status, "strategy": self.name, }, - ) \ No newline at end of file + ) + + def reset_runtime(self, symbol: str | None = None) -> None: + return \ No newline at end of file diff --git a/app/src/trading/strategies/registry.py b/app/src/trading/strategies/registry.py index 13dda73..0211bc7 100644 --- a/app/src/trading/strategies/registry.py +++ b/app/src/trading/strategies/registry.py @@ -17,6 +17,27 @@ class StrategyRegistry: "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 def get(cls, name: str | None) -> BaseStrategy: diff --git a/app/src/trading/strategies/scalp.py b/app/src/trading/strategies/scalp.py index d1af93f..eee41d5 100644 --- a/app/src/trading/strategies/scalp.py +++ b/app/src/trading/strategies/scalp.py @@ -21,6 +21,20 @@ class ScalpStrategy: # для scalp допускаем чуть больше шума _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: try: ticker = ExchangeService().get_price(context.symbol) diff --git a/app/src/trading/strategies/trend.py b/app/src/trading/strategies/trend.py index 244cf5d..986c0c3 100644 --- a/app/src/trading/strategies/trend.py +++ b/app/src/trading/strategies/trend.py @@ -22,6 +22,20 @@ class TrendStrategy: # основной таймфрейм анализа рынка _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: market = MarketAnalysisService().analyze( context.symbol, diff --git a/docs/roadmap/master-roadmap.md b/docs/roadmap/master-roadmap.md index 3ee9742..65802c8 100644 --- a/docs/roadmap/master-roadmap.md +++ b/docs/roadmap/master-roadmap.md @@ -467,6 +467,32 @@ - подготовлена база для анализа частоты причин отказа от входа - подготовлена база для 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 diff --git a/docs/roadmap/stage-07-auto-trading-roadmap.md b/docs/roadmap/stage-07-auto-trading-roadmap.md index 5dfb1ee..c3f466f 100644 --- a/docs/roadmap/stage-07-auto-trading-roadmap.md +++ b/docs/roadmap/stage-07-auto-trading-roadmap.md @@ -443,6 +443,33 @@ - подготовлена база для анализа частоты причин отказа от входа - подготовлена база для 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 diff --git a/docs/stages/stage-07_4_4_1_5-runtime_window_cleanup_and_symbol_lifecycle_isolation.md b/docs/stages/stage-07_4_4_1_5-runtime_window_cleanup_and_symbol_lifecycle_isolation.md new file mode 100644 index 0000000..45e395d --- /dev/null +++ b/docs/stages/stage-07_4_4_1_5-runtime_window_cleanup_and_symbol_lifecycle_isolation.md @@ -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 +```