07.4.4.1.9.6 Adaptive Position Sizing + 07.4.4.1.9.6.1 Market Semantic Layer for Adaptive Sizing

This commit is contained in:
2026-05-12 20:25:10 +03:00
parent 1aa8f6c407
commit 8b83055e6a
12 changed files with 1298 additions and 29 deletions

View File

@@ -92,7 +92,7 @@ class ExecutionEngine:
return ExecutionDecision(
"NONE",
False,
"Позиция не открыта: невозможно рассчитать size без Stop Loss.",
"Позиция не открыта: невозможно рассчитать adaptive size.",
)
size = self._adjust_size_by_margin_limit(
@@ -101,6 +101,12 @@ class ExecutionEngine:
size=size,
)
self._sync_effective_risk_after_margin_limit(
state,
base_size=state.adaptive_size_base or 0.0,
final_size=size,
)
size = self._round_order_size(size)
if size <= 0:
@@ -137,6 +143,16 @@ class ExecutionEngine:
"leverage": state.leverage,
"signal": state.last_signal,
"confidence": state.last_signal_confidence,
"execution_confidence_score": state.execution_confidence_score,
"execution_confidence_level": state.execution_confidence_level,
"execution_confidence_reason": state.execution_confidence_reason,
"adaptive_size_multiplier": state.adaptive_size_multiplier,
"adaptive_size_reason": state.adaptive_size_reason,
"adaptive_size_factors": state.adaptive_size_factors,
"effective_risk_percent": state.effective_risk_percent,
"effective_target_risk_usd": state.effective_target_risk_usd,
"adaptive_size_base": state.adaptive_size_base,
"adaptive_size_final": state.adaptive_size_final,
"repeat_count": state.last_signal_repeat_count,
"reason": state.last_signal_reason,
"opened_at": now,
@@ -186,7 +202,7 @@ class ExecutionEngine:
return ExecutionDecision(
"NONE",
False,
"Flip отменён: невозможно рассчитать size без Stop Loss.",
"Flip отменён: невозможно рассчитать adaptive size.",
)
new_size = self._adjust_size_by_margin_limit(
@@ -195,6 +211,12 @@ class ExecutionEngine:
size=new_size,
)
self._sync_effective_risk_after_margin_limit(
state,
base_size=state.adaptive_size_base or 0.0,
final_size=new_size,
)
new_size = self._round_order_size(new_size)
if new_size <= 0:
@@ -249,6 +271,16 @@ class ExecutionEngine:
"pnl": pnl,
"signal": state.last_signal,
"confidence": state.last_signal_confidence,
"execution_confidence_score": state.execution_confidence_score,
"execution_confidence_level": state.execution_confidence_level,
"execution_confidence_reason": state.execution_confidence_reason,
"adaptive_size_multiplier": state.adaptive_size_multiplier,
"adaptive_size_reason": state.adaptive_size_reason,
"adaptive_size_factors": state.adaptive_size_factors,
"effective_risk_percent": state.effective_risk_percent,
"effective_target_risk_usd": state.effective_target_risk_usd,
"adaptive_size_base": state.adaptive_size_base,
"adaptive_size_final": state.adaptive_size_final,
"repeat_count": state.last_signal_repeat_count,
"reason": state.last_signal_reason,
"opened_at": old_opened_at,
@@ -597,9 +629,11 @@ class ExecutionEngine:
entry_price: float | None = None,
) -> float:
if state.risk_percent is None or state.risk_percent <= 0:
self._sync_adaptive_size_state(state, base_size=0.0, final_size=0.0, multiplier=0.0)
return 0.0
if state.stop_loss_percent is None or state.stop_loss_percent <= 0:
self._sync_adaptive_size_state(state, base_size=0.0, final_size=0.0, multiplier=0.0)
return 0.0
price = entry_price
@@ -608,9 +642,11 @@ class ExecutionEngine:
try:
price = self._signal_entry_price(state).price
except Exception:
self._sync_adaptive_size_state(state, base_size=0.0, final_size=0.0, multiplier=0.0)
return 0.0
if price <= 0:
self._sync_adaptive_size_state(state, base_size=0.0, final_size=0.0, multiplier=0.0)
return 0.0
balance_usd = state.allocated_balance_usd
@@ -618,10 +654,177 @@ class ExecutionEngine:
stop_loss_distance_usd = price * (state.stop_loss_percent / 100)
if stop_loss_distance_usd <= 0:
self._sync_adaptive_size_state(state, base_size=0.0, final_size=0.0, multiplier=0.0)
return 0.0
size = target_risk_usd / stop_loss_distance_usd
return self._round_size(size)
base_size = target_risk_usd / stop_loss_distance_usd
multiplier = self._adaptive_size_multiplier(state)
final_size = base_size * multiplier
self._sync_adaptive_size_state(
state,
base_size=base_size,
final_size=final_size,
multiplier=multiplier,
)
return self._round_size(final_size)
def _adaptive_size_multiplier(self, state: AutoTradeState) -> float:
multiplier = 1.0
execution_confidence_score = getattr(state, "execution_confidence_score", None)
if execution_confidence_score is not None:
score = max(0.0, min(1.0, float(execution_confidence_score)))
if score < 0.55:
multiplier *= 0.0
elif score < 0.65:
multiplier *= 0.65
elif score < 0.75:
multiplier *= 0.85
elif score >= 0.85:
multiplier *= 1.15
market_state = getattr(state, "market_state", None)
market_trend_strength = getattr(state, "market_trend_strength", None)
market_trend_quality = getattr(state, "market_trend_quality", None)
market_phase = getattr(state, "market_phase", None)
if market_state in {"HIGH_VOLATILITY", "LOW_VOLATILITY", "RANGE"}:
multiplier *= 0.65
if market_trend_strength == "STRONG":
multiplier *= 1.1
elif market_trend_strength == "WEAK":
multiplier *= 0.75
if market_trend_quality == "CLEAN":
multiplier *= 1.05
elif market_trend_quality == "NOISY":
multiplier *= 0.75
if market_phase == "IMPULSE":
multiplier *= 1.1
elif market_phase == "PULLBACK":
multiplier *= 0.8
elif market_phase in {"RANGE", "SQUEEZE"}:
multiplier *= 0.7
execution_quality = getattr(state, "execution_quality", None)
execution_quality_reason = getattr(state, "execution_quality_reason", None)
if execution_quality == "BLOCKED":
multiplier *= 0.0
elif execution_quality == "WARNING":
if execution_quality_reason == "WIDE_SPREAD":
multiplier *= 0.75
elif execution_quality_reason == "AGING_SNAPSHOT":
multiplier *= 0.8
elif execution_quality_reason == "SNAPSHOT_UNAVAILABLE":
multiplier *= 0.7
else:
multiplier *= 0.8
return round(max(0.0, min(1.25, multiplier)), 4)
def _sync_adaptive_size_state(
self,
state: AutoTradeState,
*,
base_size: float,
final_size: float,
multiplier: float,
) -> None:
reason = self._adaptive_size_reason(multiplier)
state.adaptive_size_base = self._round_size(base_size)
state.adaptive_size_final = self._round_size(final_size)
state.adaptive_size_multiplier = multiplier
base_risk_percent = float(state.risk_percent or 0.0)
state.effective_risk_percent = round(
base_risk_percent * multiplier,
4,
)
state.effective_target_risk_usd = round(
state.allocated_balance_usd
* (state.effective_risk_percent / 100),
4,
)
state.adaptive_size_reason = reason
state.adaptive_size_factors = {
"execution_confidence_score": getattr(state, "execution_confidence_score", None),
"execution_confidence_level": getattr(state, "execution_confidence_level", None),
"market_state": getattr(state, "market_state", None),
"market_trend_strength": getattr(state, "market_trend_strength", None),
"market_trend_quality": getattr(state, "market_trend_quality", None),
"market_phase": getattr(state, "market_phase", None),
"execution_quality": getattr(state, "execution_quality", None),
"execution_quality_reason": getattr(state, "execution_quality_reason", None),
"spread_percent": getattr(state, "spread_percent", None),
"base_size": self._round_size(base_size),
"final_size": self._round_size(final_size),
"multiplier": multiplier,
}
if multiplier <= 0:
state.execution_size_adjustment_reason = "ADAPTIVE_SIZE_ZERO"
elif multiplier < 1:
state.execution_size_adjustment_reason = "ADAPTIVE_SIZE_REDUCED"
elif multiplier > 1:
state.execution_size_adjustment_reason = "ADAPTIVE_SIZE_INCREASED"
else:
state.execution_size_adjustment_reason = None
def _sync_effective_risk_after_margin_limit(
self,
state: AutoTradeState,
*,
base_size: float,
final_size: float,
) -> None:
adaptive_final = float(state.adaptive_size_final or 0.0)
if adaptive_final <= 0:
state.effective_risk_percent = 0.0
state.effective_target_risk_usd = 0.0
return
margin_ratio = max(
0.0,
min(1.0, final_size / adaptive_final),
)
current_effective_risk = float(state.effective_risk_percent or 0.0)
state.effective_risk_percent = round(
current_effective_risk * margin_ratio,
4,
)
state.effective_target_risk_usd = round(
state.allocated_balance_usd
* (state.effective_risk_percent / 100),
4,
)
def _adaptive_size_reason(self, multiplier: float) -> str:
if multiplier <= 0:
return "adaptive size заблокировал вход"
if multiplier < 0.75:
return "размер позиции сильно уменьшен по risk/runtime факторам"
if multiplier < 1:
return "размер позиции умеренно уменьшен по risk/runtime факторам"
if multiplier > 1:
return "размер позиции увеличен при сильном execution context"
return "размер позиции без adaptive корректировки"
def _adjust_size_by_margin_limit(
self,
@@ -632,9 +835,6 @@ class ExecutionEngine:
) -> 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 self._round_size(size)
@@ -653,7 +853,24 @@ class ExecutionEngine:
return self._round_size(size)
state.execution_size_adjustment_reason = "MARGIN_LIMIT"
return self._round_size(max_size)
limited_size = self._round_size(max_size)
adaptive_final = float(state.adaptive_size_final or 0.0)
if adaptive_final > 0:
effective_multiplier = limited_size / adaptive_final
if effective_multiplier < 0.5:
state.adaptive_size_reason = (
"размер позиции сильно ограничен margin limit"
)
else:
state.adaptive_size_reason = (
"размер позиции ограничен margin limit"
)
return limited_size
def _signal_entry_price(self, state: AutoTradeState) -> _ExecutionPrice:
if state.last_signal == "BUY":