FIRC-0002 v2.0 (2026-10-28): Russell 3000 extension of 'Does the Language of AI Move Markets?' — commit of the working tree before peer review (large data/ inputs gitignored)
7 changed files +19,793 −180
modified
.gitignore
+6 −0
@@ -40,3 +40,9 @@ logs/ | |||||
| 40 | # Claude Code local settings | 40 | # Claude Code local settings | ||
| 41 | .claude/ | 41 | .claude/ | ||
| 42 | **/.claude/ | 42 | **/.claude/ | ||
| 43 | + | ||||
| 44 | +# v2.0 Russell 3000 extension: large inputs stay out of git (factor panel, filing-level scores, prices, shares) | ||||
| 45 | +data/raw_russell3000/* | ||||
| 46 | +!data/raw_russell3000/universe.json | ||||
| 47 | +!data/raw_russell3000/spid_firm_year_signals.csv | ||||
| 48 | +data/processed_r3k/* | ||||
added
AI-Disclosure-Stock-Returns_WP0002_v2.0_2026-10-28.docx
+0 −0
Binary file not shown.
added
AI-Disclosure-Stock-Returns_WP17_v1.1_2026-10-26.docx
+0 −0
Binary file not shown.
modified
README.md
+31 −180
@@ -1,28 +1,9 @@ | |||||
| 1 | <!-- Authors: Manel Kammoun, Djerrycharli Tandjambianda, Simon-Pierre Boucher — simon-pierre.boucher@uqo.ca --> | 1 | <!-- Authors: Manel Kammoun, Djerrycharli Tandjambianda, Simon-Pierre Boucher — simon-pierre.boucher@uqo.ca --> | ||
| 2 | # 🧠 Does the Language of Artificial Intelligence Move Markets? | 2 | # 🧠 Does the Language of Artificial Intelligence Move Markets? | ||
| 3 | 3 | ||||
| 4 | −**UQO Working Paper No. 17** — *A Semantic Analysis of S&P 500 SEC Filings and the Cross-Section of Stock Returns, 2010–2026* | 4 | +**UQO Working Paper No. 0002 (FIRC-funded)** — *A Semantic Analysis of Russell 3000 SEC Filings and the Cross-Section of Stock Returns, 2010–2026* — **Version 2.0, October 28, 2026** (Russell 3000 extension of the S&P 500 study, formerly WP 17 v1.1, archived in `docs/paper_v1.1_sp500_20261026/`). Research funded by the FIRC (Fonds institutionnel de recherche et de création, UQO). | ||
| 5 | 5 | ||||
| 6 | −[-b31b1b?logo=latex&logoColor=white)](paper/main.pdf) | 6 | +> **TL;DR** — We embed every Form 10-K, 10-Q and 8-K of ~2,500 U.S. firms (S&P 500 ∪ iShares Russell 3000 holdings, 527,548 filings, 2010–2026) with a sentence transformer, score each chunk against AI anchor concepts, and link the filing-year signal to look-ahead-free forward returns (July Y+1 → June Y+2), with S&P 500 members and the full universe reported side by side. In the broad market AI language carries a **modest, persistent premium** (EW Q5–Q1 +5.5 pp/yr, t = 2.4; Fama–MacBeth +3.1 pp/SD, t = 2.8; within-sector +1.5 pp/SD, t = 4.4), strongest among smaller firms **before** 2022. The **post-ChatGPT jump replicates in the S&P 500 (+5.0 → +15.0 pp) but not in the Russell 3000 (+4.9 → +7.9, 2022 cohort negative, placebo break 2022 ranks 8/10, post-launch FF6 alpha −0.3 %/yr)**: it is a large-cap re-rating (largest 1,000 firms +2.9 → +12.8; event-study 1-year H−L +19.5 pp vs +9.9 for the rest). **Disclosed AI projects** (SPID database) carry no premium; **10-K language** carries the strongest. | ||
| 7 | −[](requirements.txt) | ||||
| 8 | −[](data/processed/) | ||||
| 9 | −[-2e7d32)](scripts/) | ||||
| 10 | −[](https://uqo.ca) | ||||
| 11 | −[](mailto:simon-pierre.boucher@uqo.ca) | ||||
| 12 | − | ||||
| 13 | −> **TL;DR** — We embed the SEC filings of the 500 current S&P 500 constituents (Form 8-K current | ||||
| 14 | −> reports for all firms and Form 10-Q reports for 84 of them, filed 2010–2026; no 10-K was parsed in | ||||
| 15 | −> the database version used) with a sentence transformer and score each passage by cosine similarity | ||||
| 16 | −> to AI “anchor” concepts (adoption, infrastructure, disruption risk). Linked to look-ahead-free | ||||
| 17 | −> forward returns (signal of filing year Y → July Y+1 to June Y+2), the panel of 7,869 firm-years shows | ||||
| 18 | −> a **weak unconditional relation** (Q5–Q1 ≈ +5 pp/yr, t < 2; FF6 alpha 1.9 %/yr, insignificant) and a | ||||
| 19 | −> **post-ChatGPT increase** (+2.5 → +14.6 pp/yr; long–short Sharpe 0.22 → 1.32; +10.1 pp High–Low | ||||
| 20 | −> CAR one year after the launch, portfolio t = 2.3). The revision shows that this post-2022 evidence | ||||
| 21 | −> is **thin**: it rests on three cohorts and essentially on the 2024 cohort (+38 pp vs +9 and −4 pp; | ||||
| 22 | −> without it the spread is +2.6 pp), winsorizing halves the pooled spread (+14.7 → +7.2 pp), an IT | ||||
| 23 | −> sector factor absorbs most of the post-period alpha (5.2 → 1.9 %/yr), and sector-by-year fixed | ||||
| 24 | −> effects leave the slope insignificant. Reading: a sector-level re-rating of persistent AI exposure | ||||
| 25 | −> during the 2024–2026 AI investment cycle, not a stable premium nor a sharp attention shock. | ||||
| 26 | 7 | ||||
| 27 | --- | 8 | --- | ||
| 28 | 9 | ||||
@@ -30,177 +11,47 @@ | |||||
| 30 | 11 | ||||
| 31 | | Item | Value | | 12 | | Item | Value | | ||
| 32 | |---|---| | 13 | |---|---| | ||
| 33 | −| Version | **1.1 (October 2026) — revised 2026-10-17 by the editorial-board review; à valider par les co-auteurs** (v1.0 June 2026; `main.tex` reconstructed 2026-10-17) | | 14 | +| Version | **2.0 (October 28, 2026)** — Russell 3000 extension; à valider par les co-auteurs | | ||
| 34 | −| SSRN | not yet submitted | | 15 | +| Funding | FIRC (UQO) | | ||
| 35 | −| Journal | — (draft) | | 16 | +| Previous versions | v1.1 (S&P 500, 2026-10-17 review) and v1.0 (June 2026): `docs/paper_v1.1_sp500_20261026/`, `docs/REVIEW-REPORT.md`, `docs/CHANGELOG-REVISION.md` | | ||
| 36 | −| Review documents | [`docs/REVIEW-REPORT.md`](docs/REVIEW-REPORT.md) (scientific review) · [`docs/CHANGELOG-REVISION.md`](docs/CHANGELOG-REVISION.md) (audit trail) · pre-revision PDF `docs/main_pre-revision-20261017.pdf` (tag `pre-revision-20261017`) | | 17 | +| Code & data | fully reproducible from `scripts/r3k_*.py` on `data/raw_russell3000/` (factor panel, filing-level scores, SPID counts, universe) and `data/processed_r3k/` (prices, SEC share counts, factors); raw filing text and embeddings come from the EDGAR Research Database (UQO WP 0001), not redistributed | | ||
| 37 | −| Code & data | reproducible from `scripts/` on the committed processed panel (`data/processed/`, 20 MB); raw filing text and embeddings come from the EDGAR Research Database (not redistributed) | | ||||
| 38 | − | ||||
| 39 | −## 🎯 What this paper does | ||||
| 40 | − | ||||
| 41 | −Did the *language* of AI in firms' SEC filings carry information about their subsequent stock | ||||
| 42 | −returns, when did that information begin to be priced, and through which channel? Three | ||||
| 43 | −ingredients are combined: a machine-readable filing corpus (EDGAR Research Database), a semantic | ||||
| 44 | −measurement technology (sentence embeddings scored against AI anchor concepts, section-weighted | ||||
| 45 | −within filings and averaged across the filings of a calendar year), and a look-ahead-free mapping | ||||
| 46 | −from the filing year to tradeable forward returns (July Y+1 → June Y+2, at least six months after the | ||||
| 47 | −last filing). | ||||
| 48 | − | ||||
| 49 | −``` | ||||
| 50 | −r_{i,Y+1} = α + β · AI_{i,Y} + γ' X_{i,Y} + δ · (AI_{i,Y} × Post-ChatGPT) + ε_{i,Y+1} | ||||
| 51 | −``` | ||||
| 52 | − | ||||
| 53 | −Signals: `AI_ADOPT` (internal AI use), `AI_INFRA` (compute / capex exposure), `AI_RISK` | ||||
| 54 | −(product-disruption risk), `AI_COMPOS` = ½[z(ADOPT) + z(INFRA)]. Methods: within-year z-scoring, | ||||
| 55 | −quintile portfolio sorts (EW/VW, raw and market-adjusted) with Newey–West t-statistics, FF5 + | ||||
| 56 | −momentum (+ IT-sector factor) alphas, two-way fixed-effects panels with double-clustered errors, | ||||
| 57 | −Fama–MacBeth (with and without sector dummies), post-ChatGPT (30 November 2022) interaction, DiD and | ||||
| 58 | −event-time profile on pre-determined exposure, market-model event study with dependence-robust | ||||
| 59 | −portfolio tests, placebo on five non-AI semantic factors, 8-K-only signal, seasoned-firm universe. | ||||
| 60 | 18 | ||||
| 61 | −## 🏆 Headline results | 19 | +## 🏆 Headline results (Russell 3000 unless stated) | ||
| 62 | 20 | ||||
| 63 | | Object | Estimate | | 21 | | Object | Estimate | | ||
| 64 | |---|---| | 22 | |---|---| | ||
| 65 | −| Panel | 7,869 firm-years, 500 firms, filing years 2010–2026 (estimation sample 6,852 firm-years, 497 firms, formation years 2010–2024) | | 23 | +| Panel | 27,468 firm-years, 2,379 firms, formation years 2010–2024 (6,793 firm-years of S&P 500 members) | | ||
| 66 | −| Scored corpus | 103,926 Form 8-K (all 500 firms) + 3,710 Form 10-Q (84 firms); 0 Form 10-K; median 28 chunks per firm-year | | 24 | +| Full-sample Q5–Q1 spread (composite) | EW +5.5 pp/yr (t 2.41); VW +13.9 (t 2.59); S&P 500 members EW +7.0 (t 2.56) | | ||
| 67 | −| Full-sample Q5–Q1 spread (composite, EW) | +4.9 pp/yr (t = 1.88); FF6 alpha +1.9 %/yr (t = 1.24); with IT-sector factor +0.4 %/yr | | 25 | +| Fama–MacBeth slope (composite) | +3.07 pp/SD (t 2.80); with sector dummies +1.46 (t 4.38); S&P 500 +2.07 (t 2.17) | | ||
| 68 | −| Q5–Q1 spread, pre- / post-ChatGPT | **+2.5 pp/yr → +14.6 pp/yr**; post cohorts 2022 / 2023 / 2024 = **+9.3 / −4.0 / +38.4**; post mean without 2024 = +2.6 | | 26 | +| Pre / post ChatGPT, Russell 3000 | +4.9 → +7.9 pp (post t 1.34); cohorts 2022/23/24 = −8.4 / +11.8 / +20.4; pooled post +8.2 (t 1.80), winsorized +3.0; post LS Sharpe 0.23 (pre 0.50) | | ||
| 69 | −| Pooled post-period Q5 vs Q1 (firm-clustered) | +14.7 pp (t = 2.18) raw; **+7.2 pp (t = 2.21) winsorized**; 2022–2023 only +2.6 pp (t = 0.46) | | 27 | +| Pre / post, S&P 500 members | +5.0 → +15.0 (t 2.21); cohorts +13.5 / −6.9 / +38.4; pooled +15.1 (t 2.22); post Sharpe 0.66 | | ||
| 70 | −| Monthly long–short, pre / post (36 months) | mean 1.1 → 7.5 %/yr; Sharpe **0.22 → 1.32**; FF6 alpha post +5.2 %/yr (t = 1.72); **FF6 + TECH alpha +1.9 %/yr (t = 0.86), β_TECH = 0.47** | | 28 | +| Size | largest 1,000: +2.9 → +12.8 (t 2.09); remaining firms: +9.6 → +8.4; size-neutral full-sample +7.2 (t 2.73) | | ||
| 71 | −| Event study: ChatGPT launch, High–Low 1-year CAR | **+10.1 pp**; Welch t = 1.97 (independence); portfolio t = 2.28 (estimation-window SD) / 2.60 (event-window SD); 60 days: +2.9 pp, t = 1.35 | | 29 | +| Placebo break dates | 2022 ranks 8th of 10 candidate first-post-cohorts (best: 2016) | | ||
| 72 | −| Placebo (5 non-AI semantic factors) | ESG −8.8, climate −5.0, macro −0.5 pp; but cybersecurity +13.5 and automation/labour +7.2 pp | | 30 | +| Event study (IWV market, N = 2,226) | 1-year H−L +14.0 pp (Welch 3.94, CDA 2.21); 60 days +0.9; largest 1,000 +19.5 (5.09) vs rest +9.9 (1.55) | | ||
| 73 | −| Event-time premium on 2019–21 exposure (firm-clustered SE) | 2015 +3.9 (t 2.8), 2016 +3.0 (2.3), 2022 +3.4 (2.2), 2023 +0.5 (0.3), 2024 +6.8 (3.0) — pre-trend not flat | | 31 | +| Sector | post-launch sector×year FE slope +0.10 (t 0.11); full-sample +0.88 (t 2.48); β_TECH 0.38 | | ||
| 74 | −| Sector controls | FM post slope +5.5 (t 2.0) → +2.9 (t 1.4) with sector dummies; sector×year FE +1.3 (t 1.2); sector-neutral sort +3.8 (t 0.8) | | 32 | +| Form-specific signals | 10-K only: pre +6.9 / post +16.7 (t 2.61), FM +4.66; 8-K only: +1.1 / +6.6; horse race 10-K +6.06 (1.71), 8-K +0.90 | | ||
| 75 | −| 8-K-only signal / seasoned firms / Mag-7 excluded | +1.6 → +16.3 pp; +1.0 → +17.3 pp; +1.7 → +14.1 pp (break unchanged) | | 33 | +| SPID AI projects | tech-project quintile spread −1.9 (pre) / −1.3 (post); FM −0.22 (t −0.24) | | ||
| 34 | +| Placebo factors (post) | ESG −15.9, climate −8.6, macro −16.3; cybersecurity +13.5, automation +7.8, AI disruption risk +22.8 | | ||||
| 76 | 35 | ||||
| 77 | ## 🗂 Repository layout | 36 | ## 🗂 Repository layout | ||
| 78 | 37 | ||||
| 79 | ``` | 38 | ``` | ||
| 80 | ai-disclosure-stock-returns/ | 39 | ai-disclosure-stock-returns/ | ||
| 81 | −├── data/processed/ # analysis_panel, merged_panel, filing_type_scores, prices, market caps, FF5, event CARs (parquet, 20 MB, committed) | 40 | +├── data/raw_russell3000/ # factor panels, filing_scores, filings, companies, universe.json, spid_firm_year_signals.csv (from the EDGAR DB) | ||
| 82 | −├── scripts/ # the pipeline, in order | 41 | +├── data/processed_r3k/ # prices (Yahoo), SEC share history, FF factors, merged/analysis panels, LS series, event CARs | ||
| 83 | −│ ├── 01_download_prices.py # monthly prices (500 tickers), SPY, market caps [needs network; outputs committed] | 42 | +├── scripts/r3k_00..07_*.py # the v2.0 pipeline, in order (01: Yahoo prices · 01c: SEC share counts · 02: panel · 03: core · 04: extended · 05: event · 06: figures · 07: tables + macros) | ||
| 84 | −│ ├── 02_build_panel.py # look-ahead-free forward returns (July formation) + controls [needs the EDGAR DB panel] | 43 | +├── results/r3k/ # results.json, results2.json, results_event.json, CSVs | ||
| 85 | −│ ├── 03_analysis.py # sorts, FF6 alphas, two-way FE, Fama–MacBeth, post-ChatGPT break | 44 | +├── figures/r3k/ # 14 figures (pdf + png) | ||
| 86 | −│ ├── 04_figures.py # figures 1–5 | 45 | +├── paper/ # main.tex, sections/, appendix/, tables/ (generated, incl. macros.tex), figures_r3k/ → main.pdf | ||
| 87 | −│ ├── 05_make_tables.py # LaTeX tables (core) — the committed fragments were hand-edited afterwards (see CHANGELOG) | 46 | +├── code/tex2docx/ # LaTeX → Word conversion (prep_tex.py, post_docx.py) | ||
| 88 | −│ ├── 06_extended.py # placebo, sector-neutral / double sorts, Mag-7 exclusion, PCA, DiD, event-time (firm-clustered SE since v1.1) | 47 | +└── docs/ # v1.1 archive, review report, changelog | ||
| 89 | −│ ├── 07_event_study.py # ChatGPT market-model event study [daily prices committed] | ||||
| 90 | −│ ├── 08_figures_ext.py # figures 6–13 | ||||
| 91 | −│ ├── 09_make_tables_ext.py # LaTeX tables (extended) | ||||
| 92 | −│ ├── 10_filing_type_scores.py # v1.1: 8-K vs 10-Q filing-level scores from the EDGAR DB → data/processed/filing_type_scores.parquet | ||||
| 93 | −│ ├── 11_review_reanalysis.py # v1.1: cohorts, pooled tests, monthly Sharpe/alphas + TECH factor, 8-K-only, seasoned firms, sector FE, event-study CDA | ||||
| 94 | −│ └── 12_make_tables_review.py # v1.1: tab_corpus, tab_postinference, tab_review_robust, tab_event_cda | ||||
| 95 | −├── results/tables/ # results.json, results2.json, results_event.json, results_review.json, corpus_composition.json, CSVs | ||||
| 96 | −├── figures/ # 13 figures (PNG) — copied to paper/figures/ | ||||
| 97 | −├── paper/ # main.tex, sections/, appendix/, tables/, references.bib (57 entries, DOIs), main.pdf | ||||
| 98 | −└── docs/ # REVIEW-REPORT.md, CHANGELOG-REVISION.md, README-analysis.md (June 2026), original reports, pre-revision PDF | ||||
| 99 | ``` | 48 | ``` | ||
| 100 | 49 | ||||
| 101 | ## 🚀 Quick start | 50 | ## 🚀 Quick start | ||
| 102 | 51 | ||||
| 103 | ```bash | 52 | ```bash | ||
| 104 | −pip install -r requirements.txt | 53 | +python3 -m pip install -r requirements.txt | ||
| 105 | −# Re-analyses (no network, < 1 min each) from the committed parquet files: | 54 | +python3 scripts/r3k_00_factors.py && python3 scripts/r3k_01_download_prices.py && python3 scripts/r3k_01c_sec_shares.py | ||
| 106 | −python scripts/03_analysis.py && python scripts/06_extended.py && python scripts/07_event_study.py | 55 | +for s in 02_build_panel 03_analysis 04_extended 05_event_study 06_figures 07_tables; do python3 scripts/r3k_$s.py; done | ||
| 107 | −python scripts/11_review_reanalysis.py && python scripts/12_make_tables_review.py | 56 | +cd paper && latexmk # → main.pdf (every number in the text is a macro from tables/macros.tex) | ||
| 108 | −python scripts/04_figures.py && python scripts/08_figures_ext.py && cp figures/*.png paper/figures/ | ||||
| 109 | −cd paper && make # latexmk → main.pdf | ||||
| 110 | ``` | 57 | ``` | ||
| 111 | − | ||||
| 112 | −`01_download_prices.py` and `02_build_panel.py` require, respectively, network access to Yahoo Finance and the | ||||
| 113 | −EDGAR Research Database panel (`annual_final.parquet`); their outputs are committed. `10_filing_type_scores.py` | ||||
| 114 | −also reads the EDGAR Research Database; its output is committed. | ||||
| 115 | − | ||||
| 116 | −## 🗃 Data | ||||
| 117 | − | ||||
| 118 | −- **Disclosure signals**: section-weighted cosine similarity of filing text chunks to AI anchor concepts, computed in the | ||||
| 119 | − EDGAR Research Database (`~/Desktop/UQO/UQO_RESEARCH_DB`, `all-MiniLM-L6-v2` embeddings). **Scored corpus**: all Form 8-K | ||||
| 120 | − filings of the 500 current constituents (103,926) and the Form 10-Q filings of 84 firms (3,710); no Form 10-K was parsed. | ||||
| 121 | − The panel year is the **calendar year of the filing date**. Only firm-year scores (and, since v1.1, firm-year-by-form-type | ||||
| 122 | − scores) enter this repository. | ||||
| 123 | −- **Prices and factors**: monthly prices and market capitalisations for the 500 constituents and SPY (Yahoo Finance, downloaded | ||||
| 124 | − 17 June 2026), Fama–French five factors and momentum (Kenneth French data library, through April 2026). | ||||
| 125 | −- **Timing**: filings of calendar year Y → forward 12-month return July (Y+1) to June (Y+2); gap ≥ 6 months. | ||||
| 126 | −- **Universe**: current (June 2026) S&P 500 membership — **not point-in-time**; survivorship and entrant bias are discussed in | ||||
| 127 | − the paper and tested with listing-history restrictions (not eliminated). | ||||
| 128 | −- Sample cascade: 7,869 firm-years → 6,852 firm-years (497 firms) with a forward return (Table 2 of the paper). | ||||
| 129 | − | ||||
| 130 | −## 🔬 Methodology | ||||
| 131 | − | ||||
| 132 | −1. **Signal construction** (`02_build_panel.py`): within-year z-scores of the three anchors and the composite; controls (momentum, volatility, log market cap). | ||||
| 133 | −2. **Portfolio sorts** (`03_analysis.py`): quintiles, EW/VW, raw and market-adjusted, Newey–West inference; long-short spread regressed on FF5 + momentum. | ||||
| 134 | −3. **Panel and Fama–MacBeth** (`03_analysis.py`): two-way fixed effects with double-clustered errors; year-by-year cross-sectional slopes. | ||||
| 135 | −4. **Structural break** (`03_analysis.py`, `06_extended.py`): post-ChatGPT interaction, DiD and event-time profile, sector-neutral and double sorts, Magnificent-7 exclusion, alternative signals/horizons, PCA. | ||||
| 136 | −5. **Placebo** (`06_extended.py`): five non-AI semantic factors through the same pipeline. | ||||
| 137 | −6. **Event study** (`07_event_study.py`): market-model CARs around 30 November 2022, binscatter on pre-event AI intensity. | ||||
| 138 | −7. **Review re-analyses** (`11_review_reanalysis.py`): cohort-by-cohort and leave-one-out spreads, pooled firm-clustered tests (raw and winsorized), monthly Sharpe ratios and sub-period alphas with an IT-sector factor, 8-K-only signal, seasoned-firm universe, alternative break dates, Fama–MacBeth with sector dummies and sector×year FE, event-study portfolio tests (Brown–Warner CDA, event-window SD, Kolari–Pynnönen ρ̄). | ||||
| 139 | − | ||||
| 140 | −## 🖼 Figures & tables inventory | ||||
| 141 | − | ||||
| 142 | −| Figure | Content | | ||||
| 143 | −|---|---| | ||||
| 144 | −| `fig1_quintile_returns` | forward returns by AI-language quintile (full / pre / post) | | ||||
| 145 | −| `fig2_cumulative_ls` | cumulative long-short returns by signal | | ||||
| 146 | −| `fig3_annual_spread` | Q5–Q1 spread by formation (filing) year | | ||||
| 147 | −| `fig4_ai_language_trend` | AI-language intensity over time | | ||||
| 148 | −| `fig5_binscatter` | forward return vs AI intensity, pre/post | | ||||
| 149 | −| `fig6_fm_byyear` | Fama–MacBeth slopes by year | | ||||
| 150 | −| `fig7_placebo` | AI vs five placebo semantic factors | | ||||
| 151 | −| `fig8_event_car` | cumulative abnormal returns around ChatGPT | | ||||
| 152 | −| `fig9_double_sort` | AI × size double sorts pre/post | | ||||
| 153 | −| `fig10_mag7` | Magnificent-7 exclusion | | ||||
| 154 | −| `fig11_eventtime` | event-time premium on pre-determined exposure (firm-clustered 95 % CI) | | ||||
| 155 | −| `fig12_sector_box` | AI intensity by sector | | ||||
| 156 | −| `fig13_event_binscatter` | 1-year CAR vs pre-event AI intensity | | ||||
| 157 | − | ||||
| 158 | −Tables (`paper/tables/*.tex`): corpus composition, summary statistics, correlations, top firms, portfolio sorts, alphas, | ||||
| 159 | −panel, structural break, post-period inference, double sorts, placebo, event study, event-study inference (CDA), DiD, | ||||
| 160 | −robustness, additional robustness (8-K-only, entrants, break definition, sector controls). | ||||
| 161 | − | ||||
| 162 | −## ⚠️ Limitations | ||||
| 163 | − | ||||
| 164 | −- The post-2022 period has three formation cohorts and the average spread is driven by the 2024 cohort (holding period ending | ||||
| 165 | − with the partial June 2026 observation); a regime claim is a hypothesis for future cohorts, not an established fact. | ||||
| 166 | −- The scored corpus is 8-K current reports plus a partial, alphabetically selected set of 10-Qs; no 10-K text. Results hold on | ||||
| 167 | − 8-K filings alone, but the original question about annual-report narrative has not been tested. | ||||
| 168 | −- Current S&P 500 constituents only (survivorship and entrant bias; return levels biased upward). Entrants are evenly spread | ||||
| 169 | − across AI quintiles and results hold among seasoned firms, but the point-in-time index has not been reconstructed. | ||||
| 170 | −- AI language is correlated with sector; every sector control halves the post-2022 effect and leaves it insignificant. | ||||
| 171 | −- Anchor concepts embed the authors' definitional choices; no alternative anchor set has been tried. | ||||
| 172 | −- Market-cap proxy with constant (June 2026) shares; Yahoo Finance adjusted closes rather than CRSP; French factors end April 2026. | ||||
| 173 | − | ||||
| 174 | −## 📝 Citation | ||||
| 175 | − | ||||
| 176 | −```bibtex | ||||
| 177 | −@techreport{kammoun2026ailanguage, | ||||
| 178 | − author = {Kammoun, Manel and Tandjambianda, Djerrycharli and Boucher, Simon-Pierre}, | ||||
| 179 | − title = {Does the Language of Artificial Intelligence Move Markets? A Semantic Analysis of S\&P~500 SEC Filings and the Cross-Section of Stock Returns, 2010--2026}, | ||||
| 180 | − institution = {Universit\'e du Qu\'ebec en Outaouais, D\'epartement des sciences administratives}, | ||||
| 181 | − type = {Working Paper}, | ||||
| 182 | − number = {17}, | ||||
| 183 | − year = {2026}, | ||||
| 184 | − month = {October}, | ||||
| 185 | − note = {Version 1.1} | ||||
| 186 | −} | ||||
| 187 | −``` | ||||
| 188 | − | ||||
| 189 | −## 👤 Authors & contact | ||||
| 190 | − | ||||
| 191 | −**Manel Kammoun**, **Djerrycharli Tandjambianda**, **Simon-Pierre Boucher** — Département des sciences administratives, | ||||
| 192 | −Université du Québec en Outaouais (UQO), Gatineau, QC. | ||||
| 193 | −📧 [manel.kammoun@uqo.ca](mailto:manel.kammoun@uqo.ca) · [djerrycharli.tandjambianda@uqo.ca](mailto:djerrycharli.tandjambianda@uqo.ca) · [simon-pierre.boucher@uqo.ca](mailto:simon-pierre.boucher@uqo.ca) | ||||
| 194 | − | ||||
| 195 | −## 🗒 Notes | ||||
| 196 | − | ||||
| 197 | −- **Revision 2026-10-17 (v1.1, à valider par les co-auteurs)**: complete scientific revision by the editorial-board procedure | ||||
| 198 | − (`Recherche-UQO/REVISION-MASTER-PROMPT.md`). The review found that the June 2026 draft misdescribed the corpus (10-K text | ||||
| 199 | − was claimed; the scored filings are 8-K and partial 10-Q, by calendar year of filing), overstated the uniformity of the | ||||
| 200 | − post-2022 cohorts, used degenerate year-clustered standard errors in the event-time DiD, used an independence-based test in | ||||
| 201 | − the event study, and relied on Newey–West t-statistics computed on three annual observations. All numbers were re-derived | ||||
| 202 | − from versioned scripts; see `docs/REVIEW-REPORT.md` and `docs/CHANGELOG-REVISION.md`. `docs/README-analysis.md` is the | ||||
| 203 | − June 2026 analysis note and keeps the original (incorrect) corpus description for the record. | ||||
| 204 | −- **Reconstruction (2026-10-17)**: the working-paper `main.tex` of the June 2026 draft (former folder `UQO_WP_SPB_MK_CT`) had been lost; it was rebuilt from `Recherche-UQO/_modele-working-paper/main.tex` with title, abstract, keywords and JEL taken from the original report (`docs/original_report_full_2026-06.tex`). The 13 figures were regenerated from the committed panel with `scripts/04_figures.py` and `08_figures_ext.py`. | ||||
| 205 | −- Deviations from the standard: three author e-mails on the cover (`\WPemail` holds three `\href`s, the title page prints `\WPemail` directly); the long abstract is single-spaced so that Keywords and JEL stay on page 2; `data/processed/*.parquet` (20 MB) is committed because it is the only reproducible input outside the EDGAR Research Database. | ||||
| 206 | −- Macros `\tstat` and `\tablelegend` (used by sections and tables) are defined in `main.tex`. | ||||
added
code/tex2docx/post_docx.py
+94 −0
@@ -0,0 +1,94 @@ | |||||
| 1 | +import sys, re, datetime | ||||
| 2 | +from docx import Document | ||||
| 3 | +from docx.shared import Pt | ||||
| 4 | +from docx.enum.text import WD_ALIGN_PARAGRAPH | ||||
| 5 | +path = sys.argv[1]; doc = Document(path) | ||||
| 6 | +paras = doc.paragraphs | ||||
| 7 | +norm = lambda p: re.sub(r"\s+", " ", p.text.replace(" ", " ")).strip() | ||||
| 8 | +title = "Does the Language of Artificial Intelligence Move Markets?" | ||||
| 9 | +# --- title page: everything before the Abstract block is centred | ||||
| 10 | +i_abs = next(i for i, p in enumerate(paras) if norm(p) == "Abstract") | ||||
| 11 | +for p in paras[:i_abs - 1]: | ||||
| 12 | + p.alignment = WD_ALIGN_PARAGRAPH.CENTER | ||||
| 13 | + if norm(p) == title: | ||||
| 14 | + for r in p.runs: r.font.size = Pt(18); r.font.bold = True | ||||
| 15 | + elif norm(p).startswith("A Semantic Analysis of Russell"): | ||||
| 16 | + for r in p.runs: r.font.size = Pt(13) | ||||
| 17 | +# --- move References (pandoc appends it at the end) before the appendix, as in the PDF | ||||
| 18 | +h1 = [p for p in paras if p.style.name == "Heading 1"] | ||||
| 19 | +refs = next(p for p in h1 if norm(p) == "References") | ||||
| 20 | +appA = next(p for p in h1 if re.match(r"^A [A-Z]", norm(p))) | ||||
| 21 | +body = doc.element.body; kids = list(body) | ||||
| 22 | +i_refs, i_app = kids.index(refs._p), kids.index(appA._p) | ||||
| 23 | +moved = [k for k in kids[i_refs:] if k.tag.endswith("}sectPr") is False] | ||||
| 24 | +for k in moved: appA._p.addprevious(k) | ||||
| 25 | +# --- page breaks where the LaTeX source has \newpage | ||||
| 26 | +breaks = 0 | ||||
| 27 | +paras[i_abs - 1].paragraph_format.page_break_before = True; breaks += 1 | ||||
| 28 | +for p in h1: | ||||
| 29 | + t = norm(p) | ||||
| 30 | + if t.startswith("1 Introduction") or t == "References" or p is appA: | ||||
| 31 | + p.paragraph_format.page_break_before = True; breaks += 1 | ||||
| 32 | +# --- document properties | ||||
| 33 | +cp = doc.core_properties | ||||
| 34 | +cp.title = title | ||||
| 35 | +cp.subject = "A Semantic Analysis of Russell 3000 SEC Filings and the Cross-Section of Stock Returns, 2010–2026" | ||||
| 36 | +cp.author = "Manel Kammoun; Djerrycharli Tandjambianda; Simon-Pierre Boucher" | ||||
| 37 | +cp.keywords = "textual analysis, sentence embeddings, artificial intelligence, disclosure, cross-section of returns, Russell 3000, firm size, event study, generative AI" | ||||
| 38 | +cp.comments = "UQO Working Paper No. 0002 (FIRC-funded), Version 2.0 (Russell 3000 extension), October 28, 2026" | ||||
| 39 | +cp.created = cp.modified = datetime.datetime(2026, 10, 28, 12, 0) | ||||
| 40 | +# --- base fonts closer to the PDF (Times) | ||||
| 41 | +from docx.oxml.ns import qn | ||||
| 42 | +for st in doc.styles: | ||||
| 43 | + try: | ||||
| 44 | + if st.type == 1 and st.font is not None and (st.name == "Normal" or st.name.startswith("Heading") or st.name in ("Title", "Body Text", "First Paragraph", "Compact", "Caption", "Bibliography", "Block Text")): | ||||
| 45 | + st.font.name = "Times New Roman" | ||||
| 46 | + rpr = st.element.get_or_add_rPr(); rf = rpr.find(qn("w:rFonts")) | ||||
| 47 | + if rf is None: | ||||
| 48 | + rf = rpr.makeelement(qn("w:rFonts"), {}); rpr.append(rf) | ||||
| 49 | + for a in ("w:ascii", "w:hAnsi", "w:eastAsia", "w:cs"): rf.set(qn(a), "Times New Roman") | ||||
| 50 | + if st.name in ("Normal", "Body Text", "First Paragraph", "Compact"): st.font.size = Pt(12) | ||||
| 51 | + except Exception: | ||||
| 52 | + pass | ||||
| 53 | +# --- wide tables: smaller font and tight cell padding so that 8-10 column tables fit the page width | ||||
| 54 | +from docx.shared import Inches | ||||
| 55 | +from docx.oxml import OxmlElement | ||||
| 56 | +for sec in doc.sections: | ||||
| 57 | + sec.left_margin = sec.right_margin = Inches(0.85) | ||||
| 58 | +for tbl in doc.tables: | ||||
| 59 | + ncol = max(len(r.cells) for r in tbl.rows) | ||||
| 60 | + if ncol >= 7: | ||||
| 61 | + size = Pt(7) if ncol >= 9 else Pt(8) | ||||
| 62 | + tbl.autofit = True | ||||
| 63 | + for row in tbl.rows: | ||||
| 64 | + for cell in row.cells: | ||||
| 65 | + for para in cell.paragraphs: | ||||
| 66 | + para.paragraph_format.space_after = Pt(0); para.paragraph_format.space_before = Pt(0) | ||||
| 67 | + for run in para.runs: run.font.size = size | ||||
| 68 | + tcPr = cell._tc.get_or_add_tcPr(); mar = OxmlElement("w:tcMar") | ||||
| 69 | + for side in ("left", "right"): | ||||
| 70 | + el = OxmlElement(f"w:{side}"); el.set(qn("w:w"), "30"); el.set(qn("w:type"), "dxa"); mar.append(el) | ||||
| 71 | + tcPr.append(mar) | ||||
| 72 | +# --- proportional column widths for wide tables (pandoc emits equal 0.55-inch grid columns) | ||||
| 73 | +TOTAL = 9792 # twips: 8.5in - 2 x 0.85in margins | ||||
| 74 | +for tbl in doc.tables: | ||||
| 75 | + ncol = max(len(r.cells) for r in tbl.rows) | ||||
| 76 | + if ncol < 7: continue | ||||
| 77 | + lens = [1] * ncol | ||||
| 78 | + for row in tbl.rows: | ||||
| 79 | + for j, cell in enumerate(row.cells[:ncol]): | ||||
| 80 | + lens[j] = max(lens[j], min(28, max((len(w) for w in cell.text.split()), default=1))) | ||||
| 81 | + weights = [max(l, 4) for l in lens]; tot = sum(weights); widths = [int(TOTAL * wgt / tot) for wgt in weights] | ||||
| 82 | + grid = tbl._tbl.find(qn("w:tblGrid")) | ||||
| 83 | + if grid is not None: | ||||
| 84 | + for g, wd in zip(grid.findall(qn("w:gridCol")), widths): g.set(qn("w:w"), str(wd)) | ||||
| 85 | + tblPr = tbl._tbl.tblPr | ||||
| 86 | + for e in tblPr.findall(qn("w:tblW")): e.set(qn("w:type"), "dxa"); e.set(qn("w:w"), str(TOTAL)) | ||||
| 87 | + for e in tblPr.findall(qn("w:tblLayout")): e.set(qn("w:type"), "fixed") | ||||
| 88 | + for row in tbl.rows: | ||||
| 89 | + for j, cell in enumerate(row.cells[:ncol]): | ||||
| 90 | + tcPr = cell._tc.get_or_add_tcPr(); tcW = tcPr.find(qn("w:tcW")) | ||||
| 91 | + if tcW is None: tcW = OxmlElement("w:tcW"); tcPr.insert(0, tcW) | ||||
| 92 | + tcW.set(qn("w:type"), "dxa"); tcW.set(qn("w:w"), str(widths[j])) | ||||
| 93 | +doc.save(path) | ||||
| 94 | +print(f"post-processed: {i_abs-1} title-page paragraphs centred, {len(moved)} reference paragraphs moved before appendix, {breaks} page breaks, properties set") | ||||
added
code/tex2docx/prep_tex.py
+168 −0
@@ -0,0 +1,168 @@ | |||||
| 1 | +"""Flatten a LaTeX working paper and resolve numbering from main.aux so that | ||||
| 2 | +pandoc produces a Word file with correct section/table/figure/equation numbers.""" | ||||
| 3 | +import re, sys, pathlib | ||||
| 4 | + | ||||
| 5 | +PAPER = pathlib.Path(sys.argv[1]).resolve() | ||||
| 6 | +OUT = pathlib.Path(sys.argv[2]) | ||||
| 7 | +warn = lambda *a: print("WARN:", *a, file=sys.stderr) | ||||
| 8 | + | ||||
| 9 | +def strip_comments(txt): | ||||
| 10 | + out = [] | ||||
| 11 | + for line in txt.splitlines(): | ||||
| 12 | + if re.match(r'^\s*%', line): | ||||
| 13 | + continue | ||||
| 14 | + # drop trailing unescaped % comments | ||||
| 15 | + line = re.sub(r'(?<!\\)%.*$', '', line) | ||||
| 16 | + out.append(line) | ||||
| 17 | + return "\n".join(out) + "\n" | ||||
| 18 | + | ||||
| 19 | +def inline(path): | ||||
| 20 | + txt = strip_comments(path.read_text()) | ||||
| 21 | + def rep(m): | ||||
| 22 | + name = m.group(1) | ||||
| 23 | + p = PAPER / (name if name.endswith('.tex') else name + '.tex') | ||||
| 24 | + if not p.exists(): | ||||
| 25 | + warn("missing input", p); return m.group(0) | ||||
| 26 | + return inline(p) | ||||
| 27 | + return re.sub(r'\\input\{([^}]+)\}', rep, txt) | ||||
| 28 | + | ||||
| 29 | +def brace_arg(s, i): | ||||
| 30 | + """s[i] == '{' -> return (content, index after closing brace)""" | ||||
| 31 | + assert s[i] == '{', s[i:i+20] | ||||
| 32 | + depth = 0 | ||||
| 33 | + for j in range(i, len(s)): | ||||
| 34 | + if s[j] == '{' and (j == 0 or s[j-1] != '\\'): depth += 1 | ||||
| 35 | + elif s[j] == '}' and s[j-1] != '\\': | ||||
| 36 | + depth -= 1 | ||||
| 37 | + if depth == 0: | ||||
| 38 | + return s[i+1:j], j+1 | ||||
| 39 | + raise ValueError("unbalanced braces") | ||||
| 40 | + | ||||
| 41 | +# ---------------------------------------------------------------- aux | ||||
| 42 | +aux = (PAPER / 'main.aux').read_text() | ||||
| 43 | +labels = {} | ||||
| 44 | +for m in re.finditer(r'\\newlabel\{([^}]+)\}\{', aux): | ||||
| 45 | + outer, _ = brace_arg(aux, m.end()-1) | ||||
| 46 | + num, _ = brace_arg(outer, 0) | ||||
| 47 | + labels[m.group(1)] = num.strip() | ||||
| 48 | +toc = [] | ||||
| 49 | +for m in re.finditer(r'\\contentsline \{(section|subsection|subsubsection)\}\{\\numberline \{([^}]*)\}(.*?)\}\{\d+\}\{', aux): | ||||
| 50 | + toc.append((m.group(1), m.group(2), m.group(3))) | ||||
| 51 | +print(f"aux: {len(labels)} labels, {len(toc)} numbered headings") | ||||
| 52 | + | ||||
| 53 | +# ---------------------------------------------------------------- flatten | ||||
| 54 | +tex = inline(PAPER / 'main.tex') | ||||
| 55 | + | ||||
| 56 | +# ---- WP 28 shims ---------------------------------------------------------- | ||||
| 57 | +# TikZ pipeline figure -> pre-rendered PNG | ||||
| 58 | +tex = re.sub(r'\\begin\{tikzpicture\}.*?\\end\{tikzpicture\}', r'\\includegraphics[width=\\textwidth]{figures/fig_pipeline.png}', tex, flags=re.S) | ||||
| 59 | +# PDF figures -> PNG (pandoc/docx cannot embed PDF) | ||||
| 60 | +tex = re.sub(r'\\includegraphics(\[[^\]]*\])?\{([^}]+)\.pdf\}', r'\\includegraphics\1{figures_r3k/\2.png}', tex) | ||||
| 61 | +# \adjustbox{max width=...}{...} wrapper -> content only | ||||
| 62 | +def strip_adjustbox(t): | ||||
| 63 | + out = []; pos = 0 | ||||
| 64 | + while True: | ||||
| 65 | + m = re.search(r'\\adjustbox\{[^{}]*\}\{', t[pos:]) | ||||
| 66 | + if not m: break | ||||
| 67 | + start = pos + m.start(); open_ = pos + m.end() - 1 | ||||
| 68 | + content, end = brace_arg(t, open_) | ||||
| 69 | + out.append(t[pos:start]); out.append(content); pos = end | ||||
| 70 | + out.append(t[pos:]); return "".join(out) | ||||
| 71 | +tex = strip_adjustbox(tex) | ||||
| 72 | +# threeparttable / tablenotes -> plain notes paragraph | ||||
| 73 | +tex = re.sub(r'\\(begin|end)\{threeparttable\}', '', tex) | ||||
| 74 | +tex = re.sub(r'\\begin\{tablenotes\}\\footnotesize\s*\\item\s*(.*?)\\end\{tablenotes\}', lambda m: '\\par\\textit{Notes:} ' + m.group(1).strip(), tex, flags=re.S) | ||||
| 75 | +# prompt block (quote + obeylines) -> verbatim | ||||
| 76 | +tex = re.sub(r'\\begin\{quote\}\\footnotesize\\ttfamily.*?\\end\{quote\}', lambda m: (PAPER / 'tables' / 'prompt_verbatim.tex').read_text(), tex, flags=re.S) | ||||
| 77 | +tex = re.sub(r'\\input\{tables/prompt\}', lambda m: (PAPER / 'tables' / 'prompt_verbatim.tex').read_text(), tex) | ||||
| 78 | +# tabularx -> tabular (pandoc handles tabular more reliably); X -> l | ||||
| 79 | +def fix_tabularx(m): | ||||
| 80 | + spec = m.group(1).replace('X', 'l') | ||||
| 81 | + return '\\begin{tabular}{' + spec + '}' | ||||
| 82 | +tex = re.sub(r'\\begin\{tabularx\}\{\\textwidth\}\{([^}]*)\}', fix_tabularx, tex) | ||||
| 83 | +tex = tex.replace('\\end{tabularx}', '\\end{tabular}') | ||||
| 84 | +tex = tex.replace('\\allowbreak', '') | ||||
| 85 | +tex = re.sub(r'\\setlength\{\\emergencystretch\}\{[^}]*\}', '', tex) | ||||
| 86 | +tex = re.sub(r'\\begin\{tabular\}\{lr@\{\\hspace\{2em\}\}lr@\{\\hspace\{2em\}\}lr\}', r'\\begin{tabular}{lrlrlr}', tex) | ||||
| 87 | +tex = re.sub(r'\\begin\{tabular\}\{llllp\{[^}]*\}\}', r'\\begin{tabular}{lllll}', tex) | ||||
| 88 | + | ||||
| 89 | +# titlepage: pandoc cannot evaluate \ifx -> keep the \else branch only | ||||
| 90 | +tex = re.sub(r'\\ifx\\WPsubtitle\\empty\\else(.*?)\\fi', r'\1', tex, flags=re.S) | ||||
| 91 | + | ||||
| 92 | +# appendix environment / counters that pandoc does not understand | ||||
| 93 | +tex = re.sub(r'\\(begin|end)\{appendices\}', '', tex) | ||||
| 94 | +tex = re.sub(r'\\renewcommand\{\\the(table|figure)\}\{[^{}]*(\{[^{}]*\})*[^{}]*\}', '', tex) | ||||
| 95 | +tex = re.sub(r'\\setcounter\{(table|figure)\}\{0\}', '', tex) | ||||
| 96 | + | ||||
| 97 | +# ---------------------------------------------------------------- headings | ||||
| 98 | +pos = 0; out = []; ti = 0 | ||||
| 99 | +pat = re.compile(r'\\(section|subsection|subsubsection)\{') | ||||
| 100 | +while True: | ||||
| 101 | + m = pat.search(tex, pos) | ||||
| 102 | + if not m: break | ||||
| 103 | + title, end = brace_arg(tex, m.end()-1) | ||||
| 104 | + out.append(tex[pos:m.start()]) | ||||
| 105 | + lvl = m.group(1) | ||||
| 106 | + if ti < len(toc) and toc[ti][0] == lvl: | ||||
| 107 | + num = toc[ti][1]; ti += 1 | ||||
| 108 | + if re.sub(r'\W', '', toc[ti-1][2])[:12] != re.sub(r'\W', '', title)[:12]: | ||||
| 109 | + warn("heading mismatch:", repr(toc[ti-1][2][:40]), "vs", repr(title[:40])) | ||||
| 110 | + out.append(f"\\{lvl}{{{num}~{title}}}") | ||||
| 111 | + else: | ||||
| 112 | + warn("unnumbered heading kept:", lvl, title[:40]) | ||||
| 113 | + out.append(tex[m.start():end]) | ||||
| 114 | + pos = end | ||||
| 115 | +out.append(tex[pos:]); tex = "".join(out) | ||||
| 116 | +if ti != len(toc): warn(f"only {ti}/{len(toc)} toc entries consumed") | ||||
| 117 | + | ||||
| 118 | +# ---------------------------------------------------------------- floats: caption prefix | ||||
| 119 | +def fix_float(m): | ||||
| 120 | + kind, body = m.group(1), m.group(2) | ||||
| 121 | + labs = [l for l in re.findall(r'\\label\{([^}]+)\}', body) if l in labels] | ||||
| 122 | + if not labs: | ||||
| 123 | + warn("float without known label", body[:60]); return m.group(0) | ||||
| 124 | + num = labels[labs[0]] | ||||
| 125 | + word = 'Table' if kind == 'table' else 'Figure' | ||||
| 126 | + cm = re.search(r'\\caption(\[[^\]]*\])?\{', body) | ||||
| 127 | + if not cm: return m.group(0) | ||||
| 128 | + cap, end = brace_arg(body, cm.end()-1) | ||||
| 129 | + body = body[:cm.start()] + f"\\caption{{{word} {num}: {cap}}}" + body[end:] | ||||
| 130 | + return f"\\begin{{{kind}}}{body}\\end{{{kind}}}" | ||||
| 131 | +tex = re.sub(r'\\begin\{(table|figure)\}(.*?)\\end\{\1\}', fix_float, tex, flags=re.S) | ||||
| 132 | + | ||||
| 133 | +# ---------------------------------------------------------------- equations: number tag | ||||
| 134 | +def fix_eq(m): | ||||
| 135 | + body = m.group(1) | ||||
| 136 | + lm = re.search(r'\\label\{([^}]+)\}', body) | ||||
| 137 | + if lm and lm.group(1) in labels: | ||||
| 138 | + body = body.replace(lm.group(0), '') | ||||
| 139 | + body = body.rstrip() + f"\\qquad\\text{{({labels[lm.group(1)]})}}\n" | ||||
| 140 | + return "\\begin{equation}" + body + "\\end{equation}" | ||||
| 141 | +tex = re.sub(r'\\begin\{equation\}(.*?)\\end\{equation\}', fix_eq, tex, flags=re.S) | ||||
| 142 | + | ||||
| 143 | +# ---------------------------------------------------------------- cross-references | ||||
| 144 | +missing = set() | ||||
| 145 | +def ref(m): | ||||
| 146 | + k = m.group(2) | ||||
| 147 | + if k not in labels: | ||||
| 148 | + missing.add(k); return '??' | ||||
| 149 | + return f"({labels[k]})" if m.group(1) == 'eqref' else labels[k] | ||||
| 150 | +tex = re.sub(r'\\(eqref|ref)\{([^}]+)\}', ref, tex) | ||||
| 151 | +if missing: warn("unresolved labels:", sorted(missing)) | ||||
| 152 | +tex = re.sub(r'\\label\{[^}]+\}', '', tex) | ||||
| 153 | + | ||||
| 154 | +# booktabs partial rules have no Word equivalent and leak as text | ||||
| 155 | +tex = re.sub(r'\\cmidrule(\([^)]*\))?\{[^}]*\}', '', tex) | ||||
| 156 | + | ||||
| 157 | +# ---------------------------------------------------------------- pandoc shims | ||||
| 158 | +shims = r""" | ||||
| 159 | +\newcommand{\num}[1]{#1} | ||||
| 160 | +\renewcommand{\tablelegend}[1]{\par\textit{Notes:} #1} | ||||
| 161 | +\renewcommand{\tstat}[1]{(\textit{t}=#1)} | ||||
| 162 | +\newcommand{\textperiodcentered}{·} | ||||
| 163 | +""" | ||||
| 164 | +tex = tex.replace("\\begin{document}", shims + "\\begin{document}", 1) | ||||
| 165 | +tex = re.sub(r'(\\newcommand\{\\WPauthor\}\{[^}]*)\}', lambda m: m.group(1).replace('\\quad', '~~~~') + '}', tex) | ||||
| 166 | +tex = tex.replace("\\newcommand{\\textperiodcentered}{·}", "\\providecommand{\\textperiodcentered}{·}") | ||||
| 167 | +OUT.write_text(tex) | ||||
| 168 | +print("wrote", OUT, len(tex), "chars") | ||||
added
data/raw_russell3000/spid_firm_year_signals.csv
+19494 −0
Large diff (19,494 lines) is not loaded by default to keep the page fast.Load diff
Diff truncated — file too large.