experiment-os: generic exit-config intervention + v1/pm1 exit experiments

iv_exit_config replays ANY target/breakeven/trail combo through the shared exit
engine vs the live-exit baseline — reusable way to adjudicate exit numbers.
Counterfactual over setup fires: v1 (slow, 1.37R median winners) gains most from
prior_low trailing (win 17->24%, +0.036R); pm1 (fast, 2.64R median, 10R tail)
gains most from NO target + step0.5 (+0.273R, win 24->34%) — the fixed target
throws away pm1's fat tail. Registered both as tracked experiments. +test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
momentum devandClaude Fable 5 committed 2026-07-14 22:24:18 -04:00
1 parent 7a28eafeff
commit ab2810896c
4 files changed
+63

No files matched your search

+30
View File
@@ -315,6 +315,34 @@ def _sim_baseline_r(fire, bars_after, fidelity) -> float | None:
return sum(fl.frac * ((fl.price - adv) - entry_fill) / risk for fl in res.fills)
def iv_exit_config(fire, bars_after, params, fidelity):
"""Generic exit-config experiment: run the shared exit engine (simulate_exit)
with an arbitrary ExitConfig from params['exit_cfg'] and measure R vs the LIVE
exit baseline, same adverse-fill accounting. Lets ANY target/breakeven/trail
combination be adjudicated over logged fires without new code — the reusable
way to answer 'does this exit number retain more profit?'."""
from strategy.exits import TRAIL_NONE
risk = fire.entry_price - fire.stop_price
if risk <= 0 or not len(bars_after):
return None, {"kind": "exit_config", "reason": "no_bars_or_risk"}
ec = params.get("exit_cfg", {})
cfg = ExitConfig(
target_r=float(ec.get("target_r", 2.0)),
breakeven_at_r=float(ec.get("breakeven_at_r", 0.0)),
breakeven_at_pct=float(ec.get("breakeven_at_pct", 0.05)),
trail_mode=ec.get("trail_mode", TRAIL_NONE),
trail_pct=float(ec.get("trail_pct", 0.0)),
trail_after_r=float(ec.get("trail_after_r", 1.0)),
trail_r_step=float(ec.get("trail_r_step", 0.0)),
profit_lock_tiers=[], scale_out_r=0.0, first_red_exit=False,
catastrophe_pct=0.0, enforce_stop_grace_passes=0)
adv = _adverse(fidelity)
entry_fill = fire.entry_price + adv
res = simulate_exit(entry_fill, fire.stop_price, bars_after, cfg)
v = sum(fl.frac * ((fl.price - adv) - entry_fill) / risk for fl in res.fills)
return v, {"kind": "exit_config", "cfg": ec, "reason": res.reason}
def iv_volume_fade(fire, bars_after, params, fidelity):
"""VOLUME-FADE exit: exit at market to LOCK a gain when the momentum volume
dies, instead of scratching at breakeven.
@@ -447,6 +475,8 @@ def _compute_fire(con, exp: dict, fire: Fire) -> dict | None:
v, d = iv_asym_exit(con, fire, bars, bars_after, params, fidelity)
elif kind == "volume_fade":
v, d = iv_volume_fade(fire, bars_after, params, fidelity)
elif kind == "exit_config":
v, d = iv_exit_config(fire, bars_after, params, fidelity)
else:
return None
d["fidelity"] = fidelity