07.4.4.1.9 Adaptive Market Diagnostics Layer

This commit is contained in:
2026-05-11 22:09:16 +03:00
parent 9ba1297c46
commit 5325ea3855
10 changed files with 500 additions and 7 deletions

View File

@@ -158,6 +158,7 @@ def _build_waiting_text(state) -> str:
signal_lines = [
_signal_line(state),
_market_state_line(state),
_market_diagnostics_line(state),
_entry_block_line(state),
_execution_quality_line(state),
*_signal_confidence_lines(state),
@@ -261,6 +262,49 @@ def _market_state_line(state) -> str:
return labels.get(market_state, "⏳ Рынок · Идёт анализ")
def _market_diagnostics_line(state) -> str:
strength = getattr(state, "market_trend_strength", None)
quality = getattr(state, "market_trend_quality", None)
phase = getattr(state, "market_phase", None)
if not strength and not quality and not phase:
return ""
strength_labels = {
"WEAK": "слабый",
"NORMAL": "нормальный",
"STRONG": "сильный",
}
quality_labels = {
"CLEAN": "чистый",
"NOISY": "шумный",
}
phase_labels = {
"IMPULSE": "импульс",
"PULLBACK": "откат",
"RANGE": "флэт",
"SQUEEZE": "сжатие",
}
parts = []
if strength in strength_labels:
parts.append(strength_labels[strength])
if quality in quality_labels:
parts.append(quality_labels[quality])
if phase in phase_labels:
parts.append(phase_labels[phase])
if not parts:
return ""
return f"Анализ · {' · '.join(parts)}"
def _compact_entry_block_message(message: str) -> str:
normalized = message.strip().lower()

View File

@@ -378,6 +378,9 @@ class AutoTradeService:
state.execution_quality_reason = None
state.execution_quality_message = None
state.market_runtime_degraded = False
state.market_trend_strength = None
state.market_trend_quality = None
state.market_phase = None
# собрать контекст для стратегии
def _build_strategy_context(self) -> StrategyContext:
@@ -746,6 +749,9 @@ class AutoTradeService:
state.market_state = payload.get("market_state")
state.market_trend = payload.get("market_trend")
state.market_volatility = payload.get("market_volatility")
state.market_trend_strength = payload.get("market_trend_strength")
state.market_trend_quality = payload.get("market_trend_quality")
state.market_phase = payload.get("market_phase")
state.market_analysis_interval = payload.get("market_analysis_interval")
state.market_analysis_reason = payload.get("market_analysis_reason")
state.market_analysis_updated_at = time.monotonic()

View File

@@ -115,6 +115,15 @@ class AutoTradeState:
# волатильность: LOW / NORMAL / HIGH / UNKNOWN
market_volatility: str | None = None
# сила тренда: WEAK / NORMAL / STRONG / UNKNOWN
market_trend_strength: str | None = None
# качество тренда: CLEAN / NOISY / UNKNOWN
market_trend_quality: str | None = None
# фаза рынка: IMPULSE / PULLBACK / RANGE / SQUEEZE / UNKNOWN
market_phase: str | None = None
# таймфрейм анализа рынка
market_analysis_interval: str | None = None

View File

@@ -29,6 +29,27 @@ class VolatilityState(StrEnum):
UNKNOWN = "UNKNOWN"
class TrendStrength(StrEnum):
WEAK = "WEAK"
NORMAL = "NORMAL"
STRONG = "STRONG"
UNKNOWN = "UNKNOWN"
class TrendQuality(StrEnum):
CLEAN = "CLEAN"
NOISY = "NOISY"
UNKNOWN = "UNKNOWN"
class MarketPhase(StrEnum):
IMPULSE = "IMPULSE"
PULLBACK = "PULLBACK"
RANGE = "RANGE"
SQUEEZE = "SQUEEZE"
UNKNOWN = "UNKNOWN"
@dataclass(slots=True)
class MarketAnalysisResult:
symbol: str
@@ -50,3 +71,9 @@ class MarketAnalysisResult:
is_trade_allowed: bool
payload: dict
trend_strength: TrendStrength
trend_quality: TrendQuality
market_phase: MarketPhase
trend_gap_percent: float | None
trend_consistency: float | None

View File

@@ -1,13 +1,14 @@
# app/src/trading/market_analysis/service.py
from __future__ import annotations
from src.integrations.exchange.service import ExchangeService
from src.trading.market_analysis.indicators import atr, ema, rsi
from src.trading.market_analysis.models import (
MarketAnalysisResult,
MarketPhase,
MarketState,
TrendDirection,
TrendQuality,
TrendStrength,
VolatilityState,
)
@@ -24,6 +25,7 @@ class MarketAnalysisService:
_high_volatility_atr_percent = 1.8
_trend_gap_percent = 0.03
_trend_consistency_window = 20
def analyze(
self,
@@ -77,12 +79,29 @@ class MarketAnalysisService:
)
atr_percent = (atr_value / close_price) * 100
trend_gap_percent = self._trend_gap_percent_value(
ema_fast=ema_fast,
ema_slow=ema_slow,
)
volatility = self._classify_volatility(atr_percent)
trend = self._classify_trend(
ema_fast=ema_fast,
ema_slow=ema_slow,
)
trend_strength = self._classify_trend_strength(trend_gap_percent)
trend_consistency = self._trend_consistency(
closes=closes,
trend=trend,
)
trend_quality = self._classify_trend_quality(trend_consistency)
market_phase = self._classify_market_phase(
trend=trend,
volatility=volatility,
trend_strength=trend_strength,
trend_quality=trend_quality,
rsi_value=rsi_value,
)
state = self._classify_market_state(
trend=trend,
@@ -123,6 +142,15 @@ class MarketAnalysisService:
"market_state": state.value,
"trend": trend.value,
"volatility": volatility.value,
"market_trend_strength": trend_strength.value,
"market_trend_quality": trend_quality.value,
"market_phase": market_phase.value,
"market_trend_gap_percent": round(trend_gap_percent, 5)
if trend_gap_percent is not None
else None,
"market_trend_consistency": round(trend_consistency, 3)
if trend_consistency is not None
else None,
"close_price": close_price,
"ema_fast_period": self._fast_ema_period,
"ema_slow_period": self._slow_ema_period,
@@ -136,18 +164,37 @@ class MarketAnalysisService:
"candles_count": len(candles),
"is_trade_allowed": is_trade_allowed,
},
trend_strength=trend_strength,
trend_quality=trend_quality,
market_phase=market_phase,
trend_gap_percent=trend_gap_percent,
trend_consistency=trend_consistency,
)
def _trend_gap_percent_value(
self,
*,
ema_fast: float,
ema_slow: float,
) -> float | None:
if ema_slow <= 0:
return None
return ((ema_fast - ema_slow) / ema_slow) * 100
def _classify_trend(
self,
*,
ema_fast: float,
ema_slow: float,
) -> TrendDirection:
if ema_slow <= 0:
return TrendDirection.UNKNOWN
gap_percent = self._trend_gap_percent_value(
ema_fast=ema_fast,
ema_slow=ema_slow,
)
gap_percent = ((ema_fast - ema_slow) / ema_slow) * 100
if gap_percent is None:
return TrendDirection.UNKNOWN
if gap_percent >= self._trend_gap_percent:
return TrendDirection.UP
@@ -157,6 +204,99 @@ class MarketAnalysisService:
return TrendDirection.FLAT
def _classify_trend_strength(
self,
trend_gap_percent: float | None,
) -> TrendStrength:
if trend_gap_percent is None:
return TrendStrength.UNKNOWN
gap = abs(trend_gap_percent)
if gap < 0.08:
return TrendStrength.WEAK
if gap < 0.25:
return TrendStrength.NORMAL
return TrendStrength.STRONG
def _trend_consistency(
self,
*,
closes: list[float],
trend: TrendDirection,
) -> float | None:
if len(closes) < 2:
return None
window = closes[-self._trend_consistency_window :]
if len(window) < 2:
return None
up_moves = 0
down_moves = 0
for previous_price, current_price in zip(window, window[1:]):
if current_price > previous_price:
up_moves += 1
elif current_price < previous_price:
down_moves += 1
total_moves = max(1, len(window) - 1)
if trend == TrendDirection.UP:
return up_moves / total_moves
if trend == TrendDirection.DOWN:
return down_moves / total_moves
return None
def _classify_trend_quality(
self,
trend_consistency: float | None,
) -> TrendQuality:
if trend_consistency is None:
return TrendQuality.UNKNOWN
if trend_consistency >= 0.6:
return TrendQuality.CLEAN
return TrendQuality.NOISY
def _classify_market_phase(
self,
*,
trend: TrendDirection,
volatility: VolatilityState,
trend_strength: TrendStrength,
trend_quality: TrendQuality,
rsi_value: float | None,
) -> MarketPhase:
if volatility == VolatilityState.LOW:
return MarketPhase.SQUEEZE
if trend == TrendDirection.FLAT:
return MarketPhase.RANGE
if trend not in {TrendDirection.UP, TrendDirection.DOWN}:
return MarketPhase.UNKNOWN
if trend_strength == TrendStrength.WEAK:
return MarketPhase.RANGE
if trend_quality == TrendQuality.NOISY:
return MarketPhase.PULLBACK
if trend == TrendDirection.UP and rsi_value is not None and rsi_value < 45:
return MarketPhase.PULLBACK
if trend == TrendDirection.DOWN and rsi_value is not None and rsi_value > 55:
return MarketPhase.PULLBACK
return MarketPhase.IMPULSE
def _classify_volatility(self, atr_percent: float) -> VolatilityState:
if atr_percent <= 0:
return VolatilityState.UNKNOWN
@@ -249,8 +389,18 @@ class MarketAnalysisService:
"market_state": MarketState.UNKNOWN.value,
"trend": TrendDirection.UNKNOWN.value,
"volatility": VolatilityState.UNKNOWN.value,
"market_trend_strength": TrendStrength.UNKNOWN.value,
"market_trend_quality": TrendQuality.UNKNOWN.value,
"market_phase": MarketPhase.UNKNOWN.value,
"market_trend_gap_percent": None,
"market_trend_consistency": None,
"candles_count": candles_count,
"is_trade_allowed": False,
"reason": reason,
},
trend_strength=TrendStrength.UNKNOWN,
trend_quality=TrendQuality.UNKNOWN,
market_phase=MarketPhase.UNKNOWN,
trend_gap_percent=None,
trend_consistency=None,
)

