07.4.3.13 - Risk-based Sizing and Margin Protection

This commit is contained in:
2026-05-06 16:15:43 +03:00
parent b1513a28ef
commit ee78f9774a
9 changed files with 1142 additions and 194 deletions

View File

@@ -64,7 +64,20 @@ class ExecutionEngine:
return ExecutionDecision("NONE", False, f"Не удалось получить цену для paper execution: {exc}")
now = self._now_time()
size = self._calculate_position_size(state)
size = self._calculate_position_size(state, entry_price=entry_price)
if size <= 0:
return ExecutionDecision(
"NONE",
False,
"Позиция не открыта: невозможно рассчитать size без Stop Loss.",
)
size = self._adjust_size_by_margin_limit(
state=state,
entry_price=entry_price,
size=size,
)
type(self)._position = PositionState(
side=side,
@@ -125,7 +138,20 @@ class ExecutionEngine:
now = self._now_time()
pnl = self._calculate_pnl(flip_price)
new_size = self._calculate_position_size(state)
new_size = self._calculate_position_size(state, entry_price=flip_price)
if new_size <= 0:
return ExecutionDecision(
"NONE",
False,
"Flip отменён: невозможно рассчитать size без Stop Loss.",
)
new_size = self._adjust_size_by_margin_limit(
state=state,
entry_price=flip_price,
size=new_size,
)
old_side = position.side
old_entry_price = position.entry_price
@@ -388,11 +414,74 @@ class ExecutionEngine:
self._sync_state_from_position(state)
def _calculate_position_size(self, state: AutoTradeState) -> float:
risk_percent = state.risk_percent or 0.0
leverage = state.leverage or 1.0
return round((risk_percent * leverage) / 100, 8)
def _calculate_position_size(
self,
state: AutoTradeState,
*,
entry_price: float | None = None,
) -> float:
if state.risk_percent is None or state.risk_percent <= 0:
return 0.0
if state.stop_loss_percent is None or state.stop_loss_percent <= 0:
return 0.0
price = entry_price
if price is None:
try:
ticker = ExchangeService().get_price(state.symbol)
price = ticker.price
except Exception:
return 0.0
if price <= 0:
return 0.0
balance_usd = 1000.0
target_risk_usd = balance_usd * (state.risk_percent / 100)
stop_loss_distance_usd = price * (state.stop_loss_percent / 100)
if stop_loss_distance_usd <= 0:
return 0.0
size = target_risk_usd / stop_loss_distance_usd
return round(size, 8)
def _adjust_size_by_margin_limit(
self,
*,
state: AutoTradeState,
entry_price: float,
size: float,
) -> float:
max_percent = state.max_reserved_balance_percent
state.execution_block_reason = None
state.execution_size_adjustment_reason = None
if max_percent is None or max_percent <= 0:
return round(size, 8)
leverage = state.leverage or 1.0
if leverage <= 0 or entry_price <= 0:
state.execution_block_reason = "Invalid leverage or entry price."
return 0.0
balance_usd = 1000.0
max_reserved_usd = balance_usd * (max_percent / 100)
max_notional_usd = max_reserved_usd * leverage
max_size = max_notional_usd / entry_price
if size <= max_size:
return round(size, 8)
state.execution_size_adjustment_reason = "MARGIN_LIMIT"
return round(max_size, 8)
def _calculate_pnl(self, current_price: float) -> float:
position = type(self)._position