Files
dzentra_bot/app/src/trading/execution/engine.py

174 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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,
"Нет торгового действия.",
)