Stage 07.4.4.1.15 — Runtime Payload Builders & Auto Runtime Refactoring
This commit is contained in:
@@ -41,6 +41,29 @@ class AutoLifecycleMixin(
|
|||||||
_confirm_repeats: int
|
_confirm_repeats: int
|
||||||
_execution_confidence_required_score: float
|
_execution_confidence_required_score: float
|
||||||
|
|
||||||
|
# собрать payload изменения статуса автоторговли без изменения состояния
|
||||||
|
def _build_auto_status_changed_payload(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
previous_status: str,
|
||||||
|
new_status: str,
|
||||||
|
state: AutoTradeState,
|
||||||
|
) -> dict[str, object]:
|
||||||
|
return {
|
||||||
|
"previous_status": previous_status,
|
||||||
|
"new_status": new_status,
|
||||||
|
"symbol": state.symbol,
|
||||||
|
"strategy": state.strategy,
|
||||||
|
"cycle_number": state.cycle_number,
|
||||||
|
"risk_percent": state.risk_percent,
|
||||||
|
"leverage": state.leverage,
|
||||||
|
"allocated_balance_usd": state.allocated_balance_usd,
|
||||||
|
"stop_loss_percent": state.stop_loss_percent,
|
||||||
|
"take_profit_percent": state.take_profit_percent,
|
||||||
|
"max_loss_usd": state.max_loss_usd,
|
||||||
|
"max_reserved_balance_percent": state.max_reserved_balance_percent,
|
||||||
|
}
|
||||||
|
|
||||||
def _log_auto_status_changed(
|
def _log_auto_status_changed(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -56,20 +79,11 @@ class AutoLifecycleMixin(
|
|||||||
message=message,
|
message=message,
|
||||||
screen="auto",
|
screen="auto",
|
||||||
action=action,
|
action=action,
|
||||||
payload={
|
payload=self._build_auto_status_changed_payload(
|
||||||
"previous_status": previous_status,
|
previous_status=previous_status,
|
||||||
"new_status": new_status,
|
new_status=new_status,
|
||||||
"symbol": state.symbol,
|
state=state,
|
||||||
"strategy": state.strategy,
|
),
|
||||||
"cycle_number": state.cycle_number,
|
|
||||||
"risk_percent": state.risk_percent,
|
|
||||||
"leverage": state.leverage,
|
|
||||||
"allocated_balance_usd": state.allocated_balance_usd,
|
|
||||||
"stop_loss_percent": state.stop_loss_percent,
|
|
||||||
"take_profit_percent": state.take_profit_percent,
|
|
||||||
"max_loss_usd": state.max_loss_usd,
|
|
||||||
"max_reserved_balance_percent": state.max_reserved_balance_percent,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def set_allocated_balance_usd(self, value: NumericLike) -> AutoTradeState:
|
def set_allocated_balance_usd(self, value: NumericLike) -> AutoTradeState:
|
||||||
|
|||||||
@@ -143,6 +143,29 @@ class AutoExecutionQualityMixin:
|
|||||||
|
|
||||||
return "MARKET_BREAK"
|
return "MARKET_BREAK"
|
||||||
|
|
||||||
|
# собрать payload изменения доступности биржи без изменения состояния
|
||||||
|
def _build_exchange_availability_payload(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
state: AutoTradeState,
|
||||||
|
status: ExchangeRuntimeStatus,
|
||||||
|
reason: str,
|
||||||
|
) -> dict[str, object]:
|
||||||
|
return {
|
||||||
|
"status": state.status,
|
||||||
|
"symbol": state.symbol,
|
||||||
|
"strategy": state.strategy,
|
||||||
|
"exchange_status_code": status.code.value,
|
||||||
|
"exchange_reason": status.reason,
|
||||||
|
"execution_reason": reason,
|
||||||
|
"is_open": status.is_open,
|
||||||
|
"is_available": status.is_available,
|
||||||
|
"is_auth_ok": status.is_auth_ok,
|
||||||
|
"message": status.message,
|
||||||
|
"raw_status": status.raw_status,
|
||||||
|
"raw_error": status.raw_error,
|
||||||
|
}
|
||||||
|
|
||||||
# залогировать изменение доступности биржи/рынка
|
# залогировать изменение доступности биржи/рынка
|
||||||
def _log_exchange_availability_if_changed(
|
def _log_exchange_availability_if_changed(
|
||||||
self,
|
self,
|
||||||
@@ -167,20 +190,11 @@ class AutoExecutionQualityMixin:
|
|||||||
message=status.ui_line,
|
message=status.ui_line,
|
||||||
screen="auto",
|
screen="auto",
|
||||||
action="exchange_status",
|
action="exchange_status",
|
||||||
payload={
|
payload=self._build_exchange_availability_payload(
|
||||||
"status": state.status,
|
state=state,
|
||||||
"symbol": state.symbol,
|
status=status,
|
||||||
"strategy": state.strategy,
|
reason=reason,
|
||||||
"exchange_status_code": status.code.value,
|
),
|
||||||
"exchange_reason": status.reason,
|
|
||||||
"execution_reason": reason,
|
|
||||||
"is_open": status.is_open,
|
|
||||||
"is_available": status.is_available,
|
|
||||||
"is_auth_ok": status.is_auth_ok,
|
|
||||||
"message": status.message,
|
|
||||||
"raw_status": status.raw_status,
|
|
||||||
"raw_error": status.raw_error,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -437,6 +451,20 @@ class AutoExecutionQualityMixin:
|
|||||||
else:
|
else:
|
||||||
state.execution_price_freshness = "STALE"
|
state.execution_price_freshness = "STALE"
|
||||||
|
|
||||||
|
# собрать payload изменения качества исполнения без изменения состояния
|
||||||
|
def _build_execution_quality_payload(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
state: AutoTradeState,
|
||||||
|
payload: dict[str, object],
|
||||||
|
) -> dict[str, object]:
|
||||||
|
return {
|
||||||
|
**payload,
|
||||||
|
"status": state.status,
|
||||||
|
"symbol": state.symbol,
|
||||||
|
"strategy": state.strategy,
|
||||||
|
}
|
||||||
|
|
||||||
# записать событие изменения execution quality
|
# записать событие изменения execution quality
|
||||||
def _log_execution_quality_if_changed(
|
def _log_execution_quality_if_changed(
|
||||||
self,
|
self,
|
||||||
@@ -462,12 +490,10 @@ class AutoExecutionQualityMixin:
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
log_payload = {
|
log_payload = self._build_execution_quality_payload(
|
||||||
**payload,
|
state=state,
|
||||||
"status": state.status,
|
payload=payload,
|
||||||
"symbol": state.symbol,
|
)
|
||||||
"strategy": state.strategy,
|
|
||||||
}
|
|
||||||
|
|
||||||
if quality == "BLOCKED":
|
if quality == "BLOCKED":
|
||||||
JournalService().log_ui_warning(
|
JournalService().log_ui_warning(
|
||||||
|
|||||||
@@ -140,47 +140,17 @@ class AutoMarketRuntimeMixin:
|
|||||||
payload=payload,
|
payload=payload,
|
||||||
)
|
)
|
||||||
|
|
||||||
# записать entry-block событие, если причина изменилась или истёк TTL
|
# собрать payload entry-block события без изменения состояния
|
||||||
def _log_entry_block_if_changed(
|
def _build_entry_block_payload(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
state: AutoTradeState,
|
state: AutoTradeState,
|
||||||
payload: JsonDict,
|
payload: JsonDict,
|
||||||
) -> None:
|
reason: str,
|
||||||
reason = state.entry_block_reason
|
message: str,
|
||||||
message = state.entry_block_message
|
key: str,
|
||||||
|
) -> JsonDict:
|
||||||
if not reason or not message:
|
return {
|
||||||
return
|
|
||||||
|
|
||||||
now = time.monotonic()
|
|
||||||
|
|
||||||
# status специально не входит в key:
|
|
||||||
# RUNNING / OBSERVING не должны создавать дубли одной и той же причины.
|
|
||||||
key = f"{state.symbol}:{state.strategy}:{reason}:{message}"
|
|
||||||
|
|
||||||
last_logged_at = type(self)._last_logged_entry_block_at
|
|
||||||
ttl_expired = (
|
|
||||||
last_logged_at is None
|
|
||||||
or now - last_logged_at >= type(self)._entry_block_log_ttl_seconds
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
|
||||||
key == type(self)._last_logged_entry_block_reason
|
|
||||||
and not ttl_expired
|
|
||||||
):
|
|
||||||
return
|
|
||||||
|
|
||||||
type(self)._last_logged_entry_block_reason = key
|
|
||||||
type(self)._last_logged_entry_block_at = now
|
|
||||||
|
|
||||||
try:
|
|
||||||
JournalService().log_ui_info(
|
|
||||||
event_type="entry_blocked",
|
|
||||||
message=f"Вход в позицию не выполнен: {message}.",
|
|
||||||
screen="auto",
|
|
||||||
action="entry_diagnostics",
|
|
||||||
payload={
|
|
||||||
**payload,
|
**payload,
|
||||||
"entry_block_reason": reason,
|
"entry_block_reason": reason,
|
||||||
"entry_block_message": message,
|
"entry_block_message": message,
|
||||||
@@ -222,11 +192,81 @@ class AutoMarketRuntimeMixin:
|
|||||||
"last_signal": state.last_signal,
|
"last_signal": state.last_signal,
|
||||||
"last_signal_confidence": state.last_signal_confidence,
|
"last_signal_confidence": state.last_signal_confidence,
|
||||||
"last_signal_reason": state.last_signal_reason,
|
"last_signal_reason": state.last_signal_reason,
|
||||||
},
|
}
|
||||||
|
|
||||||
|
# записать entry-block событие, если причина изменилась или истёк TTL
|
||||||
|
def _log_entry_block_if_changed(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
state: AutoTradeState,
|
||||||
|
payload: JsonDict,
|
||||||
|
) -> None:
|
||||||
|
reason = state.entry_block_reason
|
||||||
|
message = state.entry_block_message
|
||||||
|
|
||||||
|
if not reason or not message:
|
||||||
|
return
|
||||||
|
|
||||||
|
now = time.monotonic()
|
||||||
|
|
||||||
|
# status специально не входит в key:
|
||||||
|
# RUNNING / OBSERVING не должны создавать дубли одной и той же причины.
|
||||||
|
key = f"{state.symbol}:{state.strategy}:{reason}:{message}"
|
||||||
|
|
||||||
|
last_logged_at = type(self)._last_logged_entry_block_at
|
||||||
|
ttl_expired = (
|
||||||
|
last_logged_at is None
|
||||||
|
or now - last_logged_at >= type(self)._entry_block_log_ttl_seconds
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
key == type(self)._last_logged_entry_block_reason
|
||||||
|
and not ttl_expired
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
type(self)._last_logged_entry_block_reason = key
|
||||||
|
type(self)._last_logged_entry_block_at = now
|
||||||
|
|
||||||
|
try:
|
||||||
|
JournalService().log_ui_info(
|
||||||
|
event_type="entry_blocked",
|
||||||
|
message=f"Вход в позицию не выполнен: {message}.",
|
||||||
|
screen="auto",
|
||||||
|
action="entry_diagnostics",
|
||||||
|
payload=self._build_entry_block_payload(
|
||||||
|
state=state,
|
||||||
|
payload=payload,
|
||||||
|
reason=reason,
|
||||||
|
message=message,
|
||||||
|
key=key,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# собрать payload изменения состояния рынка без изменения состояния
|
||||||
|
def _build_market_state_payload(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
payload: JsonDict,
|
||||||
|
previous_market_state: str | None,
|
||||||
|
previous_market_trend: str | None,
|
||||||
|
previous_market_volatility: str | None,
|
||||||
|
current_market_state: str | None,
|
||||||
|
current_market_trend: str | None,
|
||||||
|
current_market_volatility: str | None,
|
||||||
|
) -> JsonDict:
|
||||||
|
return {
|
||||||
|
**payload,
|
||||||
|
"previous_market_state": previous_market_state,
|
||||||
|
"previous_market_trend": previous_market_trend,
|
||||||
|
"previous_market_volatility": previous_market_volatility,
|
||||||
|
"current_market_state": current_market_state,
|
||||||
|
"current_market_trend": current_market_trend,
|
||||||
|
"current_market_volatility": current_market_volatility,
|
||||||
|
}
|
||||||
|
|
||||||
# записать market state / volatility событие, если состояние изменилось
|
# записать market state / volatility событие, если состояние изменилось
|
||||||
def _log_market_state_if_changed(
|
def _log_market_state_if_changed(
|
||||||
self,
|
self,
|
||||||
@@ -269,15 +309,15 @@ class AutoMarketRuntimeMixin:
|
|||||||
if not state_changed and not volatility_changed:
|
if not state_changed and not volatility_changed:
|
||||||
return
|
return
|
||||||
|
|
||||||
journal_payload = {
|
journal_payload = self._build_market_state_payload(
|
||||||
**payload,
|
payload=payload,
|
||||||
"previous_market_state": previous_market_state,
|
previous_market_state=previous_market_state,
|
||||||
"previous_market_trend": previous_market_trend,
|
previous_market_trend=previous_market_trend,
|
||||||
"previous_market_volatility": previous_market_volatility,
|
previous_market_volatility=previous_market_volatility,
|
||||||
"current_market_state": market_state,
|
current_market_state=market_state,
|
||||||
"current_market_trend": market_trend,
|
current_market_trend=market_trend,
|
||||||
"current_market_volatility": market_volatility,
|
current_market_volatility=market_volatility,
|
||||||
}
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if state_changed:
|
if state_changed:
|
||||||
|
|||||||
@@ -388,6 +388,31 @@ class AutoTradeRunner:
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _build_position_aligned_signal_suppressed_payload(
|
||||||
|
cls,
|
||||||
|
*,
|
||||||
|
symbol: str,
|
||||||
|
strategy: str,
|
||||||
|
signal: str,
|
||||||
|
position_side: str,
|
||||||
|
confidence: float,
|
||||||
|
repeat_count: int,
|
||||||
|
reason: str | None,
|
||||||
|
) -> JsonDict:
|
||||||
|
return {
|
||||||
|
"symbol": symbol,
|
||||||
|
"strategy": strategy,
|
||||||
|
"signal": signal,
|
||||||
|
"position_side": position_side,
|
||||||
|
"confidence": confidence,
|
||||||
|
"repeat_count": repeat_count,
|
||||||
|
"reason": reason,
|
||||||
|
"suppression_interval_seconds": (
|
||||||
|
cls._position_aligned_signal_log_interval_seconds
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _log_position_aligned_signal_suppressed(
|
def _log_position_aligned_signal_suppressed(
|
||||||
cls,
|
cls,
|
||||||
@@ -444,16 +469,15 @@ class AutoTradeRunner:
|
|||||||
),
|
),
|
||||||
screen="auto",
|
screen="auto",
|
||||||
action="signal_notification",
|
action="signal_notification",
|
||||||
payload={
|
payload=cls._build_position_aligned_signal_suppressed_payload(
|
||||||
"symbol": symbol,
|
symbol=symbol,
|
||||||
"strategy": strategy,
|
strategy=strategy,
|
||||||
"signal": signal,
|
signal=signal,
|
||||||
"position_side": position_side,
|
position_side=position_side,
|
||||||
"confidence": confidence,
|
confidence=confidence,
|
||||||
"repeat_count": repeat_count,
|
repeat_count=repeat_count,
|
||||||
"reason": payload.get("reason") or state.last_signal_reason,
|
reason=payload.get("reason") or state.last_signal_reason,
|
||||||
"suppression_interval_seconds": cls._position_aligned_signal_log_interval_seconds,
|
),
|
||||||
},
|
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -509,6 +533,46 @@ class AutoTradeRunner:
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _build_runtime_signal_payload(
|
||||||
|
cls,
|
||||||
|
*,
|
||||||
|
state,
|
||||||
|
symbol: str,
|
||||||
|
strategy: str,
|
||||||
|
signal: str,
|
||||||
|
signal_intent: str,
|
||||||
|
repeat_count: int,
|
||||||
|
confidence: float,
|
||||||
|
leverage,
|
||||||
|
reason: str,
|
||||||
|
position_context: str,
|
||||||
|
is_aligned_signal: bool,
|
||||||
|
semantic_lines: list[str],
|
||||||
|
price_payload: JsonDict,
|
||||||
|
) -> JsonDict:
|
||||||
|
return {
|
||||||
|
"execution_block_title": getattr(state, "execution_block_title", None),
|
||||||
|
"execution_block_message": getattr(state, "execution_block_message", None),
|
||||||
|
"execution_block_action": getattr(state, "execution_block_action", None),
|
||||||
|
"symbol": symbol,
|
||||||
|
"strategy": strategy,
|
||||||
|
"signal": signal,
|
||||||
|
"signal_intent": signal_intent,
|
||||||
|
"repeat_count": repeat_count,
|
||||||
|
"confidence": confidence,
|
||||||
|
"leverage": leverage,
|
||||||
|
"reason": reason,
|
||||||
|
"position_context": position_context,
|
||||||
|
"position_side": position_context,
|
||||||
|
"is_position_aligned_signal": is_aligned_signal,
|
||||||
|
"decision_status": state.decision_status,
|
||||||
|
"market_score": getattr(state, "market_score", None),
|
||||||
|
"market_score_label": getattr(state, "market_score_label", None),
|
||||||
|
"semantic_lines": semantic_lines,
|
||||||
|
**price_payload,
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _publish_strong_signal_event(
|
def _publish_strong_signal_event(
|
||||||
cls,
|
cls,
|
||||||
@@ -573,32 +637,21 @@ class AutoTradeRunner:
|
|||||||
event_type=RuntimeEventType.AUTO_SIGNAL_READY,
|
event_type=RuntimeEventType.AUTO_SIGNAL_READY,
|
||||||
source="auto_trade_runner",
|
source="auto_trade_runner",
|
||||||
title=f"Auto strong signal {signal}",
|
title=f"Auto strong signal {signal}",
|
||||||
payload={
|
payload=cls._build_runtime_signal_payload(
|
||||||
"execution_block_title": getattr(state, "execution_block_title", None),
|
state=state,
|
||||||
"execution_block_message": getattr(state, "execution_block_message", None),
|
symbol=symbol,
|
||||||
"execution_block_action": getattr(state, "execution_block_action", None),
|
strategy=strategy,
|
||||||
"symbol": symbol,
|
signal=signal,
|
||||||
"strategy": strategy,
|
signal_intent=signal_intent,
|
||||||
"signal": signal,
|
repeat_count=repeat_count,
|
||||||
"signal_intent": signal_intent,
|
confidence=confidence,
|
||||||
"repeat_count": repeat_count,
|
leverage=leverage,
|
||||||
"confidence": confidence,
|
reason=reason,
|
||||||
"leverage": leverage,
|
position_context=position_context,
|
||||||
"reason": reason,
|
is_aligned_signal=is_aligned_signal,
|
||||||
"position_context": position_context,
|
semantic_lines=semantic_lines,
|
||||||
"position_side": position_context,
|
price_payload=price_payload,
|
||||||
"is_position_aligned_signal": is_aligned_signal,
|
),
|
||||||
"decision_status": state.decision_status,
|
|
||||||
|
|
||||||
# market_score передаём в уведомления,
|
|
||||||
# чтобы позже можно было показывать “Рынок · благоприятный · 82%”
|
|
||||||
# не только в экране, но и в событиях/алертах.
|
|
||||||
"market_score": getattr(state, "market_score", None),
|
|
||||||
"market_score_label": getattr(state, "market_score_label", None),
|
|
||||||
|
|
||||||
"semantic_lines": semantic_lines,
|
|
||||||
**price_payload,
|
|
||||||
},
|
|
||||||
priority=priority.lower(),
|
priority=priority.lower(),
|
||||||
dedupe_key=(
|
dedupe_key=(
|
||||||
f"auto_signal_ready:"
|
f"auto_signal_ready:"
|
||||||
@@ -612,6 +665,37 @@ class AutoTradeRunner:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _build_runtime_execution_payload(
|
||||||
|
cls,
|
||||||
|
*,
|
||||||
|
state,
|
||||||
|
payload: JsonDict,
|
||||||
|
event_type: str,
|
||||||
|
symbol: str,
|
||||||
|
side: str,
|
||||||
|
old_side: str,
|
||||||
|
new_side: str,
|
||||||
|
semantic_lines: list[str],
|
||||||
|
) -> JsonDict:
|
||||||
|
return {
|
||||||
|
**payload,
|
||||||
|
"source_event_type": event_type,
|
||||||
|
"symbol": symbol,
|
||||||
|
"side": side,
|
||||||
|
"old_side": old_side,
|
||||||
|
"new_side": new_side,
|
||||||
|
"leverage": (
|
||||||
|
payload.get("leverage")
|
||||||
|
if payload.get("leverage") is not None
|
||||||
|
else state.leverage
|
||||||
|
),
|
||||||
|
"strategy": state.strategy,
|
||||||
|
"market_score": getattr(state, "market_score", None),
|
||||||
|
"market_score_label": getattr(state, "market_score_label", None),
|
||||||
|
"semantic_lines": semantic_lines,
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _publish_execution_event(
|
def _publish_execution_event(
|
||||||
cls,
|
cls,
|
||||||
@@ -636,27 +720,16 @@ class AutoTradeRunner:
|
|||||||
event_type=runtime_event_type,
|
event_type=runtime_event_type,
|
||||||
source="auto_trade_runner",
|
source="auto_trade_runner",
|
||||||
title=cls._execution_event_title(runtime_event_type),
|
title=cls._execution_event_title(runtime_event_type),
|
||||||
payload={
|
payload=cls._build_runtime_execution_payload(
|
||||||
**payload,
|
state=state,
|
||||||
"source_event_type": event_type,
|
payload=payload,
|
||||||
"symbol": symbol,
|
event_type=event_type,
|
||||||
"side": side,
|
symbol=symbol,
|
||||||
"old_side": old_side,
|
side=side,
|
||||||
"new_side": new_side,
|
old_side=old_side,
|
||||||
"leverage": (
|
new_side=new_side,
|
||||||
payload.get("leverage")
|
semantic_lines=semantic_lines,
|
||||||
if payload.get("leverage") is not None
|
|
||||||
else state.leverage
|
|
||||||
),
|
),
|
||||||
"strategy": state.strategy,
|
|
||||||
|
|
||||||
# Фиксируем market_score на момент открытия/закрытия/flip,
|
|
||||||
# чтобы журнал и уведомления показывали рыночный контекст сделки.
|
|
||||||
"market_score": getattr(state, "market_score", None),
|
|
||||||
"market_score_label": getattr(state, "market_score_label", None),
|
|
||||||
|
|
||||||
"semantic_lines": semantic_lines,
|
|
||||||
},
|
|
||||||
priority="normal",
|
priority="normal",
|
||||||
dedupe_key=cls._execution_dedupe_key(
|
dedupe_key=cls._execution_dedupe_key(
|
||||||
runtime_event_type=runtime_event_type,
|
runtime_event_type=runtime_event_type,
|
||||||
|
|||||||
@@ -439,6 +439,39 @@ class AutoSignalRuntimeMixin:
|
|||||||
) -> None:
|
) -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# собрать payload итогового события серии сигналов
|
||||||
|
def _build_signal_summary_payload(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
strategy_name: str,
|
||||||
|
state: AutoTradeState,
|
||||||
|
previous_signal: str,
|
||||||
|
next_signal: str,
|
||||||
|
signal_intent: str,
|
||||||
|
previous_count: int,
|
||||||
|
duration_seconds: int,
|
||||||
|
duration_text: str,
|
||||||
|
confidence: float,
|
||||||
|
reason: str,
|
||||||
|
payload: JsonDict | None,
|
||||||
|
) -> JsonDict:
|
||||||
|
return {
|
||||||
|
"strategy": strategy_name,
|
||||||
|
"status": state.status,
|
||||||
|
"symbol": state.symbol,
|
||||||
|
"signal": previous_signal,
|
||||||
|
"next_signal": next_signal,
|
||||||
|
"signal_intent": signal_intent,
|
||||||
|
"repeat_count": previous_count,
|
||||||
|
"duration_seconds": duration_seconds,
|
||||||
|
"duration_text": duration_text,
|
||||||
|
"confidence": confidence,
|
||||||
|
"reason": reason,
|
||||||
|
"is_strong_signal": False,
|
||||||
|
"is_aggregated": True,
|
||||||
|
"payload": payload or {},
|
||||||
|
}
|
||||||
|
|
||||||
# записать итог серии одинаковых сигналов при смене сигнала
|
# записать итог серии одинаковых сигналов при смене сигнала
|
||||||
def _log_signal_summary(
|
def _log_signal_summary(
|
||||||
self,
|
self,
|
||||||
@@ -467,57 +500,35 @@ class AutoSignalRuntimeMixin:
|
|||||||
),
|
),
|
||||||
screen="auto",
|
screen="auto",
|
||||||
action="signal_summary",
|
action="signal_summary",
|
||||||
payload={
|
payload=self._build_signal_summary_payload(
|
||||||
"strategy": strategy_name,
|
strategy_name=strategy_name,
|
||||||
"status": state.status,
|
state=state,
|
||||||
"symbol": state.symbol,
|
previous_signal=previous_signal,
|
||||||
"signal": previous_signal,
|
next_signal=next_signal,
|
||||||
"next_signal": next_signal,
|
signal_intent=signal_intent,
|
||||||
"signal_intent": signal_intent,
|
previous_count=previous_count,
|
||||||
"repeat_count": previous_count,
|
duration_seconds=duration_seconds,
|
||||||
"duration_seconds": duration_seconds,
|
duration_text=duration_text,
|
||||||
"duration_text": duration_text,
|
confidence=confidence,
|
||||||
"confidence": confidence,
|
reason=reason,
|
||||||
"reason": reason,
|
payload=payload,
|
||||||
"is_strong_signal": False,
|
),
|
||||||
"is_aggregated": True,
|
|
||||||
"payload": payload or {},
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# записать событие готовности сигнала к исполнению
|
# собрать payload события готовности сигнала
|
||||||
def _log_ready_signal(
|
def _build_ready_signal_payload(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
state: AutoTradeState,
|
state: AutoTradeState,
|
||||||
signal: str | None,
|
snapshot: JsonDict,
|
||||||
reason: str,
|
signal: str,
|
||||||
confidence: float,
|
|
||||||
signal_intent: str,
|
signal_intent: str,
|
||||||
) -> None:
|
confidence: float,
|
||||||
normalized_signal = (signal or "HOLD").upper()
|
reason: str,
|
||||||
if normalized_signal not in {"BUY", "SELL"}:
|
) -> JsonDict:
|
||||||
return
|
return {
|
||||||
|
|
||||||
try:
|
|
||||||
snapshot = ExchangeService().get_market_snapshot(
|
|
||||||
state.symbol,
|
|
||||||
runtime_key="auto",
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
snapshot = {}
|
|
||||||
|
|
||||||
try:
|
|
||||||
JournalService().log_ui_info(
|
|
||||||
event_type="signal_ready",
|
|
||||||
message=(
|
|
||||||
f"Сигнал {normalized_signal} подтверждён и готов к исполнению."
|
|
||||||
),
|
|
||||||
screen="auto",
|
|
||||||
action="signal_ready",
|
|
||||||
payload={
|
|
||||||
# ---------- Event ----------
|
# ---------- Event ----------
|
||||||
"event_type": "signal_ready",
|
"event_type": "signal_ready",
|
||||||
"action": "signal_ready",
|
"action": "signal_ready",
|
||||||
@@ -531,7 +542,7 @@ class AutoSignalRuntimeMixin:
|
|||||||
"cycle_number": state.cycle_number,
|
"cycle_number": state.cycle_number,
|
||||||
|
|
||||||
# ---------- Signal ----------
|
# ---------- Signal ----------
|
||||||
"signal": normalized_signal,
|
"signal": signal,
|
||||||
"signal_intent": signal_intent,
|
"signal_intent": signal_intent,
|
||||||
"confidence": confidence,
|
"confidence": confidence,
|
||||||
"reason": reason,
|
"reason": reason,
|
||||||
@@ -653,7 +664,46 @@ class AutoSignalRuntimeMixin:
|
|||||||
"market_is_open": state.market_is_open,
|
"market_is_open": state.market_is_open,
|
||||||
"market_status": state.market_status,
|
"market_status": state.market_status,
|
||||||
"market_status_message": state.market_status_message,
|
"market_status_message": state.market_status_message,
|
||||||
},
|
}
|
||||||
|
|
||||||
|
# записать событие готовности сигнала к исполнению
|
||||||
|
def _log_ready_signal(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
state: AutoTradeState,
|
||||||
|
signal: str | None,
|
||||||
|
reason: str,
|
||||||
|
confidence: float,
|
||||||
|
signal_intent: str,
|
||||||
|
) -> None:
|
||||||
|
normalized_signal = (signal or "HOLD").upper()
|
||||||
|
if normalized_signal not in {"BUY", "SELL"}:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
snapshot = ExchangeService().get_market_snapshot(
|
||||||
|
state.symbol,
|
||||||
|
runtime_key="auto",
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
snapshot = {}
|
||||||
|
|
||||||
|
try:
|
||||||
|
JournalService().log_ui_info(
|
||||||
|
event_type="signal_ready",
|
||||||
|
message=(
|
||||||
|
f"Сигнал {normalized_signal} подтверждён и готов к исполнению."
|
||||||
|
),
|
||||||
|
screen="auto",
|
||||||
|
action="signal_ready",
|
||||||
|
payload=self._build_ready_signal_payload(
|
||||||
|
state=state,
|
||||||
|
snapshot=snapshot,
|
||||||
|
signal=normalized_signal,
|
||||||
|
signal_intent=signal_intent,
|
||||||
|
confidence=confidence,
|
||||||
|
reason=reason,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -801,6 +851,22 @@ class AutoSignalRuntimeMixin:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# собрать payload события устаревания runtime данных без изменения состояния
|
||||||
|
def _build_runtime_expired_payload(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
state: AutoTradeState,
|
||||||
|
payload: JsonDict,
|
||||||
|
reason: str,
|
||||||
|
) -> JsonDict:
|
||||||
|
return {
|
||||||
|
**payload,
|
||||||
|
"symbol": state.symbol,
|
||||||
|
"strategy": state.strategy,
|
||||||
|
"status": state.status,
|
||||||
|
"runtime_expired_reason": reason,
|
||||||
|
}
|
||||||
|
|
||||||
# записать событие устаревания runtime данных
|
# записать событие устаревания runtime данных
|
||||||
def _log_runtime_expired_if_changed(
|
def _log_runtime_expired_if_changed(
|
||||||
self,
|
self,
|
||||||
@@ -823,17 +889,59 @@ class AutoSignalRuntimeMixin:
|
|||||||
message=message,
|
message=message,
|
||||||
screen="auto",
|
screen="auto",
|
||||||
action="runtime_expiration",
|
action="runtime_expiration",
|
||||||
payload={
|
payload=self._build_runtime_expired_payload(
|
||||||
**payload,
|
state=state,
|
||||||
"symbol": state.symbol,
|
payload=payload,
|
||||||
"strategy": state.strategy,
|
reason=reason,
|
||||||
"status": state.status,
|
),
|
||||||
"runtime_expired_reason": reason,
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# собрать диагностические факторы execution confidence
|
||||||
|
def _build_execution_confidence_factors(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
state: AutoTradeState,
|
||||||
|
signal: str,
|
||||||
|
signal_score: float,
|
||||||
|
confirmation_score: float,
|
||||||
|
market_score: float,
|
||||||
|
execution_score: float,
|
||||||
|
) -> JsonDict:
|
||||||
|
return {
|
||||||
|
"signal_score": round(signal_score, 3),
|
||||||
|
"confirmation_score": round(confirmation_score, 3),
|
||||||
|
"market_score": round(market_score, 3),
|
||||||
|
"market_score_raw": getattr(state, "market_score", None),
|
||||||
|
"market_score_label": getattr(state, "market_score_label", None),
|
||||||
|
"execution_score": round(execution_score, 3),
|
||||||
|
"required_score": self._execution_confidence_required_score,
|
||||||
|
"signal": signal,
|
||||||
|
"market_state": state.market_state,
|
||||||
|
"market_trend": state.market_trend,
|
||||||
|
"market_trend_strength": state.market_trend_strength,
|
||||||
|
"market_trend_quality": state.market_trend_quality,
|
||||||
|
"market_phase": state.market_phase,
|
||||||
|
"current_interval_change_percent": getattr(state, "current_interval_change_percent", None),
|
||||||
|
"current_interval_direction": getattr(state, "current_interval_direction", None),
|
||||||
|
"current_interval_label": getattr(state, "current_interval_label", None),
|
||||||
|
"market_structure": getattr(state, "market_structure", None),
|
||||||
|
"market_structure_reason": getattr(state, "market_structure_reason", None),
|
||||||
|
"htf_alignment": getattr(state, "htf_alignment", None),
|
||||||
|
"htf_confirmation_score": getattr(state, "htf_confirmation_score", None),
|
||||||
|
"execution_quality": state.execution_quality,
|
||||||
|
"execution_quality_reason": state.execution_quality_reason,
|
||||||
|
"spread_percent": state.spread_percent,
|
||||||
|
"momentum_state": getattr(state, "momentum_state", None),
|
||||||
|
"momentum_direction": getattr(state, "momentum_direction", None),
|
||||||
|
"momentum_change_percent": getattr(state, "momentum_change_percent", None),
|
||||||
|
"momentum_strength": getattr(state, "momentum_strength", None),
|
||||||
|
"breakout_level": getattr(state, "breakout_level", None),
|
||||||
|
"breakout_distance_percent": getattr(state, "breakout_distance_percent", None),
|
||||||
|
"breakout_reason": getattr(state, "breakout_reason", None),
|
||||||
|
}
|
||||||
|
|
||||||
# синхронизировать итоговый execution confidence
|
# синхронизировать итоговый execution confidence
|
||||||
def _sync_execution_confidence_state(
|
def _sync_execution_confidence_state(
|
||||||
self,
|
self,
|
||||||
@@ -881,41 +989,14 @@ class AutoSignalRuntimeMixin:
|
|||||||
state.execution_confidence_required_score = self._execution_confidence_required_score
|
state.execution_confidence_required_score = self._execution_confidence_required_score
|
||||||
state.execution_confidence_level = self._execution_confidence_level(score)
|
state.execution_confidence_level = self._execution_confidence_level(score)
|
||||||
state.execution_confidence_reason = self._execution_confidence_reason(state)
|
state.execution_confidence_reason = self._execution_confidence_reason(state)
|
||||||
state.execution_confidence_factors = {
|
state.execution_confidence_factors = self._build_execution_confidence_factors(
|
||||||
"signal_score": round(signal_score, 3),
|
state=state,
|
||||||
"confirmation_score": round(confirmation_score, 3),
|
signal=signal,
|
||||||
# market_score здесь — направленная рыночная оценка 0.0..1.0
|
signal_score=signal_score,
|
||||||
# именно для текущего BUY / SELL сигнала.
|
confirmation_score=confirmation_score,
|
||||||
# state.market_score — общая оценка рынка 0..100 без привязки к сигналу.
|
market_score=market_score,
|
||||||
"market_score": round(market_score, 3),
|
execution_score=execution_score,
|
||||||
"market_score_raw": getattr(state, "market_score", None),
|
)
|
||||||
"market_score_label": getattr(state, "market_score_label", None),
|
|
||||||
"execution_score": round(execution_score, 3),
|
|
||||||
"required_score": self._execution_confidence_required_score,
|
|
||||||
"signal": signal,
|
|
||||||
"market_state": state.market_state,
|
|
||||||
"market_trend": state.market_trend,
|
|
||||||
"market_trend_strength": state.market_trend_strength,
|
|
||||||
"market_trend_quality": state.market_trend_quality,
|
|
||||||
"market_phase": state.market_phase,
|
|
||||||
"current_interval_change_percent": getattr(state, "current_interval_change_percent", None),
|
|
||||||
"current_interval_direction": getattr(state, "current_interval_direction", None),
|
|
||||||
"current_interval_label": getattr(state, "current_interval_label", None),
|
|
||||||
"market_structure": getattr(state, "market_structure", None),
|
|
||||||
"market_structure_reason": getattr(state, "market_structure_reason", None),
|
|
||||||
"htf_alignment": getattr(state, "htf_alignment", None),
|
|
||||||
"htf_confirmation_score": getattr(state, "htf_confirmation_score", None),
|
|
||||||
"execution_quality": state.execution_quality,
|
|
||||||
"execution_quality_reason": state.execution_quality_reason,
|
|
||||||
"spread_percent": state.spread_percent,
|
|
||||||
"momentum_state": getattr(state, "momentum_state", None),
|
|
||||||
"momentum_direction": getattr(state, "momentum_direction", None),
|
|
||||||
"momentum_change_percent": getattr(state, "momentum_change_percent", None),
|
|
||||||
"momentum_strength": getattr(state, "momentum_strength", None),
|
|
||||||
"breakout_level": getattr(state, "breakout_level", None),
|
|
||||||
"breakout_distance_percent": getattr(state, "breakout_distance_percent", None),
|
|
||||||
"breakout_reason": getattr(state, "breakout_reason", None),
|
|
||||||
}
|
|
||||||
|
|
||||||
# рассчитать market confidence для итогового execution confidence
|
# рассчитать market confidence для итогового execution confidence
|
||||||
def _market_confidence_score(
|
def _market_confidence_score(
|
||||||
|
|||||||
495
docs/architecture/auto_refactoring.md
Normal file
495
docs/architecture/auto_refactoring.md
Normal file
@@ -0,0 +1,495 @@
|
|||||||
|
# Auto refactoring roadmap
|
||||||
|
|
||||||
|
Цель: безопасный поэтапный аудит и рефакторинг `app/src/trading/auto`.
|
||||||
|
|
||||||
|
Принципы:
|
||||||
|
|
||||||
|
- сначала аудит;
|
||||||
|
- без изменения торговой логики;
|
||||||
|
- без изменения AutoTradeState;
|
||||||
|
- без изменения ExecutionEngine;
|
||||||
|
- без изменения payload;
|
||||||
|
- без изменения EventBus / JournalService;
|
||||||
|
- каждый шаг проверяется перезапуском бота.
|
||||||
|
|
||||||
|
## Файлы
|
||||||
|
|
||||||
|
| Файл | Статус | Комментарий |
|
||||||
|
|---|---|---|
|
||||||
|
| __init__.py | Not audited | |
|
||||||
|
| service.py | Not audited | |
|
||||||
|
| autonomous_management.py | Not audited | |
|
||||||
|
| execution_semantic.py | Not audited | |
|
||||||
|
| market_runtime.py | Not audited | |
|
||||||
|
| position_health.py | Not audited | |
|
||||||
|
| execution_quality.py | Not audited | |
|
||||||
|
| position_semantics.py | Not audited | |
|
||||||
|
| auto_lifecycle.py | Not audited | |
|
||||||
|
| runner.py | Not audited | |
|
||||||
|
| signal_runtime.py | Not audited | |
|
||||||
|
| state.py | Not audited | |
|
||||||
|
|
||||||
|
## Порядок аудита
|
||||||
|
|
||||||
|
1. service.py
|
||||||
|
2. autonomous_management.py
|
||||||
|
3. execution_semantic.py
|
||||||
|
4. market_runtime.py
|
||||||
|
5. position_health.py
|
||||||
|
6. execution_quality.py
|
||||||
|
7. position_semantics.py
|
||||||
|
8. auto_lifecycle.py
|
||||||
|
9. runner.py
|
||||||
|
10. signal_runtime.py
|
||||||
|
11. state.py
|
||||||
|
|
||||||
|
## Правила аудита
|
||||||
|
|
||||||
|
Для каждого файла фиксируем:
|
||||||
|
|
||||||
|
- назначение;
|
||||||
|
- размер и сложность;
|
||||||
|
- зависимости;
|
||||||
|
- что хорошо;
|
||||||
|
- что настораживает;
|
||||||
|
- безопасные улучшения;
|
||||||
|
- что нельзя менять;
|
||||||
|
- рекомендуемый следующий шаг.
|
||||||
|
|
||||||
|
## service.py
|
||||||
|
|
||||||
|
Статус: Completed (без изменений)
|
||||||
|
|
||||||
|
Назначение:
|
||||||
|
- Фасад AutoTradeService.
|
||||||
|
- Хранит единый runtime AutoTradeState.
|
||||||
|
- Хранит class-level настройки auto loop, signal confirmation, TTL, execution quality и spread thresholds.
|
||||||
|
|
||||||
|
Размер:
|
||||||
|
- Небольшой/средний.
|
||||||
|
|
||||||
|
Связность:
|
||||||
|
- Зависит от AutoLifecycleMixin и AutoTradeState.
|
||||||
|
- Не содержит прямой торговой логики.
|
||||||
|
|
||||||
|
Что хорошо:
|
||||||
|
- Нет JournalService/EventBus/payload.
|
||||||
|
- Нет execution-алгоритмов.
|
||||||
|
- Настройки читаются централизованно.
|
||||||
|
|
||||||
|
Что настораживает:
|
||||||
|
- Много class-level state.
|
||||||
|
- Spread thresholds находятся прямо в service.py.
|
||||||
|
|
||||||
|
Безопасные улучшения:
|
||||||
|
- Сейчас не требуются.
|
||||||
|
|
||||||
|
Что нельзя менять:
|
||||||
|
- Значения thresholds.
|
||||||
|
- Class-level state.
|
||||||
|
- Названия runtime-полей.
|
||||||
|
- Наследование от AutoLifecycleMixin.
|
||||||
|
|
||||||
|
Итог:
|
||||||
|
Файл выполняет роль конфигурационного фасада. Рефакторинг на текущем этапе не нужен.
|
||||||
|
|
||||||
|
## autonomous_management.py
|
||||||
|
|
||||||
|
Статус: Completed after minor cleanup
|
||||||
|
|
||||||
|
Назначение:
|
||||||
|
- Синхронизация autonomous trade management state.
|
||||||
|
- Определяет autonomous action: HOLD / WATCH / PROTECT / REDUCE / EXIT.
|
||||||
|
- Обновляет autonomous required-флаги.
|
||||||
|
|
||||||
|
Размер:
|
||||||
|
- Небольшой.
|
||||||
|
|
||||||
|
Связность:
|
||||||
|
- Зависит от AutoTradeState и execution.constants.
|
||||||
|
- Не вызывает ExecutionEngine напрямую.
|
||||||
|
|
||||||
|
Что хорошо:
|
||||||
|
- Один понятный метод.
|
||||||
|
- Нет JournalService/EventBus/payload.
|
||||||
|
- Нет прямого закрытия позиции.
|
||||||
|
- Используются константы.
|
||||||
|
|
||||||
|
Безопасные улучшения:
|
||||||
|
- Только косметика форматирования.
|
||||||
|
|
||||||
|
Что нельзя менять:
|
||||||
|
- Порядок выбора autonomous action.
|
||||||
|
- Thresholds confidence.
|
||||||
|
- Логику aggressive exit escalation.
|
||||||
|
- Сброс autonomous-state при отсутствии позиции.
|
||||||
|
|
||||||
|
Итог:
|
||||||
|
Файл чистый. Логических правок не требуется.
|
||||||
|
|
||||||
|
## execution_semantic.py
|
||||||
|
|
||||||
|
Статус: Completed (без изменений)
|
||||||
|
|
||||||
|
Назначение:
|
||||||
|
- Синхронизация semantic-статуса execution слоя для UI.
|
||||||
|
- Формирует execution_semantic_status/message/reason.
|
||||||
|
- Преобразует технические причины блокировки в человекочитаемые UI-сообщения.
|
||||||
|
|
||||||
|
Размер:
|
||||||
|
- Небольшой.
|
||||||
|
|
||||||
|
Связность:
|
||||||
|
- Зависит от AutoTradeState.
|
||||||
|
- Использует ExchangeStatusCode для совместимости с exchange status layer.
|
||||||
|
|
||||||
|
Что хорошо:
|
||||||
|
- Нет JournalService/EventBus/payload.
|
||||||
|
- Нет торговой логики.
|
||||||
|
- Нет прямых вызовов ExecutionEngine.
|
||||||
|
- Логика UI-сообщений отделена от execution layer.
|
||||||
|
|
||||||
|
Что настораживает:
|
||||||
|
- Строковые статусы `"BLOCKED"`, `"READY"`, `"CONFIRMING"`, `"NONE"` пока используются напрямую.
|
||||||
|
- Но сейчас это не трогаем, чтобы не менять контракт state/UI.
|
||||||
|
|
||||||
|
Безопасные улучшения:
|
||||||
|
- Сейчас не требуются.
|
||||||
|
|
||||||
|
Что нельзя менять:
|
||||||
|
- Приоритеты semantic status.
|
||||||
|
- Тексты UI-сообщений.
|
||||||
|
- Совместимость с ExchangeStatusCode.
|
||||||
|
- Значения execution_semantic_status.
|
||||||
|
|
||||||
|
Итог:
|
||||||
|
Файл выполняет UI-semantic роль и не требует refactoring на текущем этапе.
|
||||||
|
|
||||||
|
## market_runtime.py
|
||||||
|
|
||||||
|
Статус: Audited / candidate for safe payload extraction
|
||||||
|
|
||||||
|
Назначение:
|
||||||
|
- Синхронизация market analysis payload в AutoTradeState.
|
||||||
|
- Логирование market state / volatility изменений.
|
||||||
|
- Логирование entry-block событий.
|
||||||
|
|
||||||
|
Размер:
|
||||||
|
- Средний.
|
||||||
|
|
||||||
|
Связность:
|
||||||
|
- Зависит от AutoTradeState, JournalService, JsonDict.
|
||||||
|
- Не вызывает ExecutionEngine напрямую.
|
||||||
|
|
||||||
|
Что хорошо:
|
||||||
|
- Dedupe market-событий по symbol/strategy.
|
||||||
|
- Entry-block logging имеет TTL.
|
||||||
|
- Нет торговых действий.
|
||||||
|
|
||||||
|
Что настораживает:
|
||||||
|
- Большой inline payload внутри `_log_entry_block_if_changed()`.
|
||||||
|
- `_sync_market_analysis_state()` много полей переносит из payload в state, но это существующий mapping.
|
||||||
|
|
||||||
|
Безопасные улучшения:
|
||||||
|
- Вынести entry-block payload в `_build_entry_block_payload()`.
|
||||||
|
|
||||||
|
Что нельзя менять:
|
||||||
|
- Mapping payload → AutoTradeState.
|
||||||
|
- Dedupe key.
|
||||||
|
- Entry-block TTL.
|
||||||
|
- Journal event_type/action.
|
||||||
|
- Содержимое payload.
|
||||||
|
|
||||||
|
Итог:
|
||||||
|
Файл рабочий. Первый безопасный шаг — вынести payload builder без изменения поведения.
|
||||||
|
|
||||||
|
### market_runtime.py
|
||||||
|
|
||||||
|
Выполнено:
|
||||||
|
- Вынесен `_build_entry_block_payload()`.
|
||||||
|
- Логика `_log_entry_block_if_changed()` стала отвечать только за dedupe и запись в Journal.
|
||||||
|
- Содержимое payload не изменено.
|
||||||
|
- Поведение полностью сохранено.
|
||||||
|
|
||||||
|
Статус: Completed
|
||||||
|
|
||||||
|
Выполнено:
|
||||||
|
- Вынесен `_build_entry_block_payload()`.
|
||||||
|
- Вынесен `_build_market_state_payload()`.
|
||||||
|
- Методы логирования теперь отвечают только за:
|
||||||
|
- dedupe;
|
||||||
|
- принятие решения;
|
||||||
|
- запись в Journal.
|
||||||
|
- Формирование payload полностью изолировано.
|
||||||
|
- Поведение не изменилось.
|
||||||
|
|
||||||
|
## position_health.py
|
||||||
|
|
||||||
|
Статус: Completed (без изменений)
|
||||||
|
|
||||||
|
Назначение:
|
||||||
|
- Runtime health/risk оценка открытой позиции.
|
||||||
|
- Формирует pressure, health score/status/reason, risk level/reason, trend alignment, adverse momentum и exit pressure.
|
||||||
|
|
||||||
|
Размер:
|
||||||
|
- Средний/большой.
|
||||||
|
|
||||||
|
Связность:
|
||||||
|
- Зависит от AutoTradeState, safe_float и execution.constants.
|
||||||
|
- Использует get_position_health_thresholds().
|
||||||
|
|
||||||
|
Что хорошо:
|
||||||
|
- PnL и hold time не рассчитываются здесь.
|
||||||
|
- Runtime metrics приходят из execution/position_runtime.py.
|
||||||
|
- Нет JournalService/EventBus/payload.
|
||||||
|
- Методы разделены по смыслу.
|
||||||
|
- Thresholds вынесены в constants.
|
||||||
|
|
||||||
|
Что настораживает:
|
||||||
|
- Чувствительный файл: влияет на autonomous management, runtime actions, protection и exit decision.
|
||||||
|
- Много бизнес-условий.
|
||||||
|
- Много строковых runtime states.
|
||||||
|
|
||||||
|
Безопасные улучшения:
|
||||||
|
- Сейчас не требуются.
|
||||||
|
|
||||||
|
Что нельзя менять:
|
||||||
|
- Health score формулу.
|
||||||
|
- Risk level порядок условий.
|
||||||
|
- Trend alignment logic.
|
||||||
|
- Adverse momentum logic.
|
||||||
|
- Exit pressure logic.
|
||||||
|
- Threshold constants.
|
||||||
|
|
||||||
|
Итог:
|
||||||
|
Файл архитектурно понятный. На текущем safe stage оставляем без изменений.
|
||||||
|
|
||||||
|
## execution_quality.py
|
||||||
|
|
||||||
|
Статус: Audited / candidate for safe payload extraction
|
||||||
|
|
||||||
|
Назначение:
|
||||||
|
- Оценка качества исполнения.
|
||||||
|
- Синхронизация exchange availability.
|
||||||
|
- Проверка свежести market snapshot.
|
||||||
|
- Проверка spread.
|
||||||
|
- Синхронизация execution pricing fields.
|
||||||
|
- Логирование execution quality изменений.
|
||||||
|
- Расчёт execution quality confidence score.
|
||||||
|
|
||||||
|
Размер:
|
||||||
|
- Большой.
|
||||||
|
|
||||||
|
Связность:
|
||||||
|
- Зависит от AutoTradeState, ExchangeService, ExchangeRuntimeStatus, JournalService.
|
||||||
|
- Не вызывает ExecutionEngine напрямую.
|
||||||
|
|
||||||
|
Что хорошо:
|
||||||
|
- Exchange availability вынесена отдельно.
|
||||||
|
- Spread quality вынесен отдельно.
|
||||||
|
- Execution pricing sync вынесен отдельно.
|
||||||
|
- Confidence score вынесен отдельно.
|
||||||
|
- Нет торговых действий.
|
||||||
|
|
||||||
|
Что настораживает:
|
||||||
|
- Inline payload в `_log_exchange_availability_if_changed()`.
|
||||||
|
- Inline payload в `_log_execution_quality_if_changed()`.
|
||||||
|
- `_sync_execution_quality_state()` большой и чувствительный.
|
||||||
|
|
||||||
|
Безопасные улучшения:
|
||||||
|
- Вынести `_build_exchange_availability_payload()`.
|
||||||
|
- Вынести `_build_execution_quality_payload()`.
|
||||||
|
|
||||||
|
Что нельзя менять:
|
||||||
|
- Exchange status mapping.
|
||||||
|
- Spread thresholds.
|
||||||
|
- Snapshot refresh logic.
|
||||||
|
- Fallback price behavior.
|
||||||
|
- Execution quality transitions.
|
||||||
|
- Deduplication key.
|
||||||
|
- Journal event_type/action.
|
||||||
|
- Confidence score mapping.
|
||||||
|
|
||||||
|
Итог:
|
||||||
|
Файл рабочий. Первый безопасный этап — вынести payload builders без изменения поведения.
|
||||||
|
|
||||||
|
### execution_quality.py
|
||||||
|
|
||||||
|
Выполнено:
|
||||||
|
- Вынесен `_build_exchange_availability_payload()`.
|
||||||
|
- Вынесен `_build_execution_quality_payload()`.
|
||||||
|
- Логика exchange availability не менялась.
|
||||||
|
- Логика spread/snapshot quality не менялась.
|
||||||
|
- Journal payload не изменён.
|
||||||
|
- Поведение полностью сохранено.
|
||||||
|
|
||||||
|
Статус: Completed (safe refactoring stage 1)
|
||||||
|
|
||||||
|
## position_semantics.py
|
||||||
|
|
||||||
|
Статус: Completed after minor cleanup
|
||||||
|
|
||||||
|
Назначение:
|
||||||
|
- Semantic/intelligence состояние открытой позиции.
|
||||||
|
- Формирует lifecycle stage, hold quality, decay state, exit confidence, exit signal, recommended action.
|
||||||
|
- Считает advanced analytics: MFE, MAE, giveback, fatigue, conviction, reversal risk, stall state.
|
||||||
|
|
||||||
|
Размер:
|
||||||
|
- Большой.
|
||||||
|
|
||||||
|
Связность:
|
||||||
|
- Зависит от AutoTradeState, safe_float и execution.constants.
|
||||||
|
- Не вызывает ExecutionEngine напрямую.
|
||||||
|
|
||||||
|
Что хорошо:
|
||||||
|
- Нет JournalService/EventBus/payload.
|
||||||
|
- Нет прямых торговых действий.
|
||||||
|
- Thresholds вынесены в constants.
|
||||||
|
- Методы разделены по смыслу.
|
||||||
|
|
||||||
|
Что настораживает:
|
||||||
|
- Очень чувствительный файл: влияет на autonomous management, runtime actions, protection и exit decision.
|
||||||
|
- Много бизнес-условий.
|
||||||
|
- Много строковых semantic states.
|
||||||
|
- `_sync_position_semantics_state()` управляет большим количеством полей AutoTradeState.
|
||||||
|
|
||||||
|
Безопасные улучшения:
|
||||||
|
- Только косметика форматирования.
|
||||||
|
|
||||||
|
Что нельзя менять:
|
||||||
|
- Порядок расчёта lifecycle/advanced analytics/stall/exit confidence.
|
||||||
|
- Формулы exit confidence.
|
||||||
|
- Логику damping.
|
||||||
|
- MFE/MAE/giveback/fatigue/conviction/reversal/stall.
|
||||||
|
- Threshold constants.
|
||||||
|
- Значения semantic states.
|
||||||
|
|
||||||
|
Итог:
|
||||||
|
Файл архитектурно понятный, но чувствительный. На текущем safe stage логических правок не требуется.
|
||||||
|
|
||||||
|
## auto_lifecycle.py
|
||||||
|
|
||||||
|
Статус: Audited / candidate for safe payload extraction
|
||||||
|
|
||||||
|
Назначение:
|
||||||
|
- Lifecycle автоторговли: start / observe / stop.
|
||||||
|
- Управление background loop.
|
||||||
|
- Основной run_cycle pipeline.
|
||||||
|
- Reset signal/runtime tracking.
|
||||||
|
|
||||||
|
Размер:
|
||||||
|
- Большой.
|
||||||
|
|
||||||
|
Связность:
|
||||||
|
- Собирает auto mixins.
|
||||||
|
- Использует ExecutionEngine, StrategyRegistry, EventBus, JournalService.
|
||||||
|
|
||||||
|
Что хорошо:
|
||||||
|
- run_cycle имеет понятный pipeline.
|
||||||
|
- Execution делегирован ExecutionEngine.
|
||||||
|
- Market, signal, health, semantics, autonomous management вынесены в mixins.
|
||||||
|
|
||||||
|
Что настораживает:
|
||||||
|
- Большой `_reset_signal_tracking()`.
|
||||||
|
- Inline payload в `_log_auto_status_changed()`.
|
||||||
|
- Дубли reset-полей в start/observe/stop.
|
||||||
|
|
||||||
|
Безопасные улучшения:
|
||||||
|
- Вынести `_build_auto_status_changed_payload()`.
|
||||||
|
|
||||||
|
Что нельзя менять:
|
||||||
|
- Порядок run_cycle.
|
||||||
|
- start/observe/stop поведение.
|
||||||
|
- Reset signal tracking.
|
||||||
|
- EventBus события.
|
||||||
|
- Journal payload.
|
||||||
|
|
||||||
|
Итог:
|
||||||
|
Файл рабочий. Первый безопасный шаг — вынести payload builder без изменения поведения.
|
||||||
|
|
||||||
|
## runner.py
|
||||||
|
|
||||||
|
Статус: Audited / candidate for safe payload extraction
|
||||||
|
|
||||||
|
Назначение:
|
||||||
|
- Background runner автоторговли.
|
||||||
|
- Обработка EventBus событий.
|
||||||
|
- Публикация RuntimeEvent уведомлений.
|
||||||
|
- Обновление Telegram auto screen.
|
||||||
|
|
||||||
|
Безопасные улучшения:
|
||||||
|
- Вынести payload builders для notification/journal событий.
|
||||||
|
|
||||||
|
Что нельзя менять:
|
||||||
|
- Worker loop.
|
||||||
|
- EventBus version handling.
|
||||||
|
- Dedupe keys.
|
||||||
|
- Telegram refresh logic.
|
||||||
|
- RuntimeEvent payload.
|
||||||
|
|
||||||
|
Выполнено:
|
||||||
|
- Вынесен `_build_position_aligned_signal_suppressed_payload()`.
|
||||||
|
- Вынесен `_build_runtime_signal_payload()`.
|
||||||
|
- Вынесен `_build_runtime_execution_payload()`.
|
||||||
|
- Payload подавленного aligned-сигнала не изменён.
|
||||||
|
- Логика dedupe/TTL не менялась.
|
||||||
|
- RuntimeEvent payload не изменялся.
|
||||||
|
- Dedupe keys не менялись.
|
||||||
|
- Telegram refresh logic не менялась.
|
||||||
|
|
||||||
|
Статус: Completed (safe refactoring stage 1)
|
||||||
|
|
||||||
|
## signal_runtime.py
|
||||||
|
|
||||||
|
Статус: Audited / candidate for safe payload extraction
|
||||||
|
|
||||||
|
Назначение:
|
||||||
|
- Signal runtime tracking.
|
||||||
|
- Signal confirmation.
|
||||||
|
- Decision state.
|
||||||
|
- Runtime expiration.
|
||||||
|
- Execution confidence.
|
||||||
|
- Market confidence.
|
||||||
|
|
||||||
|
Безопасные улучшения:
|
||||||
|
- Вынести payload builders.
|
||||||
|
- Начать с `_build_runtime_expired_payload()`.
|
||||||
|
|
||||||
|
Что нельзя менять:
|
||||||
|
- Confirmation logic.
|
||||||
|
- READY/BLOCKED logic.
|
||||||
|
- Execution confidence formula.
|
||||||
|
- Market confidence formula.
|
||||||
|
- Runtime TTL reset logic.
|
||||||
|
- EventBus events.
|
||||||
|
|
||||||
|
Выполнено:
|
||||||
|
- Вынесен `_build_runtime_expired_payload()`.
|
||||||
|
- Вынесен `_build_ready_signal_payload()`.
|
||||||
|
- Вынесен `_build_signal_summary_payload()`.
|
||||||
|
- Вынесен `_build_execution_confidence_factors()`.
|
||||||
|
|
||||||
|
Что не менялось:
|
||||||
|
- Confirmation logic.
|
||||||
|
- READY/BLOCKED logic.
|
||||||
|
- Runtime TTL reset logic.
|
||||||
|
- Execution confidence formula.
|
||||||
|
- Market confidence formula.
|
||||||
|
- EventBus events.
|
||||||
|
- Journal event_type/action.
|
||||||
|
|
||||||
|
Статус: Completed (safe refactoring stage 1)
|
||||||
|
|
||||||
|
### state.py
|
||||||
|
|
||||||
|
Проверено полностью.
|
||||||
|
|
||||||
|
Изменения не требуются.
|
||||||
|
|
||||||
|
Причина:
|
||||||
|
- dataclass не содержит бизнес-логики;
|
||||||
|
- поля уже сгруппированы по подсистемам;
|
||||||
|
- выделение вложенных dataclass приведёт к массовым изменениям во всём проекте без архитектурной пользы.
|
||||||
|
|
||||||
|
Статус:
|
||||||
|
Completed (no changes required)
|
||||||
Reference in New Issue
Block a user