fix: harden post-stop reentry lockdown

This commit is contained in:
2026-07-17 14:17:57 +02:00
parent 1e9f2dc4fb
commit bc50ba9136
13 changed files with 318 additions and 81 deletions
+10 -10
View File
@@ -1356,7 +1356,7 @@ def _simulate_portfolio(
max_positions: int = SIM_MAX_POSITIONS,
risk_per_trade: float = SIM_RISK_PER_TRADE,
atr_trail_multiplier: float = ATR_TRAIL_MULTIPLIER,
reentry_cooldown_days: int = 0,
reentry_cooldown_sessions: int = 0,
initial_stop_refresh_fn: (
Callable[[str, int, float, dict, Any], float | None] | None
) = None,
@@ -1380,7 +1380,7 @@ def _simulate_portfolio(
runs the ATR trail *and* the S/R take-profit together — the trade ends at
whichever comes first. Stops fill at the worse of stop or open (gaps
modeled); positions still open at the end are closed at their last mark.
``reentry_cooldown_days`` blocks a ticker for that many market sessions
``reentry_cooldown_sessions`` blocks a ticker for that many market sessions
after an initial-stop loss. Profitable trailing-stop exits do not trigger
it. ``initial_stop_refresh_fn`` may supply a lower, point-in-time valid long
stop when the active initial stop is touched; the replacement is still
@@ -1541,7 +1541,7 @@ def _simulate_portfolio(
def _marked_equity() -> float:
return cash + sum(p["shares"] * p["last_close"] for p in positions.values())
cooldown_days = max(0, int(reentry_cooldown_days))
cooldown_sessions = max(0, int(reentry_cooldown_sessions))
for calendar_index, o in enumerate(calendar):
# 1) exits on today's bars (stop intraday, target intraday, time at close)
for sym in list(positions):
@@ -1579,8 +1579,8 @@ def _simulate_portfolio(
if not survived_refresh:
fill = min(pos["stop"], bar.open)
closed_pos = _close_trade(sym, fill, reason)
if reason == "stop" and cooldown_days:
cooldown_until_index[sym] = calendar_index + cooldown_days
if reason == "stop" and cooldown_sessions:
cooldown_until_index[sym] = calendar_index + cooldown_sessions
if reason == "stop" and post_stop_reentry_fn is not None:
post_stop_events += 1
post_stop_states[sym] = {
@@ -1833,8 +1833,8 @@ def _simulate_portfolio(
result["equity_curve"] = curve_payload
if benchmark_payload is not None:
result["benchmark_curve"] = benchmark_payload
if cooldown_days:
result["reentry_cooldown_days"] = cooldown_days
if cooldown_sessions:
result["reentry_cooldown_sessions"] = cooldown_sessions
result["skipped_cooldown"] = skipped_cooldown
if initial_stop_refresh_fn is not None:
result["stop_refresh_attempts"] = stop_refresh_attempts
@@ -2403,7 +2403,7 @@ def _min_rr_sweep(
max_positions=int(entry_cfg["max_positions"]),
risk_per_trade=float(entry_cfg["risk_per_trade"]),
atr_trail_multiplier=trail_multiplier,
reentry_cooldown_days=reentry_lockdown_sessions,
reentry_cooldown_sessions=reentry_lockdown_sessions,
start_date=sweep_start,
)
if sim is None:
@@ -2516,7 +2516,7 @@ def _holdout_evaluation(
max_positions=int(entry_cfg["max_positions"]),
risk_per_trade=float(entry_cfg["risk_per_trade"]),
atr_trail_multiplier=trail_multiplier,
reentry_cooldown_days=reentry_lockdown_sessions,
reentry_cooldown_sessions=reentry_lockdown_sessions,
start_date=start,
end_date=end,
include_curve=True,
@@ -2592,7 +2592,7 @@ def _portfolio_monitor(
max_positions=int(entry_cfg["max_positions"]),
risk_per_trade=float(entry_cfg["risk_per_trade"]),
atr_trail_multiplier=trail_multiplier,
reentry_cooldown_days=reentry_lockdown_sessions,
reentry_cooldown_sessions=reentry_lockdown_sessions,
start_date=start,
include_curve=True,
)