Stage 07.4.4.1.15 — Runtime Payload Builders & Auto Runtime Refactoring

This commit is contained in:
2026-07-02 20:40:04 +03:00
parent 3c336b10fb
commit 2785f83260
8 changed files with 1069 additions and 340 deletions

View File

@@ -41,6 +41,29 @@ class AutoLifecycleMixin(
_confirm_repeats: int
_execution_confidence_required_score: float
# собрать payload изменения статуса автоторговли без изменения состояния
def _build_auto_status_changed_payload(
self,
*,
previous_status: str,
new_status: str,
state: AutoTradeState,
) -> dict[str, object]:
return {
"previous_status": previous_status,
"new_status": new_status,
"symbol": state.symbol,
"strategy": state.strategy,
"cycle_number": state.cycle_number,
"risk_percent": state.risk_percent,
"leverage": state.leverage,
"allocated_balance_usd": state.allocated_balance_usd,
"stop_loss_percent": state.stop_loss_percent,
"take_profit_percent": state.take_profit_percent,
"max_loss_usd": state.max_loss_usd,
"max_reserved_balance_percent": state.max_reserved_balance_percent,
}
def _log_auto_status_changed(
self,
*,
@@ -56,20 +79,11 @@ class AutoLifecycleMixin(
message=message,
screen="auto",
action=action,
payload={
"previous_status": previous_status,
"new_status": new_status,
"symbol": state.symbol,
"strategy": state.strategy,
"cycle_number": state.cycle_number,
"risk_percent": state.risk_percent,
"leverage": state.leverage,
"allocated_balance_usd": state.allocated_balance_usd,
"stop_loss_percent": state.stop_loss_percent,
"take_profit_percent": state.take_profit_percent,
"max_loss_usd": state.max_loss_usd,
"max_reserved_balance_percent": state.max_reserved_balance_percent,
},
payload=self._build_auto_status_changed_payload(
previous_status=previous_status,
new_status=new_status,
state=state,
),
)
def set_allocated_balance_usd(self, value: NumericLike) -> AutoTradeState:

View File

@@ -41,7 +41,7 @@ class AutoAutonomousManagementMixin:
return
exit_signal = str(
state.position_exit_signal
state.position_exit_signal
or POSITION_EXIT_SIGNAL_HOLD
).upper()
exit_confidence = safe_float(state.position_exit_confidence) or 0.0

View File

@@ -143,6 +143,29 @@ class AutoExecutionQualityMixin:
return "MARKET_BREAK"
# собрать payload изменения доступности биржи без изменения состояния
def _build_exchange_availability_payload(
self,
*,
state: AutoTradeState,
status: ExchangeRuntimeStatus,
reason: str,
) -> dict[str, object]:
return {
"status": state.status,
"symbol": state.symbol,
"strategy": state.strategy,
"exchange_status_code": status.code.value,
"exchange_reason": status.reason,
"execution_reason": reason,
"is_open": status.is_open,
"is_available": status.is_available,
"is_auth_ok": status.is_auth_ok,
"message": status.message,
"raw_status": status.raw_status,
"raw_error": status.raw_error,
}
# залогировать изменение доступности биржи/рынка
def _log_exchange_availability_if_changed(
self,
@@ -167,20 +190,11 @@ class AutoExecutionQualityMixin:
message=status.ui_line,
screen="auto",
action="exchange_status",
payload={
"status": state.status,
"symbol": state.symbol,
"strategy": state.strategy,
"exchange_status_code": status.code.value,
"exchange_reason": status.reason,
"execution_reason": reason,
"is_open": status.is_open,
"is_available": status.is_available,
"is_auth_ok": status.is_auth_ok,
"message": status.message,
"raw_status": status.raw_status,
"raw_error": status.raw_error,
},
payload=self._build_exchange_availability_payload(
state=state,
status=status,
reason=reason,
),
)
except Exception:
pass
@@ -437,6 +451,20 @@ class AutoExecutionQualityMixin:
else:
state.execution_price_freshness = "STALE"
# собрать payload изменения качества исполнения без изменения состояния
def _build_execution_quality_payload(
self,
*,
state: AutoTradeState,
payload: dict[str, object],
) -> dict[str, object]:
return {
**payload,
"status": state.status,
"symbol": state.symbol,
"strategy": state.strategy,
}
# записать событие изменения execution quality
def _log_execution_quality_if_changed(
self,
@@ -462,12 +490,10 @@ class AutoExecutionQualityMixin:
return
try:
log_payload = {
**payload,
"status": state.status,
"symbol": state.symbol,
"strategy": state.strategy,
}
log_payload = self._build_execution_quality_payload(
state=state,
payload=payload,
)
if quality == "BLOCKED":
JournalService().log_ui_warning(

View File

@@ -140,6 +140,60 @@ class AutoMarketRuntimeMixin:
payload=payload,
)
# собрать payload entry-block события без изменения состояния
def _build_entry_block_payload(
self,
*,
state: AutoTradeState,
payload: JsonDict,
reason: str,
message: str,
key: str,
) -> JsonDict:
return {
**payload,
"entry_block_reason": reason,
"entry_block_message": message,
"entry_block_key": key,
"entry_block_ttl_seconds": type(self)._entry_block_log_ttl_seconds,
"symbol": state.symbol,
"strategy": state.strategy,
"status": state.status,
"market_state": state.market_state,
"market_trend": state.market_trend,
"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,
"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,
"current_interval_change_percent": state.current_interval_change_percent,
"current_interval_direction": state.current_interval_direction,
"current_interval_label": state.current_interval_label,
"market_structure": state.market_structure,
"market_structure_reason": state.market_structure_reason,
"momentum_state": state.momentum_state,
"momentum_direction": state.momentum_direction,
"momentum_strength": state.momentum_strength,
"momentum_change_percent": state.momentum_change_percent,
"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,
"execution_quality": state.execution_quality,
"execution_quality_reason": state.execution_quality_reason,
"execution_confidence_score": state.execution_confidence_score,
"last_signal": state.last_signal,
"last_signal_confidence": state.last_signal_confidence,
"last_signal_reason": state.last_signal_reason,
}
# записать entry-block событие, если причина изменилась или истёк TTL
def _log_entry_block_if_changed(
self,
@@ -180,53 +234,39 @@ class AutoMarketRuntimeMixin:
message=f"Вход в позицию не выполнен: {message}.",
screen="auto",
action="entry_diagnostics",
payload={
**payload,
"entry_block_reason": reason,
"entry_block_message": message,
"entry_block_key": key,
"entry_block_ttl_seconds": type(self)._entry_block_log_ttl_seconds,
"symbol": state.symbol,
"strategy": state.strategy,
"status": state.status,
"market_state": state.market_state,
"market_trend": state.market_trend,
"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,
"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,
"current_interval_change_percent": state.current_interval_change_percent,
"current_interval_direction": state.current_interval_direction,
"current_interval_label": state.current_interval_label,
"market_structure": state.market_structure,
"market_structure_reason": state.market_structure_reason,
"momentum_state": state.momentum_state,
"momentum_direction": state.momentum_direction,
"momentum_strength": state.momentum_strength,
"momentum_change_percent": state.momentum_change_percent,
"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,
"execution_quality": state.execution_quality,
"execution_quality_reason": state.execution_quality_reason,
"execution_confidence_score": state.execution_confidence_score,
"last_signal": state.last_signal,
"last_signal_confidence": state.last_signal_confidence,
"last_signal_reason": state.last_signal_reason,
},
payload=self._build_entry_block_payload(
state=state,
payload=payload,
reason=reason,
message=message,
key=key,
),
)
except Exception:
pass
# собрать payload изменения состояния рынка без изменения состояния
def _build_market_state_payload(
self,
*,
payload: JsonDict,
previous_market_state: str | None,
previous_market_trend: str | None,
previous_market_volatility: str | None,
current_market_state: str | None,
current_market_trend: str | None,
current_market_volatility: str | None,
) -> JsonDict:
return {
**payload,
"previous_market_state": previous_market_state,
"previous_market_trend": previous_market_trend,
"previous_market_volatility": previous_market_volatility,
"current_market_state": current_market_state,
"current_market_trend": current_market_trend,
"current_market_volatility": current_market_volatility,
}
# записать market state / volatility событие, если состояние изменилось
def _log_market_state_if_changed(
self,
@@ -269,15 +309,15 @@ class AutoMarketRuntimeMixin:
if not state_changed and not volatility_changed:
return
journal_payload = {
**payload,
"previous_market_state": previous_market_state,
"previous_market_trend": previous_market_trend,
"previous_market_volatility": previous_market_volatility,
"current_market_state": market_state,
"current_market_trend": market_trend,
"current_market_volatility": market_volatility,
}
journal_payload = self._build_market_state_payload(
payload=payload,
previous_market_state=previous_market_state,
previous_market_trend=previous_market_trend,
previous_market_volatility=previous_market_volatility,
current_market_state=market_state,
current_market_trend=market_trend,
current_market_volatility=market_volatility,
)
try:
if state_changed:

View File

@@ -537,7 +537,7 @@ class AutoPositionSemanticsMixin:
return "MODERATE"
return "LOW"
# определить, застряла ли позиция без нормального движения
def _position_stall_state(self, state: AutoTradeState) -> tuple[str, str]:
hold_seconds = safe_float(getattr(state, "position_hold_seconds", None)) or 0.0

View File

@@ -388,6 +388,31 @@ class AutoTradeRunner:
return False
@classmethod
def _build_position_aligned_signal_suppressed_payload(
cls,
*,
symbol: str,
strategy: str,
signal: str,
position_side: str,
confidence: float,
repeat_count: int,
reason: str | None,
) -> JsonDict:
return {
"symbol": symbol,
"strategy": strategy,
"signal": signal,
"position_side": position_side,
"confidence": confidence,
"repeat_count": repeat_count,
"reason": reason,
"suppression_interval_seconds": (
cls._position_aligned_signal_log_interval_seconds
),
}
@classmethod
def _log_position_aligned_signal_suppressed(
cls,
@@ -444,16 +469,15 @@ class AutoTradeRunner:
),
screen="auto",
action="signal_notification",
payload={
"symbol": symbol,
"strategy": strategy,
"signal": signal,
"position_side": position_side,
"confidence": confidence,
"repeat_count": repeat_count,
"reason": payload.get("reason") or state.last_signal_reason,
"suppression_interval_seconds": cls._position_aligned_signal_log_interval_seconds,
},
payload=cls._build_position_aligned_signal_suppressed_payload(
symbol=symbol,
strategy=strategy,
signal=signal,
position_side=position_side,
confidence=confidence,
repeat_count=repeat_count,
reason=payload.get("reason") or state.last_signal_reason,
),
)
except Exception:
pass
@@ -509,6 +533,46 @@ class AutoTradeRunner:
),
}
@classmethod
def _build_runtime_signal_payload(
cls,
*,
state,
symbol: str,
strategy: str,
signal: str,
signal_intent: str,
repeat_count: int,
confidence: float,
leverage,
reason: str,
position_context: str,
is_aligned_signal: bool,
semantic_lines: list[str],
price_payload: JsonDict,
) -> JsonDict:
return {
"execution_block_title": getattr(state, "execution_block_title", None),
"execution_block_message": getattr(state, "execution_block_message", None),
"execution_block_action": getattr(state, "execution_block_action", None),
"symbol": symbol,
"strategy": strategy,
"signal": signal,
"signal_intent": signal_intent,
"repeat_count": repeat_count,
"confidence": confidence,
"leverage": leverage,
"reason": reason,
"position_context": position_context,
"position_side": position_context,
"is_position_aligned_signal": is_aligned_signal,
"decision_status": state.decision_status,
"market_score": getattr(state, "market_score", None),
"market_score_label": getattr(state, "market_score_label", None),
"semantic_lines": semantic_lines,
**price_payload,
}
@classmethod
def _publish_strong_signal_event(
cls,
@@ -573,32 +637,21 @@ class AutoTradeRunner:
event_type=RuntimeEventType.AUTO_SIGNAL_READY,
source="auto_trade_runner",
title=f"Auto strong signal {signal}",
payload={
"execution_block_title": getattr(state, "execution_block_title", None),
"execution_block_message": getattr(state, "execution_block_message", None),
"execution_block_action": getattr(state, "execution_block_action", None),
"symbol": symbol,
"strategy": strategy,
"signal": signal,
"signal_intent": signal_intent,
"repeat_count": repeat_count,
"confidence": confidence,
"leverage": leverage,
"reason": reason,
"position_context": position_context,
"position_side": position_context,
"is_position_aligned_signal": is_aligned_signal,
"decision_status": state.decision_status,
# market_score передаём в уведомления,
# чтобы позже можно было показывать “Рынок · благоприятный · 82%”
# не только в экране, но и в событиях/алертах.
"market_score": getattr(state, "market_score", None),
"market_score_label": getattr(state, "market_score_label", None),
"semantic_lines": semantic_lines,
**price_payload,
},
payload=cls._build_runtime_signal_payload(
state=state,
symbol=symbol,
strategy=strategy,
signal=signal,
signal_intent=signal_intent,
repeat_count=repeat_count,
confidence=confidence,
leverage=leverage,
reason=reason,
position_context=position_context,
is_aligned_signal=is_aligned_signal,
semantic_lines=semantic_lines,
price_payload=price_payload,
),
priority=priority.lower(),
dedupe_key=(
f"auto_signal_ready:"
@@ -612,6 +665,37 @@ class AutoTradeRunner:
)
)
@classmethod
def _build_runtime_execution_payload(
cls,
*,
state,
payload: JsonDict,
event_type: str,
symbol: str,
side: str,
old_side: str,
new_side: str,
semantic_lines: list[str],
) -> JsonDict:
return {
**payload,
"source_event_type": event_type,
"symbol": symbol,
"side": side,
"old_side": old_side,
"new_side": new_side,
"leverage": (
payload.get("leverage")
if payload.get("leverage") is not None
else state.leverage
),
"strategy": state.strategy,
"market_score": getattr(state, "market_score", None),
"market_score_label": getattr(state, "market_score_label", None),
"semantic_lines": semantic_lines,
}
@classmethod
def _publish_execution_event(
cls,
@@ -636,27 +720,16 @@ class AutoTradeRunner:
event_type=runtime_event_type,
source="auto_trade_runner",
title=cls._execution_event_title(runtime_event_type),
payload={
**payload,
"source_event_type": event_type,
"symbol": symbol,
"side": side,
"old_side": old_side,
"new_side": new_side,
"leverage": (
payload.get("leverage")
if payload.get("leverage") is not None
else state.leverage
),
"strategy": state.strategy,
# Фиксируем market_score на момент открытия/закрытия/flip,
# чтобы журнал и уведомления показывали рыночный контекст сделки.
"market_score": getattr(state, "market_score", None),
"market_score_label": getattr(state, "market_score_label", None),
"semantic_lines": semantic_lines,
},
payload=cls._build_runtime_execution_payload(
state=state,
payload=payload,
event_type=event_type,
symbol=symbol,
side=side,
old_side=old_side,
new_side=new_side,
semantic_lines=semantic_lines,
),
priority="normal",
dedupe_key=cls._execution_dedupe_key(
runtime_event_type=runtime_event_type,

View File

@@ -439,6 +439,39 @@ class AutoSignalRuntimeMixin:
) -> None:
return
# собрать payload итогового события серии сигналов
def _build_signal_summary_payload(
self,
*,
strategy_name: str,
state: AutoTradeState,
previous_signal: str,
next_signal: str,
signal_intent: str,
previous_count: int,
duration_seconds: int,
duration_text: str,
confidence: float,
reason: str,
payload: JsonDict | None,
) -> JsonDict:
return {
"strategy": strategy_name,
"status": state.status,
"symbol": state.symbol,
"signal": previous_signal,
"next_signal": next_signal,
"signal_intent": signal_intent,
"repeat_count": previous_count,
"duration_seconds": duration_seconds,
"duration_text": duration_text,
"confidence": confidence,
"reason": reason,
"is_strong_signal": False,
"is_aggregated": True,
"payload": payload or {},
}
# записать итог серии одинаковых сигналов при смене сигнала
def _log_signal_summary(
self,
@@ -467,26 +500,172 @@ class AutoSignalRuntimeMixin:
),
screen="auto",
action="signal_summary",
payload={
"strategy": strategy_name,
"status": state.status,
"symbol": state.symbol,
"signal": previous_signal,
"next_signal": next_signal,
"signal_intent": signal_intent,
"repeat_count": previous_count,
"duration_seconds": duration_seconds,
"duration_text": duration_text,
"confidence": confidence,
"reason": reason,
"is_strong_signal": False,
"is_aggregated": True,
"payload": payload or {},
},
payload=self._build_signal_summary_payload(
strategy_name=strategy_name,
state=state,
previous_signal=previous_signal,
next_signal=next_signal,
signal_intent=signal_intent,
previous_count=previous_count,
duration_seconds=duration_seconds,
duration_text=duration_text,
confidence=confidence,
reason=reason,
payload=payload,
),
)
except Exception:
pass
# собрать payload события готовности сигнала
def _build_ready_signal_payload(
self,
*,
state: AutoTradeState,
snapshot: JsonDict,
signal: str,
signal_intent: str,
confidence: float,
reason: str,
) -> JsonDict:
return {
# ---------- Event ----------
"event_type": "signal_ready",
"action": "signal_ready",
"is_aggregated": False,
"is_strong_signal": confidence > self._ready_confidence,
# ---------- Runtime ----------
"status": state.status,
"strategy": state.strategy,
"symbol": state.symbol,
"cycle_number": state.cycle_number,
# ---------- Signal ----------
"signal": signal,
"signal_intent": signal_intent,
"confidence": confidence,
"reason": reason,
"repeat_count": state.last_signal_repeat_count,
# ---------- Confirmation ----------
"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,
# ---------- 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,
# ---------- Position Context ----------
"position_side": state.position_side,
"entry_price": state.entry_price,
"position_size": state.position_size,
"unrealized_pnl_usd": state.unrealized_pnl_usd,
"current_trade_id": state.current_trade_id,
"current_trade_cycle_number": state.current_trade_cycle_number,
# ---------- Risk Settings ----------
"risk_percent": state.risk_percent,
"stop_loss_percent": state.stop_loss_percent,
"take_profit_percent": state.take_profit_percent,
"max_loss_usd": state.max_loss_usd,
"max_reserved_balance_percent": state.max_reserved_balance_percent,
"allocated_balance_usd": state.allocated_balance_usd,
"leverage": state.leverage,
# ---------- 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,
# ---------- Live Snapshot ----------
"bid_price": snapshot.get("bid_price"),
"ask_price": snapshot.get("ask_price"),
"last_price": snapshot.get("last_price"),
# ---------- Market Score ----------
"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 ----------
"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,
# ---------- Candle ----------
"last_closed_candle_change_percent": state.last_closed_candle_change_percent,
"last_closed_candle_direction": state.last_closed_candle_direction,
"current_interval_change_percent": state.current_interval_change_percent,
"current_interval_direction": state.current_interval_direction,
"current_interval_label": state.current_interval_label,
# ---------- Structure ----------
"market_structure": state.market_structure,
"market_structure_reason": state.market_structure_reason,
# ---------- Trend Quality ----------
"market_trend_gap_percent": state.market_trend_gap_percent,
"market_trend_consistency": state.market_trend_consistency,
"market_trend_efficiency": state.market_trend_efficiency,
"trend_quality_score": state.trend_quality_score,
"ema_distance_atr_ratio": state.ema_distance_atr_ratio,
"ema_distance_state": state.ema_distance_state,
"entry_timing_state": state.entry_timing_state,
"entry_timing_reason": state.entry_timing_reason,
# ---------- Momentum / Breakout ----------
"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_atr_percent": state.htf_atr_percent,
"htf_atr_percent_baseline": state.htf_atr_percent_baseline,
"htf_volatility_ratio": state.htf_volatility_ratio,
"htf_volatility": state.htf_volatility,
"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,
# ---------- Runtime Health ----------
"market_runtime_degraded": state.market_runtime_degraded,
"runtime_expired_reason": state.runtime_expired_reason,
"runtime_expired_message": state.runtime_expired_message,
"market_is_open": state.market_is_open,
"market_status": state.market_status,
"market_status_message": state.market_status_message,
}
# записать событие готовности сигнала к исполнению
def _log_ready_signal(
self,
@@ -517,143 +696,14 @@ class AutoSignalRuntimeMixin:
),
screen="auto",
action="signal_ready",
payload={
# ---------- Event ----------
"event_type": "signal_ready",
"action": "signal_ready",
"is_aggregated": False,
"is_strong_signal": confidence > self._ready_confidence,
# ---------- Runtime ----------
"status": state.status,
"strategy": state.strategy,
"symbol": state.symbol,
"cycle_number": state.cycle_number,
# ---------- Signal ----------
"signal": normalized_signal,
"signal_intent": signal_intent,
"confidence": confidence,
"reason": reason,
"repeat_count": state.last_signal_repeat_count,
# ---------- Confirmation ----------
"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,
# ---------- 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,
# ---------- Position Context ----------
"position_side": state.position_side,
"entry_price": state.entry_price,
"position_size": state.position_size,
"unrealized_pnl_usd": state.unrealized_pnl_usd,
"current_trade_id": state.current_trade_id,
"current_trade_cycle_number": state.current_trade_cycle_number,
# ---------- Risk Settings ----------
"risk_percent": state.risk_percent,
"stop_loss_percent": state.stop_loss_percent,
"take_profit_percent": state.take_profit_percent,
"max_loss_usd": state.max_loss_usd,
"max_reserved_balance_percent": state.max_reserved_balance_percent,
"allocated_balance_usd": state.allocated_balance_usd,
"leverage": state.leverage,
# ---------- 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,
# ---------- Live Snapshot ----------
"bid_price": snapshot.get("bid_price"),
"ask_price": snapshot.get("ask_price"),
"last_price": snapshot.get("last_price"),
# ---------- Market Score ----------
"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 ----------
"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,
# ---------- Candle ----------
"last_closed_candle_change_percent": state.last_closed_candle_change_percent,
"last_closed_candle_direction": state.last_closed_candle_direction,
"current_interval_change_percent": state.current_interval_change_percent,
"current_interval_direction": state.current_interval_direction,
"current_interval_label": state.current_interval_label,
# ---------- Structure ----------
"market_structure": state.market_structure,
"market_structure_reason": state.market_structure_reason,
# ---------- Trend Quality ----------
"market_trend_gap_percent": state.market_trend_gap_percent,
"market_trend_consistency": state.market_trend_consistency,
"market_trend_efficiency": state.market_trend_efficiency,
"trend_quality_score": state.trend_quality_score,
"ema_distance_atr_ratio": state.ema_distance_atr_ratio,
"ema_distance_state": state.ema_distance_state,
"entry_timing_state": state.entry_timing_state,
"entry_timing_reason": state.entry_timing_reason,
# ---------- Momentum / Breakout ----------
"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_atr_percent": state.htf_atr_percent,
"htf_atr_percent_baseline": state.htf_atr_percent_baseline,
"htf_volatility_ratio": state.htf_volatility_ratio,
"htf_volatility": state.htf_volatility,
"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,
# ---------- Runtime Health ----------
"market_runtime_degraded": state.market_runtime_degraded,
"runtime_expired_reason": state.runtime_expired_reason,
"runtime_expired_message": state.runtime_expired_message,
"market_is_open": state.market_is_open,
"market_status": state.market_status,
"market_status_message": state.market_status_message,
},
payload=self._build_ready_signal_payload(
state=state,
snapshot=snapshot,
signal=normalized_signal,
signal_intent=signal_intent,
confidence=confidence,
reason=reason,
),
)
except Exception:
pass
@@ -801,6 +851,22 @@ class AutoSignalRuntimeMixin:
},
)
# собрать payload события устаревания runtime данных без изменения состояния
def _build_runtime_expired_payload(
self,
*,
state: AutoTradeState,
payload: JsonDict,
reason: str,
) -> JsonDict:
return {
**payload,
"symbol": state.symbol,
"strategy": state.strategy,
"status": state.status,
"runtime_expired_reason": reason,
}
# записать событие устаревания runtime данных
def _log_runtime_expired_if_changed(
self,
@@ -823,17 +889,59 @@ class AutoSignalRuntimeMixin:
message=message,
screen="auto",
action="runtime_expiration",
payload={
**payload,
"symbol": state.symbol,
"strategy": state.strategy,
"status": state.status,
"runtime_expired_reason": reason,
},
payload=self._build_runtime_expired_payload(
state=state,
payload=payload,
reason=reason,
),
)
except Exception:
pass
# собрать диагностические факторы execution confidence
def _build_execution_confidence_factors(
self,
*,
state: AutoTradeState,
signal: str,
signal_score: float,
confirmation_score: float,
market_score: float,
execution_score: float,
) -> JsonDict:
return {
"signal_score": round(signal_score, 3),
"confirmation_score": round(confirmation_score, 3),
"market_score": round(market_score, 3),
"market_score_raw": getattr(state, "market_score", None),
"market_score_label": getattr(state, "market_score_label", None),
"execution_score": round(execution_score, 3),
"required_score": self._execution_confidence_required_score,
"signal": signal,
"market_state": state.market_state,
"market_trend": state.market_trend,
"market_trend_strength": state.market_trend_strength,
"market_trend_quality": state.market_trend_quality,
"market_phase": state.market_phase,
"current_interval_change_percent": getattr(state, "current_interval_change_percent", None),
"current_interval_direction": getattr(state, "current_interval_direction", None),
"current_interval_label": getattr(state, "current_interval_label", None),
"market_structure": getattr(state, "market_structure", None),
"market_structure_reason": getattr(state, "market_structure_reason", None),
"htf_alignment": getattr(state, "htf_alignment", None),
"htf_confirmation_score": getattr(state, "htf_confirmation_score", None),
"execution_quality": state.execution_quality,
"execution_quality_reason": state.execution_quality_reason,
"spread_percent": state.spread_percent,
"momentum_state": getattr(state, "momentum_state", None),
"momentum_direction": getattr(state, "momentum_direction", None),
"momentum_change_percent": getattr(state, "momentum_change_percent", None),
"momentum_strength": getattr(state, "momentum_strength", None),
"breakout_level": getattr(state, "breakout_level", None),
"breakout_distance_percent": getattr(state, "breakout_distance_percent", None),
"breakout_reason": getattr(state, "breakout_reason", None),
}
# синхронизировать итоговый execution confidence
def _sync_execution_confidence_state(
self,
@@ -881,41 +989,14 @@ class AutoSignalRuntimeMixin:
state.execution_confidence_required_score = self._execution_confidence_required_score
state.execution_confidence_level = self._execution_confidence_level(score)
state.execution_confidence_reason = self._execution_confidence_reason(state)
state.execution_confidence_factors = {
"signal_score": round(signal_score, 3),
"confirmation_score": round(confirmation_score, 3),
# market_score здесь — направленная рыночная оценка 0.0..1.0
# именно для текущего BUY / SELL сигнала.
# state.market_score — общая оценка рынка 0..100 без привязки к сигналу.
"market_score": round(market_score, 3),
"market_score_raw": getattr(state, "market_score", None),
"market_score_label": getattr(state, "market_score_label", None),
"execution_score": round(execution_score, 3),
"required_score": self._execution_confidence_required_score,
"signal": signal,
"market_state": state.market_state,
"market_trend": state.market_trend,
"market_trend_strength": state.market_trend_strength,
"market_trend_quality": state.market_trend_quality,
"market_phase": state.market_phase,
"current_interval_change_percent": getattr(state, "current_interval_change_percent", None),
"current_interval_direction": getattr(state, "current_interval_direction", None),
"current_interval_label": getattr(state, "current_interval_label", None),
"market_structure": getattr(state, "market_structure", None),
"market_structure_reason": getattr(state, "market_structure_reason", None),
"htf_alignment": getattr(state, "htf_alignment", None),
"htf_confirmation_score": getattr(state, "htf_confirmation_score", None),
"execution_quality": state.execution_quality,
"execution_quality_reason": state.execution_quality_reason,
"spread_percent": state.spread_percent,
"momentum_state": getattr(state, "momentum_state", None),
"momentum_direction": getattr(state, "momentum_direction", None),
"momentum_change_percent": getattr(state, "momentum_change_percent", None),
"momentum_strength": getattr(state, "momentum_strength", None),
"breakout_level": getattr(state, "breakout_level", None),
"breakout_distance_percent": getattr(state, "breakout_distance_percent", None),
"breakout_reason": getattr(state, "breakout_reason", None),
}
state.execution_confidence_factors = self._build_execution_confidence_factors(
state=state,
signal=signal,
signal_score=signal_score,
confirmation_score=confirmation_score,
market_score=market_score,
execution_score=execution_score,
)
# рассчитать market confidence для итогового execution confidence
def _market_confidence_score(