Allow exchange-minimum Kelly layers

This commit is contained in:
Codex
2026-06-27 16:00:31 +03:00
parent 07d132632d
commit 7d84bf38cc
4 changed files with 117 additions and 5 deletions
+52 -2
View File
@@ -631,8 +631,11 @@ def _torch_forecast_entry_signal(
if open_positions_for_symbol >= _dynamic_symbol_position_limit(settings):
return Signal(symbol, "HOLD", 0.0, "torch_forecast: symbol position limit reached")
account_context = dict(account or {})
account_context.setdefault("symbol", symbol)
account_context.setdefault("open_positions_for_symbol", open_positions_for_symbol)
stop_loss_percent = _clamp(settings.stop_loss_percent, 0.003, 0.08)
sizing = _torch_forecast_position_sizing(settings, account, stop_loss_percent, forecast, symbol)
sizing = _torch_forecast_position_sizing(settings, account_context, stop_loss_percent, forecast, symbol)
position_notional = float(sizing["notional_usdt"])
expected_return = _safe_float(forecast.get("expected_return_percent"), 0.0)
probability_up = _safe_float(forecast.get("probability_up"), 0.5)
@@ -1218,7 +1221,28 @@ def _kelly_position(
bankroll = settings.starting_balance_usdt
target_notional = max(0.0, bankroll * effective_fraction)
open_symbol_exposure = _account_symbol_exposure(account, symbol)
remaining_notional = max(0.0, target_notional - open_symbol_exposure)
raw_remaining_notional = max(0.0, target_notional - open_symbol_exposure)
exchange_min_entry = _account_exchange_min_entry(account, settings)
remaining_notional = raw_remaining_notional
effective_target_notional = target_notional
layer_mode = False
if (
symbol
and target_notional > 0
and raw_remaining_notional < exchange_min_entry
and exchange_min_entry > settings.min_position_usdt + 1e-9
and _account_open_positions_for_symbol(account) > 0
):
room = min(
max(0.0, settings.max_position_usdt),
max(0.0, settings.max_symbol_exposure_usdt - open_symbol_exposure),
max(0.0, settings.max_total_exposure_usdt - _account_total_exposure(account)),
max(0.0, _safe_float((account or {}).get("cash"), settings.starting_balance_usdt) - settings.min_cash_reserve_usdt),
)
if room >= exchange_min_entry:
remaining_notional = exchange_min_entry
effective_target_notional = open_symbol_exposure + exchange_min_entry
layer_mode = True
return {
"kelly_probability": round(probability, 4),
"kelly_probability_source": probability_source,
@@ -1231,9 +1255,13 @@ def _kelly_position(
"kelly_effective_fraction": round(effective_fraction, 4),
"kelly_bankroll_usdt": round(bankroll, 2),
"kelly_target_notional_usdt": round(target_notional, 2),
"kelly_effective_target_notional_usdt": round(effective_target_notional, 2),
"kelly_open_symbol_exposure_usdt": round(open_symbol_exposure, 2),
"kelly_raw_remaining_notional_usdt": round(raw_remaining_notional, 2),
"kelly_remaining_notional_usdt": round(remaining_notional, 2),
"kelly_notional_usdt": round(remaining_notional, 2),
"kelly_exchange_min_entry_usdt": round(exchange_min_entry, 2),
"kelly_layer_mode": layer_mode,
}
@@ -1251,6 +1279,28 @@ def _account_symbol_exposure(account: dict | None, symbol: str | None = None) ->
return 0.0
def _account_total_exposure(account: dict | None) -> float:
if not isinstance(account, dict):
return 0.0
return max(0.0, _safe_float(account.get("exposure"), 0.0))
def _account_open_positions_for_symbol(account: dict | None) -> int:
if not isinstance(account, dict):
return 0
try:
return max(0, int(account.get("open_positions_for_symbol", 0)))
except (TypeError, ValueError):
return 0
def _account_exchange_min_entry(account: dict | None, settings: Settings) -> float:
minimum = max(0.0, settings.min_position_usdt)
if not isinstance(account, dict):
return minimum
return max(minimum, _safe_float(account.get("exchange_min_entry_usdt"), minimum))
def _confidence_probability(final_score: float, min_signal_confidence: float) -> float:
denominator = max(0.0001, 1.0 - min_signal_confidence)
ratio = _clamp((final_score - min_signal_confidence) / denominator, 0.0, 1.0)