# app/src/trading/execution/engine.py from __future__ import annotations from src.trading.auto.state import AutoTradeState from src.trading.execution.models import ExecutionDecision from src.trading.position.state import PositionState from src.trading.execution.pricing import ExecutionPricingMixin from src.trading.execution.position_runtime import ExecutionPositionRuntimeMixin 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 from src.trading.execution.risk_close import ExecutionRiskCloseMixin from src.trading.execution.flip import ExecutionFlipMixin 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( ExecutionCalculationsMixin, ExecutionResetsMixin, ExecutionPricingMixin, ExecutionPositionRuntimeMixin, ExecutionPositionExitDecisionMixin, ExecutionSizingMixin, ExecutionPositionActionsMixin, ExecutionPositionProtectionMixin, ExecutionSupervisorMixin, ExecutionRiskCloseMixin, ExecutionFlipMixin, ExecutionRuntimeActionsMixin, ): _position = PositionState() _size_precision = 5 _min_flip_confidence = 0.65 _min_flip_repeat_count = 2 _min_flip_hold_seconds = 20 _flip_cooldown_seconds = 45 _loss_flip_confidence = 0.75 _last_flip_block_key: str | None = None _runtime_action_cooldown_seconds = 30 _last_runtime_action_key: str | None = None _emergency_halt_drawdown_usd = 250.0 _emergency_halt_loss_streak = 5 _execution_cooldown_after_loss_seconds = 90 _max_execution_snapshot_age_seconds = 5 _degraded_market_block_states = { "CHAOTIC", "LIQUIDITY_VOID", } _conflict_execution_block = True _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 != 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 # Existing position validation position = type(self)._position # Не пытаемся повторно открыть позицию в ту же сторону. # Сигнал остаётся валидным для UI/Telegram, но execution не дублируется. if position.side == POSITION_SIDE_LONG and state.last_signal == SIGNAL_BUY: return ExecutionDecision( EXECUTION_ACTION_NONE, False, "Сигнал BUY совпадает с уже открытой LONG позицией.", ) if position.side == POSITION_SIDE_SHORT and state.last_signal == SIGNAL_SELL: return ExecutionDecision( EXECUTION_ACTION_NONE, False, "Сигнал SELL совпадает с уже открытой SHORT позицией.", ) # Position flip if self._should_flip_position(state): flip_block_reason = self._flip_block_reason(state) if flip_block_reason is not None: return self._block_flip(state, flip_block_reason) return self._flip_position(state) # 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 == SIGNAL_SELL: return self._open_position_if_empty( state=state, side=POSITION_SIDE_SHORT, action=EXECUTION_ACTION_OPEN_SHORT, ) return self._skip_execution( state, "Нет торгового действия.", )