diff --git a/code/c17_model_comparison.py b/code/c17_model_comparison.py new file mode 100644 index 0000000..bcbf499 --- /dev/null +++ b/code/c17_model_comparison.py @@ -0,0 +1,79 @@ +""" +C17: Regenerate the model-comparison table (manuscript Table 2) from the frozen + model-selection JSONs — closing the one paper<->code gap with no active script. +================================================================================ + +The manuscript's model-comparison table (GARCH(1,1) vs GJR-GARCH vs GJR-GARCH-X +AIC/BIC/LogLik per asset) originates from the model-selection run archived in +`_archive/outputs/analysis_results/model_parameters/_parameters.json`. +Those JSONs are the source of record for the table; this script extracts and +tabulates them so the table is reproducible from committed artifacts like every +other table in the paper. + +Parameter-count convention (matches the manuscript's ten-parameter statement): +the estimators profile the mean at the sample mean, so the GJR-GARCH-X +information criteria correspond to k = 10 (omega, alpha, gamma, beta, nu + 5 +exogenous coefficients); BIC - AIC = k * (ln n - 2). LogLik is recovered from +AIC as (2k - AIC) / 2. + +Output (results/): + c17-model-comparison.csv one row per (asset, model): AIC, BIC, LogLik +""" +import csv +import json +from pathlib import Path + +HERE = Path(__file__).resolve().parent +ROOT = HERE.parent +JSON_DIR = ROOT / "_archive" / "outputs" / "analysis_results" / "model_parameters" +OUT_DIR = ROOT / "results" + +ASSETS = ["btc", "eth", "xrp", "bnb", "ltc", "ada"] +# model label in JSON -> (manuscript label, parameter count with mean profiled) +MODELS = { + "GARCH(1,1)": ("GARCH(1,1)", 5), + "TARCH(1,1)": ("GJR-GARCH", 6), + "TARCH-X": ("GJR-GARCH-X", 10), +} + + +def main(): + OUT_DIR.mkdir(parents=True, exist_ok=True) + rows = [] + for asset in ASSETS: + with open(JSON_DIR / f"{asset}_parameters.json") as f: + data = json.load(f) + for json_label, (paper_label, k) in MODELS.items(): + block = data[json_label] + aic, bic = float(block["AIC"]), float(block["BIC"]) + loglik = (2 * k - aic) / 2 + rows.append( + { + "asset": asset.upper(), + "model": paper_label, + "k_params": k, + "AIC": round(aic, 3), + "BIC": round(bic, 3), + "loglik": round(loglik, 3), + } + ) + out = OUT_DIR / "c17-model-comparison.csv" + with open(out, "w", newline="") as f: + w = csv.DictWriter(f, fieldnames=list(rows[0].keys())) + w.writeheader() + w.writerows(rows) + print(f"wrote {out} ({len(rows)} rows)") + # console preview mirroring the manuscript table layout + for asset in ASSETS: + sub = [r for r in rows if r["asset"] == asset.upper()] + best = min(sub, key=lambda r: r["AIC"]) + line = " ".join( + f"{r['model']}: AIC {r['AIC']:.0f} BIC {r['BIC']:.0f} LL {r['loglik']:.0f}" + + (" *" if r is best else "") + for r in sub + ) + print(f"{asset.upper():4s} {line}") + + +if __name__ == "__main__": + main() diff --git a/results/c17-model-comparison.csv b/results/c17-model-comparison.csv new file mode 100644 index 0000000..33689e2 --- /dev/null +++ b/results/c17-model-comparison.csv @@ -0,0 +1,19 @@ +asset,model,k_params,AIC,BIC,loglik +BTC,"GARCH(1,1)",5,11904.019,11933.005,-5947.009 +BTC,GJR-GARCH,6,11905.615,11940.398,-5946.807 +BTC,GJR-GARCH-X,10,11900.352,11958.325,-5940.176 +ETH,"GARCH(1,1)",5,13344.705,13373.692,-6667.353 +ETH,GJR-GARCH,6,13346.557,13381.341,-6667.279 +ETH,GJR-GARCH-X,10,13329.362,13387.335,-6654.681 +XRP,"GARCH(1,1)",5,13324.298,13353.285,-6657.149 +XRP,GJR-GARCH,6,13325.112,13359.895,-6656.556 +XRP,GJR-GARCH-X,10,13324.297,13382.27,-6652.149 +BNB,"GARCH(1,1)",5,11400.368,11428.829,-5695.184 +BNB,GJR-GARCH,6,11400.936,11435.089,-5694.468 +BNB,GJR-GARCH-X,10,11400.492,11457.413,-5690.246 +LTC,"GARCH(1,1)",5,13779.841,13808.828,-6884.921 +LTC,GJR-GARCH,6,13773.559,13808.343,-6880.78 +LTC,GJR-GARCH-X,10,13773.224,13831.197,-6876.612 +ADA,"GARCH(1,1)",5,14091.197,14120.183,-7040.598 +ADA,GJR-GARCH,6,14093.131,14127.915,-7040.566 +ADA,GJR-GARCH-X,10,14092.421,14150.394,-7036.21