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:
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user