Add reversal signal block diagnostics
This commit is contained in:
@@ -141,6 +141,180 @@ class AutoSignalRuntimeMixin:
|
|||||||
|
|
||||||
return "NOISE"
|
return "NOISE"
|
||||||
|
|
||||||
|
# собрать payload диагностики заблокированного reversal-сигнала
|
||||||
|
def _build_reversal_signal_blocked_payload(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
state: AutoTradeState,
|
||||||
|
signal: str,
|
||||||
|
confidence: float,
|
||||||
|
block_stage: str,
|
||||||
|
block_reason: str,
|
||||||
|
) -> JsonDict:
|
||||||
|
return {
|
||||||
|
"event_type": "reversal_signal_blocked",
|
||||||
|
"action": "reversal_signal_blocked",
|
||||||
|
"block_stage": block_stage,
|
||||||
|
"block_reason": block_reason,
|
||||||
|
|
||||||
|
# ---------- Signal ----------
|
||||||
|
"signal": signal,
|
||||||
|
"signal_intent": "REVERSAL_CANDIDATE",
|
||||||
|
"confidence": confidence,
|
||||||
|
"signal_reason": state.last_signal_reason,
|
||||||
|
"repeat_count": state.last_signal_repeat_count,
|
||||||
|
|
||||||
|
# ---------- Decision ----------
|
||||||
|
"decision_status": state.decision_status,
|
||||||
|
"decision_reason": state.decision_reason,
|
||||||
|
"is_signal_confirmed": state.is_signal_confirmed,
|
||||||
|
"is_signal_ready": state.is_signal_ready,
|
||||||
|
"confirmation_seconds": state.signal_confirmation_seconds,
|
||||||
|
"confirmation_required_seconds": state.signal_confirmation_required_seconds,
|
||||||
|
"confirmation_missing_repeats": state.signal_confirmation_missing_repeats,
|
||||||
|
"confirmation_progress": state.signal_confirmation_progress,
|
||||||
|
"confirmation_reason": state.signal_confirmation_reason,
|
||||||
|
|
||||||
|
# ---------- Position ----------
|
||||||
|
"symbol": state.symbol,
|
||||||
|
"strategy": state.strategy,
|
||||||
|
"position_side": state.position_side,
|
||||||
|
"entry_price": state.entry_price,
|
||||||
|
"position_size": state.position_size,
|
||||||
|
"unrealized_pnl_usd": state.unrealized_pnl_usd,
|
||||||
|
"position_pnl_percent": state.position_pnl_percent,
|
||||||
|
"position_health_status": state.position_health_status,
|
||||||
|
"position_health_reason": state.position_health_reason,
|
||||||
|
"position_risk_level": state.position_risk_level,
|
||||||
|
"position_risk_reason": state.position_risk_reason,
|
||||||
|
"position_trend_alignment": state.position_trend_alignment,
|
||||||
|
"position_adverse_momentum": state.position_adverse_momentum,
|
||||||
|
|
||||||
|
# ---------- Execution Confidence ----------
|
||||||
|
"execution_confidence_score": state.execution_confidence_score,
|
||||||
|
"execution_confidence_level": state.execution_confidence_level,
|
||||||
|
"execution_confidence_required_score": (
|
||||||
|
state.execution_confidence_required_score
|
||||||
|
),
|
||||||
|
"execution_confidence_reason": state.execution_confidence_reason,
|
||||||
|
"execution_confidence_factors": state.execution_confidence_factors,
|
||||||
|
|
||||||
|
# ---------- Execution Quality ----------
|
||||||
|
"execution_quality": state.execution_quality,
|
||||||
|
"execution_quality_reason": state.execution_quality_reason,
|
||||||
|
"execution_quality_message": state.execution_quality_message,
|
||||||
|
"spread_percent": state.spread_percent,
|
||||||
|
"snapshot_age_seconds": state.snapshot_age_seconds,
|
||||||
|
|
||||||
|
# ---------- Market ----------
|
||||||
|
"market_score": state.market_score,
|
||||||
|
"market_score_label": state.market_score_label,
|
||||||
|
"market_long_score": state.market_long_score,
|
||||||
|
"market_short_score": state.market_short_score,
|
||||||
|
"market_state": state.market_state,
|
||||||
|
"market_trend": state.market_trend,
|
||||||
|
"market_volatility": state.market_volatility,
|
||||||
|
"market_trend_strength": state.market_trend_strength,
|
||||||
|
"market_trend_quality": state.market_trend_quality,
|
||||||
|
"market_phase": state.market_phase,
|
||||||
|
"market_phase_direction": state.market_phase_direction,
|
||||||
|
|
||||||
|
# ---------- Current interval ----------
|
||||||
|
"current_interval_change_percent": state.current_interval_change_percent,
|
||||||
|
"current_interval_direction": state.current_interval_direction,
|
||||||
|
"current_interval_label": state.current_interval_label,
|
||||||
|
|
||||||
|
# ---------- Structure / timing ----------
|
||||||
|
"market_structure": state.market_structure,
|
||||||
|
"market_structure_reason": state.market_structure_reason,
|
||||||
|
"trend_quality_score": state.trend_quality_score,
|
||||||
|
"ema_distance_state": state.ema_distance_state,
|
||||||
|
"entry_timing_state": state.entry_timing_state,
|
||||||
|
"entry_timing_reason": state.entry_timing_reason,
|
||||||
|
|
||||||
|
# ---------- Momentum ----------
|
||||||
|
"momentum_state": state.momentum_state,
|
||||||
|
"momentum_direction": state.momentum_direction,
|
||||||
|
"momentum_change_percent": state.momentum_change_percent,
|
||||||
|
"momentum_strength": state.momentum_strength,
|
||||||
|
"breakout_level": state.breakout_level,
|
||||||
|
"breakout_distance_percent": state.breakout_distance_percent,
|
||||||
|
"breakout_reason": state.breakout_reason,
|
||||||
|
|
||||||
|
# ---------- HTF ----------
|
||||||
|
"htf_interval": state.htf_interval,
|
||||||
|
"htf_market_state": state.htf_market_state,
|
||||||
|
"htf_trend": state.htf_trend,
|
||||||
|
"htf_trend_strength": state.htf_trend_strength,
|
||||||
|
"htf_trend_quality": state.htf_trend_quality,
|
||||||
|
"htf_market_phase": state.htf_market_phase,
|
||||||
|
"htf_alignment": state.htf_alignment,
|
||||||
|
"htf_confirmation_score": state.htf_confirmation_score,
|
||||||
|
"htf_reason": state.htf_reason,
|
||||||
|
}
|
||||||
|
|
||||||
|
# записать диагностику, если reversal-кандидат был заблокирован до READY
|
||||||
|
def _log_reversal_signal_blocked_if_needed(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
state: AutoTradeState,
|
||||||
|
signal: str,
|
||||||
|
confidence: float,
|
||||||
|
block_stage: str,
|
||||||
|
block_reason: str,
|
||||||
|
) -> None:
|
||||||
|
signal_intent = self._signal_intent(
|
||||||
|
state=state,
|
||||||
|
signal=signal,
|
||||||
|
)
|
||||||
|
|
||||||
|
if signal_intent != "REVERSAL_CANDIDATE":
|
||||||
|
return
|
||||||
|
|
||||||
|
# Дедупликация: не пишем одно и то же состояние на каждом цикле.
|
||||||
|
key = (
|
||||||
|
f"{state.status}:"
|
||||||
|
f"{state.symbol}:"
|
||||||
|
f"{state.strategy}:"
|
||||||
|
f"{state.position_side}:"
|
||||||
|
f"{signal}:"
|
||||||
|
f"{state.last_signal_repeat_count}:"
|
||||||
|
f"{confidence:.2f}:"
|
||||||
|
f"{block_stage}:"
|
||||||
|
f"{block_reason}:"
|
||||||
|
f"{state.execution_confidence_score}"
|
||||||
|
)
|
||||||
|
|
||||||
|
last_key = getattr(type(self), "_last_reversal_signal_block_key", None)
|
||||||
|
|
||||||
|
if key == last_key:
|
||||||
|
return
|
||||||
|
|
||||||
|
setattr(type(self), "_last_reversal_signal_block_key", key)
|
||||||
|
|
||||||
|
payload = self._build_reversal_signal_blocked_payload(
|
||||||
|
state=state,
|
||||||
|
signal=signal,
|
||||||
|
confidence=confidence,
|
||||||
|
block_stage=block_stage,
|
||||||
|
block_reason=block_reason,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
JournalService().log_ui_warning(
|
||||||
|
event_type="reversal_signal_blocked",
|
||||||
|
message=(
|
||||||
|
f"Reversal-сигнал {signal} заблокирован: {block_reason}"
|
||||||
|
),
|
||||||
|
screen="auto",
|
||||||
|
action="signal_runtime",
|
||||||
|
payload=payload,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
EventBus.emit("reversal_signal_blocked", payload)
|
||||||
|
|
||||||
# обновить статус решения по текущему сигналу
|
# обновить статус решения по текущему сигналу
|
||||||
def _update_decision_state(
|
def _update_decision_state(
|
||||||
self,
|
self,
|
||||||
@@ -220,6 +394,14 @@ class AutoSignalRuntimeMixin:
|
|||||||
f"Сигнал {signal} подтверждён, но уверенность низкая: "
|
f"Сигнал {signal} подтверждён, но уверенность низкая: "
|
||||||
f"{confidence:.2f} < {self._ready_confidence:.2f}."
|
f"{confidence:.2f} < {self._ready_confidence:.2f}."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._log_reversal_signal_blocked_if_needed(
|
||||||
|
state=state,
|
||||||
|
signal=signal,
|
||||||
|
confidence=confidence,
|
||||||
|
block_stage="LOW_SIGNAL_CONFIDENCE",
|
||||||
|
block_reason=state.decision_reason,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
self._sync_execution_confidence_state(
|
self._sync_execution_confidence_state(
|
||||||
@@ -238,6 +420,14 @@ class AutoSignalRuntimeMixin:
|
|||||||
f"{state.execution_confidence_score:.2f} < "
|
f"{state.execution_confidence_score:.2f} < "
|
||||||
f"{self._execution_confidence_required_score:.2f}."
|
f"{self._execution_confidence_required_score:.2f}."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._log_reversal_signal_blocked_if_needed(
|
||||||
|
state=state,
|
||||||
|
signal=signal,
|
||||||
|
confidence=confidence,
|
||||||
|
block_stage="LOW_EXECUTION_CONFIDENCE",
|
||||||
|
block_reason=state.decision_reason,
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
state.is_signal_ready = True
|
state.is_signal_ready = True
|
||||||
@@ -475,7 +665,7 @@ class AutoSignalRuntimeMixin:
|
|||||||
"is_aggregated": True,
|
"is_aggregated": True,
|
||||||
"payload": payload or {},
|
"payload": payload or {},
|
||||||
}
|
}
|
||||||
|
|
||||||
# записать итог серии одинаковых сигналов при смене сигнала
|
# записать итог серии одинаковых сигналов при смене сигнала
|
||||||
def _log_signal_summary(
|
def _log_signal_summary(
|
||||||
self,
|
self,
|
||||||
@@ -669,7 +859,7 @@ class AutoSignalRuntimeMixin:
|
|||||||
"market_status": state.market_status,
|
"market_status": state.market_status,
|
||||||
"market_status_message": state.market_status_message,
|
"market_status_message": state.market_status_message,
|
||||||
}
|
}
|
||||||
|
|
||||||
# записать событие готовности сигнала к исполнению
|
# записать событие готовности сигнала к исполнению
|
||||||
def _log_ready_signal(
|
def _log_ready_signal(
|
||||||
self,
|
self,
|
||||||
@@ -801,7 +991,7 @@ class AutoSignalRuntimeMixin:
|
|||||||
"status": state.status,
|
"status": state.status,
|
||||||
"runtime_expired_reason": reason,
|
"runtime_expired_reason": reason,
|
||||||
}
|
}
|
||||||
|
|
||||||
# записать событие устаревания runtime данных
|
# записать событие устаревания runtime данных
|
||||||
def _log_runtime_expired_if_changed(
|
def _log_runtime_expired_if_changed(
|
||||||
self,
|
self,
|
||||||
@@ -876,7 +1066,7 @@ class AutoSignalRuntimeMixin:
|
|||||||
"breakout_distance_percent": getattr(state, "breakout_distance_percent", None),
|
"breakout_distance_percent": getattr(state, "breakout_distance_percent", None),
|
||||||
"breakout_reason": getattr(state, "breakout_reason", None),
|
"breakout_reason": getattr(state, "breakout_reason", None),
|
||||||
}
|
}
|
||||||
|
|
||||||
# синхронизировать итоговый execution confidence
|
# синхронизировать итоговый execution confidence
|
||||||
def _sync_execution_confidence_state(
|
def _sync_execution_confidence_state(
|
||||||
self,
|
self,
|
||||||
@@ -896,10 +1086,8 @@ class AutoSignalRuntimeMixin:
|
|||||||
signal_score = self._clamp_score(confidence)
|
signal_score = self._clamp_score(confidence)
|
||||||
confirmation_score = self._clamp_score(state.signal_confirmation_progress)
|
confirmation_score = self._clamp_score(state.signal_confirmation_progress)
|
||||||
|
|
||||||
# ВАЖНО:
|
# Сейчас market_score считается как entry-confidence.
|
||||||
# market_score теперь считается с учётом направления сигнала.
|
# Для reversal/flip это полезно диагностировать, но пока не меняем поведение.
|
||||||
# Раньше BUY мог получить хороший market_score просто потому,
|
|
||||||
# что рынок трендовый, даже если тренд/моментум были против BUY.
|
|
||||||
market_score = self._market_confidence_score(
|
market_score = self._market_confidence_score(
|
||||||
state=state,
|
state=state,
|
||||||
signal=signal,
|
signal=signal,
|
||||||
@@ -1012,6 +1200,7 @@ class AutoSignalRuntimeMixin:
|
|||||||
return 0.15
|
return 0.15
|
||||||
|
|
||||||
# Жёсткая защита от входа против локального тренда.
|
# Жёсткая защита от входа против локального тренда.
|
||||||
|
# Для будущего этапа: именно это может быть слишком жёстко для flip.
|
||||||
if normalized_signal == "BUY" and market_trend == "DOWN":
|
if normalized_signal == "BUY" and market_trend == "DOWN":
|
||||||
return 0.05
|
return 0.05
|
||||||
|
|
||||||
@@ -1019,6 +1208,7 @@ class AutoSignalRuntimeMixin:
|
|||||||
return 0.05
|
return 0.05
|
||||||
|
|
||||||
# Жёсткая защита от входа против momentum.
|
# Жёсткая защита от входа против momentum.
|
||||||
|
# Для будущего этапа: reversal может начинаться до смены полного trend.
|
||||||
if normalized_signal == "BUY" and momentum_direction == "DOWN":
|
if normalized_signal == "BUY" and momentum_direction == "DOWN":
|
||||||
return 0.05
|
return 0.05
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user