contrib: clboss-xrebalance-survival splits curves by regime sample count
Some checks are pending
Code Base Sanity Check / tests (push) Waiting to run
Code Base Sanity Check / coverage (push) Waiting to run
Code Base Sanity Check / build-clang (push) Waiting to run

Both P(changed|...) curves -- the absolute-gap cap curve and the gap/span
frac curve -- now break each bucket's contradiction rate into nN sub-columns:
the rate among regimes backed by 2, 3-5, and 6+ observations (n2 / n3-5 / n6+).

The horizon (min(cap, frac*span)) extrapolates from a regime's span no matter
how many points define it, gated only by min-samples=2. So a flat headline
rate can hide thin two-observation regimes -- the ones a longer frac or cap
trusts the most -- misbehaving. The sub-columns surface that before lengthening
either knob. A trailing ~ marks a sub-rate with fewer than 30 trials.

Adds nrec (regime record count) to each replayed trial and a sample_bin helper;
the curve aggregator now tracks per-bucket sub-bin trials and contradictions.
Other tables are unchanged.
This commit is contained in:
Ken Sedgwick 2026-06-14 15:24:52 -07:00
parent b90d366b8e
commit fb226dd419
No known key found for this signature in database
GPG key ID: DBD2AF0849D711A9

View file

@ -28,6 +28,13 @@ Outputs:
4. P(changed | gap/span) -- tests the frac*span rule itself: if
this curve is cleaner than (3), frac is the knob that matters;
if (3) dominates, the cap is.
Both (3) and (4) split each bucket's rate into nN sub-columns:
the contradiction rate among regimes backed by N observations
(n2 / n3-5 / n6+). The horizon extrapolates from span no matter
how many points define it, so a flat headline rate can still
hide thin 2-observation regimes misbehaving -- the sub-columns
expose that before you lengthen frac or the cap. ('~' marks a
sub-rate with < 30 trials.)
5. policy-fail walls vs liquidity walls (inbound-fee exclusions
should survive far longer).
6. visit-frequency terciles for the long-gap buckets (the tail is
@ -103,6 +110,24 @@ REL_BUCKETS = [
(float("inf"), ">8x"),
]
# Sub-bins for the P(changed|...) curves: split each gap bucket's
# rate by how many observations back the regime (and hence its span).
# The horizon extrapolates from span regardless of point count, so a
# flat headline rate can hide thin 2-observation regimes misbehaving.
SAMPLE_BINS = [
(2, "n2"),
(5, "n3-5"),
(INF, "n6+"),
]
SAMPLE_HDR = tuple(label for _, label in SAMPLE_BINS)
def sample_bin(nrec):
for limit, label in SAMPLE_BINS:
if nrec <= limit:
return label
return SAMPLE_BINS[-1][1]
def bucket_of(value, buckets):
for limit, label in buckets:
@ -191,6 +216,7 @@ def replay(rows, frac, cap, min_samples):
yield {
"gap": gap,
"span": span,
"nrec": reg["nfail"] + reg["nok"],
"contradiction": contradiction,
"side": "drain" if isf else "refill",
"asserting": asserting,
@ -314,22 +340,32 @@ def main():
agg = {}
for t in trial_list:
b = keyfn(t)
a = agg.setdefault(b, [0, 0, 0, 0])
a[0] += 1
a = agg.setdefault(b, {"tot": [0, 0, 0, 0], "smp": {}})
a["tot"][0] += 1
if t["contradiction"]:
a[1] += 1
a[2 if t["side"] == "refill" else 3] += 1
a["tot"][1] += 1
a["tot"][2 if t["side"] == "refill" else 3] += 1
sb = a["smp"].setdefault(sample_bin(t["nrec"]), [0, 0])
sb[0] += 1
if t["contradiction"]:
sb[1] += 1
rows = []
for limit, label in buckets:
if label not in agg:
continue
n, ch, re, dr = agg[label]
rows.append((label, n, ch, rate(ch, n), re, dr))
n, ch, re, dr = agg[label]["tot"]
smp = agg[label]["smp"]
subs = []
for _, sbl in SAMPLE_BINS:
stot, sch = smp.get(sbl, [0, 0])
subs.append(rate(sch, stot))
rows.append((label, n, ch, rate(ch, n), *subs, re, dr))
return rows
print(fmt_table(
"P(changed | gap) [the cap-setting curve]",
("gap", "trials", "changed", "rate", "refill", "drain"),
("gap", "trials", "changed", "rate") + SAMPLE_HDR
+ ("refill", "drain"),
curve(curve_trials, ABS_BUCKETS,
lambda t: bucket_of(t["gap"], ABS_BUCKETS))))
@ -339,7 +375,8 @@ def main():
print(fmt_table(
"P(changed | gap/span) [tests the frac*span rule;"
" %d no-span trials excluded]" % nospan,
("gap/span", "trials", "changed", "rate", "refill", "drain"),
("gap/span", "trials", "changed", "rate") + SAMPLE_HDR
+ ("refill", "drain"),
curve(spanned, REL_BUCKETS,
lambda t: bucket_of(t["gap"] / t["span"],
REL_BUCKETS))))