SPB Git

spb/wp27_uqo

Public
13.4 KB · 226 lines python
1"""LaTeX table fragments from saved outcomes (results/tables/*.tex). Every number comes from2results/runs/<design>/outcomes.parquet or the run manifests; nothing is typed by hand."""3from __future__ import annotations45import json6import sys7from pathlib import Path89import numpy as np10import pandas as pd1112ROOT = Path(__file__).resolve().parents[1]13sys.path.insert(0, str(ROOT / "src"))14from market_agents.analysis.estimators import dose_response, ols_seed_fe, paired_effects, holm  # noqa: E4021516RUNS, OUT = ROOT / "results/runs", ROOT / "results/tables"17OUT.mkdir(parents=True, exist_ok=True)18PRIMARY = ["msd_log", "mad_pct", "time_to_absorb", "response_ratio", "spread_bp", "eff_spread_bp", "depth5", "lambda_bp_100",19           "n_bubbles", "bubble_amp", "max_dd", "n_crashes", "acf_absr_1_10", "kurtosis"]20NICE = {"msd_log": "Sq. log pricing error ($\\times 10^{4}$)", "mad_pct": "Mean $|P/V-1|$ (\\%)", "time_to_absorb": "Time to absorb (ticks)",21        "response_ratio": "Response ratio (20 ticks)", "spread_bp": "Quoted spread (bp)", "eff_spread_bp": "Effective spread (bp)",22        "depth5": "Depth, 5 levels (lots)", "lambda_bp_100": "Price impact (bp/100 lots)", "n_bubbles": "Bubble episodes",23        "bubble_amp": "Bubble amplitude", "max_dd": "Max drawdown", "n_crashes": "Crashes", "acf_absr_1_10": "Vol. clustering $\\sum_{1}^{10}\\mathrm{ACF}|r|$",24        "kurtosis": "Excess kurtosis", "vr5": "Variance ratio (5)", "acf_r_1": "ACF$_1$ of returns", "vol_bp": "Tick volatility (bp)",25        "llm_valid_rate": "LLM valid-decision rate", "llm_synchrony": "LLM synchrony", "llm_pairwise_corr": "LLM order-flow corr.",26        "llm_usd": "API cost per run (USD)", "llm_share_volume": "Realized LLM volume share", "llm_share_orders": "Realized LLM order share",27        "gini_final": "Wealth Gini (final)", "n_bankrupt": "Bankruptcies", "n_halts": "Halts", "excess_comovement": "Excess comovement",28        "spillover_dy": "DY spillover (\\%)", "ret_corr": "Return correlation", "fund_corr": "Fundamental correlation", "pnl_market_maker": "MM P\\&L (\\%)"}29SCALE = {"msd_log": 1e4}303132def fmt(x, nd=2):33    if x is None or (isinstance(x, float) and not np.isfinite(x)):34        return "--"35    return f"{x:.{nd}f}"363738def star(p):39    return "" if not np.isfinite(p) else ("$^{***}$" if p < 0.01 else "$^{**}$" if p < 0.05 else "$^{*}$" if p < 0.10 else "")404142def load(design):43    p = RUNS / design / "outcomes.parquet"44    if not p.exists():45        print(f"  [skip] {design}")46        return None47    df = pd.read_parquet(p)48    df["cell"] = df["name"].str.split("/").str[-1]49    return df505152def write(name: str, body: str):53    (OUT / name).write_text(body)54    print("  wrote", name)555657def tab_stylized_facts():58    df = load("classical_confirmatory")59    if df is None:60        return61    d = df[df.cell == "control"]62    rows = [("acf_r_1", 3), ("vr5", 2), ("kurtosis", 1), ("acf_absr_1_10", 2), ("vol_bp", 1), ("spread_bp", 1), ("eff_spread_bp", 1), ("depth5", 0),63            ("lambda_bp_100", 1), ("mad_pct", 2), ("n_bubbles", 2), ("max_dd", 3), ("pnl_market_maker", 1), ("gini_final", 3)]64    lines = ["\\begin{tabular}{lrrrr}", "\\toprule", "Statistic & Mean & SD & Min & Max \\\\", "\\midrule"]65    for k, nd in rows:66        if k in d:67            v = d[k].astype(float)68            lines.append(f"{NICE.get(k, k)} & {fmt(v.mean(), nd)} & {fmt(v.std(ddof=1), nd)} & {fmt(v.min(), nd)} & {fmt(v.max(), nd)} \\\\")69    lines += ["\\midrule", f"Runs (seeds) & {len(d)} & & & \\\\", "\\bottomrule", "\\end{tabular}"]70    write("tab_stylized_facts.tex", "\n".join(lines))71    # scale benchmark72    p = ROOT / "results/calibration/scale_benchmark.json"73    if p.exists():74        sb = pd.DataFrame(json.load(open(p)))75        g = sb.groupby("n_traders").agg(wall=("wall_s", "mean"), trades=("n_trades", "mean"), orders=("n_orders", "mean"), mad=("mad_pct", "mean"),76                                       acf=("acf_r_1", "mean"), kurt=("kurtosis", "mean"), spread=("spread_bp", "mean"), n=("seed", "size"))77        lines = ["\\begin{tabular}{rrrrrrrr}", "\\toprule", "Traders & Seeds & Wall-clock (s) & Orders & Trades & Mean $|P/V-1|$ (\\%) & ACF$_1$ & Spread (bp) \\\\", "\\midrule"]78        for n, r in g.iterrows():79            lines.append(f"{n:,} & {int(r.n)} & {fmt(r.wall, 1)} & {int(r.orders):,} & {int(r.trades):,} & {fmt(r.mad, 2)} & {fmt(r.acf, 2)} & {fmt(r.spread, 1)} \\\\")80        lines += ["\\bottomrule", "\\end{tabular}"]81        write("tab_scale.tex", "\n".join(lines))828384def tab_cells(design: str, cells: list[str], labels: dict, outcomes: list[str], name: str, control: str):85    df = load(design)86    if df is None:87        return88    cells = [c for c in cells if c in set(df.cell)]89    outcomes = [o for o in outcomes if o in df]90    lines = ["\\begin{tabular}{l" + "r" * len(outcomes) + "}", "\\toprule",91             "Cell & " + " & ".join(NICE.get(o, o) for o in outcomes) + " \\\\", "\\midrule"]92    ctrl = df[df.cell == control]93    for c in cells:94        d = df[df.cell == c]95        vals = []96        for o in outcomes:97            sc = SCALE.get(o, 1.0)98            m = d[o].astype(float).mean() * sc99            if c == control:100                vals.append(fmt(m))101            else:102                e = paired_effects(df, o, "cell", c, control)103                vals.append(f"{fmt(m)}{star(e.get('p', np.nan))}")104        lines.append(f"{labels.get(c, c)} & " + " & ".join(vals) + " \\\\")105    lines += ["\\midrule", f"Seeds per cell & {len(ctrl)}" + " &" * (len(outcomes) - 1) + " \\\\", "\\bottomrule", "\\end{tabular}"]106    write(name, "\n".join(lines))107108109def tab_effects(design: str, cells: list[str], labels: dict, outcomes: list[str], name: str, control: str, holm_family=True):110    """Paired effects vs control with 95 % CI and Holm-adjusted p within the primary family."""111    df = load(design)112    if df is None:113        return114    cells = [c for c in cells if c in set(df.cell) and c != control]115    outcomes = [o for o in outcomes if o in df]116    rows = []117    for c in cells:118        for o in outcomes:119            e = paired_effects(df, o, "cell", c, control)120            e["cell"] = c121            rows.append(e)122    t = pd.DataFrame(rows)123    if "p" not in t:124        return125    t["p_holm"] = np.nan126    for c in cells:127        m = t.cell == c128        t.loc[m, "p_holm"] = holm(t.loc[m, "p"].to_numpy())129    t.to_csv(OUT / name.replace(".tex", ".csv"), index=False)130    lines = ["\\begin{tabular}{llrrrrr}", "\\toprule", "Cell & Outcome & Control & Effect & 95\\% CI & $d$ & $p$ (Holm) \\\\", "\\midrule"]131    for c in cells:132        for i, o in enumerate(outcomes):133            r = t[(t.cell == c) & (t.outcome == o)].iloc[0]134            sc = SCALE.get(o, 1.0)135            lab = labels.get(c, c) if i == 0 else ""136            lines.append(f"{lab} & {NICE.get(o, o)} & {fmt(r.control_mean * sc)} & {fmt(r['diff'] * sc)}{star(r.p_holm)} & "137                         f"[{fmt(r.ci_lo * sc)}, {fmt(r.ci_hi * sc)}] & {fmt(r.cohen_d)} & {fmt(r.p_holm, 3)} \\\\")138        lines.append("\\addlinespace")139    lines += ["\\bottomrule", "\\end{tabular}"]140    write(name, "\n".join(lines))141142143def tab_dose(design: str, name: str):144    df = load(design)145    if df is None or "tr_arm" not in df:146        return147    d = df[df.tr_arm == "dose"].copy()148    d["share"] = d.tr_share.astype(float)149    outs = [o for o in ("msd_log", "mad_pct", "time_to_absorb", "spread_bp", "lambda_bp_100", "n_bubbles", "max_dd", "acf_absr_1_10", "llm_pairwise_corr", "llm_valid_rate") if o in d]150    levels = sorted(d.share.unique())151    lines = ["\\begin{tabular}{l" + "r" * len(levels) + "}", "\\toprule", "Outcome & " + " & ".join(f"{int(l * 100)}\\%" for l in levels) + " \\\\", "\\midrule"]152    for o in outs:153        sc = SCALE.get(o, 1.0)154        dr = None155        if o not in ("llm_pairwise_corr", "llm_valid_rate") and 0.0 in levels:156            dr = dose_response(d, o, "share", 0.0).set_index("level")157            if "p_holm" not in dr:158                dr = None159        cells = []160        for l in levels:161            m = d[d.share == l][o].astype(float).mean() * sc162            s = "" if (dr is None or l == 0.0 or l not in dr.index) else star(dr.loc[l, "p_holm"])163            cells.append(f"{fmt(m)}{s}")164        lines.append(f"{NICE.get(o, o)} & " + " & ".join(cells) + " \\\\")165    rs = d.groupby("share")[["llm_share_traders", "llm_share_orders", "llm_share_volume", "llm_usd"]].mean()166    lines.append("\\midrule")167    for k, lab in (("llm_share_traders", "Realized share of traders"), ("llm_share_orders", "Realized share of orders"), ("llm_share_volume", "Realized share of volume"), ("llm_usd", "API cost per run (USD)")):168        lines.append(f"{lab} & " + " & ".join(fmt(rs.loc[l, k], 3) for l in levels) + " \\\\")169    lines += ["\\midrule", f"Seeds per cell & {int(d.groupby('share').size().min())}" + " &" * (len(levels) - 1) + " \\\\", "\\bottomrule", "\\end{tabular}"]170    write(name, "\n".join(lines))171172173def tab_llm_reliability(design: str, name: str):174    df = load(design)175    if df is None:176        return177    rows = []178    for c, d in df[df.llm_share > 0].groupby("cell"):179        rec = []180        for rd in d.run_dir:181            p = Path(rd) / "llm_decisions.parquet"182            if p.exists():183                rec.append(pd.read_parquet(p))184        if not rec:185            continue186        L = pd.concat(rec)187        rows.append({"cell": c, "n": len(L), "valid": L.valid.mean(), "hold": (L.action == "hold").mean(), "place": (L.action == "place").mean(),188                     "market": (L.order_type == "market").mean(), "json_err": L.reason.str.startswith("json").mean(), "schema_err": L.reason.str.startswith("schema").mean(),189                     "feas_err": L.reason.str.startswith("place").mean(), "api_err": (L.error != "").mean(), "lat": L.latency_s.mean(),190                     "in_tok": L.input_tokens.mean(), "out_tok": L.output_tokens.mean(), "reason_tok": L.reasoning_tokens.mean(), "usd": L.usd.sum() / len(d)})191    if not rows:192        return193    t = pd.DataFrame(rows)194    t.to_csv(OUT / name.replace(".tex", ".csv"), index=False)195    lines = ["\\begin{tabular}{lrrrrrrrrrr}", "\\toprule", "Cell & Decisions & Valid & Hold & Market & JSON err. & Schema err. & Infeasible & API err. & Latency (s) & USD/run \\\\", "\\midrule"]196    for r in t.itertuples():197        lines.append(f"{r.cell.replace('_', '\\_')} & {r.n:,} & {fmt(r.valid, 3)} & {fmt(r.hold, 2)} & {fmt(r.market, 2)} & {fmt(r.json_err, 3)} & {fmt(r.schema_err, 3)} & {fmt(r.feas_err, 3)} & {fmt(r.api_err, 3)} & {fmt(r.lat, 2)} & {fmt(r.usd, 2)} \\\\")198    lines += ["\\bottomrule", "\\end{tabular}"]199    write(name, "\n".join(lines))200201202CLASSICAL_CELLS = ["control", "placebo_shock", "trend_heavy", "leverage_heavy", "leverage_heavy_funding_shock", "liquidity_shock", "info_low", "info_high",203                   "misleading_news", "breaker_5", "poslimit_300", "auction_5", "mock_llm_25_indep", "mock_llm_50_indep"]204CLASSICAL_LAB = {"control": "Control", "placebo_shock": "Placebo shock", "trend_heavy": "Trend followers 16\\%", "leverage_heavy": "Leveraged 19\\%",205                 "leverage_heavy_funding_shock": "Leveraged 19\\% + funding shock", "liquidity_shock": "Market makers withdraw", "info_low": "Signal precision 0.5",206                 "info_high": "Signal precision 8", "misleading_news": "Misleading news 50\\%", "breaker_5": "Circuit breaker 5\\%", "poslimit_300": "Position limit 300",207                 "auction_5": "Call auction (5 ticks)", "mock_llm_25_indep": "Mock LLM 25\\%", "mock_llm_50_indep": "Mock LLM 50\\%"}208ARM_CELLS = ["dose_000", "dose_025", "div_prompts_025", "div_models_025", "memory_025", "channel_025", "adaptive_025", "leverage_025", "breaker_025", "poslimit_025", "fair_mini_005", "fair_latency_025"]209ARM_LAB = {"dose_000": "0\\% (control)", "dose_025": "25\\% single model/prompt", "div_prompts_025": "25\\% diverse prompts", "div_models_025": "25\\% two models",210           "memory_025": "25\\% memory (5)", "channel_025": "25\\% communication channel", "adaptive_025": "25\\% adaptive feedback", "leverage_025": "25\\% + leveraged 19\\%",211           "breaker_025": "25\\% + circuit breaker", "poslimit_025": "25\\% + position limit", "fair_mini_005": "5\\% gpt-5.4-mini (equal USD)", "fair_latency_025": "25\\% matched fast latency"}212213if __name__ == "__main__":214    tab_stylized_facts()215    tab_cells("classical_confirmatory", CLASSICAL_CELLS, CLASSICAL_LAB, ["mad_pct", "time_to_absorb", "spread_bp", "lambda_bp_100", "n_bubbles", "max_dd", "n_crashes", "acf_absr_1_10"], "tab_classical_cells.tex", "control")216    tab_effects("classical_confirmatory", CLASSICAL_CELLS, CLASSICAL_LAB, ["mad_pct", "time_to_absorb", "spread_bp", "n_bubbles", "max_dd"], "tab_classical_effects.tex", "control")217    tab_cells("classical_confirmatory", ["k2_control", "k2_leverage"], {"k2_control": "Two assets", "k2_leverage": "Two assets + leveraged 19\\% + funding shock"},218              ["ret_corr", "fund_corr", "excess_comovement", "spillover_dy", "mad_pct", "max_dd"], "tab_contagion.tex", "k2_control")219    tab_dose("pilot_openai", "tab_dose_pilot.tex")220    tab_dose("confirmatory_openai", "tab_dose_conf.tex")221    tab_cells("confirmatory_openai", ARM_CELLS, ARM_LAB, ["mad_pct", "time_to_absorb", "spread_bp", "n_bubbles", "max_dd", "llm_pairwise_corr", "llm_synchrony", "llm_valid_rate"], "tab_arms_conf.tex", "dose_025")222    tab_effects("confirmatory_openai", ARM_CELLS, ARM_LAB, ["mad_pct", "time_to_absorb", "spread_bp", "n_bubbles", "max_dd"], "tab_arms_effects.tex", "dose_025")223    tab_llm_reliability("pilot_openai", "tab_llm_reliability_pilot.tex")224    tab_llm_reliability("confirmatory_openai", "tab_llm_reliability_conf.tex")225    print("tables in", OUT)226
Files · d87f8d8bd9679faee8ccd55c141f50c64de63705
Menu

Theme