Stage 07.4.4.1.14 — Execution refactoring and runtime semantics

This commit is contained in:
2026-07-02 16:25:18 +03:00
parent d9e6392e28
commit e08c604ff5
72 changed files with 13626 additions and 4233 deletions

View File

@@ -2,22 +2,12 @@
from __future__ import annotations
import time
#import math
#from dataclasses import dataclass
#from datetime import datetime
#from src.core.event_bus import EventBus
#from src.integrations.exchange.service import ExchangeService
from src.trading.auto.state import AutoTradeState
from src.trading.execution.models import ExecutionDecision
#from src.trading.journal.service import JournalService
from src.trading.position.state import PositionState
#from src.core.numbers import safe_float
#from src.core.types import NumericLike
from src.trading.execution.pricing import ExecutionPricingMixin
from src.trading.execution.position_runtime import ExecutionPositionRuntimeMixin
from src.trading.execution.position_intelligence import ExecutionPositionIntelligenceMixin
from src.trading.execution.position_exit_decision import ExecutionPositionExitDecisionMixin
from src.trading.execution.position_protection import ExecutionPositionProtectionMixin
from src.trading.execution.supervisor import ExecutionSupervisorMixin
from src.trading.execution.sizing import ExecutionSizingMixin
@@ -27,6 +17,17 @@ from src.trading.execution.position_actions import ExecutionPositionActionsMixin
from src.trading.execution.runtime_actions import ExecutionRuntimeActionsMixin
from src.trading.execution.calculations import ExecutionCalculationsMixin
from src.trading.execution.resets import ExecutionResetsMixin
from src.trading.execution.constants import (
EXECUTION_ACTION_NONE,
EXECUTION_ACTION_OPEN_LONG,
EXECUTION_ACTION_OPEN_SHORT,
EXECUTION_DECISION_READY,
EXECUTION_STATUS_RUNNING,
POSITION_SIDE_LONG,
POSITION_SIDE_SHORT,
SIGNAL_BUY,
SIGNAL_SELL,
)
class ExecutionEngine(
@@ -34,7 +35,7 @@ class ExecutionEngine(
ExecutionResetsMixin,
ExecutionPricingMixin,
ExecutionPositionRuntimeMixin,
ExecutionPositionIntelligenceMixin,
ExecutionPositionExitDecisionMixin,
ExecutionSizingMixin,
ExecutionPositionActionsMixin,
ExecutionPositionProtectionMixin,
@@ -45,11 +46,11 @@ class ExecutionEngine(
):
_position = PositionState()
_size_precision = 5
_min_flip_confidence = 0.75
_min_flip_repeat_count = 3
_min_flip_hold_seconds = 60
_min_flip_confidence = 0.65
_min_flip_repeat_count = 2
_min_flip_hold_seconds = 20
_flip_cooldown_seconds = 45
_loss_flip_confidence = 0.9
_loss_flip_confidence = 0.75
_last_flip_block_key: str | None = None
_runtime_action_cooldown_seconds = 30
_last_runtime_action_key: str | None = None
@@ -61,7 +62,6 @@ class ExecutionEngine(
_max_execution_snapshot_age_seconds = 5
_degraded_market_block_states = {
"HIGH_VOLATILITY",
"CHAOTIC",
"LIQUIDITY_VOID",
}
@@ -70,47 +70,81 @@ class ExecutionEngine(
_last_supervisor_block_key: str | None = None
# вернуть ExecutionDecision без выполнения торгового действия
def _skip_execution(
self,
state: AutoTradeState,
reason: str,
) -> ExecutionDecision:
state.last_execution_action = EXECUTION_ACTION_NONE
state.last_execution_reason = reason
return ExecutionDecision(
EXECUTION_ACTION_NONE,
False,
reason,
)
def process(self, state: AutoTradeState) -> ExecutionDecision:
# Synchronize runtime state
self._sync_state_from_position(state)
if state.status != "RUNNING":
return ExecutionDecision("NONE", False, "Execution доступен только в режиме RUNNING.")
if state.status != EXECUTION_STATUS_RUNNING:
return self._skip_execution(
state,
"Execution доступен только в режиме RUNNING.",
)
self._update_unrealized_pnl(state)
# Emergency risk management
risk_decision = self._risk_close_decision(state)
if risk_decision is not None:
return risk_decision
# Runtime position protection
protection_decision = self._process_runtime_protection(state)
if protection_decision is not None:
return protection_decision
# Signal readiness validation
if state.decision_status != EXECUTION_DECISION_READY or not state.is_signal_ready:
reason = (
f"Execution ожидает READY "
f"(decision={state.decision_status}, "
f"ready={state.is_signal_ready})."
)
return self._skip_execution(
state,
reason,
)
# Execution supervisor
supervisor_decision = self._process_execution_supervisor(state)
if supervisor_decision is not None:
return supervisor_decision
if state.decision_status != "READY" or not state.is_signal_ready:
return ExecutionDecision("NONE", False, "Сигнал ещё не готов к execution.")
# Existing position validation
position = type(self)._position
# Не пытаемся повторно открыть позицию в ту же сторону.
# Сигнал остаётся валидным для UI/Telegram, но execution не дублируется.
if position.side == "LONG" and state.last_signal == "BUY":
if position.side == POSITION_SIDE_LONG and state.last_signal == SIGNAL_BUY:
return ExecutionDecision(
"NONE",
EXECUTION_ACTION_NONE,
False,
"Сигнал BUY совпадает с уже открытой LONG позицией.",
)
if position.side == "SHORT" and state.last_signal == "SELL":
if position.side == POSITION_SIDE_SHORT and state.last_signal == SIGNAL_SELL:
return ExecutionDecision(
"NONE",
EXECUTION_ACTION_NONE,
False,
"Сигнал SELL совпадает с уже открытой SHORT позицией.",
)
# Position flip
if self._should_flip_position(state):
flip_block_reason = self._flip_block_reason(state)
@@ -119,10 +153,22 @@ class ExecutionEngine(
return self._flip_position(state)
if state.last_signal == "BUY":
return self._open_position_if_empty(state=state, side="LONG", action="OPEN_LONG")
# New position opening
if state.last_signal == SIGNAL_BUY:
return self._open_position_if_empty(
state=state,
side=POSITION_SIDE_LONG,
action=EXECUTION_ACTION_OPEN_LONG,
)
if state.last_signal == "SELL":
return self._open_position_if_empty(state=state, side="SHORT", action="OPEN_SHORT")
if state.last_signal == SIGNAL_SELL:
return self._open_position_if_empty(
state=state,
side=POSITION_SIDE_SHORT,
action=EXECUTION_ACTION_OPEN_SHORT,
)
return ExecutionDecision("NONE", False, "Нет торгового действия.")
return self._skip_execution(
state,
"Нет торгового действия.",
)