diff --git a/live/daily.sh b/live/daily.sh index 8ad8915c..52574b77 100755 --- a/live/daily.sh +++ b/live/daily.sh @@ -56,6 +56,7 @@ python3 conviction_scan.py python3 validate_timing.py 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_live_floors.py || echo "[daily] live 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. diff --git a/live/sync_live_floors.py b/live/sync_live_floors.py new file mode 100644 index 00000000..32c8055e --- /dev/null +++ b/live/sync_live_floors.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 +"""Sync floors from copybot.paper.json to config.live.json. + +sync_floors.py only updates copybot.paper.json (paper bot). The live bot's +config.live.json floors are never auto-updated (HANDOFF.md: "nothing +auto-writes it"). This script mirrors the paper bot's floors into the live +config so both bots gate on the same conviction thresholds. + +Run after sync_floors.py (daily.sh calls sync_floors.py, then this): + python3 sync_live_floors.py + +Or add to daily.sh after the sync_floors.py line: + python3 sync_live_floors.py || echo "[daily] live floor sync skipped" +""" +import json +import os +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +PAPER = os.path.join(HERE, "copybot.paper.json") +LIVE = os.path.join(HERE, "..", "config.live.json") + + +def main(): + if not os.path.exists(PAPER): + print("[live-floors] copybot.paper.json not found - skipping") + return + if not os.path.exists(LIVE): + print("[live-floors] config.live.json not found - skipping") + return + + paper = json.load(open(PAPER)) + live = json.load(open(LIVE)) + + # Build a map of wallet -> floor from paper config + paper_floors = {} + for w in paper.get("wallets", []): + addr = w.get("wallet", "").lower() + if addr: + paper_floors[addr] = w.get("floor") + + # Also sync floor_pin if present (manual override protection) + paper_pins = {} + for w in paper.get("wallets", []): + addr = w.get("wallet", "").lower() + if addr and w.get("floor_pin") is not None: + paper_pins[addr] = w.get("floor_pin") + + changed = [] + for w in live.get("wallets", []): + addr = w.get("wallet", "").lower() + if addr not in paper_floors: + continue + new_floor = paper_floors[addr] + old_floor = w.get("floor") + if new_floor is None: + continue + if old_floor != new_floor: + changed.append((w.get("name", addr[:10]), old_floor, new_floor)) + w["floor"] = new_floor + # Also mirror floor_pin if paper has one + if addr in paper_pins and w.get("floor_pin") is None: + w["floor_pin"] = paper_pins[addr] + + if changed: + tmp = LIVE + ".tmp" + json.dump(live, open(tmp, "w"), indent=2) + os.replace(tmp, LIVE) + print(f"[live-floors] synced {len(changed)} floor(s) to config.live.json: " + + ", ".join(f"{n} {o}->{v:g}" for n, o, v in changed)) + else: + print("[live-floors] no floor changes to sync") + + +if __name__ == "__main__": + main()