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:
1 parent
7a28eafeff
commit
ab2810896c
4 files changed
+63
No files matched your search
@@ -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
|
||||
|
||||
Reference in new issue
Block a user