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:
@@ -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":
|
||||
|
||||
Reference in New Issue
Block a user