diff --git a/app/src/trading/auto/auto_lifecycle.py b/app/src/trading/auto/auto_lifecycle.py index 2dc0fc2..49c7b97 100644 --- a/app/src/trading/auto/auto_lifecycle.py +++ b/app/src/trading/auto/auto_lifecycle.py @@ -11,6 +11,19 @@ from src.core.event_bus import EventBus from src.core.numbers import safe_float from src.core.types import NumericLike from src.trading.auto.state import AutoTradeState +from src.trading.auto.state_reset import ( + reset_adaptive_size_state, + reset_autonomous_runtime_state, + reset_cycle_statistics_state, + reset_execution_runtime_state, + reset_flip_runtime_state, + reset_loss_cooldown_state, + reset_market_analysis_state, + reset_position_protection_state, + reset_position_semantics_state, + reset_runtime_expiration_state, + reset_signal_runtime_state, +) from src.trading.execution.engine import ExecutionEngine from src.trading.strategies.base import BaseStrategy, StrategyContext from src.trading.strategies.registry import StrategyRegistry @@ -141,9 +154,8 @@ class AutoLifecycleMixin( state.status = "RUNNING" # При ручном запуске из OBSERVING очищаем старую cooldown-блокировку, # чтобы запуск не наследовал паузу прошлого цикла. - state.loss_cooldown_active = False - state.loss_cooldown_reason = None - state.last_loss_monotonic_at = None + reset_loss_cooldown_state(state) + state.execution_block_title = None state.execution_block_message = None state.execution_block_action = None @@ -168,21 +180,16 @@ class AutoLifecycleMixin( state.status = "RUNNING" self._reset_signal_tracking() - state.cycle_realized_pnl_usd = 0.0 - state.cycle_closed_trades = 0 - state.cycle_winning_trades = 0 - # Новый цикл должен начинаться без старой блокировки после убытков. - state.cycle_losing_trades = 0 - state.cycle_consecutive_losses = 0 - state.loss_cooldown_active = False - state.loss_cooldown_reason = None - state.last_loss_monotonic_at = None - state.execution_block_title = None - state.execution_block_message = None - state.execution_block_action = None - state.cycle_trade_fees_usd = 0.0 - state.cycle_overnight_fees_usd = 0.0 + reset_cycle_statistics_state(state) + reset_loss_cooldown_state(state) + reset_flip_runtime_state(state) + state.cycle_started_at = time.monotonic() + state.cycle_number = int(getattr(state, "cycle_number", 0) or 0) + 1 + + state.last_signal = "HOLD" + state.signal_started_at = time.monotonic() + state.cycle_number = int(getattr(state, "cycle_number", 0) or 0) + 1 state.last_flip_old_side = None state.last_flip_new_side = None @@ -227,30 +234,13 @@ class AutoLifecycleMixin( ) if previous_status == "OFF": - state.cycle_realized_pnl_usd = 0.0 - state.cycle_closed_trades = 0 - state.cycle_losing_trades = 0 - state.cycle_consecutive_losses = 0 - state.loss_cooldown_active = False - state.loss_cooldown_reason = None - state.last_loss_monotonic_at = None - state.cycle_winning_trades = 0 - state.cycle_trade_fees_usd = 0.0 - state.cycle_overnight_fees_usd = 0.0 + reset_cycle_statistics_state(state) + reset_loss_cooldown_state(state) + reset_flip_runtime_state(state) + reset_execution_runtime_state(state) + reset_position_semantics_state(state) + state.cycle_started_at = time.monotonic() - state.last_flip_old_side = None - state.last_flip_new_side = None - state.last_flip_pnl_usd = None - state.last_flip_reason = None - state.last_flip_monotonic_at = None - state.position_stall_state = None - state.position_stall_reason = None - state.position_mfe_percent = None - state.position_mae_percent = None - state.execution_block_title = None - state.execution_block_message = None - state.execution_block_action = None - state.execution_block_reason = None self._log_auto_status_changed( previous_status=previous_status, @@ -279,31 +269,14 @@ class AutoLifecycleMixin( return state, "Автоторговля уже выключена." state.status = "OFF" - state.cycle_realized_pnl_usd = 0.0 - state.cycle_closed_trades = 0 - state.cycle_losing_trades = 0 - state.cycle_consecutive_losses = 0 - state.loss_cooldown_active = False - state.loss_cooldown_reason = None - state.last_loss_monotonic_at = None - state.execution_block_title = None - state.execution_block_message = None - state.execution_block_action = None - state.execution_block_reason = None - state.cycle_winning_trades = 0 - state.cycle_trade_fees_usd = 0.0 - state.cycle_overnight_fees_usd = 0.0 + reset_cycle_statistics_state(state) + reset_loss_cooldown_state(state) + reset_execution_runtime_state(state) + reset_adaptive_size_state(state) + reset_flip_runtime_state(state) + reset_position_semantics_state(state) + state.cycle_started_at = None - state.adaptive_size_changed_at = None - state.last_flip_old_side = None - state.last_flip_new_side = None - state.last_flip_pnl_usd = None - state.last_flip_reason = None - state.last_flip_monotonic_at = None - state.position_stall_state = None - state.position_stall_reason = None - state.position_mfe_percent = None - state.position_mae_percent = None self.stop_loop() EventBus.emit( @@ -390,116 +363,21 @@ class AutoLifecycleMixin( state = self.get_state() - state.adaptive_size_base = None - state.adaptive_size_final = None - state.adaptive_size_multiplier = None - state.adaptive_size_reason = None - state.adaptive_size_factors = None - state.effective_risk_percent = None - state.effective_target_risk_usd = None - state.execution_size_adjustment_reason = None + reset_adaptive_size_state(state) + reset_signal_runtime_state(state) + reset_execution_runtime_state(state) + reset_market_analysis_state(state) + reset_runtime_expiration_state(state) + reset_position_semantics_state(state) + reset_position_protection_state(state) + reset_autonomous_runtime_state(state) + reset_loss_cooldown_state(state) - state.last_signal = "HOLD" - state.last_signal_repeat_count = 0 - state.last_signal_confidence = 0.0 - state.last_signal_reason = None - state.decision_status = "WAITING" - state.decision_reason = None - state.is_signal_confirmed = False - state.is_signal_ready = False - state.signal_confirmation_seconds = 0 state.signal_confirmation_required_seconds = self._confirm_min_duration_seconds state.signal_confirmation_missing_repeats = self._confirm_repeats - state.signal_confirmation_progress = 0.0 - state.signal_confirmation_reason = None - state.signal_started_at = None - state.signal_updated_at = None - - state.execution_block_reason = None - state.execution_semantic_status = None - state.execution_semantic_message = None - state.execution_semantic_reason = None - state.execution_quality = None - state.execution_quality_reason = None - state.execution_quality_message = None - state.execution_price_source = None - state.execution_price_age_seconds = None - state.execution_bid_price = None - state.execution_ask_price = None - state.execution_last_price = None - state.execution_price_freshness = None - state.execution_confidence_score = None - state.execution_confidence_level = None - state.execution_confidence_required_score = self._execution_confidence_required_score - state.execution_confidence_reason = None - state.execution_confidence_factors = None - - state.market_state = None - state.market_trend = None - state.market_volatility = None - state.market_analysis_interval = None - state.market_analysis_reason = None - state.market_analysis_updated_at = None - state.market_runtime_degraded = False - state.market_trend_strength = None - state.market_trend_quality = None - state.market_phase = None - state.market_phase_direction = None - state.market_structure = None - state.market_structure_reason = None - state.market_score = None - state.market_score_label = None - state.market_long_score = None - state.market_short_score = None - - state.last_closed_candle_change_percent = None - state.last_closed_candle_direction = None - state.current_interval_change_percent = None - state.current_interval_direction = None - state.current_interval_label = None - - state.market_trend_gap_percent = None - state.market_trend_consistency = None - state.market_trend_efficiency = None - state.trend_quality_score = None - state.ema_distance_atr_ratio = None - state.ema_distance_state = None - state.entry_timing_state = None - state.entry_timing_reason = None - state.ema_fast_slope_percent = None - state.ema_slow_slope_percent = None - state.candle_noise_score = None - state.price_position_score = None - - state.htf_interval = None - state.htf_atr_percent = None - state.htf_atr_percent_baseline = None - state.htf_volatility_ratio = None - state.htf_volatility = None - state.htf_market_state = None - state.htf_trend = None - state.htf_trend_strength = None - state.htf_trend_quality = None - state.htf_market_phase = None - state.htf_alignment = None - state.htf_confirmation_score = None - state.htf_reason = None - - state.entry_block_reason = None - state.entry_block_message = None - - state.momentum_state = None - state.momentum_direction = None - state.momentum_change_percent = None - state.momentum_strength = None - state.breakout_level = None - state.breakout_distance_percent = None - state.breakout_reason = None - - state.runtime_expired_reason = None - state.runtime_expired_message = None - state.snapshot_age_seconds = None - state.spread_percent = None + state.execution_confidence_required_score = ( + self._execution_confidence_required_score + ) state.position_pnl_percent = None state.position_hold_seconds = None @@ -513,58 +391,6 @@ class AutoLifecycleMixin( state.position_adverse_momentum = False state.position_exit_pressure = None - state.position_lifecycle_stage = None - state.position_hold_quality = None - state.position_decay_state = None - state.position_exit_confidence = None - state.position_exit_signal = None - state.position_intelligence_reason = None - state.position_recommended_action = None - - state.position_peak_pnl_usd = None - state.position_peak_pnl_percent = None - state.position_mfe_percent = None - state.position_mae_percent = None - state.position_fatigue_score = None - state.position_fatigue_state = None - state.position_giveback_percent = None - state.position_conviction_state = None - state.position_exit_urgency = None - state.position_reversal_risk = None - state.position_stall_state = None - state.position_stall_reason = None - - state.position_protection_status = None - state.position_protection_reason = None - state.break_even_armed = False - state.break_even_price = None - state.trailing_stop_active = False - state.trailing_stop_price = None - state.profit_lock_active = False - state.profit_lock_price = None - state.runtime_protection_action = None - state.runtime_protection_reason = None - state.runtime_protection_updated_at = None - - state.autonomous_action = None - state.autonomous_action_reason = None - state.autonomous_action_confidence = None - state.autonomous_protection_required = False - state.autonomous_reduce_required = False - state.autonomous_exit_required = False - state.autonomous_last_action = None - state.autonomous_last_action_reason = None - state.autonomous_last_action_at = None - state.last_loss_monotonic_at = None - - # Сброс именно runtime-блокировки, чтобы после нового запуска - # не оставалась старая пауза после прошлой убыточной сделки. - state.loss_cooldown_active = False - state.loss_cooldown_reason = None - state.execution_block_title = None - state.execution_block_message = None - state.execution_block_action = None - def _build_strategy_context(self) -> StrategyContext: state = self.get_state() diff --git a/app/src/trading/auto/state_reset.py b/app/src/trading/auto/state_reset.py new file mode 100644 index 0000000..d5c7e97 --- /dev/null +++ b/app/src/trading/auto/state_reset.py @@ -0,0 +1,428 @@ +# app/src/trading/auto/state_reset.py + +from __future__ import annotations + +""" +Centralized runtime reset helpers for AutoTrade. +Helpers для сброса отдельных частей AutoTradeState. + +ВАЖНО: +Все функции этого файла отвечают только за очистку runtime-состояния. +Каждая функция отвечает только за одну смысловую область runtime. + +Функции намеренно не используют EventBus, JournalService, +ExecutionEngine или другие сервисы. Они изменяют только +поля AutoTradeState. + +Это позволяет безопасно использовать их из разных runtime-компонентов +без изменения бизнес-логики. + +ВАЖНО: +Они не должны изменять пользовательские настройки: + • symbol + • strategy + • leverage + • risk_percent + • stop_loss_percent + • take_profit_percent + • allocated_balance_usd + +Их задача — заменить сотни ручных присваиваний вида + + state.xxx = None + state.yyy = False + state.zzz = 0 + +едиными helper-функциями. + +Используются из: + • auto_lifecycle.py + • signal_runtime.py + • execution.py + • других runtime-модулей. +""" + +from src.trading.auto.state import AutoTradeState + + +# ----------------------------------------------------------------------------- +# Adaptive Position Sizing +# ----------------------------------------------------------------------------- +# Runtime-параметры, связанные с динамическим изменением размера позиции. +# Сбрасываются при начале нового цикла или полном сбросе runtime. +# ----------------------------------------------------------------------------- + +def reset_adaptive_size_state(state: AutoTradeState) -> None: + state.adaptive_size_base = None + state.adaptive_size_final = None + state.adaptive_size_multiplier = None + state.adaptive_size_reason = None + state.adaptive_size_factors = None + state.effective_risk_percent = None + state.effective_target_risk_usd = None + state.execution_size_adjustment_reason = None + state.adaptive_size_changed_at = None + + +# ----------------------------------------------------------------------------- +# Signal Runtime +# ----------------------------------------------------------------------------- +# Сбрасывает только runtime сигнала. +# Не затрагивает market context, позицию или protection. +# ----------------------------------------------------------------------------- + +def reset_signal_runtime_state(state: AutoTradeState) -> None: + state.last_signal = "HOLD" + state.last_signal_repeat_count = 0 + state.last_signal_confidence = 0.0 + state.last_signal_reason = None + + state.signal_started_at = None + state.signal_updated_at = None + + state.signal_confirmation_seconds = 0 + state.signal_confirmation_missing_repeats = 0 + state.signal_confirmation_progress = 0.0 + state.signal_confirmation_reason = None + + state.decision_status = "WAITING" + state.decision_reason = None + state.is_signal_confirmed = False + state.is_signal_ready = False + + +# ----------------------------------------------------------------------------- +# Execution Runtime +# ----------------------------------------------------------------------------- +# Сбрасывает все runtime-данные, связанные с execution layer: +# semantic status, quality, pricing snapshot и confidence. +# ----------------------------------------------------------------------------- + +def reset_execution_runtime_state(state: AutoTradeState) -> None: + state.execution_block_reason = None + state.execution_block_title = None + state.execution_block_message = None + state.execution_block_action = None + + state.execution_semantic_status = None + state.execution_semantic_message = None + state.execution_semantic_reason = None + + state.execution_quality = None + state.execution_quality_reason = None + state.execution_quality_message = None + + state.execution_price_source = None + state.execution_price_age_seconds = None + state.execution_bid_price = None + state.execution_ask_price = None + state.execution_last_price = None + state.execution_price_freshness = None + + state.execution_confidence_score = None + state.execution_confidence_level = None + state.execution_confidence_reason = None + state.execution_confidence_factors = None + + state.snapshot_age_seconds = None + state.spread_percent = None + + +# ----------------------------------------------------------------------------- +# Market Analysis Runtime +# ----------------------------------------------------------------------------- +# Полностью очищает runtime-анализ рынка. +# Используется при истечении TTL или полном сбросе runtime. +# ----------------------------------------------------------------------------- + +def reset_market_analysis_state(state: AutoTradeState) -> None: + state.market_state = None + state.market_trend = None + state.market_volatility = None + + state.market_trend_strength = None + state.market_trend_quality = None + + state.market_phase = None + state.market_phase_direction = None + + state.market_score = None + state.market_score_label = None + state.market_long_score = None + state.market_short_score = None + + state.last_closed_candle_change_percent = None + state.last_closed_candle_direction = None + + state.current_interval_change_percent = None + state.current_interval_direction = None + state.current_interval_label = None + + state.market_structure = None + state.market_structure_reason = None + + state.market_trend_gap_percent = None + state.market_trend_consistency = None + state.market_trend_efficiency = None + + state.trend_quality_score = None + + state.ema_distance_atr_ratio = None + state.ema_distance_state = None + + state.entry_timing_state = None + state.entry_timing_reason = None + + state.ema_fast_slope_percent = None + state.ema_slow_slope_percent = None + + state.candle_noise_score = None + state.price_position_score = None + + state.htf_interval = None + state.htf_atr_percent = None + state.htf_atr_percent_baseline = None + state.htf_volatility_ratio = None + state.htf_volatility = None + + state.htf_market_state = None + state.htf_trend = None + state.htf_trend_strength = None + state.htf_trend_quality = None + state.htf_market_phase = None + + state.htf_alignment = None + state.htf_confirmation_score = None + state.htf_reason = None + + state.momentum_state = None + state.momentum_direction = None + state.momentum_change_percent = None + state.momentum_strength = None + + state.breakout_level = None + state.breakout_distance_percent = None + state.breakout_reason = None + + state.market_analysis_interval = None + state.market_analysis_reason = None + state.market_analysis_updated_at = None + + state.entry_block_reason = None + state.entry_block_message = None + + state.market_runtime_degraded = False + + +# ----------------------------------------------------------------------------- +# Runtime Expiration +# ----------------------------------------------------------------------------- +# Причина последнего автоматического сброса runtime. +# ----------------------------------------------------------------------------- + +def reset_runtime_expiration_state(state: AutoTradeState) -> None: + state.runtime_expired_reason = None + state.runtime_expired_message = None + + +# ----------------------------------------------------------------------------- +# Position Runtime +# ----------------------------------------------------------------------------- + +def reset_position_runtime_state(state: AutoTradeState) -> None: + state.position_side = "NONE" + state.entry_price = None + state.position_size = None + state.position_opened_monotonic_at = None + state.unrealized_pnl_usd = None + + state.position_pnl_percent = None + state.position_hold_seconds = None + state.position_pressure = None + state.position_health_score = None + state.position_health_status = None + state.position_health_reason = None + state.position_risk_level = None + state.position_risk_reason = None + state.position_trend_alignment = None + state.position_adverse_momentum = False + state.position_exit_pressure = None + + +# ----------------------------------------------------------------------------- +# Position Semantics / Intelligence +# ----------------------------------------------------------------------------- + +def reset_position_semantics_state(state: AutoTradeState) -> None: + state.position_lifecycle_stage = None + state.position_hold_quality = None + state.position_decay_state = None + state.position_exit_confidence = None + state.position_exit_signal = None + state.position_intelligence_reason = None + state.position_recommended_action = None + + state.position_peak_pnl_usd = None + state.position_peak_pnl_percent = None + state.position_mfe_percent = None + state.position_mae_percent = None + state.position_fatigue_score = None + state.position_fatigue_state = None + state.position_giveback_percent = None + state.position_conviction_state = None + state.position_exit_urgency = None + state.position_reversal_risk = None + state.position_stall_state = None + state.position_stall_reason = None + + +# ----------------------------------------------------------------------------- +# Protection Runtime +# ----------------------------------------------------------------------------- + +def reset_position_protection_state(state: AutoTradeState) -> None: + state.position_protection_status = None + state.position_protection_reason = None + + state.break_even_armed = False + state.break_even_price = None + state.trailing_stop_active = False + state.trailing_stop_price = None + state.profit_lock_active = False + state.profit_lock_price = None + + state.runtime_protection_action = None + state.runtime_protection_reason = None + state.runtime_protection_updated_at = None + + +# ----------------------------------------------------------------------------- +# Autonomous Management +# ----------------------------------------------------------------------------- + +def reset_autonomous_runtime_state(state: AutoTradeState) -> None: + state.autonomous_action = None + state.autonomous_action_reason = None + state.autonomous_action_confidence = None + state.autonomous_protection_required = False + state.autonomous_reduce_required = False + state.autonomous_exit_required = False + + state.autonomous_last_action = None + state.autonomous_last_action_reason = None + state.autonomous_last_action_at = None + + +# ----------------------------------------------------------------------------- +# Loss Cooldown +# ----------------------------------------------------------------------------- + +def reset_loss_cooldown_state(state: AutoTradeState) -> None: + state.last_loss_monotonic_at = None + state.loss_cooldown_active = False + state.loss_cooldown_reason = None + + +# ----------------------------------------------------------------------------- +# Flip Runtime +# ----------------------------------------------------------------------------- + +def reset_flip_runtime_state(state: AutoTradeState) -> None: + state.last_flip_old_side = None + state.last_flip_new_side = None + state.last_flip_pnl_usd = None + state.last_flip_reason = None + state.last_flip_monotonic_at = None + state.last_flip_at = None + state.last_flip_block_reason = None + + +# ----------------------------------------------------------------------------- +# Last Execution Runtime +# ----------------------------------------------------------------------------- + +def reset_last_execution_state(state: AutoTradeState) -> None: + state.last_execution_action = None + state.last_execution_reason = None + + +# ----------------------------------------------------------------------------- +# Cycle Statistics +# ----------------------------------------------------------------------------- + +def reset_cycle_statistics_state(state: AutoTradeState) -> None: + state.cycle_realized_pnl_usd = 0.0 + state.cycle_closed_trades = 0 + state.cycle_winning_trades = 0 + state.cycle_losing_trades = 0 + state.cycle_consecutive_losses = 0 + state.cycle_trade_fees_usd = 0.0 + state.cycle_overnight_fees_usd = 0.0 + + +# ----------------------------------------------------------------------------- +# Full Runtime Reset +# ----------------------------------------------------------------------------- + +def reset_full_runtime_state(state: AutoTradeState) -> None: + reset_cycle_statistics_state(state) + reset_adaptive_size_state(state) + reset_signal_runtime_state(state) + reset_execution_runtime_state(state) + reset_market_analysis_state(state) + reset_runtime_expiration_state(state) + reset_position_runtime_state(state) + reset_position_semantics_state(state) + reset_position_protection_state(state) + reset_autonomous_runtime_state(state) + reset_loss_cooldown_state(state) + reset_flip_runtime_state(state) + reset_last_execution_state(state) + + +# Alias на случай старого имени. +def reset_runtime_state(state: AutoTradeState) -> None: + reset_full_runtime_state(state) + + +# ----------------------------------------------------------------------------- +# Lifecycle Scenario Resets +# ----------------------------------------------------------------------------- + +def reset_after_auto_start(state: AutoTradeState) -> None: + reset_cycle_statistics_state(state) + reset_loss_cooldown_state(state) + reset_adaptive_size_state(state) + reset_signal_runtime_state(state) + reset_execution_runtime_state(state) + reset_runtime_expiration_state(state) + reset_flip_runtime_state(state) + + +def reset_after_auto_stop(state: AutoTradeState) -> None: + reset_full_runtime_state(state) + + +def reset_after_symbol_or_strategy_change(state: AutoTradeState) -> None: + reset_adaptive_size_state(state) + reset_signal_runtime_state(state) + reset_execution_runtime_state(state) + reset_market_analysis_state(state) + reset_runtime_expiration_state(state) + + +def reset_after_position_closed(state: AutoTradeState) -> None: + reset_position_runtime_state(state) + reset_position_semantics_state(state) + reset_position_protection_state(state) + reset_autonomous_runtime_state(state) + reset_flip_runtime_state(state) + + +def reset_after_market_runtime_expired(state: AutoTradeState) -> None: + reset_market_analysis_state(state) + + +def reset_after_signal_runtime_expired(state: AutoTradeState) -> None: + reset_signal_runtime_state(state) + reset_execution_runtime_state(state) \ No newline at end of file