diagnosability: gate first-failure tally + BH no-pass message says why

validate_timing: the holder gate went 37->34->14->0 across runs and the
log said only '0 copy-positive holders' — which condition bit took an
hour of hand queries to reconstruct. Now every run prints the
first-failure breakdown (today: inactive_30d 48, PASS 34, copy_pnl<=0
20, trust_roi<=0 7, trust_wr<55% 3).

skill.py: a BH threshold of 0.0 is NOT a broken bar — it is 'nobody
cleared FDR'. With 1,207 simultaneous tests the rank-1 bar is p<=4.1e-5
and the best wallet is 1.7e-4, so a z~3 record is no longer
distinguishable from luck. The message now says that with the numbers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
jaxperro
2026-08-02 21:54:49 -04:00
parent b79dd7c4cc
commit 6a27f79df6
3 changed files with 1666 additions and 5 deletions
+13 -1
View File
@@ -148,7 +148,19 @@ def main():
json.dump(rows, open(os.path.join(HERE, OUT.replace(".json", "_scored.json")), "w"))
val = sum(1 for r in skilled if r["tier"] == "validated")
print(f"\nscored {len(rows):,} wallets · BH@{int(FDR_Q*100)}% threshold p<= {thresh:.1e}")
# A zero threshold is NOT a broken bar — it is BH reporting that nothing
# cleared FDR. Say so, with the numbers that make it checkable (2026-08-02:
# "p<= 0.0e+00" read like a defect and cost an hour of forensics).
if thresh > 0:
print(f"\nscored {len(rows):,} wallets · BH@{int(FDR_Q*100)}% "
f"threshold p<= {thresh:.1e}")
else:
_ps = sorted(r["pval"] for r in rows) or [float("nan")]
print(f"\nscored {len(rows):,} wallets · BH@{int(FDR_Q*100)}%: "
f"NOBODY CLEARED FDR — best p={_ps[0]:.2e} vs the rank-1 bar "
f"p<={FDR_Q/max(1,len(rows)):.2e}. Not a bug: with "
f"{len(rows):,} simultaneous tests the bar tightens, so a "
f"z~3 wallet is no longer distinguishable from luck.")
print(f"SKILLED: {len(skilled)} ({val} validated OOS, {len(skilled)-val} candidate) "
f"-> watch_skilled.json\n")
hdr = f"{'tier':>10}{'z':>6}{'z_oos':>6}{'rec':>12}{'win%':>6}{'avgP':>6}{'0.2-0.4':>8} wallet"
+19 -3
View File
@@ -28,6 +28,7 @@ copy_pnl (fees, mirror exits) remains the other selection leg, and held stats
are still computed for display.
"""
import collections
import json
import os
import ssl
@@ -376,6 +377,7 @@ def main():
stats = list(ex.map(safe_stats, conv))
cut30 = time.time() - 30 * 86400
sharps = []
gate_fails = collections.Counter()
for c, ds in zip(conv, stats):
if ds is None:
continue
@@ -397,12 +399,26 @@ def main():
# clear majority with positive flat-stake ROI on a real sample, so the edge
# survives live latency and isn't longshot variance or all sell-timing. A
# light lead floor drops true sub-hour snipers.
if ((ds["last_trade"] or 0) >= cut30 and ds["copy_pnl"] > 0
and tr["n"] >= MIN_HELD and tr["wr"] >= MIN_HELD_WR and tr["roi"] > 0
and (c["med_lead_h"] is None or c["med_lead_h"] >= MIN_LEAD_H)):
# FIRST-FAILURE TALLY (2026-08-02): the gate went 37->34->14->0 across
# runs and the log said only "0 copy-positive holders" — reconstructing
# WHICH condition bit took an hour of hand queries. Count them here so
# any future zero explains itself in daily.log.
checks = (("inactive_30d", (ds["last_trade"] or 0) >= cut30),
("copy_pnl<=0", ds["copy_pnl"] > 0),
(f"trust_n<{MIN_HELD}", tr["n"] >= MIN_HELD),
(f"trust_wr<{MIN_HELD_WR:.0%}", tr["wr"] >= MIN_HELD_WR),
("trust_roi<=0", tr["roi"] > 0),
(f"lead<{MIN_LEAD_H}h", c["med_lead_h"] is None
or c["med_lead_h"] >= MIN_LEAD_H))
failed = [name for name, ok in checks if not ok]
if failed:
gate_fails[failed[0]] += 1
else:
gate_fails["PASS"] += 1
sharps.append(c)
sharps.sort(key=lambda c: c["copy_pnl"], reverse=True)
print(f" gate first-failure: {dict(gate_fails.most_common())}")
print(f"copy-positive holders (copy>0, trust_n>={MIN_HELD}, trust_wr>={MIN_HELD_WR:.0%}, "
f"trust_roi>0 over {TRUST_DAYS}d, active, lead>={MIN_LEAD_H}h): "
f"{len(sharps)} of {len(conv)}\n")
+1634 -1
View File
File diff suppressed because it is too large Load Diff