07.4.4.1.5 — Runtime Window Cleanup & Symbol Lifecycle Isolation
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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:
|
||||||
...
|
...
|
||||||
@@ -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
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user