SPB Git

spb/wp27_uqo

Public
7.9 KB · 153 lines python
1"""Generate every figure of the paper from saved outcomes/run artefacts. Each figure is written to2results/figures/ (PDF + PNG) and copied to paper/figures/ by `make tables`. Designs that have not been run3are skipped with a message (no figure is ever drawn from invented numbers)."""4from __future__ import annotations56import json7import sys8from pathlib import Path910import numpy as np11import pandas as pd1213ROOT = Path(__file__).resolve().parents[1]14sys.path.insert(0, str(ROOT / "src"))15from market_agents.analysis.estimators import dose_response, local_projection, paired_effects  # noqa: E40216from market_agents.analysis import plots as P  # noqa: E4021718RUNS = ROOT / "results/runs"19OUT = ROOT / "results/figures"20OUT.mkdir(parents=True, exist_ok=True)21LABELS = {"msd_log": "squared log pricing error", "mad_pct": "mean |P/V − 1|, %", "time_to_absorb": "time to absorb shock (ticks)",22          "response_ratio": "20-tick response ratio", "spread_bp": "quoted spread (bp)", "eff_spread_bp": "effective spread (bp)",23          "depth5": "depth, 5 levels (lots)", "lambda_bp_100": "price impact (bp / 100 lots)", "n_bubbles": "bubble episodes",24          "bubble_amp": "bubble amplitude", "max_dd": "max drawdown", "n_crashes": "crashes", "acf_absr_1_10": "vol. clustering (Σ ACF|r|)",25          "kurtosis": "excess kurtosis", "vr5": "variance ratio (5)", "acf_r_1": "return autocorrelation (lag 1)", "llm_synchrony": "LLM synchrony", "llm_pairwise_corr": "LLM order-flow corr."}262728def load(design: str) -> pd.DataFrame | None:29    p = RUNS / design / "outcomes.parquet"30    if not p.exists():31        print(f"  [skip] {design}: not run yet")32        return None33    df = pd.read_parquet(p)34    df["cell"] = df["name"].str.split("/").str[-1]35    return df363738def ticks_of(run_dir: str) -> pd.DataFrame:39    return pd.read_parquet(Path(run_dir) / "ticks.parquet")404142def fig_baseline_paths():43    df = load("classical_confirmatory")44    if df is None:45        return46    rows = [df[(df.cell == c) & (df.seed == 101)].iloc[0] for c in ("control", "leverage_heavy_funding_shock", "liquidity_shock")]47    fig = P.price_paths([("control", ticks_of(rows[0].run_dir)), ("leverage 19 % + funding shock", ticks_of(rows[1].run_dir)),48                         ("market makers withdraw", ticks_of(rows[2].run_dir))], shock_t=300)49    P.save(fig, OUT / "fig_baseline_paths.pdf")505152def fig_classical_mechanisms():53    df = load("classical_confirmatory")54    if df is None:55        return56    cells = ["control", "placebo_shock", "trend_heavy", "leverage_heavy", "leverage_heavy_funding_shock", "liquidity_shock",57             "info_low", "info_high", "misleading_news", "breaker_5", "poslimit_300", "auction_5"]58    lab = {"control": "control", "placebo_shock": "placebo", "trend_heavy": "trend 16 %", "leverage_heavy": "leverage 19 %",59           "leverage_heavy_funding_shock": "leverage + funding shock", "liquidity_shock": "MM withdraw", "info_low": "precision 0.5",60           "info_high": "precision 8", "misleading_news": "misleading news", "breaker_5": "circuit breaker 5 %", "poslimit_300": "position limit", "auction_5": "call auction"}61    for oc in ("mad_pct", "time_to_absorb", "max_dd", "n_bubbles", "spread_bp", "lambda_bp_100"):62        fig = P.cell_bars(df, oc, cells, lab, LABELS[oc], control="control")63        P.save(fig, OUT / f"fig_classical_{oc}.pdf")646566def fig_dose_response(design: str, tag: str):67    df = load(design)68    if df is None or "tr_arm" not in df:69        return70    d = df[df.tr_arm == "dose"].copy()71    d["share"] = d["tr_share"].astype(float)72    if d["share"].nunique() < 3:73        return74    eff = {}75    for oc in ("mad_pct", "time_to_absorb", "vr5", "spread_bp", "lambda_bp_100", "depth5", "max_dd", "acf_absr_1_10", "acf_r_1"):76        if oc in d:77            eff[oc] = dose_response(d, oc, "share", 0.0)78    fig = P.dose_response(eff, LABELS)79    P.save(fig, OUT / f"fig_dose_{tag}.pdf")80    # realized shares81    fig = P.scatter_mechanism(d, "llm_share_traders", "llm_share_volume", "share", "LLM share of trader slots", "LLM share of traded volume")82    P.save(fig, OUT / f"fig_realized_share_{tag}.pdf")83    # synchrony / correlation vs share (LLM-only cells)84    dd = d[d.share > 0]85    if "llm_pairwise_corr" in dd and dd["llm_pairwise_corr"].notna().any():86        fig = P.scatter_mechanism(dd, "share", "llm_pairwise_corr", "share", "LLM share", "pairwise order-flow correlation (LLM agents)")87        P.save(fig, OUT / f"fig_sync_{tag}.pdf")88    # local projections around the t = 300 shock: 25 % and 50 % vs 0 %89    lp = {}90    for lv in (0.25, 0.50, 1.00):91        sub = d[d.share.isin([0.0, lv])]92        if sub.share.nunique() < 2:93            continue94        tb = {r.run_dir: ticks_of(r.run_dir) for r in sub.itertuples()}95        tr = {r.run_dir: r.share for r in sub.itertuples()}96        lp[f"{int(lv * 100)} % LLM"] = local_projection(tb, tr, 300, H=60)97    if lp:98        P.save(P.local_projection_plot(lp), OUT / f"fig_lp_{tag}.pdf")99    # paths for one seed100    seed = int(d.seed.min())101    runs = [(f"{int(s * 100)} % LLM", ticks_of(d[(d.share == s) & (d.seed == seed)].iloc[0].run_dir)) for s in sorted(d.share.unique()) if ((d.share == s) & (d.seed == seed)).any()][:4]102    P.save(P.price_paths(runs, shock_t=300), OUT / f"fig_paths_{tag}.pdf")103104105def fig_arms(design: str, tag: str):106    df = load(design)107    if df is None or "tr_arm" not in df:108        return109    ref = "dose_025"110    cells = [c for c in ["dose_000", "dose_025", "div_prompts_025", "div_models_025", "memory_025", "channel_025", "adaptive_025",111                         "leverage_000", "leverage_025", "breaker_025", "poslimit_025", "fair_mini_005", "fair_latency_025"] if c in set(df.cell)]112    lab = {"dose_000": "0 % (control)", "dose_025": "25 % single", "div_prompts_025": "25 % diverse prompts", "div_models_025": "25 % two models",113           "memory_025": "25 % memory", "channel_025": "25 % channel", "adaptive_025": "25 % adaptive", "leverage_000": "0 % + leverage", "leverage_025": "25 % + leverage",114           "breaker_025": "25 % + breaker", "poslimit_025": "25 % + pos. limit", "fair_mini_005": "5 % mini (equal USD)", "fair_latency_025": "25 % fast latency"}115    for oc in ("mad_pct", "time_to_absorb", "spread_bp", "n_bubbles", "max_dd", "llm_pairwise_corr", "llm_synchrony", "llm_valid_rate"):116        if oc in df:117            P.save(P.cell_bars(df, oc, cells, lab, LABELS.get(oc, oc), control=ref), OUT / f"fig_arms_{tag}_{oc}.pdf")118    if {"llm_usd", "mad_pct"} <= set(df.columns):119        dd = df[df.cell.isin(["dose_005", "dose_025", "dose_050", "dose_100", "div_models_025", "fair_mini_005"])]120        P.save(P.cost_quality(dd, "llm_usd", "mad_pct", "cell", "API cost per run (USD)", LABELS["mad_pct"]), OUT / f"fig_cost_quality_{tag}.pdf")121122123def fig_contagion():124    df = load("classical_confirmatory")125    if df is None:126        return127    cells = [c for c in ("k2_control", "k2_leverage") if c in set(df.cell)]128    if cells and "excess_comovement" in df:129        P.save(P.cell_bars(df, "excess_comovement", cells, {"k2_control": "two assets", "k2_leverage": "two assets + leverage + funding shock"},130                           "excess comovement (return corr − fundamental corr)"), OUT / "fig_contagion_excess.pdf")131        P.save(P.cell_bars(df, "spillover_dy", cells, {"k2_control": "two assets", "k2_leverage": "two assets + leverage + funding shock"},132                           "Diebold–Yilmaz spillover (%)"), OUT / "fig_contagion_spillover.pdf")133134135def fig_wealth():136    df = load("classical_confirmatory")137    if df is None:138        return139    r = df[(df.cell == "control") & (df.seed == 101)].iloc[0]140    ag = pd.read_parquet(Path(r.run_dir) / "agents.parquet")141    P.save(P.wealth_and_inventory(ag, ticks_of(r.run_dir)), OUT / "fig_wealth_depth.pdf")142143144if __name__ == "__main__":145    fig_baseline_paths()146    fig_classical_mechanisms()147    fig_contagion()148    fig_wealth()149    fig_dose_response("pilot_openai", "pilot")150    fig_dose_response("confirmatory_openai", "conf")151    fig_arms("confirmatory_openai", "conf")152    print("figures in", OUT)153
Files · main
Menu

Theme