07.4.4.1.12 — Position Health & Runtime Risk Layer
This commit is contained in:
@@ -41,21 +41,21 @@ def auto_keyboard() -> InlineKeyboardMarkup:
|
||||
status = (state.status or "").upper()
|
||||
|
||||
if status == "OFF":
|
||||
builder.button(text="▶️ Start", callback_data="auto:start")
|
||||
builder.button(text="👀 Watch", callback_data="auto:observe")
|
||||
builder.button(text="▶️ Запустить", callback_data="auto:start")
|
||||
builder.button(text="👀 Наблюдать", callback_data="auto:observe")
|
||||
|
||||
elif status == "RUNNING":
|
||||
builder.button(text="👀 Watch", callback_data="auto:observe")
|
||||
builder.button(text="🛑 Stop", callback_data="auto:stop")
|
||||
builder.button(text="👀 Наблюдать", callback_data="auto:observe")
|
||||
builder.button(text="🛑 Остановить", callback_data="auto:stop")
|
||||
|
||||
elif status == "OBSERVING":
|
||||
builder.button(text="▶️ Start", callback_data="auto:start")
|
||||
builder.button(text="🛑 Stop", callback_data="auto:stop")
|
||||
builder.button(text="▶️ Запустить", callback_data="auto:start")
|
||||
builder.button(text="🛑 Остановить", callback_data="auto:stop")
|
||||
|
||||
else:
|
||||
builder.button(text="▶️ Start", callback_data="auto:start")
|
||||
builder.button(text="👀 Watch", callback_data="auto:observe")
|
||||
builder.button(text="🛑 Stop", callback_data="auto:stop")
|
||||
builder.button(text="▶️ Запустить", callback_data="auto:start")
|
||||
builder.button(text="👀 Наблюдать", callback_data="auto:observe")
|
||||
builder.button(text="🛑 Остановить", callback_data="auto:stop")
|
||||
|
||||
builder.button(text="🛠️ Настройки", callback_data="settings:auto")
|
||||
builder.button(text="🧯 Защита", callback_data="auto:risk")
|
||||
@@ -192,7 +192,7 @@ def _settings_risk_percent_line(state) -> str:
|
||||
ml = (
|
||||
f"-{_format_money_compact(abs(state.max_loss_usd))}"
|
||||
if state.max_loss_usd is not None
|
||||
else "off"
|
||||
else "выкл"
|
||||
)
|
||||
|
||||
return f"SL {sl} · TP {tp} · ML {ml}"
|
||||
@@ -205,15 +205,10 @@ def _build_waiting_text(state) -> str:
|
||||
estimated_size = _estimated_size(state, price)
|
||||
|
||||
cycle_trades = int(getattr(state, "cycle_closed_trades", 0) or 0)
|
||||
|
||||
cycle_pnl = float(
|
||||
getattr(state, "cycle_realized_pnl_usd", 0.0) or 0.0
|
||||
)
|
||||
cycle_pnl = float(getattr(state, "cycle_realized_pnl_usd", 0.0) or 0.0)
|
||||
|
||||
parts = [
|
||||
(
|
||||
f"{_status_text(state)}"
|
||||
),
|
||||
f"{_status_text(state)}",
|
||||
_account_mode_line(),
|
||||
"",
|
||||
f"Доступно 💰 {_format_money_compact(available)}",
|
||||
@@ -222,10 +217,7 @@ def _build_waiting_text(state) -> str:
|
||||
if cycle_trades > 0:
|
||||
parts.extend([
|
||||
"",
|
||||
(
|
||||
f"🔄 {_cycle_number_text(state)} · "
|
||||
f"{cycle_trades} {_trade_word(cycle_trades)}"
|
||||
),
|
||||
f"🔄 {_cycle_number_text(state)} · {cycle_trades} {_trade_word(cycle_trades)}",
|
||||
_format_pnl_line(cycle_pnl),
|
||||
])
|
||||
|
||||
@@ -236,7 +228,13 @@ def _build_waiting_text(state) -> str:
|
||||
parts.extend([
|
||||
"",
|
||||
_signal_line(state),
|
||||
])
|
||||
|
||||
execution_runtime_line = _execution_runtime_line(state)
|
||||
if execution_runtime_line:
|
||||
parts.extend([
|
||||
"",
|
||||
execution_runtime_line,
|
||||
])
|
||||
|
||||
block_title = (
|
||||
@@ -245,28 +243,35 @@ def _build_waiting_text(state) -> str:
|
||||
else "Подготовка ордера 🧾"
|
||||
)
|
||||
|
||||
parts.extend([
|
||||
order_lines = [
|
||||
"",
|
||||
block_title,
|
||||
_order_header_line(state),
|
||||
f"Цена · {_format_plain_or_dash(price)}",
|
||||
_estimated_size_text(state, price),
|
||||
_max_reserved_line(state, price),
|
||||
_effective_risk_line(state),
|
||||
]
|
||||
|
||||
execution_confidence_line = _execution_confidence_line(state)
|
||||
if execution_confidence_line:
|
||||
order_lines.append(execution_confidence_line)
|
||||
|
||||
order_lines.append(
|
||||
_risk_summary_line(
|
||||
state,
|
||||
estimated_size,
|
||||
entry_price_override=price,
|
||||
),
|
||||
])
|
||||
)
|
||||
)
|
||||
|
||||
parts.extend(order_lines)
|
||||
|
||||
adjustment_visible = _adaptive_adjustment_visible(state)
|
||||
|
||||
if adjustment_visible:
|
||||
reason = getattr(state, "adaptive_size_reason", "") or ""
|
||||
|
||||
multiplier = float(
|
||||
getattr(state, "adaptive_size_multiplier", 1.0) or 1.0
|
||||
)
|
||||
multiplier = float(getattr(state, "adaptive_size_multiplier", 1.0) or 1.0)
|
||||
|
||||
parts.extend([
|
||||
"",
|
||||
@@ -280,6 +285,50 @@ def _build_waiting_text(state) -> str:
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
def _execution_runtime_line(state) -> str:
|
||||
quality = str(
|
||||
getattr(state, "execution_quality", "") or ""
|
||||
).upper()
|
||||
|
||||
reason = str(
|
||||
getattr(state, "execution_quality_reason", "") or ""
|
||||
).upper()
|
||||
|
||||
freshness = _execution_freshness_text(state)
|
||||
|
||||
if quality == "GOOD":
|
||||
return ""
|
||||
|
||||
if quality == "WARNING":
|
||||
if reason == "WIDE_SPREAD":
|
||||
return f"Исполнение ⚠️ Повышенный spread · {freshness}"
|
||||
|
||||
if reason == "AGING_SNAPSHOT":
|
||||
return f"Исполнение ⚠️ Snapshot стареет · {freshness}"
|
||||
|
||||
if reason == "SNAPSHOT_UNAVAILABLE":
|
||||
return f"Исполнение ⚠️ Нет стакана · {freshness}"
|
||||
|
||||
return f"Исполнение ⚠️ Предупреждение · {freshness}"
|
||||
|
||||
if quality == "BLOCKED":
|
||||
if reason == "MARKET_CLOSED":
|
||||
return "Исполнение ⏸️ Рынок закрыт"
|
||||
|
||||
if reason == "STALE_SNAPSHOT":
|
||||
return f"Исполнение 🔴 Snapshot устарел · {freshness}"
|
||||
|
||||
if reason == "HIGH_SPREAD":
|
||||
return f"Исполнение 🔴 Высокий spread · {freshness}"
|
||||
|
||||
if reason == "SNAPSHOT_ERROR":
|
||||
return "Исполнение 🔴 Нет данных рынка"
|
||||
|
||||
return f"Исполнение 🔴 Заблокировано · {freshness}"
|
||||
|
||||
return ""
|
||||
|
||||
|
||||
def _build_active_position_text(state) -> str:
|
||||
current_price = _current_price(state.symbol)
|
||||
price_for_calc = current_price or state.entry_price or 0.0
|
||||
@@ -356,9 +405,14 @@ def _build_active_position_text(state) -> str:
|
||||
),
|
||||
f"Объём · {_format_usd_compact(notional)}",
|
||||
_format_pnl_line(pnl),
|
||||
_risk_summary_line(state, size),
|
||||
])
|
||||
|
||||
execution_runtime_line = _execution_runtime_line(state)
|
||||
if execution_runtime_line:
|
||||
parts.append(execution_runtime_line)
|
||||
|
||||
parts.append(_risk_summary_line(state, size))
|
||||
|
||||
if pnl < 0:
|
||||
reason_block = _position_warning_reason(state)
|
||||
|
||||
@@ -528,6 +582,40 @@ def _effective_risk_line(state) -> str:
|
||||
return f"Риск · {_format_usd_compact(_target_risk_usd(state))}"
|
||||
|
||||
|
||||
def _execution_confidence_line(state) -> str:
|
||||
score = safe_float(
|
||||
getattr(state, "execution_confidence_score", None)
|
||||
)
|
||||
|
||||
if score is None:
|
||||
return ""
|
||||
|
||||
level = str(
|
||||
getattr(state, "execution_confidence_level", "") or ""
|
||||
).upper()
|
||||
|
||||
if level == "HIGH":
|
||||
icon = "🟢"
|
||||
level_text = "Высокая"
|
||||
|
||||
elif level == "NORMAL":
|
||||
icon = "🟡"
|
||||
level_text = "Нормальная"
|
||||
|
||||
elif level == "LOW":
|
||||
icon = "🔴"
|
||||
level_text = "Низкая"
|
||||
|
||||
else:
|
||||
icon = "⚪"
|
||||
level_text = "Неизвестно"
|
||||
|
||||
return (
|
||||
f"Уверенность исполнения "
|
||||
f"{icon} {level_text} · {score:.2f}"
|
||||
)
|
||||
|
||||
|
||||
def _estimated_size(state, price: float | None) -> float | None:
|
||||
if (
|
||||
price is None
|
||||
@@ -642,13 +730,13 @@ def _risk_summary_line(
|
||||
key, value = enabled[0]
|
||||
|
||||
if key == "SL":
|
||||
return f"Stop Loss -{_format_usd_compact(value)}"
|
||||
return f"Stop Loss −{_format_usd_compact(value)}"
|
||||
|
||||
if key == "TP":
|
||||
return f"Take Profit +{_format_usd_compact(value)}"
|
||||
|
||||
if key == "ML":
|
||||
return f"Max Loss -{_format_usd_compact(value)}"
|
||||
return f"Макс. убыток −{_format_usd_compact(value)}"
|
||||
|
||||
items: list[str] = []
|
||||
|
||||
@@ -662,7 +750,7 @@ def _risk_summary_line(
|
||||
items.append(f"ML -{_format_usd_compact(ml_value)}")
|
||||
|
||||
if not items:
|
||||
return "SL off · TP off · ML off"
|
||||
return "SL выкл · TP выкл · ML выкл"
|
||||
|
||||
return " · ".join(items)
|
||||
|
||||
@@ -739,7 +827,7 @@ def _signal_text(signal: str) -> str:
|
||||
mapping = {
|
||||
"BUY": "Long",
|
||||
"SELL": "Short",
|
||||
"HOLD": "Hold",
|
||||
"HOLD": "Ожидание",
|
||||
}
|
||||
|
||||
return mapping.get(signal.upper(), signal.title())
|
||||
@@ -757,7 +845,7 @@ def _signal_line(state) -> str:
|
||||
state.decision_status == "READY"
|
||||
or getattr(state, "is_signal_ready", False)
|
||||
):
|
||||
return f"{signal_text} · READY"
|
||||
return f"{signal_text} · готов"
|
||||
|
||||
duration = _signal_duration_text(state)
|
||||
|
||||
@@ -845,6 +933,21 @@ def _signal_duration_text(state) -> str:
|
||||
return f"{seconds}с"
|
||||
|
||||
|
||||
def _execution_freshness_text(state) -> str:
|
||||
freshness = str(
|
||||
getattr(state, "execution_price_freshness", "") or ""
|
||||
).upper()
|
||||
|
||||
mapping = {
|
||||
"FRESH": "данные свежие",
|
||||
"AGING": "данные стареют",
|
||||
"STALE": "данные устарели",
|
||||
"UNKNOWN": "нет данных",
|
||||
}
|
||||
|
||||
return mapping.get(freshness, "нет данных")
|
||||
|
||||
|
||||
def _status_text(state) -> str:
|
||||
runtime = _cycle_runtime_text(state)
|
||||
|
||||
@@ -1012,7 +1115,7 @@ def _format_crypto_size(value: float | int | None) -> str:
|
||||
|
||||
def _format_percent(value: float | int | None) -> str:
|
||||
if value is None:
|
||||
return "off"
|
||||
return "выкл"
|
||||
|
||||
number = float(value)
|
||||
|
||||
@@ -1085,24 +1188,21 @@ def _adaptive_adjustment_visible(state) -> bool:
|
||||
return _adaptive_size_active(state) and _show_adaptive_banner(state)
|
||||
|
||||
|
||||
def _short_adaptive_reason(reason: str, multiplier: float | None = None) -> str:
|
||||
if multiplier is not None:
|
||||
try:
|
||||
value = float(multiplier)
|
||||
except (TypeError, ValueError):
|
||||
value = 1.0
|
||||
def _short_adaptive_reason(
|
||||
reason: str,
|
||||
multiplier: float | None = None,
|
||||
) -> str:
|
||||
value = safe_float(multiplier)
|
||||
|
||||
if value is not None:
|
||||
if value < 0.15:
|
||||
return "Вход почти заблокирован"
|
||||
|
||||
if value < 0.40:
|
||||
return "Размер сильно уменьшен"
|
||||
return "Вход заблокирован"
|
||||
|
||||
if value < 0.75:
|
||||
return "Размер уменьшен"
|
||||
return "Риск снижен"
|
||||
|
||||
if value < 1.0:
|
||||
return "Небольшая коррекция"
|
||||
return "Размер уменьшен"
|
||||
|
||||
if value > 1.05:
|
||||
return "Размер увеличен"
|
||||
@@ -1122,6 +1222,6 @@ def _short_adaptive_reason(reason: str, multiplier: float | None = None) -> str:
|
||||
return "Низкая уверенность"
|
||||
|
||||
if not reason:
|
||||
return "Размер изменён"
|
||||
return "Размер скорректирован"
|
||||
|
||||
return reason[:1].upper() + reason[1:]
|
||||
@@ -526,6 +526,12 @@ class AutoTradeService:
|
||||
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
|
||||
@@ -579,6 +585,49 @@ class AutoTradeService:
|
||||
state.snapshot_age_seconds = None
|
||||
state.spread_percent = 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
|
||||
|
||||
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.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
|
||||
|
||||
# собрать контекст для стратегии
|
||||
def _build_strategy_context(self) -> StrategyContext:
|
||||
state = self.get_state()
|
||||
@@ -1394,6 +1443,11 @@ class AutoTradeService:
|
||||
is_fresh = bool(snapshot.get("is_fresh", False))
|
||||
source = str(snapshot.get("source") or "")
|
||||
|
||||
self._sync_execution_pricing_state(
|
||||
state,
|
||||
snapshot,
|
||||
)
|
||||
|
||||
state.snapshot_age_seconds = age_seconds
|
||||
state.spread_percent = self._spread_percent(
|
||||
bid_price=bid_price,
|
||||
@@ -1490,6 +1544,767 @@ class AutoTradeService:
|
||||
|
||||
return round((spread / mid_price) * 100, 5)
|
||||
|
||||
def _sync_execution_pricing_state(
|
||||
self,
|
||||
state: AutoTradeState,
|
||||
snapshot: JsonDict,
|
||||
) -> None:
|
||||
age_seconds = safe_float(snapshot.get("age_seconds"))
|
||||
|
||||
state.execution_price_source = str(snapshot.get("source") or "")
|
||||
state.execution_price_age_seconds = age_seconds
|
||||
state.execution_bid_price = safe_float(snapshot.get("bid_price"))
|
||||
state.execution_ask_price = safe_float(snapshot.get("ask_price"))
|
||||
state.execution_last_price = safe_float(snapshot.get("last_price"))
|
||||
|
||||
if age_seconds is None:
|
||||
state.execution_price_freshness = "UNKNOWN"
|
||||
elif age_seconds <= 1:
|
||||
state.execution_price_freshness = "FRESH"
|
||||
elif age_seconds <= self._warning_snapshot_age_seconds:
|
||||
state.execution_price_freshness = "AGING"
|
||||
else:
|
||||
state.execution_price_freshness = "STALE"
|
||||
|
||||
def _sync_position_health_state(self, state: AutoTradeState) -> None:
|
||||
if state.position_side == "NONE" or state.entry_price is 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
|
||||
return
|
||||
|
||||
pnl_percent = self._position_pnl_percent(state)
|
||||
hold_seconds = self._position_hold_seconds(state)
|
||||
trend_alignment = self._position_trend_alignment(state)
|
||||
adverse_momentum = self._has_adverse_position_momentum(state)
|
||||
|
||||
pressure = self._position_pressure(
|
||||
state=state,
|
||||
pnl_percent=pnl_percent,
|
||||
)
|
||||
|
||||
health_score = self._position_health_score(
|
||||
state=state,
|
||||
pnl_percent=pnl_percent,
|
||||
trend_alignment=trend_alignment,
|
||||
adverse_momentum=adverse_momentum,
|
||||
)
|
||||
|
||||
risk_level, risk_reason = self._position_risk_level(
|
||||
state=state,
|
||||
pnl_percent=pnl_percent,
|
||||
trend_alignment=trend_alignment,
|
||||
adverse_momentum=adverse_momentum,
|
||||
)
|
||||
|
||||
state.position_pnl_percent = pnl_percent
|
||||
state.position_hold_seconds = hold_seconds
|
||||
state.position_pressure = pressure
|
||||
state.position_health_score = health_score
|
||||
state.position_health_status = self._position_health_status(health_score)
|
||||
state.position_health_reason = self._position_health_reason(
|
||||
pressure=pressure,
|
||||
trend_alignment=trend_alignment,
|
||||
adverse_momentum=adverse_momentum,
|
||||
)
|
||||
state.position_risk_level = risk_level
|
||||
state.position_risk_reason = risk_reason
|
||||
state.position_trend_alignment = trend_alignment
|
||||
state.position_adverse_momentum = adverse_momentum
|
||||
state.position_exit_pressure = self._position_exit_pressure(
|
||||
state=state,
|
||||
pnl_percent=pnl_percent,
|
||||
risk_level=risk_level,
|
||||
)
|
||||
|
||||
def _position_pnl_percent(self, state: AutoTradeState) -> float | None:
|
||||
entry_price = safe_float(state.entry_price)
|
||||
size = safe_float(state.position_size)
|
||||
pnl = safe_float(state.unrealized_pnl_usd)
|
||||
|
||||
if entry_price is None or entry_price <= 0:
|
||||
return None
|
||||
|
||||
if size is None or size <= 0:
|
||||
return None
|
||||
|
||||
if pnl is None:
|
||||
return None
|
||||
|
||||
notional = entry_price * size
|
||||
|
||||
if notional <= 0:
|
||||
return None
|
||||
|
||||
return round((pnl / notional) * 100, 4)
|
||||
|
||||
def _position_hold_seconds(self, state: AutoTradeState) -> int | None:
|
||||
opened_at = getattr(state, "position_opened_monotonic_at", None)
|
||||
|
||||
if opened_at is None:
|
||||
return None
|
||||
|
||||
opened = safe_float(opened_at)
|
||||
|
||||
if opened is None:
|
||||
return None
|
||||
|
||||
return max(0, int(time.monotonic() - opened))
|
||||
|
||||
def _position_pressure(
|
||||
self,
|
||||
*,
|
||||
state: AutoTradeState,
|
||||
pnl_percent: float | None,
|
||||
) -> str:
|
||||
pnl = safe_float(state.unrealized_pnl_usd) or 0.0
|
||||
|
||||
if pnl_percent is None:
|
||||
if pnl < 0:
|
||||
return "LOSS"
|
||||
if pnl > 0:
|
||||
return "PROFIT"
|
||||
return "FLAT"
|
||||
|
||||
if pnl_percent <= -0.8:
|
||||
return "HIGH_LOSS"
|
||||
|
||||
if pnl_percent <= -0.3:
|
||||
return "LOSS"
|
||||
|
||||
if pnl_percent >= 0.8:
|
||||
return "STRONG_PROFIT"
|
||||
|
||||
if pnl_percent >= 0.3:
|
||||
return "PROFIT"
|
||||
|
||||
return "FLAT"
|
||||
|
||||
def _position_trend_alignment(self, state: AutoTradeState) -> str:
|
||||
side = str(state.position_side or "NONE").upper()
|
||||
market_state = str(state.market_state or "").upper()
|
||||
trend = str(state.market_trend or "").upper()
|
||||
|
||||
if side == "NONE":
|
||||
return "NONE"
|
||||
|
||||
if side == "LONG":
|
||||
if market_state == "TREND_UP" or trend == "UP":
|
||||
return "ALIGNED"
|
||||
if market_state == "TREND_DOWN" or trend == "DOWN":
|
||||
return "AGAINST"
|
||||
|
||||
if side == "SHORT":
|
||||
if market_state == "TREND_DOWN" or trend == "DOWN":
|
||||
return "ALIGNED"
|
||||
if market_state == "TREND_UP" or trend == "UP":
|
||||
return "AGAINST"
|
||||
|
||||
return "NEUTRAL"
|
||||
|
||||
def _has_adverse_position_momentum(self, state: AutoTradeState) -> bool:
|
||||
side = str(state.position_side or "NONE").upper()
|
||||
momentum_direction = str(state.momentum_direction or "").upper()
|
||||
momentum_state = str(state.momentum_state or "").upper()
|
||||
|
||||
if side == "LONG":
|
||||
return (
|
||||
momentum_direction == "DOWN"
|
||||
or momentum_state in {"MOMENTUM_DOWN", "BREAKOUT_DOWN"}
|
||||
)
|
||||
|
||||
if side == "SHORT":
|
||||
return (
|
||||
momentum_direction == "UP"
|
||||
or momentum_state in {"MOMENTUM_UP", "BREAKOUT_UP"}
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
def _position_health_score(
|
||||
self,
|
||||
*,
|
||||
state: AutoTradeState,
|
||||
pnl_percent: float | None,
|
||||
trend_alignment: str,
|
||||
adverse_momentum: bool,
|
||||
) -> int:
|
||||
score = 100
|
||||
|
||||
if pnl_percent is not None:
|
||||
if pnl_percent <= -1.0:
|
||||
score -= 35
|
||||
elif pnl_percent <= -0.5:
|
||||
score -= 22
|
||||
elif pnl_percent < 0:
|
||||
score -= 10
|
||||
elif pnl_percent >= 0.8:
|
||||
score += 5
|
||||
|
||||
if trend_alignment == "AGAINST":
|
||||
score -= 25
|
||||
elif trend_alignment == "NEUTRAL":
|
||||
score -= 8
|
||||
|
||||
if adverse_momentum:
|
||||
score -= 20
|
||||
|
||||
if state.execution_quality == "BLOCKED":
|
||||
score -= 15
|
||||
elif state.execution_quality == "WARNING":
|
||||
score -= 8
|
||||
|
||||
if state.market_runtime_degraded:
|
||||
score -= 10
|
||||
|
||||
return max(0, min(100, score))
|
||||
|
||||
def _position_health_status(self, score: int | None) -> str:
|
||||
if score is None:
|
||||
return "UNKNOWN"
|
||||
|
||||
if score >= 80:
|
||||
return "HEALTHY"
|
||||
|
||||
if score >= 55:
|
||||
return "WATCH"
|
||||
|
||||
if score >= 35:
|
||||
return "PRESSURE"
|
||||
|
||||
return "DANGER"
|
||||
|
||||
def _position_health_reason(
|
||||
self,
|
||||
*,
|
||||
pressure: str,
|
||||
trend_alignment: str,
|
||||
adverse_momentum: bool,
|
||||
) -> str:
|
||||
if trend_alignment == "AGAINST" and adverse_momentum:
|
||||
return "тренд и momentum против позиции"
|
||||
|
||||
if trend_alignment == "AGAINST":
|
||||
return "тренд против позиции"
|
||||
|
||||
if adverse_momentum:
|
||||
return "momentum против позиции"
|
||||
|
||||
if pressure in {"HIGH_LOSS", "LOSS"}:
|
||||
return "позиция под давлением"
|
||||
|
||||
if pressure in {"PROFIT", "STRONG_PROFIT"}:
|
||||
return "позиция в прибыли"
|
||||
|
||||
return "позиция стабильна"
|
||||
|
||||
def _position_risk_level(
|
||||
self,
|
||||
*,
|
||||
state: AutoTradeState,
|
||||
pnl_percent: float | None,
|
||||
trend_alignment: str,
|
||||
adverse_momentum: bool,
|
||||
) -> tuple[str, str]:
|
||||
if state.execution_quality == "BLOCKED":
|
||||
return "HIGH", "исполнение заблокировано"
|
||||
|
||||
if pnl_percent is not None and pnl_percent <= -1.0:
|
||||
return "HIGH", "сильная просадка позиции"
|
||||
|
||||
if trend_alignment == "AGAINST" and adverse_momentum:
|
||||
return "HIGH", "рынок движется против позиции"
|
||||
|
||||
if pnl_percent is not None and pnl_percent < 0:
|
||||
if trend_alignment == "AGAINST" or adverse_momentum:
|
||||
return "ELEVATED", "убыток усиливается рыночным контекстом"
|
||||
|
||||
return "MODERATE", "позиция в минусе"
|
||||
|
||||
if adverse_momentum:
|
||||
return "MODERATE", "momentum против позиции"
|
||||
|
||||
return "LOW", "критичных рисков нет"
|
||||
|
||||
def _position_exit_pressure(
|
||||
self,
|
||||
*,
|
||||
state: AutoTradeState,
|
||||
pnl_percent: float | None,
|
||||
risk_level: str,
|
||||
) -> str:
|
||||
if risk_level == "HIGH":
|
||||
return "HIGH"
|
||||
|
||||
if risk_level == "ELEVATED":
|
||||
return "WATCH"
|
||||
|
||||
if pnl_percent is not None and pnl_percent <= -0.5:
|
||||
return "WATCH"
|
||||
|
||||
return "LOW"
|
||||
|
||||
def _sync_position_intelligence_state(self, state: AutoTradeState) -> None:
|
||||
if state.position_side == "NONE" or state.entry_price is 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
|
||||
return
|
||||
|
||||
lifecycle_stage = self._position_lifecycle_stage(state)
|
||||
hold_quality = self._position_hold_quality(state)
|
||||
decay_state = self._position_decay_state(state)
|
||||
|
||||
self._sync_advanced_position_analytics(
|
||||
state=state,
|
||||
lifecycle_stage=lifecycle_stage,
|
||||
hold_quality=hold_quality,
|
||||
decay_state=decay_state,
|
||||
)
|
||||
|
||||
exit_confidence = self._position_exit_confidence(
|
||||
state=state,
|
||||
hold_quality=hold_quality,
|
||||
decay_state=decay_state,
|
||||
)
|
||||
|
||||
exit_signal = self._position_exit_signal(exit_confidence)
|
||||
|
||||
state.position_lifecycle_stage = lifecycle_stage
|
||||
state.position_hold_quality = hold_quality
|
||||
state.position_decay_state = decay_state
|
||||
state.position_exit_confidence = exit_confidence
|
||||
state.position_exit_signal = exit_signal
|
||||
state.position_intelligence_reason = self._position_intelligence_reason(
|
||||
state=state,
|
||||
hold_quality=hold_quality,
|
||||
decay_state=decay_state,
|
||||
exit_signal=exit_signal,
|
||||
)
|
||||
state.position_recommended_action = self._position_recommended_action(
|
||||
exit_signal
|
||||
)
|
||||
|
||||
def _position_lifecycle_stage(self, state: AutoTradeState) -> str:
|
||||
hold_seconds = state.position_hold_seconds
|
||||
|
||||
if hold_seconds is None:
|
||||
return "UNKNOWN"
|
||||
|
||||
if hold_seconds < 60:
|
||||
return "NEW"
|
||||
|
||||
if hold_seconds < 300:
|
||||
return "ACTIVE"
|
||||
|
||||
if hold_seconds < 900:
|
||||
return "MATURE"
|
||||
|
||||
return "AGED"
|
||||
|
||||
def _position_hold_quality(self, state: AutoTradeState) -> str:
|
||||
health_status = str(state.position_health_status or "").upper()
|
||||
pressure = str(state.position_pressure or "").upper()
|
||||
trend_alignment = str(state.position_trend_alignment or "").upper()
|
||||
|
||||
if health_status == "DANGER":
|
||||
return "BAD"
|
||||
|
||||
if pressure == "HIGH_LOSS":
|
||||
return "BAD"
|
||||
|
||||
if trend_alignment == "AGAINST" and state.position_adverse_momentum:
|
||||
return "BAD"
|
||||
|
||||
if health_status == "PRESSURE":
|
||||
return "WEAK"
|
||||
|
||||
if pressure == "LOSS":
|
||||
return "WEAK"
|
||||
|
||||
if pressure in {"PROFIT", "STRONG_PROFIT"} and trend_alignment == "ALIGNED":
|
||||
return "GOOD"
|
||||
|
||||
if health_status == "HEALTHY":
|
||||
return "GOOD"
|
||||
|
||||
return "NEUTRAL"
|
||||
|
||||
def _position_decay_state(self, state: AutoTradeState) -> str:
|
||||
pressure = str(state.position_pressure or "").upper()
|
||||
trend_alignment = str(state.position_trend_alignment or "").upper()
|
||||
lifecycle = str(state.position_lifecycle_stage or "").upper()
|
||||
|
||||
if pressure in {"HIGH_LOSS", "LOSS"} and state.position_adverse_momentum:
|
||||
return "ACCELERATING_LOSS"
|
||||
|
||||
if trend_alignment == "AGAINST" and state.position_adverse_momentum:
|
||||
return "CONTEXT_DECAY"
|
||||
|
||||
if pressure == "PROFIT" and state.position_adverse_momentum:
|
||||
return "PROFIT_DECAY"
|
||||
|
||||
if lifecycle == "AGED" and pressure == "FLAT":
|
||||
return "TIME_DECAY"
|
||||
|
||||
return "NONE"
|
||||
|
||||
def _position_exit_confidence(
|
||||
self,
|
||||
*,
|
||||
state: AutoTradeState,
|
||||
hold_quality: str,
|
||||
decay_state: str,
|
||||
) -> float:
|
||||
score = 0.0
|
||||
|
||||
risk_level = str(state.position_risk_level or "").upper()
|
||||
exit_pressure = str(state.position_exit_pressure or "").upper()
|
||||
|
||||
if risk_level == "HIGH":
|
||||
score += 0.45
|
||||
elif risk_level == "ELEVATED":
|
||||
score += 0.30
|
||||
elif risk_level == "MODERATE":
|
||||
score += 0.15
|
||||
|
||||
if exit_pressure == "HIGH":
|
||||
score += 0.30
|
||||
elif exit_pressure == "WATCH":
|
||||
score += 0.15
|
||||
|
||||
if hold_quality == "BAD":
|
||||
score += 0.25
|
||||
elif hold_quality == "WEAK":
|
||||
score += 0.15
|
||||
|
||||
if decay_state in {"ACCELERATING_LOSS", "CONTEXT_DECAY"}:
|
||||
score += 0.25
|
||||
elif decay_state in {"PROFIT_DECAY", "TIME_DECAY"}:
|
||||
score += 0.15
|
||||
|
||||
if state.execution_quality == "BLOCKED":
|
||||
score += 0.10
|
||||
|
||||
return round(max(0.0, min(1.0, score)), 3)
|
||||
|
||||
def _position_exit_signal(self, exit_confidence: float | None) -> str:
|
||||
if exit_confidence is None:
|
||||
return "NONE"
|
||||
|
||||
if exit_confidence >= 0.75:
|
||||
return "EXIT"
|
||||
|
||||
if exit_confidence >= 0.50:
|
||||
return "REDUCE_OR_PROTECT"
|
||||
|
||||
if exit_confidence >= 0.30:
|
||||
return "WATCH"
|
||||
|
||||
return "HOLD"
|
||||
|
||||
def _position_intelligence_reason(
|
||||
self,
|
||||
*,
|
||||
state: AutoTradeState,
|
||||
hold_quality: str,
|
||||
decay_state: str,
|
||||
exit_signal: str,
|
||||
) -> str:
|
||||
if exit_signal == "EXIT":
|
||||
return "позиция требует выхода"
|
||||
|
||||
if exit_signal == "REDUCE_OR_PROTECT":
|
||||
return "позицию нужно защитить или уменьшить"
|
||||
|
||||
if decay_state != "NONE":
|
||||
return "качество удержания ухудшается"
|
||||
|
||||
if hold_quality == "GOOD":
|
||||
return "позицию можно удерживать"
|
||||
|
||||
if hold_quality == "WEAK":
|
||||
return "позиция требует наблюдения"
|
||||
|
||||
return "критичных признаков выхода нет"
|
||||
|
||||
def _position_recommended_action(self, exit_signal: str | None) -> str:
|
||||
if exit_signal == "EXIT":
|
||||
return "CLOSE"
|
||||
|
||||
if exit_signal == "REDUCE_OR_PROTECT":
|
||||
return "PROTECT"
|
||||
|
||||
if exit_signal == "WATCH":
|
||||
return "WATCH"
|
||||
|
||||
return "HOLD"
|
||||
|
||||
def _sync_advanced_position_analytics(
|
||||
self,
|
||||
*,
|
||||
state: AutoTradeState,
|
||||
lifecycle_stage: str,
|
||||
hold_quality: str,
|
||||
decay_state: str,
|
||||
) -> None:
|
||||
pnl = safe_float(state.unrealized_pnl_usd)
|
||||
pnl_percent = safe_float(state.position_pnl_percent)
|
||||
|
||||
peak_pnl = safe_float(state.position_peak_pnl_usd)
|
||||
peak_pnl_percent = safe_float(state.position_peak_pnl_percent)
|
||||
|
||||
if pnl is not None:
|
||||
if peak_pnl is None or pnl > peak_pnl:
|
||||
state.position_peak_pnl_usd = pnl
|
||||
|
||||
if pnl_percent is not None:
|
||||
if peak_pnl_percent is None or pnl_percent > peak_pnl_percent:
|
||||
state.position_peak_pnl_percent = pnl_percent
|
||||
|
||||
state.position_mfe_percent = self._position_mfe_percent(state)
|
||||
state.position_mae_percent = self._position_mae_percent(state)
|
||||
state.position_giveback_percent = self._position_giveback_percent(state)
|
||||
|
||||
fatigue_score = self._position_fatigue_score(
|
||||
state=state,
|
||||
lifecycle_stage=lifecycle_stage,
|
||||
hold_quality=hold_quality,
|
||||
decay_state=decay_state,
|
||||
)
|
||||
|
||||
state.position_fatigue_score = fatigue_score
|
||||
state.position_fatigue_state = self._position_fatigue_state(fatigue_score)
|
||||
state.position_conviction_state = self._position_conviction_state(state)
|
||||
state.position_exit_urgency = self._position_exit_urgency(state)
|
||||
state.position_reversal_risk = self._position_reversal_risk(state)
|
||||
|
||||
def _position_mfe_percent(self, state: AutoTradeState) -> float | None:
|
||||
peak = safe_float(state.position_peak_pnl_percent)
|
||||
|
||||
if peak is None:
|
||||
return None
|
||||
|
||||
return round(max(0.0, peak), 4)
|
||||
|
||||
def _position_mae_percent(self, state: AutoTradeState) -> float | None:
|
||||
current = safe_float(state.position_pnl_percent)
|
||||
|
||||
if current is None:
|
||||
return None
|
||||
|
||||
return round(min(0.0, current), 4)
|
||||
|
||||
def _position_giveback_percent(self, state: AutoTradeState) -> float | None:
|
||||
peak = safe_float(state.position_peak_pnl_percent)
|
||||
current = safe_float(state.position_pnl_percent)
|
||||
|
||||
if peak is None or current is None:
|
||||
return None
|
||||
|
||||
if peak <= 0:
|
||||
return 0.0
|
||||
|
||||
giveback = peak - current
|
||||
|
||||
if giveback <= 0:
|
||||
return 0.0
|
||||
|
||||
return round((giveback / peak) * 100, 2)
|
||||
|
||||
def _position_fatigue_score(
|
||||
self,
|
||||
*,
|
||||
state: AutoTradeState,
|
||||
lifecycle_stage: str,
|
||||
hold_quality: str,
|
||||
decay_state: str,
|
||||
) -> float:
|
||||
score = 0.0
|
||||
|
||||
giveback = safe_float(state.position_giveback_percent) or 0.0
|
||||
hold_seconds = safe_float(state.position_hold_seconds) or 0.0
|
||||
|
||||
if lifecycle_stage == "AGED":
|
||||
score += 0.25
|
||||
elif lifecycle_stage == "MATURE":
|
||||
score += 0.15
|
||||
|
||||
if hold_quality == "BAD":
|
||||
score += 0.30
|
||||
elif hold_quality == "WEAK":
|
||||
score += 0.18
|
||||
|
||||
if decay_state in {"ACCELERATING_LOSS", "CONTEXT_DECAY"}:
|
||||
score += 0.30
|
||||
elif decay_state in {"PROFIT_DECAY", "TIME_DECAY"}:
|
||||
score += 0.18
|
||||
|
||||
if giveback >= 70:
|
||||
score += 0.30
|
||||
elif giveback >= 45:
|
||||
score += 0.20
|
||||
elif giveback >= 25:
|
||||
score += 0.10
|
||||
|
||||
if hold_seconds >= 1800:
|
||||
score += 0.15
|
||||
elif hold_seconds >= 900:
|
||||
score += 0.08
|
||||
|
||||
if state.position_adverse_momentum:
|
||||
score += 0.15
|
||||
|
||||
return round(max(0.0, min(1.0, score)), 3)
|
||||
|
||||
def _position_fatigue_state(self, score: float | None) -> str:
|
||||
value = safe_float(score)
|
||||
|
||||
if value is None:
|
||||
return "UNKNOWN"
|
||||
|
||||
if value >= 0.75:
|
||||
return "EXHAUSTED"
|
||||
|
||||
if value >= 0.50:
|
||||
return "TIRED"
|
||||
|
||||
if value >= 0.25:
|
||||
return "WATCH"
|
||||
|
||||
return "FRESH"
|
||||
|
||||
def _position_conviction_state(self, state: AutoTradeState) -> str:
|
||||
health = str(state.position_health_status or "").upper()
|
||||
fatigue = str(state.position_fatigue_state or "").upper()
|
||||
alignment = str(state.position_trend_alignment or "").upper()
|
||||
|
||||
if health == "DANGER" or fatigue == "EXHAUSTED":
|
||||
return "BROKEN"
|
||||
|
||||
if alignment == "AGAINST" or fatigue == "TIRED":
|
||||
return "WEAKENING"
|
||||
|
||||
if health == "HEALTHY" and alignment == "ALIGNED":
|
||||
return "STRONG"
|
||||
|
||||
return "NEUTRAL"
|
||||
|
||||
def _position_exit_urgency(self, state: AutoTradeState) -> str:
|
||||
exit_signal = str(state.position_exit_signal or "").upper()
|
||||
fatigue = str(state.position_fatigue_state or "").upper()
|
||||
risk = str(state.position_risk_level or "").upper()
|
||||
|
||||
if exit_signal == "EXIT" or risk == "HIGH":
|
||||
return "IMMEDIATE"
|
||||
|
||||
if fatigue == "EXHAUSTED":
|
||||
return "HIGH"
|
||||
|
||||
if exit_signal == "REDUCE_OR_PROTECT" or fatigue == "TIRED":
|
||||
return "MEDIUM"
|
||||
|
||||
if exit_signal == "WATCH":
|
||||
return "LOW"
|
||||
|
||||
return "NONE"
|
||||
|
||||
def _position_reversal_risk(self, state: AutoTradeState) -> str:
|
||||
giveback = safe_float(state.position_giveback_percent) or 0.0
|
||||
fatigue = str(state.position_fatigue_state or "").upper()
|
||||
adverse = bool(state.position_adverse_momentum)
|
||||
|
||||
if adverse and giveback >= 45:
|
||||
return "HIGH"
|
||||
|
||||
if fatigue in {"TIRED", "EXHAUSTED"} and giveback >= 25:
|
||||
return "ELEVATED"
|
||||
|
||||
if adverse:
|
||||
return "MODERATE"
|
||||
|
||||
return "LOW"
|
||||
|
||||
def _sync_autonomous_trade_management(
|
||||
self,
|
||||
state: AutoTradeState,
|
||||
) -> None:
|
||||
if state.position_side == "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
|
||||
return
|
||||
|
||||
exit_signal = str(state.position_exit_signal or "HOLD").upper()
|
||||
exit_confidence = safe_float(state.position_exit_confidence) or 0.0
|
||||
|
||||
action = "HOLD"
|
||||
reason = "позиция удерживается"
|
||||
|
||||
protect_required = False
|
||||
reduce_required = False
|
||||
exit_required = False
|
||||
|
||||
if exit_signal == "WATCH":
|
||||
action = "WATCH"
|
||||
reason = "позиция требует наблюдения"
|
||||
|
||||
elif exit_signal == "REDUCE_OR_PROTECT":
|
||||
if state.position_pressure in {"HIGH_LOSS", "LOSS"}:
|
||||
action = "REDUCE"
|
||||
reduce_required = True
|
||||
reason = "позиция должна быть уменьшена"
|
||||
else:
|
||||
action = "PROTECT"
|
||||
protect_required = True
|
||||
reason = "позиция требует защиты"
|
||||
|
||||
elif exit_signal == "EXIT":
|
||||
action = "EXIT"
|
||||
exit_required = True
|
||||
reason = "позиция требует закрытия"
|
||||
|
||||
if (
|
||||
state.position_adverse_momentum
|
||||
and state.position_trend_alignment == "AGAINST"
|
||||
and exit_confidence >= 0.65
|
||||
):
|
||||
action = "EXIT"
|
||||
exit_required = True
|
||||
reason = "рынок агрессивно движется против позиции"
|
||||
|
||||
state.autonomous_action = action
|
||||
state.autonomous_action_reason = reason
|
||||
state.autonomous_action_confidence = exit_confidence
|
||||
state.autonomous_protection_required = protect_required
|
||||
state.autonomous_reduce_required = reduce_required
|
||||
state.autonomous_exit_required = exit_required
|
||||
|
||||
def _log_execution_quality_if_changed(
|
||||
self,
|
||||
*,
|
||||
@@ -1831,6 +2646,13 @@ class AutoTradeService:
|
||||
if state.execution_quality != "BLOCKED":
|
||||
ExecutionEngine().process(state)
|
||||
|
||||
self._sync_position_health_state(state)
|
||||
self._sync_position_intelligence_state(state)
|
||||
self._sync_autonomous_trade_management(state)
|
||||
|
||||
if state.execution_quality != "BLOCKED":
|
||||
ExecutionEngine().process_runtime_action(state)
|
||||
|
||||
self._sync_execution_semantic_state(state)
|
||||
|
||||
return state
|
||||
@@ -61,9 +61,88 @@ class AutoTradeState:
|
||||
# размер позиции
|
||||
position_size: float | None = None
|
||||
|
||||
# monotonic timestamp открытия текущей позиции
|
||||
position_opened_monotonic_at: float | None = None
|
||||
|
||||
# нереализованный PnL
|
||||
unrealized_pnl_usd: float | None = None
|
||||
|
||||
# position health / runtime risk
|
||||
position_pnl_percent: float | None = None
|
||||
position_hold_seconds: int | None = None
|
||||
position_pressure: str | None = None
|
||||
position_health_score: int | None = None
|
||||
position_health_status: str | None = None
|
||||
position_health_reason: str | None = None
|
||||
position_risk_level: str | None = None
|
||||
position_risk_reason: str | None = None
|
||||
position_trend_alignment: str | None = None
|
||||
position_adverse_momentum: bool = False
|
||||
position_exit_pressure: str | None = None
|
||||
|
||||
# position intelligence layer
|
||||
position_lifecycle_stage: str | None = None
|
||||
position_hold_quality: str | None = None
|
||||
position_decay_state: str | None = None
|
||||
position_exit_confidence: float | None = None
|
||||
position_exit_signal: str | None = None
|
||||
position_intelligence_reason: str | None = None
|
||||
position_recommended_action: str | None = None
|
||||
|
||||
# advanced lifecycle analytics
|
||||
position_peak_pnl_usd: float | None = None
|
||||
position_peak_pnl_percent: float | None = None
|
||||
|
||||
position_mfe_percent: float | None = None
|
||||
position_mae_percent: float | None = None
|
||||
|
||||
position_fatigue_score: float | None = None
|
||||
position_fatigue_state: str | None = None
|
||||
|
||||
position_giveback_percent: float | None = None
|
||||
|
||||
# position behavioral state
|
||||
position_conviction_state: str | None = None
|
||||
position_exit_urgency: str | None = None
|
||||
position_reversal_risk: str | None = None
|
||||
|
||||
# autonomous trade management
|
||||
autonomous_action: str | None = None
|
||||
autonomous_action_reason: str | None = None
|
||||
autonomous_action_confidence: float | None = None
|
||||
autonomous_protection_required: bool = False
|
||||
autonomous_reduce_required: bool = False
|
||||
autonomous_exit_required: bool = False
|
||||
|
||||
# runtime position action execution
|
||||
autonomous_last_action: str | None = None
|
||||
autonomous_last_action_reason: str | None = None
|
||||
autonomous_last_action_at: float | None = None
|
||||
|
||||
# loss cooldown tracking
|
||||
last_loss_monotonic_at: float | None = None
|
||||
|
||||
# runtime protection engine
|
||||
position_protection_status: str | None = None
|
||||
position_protection_reason: str | None = None
|
||||
|
||||
# break-even protection
|
||||
break_even_armed: bool = False
|
||||
break_even_price: float | None = None
|
||||
|
||||
# trailing protection
|
||||
trailing_stop_active: bool = False
|
||||
trailing_stop_price: float | None = None
|
||||
|
||||
# locked profit protection
|
||||
profit_lock_active: bool = False
|
||||
profit_lock_price: float | None = None
|
||||
|
||||
# runtime protection execution
|
||||
runtime_protection_action: str | None = None
|
||||
runtime_protection_reason: str | None = None
|
||||
runtime_protection_updated_at: float | None = None
|
||||
|
||||
# максимальная просадка
|
||||
max_drawdown_usd: float | None = None
|
||||
|
||||
@@ -235,6 +314,25 @@ class AutoTradeState:
|
||||
# человекочитаемое объяснение качества исполнения
|
||||
execution_quality_message: str | None = None
|
||||
|
||||
# источник execution pricing snapshot
|
||||
execution_price_source: str | None = None
|
||||
|
||||
# возраст execution snapshot
|
||||
execution_price_age_seconds: float | None = None
|
||||
|
||||
# bid execution price
|
||||
execution_bid_price: float | None = None
|
||||
|
||||
# ask execution price
|
||||
execution_ask_price: float | None = None
|
||||
|
||||
# last execution price
|
||||
execution_last_price: float | None = None
|
||||
|
||||
# pricing freshness status:
|
||||
# FRESH / AGING / STALE / UNKNOWN
|
||||
execution_price_freshness: str | None = None
|
||||
|
||||
# признак деградации runtime market data
|
||||
market_runtime_degraded: bool = False
|
||||
|
||||
|
||||
@@ -56,6 +56,9 @@ class SemanticDiagnosticFormatter:
|
||||
if mode != "COMPACT":
|
||||
if has_position:
|
||||
sections.append(self._position_block(position))
|
||||
position_health_block = self._position_health_block(position)
|
||||
if position_health_block:
|
||||
sections.append(position_health_block)
|
||||
|
||||
if self._has_adaptive_size(adaptive):
|
||||
sections.append(self._adaptive_block(adaptive))
|
||||
@@ -1035,6 +1038,111 @@ class SemanticDiagnosticFormatter:
|
||||
|
||||
return "\n".join(lines).strip()
|
||||
|
||||
def _position_health_block(self, data: JsonDict) -> str:
|
||||
health_state = str(data.get("health_state") or "")
|
||||
health_score = safe_float(data.get("health_score"))
|
||||
health_message = str(data.get("health_message") or "").strip()
|
||||
pressure_state = str(data.get("pressure_state") or "")
|
||||
trend_alignment = str(data.get("trend_alignment") or "")
|
||||
adverse_momentum = bool(data.get("adverse_momentum"))
|
||||
risk_used = safe_float(data.get("risk_used_percent"))
|
||||
price_move = safe_float(data.get("price_move_percent"))
|
||||
opened_age = data.get("opened_age_seconds")
|
||||
|
||||
if not health_state or health_state in {"NONE", "UNKNOWN"}:
|
||||
return ""
|
||||
|
||||
lines = [
|
||||
(
|
||||
f"{self._position_health_icon(health_state)} "
|
||||
f"Здоровье позиции · {self._position_health_title(health_state)}"
|
||||
),
|
||||
]
|
||||
|
||||
if health_score is not None:
|
||||
lines.append(f"• Score: {health_score:.0f}/100")
|
||||
|
||||
if price_move is not None:
|
||||
lines.append(f"• Движение цены: {price_move:+.3f}%")
|
||||
|
||||
if risk_used is not None and risk_used > 0:
|
||||
lines.append(f"• Использовано риска: {risk_used:.1f}%")
|
||||
|
||||
alignment_line = self._position_alignment_line(trend_alignment)
|
||||
if alignment_line:
|
||||
lines.append(alignment_line)
|
||||
|
||||
pressure_line = self._position_pressure_line(pressure_state)
|
||||
if pressure_line:
|
||||
lines.append(pressure_line)
|
||||
|
||||
if adverse_momentum:
|
||||
lines.append("• Импульс: против позиции")
|
||||
|
||||
hold_time = self._duration(opened_age)
|
||||
if hold_time != "—":
|
||||
lines.append(f"• В позиции: {hold_time}")
|
||||
|
||||
if health_message:
|
||||
lines.append(health_message)
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def _position_health_icon(self, value: object) -> str:
|
||||
text = str(value or "")
|
||||
|
||||
if text == "HEALTHY":
|
||||
return "🟢"
|
||||
|
||||
if text == "WATCH":
|
||||
return "🟡"
|
||||
|
||||
if text == "PRESSURE":
|
||||
return "🟠"
|
||||
|
||||
if text == "DANGER":
|
||||
return "🔴"
|
||||
|
||||
return "⚪️"
|
||||
|
||||
def _position_health_title(self, value: object) -> str:
|
||||
text = str(value or "")
|
||||
|
||||
mapping = {
|
||||
"HEALTHY": "устойчива",
|
||||
"WATCH": "под наблюдением",
|
||||
"PRESSURE": "под давлением",
|
||||
"DANGER": "высокий риск",
|
||||
}
|
||||
|
||||
return mapping.get(text, "неизвестно")
|
||||
|
||||
def _position_alignment_line(self, value: object) -> str:
|
||||
text = str(value or "")
|
||||
|
||||
if text == "ALIGNED":
|
||||
return "• Рынок: по позиции"
|
||||
|
||||
if text == "AGAINST":
|
||||
return "• Рынок: против позиции"
|
||||
|
||||
if text == "NEUTRAL":
|
||||
return "• Рынок: нейтрально"
|
||||
|
||||
return ""
|
||||
|
||||
def _position_pressure_line(self, value: object) -> str:
|
||||
text = str(value or "")
|
||||
|
||||
mapping = {
|
||||
"PROFIT": "• Давление: нет",
|
||||
"PROFIT_UNDER_PRESSURE": "• Давление: прибыль под риском",
|
||||
"LOSS": "• Давление: умеренное",
|
||||
"PRESSURE": "• Давление: повышенное",
|
||||
"DANGER": "• Давление: критическое",
|
||||
}
|
||||
|
||||
return mapping.get(text, "")
|
||||
|
||||
def _position_risk_line(self, data: JsonDict) -> str:
|
||||
items: list[str] = []
|
||||
|
||||
@@ -33,6 +33,11 @@ class SemanticDiagnosticSnapshotBuilder:
|
||||
|
||||
position_current_price = self._position_current_price(state)
|
||||
|
||||
position_health = self._position_health(
|
||||
state=state,
|
||||
current_price=position_current_price,
|
||||
)
|
||||
|
||||
return {
|
||||
"status": {
|
||||
"status": state.status,
|
||||
@@ -142,6 +147,18 @@ class SemanticDiagnosticSnapshotBuilder:
|
||||
"stop_loss_usd": state.effective_target_risk_usd,
|
||||
"take_profit_usd": self._take_profit_usd(state),
|
||||
"max_loss_usd": state.max_loss_usd,
|
||||
"opened_age_seconds": self._age_seconds(
|
||||
now=now,
|
||||
started_at=state.position_opened_monotonic_at,
|
||||
),
|
||||
"price_move_percent": position_health.get("price_move_percent"),
|
||||
"risk_used_percent": position_health.get("risk_used_percent"),
|
||||
"health_state": position_health.get("health_state"),
|
||||
"health_score": position_health.get("health_score"),
|
||||
"health_message": position_health.get("health_message"),
|
||||
"pressure_state": position_health.get("pressure_state"),
|
||||
"trend_alignment": position_health.get("trend_alignment"),
|
||||
"adverse_momentum": position_health.get("adverse_momentum"),
|
||||
},
|
||||
"runtime_health": {
|
||||
"health_score": health_score,
|
||||
@@ -153,6 +170,8 @@ class SemanticDiagnosticSnapshotBuilder:
|
||||
"runtime_expired_message": state.runtime_expired_message,
|
||||
"has_market_data": state.market_state is not None,
|
||||
"has_momentum_data": getattr(state, "momentum_state", None) is not None,
|
||||
"position_health_state": position_health.get("health_state"),
|
||||
"position_health_score": position_health.get("health_score"),
|
||||
},
|
||||
"summary": {
|
||||
"health_score": health_score,
|
||||
@@ -177,6 +196,7 @@ class SemanticDiagnosticSnapshotBuilder:
|
||||
"momentum": getattr(state, "momentum_state", None),
|
||||
"execution": state.execution_semantic_status,
|
||||
"position": state.position_side,
|
||||
"position_health": position_health.get("health_state"),
|
||||
"is_ready": state.is_signal_ready,
|
||||
"is_blocked": bool(blockers),
|
||||
"blockers": blockers,
|
||||
@@ -483,6 +503,288 @@ class SemanticDiagnosticSnapshotBuilder:
|
||||
|
||||
return result
|
||||
|
||||
def _position_health(
|
||||
self,
|
||||
*,
|
||||
state: AutoTradeState,
|
||||
current_price: float | None,
|
||||
) -> dict[str, Any]:
|
||||
if state.position_side == "NONE":
|
||||
return {
|
||||
"health_state": "NONE",
|
||||
"health_score": None,
|
||||
"health_message": None,
|
||||
"price_move_percent": None,
|
||||
"risk_used_percent": None,
|
||||
"pressure_state": "NONE",
|
||||
"trend_alignment": "NONE",
|
||||
"adverse_momentum": False,
|
||||
}
|
||||
|
||||
entry_price = safe_float(state.entry_price)
|
||||
pnl = safe_float(state.unrealized_pnl_usd)
|
||||
stop_loss_usd = safe_float(state.effective_target_risk_usd)
|
||||
max_loss_usd = safe_float(state.max_loss_usd)
|
||||
|
||||
price_move_percent = self._position_price_move_percent(
|
||||
side=state.position_side,
|
||||
entry_price=entry_price,
|
||||
current_price=current_price,
|
||||
)
|
||||
|
||||
risk_used_percent = self._position_risk_used_percent(
|
||||
pnl=pnl,
|
||||
stop_loss_usd=stop_loss_usd,
|
||||
max_loss_usd=max_loss_usd,
|
||||
)
|
||||
|
||||
trend_alignment = self._position_trend_alignment(state)
|
||||
adverse_momentum = self._has_adverse_momentum(state)
|
||||
pressure_state = self._position_pressure_state(
|
||||
pnl=pnl,
|
||||
risk_used_percent=risk_used_percent,
|
||||
adverse_momentum=adverse_momentum,
|
||||
)
|
||||
|
||||
opened_age_seconds = self._age_seconds(
|
||||
now=time.monotonic(),
|
||||
started_at=state.position_opened_monotonic_at,
|
||||
)
|
||||
|
||||
health_score = self._position_health_score(
|
||||
pnl=pnl,
|
||||
risk_used_percent=risk_used_percent,
|
||||
trend_alignment=trend_alignment,
|
||||
adverse_momentum=adverse_momentum,
|
||||
pressure_state=pressure_state,
|
||||
opened_age_seconds=opened_age_seconds,
|
||||
)
|
||||
|
||||
health_state = self._position_health_state(health_score)
|
||||
health_message = self._position_health_message(
|
||||
health_state=health_state,
|
||||
pressure_state=pressure_state,
|
||||
trend_alignment=trend_alignment,
|
||||
adverse_momentum=adverse_momentum,
|
||||
)
|
||||
|
||||
return {
|
||||
"health_state": health_state,
|
||||
"health_score": health_score,
|
||||
"health_message": health_message,
|
||||
"price_move_percent": price_move_percent,
|
||||
"risk_used_percent": risk_used_percent,
|
||||
"pressure_state": pressure_state,
|
||||
"trend_alignment": trend_alignment,
|
||||
"adverse_momentum": adverse_momentum,
|
||||
}
|
||||
|
||||
def _position_price_move_percent(
|
||||
self,
|
||||
*,
|
||||
side: str | None,
|
||||
entry_price: float | None,
|
||||
current_price: float | None,
|
||||
) -> float | None:
|
||||
if entry_price is None or current_price is None:
|
||||
return None
|
||||
|
||||
if entry_price <= 0 or current_price <= 0:
|
||||
return None
|
||||
|
||||
normalized_side = str(side or "").upper()
|
||||
|
||||
if normalized_side == "LONG":
|
||||
return round(((current_price - entry_price) / entry_price) * 100, 4)
|
||||
|
||||
if normalized_side == "SHORT":
|
||||
return round(((entry_price - current_price) / entry_price) * 100, 4)
|
||||
|
||||
return None
|
||||
|
||||
def _position_risk_used_percent(
|
||||
self,
|
||||
*,
|
||||
pnl: float | None,
|
||||
stop_loss_usd: float | None,
|
||||
max_loss_usd: float | None,
|
||||
) -> float | None:
|
||||
if pnl is None or pnl >= 0:
|
||||
return 0.0
|
||||
|
||||
candidates = [
|
||||
value
|
||||
for value in [stop_loss_usd, max_loss_usd]
|
||||
if value is not None and value > 0
|
||||
]
|
||||
|
||||
if not candidates:
|
||||
return None
|
||||
|
||||
risk_base = min(candidates)
|
||||
|
||||
if risk_base is None or risk_base <= 0:
|
||||
return None
|
||||
|
||||
return round(min(999.0, (abs(pnl) / abs(risk_base)) * 100), 1)
|
||||
|
||||
def _position_trend_alignment(self, state: AutoTradeState) -> str:
|
||||
side = str(state.position_side or "").upper()
|
||||
market_state = str(state.market_state or "").upper()
|
||||
trend = str(state.market_trend or "").upper()
|
||||
|
||||
if side == "LONG":
|
||||
if market_state == "TREND_UP" or trend == "UP":
|
||||
return "ALIGNED"
|
||||
|
||||
if market_state == "TREND_DOWN" or trend == "DOWN":
|
||||
return "AGAINST"
|
||||
|
||||
if side == "SHORT":
|
||||
if market_state == "TREND_DOWN" or trend == "DOWN":
|
||||
return "ALIGNED"
|
||||
|
||||
if market_state == "TREND_UP" or trend == "UP":
|
||||
return "AGAINST"
|
||||
|
||||
return "NEUTRAL"
|
||||
|
||||
def _has_adverse_momentum(self, state: AutoTradeState) -> bool:
|
||||
side = str(state.position_side or "").upper()
|
||||
momentum_direction = str(state.momentum_direction or "").upper()
|
||||
momentum_state = str(state.momentum_state or "").upper()
|
||||
|
||||
if side == "LONG":
|
||||
return (
|
||||
momentum_direction == "DOWN"
|
||||
or momentum_state in {"MOMENTUM_DOWN", "BREAKOUT_DOWN"}
|
||||
)
|
||||
|
||||
if side == "SHORT":
|
||||
return (
|
||||
momentum_direction == "UP"
|
||||
or momentum_state in {"MOMENTUM_UP", "BREAKOUT_UP"}
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
def _position_pressure_state(
|
||||
self,
|
||||
*,
|
||||
pnl: float | None,
|
||||
risk_used_percent: float | None,
|
||||
adverse_momentum: bool,
|
||||
) -> str:
|
||||
if pnl is None:
|
||||
return "UNKNOWN"
|
||||
|
||||
if pnl >= 0:
|
||||
if adverse_momentum:
|
||||
return "PROFIT_UNDER_PRESSURE"
|
||||
|
||||
return "PROFIT"
|
||||
|
||||
if risk_used_percent is None:
|
||||
return "LOSS"
|
||||
|
||||
if risk_used_percent >= 80:
|
||||
return "DANGER"
|
||||
|
||||
if risk_used_percent >= 50:
|
||||
return "PRESSURE"
|
||||
|
||||
return "LOSS"
|
||||
|
||||
def _position_health_score(
|
||||
self,
|
||||
*,
|
||||
pnl: float | None,
|
||||
risk_used_percent: float | None,
|
||||
trend_alignment: str,
|
||||
adverse_momentum: bool,
|
||||
pressure_state: str,
|
||||
opened_age_seconds: int | None,
|
||||
) -> int:
|
||||
score = 100
|
||||
|
||||
if pnl is not None:
|
||||
if pnl < 0:
|
||||
score -= 20
|
||||
elif pnl > 0:
|
||||
score += 5
|
||||
|
||||
if risk_used_percent is not None:
|
||||
if risk_used_percent >= 80:
|
||||
score -= 45
|
||||
elif risk_used_percent >= 50:
|
||||
score -= 30
|
||||
elif risk_used_percent >= 25:
|
||||
score -= 15
|
||||
|
||||
if trend_alignment == "AGAINST":
|
||||
score -= 25
|
||||
elif trend_alignment == "ALIGNED":
|
||||
score += 8
|
||||
|
||||
if adverse_momentum:
|
||||
score -= 20
|
||||
|
||||
if pressure_state == "DANGER":
|
||||
score -= 20
|
||||
elif pressure_state == "PRESSURE":
|
||||
score -= 10
|
||||
|
||||
if opened_age_seconds is not None:
|
||||
if opened_age_seconds >= 7200:
|
||||
score -= 20
|
||||
elif opened_age_seconds >= 3600:
|
||||
score -= 10
|
||||
|
||||
return max(0, min(100, score))
|
||||
|
||||
def _position_health_state(self, score: int | None) -> str:
|
||||
if score is None:
|
||||
return "UNKNOWN"
|
||||
|
||||
if score >= 75:
|
||||
return "HEALTHY"
|
||||
|
||||
if score >= 50:
|
||||
return "WATCH"
|
||||
|
||||
if score >= 30:
|
||||
return "PRESSURE"
|
||||
|
||||
return "DANGER"
|
||||
|
||||
def _position_health_message(
|
||||
self,
|
||||
*,
|
||||
health_state: str,
|
||||
pressure_state: str,
|
||||
trend_alignment: str,
|
||||
adverse_momentum: bool,
|
||||
) -> str:
|
||||
if health_state == "HEALTHY":
|
||||
return "Позиция выглядит устойчиво."
|
||||
|
||||
if health_state == "WATCH":
|
||||
return "Позиция требует наблюдения."
|
||||
|
||||
if health_state == "PRESSURE":
|
||||
if trend_alignment == "AGAINST":
|
||||
return "Позиция под давлением: рынок против направления."
|
||||
|
||||
if adverse_momentum:
|
||||
return "Позиция под давлением: импульс против позиции."
|
||||
|
||||
return "Позиция под давлением."
|
||||
|
||||
if health_state == "DANGER":
|
||||
return "Высокий риск по открытой позиции."
|
||||
|
||||
return "Состояние позиции не определено."
|
||||
|
||||
def _take_profit_usd(self, state: AutoTradeState) -> float | None:
|
||||
take_profit_percent = safe_float(state.take_profit_percent)
|
||||
position_size = safe_float(state.position_size)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,8 +25,27 @@ class PositionState:
|
||||
# нереализованный PnL
|
||||
unrealized_pnl_usd: float | None = None
|
||||
|
||||
# peak pnl tracking
|
||||
peak_unrealized_pnl_usd: float | None = None
|
||||
peak_pnl_percent: float | None = None
|
||||
|
||||
# lifecycle tracking
|
||||
max_favorable_excursion_percent: float | None = None
|
||||
max_adverse_excursion_percent: float | None = None
|
||||
|
||||
# runtime fatigue tracking
|
||||
fatigue_score: float | None = None
|
||||
fatigue_state: str | None = None
|
||||
|
||||
# position regime memory
|
||||
best_price_seen: float | None = None
|
||||
worst_price_seen: float | None = None
|
||||
|
||||
# время открытия позиции
|
||||
opened_at: str | None = None
|
||||
|
||||
# monotonic timestamp открытия позиции
|
||||
opened_monotonic_at: float | None = None
|
||||
|
||||
# время последнего обновления позиции
|
||||
updated_at: str | None = None
|
||||
@@ -1385,6 +1385,52 @@
|
||||
- реализована подготовка semantic position health layer
|
||||
- реализована подготовка runtime risk reasoning layer
|
||||
|
||||
### 07.4.4.1.12 ✅ Position Health & Runtime Risk Layer
|
||||
- реализован runtime position protection engine
|
||||
- реализован position-aware execution layer
|
||||
- реализован runtime protection lifecycle
|
||||
- реализован break-even protection engine
|
||||
- реализован automated break-even activation
|
||||
- реализован runtime break-even synchronization
|
||||
- реализован profit lock protection engine
|
||||
- реализован runtime profit protection logic
|
||||
- реализован trailing stop runtime engine
|
||||
- реализован dynamic trailing stop synchronization
|
||||
- реализован giveback protection engine
|
||||
- реализован runtime peak pnl tracking
|
||||
- реализован pnl deterioration analysis
|
||||
- реализован giveback runtime exits
|
||||
- реализован time decay protection engine
|
||||
- реализован stale position detection
|
||||
- реализован weak hold structure analysis
|
||||
- реализован time-based runtime exit layer
|
||||
- реализован runtime autonomous action engine
|
||||
- реализованы runtime actions PROTECT / REDUCE / EXIT
|
||||
- реализован runtime action cooldown layer
|
||||
- реализован execution supervisor layer
|
||||
- реализован emergency execution halt
|
||||
- реализован execution cooldown after loss
|
||||
- реализован degraded market execution blocker
|
||||
- реализован stale snapshot execution blocker
|
||||
- реализован signal conflict protection layer
|
||||
- реализован runtime market regime protection
|
||||
- реализован execution snapshot freshness validation
|
||||
- реализован runtime flip protection upgrade
|
||||
- реализован flip cooldown engine
|
||||
- реализован breakout-aware flip protection
|
||||
- реализован loss-aware flip protection
|
||||
- реализован adaptive runtime risk engine
|
||||
- реализован advanced adaptive sizing layer
|
||||
- реализована execution quality-aware sizing logic
|
||||
- реализован margin-aware effective risk engine
|
||||
- реализован effective risk synchronization
|
||||
- реализован runtime event propagation layer
|
||||
- реализована runtime journal observability architecture
|
||||
- реализован runtime semantic reasoning layer
|
||||
- реализованы human-readable runtime explanations
|
||||
- реализована runtime diagnostics propagation
|
||||
- реализована preparation for partial exit engine
|
||||
- реализована preparation for advanced runtime orchestration
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1495,6 +1495,53 @@
|
||||
- реализована подготовка semantic position health layer
|
||||
- реализована подготовка runtime risk reasoning layer
|
||||
|
||||
### 07.4.4.1.12 ✅ Position Health & Runtime Risk Layer
|
||||
- реализован runtime position protection engine
|
||||
- реализован position-aware execution layer
|
||||
- реализован runtime protection lifecycle
|
||||
- реализован break-even protection engine
|
||||
- реализован automated break-even activation
|
||||
- реализован runtime break-even synchronization
|
||||
- реализован profit lock protection engine
|
||||
- реализован runtime profit protection logic
|
||||
- реализован trailing stop runtime engine
|
||||
- реализован dynamic trailing stop synchronization
|
||||
- реализован giveback protection engine
|
||||
- реализован runtime peak pnl tracking
|
||||
- реализован pnl deterioration analysis
|
||||
- реализован giveback runtime exits
|
||||
- реализован time decay protection engine
|
||||
- реализован stale position detection
|
||||
- реализован weak hold structure analysis
|
||||
- реализован time-based runtime exit layer
|
||||
- реализован runtime autonomous action engine
|
||||
- реализованы runtime actions PROTECT / REDUCE / EXIT
|
||||
- реализован runtime action cooldown layer
|
||||
- реализован execution supervisor layer
|
||||
- реализован emergency execution halt
|
||||
- реализован execution cooldown after loss
|
||||
- реализован degraded market execution blocker
|
||||
- реализован stale snapshot execution blocker
|
||||
- реализован signal conflict protection layer
|
||||
- реализован runtime market regime protection
|
||||
- реализован execution snapshot freshness validation
|
||||
- реализован runtime flip protection upgrade
|
||||
- реализован flip cooldown engine
|
||||
- реализован breakout-aware flip protection
|
||||
- реализован loss-aware flip protection
|
||||
- реализован adaptive runtime risk engine
|
||||
- реализован advanced adaptive sizing layer
|
||||
- реализована execution quality-aware sizing logic
|
||||
- реализован margin-aware effective risk engine
|
||||
- реализован effective risk synchronization
|
||||
- реализован runtime event propagation layer
|
||||
- реализована runtime journal observability architecture
|
||||
- реализован runtime semantic reasoning layer
|
||||
- реализованы human-readable runtime explanations
|
||||
- реализована runtime diagnostics propagation
|
||||
- реализована preparation for partial exit engine
|
||||
- реализована preparation for advanced runtime orchestration
|
||||
|
||||
---
|
||||
|
||||
### 07.4.5
|
||||
|
||||
@@ -0,0 +1,207 @@
|
||||
# 07.4.4.1.12 — Position Health & Runtime Risk Layer
|
||||
|
||||
Статус
|
||||
|
||||
## ✅ Реализовано
|
||||
|
||||
Рекомендуемый commit message:
|
||||
|
||||
git commit -m "07.4.4.1.12 — Position Health & Runtime Risk Layer"
|
||||
|
||||
---
|
||||
|
||||
## Краткое описание этапа
|
||||
|
||||
Этап посвящён развитию runtime execution protection layer и внедрению полноценной position-aware risk management architecture поверх execution engine.
|
||||
|
||||
Главная цель этапа:
|
||||
|
||||
* научить систему анализировать состояние уже открытой позиции;
|
||||
* реализовать runtime position protection;
|
||||
* внедрить adaptive runtime risk management;
|
||||
* реализовать autonomous runtime actions;
|
||||
* внедрить giveback protection;
|
||||
* реализовать time decay logic;
|
||||
* внедрить runtime execution supervisor;
|
||||
* подготовить execution engine к institutional-grade runtime orchestration.
|
||||
|
||||
---
|
||||
|
||||
## Основные реализованные изменения
|
||||
|
||||
1. Runtime Position Protection Engine
|
||||
|
||||
Реализован полноценный runtime protection engine для активной позиции.
|
||||
|
||||
Добавлены:
|
||||
|
||||
* runtime protection lifecycle;
|
||||
* runtime protection state synchronization;
|
||||
* runtime protection event propagation;
|
||||
* runtime protection diagnostics rendering.
|
||||
|
||||
Теперь execution engine способен:
|
||||
|
||||
* отслеживать состояние позиции в runtime;
|
||||
* реагировать на ухудшение структуры позиции;
|
||||
* управлять protection logic в реальном времени;
|
||||
* выполнять forced runtime exits.
|
||||
|
||||
---
|
||||
|
||||
2. Break-even Protection Layer
|
||||
|
||||
Реализован automated break-even engine.
|
||||
|
||||
Добавлены:
|
||||
|
||||
* break_even_armed
|
||||
* break_even_price
|
||||
* BREAK_EVEN runtime actions
|
||||
|
||||
Теперь система автоматически переводит позицию в break-even и защищает капитал после выхода позиции в прибыль.
|
||||
|
||||
---
|
||||
|
||||
3. Profit Lock Engine
|
||||
|
||||
Реализован profit lock protection layer.
|
||||
|
||||
Добавлены:
|
||||
|
||||
* profit_lock_active
|
||||
* profit_lock_price
|
||||
* PROFIT_LOCK runtime protection logic
|
||||
|
||||
Теперь execution layer способен фиксировать часть прибыли и предотвращать глубокий giveback прибыли.
|
||||
|
||||
---
|
||||
|
||||
4. Trailing Stop Runtime Engine
|
||||
|
||||
Реализован полноценный trailing stop engine.
|
||||
|
||||
Добавлены:
|
||||
|
||||
* trailing_stop_active
|
||||
* trailing_stop_price
|
||||
* dynamic trailing updates
|
||||
* runtime trailing synchronization
|
||||
|
||||
Теперь runtime protection подтягивает protection level вслед за ценой и сопровождает трендовое движение.
|
||||
|
||||
---
|
||||
|
||||
5. Giveback Protection Engine
|
||||
|
||||
Реализован отдельный giveback engine.
|
||||
|
||||
Добавлены:
|
||||
|
||||
* runtime peak pnl tracking
|
||||
* giveback detection logic
|
||||
* profit deterioration analysis
|
||||
* aggressive runtime exit handling
|
||||
|
||||
Теперь execution engine способен отслеживать потерю накопленной прибыли и закрывать позицию при dangerous pnl giveback.
|
||||
|
||||
---
|
||||
|
||||
6. Time Decay Runtime Engine
|
||||
|
||||
Реализован time decay protection layer.
|
||||
|
||||
Добавлены:
|
||||
|
||||
* position hold duration analysis
|
||||
* stale position detection
|
||||
* weak holding structure analysis
|
||||
* time-based runtime exits
|
||||
|
||||
Теперь система умеет определять слишком долгие позиции и закрывать weak stagnant positions.
|
||||
|
||||
---
|
||||
|
||||
7. Runtime Autonomous Action Layer
|
||||
|
||||
Существенно расширен runtime autonomous action engine.
|
||||
|
||||
Добавлены runtime actions:
|
||||
|
||||
* PROTECT
|
||||
* REDUCE
|
||||
* EXIT
|
||||
* EXIT_BLOCKED
|
||||
|
||||
Теперь runtime layer способен самостоятельно инициировать runtime actions и выполнять autonomous exits.
|
||||
|
||||
---
|
||||
|
||||
8. Execution Supervisor Layer
|
||||
|
||||
Реализован полноценный execution supervisor.
|
||||
|
||||
Добавлены:
|
||||
|
||||
* emergency execution halt;
|
||||
* execution cooldown after loss;
|
||||
* degraded market execution blocking;
|
||||
* stale snapshot blocking;
|
||||
* signal conflict protection.
|
||||
|
||||
Теперь supervisor умеет блокировать execution в опасных market regimes.
|
||||
|
||||
---
|
||||
|
||||
9. Adaptive Runtime Risk Layer
|
||||
|
||||
Существенно расширен adaptive sizing engine.
|
||||
|
||||
Теперь adaptive risk учитывает:
|
||||
|
||||
* execution confidence;
|
||||
* market state;
|
||||
* trend quality;
|
||||
* market phase;
|
||||
* momentum structure;
|
||||
* execution quality;
|
||||
* spread degradation;
|
||||
* snapshot degradation.
|
||||
|
||||
---
|
||||
|
||||
10. Runtime Event & Journal Architecture
|
||||
|
||||
Существенно расширена observability architecture.
|
||||
|
||||
Добавлены runtime events:
|
||||
|
||||
* runtime_position_action
|
||||
* runtime_protection_updated
|
||||
* execution_supervisor_block
|
||||
* paper_flip_blocked
|
||||
* paper_position_closed
|
||||
* paper_position_flipped
|
||||
|
||||
---
|
||||
|
||||
Итог этапа
|
||||
|
||||
После этапа:
|
||||
|
||||
* execution engine стал position-aware;
|
||||
* реализован полноценный runtime risk layer;
|
||||
* система научилась защищать уже открытую позицию;
|
||||
* внедрён autonomous runtime execution;
|
||||
* реализован giveback protection;
|
||||
* реализован time decay analysis;
|
||||
* execution supervisor стал market-aware;
|
||||
* runtime execution стал значительно безопаснее.
|
||||
|
||||
---
|
||||
|
||||
Рекомендуемый commit
|
||||
|
||||
git add .
|
||||
git commit -m "07.4.4.1.12 — Position Health & Runtime Risk Layer"
|
||||
git push origin main
|
||||
Reference in New Issue
Block a user