Add flip execution diagnostics
This commit is contained in:
@@ -52,6 +52,7 @@ class ExecutionEngine(
|
||||
_flip_cooldown_seconds = 45
|
||||
_loss_flip_confidence = 0.75
|
||||
_last_flip_block_key: str | None = None
|
||||
_last_flip_diagnostic_key: str | None = None
|
||||
_runtime_action_cooldown_seconds = 30
|
||||
_last_runtime_action_key: str | None = None
|
||||
_emergency_halt_drawdown_usd = 250.0
|
||||
@@ -107,6 +108,18 @@ class ExecutionEngine(
|
||||
if protection_decision is not None:
|
||||
return protection_decision
|
||||
|
||||
# Flip diagnostics before READY / supervisor.
|
||||
# Это не меняет торговую логику: только фиксирует, что противоположный
|
||||
# сигнал появился при уже открытой позиции.
|
||||
flip_requested = self._should_flip_position(state)
|
||||
|
||||
if flip_requested:
|
||||
self._log_flip_diagnostic(
|
||||
state=state,
|
||||
stage="FLIP_REQUESTED",
|
||||
reason="opposite signal while position is open",
|
||||
)
|
||||
|
||||
# Signal readiness validation
|
||||
if state.decision_status != EXECUTION_DECISION_READY or not state.is_signal_ready:
|
||||
reason = (
|
||||
@@ -115,6 +128,13 @@ class ExecutionEngine(
|
||||
f"ready={state.is_signal_ready})."
|
||||
)
|
||||
|
||||
if flip_requested:
|
||||
self._log_flip_diagnostic(
|
||||
state=state,
|
||||
stage="FLIP_BLOCKED_BY_READY",
|
||||
reason=reason,
|
||||
)
|
||||
|
||||
return self._skip_execution(
|
||||
state,
|
||||
reason,
|
||||
@@ -123,6 +143,13 @@ class ExecutionEngine(
|
||||
# Execution supervisor
|
||||
supervisor_decision = self._process_execution_supervisor(state)
|
||||
if supervisor_decision is not None:
|
||||
if flip_requested:
|
||||
self._log_flip_diagnostic(
|
||||
state=state,
|
||||
stage="FLIP_BLOCKED_BY_SUPERVISOR",
|
||||
reason=supervisor_decision.reason,
|
||||
)
|
||||
|
||||
return supervisor_decision
|
||||
|
||||
# Existing position validation
|
||||
@@ -143,7 +170,7 @@ class ExecutionEngine(
|
||||
)
|
||||
|
||||
# Position flip
|
||||
if self._should_flip_position(state):
|
||||
if flip_requested:
|
||||
flip_block_reason = self._flip_block_reason(state)
|
||||
|
||||
if flip_block_reason is not None:
|
||||
|
||||
@@ -58,6 +58,7 @@ class _ExecutionFlipProtocol(Protocol):
|
||||
_flip_cooldown_seconds: int
|
||||
_loss_flip_confidence: float
|
||||
_last_flip_block_key: str | None
|
||||
_last_flip_diagnostic_key: str | None
|
||||
|
||||
def _create_trade_id(self, state: AutoTradeState, side: str) -> str:
|
||||
...
|
||||
@@ -111,6 +112,67 @@ class _ExecutionFlipProtocol(Protocol):
|
||||
|
||||
|
||||
class ExecutionFlipMixin(_ExecutionFlipProtocol):
|
||||
# ---------- Diagnostics ----------
|
||||
# записать диагностическое событие по пути flip без изменения торговой логики
|
||||
def _log_flip_diagnostic(
|
||||
self,
|
||||
*,
|
||||
state: AutoTradeState,
|
||||
stage: str,
|
||||
reason: str,
|
||||
) -> None:
|
||||
position = type(self)._position
|
||||
confidence = safe_float(state.last_signal_confidence) or 0.0
|
||||
repeat_count = int(safe_float(state.last_signal_repeat_count) or 0)
|
||||
|
||||
key = (
|
||||
f"{stage}:"
|
||||
f"{state.symbol}:"
|
||||
f"{position.side}:"
|
||||
f"{state.last_signal}:"
|
||||
f"{repeat_count}:"
|
||||
f"{confidence:.2f}:"
|
||||
f"{reason}"
|
||||
)
|
||||
|
||||
if key == type(self)._last_flip_diagnostic_key:
|
||||
return
|
||||
|
||||
type(self)._last_flip_diagnostic_key = key
|
||||
|
||||
payload = {
|
||||
"execution_type": "FLIP_DIAGNOSTIC",
|
||||
"stage": stage,
|
||||
"reason": reason,
|
||||
"symbol": state.symbol,
|
||||
"position_side": position.side,
|
||||
"signal": state.last_signal,
|
||||
"signal_confidence": confidence,
|
||||
"signal_repeat_count": repeat_count,
|
||||
"decision_status": state.decision_status,
|
||||
"is_signal_ready": state.is_signal_ready,
|
||||
"is_signal_confirmed": state.is_signal_confirmed,
|
||||
"execution_confidence_score": state.execution_confidence_score,
|
||||
"execution_confidence_required_score": (
|
||||
state.execution_confidence_required_score
|
||||
),
|
||||
"execution_block_reason": state.execution_block_reason,
|
||||
"entry_block_reason": state.entry_block_reason,
|
||||
"entry_block_message": state.entry_block_message,
|
||||
"unrealized_pnl_usd": state.unrealized_pnl_usd,
|
||||
**build_market_context_payload(state),
|
||||
}
|
||||
|
||||
JournalService().log_ui_info(
|
||||
event_type="position_flip_diagnostic",
|
||||
message=f"Flip diagnostic: {stage} · {reason}",
|
||||
screen="auto",
|
||||
action="paper_execution",
|
||||
payload=payload,
|
||||
)
|
||||
|
||||
EventBus.emit("paper_flip_diagnostic", payload)
|
||||
|
||||
# ---------- Payload builders ----------
|
||||
# собрать payload отказа flip без изменения состояния
|
||||
def _build_flip_rejected_payload(
|
||||
|
||||
Reference in New Issue
Block a user