mirror of
https://github.com/ZmnSCPxj/clboss.git
synced 2026-08-13 12:33:20 +02:00
contrib: clboss-xrebalance-survival defaults frac/cap/min-samples to live config
The survival replay took --frac/--cap/--min-samples as args defaulting to the compiled 2.0/86400/2, which silently diverged from the live predictor after a setconfig: a no-arg run measured the wrong settings. (Only the wrong-while- asserting headlines and floor sweep are affected -- they gate on frac/cap/ min-samples; the P(changed|...) curve tables are settings-independent.) Default them to the live predictor config instead, read from lightning-cli listconfigs (the clboss-xrebalance-predict-horizon-frac / -horizon-max-secs / -min-samples options), matching how clboss-xrebalance-survey already mirrors live. An explicit arg still wins, for frac x cap what-if grids; with no reachable node the run falls back to the compiled defaults. The effective values and their source (live / arg / default) are printed in a new settings: header line so the run is never ambiguous. LIGHTNING_CLI overrides the executable and may carry flags (e.g. for signet).
This commit is contained in:
parent
fb226dd419
commit
8e5282294e
1 changed files with 83 additions and 9 deletions
|
|
@ -58,7 +58,15 @@ difference is confined to that excluded bucket.
|
|||
|
||||
Usage:
|
||||
clboss-xrebalance-survival /path/to/data.clboss
|
||||
[--frac 2.0] [--cap 86400] [--min-samples 2] [--days N]
|
||||
[--frac F] [--cap SECS] [--min-samples N] [--days N]
|
||||
|
||||
frac / cap / min-samples default to the LIVE predictor config (read
|
||||
from `lightning-cli listconfigs`), so a no-arg run measures what the
|
||||
node is actually doing; pass them explicitly to override (e.g. a
|
||||
frac x cap grid for what-if analysis). With no reachable node they
|
||||
fall back to the compiled defaults (2.0 / 86400 / 2). The effective
|
||||
values + their source are printed in the `settings:` header line.
|
||||
LIGHTNING_CLI overrides the executable/flags (e.g. for signet).
|
||||
|
||||
ALWAYS run against a copy, never the live file: CLBOSS opens its
|
||||
database with no sqlite busy handler (rollback-journal mode, lone-
|
||||
|
|
@ -76,7 +84,11 @@ honest decision statistic, not a hazard curve.
|
|||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shlex
|
||||
import sqlite3
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
INF = 1 << 63
|
||||
|
|
@ -261,20 +273,83 @@ def rate(changed, n):
|
|||
return "%.1f%%%s" % (100.0 * changed / n, flag)
|
||||
|
||||
|
||||
# Compiled fallbacks (match the predictor's spot-check defaults), used
|
||||
# only when no explicit arg is given AND the live config can't be read.
|
||||
DEFAULT_FRAC = 2.0
|
||||
DEFAULT_CAP = 86400
|
||||
DEFAULT_MIN_SAMPLES = 2
|
||||
|
||||
# Live clboss-xrebalance-predict-* option -> survival param name.
|
||||
LIVE_OPTS = {
|
||||
"clboss-xrebalance-predict-horizon-frac": "frac",
|
||||
"clboss-xrebalance-predict-horizon-max-secs": "cap",
|
||||
"clboss-xrebalance-predict-min-samples": "min_samples",
|
||||
}
|
||||
|
||||
|
||||
def read_live_config():
|
||||
"""{frac, cap, min_samples} from `lightning-cli listconfigs`, so a
|
||||
no-arg run mirrors the live predictor instead of the compiled
|
||||
defaults. Returns {} if no node is reachable (offline copy, missing
|
||||
binary, etc.) -- the caller then falls back to the defaults.
|
||||
LIGHTNING_CLI overrides the executable and may carry flags, e.g.
|
||||
LIGHTNING_CLI='lightning-cli --network=signet'."""
|
||||
cli = shlex.split(os.environ.get("LIGHTNING_CLI", "lightning-cli"))
|
||||
try:
|
||||
out = subprocess.run(cli + ["listconfigs"], timeout=10,
|
||||
capture_output=True, text=True)
|
||||
if out.returncode != 0:
|
||||
return {}
|
||||
configs = json.loads(out.stdout).get("configs", {})
|
||||
except (OSError, ValueError, subprocess.SubprocessError):
|
||||
return {}
|
||||
live = {}
|
||||
for opt, name in LIVE_OPTS.items():
|
||||
c = configs.get(opt)
|
||||
if not c:
|
||||
continue
|
||||
v = c.get("value_int", c.get("value_str"))
|
||||
if v is None:
|
||||
continue
|
||||
live[name] = float(v) if name == "frac" else int(v)
|
||||
return live
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(
|
||||
description="xrebalance regime survival calibration")
|
||||
ap.add_argument("db", help="path to (a copy of) data.clboss")
|
||||
ap.add_argument("--frac", type=float, default=2.0,
|
||||
help="horizon frac (default 2.0)")
|
||||
ap.add_argument("--cap", type=int, default=86400,
|
||||
help="horizon max secs (default 86400)")
|
||||
ap.add_argument("--min-samples", type=int, default=2,
|
||||
help="per-side sample gate (default 2)")
|
||||
ap.add_argument("--frac", type=float, default=None,
|
||||
help="horizon frac (default: live config, else 2.0)")
|
||||
ap.add_argument("--cap", type=int, default=None,
|
||||
help="horizon max secs"
|
||||
" (default: live config, else 86400)")
|
||||
ap.add_argument("--min-samples", type=int, default=None,
|
||||
help="per-side sample gate"
|
||||
" (default: live config, else 2)")
|
||||
ap.add_argument("--days", type=int, default=0,
|
||||
help="restrict to the most recent N days")
|
||||
args = ap.parse_args()
|
||||
|
||||
# Resolve frac/cap/min-samples: an explicit arg wins; else mirror
|
||||
# the live predictor via listconfigs; else the compiled fallback.
|
||||
live = read_live_config()
|
||||
|
||||
def resolve(argval, name, default):
|
||||
if argval is not None:
|
||||
return argval, "arg"
|
||||
if name in live:
|
||||
return live[name], "live"
|
||||
return default, "default"
|
||||
|
||||
frac, frac_src = resolve(args.frac, "frac", DEFAULT_FRAC)
|
||||
cap, cap_src = resolve(args.cap, "cap", DEFAULT_CAP)
|
||||
min_samples, ms_src = resolve(
|
||||
args.min_samples, "min_samples", DEFAULT_MIN_SAMPLES)
|
||||
note = "" if live else " [no live node; built-in defaults]"
|
||||
print("settings: frac=%g (%s) cap=%d (%s) min-samples=%d (%s)%s"
|
||||
% (frac, frac_src, cap, cap_src, min_samples, ms_src, note))
|
||||
|
||||
conn = sqlite3.connect("file:%s?mode=ro" % args.db, uri=True)
|
||||
conn.execute("PRAGMA busy_timeout = 5000")
|
||||
q = ('SELECT time, scid, dir, kind, amount_msat'
|
||||
|
|
@ -304,8 +379,7 @@ def main():
|
|||
trials = []
|
||||
visit_rate = {}
|
||||
for key, rows in per_dir.items():
|
||||
for tr in replay(rows, args.frac, args.cap,
|
||||
args.min_samples):
|
||||
for tr in replay(rows, frac, cap, min_samples):
|
||||
tr["dir_key"] = key
|
||||
trials.append(tr)
|
||||
times = sorted(set(r[0] for r in rows))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue