07.4.4.1.1 — Market State Human UI + HOLD Lifecycle Fix
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from src.integrations.exchange.service import ExchangeService
|
||||
from src.trading.market_analysis.models import MarketState
|
||||
from src.trading.market_analysis.service import MarketAnalysisService
|
||||
from src.trading.strategies.base import StrategyContext
|
||||
from src.trading.strategies.signals import SignalResult, SignalType
|
||||
|
||||
@@ -12,18 +14,26 @@ class TrendStrategy:
|
||||
|
||||
_price_window: dict[str, list[float]] = {}
|
||||
|
||||
# длиннее окно = меньше шума
|
||||
# короткое окно оставляем как дополнительное подтверждение импульса
|
||||
_window_size = 8
|
||||
|
||||
# общий порог изменения за окно
|
||||
_threshold_percent = 0.05
|
||||
|
||||
# сколько движений внутри окна должно быть в сторону сигнала
|
||||
_min_direction_ratio = 0.6
|
||||
|
||||
# основной таймфрейм анализа рынка
|
||||
_market_interval = "5m"
|
||||
|
||||
def analyze(self, context: StrategyContext) -> SignalResult:
|
||||
market = MarketAnalysisService().analyze(
|
||||
context.symbol,
|
||||
interval=self._market_interval,
|
||||
limit=200,
|
||||
)
|
||||
|
||||
try:
|
||||
snapshot = ExchangeService().get_market_snapshot(context.symbol)
|
||||
snapshot = ExchangeService().get_market_snapshot(
|
||||
context.symbol,
|
||||
runtime_key="auto",
|
||||
)
|
||||
except Exception as exc:
|
||||
return SignalResult(
|
||||
signal=SignalType.HOLD,
|
||||
@@ -33,6 +43,7 @@ class TrendStrategy:
|
||||
"strategy": self.name,
|
||||
"symbol": context.symbol,
|
||||
"error": str(exc),
|
||||
"market_analysis": market.payload,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -48,6 +59,7 @@ class TrendStrategy:
|
||||
"strategy": self.name,
|
||||
"symbol": symbol,
|
||||
"snapshot": snapshot,
|
||||
"market_analysis": market.payload,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -57,15 +69,39 @@ class TrendStrategy:
|
||||
if len(prices) > self._window_size:
|
||||
prices.pop(0)
|
||||
|
||||
base_payload = {
|
||||
"strategy": self.name,
|
||||
"symbol": symbol,
|
||||
"analysis_price": current_price,
|
||||
"last_price": snapshot.get("last_price"),
|
||||
"bid_price": snapshot.get("bid_price"),
|
||||
"ask_price": snapshot.get("ask_price"),
|
||||
"market_state": market.state.value,
|
||||
"market_trend": market.trend.value,
|
||||
"market_volatility": market.volatility.value,
|
||||
"market_analysis_interval": market.interval,
|
||||
"market_analysis_reason": market.reason,
|
||||
"market_analysis": market.payload,
|
||||
}
|
||||
|
||||
if not market.is_trade_allowed:
|
||||
return SignalResult(
|
||||
signal=SignalType.HOLD,
|
||||
reason=f"Market filter: {market.reason}",
|
||||
confidence=0.0,
|
||||
payload={
|
||||
**base_payload,
|
||||
"market_filter_blocked": True,
|
||||
},
|
||||
)
|
||||
|
||||
if len(prices) < self._window_size:
|
||||
return SignalResult(
|
||||
signal=SignalType.HOLD,
|
||||
reason="Недостаточно данных для анализа тренда.",
|
||||
reason="Недостаточно live-данных для подтверждения TREND.",
|
||||
confidence=0.0,
|
||||
payload={
|
||||
"strategy": self.name,
|
||||
"symbol": symbol,
|
||||
"price": current_price,
|
||||
**base_payload,
|
||||
"window_size": len(prices),
|
||||
"required_window_size": self._window_size,
|
||||
},
|
||||
@@ -77,11 +113,10 @@ class TrendStrategy:
|
||||
if first_price <= 0:
|
||||
return SignalResult(
|
||||
signal=SignalType.HOLD,
|
||||
reason="Некорректная стартовая цена в окне.",
|
||||
reason="Некорректная стартовая цена в live-окне.",
|
||||
confidence=0.0,
|
||||
payload={
|
||||
"strategy": self.name,
|
||||
"symbol": symbol,
|
||||
**base_payload,
|
||||
"prices": prices,
|
||||
},
|
||||
)
|
||||
@@ -90,14 +125,9 @@ class TrendStrategy:
|
||||
direction_ratio = self._direction_ratio(prices, change_percent)
|
||||
|
||||
payload = {
|
||||
"strategy": self.name,
|
||||
"symbol": symbol,
|
||||
"analysis_price": last_price,
|
||||
**base_payload,
|
||||
"first_price": first_price,
|
||||
"current_price": last_price,
|
||||
"last_price": snapshot.get("last_price"),
|
||||
"bid_price": snapshot.get("bid_price"),
|
||||
"ask_price": snapshot.get("ask_price"),
|
||||
"change_percent": round(change_percent, 5),
|
||||
"direction_ratio": round(direction_ratio, 3),
|
||||
"window_size": len(prices),
|
||||
@@ -105,31 +135,47 @@ class TrendStrategy:
|
||||
"min_direction_ratio": self._min_direction_ratio,
|
||||
}
|
||||
|
||||
if (
|
||||
change_percent >= self._threshold_percent
|
||||
and direction_ratio >= self._min_direction_ratio
|
||||
):
|
||||
if market.state == MarketState.TREND_UP:
|
||||
if (
|
||||
change_percent >= self._threshold_percent
|
||||
and direction_ratio >= self._min_direction_ratio
|
||||
):
|
||||
return SignalResult(
|
||||
signal=SignalType.BUY,
|
||||
reason="TREND_UP подтверждён market analysis и live-импульсом.",
|
||||
confidence=self._calculate_confidence(change_percent, direction_ratio),
|
||||
payload=payload,
|
||||
)
|
||||
|
||||
return SignalResult(
|
||||
signal=SignalType.BUY,
|
||||
reason="Устойчивый рост цены в окне TREND.",
|
||||
confidence=self._calculate_confidence(change_percent, direction_ratio),
|
||||
signal=SignalType.HOLD,
|
||||
reason="TREND_UP есть, но live-импульс вверх недостаточно сильный.",
|
||||
confidence=0.0,
|
||||
payload=payload,
|
||||
)
|
||||
|
||||
if (
|
||||
change_percent <= -self._threshold_percent
|
||||
and direction_ratio >= self._min_direction_ratio
|
||||
):
|
||||
if market.state == MarketState.TREND_DOWN:
|
||||
if (
|
||||
change_percent <= -self._threshold_percent
|
||||
and direction_ratio >= self._min_direction_ratio
|
||||
):
|
||||
return SignalResult(
|
||||
signal=SignalType.SELL,
|
||||
reason="TREND_DOWN подтверждён market analysis и live-импульсом.",
|
||||
confidence=self._calculate_confidence(change_percent, direction_ratio),
|
||||
payload=payload,
|
||||
)
|
||||
|
||||
return SignalResult(
|
||||
signal=SignalType.SELL,
|
||||
reason="Устойчивое снижение цены в окне TREND.",
|
||||
confidence=self._calculate_confidence(change_percent, direction_ratio),
|
||||
signal=SignalType.HOLD,
|
||||
reason="TREND_DOWN есть, но live-импульс вниз недостаточно сильный.",
|
||||
confidence=0.0,
|
||||
payload=payload,
|
||||
)
|
||||
|
||||
return SignalResult(
|
||||
signal=SignalType.HOLD,
|
||||
reason="Тренд недостаточно устойчивый.",
|
||||
reason=f"Market state не подходит для TREND: {market.state.value}.",
|
||||
confidence=0.0,
|
||||
payload=payload,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user