feat: auto-sync floors from paper to live config

sync_floors.py only updates copybot.paper.json (paper bot). The live
bot's config.live.json floors were never auto-updated (HANDOFF.md:
'nothing auto-writes it'), so manually-added wallets kept their
floor forever.

New sync_live_floors.py mirrors paper floors into config.live.json:
- Reads floors (and floor_pin overrides) from copybot.paper.json
- Updates matching wallets in config.live.json
- Only writes if something changed

Added to daily.sh right after sync_floors.py, so both configs stay
in sync every day.
This commit is contained in:
2026-07-21 07:45:24 +08:00
parent 0e12b7ba96
commit 935e04e55c
2 changed files with 77 additions and 0 deletions
+1
View File
@@ -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.
+76
View File
@@ -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()