"""LaTeX table generators for the Polymarket Arrow-Debreu paper.""" from __future__ import annotations from pathlib import Path import numpy as np import pandas as pd from src.config import TABLES def _save_table(content: str, name: str) -> str: TABLES.mkdir(parents=True, exist_ok=True) path = TABLES / f"{name}.tex" path.write_text(content) return content def _esc(s: str) -> str: """Escape LaTeX special characters in text.""" return str(s).replace("$", r"\$").replace("_", r"\_").replace("&", r"\&").replace("%", r"\%").replace("#", r"\#") def _fmt(x: float, decimals: int = 3) -> str: if pd.isna(x): return "" return f"{x:.{decimals}f}" def _stars(p: float) -> str: if pd.isna(p): return "" if p < 0.01: return "^{***}" if p < 0.05: return "^{**}" if p < 0.10: return "^{*}" return "" def table_descriptive_stats(metadata: pd.DataFrame) -> str: """Table 1: Descriptive statistics of Polymarket contracts.""" rows = [] for _, row in metadata.iterrows(): name = _esc(str(row.get("name", row.get("question", "")))[:40]) vol = row.get("volume", 0) liq = row.get("liquidity", 0) n_obs = int(row.get("n_obs", 0)) mean_p = row.get("mean_price", np.nan) std_p = row.get("std_price", np.nan) rows.append( f" {name} & {vol:,.0f} & {liq:,.0f} & {n_obs} & {_fmt(mean_p)} & {_fmt(std_p)} \\\\" ) body = "\n".join(rows) tex = rf"""\begin{{table}}[htbp] \centering \begin{{threeparttable}} \caption{{Descriptive Statistics of Polymarket Contracts}} \label{{tab:descriptive_stats}} \begin{{tabular}}{{l S S c S S}} \toprule Market & {{Volume (\$)}} & {{Liquidity (\$)}} & Obs & {{Mean Price}} & {{Std Dev}} \\ \midrule {body} \bottomrule \end{{tabular}} \begin{{tablenotes}} \small \item \textit{{Notes:}} This table reports summary statistics for the Polymarket contracts analyzed. Volume and liquidity are reported in US dollars. Mean price represents the average implied probability. \end{{tablenotes}} \end{{threeparttable}} \end{{table}}""" return _save_table(tex, "table1_descriptive_stats") def table_summary_stats(prices: pd.DataFrame) -> str: """Table 2: Summary statistics of price series.""" stats = [] for col in prices.columns: s = prices[col].dropna() from scipy.stats import jarque_bera try: jb_stat, jb_p = jarque_bera(s) except Exception: jb_stat, jb_p = np.nan, np.nan stats.append({ "Market": _esc(col[:30]), "Mean": s.mean(), "Std": s.std(), "Min": s.min(), "Max": s.max(), "Skew": s.skew(), "Kurt": s.kurtosis(), "JB": jb_stat, }) rows = [] for st in stats: rows.append( f" {st['Market']} & {_fmt(st['Mean'])} & {_fmt(st['Std'])} & " f"{_fmt(st['Min'])} & {_fmt(st['Max'])} & {_fmt(st['Skew'],2)} & " f"{_fmt(st['Kurt'],2)} & {_fmt(st['JB'],1)} \\\\" ) body = "\n".join(rows) tex = rf"""\begin{{table}}[htbp] \centering \begin{{threeparttable}} \caption{{Summary Statistics of Prediction Market Prices}} \label{{tab:summary_stats}} \begin{{tabular}}{{l S S S S S S S}} \toprule Market & {{Mean}} & {{Std}} & {{Min}} & {{Max}} & {{Skew}} & {{Kurt}} & {{JB}} \\ \midrule {body} \bottomrule \end{{tabular}} \begin{{tablenotes}} \small \item \textit{{Notes:}} JB is the Jarque--Bera test statistic for normality. $^{{***}}$, $^{{**}}$, and $^{{*}}$ denote significance at the 1\%, 5\%, and 10\% levels. \end{{tablenotes}} \end{{threeparttable}} \end{{table}}""" return _save_table(tex, "table2_summary_stats") def table_completeness_test(test_result) -> str: """Table 3: Arrow-Debreu completeness test results.""" tex = rf"""\begin{{table}}[htbp] \centering \begin{{threeparttable}} \caption{{Arrow--Debreu Market Completeness Tests}} \label{{tab:completeness}} \begin{{tabular}}{{l S}} \toprule Statistic & {{Value}} \\ \midrule Mean state price sum & {_fmt(test_result.mean_sum, 4)} \\ Std of state price sum & {_fmt(test_result.std_sum, 4)} \\ Mean deviation from unity & {_fmt(test_result.mean_deviation, 4)} \\ Max absolute deviation & {_fmt(test_result.max_deviation, 4)} \\ All state prices positive & {{{'Yes' if test_result.all_positive else 'No'}}} \\ Implied discount factor & {_fmt(test_result.discount_factor, 4)} \\ Number of observations & {len(test_result.daily_sums)} \\ \bottomrule \end{{tabular}} \begin{{tablenotes}} \small \item \textit{{Notes:}} This table reports results from testing Arrow--Debreu market completeness conditions using the Fed rate decision markets on Polymarket. Under complete markets, the sum of state prices equals $1/(1+r_f)$. Mean deviation measures the average departure from this theoretical prediction. \end{{tablenotes}} \end{{threeparttable}} \end{{table}}""" return _save_table(tex, "table3_completeness") def table_event_study(results: dict[str, object]) -> str: """Table 4: Event study cumulative abnormal returns.""" rows = [] for event_type, result in results.items(): if hasattr(result, "test_stats") and not result.test_stats.empty: for _, row in result.test_stats.iterrows(): window = row.get("window", "") market = _esc(str(row.get("market", ""))) car = row.get("mean_CAR", np.nan) t = row.get("t_stat", np.nan) p = row.get("p_value", np.nan) stars = _stars(p) rows.append( f" {_esc(event_type)}: {market} & {window} & ${_fmt(car)}{stars}$ & " f"{_fmt(t, 2)} & {_fmt(p)} & {result.n_events} \\\\" ) body = "\n".join(rows) if rows else " \\multicolumn{6}{c}{No significant results} \\\\" tex = rf"""\begin{{table}}[htbp] \centering \begin{{threeparttable}} \caption{{Event Study: Cumulative Abnormal Returns Around Macroeconomic Events}} \label{{tab:event_study}} \begin{{tabular}}{{l l c c c c}} \toprule Event Type & Window & {{CAR}} & {{$t$-stat}} & {{$p$-value}} & $N$ \\ \midrule {body} \bottomrule \end{{tabular}} \begin{{tablenotes}} \small \item \textit{{Notes:}} This table reports cumulative abnormal returns (CARs) for prediction market prices around macroeconomic events. Abnormal returns are computed relative to a constant mean model estimated over a 60-day estimation window. $t$-statistics are computed using the cross-sectional standard deviation of CARs. $^{{***}}$, $^{{**}}$, $^{{*}}$ denote significance at 1\%, 5\%, 10\%. \end{{tablenotes}} \end{{threeparttable}} \end{{table}}""" return _save_table(tex, "table4_event_study") def table_granger_causality(f_stats: pd.DataFrame, p_values: pd.DataFrame) -> str: """Table 5: Granger causality test results.""" rows = [] for caused in f_stats.index: for causing in f_stats.columns: if caused != causing: f_val = f_stats.loc[caused, causing] p_val = p_values.loc[caused, causing] stars = _stars(p_val) rows.append( f" {_esc(causing)} $\\to$ {_esc(caused)} & ${_fmt(f_val, 2)}{stars}$ & {_fmt(p_val)} \\\\" ) body = "\n".join(rows) tex = rf"""\begin{{table}}[htbp] \centering \begin{{threeparttable}} \caption{{Granger Causality Tests}} \label{{tab:granger}} \begin{{tabular}}{{l c c}} \toprule Direction & {{$F$-stat}} & {{$p$-value}} \\ \midrule {body} \bottomrule \end{{tabular}} \begin{{tablenotes}} \small \item \textit{{Notes:}} This table reports $F$-statistics and $p$-values for pairwise Granger causality tests. The null hypothesis is that the causing variable does not Granger-cause the caused variable. Lag length selected by AIC. $^{{***}}$, $^{{**}}$, $^{{*}}$ denote significance at 1\%, 5\%, 10\%. \end{{tablenotes}} \end{{threeparttable}} \end{{table}}""" return _save_table(tex, "table5_granger") def table_var_results(var_results: dict) -> str: """Table 6: VAR estimation results.""" tex = rf"""\begin{{table}}[htbp] \centering \begin{{threeparttable}} \caption{{Vector Autoregression Estimates}} \label{{tab:var}} \begin{{tabular}}{{l {'S ' * min(len(var_results.get('coefficients', {}).keys()), 4)}}} \toprule """ coefs = var_results.get("coefficients", {}) if coefs: cols = list(coefs.keys())[:4] tex += " & ".join(["Variable"] + [f"{{{_esc(c)}}}" for c in cols]) + " \\\\\n\\midrule\n" first_col = coefs[cols[0]] if isinstance(first_col, pd.Series): for var_name in first_col.index[:15]: row_vals = [f"{_fmt(coefs[c].get(var_name, np.nan), 3)}" for c in cols] tex += f" {_esc(str(var_name))} & " + " & ".join(row_vals) + " \\\\\n" tex += rf"""\bottomrule \end{{tabular}} \begin{{tablenotes}} \small \item \textit{{Notes:}} VAR model estimates. Optimal lag length selected by AIC. Standard errors in parentheses. $^{{***}}$, $^{{**}}$, $^{{*}}$ denote significance at 1\%, 5\%, 10\%. \end{{tablenotes}} \end{{threeparttable}} \end{{table}}""" return _save_table(tex, "table6_var") def table_garch(garch_results: dict[str, object]) -> str: """Table 7: GARCH estimation results.""" rows = [] for name, result in garch_results.items(): if hasattr(result, "params"): for param_name, value in result.params.items(): se = result.std_errors.get(param_name, np.nan) if hasattr(result, "std_errors") else np.nan rows.append(f" {_esc(name)}: {_esc(str(param_name))} & {_fmt(value, 4)} & ({_fmt(se, 4)}) \\\\") body = "\n".join(rows) if rows else " \\multicolumn{3}{c}{No results} \\\\" tex = rf"""\begin{{table}}[htbp] \centering \begin{{threeparttable}} \caption{{GARCH Model Estimates}} \label{{tab:garch}} \begin{{tabular}}{{l S l}} \toprule Parameter & {{Estimate}} & (Std Err) \\ \midrule {body} \bottomrule \end{{tabular}} \begin{{tablenotes}} \small \item \textit{{Notes:}} GARCH(1,1) model estimates for prediction market return volatility. $^{{***}}$, $^{{**}}$, $^{{*}}$ denote significance at 1\%, 5\%, 10\%. \end{{tablenotes}} \end{{threeparttable}} \end{{table}}""" return _save_table(tex, "table7_garch") def table_har_rv(har_result) -> str: """Table 8: HAR-RV estimation results.""" rows = [] if hasattr(har_result, "coefficients"): for name, val in har_result.coefficients.items(): se = har_result.std_errors.get(name, np.nan) if hasattr(har_result, "std_errors") else np.nan t = har_result.t_stats.get(name, np.nan) if hasattr(har_result, "t_stats") else np.nan rows.append(f" {_esc(str(name))} & {_fmt(val, 4)} & ({_fmt(se, 4)}) & {_fmt(t, 2)} \\\\") body = "\n".join(rows) if rows else " \\multicolumn{4}{c}{No results} \\\\" r2 = _fmt(har_result.r_squared, 3) if hasattr(har_result, "r_squared") else "" adj_r2 = _fmt(har_result.adj_r_squared, 3) if hasattr(har_result, "adj_r_squared") else "" tex = rf"""\begin{{table}}[htbp] \centering \begin{{threeparttable}} \caption{{HAR-RV Model Estimates}} \label{{tab:har_rv}} \begin{{tabular}}{{l S l S}} \toprule Variable & {{Coefficient}} & (Std Err) & {{$t$-stat}} \\ \midrule {body} \midrule $R^2$ & \multicolumn{{3}}{{c}}{{{r2}}} \\ Adj.~$R^2$ & \multicolumn{{3}}{{c}}{{{adj_r2}}} \\ \bottomrule \end{{tabular}} \begin{{tablenotes}} \small \item \textit{{Notes:}} HAR-RV model following \citet{{corsi2009simple}}. RV$_{{t+1}}^{{(d)}} = c + \beta_d RV_t^{{(d)}} + \beta_w RV_t^{{(w)}} + \beta_m RV_t^{{(m)}} + \varepsilon_{{t+1}}$. Newey--West standard errors with 5 lags. \end{{tablenotes}} \end{{threeparttable}} \end{{table}}""" return _save_table(tex, "table8_har_rv") def table_liquidity(liquidity_stats: pd.DataFrame) -> str: """Table 9: Liquidity regression results.""" rows = [] for _, row in liquidity_stats.iterrows(): var_name = _esc(str(row.get("variable", ""))) coef = row.get("coefficient", np.nan) se = row.get("std_error", np.nan) t = row.get("t_stat", np.nan) p = row.get("p_value", np.nan) stars = _stars(p) rows.append( f" {var_name} & ${_fmt(coef, 4)}{stars}$ & ({_fmt(se, 4)}) & {_fmt(t, 2)} \\\\" ) body = "\n".join(rows) if rows else " \\multicolumn{4}{c}{No results} \\\\" tex = rf"""\begin{{table}}[htbp] \centering \begin{{threeparttable}} \caption{{Liquidity Determinants}} \label{{tab:liquidity}} \begin{{tabular}}{{l S l S}} \toprule Variable & {{Coefficient}} & (Std Err) & {{$t$-stat}} \\ \midrule {body} \bottomrule \end{{tabular}} \begin{{tablenotes}} \small \item \textit{{Notes:}} OLS regression of bid--ask spread on volume, volatility, and event dummies. Newey--West standard errors. $^{{***}}$, $^{{**}}$, $^{{*}}$ denote significance at 1\%, 5\%, 10\%. \end{{tablenotes}} \end{{threeparttable}} \end{{table}}""" return _save_table(tex, "table9_liquidity")