1"""`ma` command line: run one config, run a design, analyze, estimate budget."""2from __future__ import annotations34import argparse5import json6import sys7from pathlib import Path8910def main(argv=None) -> None:11 ap = argparse.ArgumentParser(prog="ma")12 sub = ap.add_subparsers(dest="cmd", required=True)13 r = sub.add_parser("run", help="run one configuration")14 r.add_argument("config"); r.add_argument("--seed", type=int); r.add_argument("--out"); r.add_argument("--set", nargs="*", default=[])15 r.add_argument("--cache"); r.add_argument("--progress", action="store_true")16 d = sub.add_parser("design", help="run a design file (cells x seeds)")17 d.add_argument("design"); d.add_argument("--workers", type=int, default=4); d.add_argument("--cache"); d.add_argument("--cells", nargs="*")18 d.add_argument("--max-seeds", type=int); d.add_argument("--root", default="results/runs")19 b = sub.add_parser("budget", help="estimate API calls and USD for a design")20 b.add_argument("design"); b.add_argument("--tokens-in", type=int, default=800); b.add_argument("--tokens-out", type=int, default=250)21 args = ap.parse_args(argv)22 from dotenv import load_dotenv23 load_dotenv(Path(".env"), override=False)24 if args.cmd == "run":25 from .experiments.config import RunConfig26 from .experiments.runner import run_config27 from .metrics.outcomes import run_outcomes28 ov = {}29 for kv in args.set:30 k, v = kv.split("=", 1)31 ov[k] = json.loads(v) if v[:1] in "[{\"0123456789-tfn" and v not in ("true", "false") else v32 if v in ("true", "false"):33 ov[k] = v == "true"34 cfg = RunConfig.from_yaml(args.config, **ov)35 if args.seed is not None:36 cfg = cfg.with_overrides(seed=args.seed)37 res = run_config(cfg, out_dir=args.out, cache_path=args.cache, progress=args.progress)38 oc = run_outcomes(res)39 print(json.dumps({k: (round(v, 4) if isinstance(v, float) else v) for k, v in oc.items()}, indent=1, default=str))40 elif args.cmd == "design":41 from .experiments.design import run_design42 df = run_design(args.design, args.root, args.workers, args.cache, args.cells, args.max_seeds)43 print(df.groupby("name")[[c for c in ("msd_log", "spread_bp", "n_bubbles", "llm_usd") if c in df]].mean().round(4))44 elif args.cmd == "budget":45 from .experiments.design import cell_config, expand_cells, load_design, seeds_of46 design = load_design(args.design)47 total_calls, total_usd = 0, 0.048 for c in expand_cells(design):49 for s in seeds_of(design):50 cfg = cell_config(design, c, s, Path(args.design).parent)51 p = cfg.population52 n_llm = int(round(p.llm_share * p.n_traders))53 n_dec = (cfg.n_ticks - cfg.warmup // 2) // p.llm.decision_interval54 if p.llm.decision_cap:55 n_dec = min(n_dec, p.llm.decision_cap)56 calls = n_llm * n_dec57 usd = 0.058 for prov, share in p.llm.provider_mix.items():59 model = p.llm.models.get(prov, "")60 pr = p.llm.price_assumptions.get(model, {"input": 0, "output": 0})61 usd += calls * share * (args.tokens_in * pr["input"] + args.tokens_out * pr["output"]) / 1e662 total_calls += calls63 total_usd += usd64 print(f"design {design['name']}: {total_calls} LLM calls, est. ${total_usd:.2f} at {args.tokens_in} in / {args.tokens_out} out tokens per call")656667if __name__ == "__main__":68 main(sys.argv[1:])69