mirror of
https://github.com/jaxperro/winning-wallet-finder.git
synced 2026-07-28 00:07:47 +00:00
audit fixes: pin-mirror to backtest, unpinned-floor warning, watch_sharps diff, class_pct guard
From the automated-writer clobber audit (3-agent workflow): - sync_floors now ACTUALLY mirrors floor_pin into backtest.json (the comment claimed it; the code never did — so a paper pin diverged from the backtest's dynamic p80, defeating calibration). Kruto 80 now consistent across all 3. - sync_floors warns loudly when about to revert an UNPINNED floor that looks hand-edited (>5 and >15%) — floor_pin is the only protected key, so a direct edit used to vanish silently at 08:00. - validate_timing logs the sharps add/drop delta and documents watch_sharps.json as generated-only (format kept a plain list — dashboard/discord read it so). - daily.sh warns if copybot.paper.json vs backtest.json class_pct desync. Audit verdict otherwise CLEAN: sync_floors is the only pipeline step that mutates a manual-knob file, and it round-trips the whole JSON touching only floor — all other knobs (caps, guard, depth_gate, follow set, band) survive. config.live.example.json is untouched by every automated writer (safe by neglect). No second floor_pin-class bug exists. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -52,6 +52,15 @@ python3 conviction_scan.py
|
|||||||
python3 validate_timing.py
|
python3 validate_timing.py
|
||||||
echo "[daily] $(date '+%F %T') 5/7 floors: pin copy-bot p80 conviction floors -> copybot.paper.json"
|
echo "[daily] $(date '+%F %T') 5/7 floors: pin copy-bot p80 conviction floors -> copybot.paper.json"
|
||||||
python3 sync_floors.py || echo "[daily] floor sync skipped"
|
python3 sync_floors.py || echo "[daily] floor sync skipped"
|
||||||
|
# parity guard (2026-07-13 audit): class_pct lives in BOTH copybot.paper.json
|
||||||
|
# and backtest.json with no generator — warn if they silently desync so the
|
||||||
|
# paper book and backtest don't drift apart on sizing.
|
||||||
|
python3 - <<'PY' || true
|
||||||
|
import json
|
||||||
|
p=json.load(open("copybot.paper.json")).get("follow",{}).get("class_pct")
|
||||||
|
b=json.load(open("backtest.json")).get("class_pct")
|
||||||
|
if p!=b: print(f"[daily] ⚠ class_pct DESYNC: paper {p} != backtest {b}")
|
||||||
|
PY
|
||||||
echo "[daily] $(date '+%F %T') portfolio: cache-based \$1k book -> portfolio.json"
|
echo "[daily] $(date '+%F %T') portfolio: cache-based \$1k book -> portfolio.json"
|
||||||
python3 portfolio.py || echo "[daily] portfolio skipped"
|
python3 portfolio.py || echo "[daily] portfolio skipped"
|
||||||
echo "[daily] $(date '+%F %T') calibration: live-book vs model, one row/day"
|
echo "[daily] $(date '+%F %T') calibration: live-book vs model, one row/day"
|
||||||
|
|||||||
@@ -30,6 +30,35 @@ import trust # noqa: E402
|
|||||||
|
|
||||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
PAPER = os.path.join(HERE, "copybot.paper.json")
|
PAPER = os.path.join(HERE, "copybot.paper.json")
|
||||||
|
BACKTEST = os.path.join(HERE, "backtest.json")
|
||||||
|
|
||||||
|
|
||||||
|
def mirror_pins_to_backtest(paper_ws):
|
||||||
|
"""Keep backtest.json's floors on the SAME pins as the paper book so the
|
||||||
|
three books stay comparable (the audit found the code never did this
|
||||||
|
despite the comment claiming it — a floor_pin=80 on Kruto in paper vs a
|
||||||
|
dynamic p80=125.61 in the backtest silently defeated the calibration)."""
|
||||||
|
pins = {w["wallet"].lower(): w["floor_pin"]
|
||||||
|
for w in paper_ws if w.get("floor_pin") is not None}
|
||||||
|
if not pins:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
bt = json.load(open(BACKTEST))
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
touched = 0
|
||||||
|
for w in bt.get("wallets", []):
|
||||||
|
p = pins.get(w["wallet"].lower())
|
||||||
|
if p is not None:
|
||||||
|
w["floor_pin"] = round(float(p), 2)
|
||||||
|
if w.get("floor") != round(float(p), 2):
|
||||||
|
touched += 1
|
||||||
|
w["floor"] = round(float(p), 2)
|
||||||
|
if touched:
|
||||||
|
tmp = BACKTEST + ".tmp"
|
||||||
|
json.dump(bt, open(tmp, "w"), indent=2)
|
||||||
|
os.replace(tmp, BACKTEST)
|
||||||
|
print(f"[floors] mirrored {len(pins)} pin(s) into backtest.json")
|
||||||
|
|
||||||
|
|
||||||
def trusted_p80(wallet, now=None):
|
def trusted_p80(wallet, now=None):
|
||||||
@@ -71,12 +100,19 @@ def main():
|
|||||||
continue # no trusted sized bets — leave as-is
|
continue # no trusted sized bets — leave as-is
|
||||||
new = round(p80, 2)
|
new = round(p80, 2)
|
||||||
old = w.get("floor")
|
old = w.get("floor")
|
||||||
|
# a big UNPINNED gap = a hand edit about to be reverted. floor_pin is
|
||||||
|
# the only protected key; warn loudly so a direct floor edit doesn't
|
||||||
|
# vanish silently at 08:00 (the trap behind the original Kruto bug).
|
||||||
|
if old is not None and abs(old - new) > 5 and abs(old - new) / max(new, 1) > 0.15:
|
||||||
|
print(f"[floors] ⚠ {w.get('name', w['wallet'][:8])} floor {old}→{new} "
|
||||||
|
f"(p80) — was this a manual edit? add \"floor_pin\": {old} to keep it")
|
||||||
w["floor"] = new
|
w["floor"] = new
|
||||||
if old is None or abs((old or 0) - new) > 0.5:
|
if old is None or abs((old or 0) - new) > 0.5:
|
||||||
changed.append((w.get("name", w["wallet"][:8]), old, new))
|
changed.append((w.get("name", w["wallet"][:8]), old, new))
|
||||||
tmp = PAPER + ".tmp"
|
tmp = PAPER + ".tmp"
|
||||||
json.dump(cfg, open(tmp, "w"), indent=2)
|
json.dump(cfg, open(tmp, "w"), indent=2)
|
||||||
os.replace(tmp, PAPER)
|
os.replace(tmp, PAPER)
|
||||||
|
mirror_pins_to_backtest(ws)
|
||||||
print(f"[floors] pinned {len(ws)} trusted-p80 floors to copybot.paper.json"
|
print(f"[floors] pinned {len(ws)} trusted-p80 floors to copybot.paper.json"
|
||||||
+ (f"; changed: " + ", ".join(f"{n} {o}→{v:g}" for n, o, v in changed)
|
+ (f"; changed: " + ", ".join(f"{n} {o}→{v:g}" for n, o, v in changed)
|
||||||
if changed else " (no material change)"))
|
if changed else " (no material change)"))
|
||||||
|
|||||||
+15
-1
@@ -418,7 +418,21 @@ def main():
|
|||||||
f"{(str(c['held_won'])+'-'+str(c['held_lost'])):>9}"
|
f"{(str(c['held_won'])+'-'+str(c['held_lost'])):>9}"
|
||||||
f"{sp:>5.0f}%{ld:>9} {(c.get('name') or c['wallet'][:10])}")
|
f"{sp:>5.0f}%{ld:>9} {(c.get('name') or c['wallet'][:10])}")
|
||||||
|
|
||||||
json.dump(sharps, open(os.path.join(HERE, "watch_sharps.json"), "w"), indent=2)
|
# GENERATED-ONLY file (2026-07-13 audit): regenerated wholesale nightly —
|
||||||
|
# any hand edit here is silently clobbered. Manual follow decisions live
|
||||||
|
# in copybot.paper.json, not here. Log the delta vs the prior file so a
|
||||||
|
# replacement always leaves an audit trail in daily.log. Format stays a
|
||||||
|
# plain list (the dashboard + discord_daily read it as one).
|
||||||
|
path = os.path.join(HERE, "watch_sharps.json")
|
||||||
|
try:
|
||||||
|
prev = {s.get("wallet") for s in json.load(open(path))}
|
||||||
|
cur = {s.get("wallet") for s in sharps}
|
||||||
|
if cur - prev or prev - cur:
|
||||||
|
print(f" sharps Δ: +{len(cur - prev)} {[w[:10] for w in cur - prev]} "
|
||||||
|
f"· -{len(prev - cur)} {[w[:10] for w in prev - cur]}")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
json.dump(sharps, open(path, "w"), indent=2)
|
||||||
print(f"\n-> watch_sharps.json ({len(sharps)} copy-positive holders)")
|
print(f"\n-> watch_sharps.json ({len(sharps)} copy-positive holders)")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user