View File

@@ -5,7 +5,12 @@ from __future__ import annotations
import time
from src.integrations.exchange.service import ExchangeService
from src.trading.market_analysis.models import MarketState
from src.trading.market_analysis.models import (
MarketPhase,
MarketState,
TrendQuality,
TrendStrength,
)
from src.trading.market_analysis.service import MarketAnalysisService
from src.trading.strategies.base import StrategyContext
from src.trading.strategies.signals import SignalResult, SignalType
@@ -116,6 +121,11 @@ class TrendStrategy:
"market_analysis_interval": market.interval,
"market_analysis_reason": market.reason,
"market_analysis": market.payload,
"market_trend_strength": market.trend_strength.value,
"market_trend_quality": market.trend_quality.value,
"market_phase": market.market_phase.value,
"market_trend_gap_percent": market.trend_gap_percent,
"market_trend_consistency": market.trend_consistency,
"runtime_window_ttl_seconds": self._window_ttl_seconds,
"runtime_window_size": len(prices),
}
@@ -133,6 +143,42 @@ class TrendStrategy:
},
)
if market.trend_strength == TrendStrength.WEAK:
return SignalResult(
signal=SignalType.HOLD,
reason="TREND есть, но сила тренда слабая.",
confidence=0.0,
payload={
**base_payload,
"entry_block_reason": "WEAK_MARKET_TREND",
"entry_block_message": "слабый тренд",
},
)
if market.trend_quality == TrendQuality.NOISY:
return SignalResult(
signal=SignalType.HOLD,
reason="TREND есть, но движение шумное.",
confidence=0.0,
payload={
**base_payload,
"entry_block_reason": "NOISY_MARKET_TREND",
"entry_block_message": "шумный тренд",
},
)
if market.market_phase == MarketPhase.PULLBACK:
return SignalResult(
signal=SignalType.HOLD,
reason="TREND есть, но рынок находится в откате.",
confidence=0.0,
payload={
**base_payload,
"entry_block_reason": "MARKET_PULLBACK",
"entry_block_message": "откат",
},
)
if len(prices) < self._window_size:
return SignalResult(
signal=SignalType.HOLD,

View File

@@ -635,6 +635,55 @@
- подготовлена база для volatility-aware spread thresholds
- подготовлена база для adaptive execution quality model
#### 07.4.4.1.9 ✅ Adaptive Market Diagnostics Layer
- добавлен расширенный слой диагностики рынка
- добавлены enum-модели TrendStrength, TrendQuality и MarketPhase
- MarketAnalysisResult расширен полями trend_strength, trend_quality и market_phase
- MarketAnalysisResult расширен полями trend_gap_percent и trend_consistency
- MarketAnalysisService получил расчёт EMA gap в процентах
- добавлен анализ силы тренда по EMA gap
- добавлена классификация WEAK / NORMAL / STRONG trend
- добавлен анализ trend consistency по последним свечам
- добавлена классификация CLEAN / NOISY trend
- добавлена классификация market phase
- добавлены фазы IMPULSE / PULLBACK / RANGE / SQUEEZE / UNKNOWN
- LOW volatility теперь интерпретируется как SQUEEZE phase
- FLAT trend теперь интерпретируется как RANGE phase
- слабый тренд теперь может блокировать TREND вход
- шумный тренд теперь может блокировать TREND вход
- откат внутри тренда теперь может блокировать TREND вход
- TrendStrategy получила поддержку adaptive market diagnostics
- TrendStrategy пробрасывает market_trend_strength в payload
- TrendStrategy пробрасывает market_trend_quality в payload
- TrendStrategy пробрасывает market_phase в payload
- TrendStrategy пробрасывает market_trend_gap_percent в payload
- TrendStrategy пробрасывает market_trend_consistency в payload
- добавлен HOLD reason WEAK_MARKET_TREND
- добавлен HOLD reason NOISY_MARKET_TREND
- добавлен HOLD reason MARKET_PULLBACK
- AutoTradeState расширен market_trend_strength
- AutoTradeState расширен market_trend_quality
- AutoTradeState расширен market_phase
- reset runtime очищает новые market diagnostics поля
- sync market analysis обновляет новые market diagnostics поля
- Telegram UI получил строку расширенной аналитики
- Telegram UI отображает силу тренда
- Telegram UI отображает качество тренда
- Telegram UI отображает фазу рынка
- HOLD diagnostics стали точнее
- причина HOLD теперь показывает слабый тренд
- причина HOLD теперь показывает шумный тренд
- причина HOLD теперь показывает откат
- исправлен auto_run_cycle_error после расширения MarketAnalysisResult
- исправлено зависание market state в “Идёт анализ”
- подтверждена работа live runtime после расширения аналитики
- подготовлена база для Market Semantic Runtime Layer
- подготовлена база для compact semantic UI labels
- подготовлена база для adaptive thresholds
- подготовлена база для semantic entry filters
- подготовлена база для более точного TREND execution
---
### 07.4.5

View File

@@ -611,6 +611,54 @@
- подготовлена база для volatility-aware spread thresholds
- подготовлена база для adaptive execution quality model
#### 07.4.4.1.9 ✅ Adaptive Market Diagnostics Layer
- добавлен расширенный слой диагностики рынка
- добавлены enum-модели TrendStrength, TrendQuality и MarketPhase
- MarketAnalysisResult расширен полями trend_strength, trend_quality и market_phase
- MarketAnalysisResult расширен полями trend_gap_percent и trend_consistency
- MarketAnalysisService получил расчёт EMA gap в процентах
- добавлен анализ силы тренда по EMA gap
- добавлена классификация WEAK / NORMAL / STRONG trend
- добавлен анализ trend consistency по последним свечам
- добавлена классификация CLEAN / NOISY trend
- добавлена классификация market phase
- добавлены фазы IMPULSE / PULLBACK / RANGE / SQUEEZE / UNKNOWN
- LOW volatility теперь интерпретируется как SQUEEZE phase
- FLAT trend теперь интерпретируется как RANGE phase
- слабый тренд теперь может блокировать TREND вход
- шумный тренд теперь может блокировать TREND вход
- откат внутри тренда теперь может блокировать TREND вход
- TrendStrategy получила поддержку adaptive market diagnostics
- TrendStrategy пробрасывает market_trend_strength в payload
- TrendStrategy пробрасывает market_trend_quality в payload
- TrendStrategy пробрасывает market_phase в payload
- TrendStrategy пробрасывает market_trend_gap_percent в payload
- TrendStrategy пробрасывает market_trend_consistency в payload
- добавлен HOLD reason WEAK_MARKET_TREND
- добавлен HOLD reason NOISY_MARKET_TREND
- добавлен HOLD reason MARKET_PULLBACK
- AutoTradeState расширен market_trend_strength
- AutoTradeState расширен market_trend_quality
- AutoTradeState расширен market_phase
- reset runtime очищает новые market diagnostics поля
- sync market analysis обновляет новые market diagnostics поля
- Telegram UI получил строку расширенной аналитики
- Telegram UI отображает силу тренда
- Telegram UI отображает качество тренда
- Telegram UI отображает фазу рынка
- HOLD diagnostics стали точнее
- причина HOLD теперь показывает слабый тренд
- причина HOLD теперь показывает шумный тренд
- причина HOLD теперь показывает откат
- исправлен auto_run_cycle_error после расширения MarketAnalysisResult
- исправлено зависание market state в “Идёт анализ”
- подтверждена работа live runtime после расширения аналитики
- подготовлена база для Market Semantic Runtime Layer
- подготовлена база для compact semantic UI labels
- подготовлена база для adaptive thresholds
- подготовлена база для semantic entry filters
- подготовлена база для более точного TREND execution
---
### 07.4.5

View File

@@ -0,0 +1,114 @@
# 07.4.4.1.9 Adaptive Market Diagnostics Layer
## Что сделано
Добавлен расширенный слой диагностики рынка поверх базового Market State Engine.
Теперь система анализирует не только направление рынка:
- TREND_UP
- TREND_DOWN
- RANGE
- HIGH_VOLATILITY
- LOW_VOLATILITY
но и дополнительные характеристики тренда:
- сила тренда
- качество тренда
- текущая фаза рынка
- процентный разрыв EMA
- consistency движения
## Новые сущности
Добавлены новые enum-модели:
- TrendStrength
- WEAK
- NORMAL
- STRONG
- UNKNOWN
- TrendQuality
- CLEAN
- NOISY
- UNKNOWN
- MarketPhase
- IMPULSE
- PULLBACK
- RANGE
- SQUEEZE
- UNKNOWN
## MarketAnalysisResult
MarketAnalysisResult расширен новыми полями:
- trend_strength
- trend_quality
- market_phase
- trend_gap_percent
- trend_consistency
## TrendStrategy
TrendStrategy теперь получает расширенную аналитику из MarketAnalysisService и пробрасывает её в payload:
- market_trend_strength
- market_trend_quality
- market_phase
- market_trend_gap_percent
- market_trend_consistency
Добавлены дополнительные HOLD-фильтры:
- WEAK_MARKET_TREND
- NOISY_MARKET_TREND
- MARKET_PULLBACK
## Telegram UI
В UI добавлена строка расширенной аналитики:
Анализ · сильный · шумный · откат
или:
Анализ · нормальный · чистый · импульс
## Что исправлено
Исправлена ошибка auto_run_cycle_error после расширения MarketAnalysisResult.
После исправления:
- run_cycle больше не падает
- рынок больше не зависает в состоянии “Идёт анализ”
- Telegram UI снова получает актуальную market diagnostics
- HOLD timer продолжает работать
## Проверка
Команды:
python -m compileall src
Runtime-проверка:
- автоторговля запускается
- экран обновляется автоматически
- HOLD timer растёт
- market state отображается корректно
- строка Анализ появляется
- ошибки auto_run_cycle_error отсутствуют
## Результат
Этап подготовил базу для:
- Market Semantic Runtime Layer
- semantic UI labels
- adaptive thresholds
- semantic entry filters