Execute autonomous protect action

This commit is contained in:
2026-07-03 13:51:40 +03:00
parent 5f1f522fd7
commit 26deb861bc
2 changed files with 131 additions and 5 deletions

View File

@@ -196,6 +196,113 @@ class ExecutionPositionProtectionMixin(_ExecutionPositionProtectionProtocol):
state.position_protection_reason = reason state.position_protection_reason = reason
state.runtime_protection_updated_at = time.monotonic() state.runtime_protection_updated_at = time.monotonic()
# принудительно усилить защиту позиции по запросу autonomous PROTECT
def _force_runtime_protect(
self,
state: AutoTradeState,
*,
reason: str,
) -> bool:
position = type(self)._position
if position.side == "NONE":
return False
try:
current_execution = self._exit_price_for_side(
position.symbol or state.symbol,
position.side,
)
current_price = safe_float(current_execution.price)
if current_price is None or current_price <= 0:
return False
except Exception:
return False
metrics = build_position_metrics(
position,
current_price=current_price,
)
entry_price = safe_float(position.entry_price)
price_move_percent = safe_float(metrics.price_move_percent)
if entry_price is None or entry_price <= 0:
return False
if price_move_percent is None:
return False
# PROTECT не должен ставить защиту, если позиция уже в минусе.
# В минусовой позиции protection-цена может немедленно закрыть сделку
# или создать ложное ощущение защиты.
if price_move_percent <= 0:
return False
changed = False
# 1. Если позиция уже в прибыли, но break-even ещё не включён —
# включаем его сразу, не дожидаясь обычного порога.
if not state.break_even_armed:
buffer_percent = 0.03
if position.side == "LONG":
state.break_even_price = entry_price * (1 + buffer_percent / 100)
elif position.side == "SHORT":
state.break_even_price = entry_price * (1 - buffer_percent / 100)
else:
return False
state.break_even_armed = True
changed = True
# 2. Если прибыль уже покрывает хотя бы небольшой запас,
# подтягиваем profit-lock ближе, чем обычные thresholds.
# Это помогает не отдавать маленькую прибыль обратно комиссии/шуму.
if price_move_percent >= 0.25:
lock_distance_percent = 0.18
if position.side == "LONG":
min_lock_price = entry_price * 1.0002
dynamic_lock_price = current_price * (1 - lock_distance_percent / 100)
lock_price = max(min_lock_price, dynamic_lock_price)
previous_price = safe_float(state.profit_lock_price)
if previous_price is None or lock_price > previous_price:
state.profit_lock_active = True
state.profit_lock_price = round(lock_price, 8)
changed = True
elif position.side == "SHORT":
min_lock_price = entry_price * 0.9998
dynamic_lock_price = current_price * (1 + lock_distance_percent / 100)
lock_price = min(min_lock_price, dynamic_lock_price)
previous_price = safe_float(state.profit_lock_price)
if previous_price is None or lock_price < previous_price:
state.profit_lock_active = True
state.profit_lock_price = round(lock_price, 8)
changed = True
if not changed:
return False
state.runtime_protection_action = "FORCED_PROTECT"
state.runtime_protection_reason = reason
state.runtime_protection_updated_at = time.monotonic()
self._log_runtime_protection_event(
state=state,
action="FORCED_PROTECT",
reason=reason,
current_price=current_price,
metrics=metrics,
)
return True
def _update_break_even_protection( def _update_break_even_protection(
self, self,
*, *,

View File

@@ -59,6 +59,14 @@ class _ExecutionRuntimeActionsProtocol(Protocol):
) -> ExecutionDecision: ) -> ExecutionDecision:
... ...
def _force_runtime_protect(
self,
state: AutoTradeState,
*,
reason: str,
) -> bool:
...
class ExecutionRuntimeActionsMixin(_ExecutionRuntimeActionsProtocol): class ExecutionRuntimeActionsMixin(_ExecutionRuntimeActionsProtocol):
# ----- Runtime autonomous actions subsystem. # ----- Runtime autonomous actions subsystem.
@@ -129,15 +137,26 @@ class ExecutionRuntimeActionsMixin(_ExecutionRuntimeActionsProtocol):
return ExecutionDecision(EXECUTION_ACTION_NONE, False, skip_reason) return ExecutionDecision(EXECUTION_ACTION_NONE, False, skip_reason)
if action == AUTONOMOUS_ACTION_PROTECT: if action == AUTONOMOUS_ACTION_PROTECT:
# Пока PROTECT только логируется. protect_reason = reason or "позиция требует защиты"
# Следующим этапом можно сделать его реальным действием:
# например, принудительно подтягивать break-even / profit-lock. # Теперь PROTECT — это не только лог.
# Если позиция уже в плюсе, protection layer принудительно включает
# break-even и при достаточной прибыли подтягивает profit-lock.
protected = self._force_runtime_protect(
state,
reason=protect_reason,
)
return self._log_runtime_action( return self._log_runtime_action(
state=state, state=state,
action=AUTONOMOUS_ACTION_PROTECT, action=AUTONOMOUS_ACTION_PROTECT,
reason=reason or "позиция требует защиты", reason=(
protect_reason
if protected
else f"{protect_reason}; protection не применён"
),
confidence=confidence, confidence=confidence,
executed=False, executed=protected,
) )
if action == AUTONOMOUS_ACTION_REDUCE: if action == AUTONOMOUS_ACTION_REDUCE: