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