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)
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:

View File

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

View File

@@ -26,4 +26,8 @@ class BaseStrategy(Protocol):
# выполнить анализ и вернуть торговый сигнал
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,
"strategy": self.name,
},
)
)
def reset_runtime(self, symbol: str | None = None) -> None:
return

View File

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

View File

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

View File

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