SPB Git

spb/wp39_uqo

Public

WP39 v1.0: journal-style figures and Times math typography (newtxmath, notation macros, displayed definitions)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed (Nov 2, 2026) parent 42edd96 Browse files

36 changed files +358 −164

modified PROGRESS.md +10 −0
@@ -66,3 +66,13 @@
66 - `09_make_manuscript`: 15 LaTeX tables, 9 figures (validated palette), 206 macros in `paper/results_macros.tex` 66 - `09_make_manuscript`: 15 LaTeX tables, 9 figures (validated palette), 206 macros in `paper/results_macros.tex`
67 (fixes: NaN subgroup in figure, LaTeX escaping of math labels, Unicode in bib). `paper/main.pdf` compiled with 67 (fixes: NaN subgroup in figure, LaTeX escaping of math labels, Unicode in bib). `paper/main.pdf` compiled with
68 `latexmk` on the laptop: 33 pages, 0 undefined references/citations. 68 `latexmk` on the laptop: 33 pages, 0 undefined references/citations.
69+
70+## Typography and figure pass (2026-11-02 16:30–17:00)
71+- Paper: `newtxtext` + `newtxmath` (Times math with proper metrics), `mathtools`, `bm`, `dsfont`; notation macros
72+ (`\E`, `\ind{}`, `\Info`, `\Acc`, `\Loss`, `\LFDS`, `\RV`, `\BPV`, `\RS`, `\TA`, `\dlog`); displayed definitions for the
73+ surprise and its seasonal expectation (eq. 1), the log loss (eq. 2), the information set (eq. 3), the realized-measure
74+ families (eq. 4), the panel regression and the Giacomini–White statistic; author names normalised in the bibliography.
75+- Figures regenerated in journal style (`src/wp39/figures.py`): Times/STIX typography, monochrome marks distinguished by
76+ shape and line style with a single restrained accent for the 1-minute series, Panel A/B labels, inward ticks, no grid,
77+ journal widths (3.25 / 6.5 in), redesigned timeline schematic, ordinal horizon axis. Tables: grouped rules by family.
78+- `paper/main.pdf`: 34 pages, 0 undefined references.
modified literature/references.bib +13 −13
@@ -198,7 +198,7 @@
198 } 198 }
199 199
200 @article{chen2022, 200 @article{chen2022,
201− author = {CHEN, XI and CHO, YANG HA (TONY) and DOU, YIWEI and LEV, BARUCH}, 201+ author = {Chen, XI and Cho, Yang Ha (tony) and Dou, Yiwei and Lev, Baruch},
202 title = {{Predicting Future Earnings Changes Using Machine Learning and Detailed Financial Data}}, 202 title = {{Predicting Future Earnings Changes Using Machine Learning and Detailed Financial Data}},
203 journal = {Journal of Accounting Research}, 203 journal = {Journal of Accounting Research},
204 year = {2022}, 204 year = {2022},
@@ -219,7 +219,7 @@
219 } 219 }
220 220
221 @article{hirshleifer2009, 221 @article{hirshleifer2009,
222− author = {HIRSHLEIFER, DAVID and LIM, SONYA SEONGYEON and TEOH, SIEW HONG}, 222+ author = {Hirshleifer, David and Lim, Sonya Seongyeon and Teoh, Siew Hong},
223 title = {{Driven to Distraction: Extraneous Events and Underreaction to Earnings News}}, 223 title = {{Driven to Distraction: Extraneous Events and Underreaction to Earnings News}},
224 journal = {The Journal of Finance}, 224 journal = {The Journal of Finance},
225 year = {2009}, 225 year = {2009},
@@ -230,7 +230,7 @@
230 } 230 }
231 231
232 @article{dellavigna2009, 232 @article{dellavigna2009,
233− author = {DELLAVIGNA, STEFANO and POLLET, JOSHUA M.}, 233+ author = {DellaVigna, Stefano and Pollet, JOSHUA M.},
234 title = {{Investor Inattention and Friday Earnings Announcements}}, 234 title = {{Investor Inattention and Friday Earnings Announcements}},
235 journal = {The Journal of Finance}, 235 journal = {The Journal of Finance},
236 year = {2009}, 236 year = {2009},
@@ -241,7 +241,7 @@
241 } 241 }
242 242
243 @article{loughran2011, 243 @article{loughran2011,
244− author = {LOUGHRAN, TIM and MCDONALD, BILL}, 244+ author = {Loughran, Tim and McDonald, Bill},
245 title = {{When Is a Liability Not a Liability? Textual Analysis, Dictionaries, and 10-Ks}}, 245 title = {{When Is a Liability Not a Liability? Textual Analysis, Dictionaries, and 10-Ks}},
246 journal = {The Journal of Finance}, 246 journal = {The Journal of Finance},
247 year = {2011}, 247 year = {2011},
@@ -285,7 +285,7 @@
285 } 285 }
286 286
287 @article{hendershott2011, 287 @article{hendershott2011,
288− author = {HENDERSHOTT, TERRENCE and JONES, CHARLES M. and MENKVELD, ALBERT J.}, 288+ author = {Hendershott, Terrence and Jones, CHARLES M. and Menkveld, ALBERT J.},
289 title = {{Does Algorithmic Trading Improve Liquidity?}}, 289 title = {{Does Algorithmic Trading Improve Liquidity?}},
290 journal = {The Journal of Finance}, 290 journal = {The Journal of Finance},
291 year = {2011}, 291 year = {2011},
@@ -318,7 +318,7 @@
318 } 318 }
319 319
320 @article{campbell2008, 320 @article{campbell2008,
321− author = {CAMPBELL, JOHN Y. and HILSCHER, JENS and SZILAGYI, JAN}, 321+ author = {Campbell, JOHN Y. and Hilscher, Jens and Szilagyi, Jan},
322 title = {{In Search of Distress Risk}}, 322 title = {{In Search of Distress Risk}},
323 journal = {The Journal of Finance}, 323 journal = {The Journal of Finance},
324 year = {2008}, 324 year = {2008},
@@ -505,7 +505,7 @@
505 } 505 }
506 506
507 @article{bollerslev2011tails, 507 @article{bollerslev2011tails,
508− author = {BOLLERSLEV, TIM and TODOROV, VIKTOR}, 508+ author = {Bollerslev, Tim and Todorov, Viktor},
509 title = {{Tails, Fears, and Risk Premia}}, 509 title = {{Tails, Fears, and Risk Premia}},
510 journal = {The Journal of Finance}, 510 journal = {The Journal of Finance},
511 year = {2011}, 511 year = {2011},
@@ -527,7 +527,7 @@
527 } 527 }
528 528
529 @article{savor2016, 529 @article{savor2016,
530− author = {SAVOR, PAVEL and WILSON, MUNGO}, 530+ author = {Savor, Pavel and Wilson, Mungo},
531 title = {{Earnings Announcements and Systematic Risk}}, 531 title = {{Earnings Announcements and Systematic Risk}},
532 journal = {The Journal of Finance}, 532 journal = {The Journal of Finance},
533 year = {2016}, 533 year = {2016},
@@ -571,7 +571,7 @@
571 } 571 }
572 572
573 @article{brogaard2019, 573 @article{brogaard2019,
574− author = {BROGAARD, JONATHAN and HENDERSHOTT, TERRENCE and RIORDAN, RYAN}, 574+ author = {Brogaard, Jonathan and Hendershott, Terrence and Riordan, Ryan},
575 title = {{Price Discovery without Trading: Evidence from Limit Orders}}, 575 title = {{Price Discovery without Trading: Evidence from Limit Orders}},
576 journal = {The Journal of Finance}, 576 journal = {The Journal of Finance},
577 year = {2019}, 577 year = {2019},
@@ -582,7 +582,7 @@
582 } 582 }
583 583
584 @article{ang2006, 584 @article{ang2006,
585− author = {ANG, ANDREW and HODRICK, ROBERT J. and XING, YUHANG and ZHANG, XIAOYAN}, 585+ author = {Ang, Andrew and Hodrick, ROBERT J. and Xing, Yuhang and Zhang, Xiaoyan},
586 title = {{The Cross-Section of Volatility and Expected Returns}}, 586 title = {{The Cross-Section of Volatility and Expected Returns}},
587 journal = {The Journal of Finance}, 587 journal = {The Journal of Finance},
588 year = {2006}, 588 year = {2006},
@@ -626,7 +626,7 @@
626 } 626 }
627 627
628 @article{fang2009, 628 @article{fang2009,
629− author = {FANG, LILY and PERESS, JOEL}, 629+ author = {Fang, Lily and Peress, Joel},
630 title = {{Media Coverage and the Cross-section of Stock Returns}}, 630 title = {{Media Coverage and the Cross-section of Stock Returns}},
631 journal = {The Journal of Finance}, 631 journal = {The Journal of Finance},
632 year = {2009}, 632 year = {2009},
@@ -714,7 +714,7 @@
714 } 714 }
715 715
716 @article{kaniel2012, 716 @article{kaniel2012,
717− author = {KANIEL, RON and LIU, SHUMING and SAAR, GIDEON and TITMAN, SHERIDAN}, 717+ author = {Kaniel, Ron and Liu, Shuming and Saar, Gideon and Titman, Sheridan},
718 title = {{Individual Investor Trading and Return Patterns around Earnings Announcements}}, 718 title = {{Individual Investor Trading and Return Patterns around Earnings Announcements}},
719 journal = {The Journal of Finance}, 719 journal = {The Journal of Finance},
720 year = {2012}, 720 year = {2012},
@@ -845,7 +845,7 @@
845 } 845 }
846 846
847 @article{vankervel2019, 847 @article{vankervel2019,
848− author = {VAN KERVEL, VINCENT and MENKVELD, ALBERT J.}, 848+ author = {van Kervel, Vincent and Menkveld, ALBERT J.},
849 title = {{High-Frequency Trading around Large Institutional Orders}}, 849 title = {{High-Frequency Trading around Large Institutional Orders}},
850 journal = {The Journal of Finance}, 850 journal = {The Journal of Finance},
851 year = {2019}, 851 year = {2019},
modified paper/appendix/appendix.tex +4 −4
@@ -11,10 +11,10 @@ Variable & Definition & Source / step \\
11 \midrule 11 \midrule
12 $A_{i,q}$ & Earnings-release date: first \texttt{earnings\_events.event\_date} in $[E_q, F_q+10\,\mathrm{d}]$ & FinData Postgres extract (01a) \\ 12 $A_{i,q}$ & Earnings-release date: first \texttt{earnings\_events.event\_date} in $[E_q, F_q+10\,\mathrm{d}]$ & FinData Postgres extract (01a) \\
13 $F_{i,q}$, $V_{i,q}$ & SEC filing date; availability $=F+1$ day 00:00 ET & \texttt{/fundamentals} (02) \\ 13 $F_{i,q}$, $V_{i,q}$ & SEC filing date; availability $=F+1$ day 00:00 ET & \texttt{/fundamentals} (02) \\
14−$S_{i,q}$ & $(X_q-\hat X_q)/TA_{q-1}$, $X=$ operating income, $\hat X_q=X_{q-4}+\frac14\sum_{k=0}^3(X_{q-1-k}-X_{q-5-k})$ & 02, \texttt{labels.py} \\ 14+$S_{i,q}$ & $(X_q-\hat X_q)/\TA_{q-1}$, $X=$ operating income, $\hat X_q=X_{q-4}+\tfrac14\sum_{k=0}^{3}(X_{q-1-k}-X_{q-5-k})$ & 02, \texttt{labels.py} \\
15−$D_{i,q}$ & $\mathbb 1[S_{i,q}\le-0.005]$ & 02 \\ 15+$D_{i,q}$ & $\ind{S_{i,q}\le-0.005}$ & 02 \\
16−Secondary targets & net-income surprise; $\Delta$ROA$_{yoy}\le-0.01$; $(CFO_q-CFO_{q-4})/TA\le-0.01$; current ratio $-10\%$ or coverage $<1$ from $\ge1$; $|S|\ge0.005$; EPS $<$ estimate & 02 \\ 16+Secondary targets & net-income surprise; $\Delta$ROA$_{yoy}\le-0.01$; $(\mathit{CFO}_q-\mathit{CFO}_{q-4})/\TA\le-0.01$; current ratio $-10\%$ or coverage $<1$ from $\ge1$; $\abs{S}\ge0.005$; EPS $<$ estimate & 02 \\
17−Intraday families (per bar length $f\in\{1,5,30\}$ min) & RV $=\sum r^2$; BPV $=\frac{\pi}{2}\sum|r_t||r_{t-1}|$; RJ $=\max(0,\mathrm{RV}-\mathrm{BPV})/\mathrm{RV}$; RS$^-$/RV; SJV $=(\mathrm{RS}^+-\mathrm{RS}^-)/\mathrm{RV}$; realized skewness/kurtosis; $\max r^2/\mathrm{RV}$; AC(1); drift $=\ln(C/O)$; opening/closing 30-min volume shares; volume Gini; down-bar volume share; present-bar share; RV share on SPY-down bars; $\beta=\sum r\,r^{SPY}/\sum (r^{SPY})^2$; vol-of-vol; RV/Var(daily) & 03, \texttt{features.py}, \texttt{samples.py} \\ 17+Intraday families (per bar length $f\in\set{1,5,30}$ min) & $\RV=\sum_t r_t^2$; $\BPV=\tfrac{\pi}{2}\sum_t\abs{r_t}\abs{r_{t-1}}$; $\mathrm{RJ}=\max(0,\RV-\BPV)/\RV$; $\RS^-/\RV$; $\mathrm{SJV}=(\RS^+-\RS^-)/\RV$; realized skewness/kurtosis; $\max_t r_t^2/\RV$; AC(1); drift $=\log(C/O)$; opening/closing 30-min volume shares; volume Gini; down-bar volume share; present-bar share; $\RV$ share on SPY-down bars; $\hat\beta=\sum_t r_t\,r^{\mathrm{SPY}}_t/\sum_t (r^{\mathrm{SPY}}_t)^2$; vol-of-vol; $\overline{\RV}/\operatorname{Var}(r^{\mathrm{daily}})$ & 03, \texttt{features.py}, \texttt{samples.py} \\
18 Daily block & 20/60-day log returns, SD, Parkinson, skewness, kurtosis, overnight/intraday sums, volume ratios (20d, 5d vs 60d median), Amihud (60d), zero-return share, log dollar volume, max drawdown (60d), bars per day & 03 \\ 18 Daily block & 20/60-day log returns, SD, Parkinson, skewness, kurtosis, overnight/intraday sums, volume ratios (20d, 5d vs 60d median), Amihud (60d), zero-return share, log dollar volume, max drawdown (60d), bars per day & 03 \\
19 Accounting block & gross/operating margin, NI/TA, CFO/TA, accruals, cash/TA, debt/TA, current ratio, capex/TA, R\&D/revenue, coverage, loss dummy, log assets, ROA, revenue growth, net issuance, log market cap, last surprise, mean of last four surprises, trailing adverse rate, information age & 02--04 \\ 19 Accounting block & gross/operating margin, NI/TA, CFO/TA, accruals, cash/TA, debt/TA, current ratio, capex/TA, R\&D/revenue, coverage, loss dummy, log assets, ROA, revenue growth, net issuance, log market cap, last surprise, mean of last four surprises, trailing adverse rate, information age & 02--04 \\
20 Returns & split- and dividend-adjusted close-to-close from raw bars and \texttt{corporate\_actions} (ex-date rule) & 03 \\ 20 Returns & split- and dividend-adjusted close-to-close from raw bars and \texttt{corporate\_actions} (ex-date rule) & 03 \\
modified paper/figures/fig01_timeline.pdf +0 −0

Binary file not shown.

modified paper/figures/fig01_timeline.png +0 −0

Binary file not shown.

modified paper/figures/fig02_coverage.pdf +0 −0

Binary file not shown.

modified paper/figures/fig02_coverage.png +0 −0

Binary file not shown.

modified paper/figures/fig03_prevalence.pdf +0 −0

Binary file not shown.

modified paper/figures/fig03_prevalence.png +0 −0

Binary file not shown.

modified paper/figures/fig04_frequency_gains.pdf +0 −0

Binary file not shown.

modified paper/figures/fig04_frequency_gains.png +0 −0

Binary file not shown.

modified paper/figures/fig05_reliability.pdf +0 −0

Binary file not shown.

modified paper/figures/fig05_reliability.png +0 −0

Binary file not shown.

modified paper/figures/fig06_signal_evolution.pdf +0 −0

Binary file not shown.

modified paper/figures/fig06_signal_evolution.png +0 −0

Binary file not shown.

modified paper/figures/fig07_heterogeneity.pdf +0 −0

Binary file not shown.

modified paper/figures/fig07_heterogeneity.png +0 −0

Binary file not shown.

modified paper/figures/fig08_rolling.pdf +0 −0

Binary file not shown.

modified paper/figures/fig08_rolling.png +0 −0

Binary file not shown.

modified paper/figures/fig09_economic.pdf +0 −0

Binary file not shown.

modified paper/figures/fig09_economic.png +0 −0

Binary file not shown.

modified paper/main.pdf +0 −0

Binary file not shown.

modified paper/main.tex +28 −2
@@ -17,14 +17,21 @@
17 right=1in 17 right=1in
18 ]{geometry} 18 ]{geometry}
19 19
20+% --- Math (amsthm must precede newtxmath) ---
21+\usepackage{amsmath,amsthm}
22+\usepackage{mathtools}
23+\allowdisplaybreaks
24+
20 % --- Typography --- 25 % --- Typography ---
21−\usepackage{mathptmx} % Times-like font (text + math) 26+\usepackage{newtxtext} % Times text
27+\usepackage[varvw,cmintegrals,cmbraces]{newtxmath} % matching Times math with proper metrics
28+\usepackage{bm} % bold math for vectors
29+\usepackage{dsfont} % \mathds{1} indicator
22 \usepackage{microtype} % Improved typographic refinements 30 \usepackage{microtype} % Improved typographic refinements
23 \usepackage{setspace} 31 \usepackage{setspace}
24 \onehalfspacing % 1.5 line spacing 32 \onehalfspacing % 1.5 line spacing
25 33
26 % --- Math --- 34 % --- Math ---
27−\usepackage{amsmath,amssymb,amsthm}
28 35
29 % --- Tables --- 36 % --- Tables ---
30 \usepackage{booktabs} 37 \usepackage{booktabs}
@@ -116,6 +123,25 @@
116 Gatineau, Québec, Canada J9A 1L8% 123 Gatineau, Québec, Canada J9A 1L8%
117 } 124 }
118 125
126+% --- Notation macros (consistent symbols throughout) ---
127+\newcommand{\E}{\mathbb{E}}
128+\newcommand{\Prob}{\mathbb{P}}
129+\newcommand{\ind}[1]{\mathds{1}\!\left[#1\right]}
130+\newcommand{\Info}{\mathcal{I}}
131+\newcommand{\Acc}{\mathcal{A}}
132+\newcommand{\Loss}{\mathcal{L}}
133+\newcommand{\LFDS}{\mathrm{LFDS}}
134+\newcommand{\RV}{\mathrm{RV}}
135+\newcommand{\BPV}{\mathrm{BPV}}
136+\newcommand{\RS}{\mathrm{RS}}
137+\newcommand{\TA}{\mathit{TA}}
138+\newcommand{\dlog}{\Delta\Loss}
139+\newcommand{\set}[1]{\left\{#1\right\}}
140+\newcommand{\abs}[1]{\left\lvert#1\right\rvert}
141+\newcommand{\tr}{^{\mkern-1.5mu\mathsf{T}}}
142+\newcommand{\bgamma}{\bm{\gamma}}
143+\newcommand{\bX}{\bm{X}}
144+
119 % ============================================================================ 145 % ============================================================================
120 % DOCUMENT 146 % DOCUMENT
121 % ============================================================================ 147 % ============================================================================
modified paper/references.bib +13 −13
@@ -198,7 +198,7 @@
198 } 198 }
199 199
200 @article{chen2022, 200 @article{chen2022,
201− author = {CHEN, XI and CHO, YANG HA (TONY) and DOU, YIWEI and LEV, BARUCH}, 201+ author = {Chen, XI and Cho, Yang Ha (tony) and Dou, Yiwei and Lev, Baruch},
202 title = {{Predicting Future Earnings Changes Using Machine Learning and Detailed Financial Data}}, 202 title = {{Predicting Future Earnings Changes Using Machine Learning and Detailed Financial Data}},
203 journal = {Journal of Accounting Research}, 203 journal = {Journal of Accounting Research},
204 year = {2022}, 204 year = {2022},
@@ -219,7 +219,7 @@
219 } 219 }
220 220
221 @article{hirshleifer2009, 221 @article{hirshleifer2009,
222− author = {HIRSHLEIFER, DAVID and LIM, SONYA SEONGYEON and TEOH, SIEW HONG}, 222+ author = {Hirshleifer, David and Lim, Sonya Seongyeon and Teoh, Siew Hong},
223 title = {{Driven to Distraction: Extraneous Events and Underreaction to Earnings News}}, 223 title = {{Driven to Distraction: Extraneous Events and Underreaction to Earnings News}},
224 journal = {The Journal of Finance}, 224 journal = {The Journal of Finance},
225 year = {2009}, 225 year = {2009},
@@ -230,7 +230,7 @@
230 } 230 }
231 231
232 @article{dellavigna2009, 232 @article{dellavigna2009,
233− author = {DELLAVIGNA, STEFANO and POLLET, JOSHUA M.}, 233+ author = {DellaVigna, Stefano and Pollet, JOSHUA M.},
234 title = {{Investor Inattention and Friday Earnings Announcements}}, 234 title = {{Investor Inattention and Friday Earnings Announcements}},
235 journal = {The Journal of Finance}, 235 journal = {The Journal of Finance},
236 year = {2009}, 236 year = {2009},
@@ -241,7 +241,7 @@
241 } 241 }
242 242
243 @article{loughran2011, 243 @article{loughran2011,
244− author = {LOUGHRAN, TIM and MCDONALD, BILL}, 244+ author = {Loughran, Tim and McDonald, Bill},
245 title = {{When Is a Liability Not a Liability? Textual Analysis, Dictionaries, and 10-Ks}}, 245 title = {{When Is a Liability Not a Liability? Textual Analysis, Dictionaries, and 10-Ks}},
246 journal = {The Journal of Finance}, 246 journal = {The Journal of Finance},
247 year = {2011}, 247 year = {2011},
@@ -285,7 +285,7 @@
285 } 285 }
286 286
287 @article{hendershott2011, 287 @article{hendershott2011,
288− author = {HENDERSHOTT, TERRENCE and JONES, CHARLES M. and MENKVELD, ALBERT J.}, 288+ author = {Hendershott, Terrence and Jones, CHARLES M. and Menkveld, ALBERT J.},
289 title = {{Does Algorithmic Trading Improve Liquidity?}}, 289 title = {{Does Algorithmic Trading Improve Liquidity?}},
290 journal = {The Journal of Finance}, 290 journal = {The Journal of Finance},
291 year = {2011}, 291 year = {2011},
@@ -318,7 +318,7 @@
318 } 318 }
319 319
320 @article{campbell2008, 320 @article{campbell2008,
321− author = {CAMPBELL, JOHN Y. and HILSCHER, JENS and SZILAGYI, JAN}, 321+ author = {Campbell, JOHN Y. and Hilscher, Jens and Szilagyi, Jan},
322 title = {{In Search of Distress Risk}}, 322 title = {{In Search of Distress Risk}},
323 journal = {The Journal of Finance}, 323 journal = {The Journal of Finance},
324 year = {2008}, 324 year = {2008},
@@ -505,7 +505,7 @@
505 } 505 }
506 506
507 @article{bollerslev2011tails, 507 @article{bollerslev2011tails,
508− author = {BOLLERSLEV, TIM and TODOROV, VIKTOR}, 508+ author = {Bollerslev, Tim and Todorov, Viktor},
509 title = {{Tails, Fears, and Risk Premia}}, 509 title = {{Tails, Fears, and Risk Premia}},
510 journal = {The Journal of Finance}, 510 journal = {The Journal of Finance},
511 year = {2011}, 511 year = {2011},
@@ -527,7 +527,7 @@
527 } 527 }
528 528
529 @article{savor2016, 529 @article{savor2016,
530− author = {SAVOR, PAVEL and WILSON, MUNGO}, 530+ author = {Savor, Pavel and Wilson, Mungo},
531 title = {{Earnings Announcements and Systematic Risk}}, 531 title = {{Earnings Announcements and Systematic Risk}},
532 journal = {The Journal of Finance}, 532 journal = {The Journal of Finance},
533 year = {2016}, 533 year = {2016},
@@ -571,7 +571,7 @@
571 } 571 }
572 572
573 @article{brogaard2019, 573 @article{brogaard2019,
574− author = {BROGAARD, JONATHAN and HENDERSHOTT, TERRENCE and RIORDAN, RYAN}, 574+ author = {Brogaard, Jonathan and Hendershott, Terrence and Riordan, Ryan},
575 title = {{Price Discovery without Trading: Evidence from Limit Orders}}, 575 title = {{Price Discovery without Trading: Evidence from Limit Orders}},
576 journal = {The Journal of Finance}, 576 journal = {The Journal of Finance},
577 year = {2019}, 577 year = {2019},
@@ -582,7 +582,7 @@
582 } 582 }
583 583
584 @article{ang2006, 584 @article{ang2006,
585− author = {ANG, ANDREW and HODRICK, ROBERT J. and XING, YUHANG and ZHANG, XIAOYAN}, 585+ author = {Ang, Andrew and Hodrick, ROBERT J. and Xing, Yuhang and Zhang, Xiaoyan},
586 title = {{The Cross-Section of Volatility and Expected Returns}}, 586 title = {{The Cross-Section of Volatility and Expected Returns}},
587 journal = {The Journal of Finance}, 587 journal = {The Journal of Finance},
588 year = {2006}, 588 year = {2006},
@@ -626,7 +626,7 @@
626 } 626 }
627 627
628 @article{fang2009, 628 @article{fang2009,
629− author = {FANG, LILY and PERESS, JOEL}, 629+ author = {Fang, Lily and Peress, Joel},
630 title = {{Media Coverage and the Cross-section of Stock Returns}}, 630 title = {{Media Coverage and the Cross-section of Stock Returns}},
631 journal = {The Journal of Finance}, 631 journal = {The Journal of Finance},
632 year = {2009}, 632 year = {2009},
@@ -714,7 +714,7 @@
714 } 714 }
715 715
716 @article{kaniel2012, 716 @article{kaniel2012,
717− author = {KANIEL, RON and LIU, SHUMING and SAAR, GIDEON and TITMAN, SHERIDAN}, 717+ author = {Kaniel, Ron and Liu, Shuming and Saar, Gideon and Titman, Sheridan},
718 title = {{Individual Investor Trading and Return Patterns around Earnings Announcements}}, 718 title = {{Individual Investor Trading and Return Patterns around Earnings Announcements}},
719 journal = {The Journal of Finance}, 719 journal = {The Journal of Finance},
720 year = {2012}, 720 year = {2012},
@@ -845,7 +845,7 @@
845 } 845 }
846 846
847 @article{vankervel2019, 847 @article{vankervel2019,
848− author = {VAN KERVEL, VINCENT and MENKVELD, ALBERT J.}, 848+ author = {van Kervel, Vincent and Menkveld, ALBERT J.},
849 title = {{High-Frequency Trading around Large Institutional Orders}}, 849 title = {{High-Frequency Trading around Large Institutional Orders}},
850 journal = {The Journal of Finance}, 850 journal = {The Journal of Finance},
851 year = {2019}, 851 year = {2019},
modified paper/sections/mechanisms.tex +3 −3
@@ -36,11 +36,11 @@ period and therefore absent from the test subgroups.
36 \end{figure} 36 \end{figure}
37 37
38 The panel regressions of Table~\ref{tab:panel} use the out-of-fold Latent Fundamental Deterioration Signal from the 38 The panel regressions of Table~\ref{tab:panel} use the out-of-fold Latent Fundamental Deterioration Signal from the
39−expanding-window refits. The daily-benchmark signal $\mathrm{LFDS}^{\mathrm{B2}}$ is a powerful predictor of the 39+expanding-window refits. The daily-benchmark signal $\LFDS^{\mathrm{B2}}$ is a powerful predictor of the
40 realised surprise with firm and sector-by-quarter fixed effects ($\beta=\panSonlfdsTwoanddlfdslfdsTwo$, 40 realised surprise with firm and sector-by-quarter fixed effects ($\beta=\panSonlfdsTwoanddlfdslfdsTwo$,
41 $t=\pantSonlfdsTwoanddlfdslfdsTwo$, two-way clustered): a one-point increase in the predicted probability of an adverse 41 $t=\pantSonlfdsTwoanddlfdslfdsTwo$, two-way clustered): a one-point increase in the predicted probability of an adverse
42 quarter is associated with a surprise lower by 7.6\% of assets. The \emph{incremental} 1-minute component 42 quarter is associated with a surprise lower by 7.6\% of assets. The \emph{incremental} 1-minute component
43−$\mathrm{LFDS}^{\mathrm{B5}}-\mathrm{LFDS}^{\mathrm{B2}}$ has a coefficient of \panSonlfdsTwoanddlfdsdlfds{} 43+$\LFDS^{\mathrm{B5}}-\LFDS^{\mathrm{B2}}$ has a coefficient of \panSonlfdsTwoanddlfdsdlfds{}
44 ($t=\pantSonlfdsTwoanddlfdsdlfds$), one sixth of the benchmark's and marginal at conventional levels. Its interactions 44 ($t=\pantSonlfdsTwoanddlfdsdlfds$), one sixth of the benchmark's and marginal at conventional levels. Its interactions
45 with ex-ante terciles point in the direction \emph{opposite} to H3: the incremental component is less informative for 45 with ex-ante terciles point in the direction \emph{opposite} to H3: the incremental component is less informative for
46 the smallest firms ($t=\pantdlfdsxsizedlfdsxsizeZero$ on the bottom-tercile interaction) and for the least levered 46 the smallest firms ($t=\pantdlfdsxsizedlfdsxsizeZero$ on the bottom-tercile interaction) and for the least levered
@@ -68,7 +68,7 @@ $^{*}$, $^{**}$, $^{***}$: 10, 5, 1\%.
68 68
69 If the deterioration probability were news to the market, firms with a high ex-ante probability should experience 69 If the deterioration probability were news to the market, firms with a high ex-ante probability should experience
70 negative abnormal returns between the cutoff and the day after the release. Appendix Table~\ref{app:tables} shows that 70 negative abnormal returns between the cutoff and the day after the release. Appendix Table~\ref{app:tables} shows that
71−the market-adjusted return from $c+1$ to $A_q+1$ is unrelated to $\mathrm{LFDS}^{\mathrm{B5}}$ 71+the market-adjusted return from $c+1$ to $A_q+1$ is unrelated to $\LFDS^{\mathrm{B5}}$
72 ($\beta=\carcaronlfdsFivelfdsFive$, $t=\cartcaronlfdsFivelfdsFive$) and to the daily benchmark signal: by the cutoff, 72 ($\beta=\carcaronlfdsFivelfdsFive$, $t=\cartcaronlfdsFivelfdsFive$) and to the daily benchmark signal: by the cutoff,
73 twenty trading days before the release, the information that the models extract is already in prices. The incremental 73 twenty trading days before the release, the information that the models extract is already in prices. The incremental
74 1-minute component carries a positive coefficient ($\carcaronlfdsTwodlfdsdlfds$, $t=\cartcaronlfdsTwodlfdsdlfds$), 74 1-minute component carries a positive coefficient ($\carcaronlfdsTwodlfdsdlfds$, $t=\cartcaronlfdsTwodlfdsdlfds$),
modified paper/sections/methodology.tex +43 −19
@@ -8,9 +8,15 @@ $E_{i,q}$, the earnings-release date $A_{i,q}$ (the reveal date, with its before
8 10-Q/10-K filing date $F_{i,q}$ and the vendor availability instant $V_{i,q}=F_{i,q}+1$ day. The prediction cutoff is 8 10-Q/10-K filing date $F_{i,q}$ and the vendor availability instant $V_{i,q}=F_{i,q}+1$ day. The prediction cutoff is
9 $c=A_{i,q}-h$ trading days for $h\in\{40,20,10,5,1\}$, with $h=20$ as the preregistered primary horizon. A cutoff is 9 $c=A_{i,q}-h$ trading days for $h\in\{40,20,10,5,1\}$, with $h=20$ as the preregistered primary horizon. A cutoff is
10 admissible only if $c<\min(A_{i,q},F_{i,q})$ and $c>A_{i,q-1}$, so the feature window never contains the release of 10 admissible only if $c<\min(A_{i,q},F_{i,q})$ and $c>A_{i,q-1}$, so the feature window never contains the release of
11−quarter $q$ and never straddles two reveals. The information set $\mathcal{I}(c)$ contains regular-session bars with 11+quarter $q$ and never straddles two reveals. The information set is
12−session date $\le c$ and accounting facts with $V_{i,j}\le c$ only; the latest such quarter is $j^*(c)=q-1$ in the 12+\begin{equation}
13−retained sample (we require that the forecaster predicts the quarter immediately following the latest available one). 13+ \Info(c)\;=\;\set{\text{bars}_{i,t}:\ \mathrm{session}(t)\le c}\;\cup\;\set{\text{facts}_{i,j}:\ V_{i,j}\le c},
14+ \qquad j^*(c)=\max\set{j: V_{i,j}\le c},
15+ \label{eq:infoset}
16+\end{equation}
17+regular-session bars up to the close of $c$ and accounting facts available by $c$ only; the latest such quarter is
18+$j^*(c)=q-1$ in the retained sample (we require that the forecaster predicts the quarter immediately following the
19+latest available one).
14 Twelve leakage guards (future facts, fiscal-end joins, post-cutoff bars, test-period transforms, daylight-saving errors, 20 Twelve leakage guards (future facts, fiscal-end joins, post-cutoff bars, test-period transforms, daylight-saving errors,
15 identifier drift, split artefacts, release-day bars, overlapping windows across splits, survivorship, ingestion dates, 21 identifier drift, split artefacts, release-day bars, overlapping windows across splits, survivorship, ingestion dates,
16 randomised placebos) are implemented as unit tests in the replication package; the local as-of join was verified live 22 randomised placebos) are implemented as unit tests in the replication package; the local as-of join was verified live
@@ -30,10 +36,10 @@ tests and as a robustness check because it does not condition on knowledge of th
30 36
31 \subsection{Targets} 37 \subsection{Targets}
32 38
33−The primary outcome is the scaled operating-income surprise $S_{i,q}$ of equation~\eqref{eq:surprise}, with 39+The primary outcome is the scaled operating-income surprise $S_{i,q}$ of equation~\eqref{eq:surprise}, with the
34−$\mathbb{E}[X_q]=X_{q-4}+\tfrac14\sum_{k=0}^{3}(X_{q-1-k}-X_{q-5-k})$ computed from quarters available at $c$ and scaled by 40+seasonal expectation computed from quarters available at $c$ and scaled by the latest available total assets. The binary
35−the latest available total assets. The binary adverse outcome is $D_{i,q}=\mathbb{1}[S_{i,q}\le-0.5\%]$, i.e., a 41+adverse outcome is $D_{i,q}=\ind{S_{i,q}\le-0.005}$, i.e., a shortfall of at least half a percent of assets (roughly a
36−shortfall of at least half a percent of assets (roughly a fifth of a typical quarterly return on assets). Secondary 42+fifth of a typical quarterly return on assets). Secondary
37 outcomes are the net-income analogue, a year-on-year ROA decline of at least one point, a negative operating-cash-flow 43 outcomes are the net-income analogue, a year-on-year ROA decline of at least one point, a negative operating-cash-flow
38 innovation of at least one percent of assets, a liquidity/coverage deterioration (current ratio down 10\% or interest 44 innovation of at least one percent of assets, a liquidity/coverage deterioration (current ratio down 10\% or interest
39 coverage falling below one), a symmetric placebo $|S_{i,q}|\ge0.5\%$ that a pure volatility channel would predict, and an 45 coverage falling below one), a symmetric placebo $|S_{i,q}|\ge0.5\%$ that a pure volatility channel would predict, and an
@@ -45,12 +51,23 @@ target uses prices.
45 51
46 All market features are derived from the same raw 1-minute bars of the regular session (09:30 to the closing print), 52 All market features are derived from the same raw 1-minute bars of the regular session (09:30 to the closing print),
47 aggregated to 5-minute and 30-minute buckets and to daily bars. Each intraday \emph{family} is computed per session 53 aggregated to 5-minute and 30-minute buckets and to daily bars. Each intraday \emph{family} is computed per session
48−on bars of the given length and averaged (or summed) over the 20-session window ending at $c$: realized variance, 54+on bars of the given length and averaged (or summed) over the 20-session window ending at $c$. With $r_{t}$ the
49−bipower variation, relative jump, negative-semivariance share, signed-jump variation, realized skewness and kurtosis, 55+within-session log return of bar $t$ and $n$ the number of bars present, the variance families are
50−concentration of variance in the largest bar, first-order autocorrelation, intraday drift, opening and closing volume 56+\begin{equation}
51−shares, volume dispersion (Gini), down-bar volume share, share of expected bars present, share of variance on 57+ \RV=\sum_{t} r_t^{2},\qquad
52−market-down bars, realized beta to SPY (ratio of summed cross-products), variance ratio against the daily return variance, 58+ \BPV=\frac{\pi}{2}\sum_{t}\abs{r_t}\,\abs{r_{t-1}},\qquad
53−and volatility-of-volatility. The 30-minute, 5-minute and 1-minute sets therefore have the \emph{same} \kIntra{} columns. 59+ \mathrm{RJ}=\frac{\max(\RV-\BPV,0)}{\RV},\qquad
60+ \RS^{\pm}=\sum_{t} r_t^{2}\,\ind{r_t\gtrless 0},
61+ \label{eq:rv}
62+\end{equation}
63+together with the negative-semivariance share $\RS^-/\RV$, the signed-jump variation $(\RS^+-\RS^-)/\RV$, realized
64+skewness $\sqrt{n}\sum_t r_t^{3}/\RV^{3/2}$ and kurtosis $n\sum_t r_t^{4}/\RV^{2}$, the concentration $\max_t r_t^{2}/\RV$,
65+and the first-order autocorrelation of $r_t$. The activity families are the intraday drift $\log(C/O)$, the opening and
66+closing 30-minute volume shares, the volume Gini across bars, the down-bar volume share
67+$\sum_t v_t\ind{r_t<0}/\sum_t v_t$, and the share of expected bars present. The co-movement families are the realized
68+beta to SPY, $\hat\beta=\sum_t r_t r^{\mathrm{SPY}}_t/\sum_t (r^{\mathrm{SPY}}_t)^{2}$ with sums taken over the window,
69+the share of $\RV$ accruing on market-down bars, the variance ratio $\overline{\RV}/\operatorname{Var}(r^{\mathrm{daily}})$,
70+and the volatility-of-volatility (dispersion of daily $\RV$ within the window). The 30-minute, 5-minute and 1-minute sets therefore have the \emph{same} \kIntra{} columns.
54 Daily features (\kDaily{} columns) are 20- and 60-day returns, volatilities, Parkinson range, skewness, kurtosis, 71 Daily features (\kDaily{} columns) are 20- and 60-day returns, volatilities, Parkinson range, skewness, kurtosis,
55 overnight and intraday return components, volume ratios, Amihud illiquidity, zero-return share, dollar volume, maximum 72 overnight and intraday return components, volume ratios, Amihud illiquidity, zero-return share, dollar volume, maximum
56 drawdown and bar activity. Point-in-time accounting features (\kFund{} columns) are margins, ROA, accruals, cash, 73 drawdown and bar activity. Point-in-time accounting features (\kFund{} columns) are margins, ROA, accruals, cash,
@@ -80,15 +97,22 @@ The chronological split trains on cutoffs in 2012--2018, tunes on 2019--2020 and
80 of training outcomes revealed after the next split's start minus a one-quarter embargo \citep{lopezdeprado2018}. 97 of training outcomes revealed after the next split's start minus a one-quarter embargo \citep{lopezdeprado2018}.
81 Expanding-window annual refits from 2016 provide out-of-fold predictions for the panel regressions and the timing and 98 Expanding-window annual refits from 2016 provide out-of-fold predictions for the panel regressions and the timing and
82 pricing exercises; a 20\% firm hold-out evaluates transfer to unseen firms. The primary metric is log loss; Brier score, 99 pricing exercises; a 20\% firm hold-out evaluates transfer to unseen firms. The primary metric is log loss; Brier score,
83−AUC, PR-AUC and calibration are reported for every model. Paired differences (B5$-$B2, B5$-$B4, B5$-$B3) are assessed 100+AUC, PR-AUC and calibration are reported for every model. Paired differences (B5$-$B2, B5$-$B4, B5$-$B3) are assessed on the per-observation loss differential
84−with a block bootstrap over calendar quarters of the reveal date \citep{kunsch1989, politis1994} and with the 101+$d_{i,q}=\ell^{\mathrm{A}}_{i,q}-\ell^{\mathrm{B}}_{i,q}$ with a block bootstrap over calendar quarters of the reveal
85−Giacomini--White test on quarterly mean loss differentials with Newey--West variance \citep{diebold1995, 102+date \citep{kunsch1989, politis1994} and with the Giacomini--White statistic on quarterly means,
86−giacomini2006}; Holm's correction is applied within each hypothesis family \citep{holm1979}. Panel regressions 103+\begin{equation}
104+ t_{\mathrm{GW}}\;=\;\frac{\bar d}{\sqrt{\hat\sigma^{2}_{\mathrm{NW}}/T}},\qquad
105+ \bar d=\frac{1}{T}\sum_{\tau=1}^{T}\bar d_{\tau},
106+ \label{eq:gw}
107+\end{equation}
108+where $\bar d_\tau$ is the mean differential in calendar quarter $\tau$ and $\hat\sigma^{2}_{\mathrm{NW}}$ its
109+Newey--West long-run variance \citep{diebold1995, giacomini2006}; Holm's correction is applied within each hypothesis
110+family \citep{holm1979}. Panel regressions
87 \begin{equation} 111 \begin{equation}
88− S_{i,q}=\alpha_i+\delta_{s(i)\times t(q)}+\beta\,\mathrm{LFDS}_{i,c}+\gamma'X_{i,c}+\varepsilon_{i,q} 112+ S_{i,q}\;=\;\alpha_i+\delta_{s(i)\times t(q)}+\beta\,\LFDS_{i,c}+\bgamma\tr\bX_{i,c}+\varepsilon_{i,q}
89 \label{eq:panel} 113 \label{eq:panel}
90 \end{equation} 114 \end{equation}
91 use the out-of-fold B5 probability as the Latent Fundamental Deterioration Signal (LFDS), firm and sector-by-quarter 115 use the out-of-fold B5 probability as the Latent Fundamental Deterioration Signal (LFDS), firm and sector-by-quarter
92 fixed effects, and two-way clustering by firm and calendar quarter \citep{petersen2009, cameron2011}. Mechanism tests 116 fixed effects, and two-way clustering by firm and calendar quarter \citep{petersen2009, cameron2011}. Mechanism tests
93−(H3) interact the \emph{incremental} signal $\mathrm{LFDS}^{\mathrm{B5}}-\mathrm{LFDS}^{\mathrm{B2}}$ with ex-ante 117+(H3) interact the \emph{incremental} signal $\LFDS^{\mathrm{B5}}-\LFDS^{\mathrm{B2}}$ with ex-ante
94 terciles of size, illiquidity and leverage formed within each calendar quarter. 118 terciles of size, illiquidity and leverage formed within each calendar quarter.
modified paper/sections/theory_hypotheses.tex +15 −8
@@ -5,16 +5,18 @@
5 5
6 Let $X_{i,q}$ denote an accounting flow of firm $i$ for fiscal quarter $q$ (operating income in our primary 6 Let $X_{i,q}$ denote an accounting flow of firm $i$ for fiscal quarter $q$ (operating income in our primary
7 specification), revealed to the public at the earnings-release date $A_{i,q}$ and structured in the SEC filing of date 7 specification), revealed to the public at the earnings-release date $A_{i,q}$ and structured in the SEC filing of date
8−$F_{i,q}\ge A_{i,q}$. A forecaster standing at a trading day $c<A_{i,q}$ holds an information set $\mathcal{I}(c)$ that 8+$F_{i,q}\ge A_{i,q}$. A forecaster standing at a trading day $c<A_{i,q}$ holds an information set $\Info(c)$ that
9 contains, at most, prices and volumes up to the close of $c$ and the accounting statements that were \emph{available} at 9 contains, at most, prices and volumes up to the close of $c$ and the accounting statements that were \emph{available} at
10 $c$, i.e., quarters $j$ with $F_{i,j}+1\le c$. The quantity of interest is the conditional distribution of the surprise 10 $c$, i.e., quarters $j$ with $F_{i,j}+1\le c$. The quantity of interest is the conditional distribution of the surprise
11 \begin{equation} 11 \begin{equation}
12− S_{i,q} \;=\; \frac{X_{i,q}-\mathbb{E}\!\left[X_{i,q}\mid \mathcal{A}(c)\right]}{TA_{i,j^*(c)}}, 12+ S_{i,q} \;=\; \frac{X_{i,q}-\E\!\left[X_{i,q}\mid \Acc(c)\right]}{\TA_{i,j^*(c)}},
13+ \qquad
14+ \E\!\left[X_{i,q}\mid \Acc(c)\right] \;=\; X_{i,q-4}+\frac{1}{4}\sum_{k=0}^{3}\bigl(X_{i,q-1-k}-X_{i,q-5-k}\bigr),
13 \label{eq:surprise} 15 \label{eq:surprise}
14 \end{equation} 16 \end{equation}
15−where $\mathcal{A}(c)\subset\mathcal{I}(c)$ is the accounting information alone, $j^*(c)$ is the latest available quarter, 17+where $\Acc(c)\subset\Info(c)$ is the accounting information alone, $j^*(c)$ is the latest available quarter, and the
16−and the expectation is a seasonal random walk with drift \citep{foster1977}, the classic honest benchmark when no 18+expectation is a seasonal random walk with drift \citep{foster1977}, the classic honest benchmark when no point-in-time
17−point-in-time consensus exists. Semi-strong efficiency \citep{fama1970} implies that prices at $c$ impound all public 19+consensus exists. The adverse event is $D_{i,q}=\ind{S_{i,q}\le -0.005}$. Semi-strong efficiency \citep{fama1970} implies that prices at $c$ impound all public
18 information, including $\mathcal{A}(c)$; the ``prices lead earnings'' literature \citep{beaver1980, kothari1992, 20 information, including $\mathcal{A}(c)$; the ``prices lead earnings'' literature \citep{beaver1980, kothari1992,
19 collins1994} documents that daily and annual returns indeed forecast accounting changes. None of this is the question 21 collins1994} documents that daily and annual returns indeed forecast accounting changes. None of this is the question
20 we ask. Our question is whether the \emph{resolution} at which the market is observed carries incremental information, 22 we ask. Our question is whether the \emph{resolution} at which the market is observed carries incremental information,
@@ -43,11 +45,16 @@ empirical questions, and a precise null is as informative as a positive estimate
43 \subsection{Hypotheses} 45 \subsection{Hypotheses}
44 46
45 All hypotheses, endpoints and decision rules were frozen before the test period was opened (\texttt{docs/RESEARCH\_DESIGN.md} 47 All hypotheses, endpoints and decision rules were frozen before the test period was opened (\texttt{docs/RESEARCH\_DESIGN.md}
46−in the replication package). Let $\mathcal{L}(M)$ denote the out-of-sample log loss of model $M$ for the binary adverse 48+in the replication package). Let
47−outcome $D_{i,q}=\mathbb{1}[S_{i,q}\le -0.5\%]$, and let B1--B5 be the nested information sets of Table~\ref{tab:infosets}. 49+\begin{equation}
50+ \Loss(M)\;=\;-\frac{1}{N}\sum_{(i,q)}\Bigl[D_{i,q}\log \hat p^{M}_{i,q}+(1-D_{i,q})\log\bigl(1-\hat p^{M}_{i,q}\bigr)\Bigr]
51+ \label{eq:logloss}
52+\end{equation}
53+denote the out-of-sample log loss of model $M$ for the binary adverse outcome, and let B1--B5 be the nested information
54+sets of Table~\ref{tab:infosets}; the paired increment of interest is $\dlog(\mathrm{A},\mathrm{B})=\Loss(\mathrm{A})-\Loss(\mathrm{B})$.
48 55
49 \begin{description}[leftmargin=1.5em] 56 \begin{description}[leftmargin=1.5em]
50−\item[H1 (incremental information).] $\mathcal{L}(\mathrm{B5})<\mathcal{L}(\mathrm{B2})$ at the 20-trading-day horizon: 57+\item[H1 (incremental information).] $\Loss(\mathrm{B5})<\Loss(\mathrm{B2})$ at the 20-trading-day horizon:
51 one-minute features improve forecasts beyond point-in-time fundamentals and daily OHLCV. Null: no improvement. 58 one-minute features improve forecasts beyond point-in-time fundamentals and daily OHLCV. Null: no improvement.
52 \item[H2 (temporal resolution).] The gain of B5 over B2 exceeds the gains of B4 (5-minute) and B3 (30-minute) over B2, 59 \item[H2 (temporal resolution).] The gain of B5 over B2 exceeds the gains of B4 (5-minute) and B3 (30-minute) over B2,
53 with the same feature families and the same number of inputs. Null: the gains are equal. 60 with the same feature families and the same number of inputs. Null: the gains are equal.
modified paper/tables/tab05_headline.tex +2 −0
@@ -4,12 +4,14 @@ Model & N & Log loss & Brier & AUC & PR-AUC & Cal. slope & Cal. int. \\
4 \midrule 4 \midrule
5 B0 & 53,667 & 0.6282 & 0.2178 & 0.500 & 0.315 & 0.39 & -0.39 \\ 5 B0 & 53,667 & 0.6282 & 0.2178 & 0.500 & 0.315 & 0.39 & -0.39 \\
6 B0b & 53,667 & 0.6137 & 0.2126 & 0.596 & 0.371 & 0.75 & -0.00 \\ 6 B0b & 53,667 & 0.6137 & 0.2126 & 0.596 & 0.371 & 0.75 & -0.00 \\
7+\addlinespace[2pt]\midrule
7 B1 & 53,667 & 0.5527 & 0.1854 & 0.730 & 0.565 & 0.95 & 0.10 \\ 8 B1 & 53,667 & 0.5527 & 0.1854 & 0.730 & 0.565 & 0.95 & 0.10 \\
8 B2 & 53,667 & 0.5467 & 0.1828 & 0.738 & 0.572 & 0.86 & -0.13 \\ 9 B2 & 53,667 & 0.5467 & 0.1828 & 0.738 & 0.572 & 0.86 & -0.13 \\
9 B3 & 53,667 & 0.5464 & 0.1830 & 0.739 & 0.571 & 0.87 & -0.04 \\ 10 B3 & 53,667 & 0.5464 & 0.1830 & 0.739 & 0.571 & 0.87 & -0.04 \\
10 B4 & 53,667 & 0.5466 & 0.1831 & 0.739 & 0.571 & 0.87 & -0.02 \\ 11 B4 & 53,667 & 0.5466 & 0.1831 & 0.739 & 0.571 & 0.87 & -0.02 \\
11 B5 & 53,667 & 0.5481 & 0.1838 & 0.738 & 0.569 & 0.86 & 0.00 \\ 12 B5 & 53,667 & 0.5481 & 0.1838 & 0.738 & 0.569 & 0.86 & 0.00 \\
12 B5all & 53,667 & 0.5480 & 0.1837 & 0.738 & 0.569 & 0.86 & 0.00 \\ 13 B5all & 53,667 & 0.5480 & 0.1837 & 0.738 & 0.569 & 0.86 & 0.00 \\
14+\addlinespace[2pt]\midrule
13 LB1 & 53,667 & 0.4937 & 0.1634 & 0.797 & 0.655 & 1.07 & 0.13 \\ 15 LB1 & 53,667 & 0.4937 & 0.1634 & 0.797 & 0.655 & 1.07 & 0.13 \\
14 LB2 & 53,667 & 0.4869 & 0.1606 & 0.803 & 0.665 & 1.04 & -0.02 \\ 16 LB2 & 53,667 & 0.4869 & 0.1606 & 0.803 & 0.665 & 1.04 & -0.02 \\
15 LB3 & 53,667 & 0.4886 & 0.1613 & 0.801 & 0.662 & 1.03 & 0.04 \\ 17 LB3 & 53,667 & 0.4886 & 0.1613 & 0.801 & 0.662 & 1.03 & 0.04 \\
modified paper/tables/tab05b_contrasts.tex +1 −0
@@ -11,6 +11,7 @@ logistic & B5all & B2 & 53,667 & +0.0012 & [-0.0014, +0.0040] & 0.495 & & +0.00
11 logistic & B5all & B5 & 53,667 & -0.0001 & [-0.0006, +0.0004] & 0.899 & & -0.00008 \\ 11 logistic & B5all & B5 & 53,667 & -0.0001 & [-0.0006, +0.0004] & 0.899 & & -0.00008 \\
12 logistic & B2 & B1 & 53,667 & -0.0060 & [-0.0101, -0.0023] & 0.085* & & -0.00265 \\ 12 logistic & B2 & B1 & 53,667 & -0.0060 & [-0.0101, -0.0023] & 0.085* & & -0.00265 \\
13 logistic & B1 & B0 & 53,667 & -0.0754 & [-0.0846, -0.0675] & 0.000*** & & -0.03243 \\ 13 logistic & B1 & B0 & 53,667 & -0.0754 & [-0.0846, -0.0675] & 0.000*** & & -0.03243 \\
14+\addlinespace[2pt]\midrule
14 lightgbm & LB5 & LB2 & 53,667 & +0.0016 & [+0.0010, +0.0023] & 0.001*** & 0.002 & +0.00057 \\ 15 lightgbm & LB5 & LB2 & 53,667 & +0.0016 & [+0.0010, +0.0023] & 0.001*** & 0.002 & +0.00057 \\
15 lightgbm & LB4 & LB2 & 53,667 & +0.0021 & [+0.0007, +0.0035] & 0.030** & 0.049 & +0.00082 \\ 16 lightgbm & LB4 & LB2 & 53,667 & +0.0021 & [+0.0007, +0.0035] & 0.030** & 0.049 & +0.00082 \\
16 lightgbm & LB3 & LB2 & 53,667 & +0.0017 & [+0.0003, +0.0031] & 0.025** & 0.049 & +0.00074 \\ 17 lightgbm & LB3 & LB2 & 53,667 & +0.0017 & [+0.0003, +0.0031] & 0.025** & 0.049 & +0.00074 \\
modified paper/tables/tab06_panel.tex +5 −0
@@ -3,17 +3,22 @@
3 Specification & Regressor & Coef. & SE & N & Firms & Quarters \\ 3 Specification & Regressor & Coef. & SE & N & Firms & Quarters \\
4 \midrule 4 \midrule
5 S on lfds5 & lfds5 & -0.0763*** & (0.0024) & 84,759 & 3,598 & 44 \\ 5 S on lfds5 & lfds5 & -0.0763*** & (0.0024) & 84,759 & 3,598 & 44 \\
6+\addlinespace[2pt]\midrule
6 S on lfds2 and dlfds & lfds2 & -0.0765*** & (0.0024) & 84,759 & 3,598 & 44 \\ 7 S on lfds2 and dlfds & lfds2 & -0.0765*** & (0.0024) & 84,759 & 3,598 & 44 \\
7 S on lfds2 and dlfds & d lfds & -0.0129* & (0.0073) & 84,759 & 3,598 & 44 \\ 8 S on lfds2 and dlfds & d lfds & -0.0129* & (0.0073) & 84,759 & 3,598 & 44 \\
9+\addlinespace[2pt]\midrule
8 y on lfds5 & lfds5 & 0.9681*** & (0.0180) & 84,759 & 3,598 & 44 \\ 10 y on lfds5 & lfds5 & 0.9681*** & (0.0180) & 84,759 & 3,598 & 44 \\
11+\addlinespace[2pt]\midrule
9 dlfds x size & lfds2 & -0.0765*** & (0.0024) & 84,759 & 3,598 & 44 \\ 12 dlfds x size & lfds2 & -0.0765*** & (0.0024) & 84,759 & 3,598 & 44 \\
10 dlfds x size & d lfds & -0.0228*** & (0.0082) & 84,759 & 3,598 & 44 \\ 13 dlfds x size & d lfds & -0.0228*** & (0.0082) & 84,759 & 3,598 & 44 \\
11 dlfds x size & dlfds x size0 & 0.0327** & (0.0143) & 84,759 & 3,598 & 44 \\ 14 dlfds x size & dlfds x size0 & 0.0327** & (0.0143) & 84,759 & 3,598 & 44 \\
12 dlfds x size & dlfds x size2 & -0.0089 & (0.0096) & 84,759 & 3,598 & 44 \\ 15 dlfds x size & dlfds x size2 & -0.0089 & (0.0096) & 84,759 & 3,598 & 44 \\
16+\addlinespace[2pt]\midrule
13 dlfds x illiq & lfds2 & -0.0765*** & (0.0024) & 84,759 & 3,598 & 44 \\ 17 dlfds x illiq & lfds2 & -0.0765*** & (0.0024) & 84,759 & 3,598 & 44 \\
14 dlfds x illiq & d lfds & -0.0148 & (0.0090) & 84,759 & 3,598 & 44 \\ 18 dlfds x illiq & d lfds & -0.0148 & (0.0090) & 84,759 & 3,598 & 44 \\
15 dlfds x illiq & dlfds x illiq0 & -0.0118 & (0.0113) & 84,759 & 3,598 & 44 \\ 19 dlfds x illiq & dlfds x illiq0 & -0.0118 & (0.0113) & 84,759 & 3,598 & 44 \\
16 dlfds x illiq & dlfds x illiq2 & 0.0152 & (0.0157) & 84,759 & 3,598 & 44 \\ 20 dlfds x illiq & dlfds x illiq2 & 0.0152 & (0.0157) & 84,759 & 3,598 & 44 \\
21+\addlinespace[2pt]\midrule
17 dlfds x lev & lfds2 & -0.0765*** & (0.0024) & 84,759 & 3,598 & 44 \\ 22 dlfds x lev & lfds2 & -0.0765*** & (0.0024) & 84,759 & 3,598 & 44 \\
18 dlfds x lev & d lfds & -0.0239*** & (0.0083) & 84,759 & 3,598 & 44 \\ 23 dlfds x lev & d lfds & -0.0239*** & (0.0083) & 84,759 & 3,598 & 44 \\
19 dlfds x lev & dlfds x lev0 & 0.0300*** & (0.0103) & 84,759 & 3,598 & 44 \\ 24 dlfds x lev & dlfds x lev0 & 0.0300*** & (0.0103) & 84,759 & 3,598 & 44 \\
modified paper/tables/tab07_horizons.tex +1 −0
@@ -7,6 +7,7 @@ lightgbm & 20 & 53,667 & 0.804 & 0.804 & -0.0012 & [-0.0023, -0.0001] & 0.134 \\
7 lightgbm & 10 & 53,751 & 0.805 & 0.803 & +0.0006 & [-0.0005, +0.0018] & 0.240 \\ 7 lightgbm & 10 & 53,751 & 0.805 & 0.803 & +0.0006 & [-0.0005, +0.0018] & 0.240 \\
8 lightgbm & 5 & 53,575 & 0.803 & 0.803 & -0.0004 & [-0.0013, +0.0005] & 0.786 \\ 8 lightgbm & 5 & 53,575 & 0.803 & 0.803 & -0.0004 & [-0.0013, +0.0005] & 0.786 \\
9 lightgbm & 1 & 53,669 & 0.804 & 0.803 & +0.0009 & [+0.0001, +0.0017] & 0.063 \\ 9 lightgbm & 1 & 53,669 & 0.804 & 0.803 & +0.0009 & [+0.0001, +0.0017] & 0.063 \\
10+\addlinespace[2pt]\midrule
10 logistic & 40 & 49,340 & 0.740 & 0.740 & +0.0025 & [+0.0005, +0.0048] & 0.195 \\ 11 logistic & 40 & 49,340 & 0.740 & 0.740 & +0.0025 & [+0.0005, +0.0048] & 0.195 \\
11 logistic & 20 & 53,667 & 0.738 & 0.738 & +0.0013 & [-0.0013, +0.0041] & 0.518 \\ 12 logistic & 20 & 53,667 & 0.738 & 0.738 & +0.0013 & [-0.0013, +0.0041] & 0.518 \\
12 logistic & 10 & 53,751 & 0.737 & 0.738 & +0.0029 & [-0.0004, +0.0066] & 0.295 \\ 13 logistic & 10 & 53,751 & 0.737 & 0.738 & +0.0029 & [-0.0004, +0.0066] & 0.295 \\
modified paper/tables/tab10_targets.tex +5 −0
@@ -4,14 +4,19 @@ Target & Family & N & Base rate & AUC B2 & AUC B5 & $\Delta$ log loss & 95\% CI
4 \midrule 4 \midrule
5 y adverse netinc & logistic & 53,663 & 0.331 & 0.708 & 0.709 & -0.0005 & [-0.0014, +0.0004] & 0.828 \\ 5 y adverse netinc & logistic & 53,663 & 0.331 & 0.708 & 0.709 & -0.0005 & [-0.0014, +0.0004] & 0.828 \\
6 y adverse netinc & lightgbm & 53,663 & 0.331 & 0.758 & 0.757 & +0.0013 & [+0.0000, +0.0023] & 0.081 \\ 6 y adverse netinc & lightgbm & 53,663 & 0.331 & 0.758 & 0.757 & +0.0013 & [+0.0000, +0.0023] & 0.081 \\
7+\addlinespace[2pt]\midrule
7 y roa decline & logistic & 53,662 & 0.277 & 0.688 & 0.688 & -0.0014 & [-0.0030, -0.0001] & 0.334 \\ 8 y roa decline & logistic & 53,662 & 0.277 & 0.688 & 0.688 & -0.0014 & [-0.0030, -0.0001] & 0.334 \\
8 y roa decline & lightgbm & 53,662 & 0.277 & 0.722 & 0.724 & +0.0002 & [-0.0010, +0.0015] & 0.484 \\ 9 y roa decline & lightgbm & 53,662 & 0.277 & 0.722 & 0.724 & +0.0002 & [-0.0010, +0.0015] & 0.484 \\
10+\addlinespace[2pt]\midrule
9 y ocf negative & logistic & 52,999 & 0.243 & 0.704 & 0.702 & +0.0078 & [+0.0035, +0.0131] & 0.016 \\ 11 y ocf negative & logistic & 52,999 & 0.243 & 0.704 & 0.702 & +0.0078 & [+0.0035, +0.0131] & 0.016 \\
10 y ocf negative & lightgbm & 52,999 & 0.243 & 0.745 & 0.745 & +0.0022 & [+0.0005, +0.0039] & 0.019 \\ 12 y ocf negative & lightgbm & 52,999 & 0.243 & 0.745 & 0.745 & +0.0022 & [+0.0005, +0.0039] & 0.019 \\
13+\addlinespace[2pt]\midrule
11 y liquidity deterioration & logistic & 40,376 & 0.447 & 0.642 & 0.639 & +0.0006 & [-0.0007, +0.0017] & 0.335 \\ 14 y liquidity deterioration & logistic & 40,376 & 0.447 & 0.642 & 0.639 & +0.0006 & [-0.0007, +0.0017] & 0.335 \\
12 y liquidity deterioration & lightgbm & 40,376 & 0.447 & 0.694 & 0.695 & -0.0005 & [-0.0015, +0.0005] & 0.593 \\ 15 y liquidity deterioration & lightgbm & 40,376 & 0.447 & 0.694 & 0.695 & -0.0005 & [-0.0015, +0.0005] & 0.593 \\
16+\addlinespace[2pt]\midrule
13 y abs surprise & logistic & 53,667 & 0.628 & 0.783 & 0.785 & +0.0015 & [-0.0003, +0.0037] & 0.067 \\ 17 y abs surprise & logistic & 53,667 & 0.628 & 0.783 & 0.785 & +0.0015 & [-0.0003, +0.0037] & 0.067 \\
14 y abs surprise & lightgbm & 53,667 & 0.628 & 0.836 & 0.835 & +0.0008 & [+0.0002, +0.0013] & 0.008 \\ 18 y abs surprise & lightgbm & 53,667 & 0.628 & 0.836 & 0.835 & +0.0008 & [+0.0002, +0.0013] & 0.008 \\
19+\addlinespace[2pt]\midrule
15 y adverse opinc & logistic & 53,667 & 0.315 & 0.738 & 0.738 & +0.0013 & [-0.0013, +0.0041] & 0.518 \\ 20 y adverse opinc & logistic & 53,667 & 0.315 & 0.738 & 0.738 & +0.0013 & [-0.0013, +0.0041] & 0.518 \\
16 y adverse opinc & lightgbm & 53,667 & 0.315 & 0.803 & 0.801 & +0.0016 & [+0.0010, +0.0023] & 0.001 \\ 21 y adverse opinc & lightgbm & 53,667 & 0.315 & 0.803 & 0.801 & +0.0016 & [+0.0010, +0.0023] & 0.001 \\
17 \bottomrule 22 \bottomrule
modified paper/tables/tab11_economic.tex +1 −0
@@ -4,6 +4,7 @@ Score & Variant & Mean L/S (\%/mo) & FF5+MOM $\alpha$ (\%/mo) & $t$ (HAC) & Shar
4 \midrule 4 \midrule
5 LB2 & gross & -0.51 & -0.49 & -1.64 & -0.36 & 0.59 & 129 \\ 5 LB2 & gross & -0.51 & -0.49 & -1.64 & -0.36 & 0.59 & 129 \\
6 LB2 & net20bps & -0.53 & -0.51 & -1.73 & -0.38 & 0.59 & 129 \\ 6 LB2 & net20bps & -0.53 & -0.51 & -1.73 & -0.38 & 0.59 & 129 \\
7+\addlinespace[2pt]\midrule
7 LB5 & gross & -0.65 & -0.59 & -1.85 & -0.44 & 0.60 & 129 \\ 8 LB5 & gross & -0.65 & -0.59 & -1.85 & -0.44 & 0.60 & 129 \\
8 LB5 & net20bps & -0.68 & -0.62 & -1.93 & -0.46 & 0.60 & 129 \\ 9 LB5 & net20bps & -0.68 & -0.62 & -1.93 & -0.46 & 0.60 & 129 \\
9 \bottomrule 10 \bottomrule
modified paper/tables/tab11b_car.tex +1 −0
@@ -3,6 +3,7 @@
3 Specification & Regressor & Coef. & SE & N \\ 3 Specification & Regressor & Coef. & SE & N \\
4 \midrule 4 \midrule
5 car on lfds5 & lfds5 & 0.0001 & (0.0042) & 84,755 \\ 5 car on lfds5 & lfds5 & 0.0001 & (0.0042) & 84,755 \\
6+\addlinespace[2pt]\midrule
6 car on lfds2 dlfds & lfds2 & -0.0002 & (0.0042) & 84,755 \\ 7 car on lfds2 dlfds & lfds2 & -0.0002 & (0.0042) & 84,755 \\
7 car on lfds2 dlfds & d lfds & 0.0605*** & (0.0209) & 84,755 \\ 8 car on lfds2 dlfds & d lfds & 0.0605*** & (0.0209) & 84,755 \\
8 \bottomrule 9 \bottomrule
modified scripts/09_make_manuscript.py +16 −9
@@ -29,10 +29,16 @@ def stars(p):
29 return "***" if p < 0.01 else "**" if p < 0.05 else "*" if p < 0.1 else "" 29 return "***" if p < 0.01 else "**" if p < 0.05 else "*" if p < 0.1 else ""
30 30
31 31
32−def tex_table(df: pd.DataFrame, path: Path, cols: list[tuple[str, str, str]], note: str = ""): 32+def tex_table(df: pd.DataFrame, path: Path, cols: list[tuple[str, str, str]], note: str = "", group: str | None = None):
33− """cols: (column, header, fmt) — fmt is a python format or 'int'/'str'.""" 33+ """cols: (column, header, fmt) — fmt is a python format or 'int'/'str'/'tex'. `group`: column whose change inserts a rule."""
34 lines = ["\\begin{tabular}{l" + "r" * (len(cols) - 1) + "}", "\\toprule", " & ".join(h for _, h, _ in cols) + " \\\\", "\\midrule"] 34 lines = ["\\begin{tabular}{l" + "r" * (len(cols) - 1) + "}", "\\toprule", " & ".join(h for _, h, _ in cols) + " \\\\", "\\midrule"]
35+ prev = None
35 for _, r in df.iterrows(): 36 for _, r in df.iterrows():
37+ if group is not None:
38+ g = r[group] if group in df.columns else None
39+ if prev is not None and g != prev:
40+ lines.append("\\addlinespace[2pt]\\midrule")
41+ prev = g
36 cells = [] 42 cells = []
37 for c, _, fmt in cols: 43 for c, _, fmt in cols:
38 v = r[c] 44 v = r[c]
@@ -101,7 +107,8 @@ def main() -> int:
101 m20 = met[(met.h == hp) & (met.target == "y_adverse_opinc") & (met.part == "test") & (met.tag == "chrono")].copy() 107 m20 = met[(met.h == hp) & (met.target == "y_adverse_opinc") & (met.part == "test") & (met.tag == "chrono")].copy()
102 order = ["B0", "B0b", "B1", "B2", "B3", "B4", "B5", "B5all", "LB1", "LB2", "LB3", "LB4", "LB5", "LB5all"] 108 order = ["B0", "B0b", "B1", "B2", "B3", "B4", "B5", "B5all", "LB1", "LB2", "LB3", "LB4", "LB5", "LB5all"]
103 m20["o"] = m20.model.map({m: i for i, m in enumerate(order)}); m20 = m20.sort_values("o") 109 m20["o"] = m20.model.map({m: i for i, m in enumerate(order)}); m20 = m20.sort_values("o")
104− tex_table(m20, TB / "tab05_headline.tex", [("model", "Model", "str"), ("n", "N", "int"), ("logloss", "Log loss", "{:.4f}"), ("brier", "Brier", "{:.4f}"), 110+ m20["fam"] = np.where(m20.model.str.startswith("L"), "lightgbm", np.where(m20.model.str.startswith("B0"), "base", "logistic"))
111+ tex_table(m20, TB / "tab05_headline.tex", group="fam", cols= [("model", "Model", "str"), ("n", "N", "int"), ("logloss", "Log loss", "{:.4f}"), ("brier", "Brier", "{:.4f}"),
105 ("auc", "AUC", "{:.3f}"), ("pr_auc", "PR-AUC", "{:.3f}"), ("cal_slope", "Cal. slope", "{:.2f}"), ("cal_intercept", "Cal. int.", "{:.2f}")]) 112 ("auc", "AUC", "{:.3f}"), ("pr_auc", "PR-AUC", "{:.3f}"), ("cal_slope", "Cal. slope", "{:.2f}"), ("cal_intercept", "Cal. int.", "{:.2f}")])
106 for _, r in m20.iterrows(): 113 for _, r in m20.iterrows():
107 macro(f"ll{r.model}", r.logloss, "{:.4f}"); macro(f"auc{r.model}", r.auc, "{:.3f}") 114 macro(f"ll{r.model}", r.logloss, "{:.4f}"); macro(f"auc{r.model}", r.auc, "{:.3f}")
@@ -110,7 +117,7 @@ def main() -> int:
110 con["ci"] = con.apply(lambda r: f"[{r.ci_lo:+.4f}, {r.ci_hi:+.4f}]", axis=1) 117 con["ci"] = con.apply(lambda r: f"[{r.ci_lo:+.4f}, {r.ci_hi:+.4f}]", axis=1)
111 con["gwp"] = con.apply(lambda r: f"{r.gw_p:.3f}{stars(r.gw_p)}", axis=1) 118 con["gwp"] = con.apply(lambda r: f"{r.gw_p:.3f}{stars(r.gw_p)}", axis=1)
112 con["holm"] = con["gw_p_holm"].map(lambda v: "" if pd.isna(v) else f"{v:.3f}") 119 con["holm"] = con["gw_p_holm"].map(lambda v: "" if pd.isna(v) else f"{v:.3f}")
113− tex_table(con, TB / "tab05b_contrasts.tex", [("family", "Family", "str"), ("a", "A", "str"), ("b", "B", "str"), ("n", "N", "int"), ("d_logloss", "$\\Delta$ log loss", "{:+.4f}"), 120+ tex_table(con, TB / "tab05b_contrasts.tex", group="family", cols=[("family", "Family", "str"), ("a", "A", "str"), ("b", "B", "str"), ("n", "N", "int"), ("d_logloss", "$\\Delta$ log loss", "{:+.4f}"),
114 ("ci", "95\\% CI", "str"), ("gwp", "GW $p$", "str"), ("holm", "Holm", "str"), ("d_brier", "$\\Delta$ Brier", "{:+.5f}")]) 121 ("ci", "95\\% CI", "str"), ("gwp", "GW $p$", "str"), ("holm", "Holm", "str"), ("d_brier", "$\\Delta$ Brier", "{:+.5f}")])
115 for _, r in con.iterrows(): 122 for _, r in con.iterrows():
116 k = f"{r.family[:3]}{r.a}vs{r.b}".replace("B5all", "Ball") 123 k = f"{r.family[:3]}{r.a}vs{r.b}".replace("B5all", "Ball")
@@ -119,14 +126,14 @@ def main() -> int:
119 # ---------------- Table 7: horizon ladder, secondary targets, firm holdout 126 # ---------------- Table 7: horizon ladder, secondary targets, firm holdout
120 lad = pd.read_csv(E / "horizon_ladder.csv") 127 lad = pd.read_csv(E / "horizon_ladder.csv")
121 lad["ci"] = lad.apply(lambda r: f"[{r.ci_lo:+.4f}, {r.ci_hi:+.4f}]", axis=1) 128 lad["ci"] = lad.apply(lambda r: f"[{r.ci_lo:+.4f}, {r.ci_hi:+.4f}]", axis=1)
122− tex_table(lad.sort_values(["family", "h"], ascending=[True, False]), TB / "tab07_horizons.tex", [("family", "Family", "str"), ("h", "$h$", "int"), ("n", "N", "int"), 129+ tex_table(lad.sort_values(["family", "h"], ascending=[True, False]), TB / "tab07_horizons.tex", group="family", cols=[("family", "Family", "str"), ("h", "$h$", "int"), ("n", "N", "int"),
123 ("auc_b", "AUC B2", "{:.3f}"), ("auc_a", "AUC B5", "{:.3f}"), ("d_logloss", "$\\Delta$ log loss", "{:+.4f}"), ("ci", "95\\% CI", "str"), ("gw_p", "GW $p$", "{:.3f}")]) 130 ("auc_b", "AUC B2", "{:.3f}"), ("auc_a", "AUC B5", "{:.3f}"), ("d_logloss", "$\\Delta$ log loss", "{:+.4f}"), ("ci", "95\\% CI", "str"), ("gw_p", "GW $p$", "{:.3f}")])
124 h4 = json.load(open(E / "h4_timing.json")) 131 h4 = json.load(open(E / "h4_timing.json"))
125 for fam, v in h4.items(): 132 for fam, v in h4.items():
126 macro(f"h4rho{fam}", v["spearman_gain_vs_minus_h"], "{:+.2f}"); macro(f"h4p{fam}", v["p"], "{:.2f}") 133 macro(f"h4rho{fam}", v["spearman_gain_vs_minus_h"], "{:+.2f}"); macro(f"h4p{fam}", v["p"], "{:.2f}")
127 sec = pd.read_csv(E / "secondary_targets.csv") 134 sec = pd.read_csv(E / "secondary_targets.csv")
128 sec["ci"] = sec.apply(lambda r: f"[{r.ci_lo:+.4f}, {r.ci_hi:+.4f}]", axis=1) 135 sec["ci"] = sec.apply(lambda r: f"[{r.ci_lo:+.4f}, {r.ci_hi:+.4f}]", axis=1)
129− tex_table(sec, TB / "tab10_targets.tex", [("target", "Target", "str"), ("family", "Family", "str"), ("n", "N", "int"), ("base_rate", "Base rate", "{:.3f}"), 136+ tex_table(sec, TB / "tab10_targets.tex", group="target", cols=[("target", "Target", "str"), ("family", "Family", "str"), ("n", "N", "int"), ("base_rate", "Base rate", "{:.3f}"),
130 ("auc_b", "AUC B2", "{:.3f}"), ("auc_a", "AUC B5", "{:.3f}"), ("d_logloss", "$\\Delta$ log loss", "{:+.4f}"), ("ci", "95\\% CI", "str"), ("gw_p", "GW $p$", "{:.3f}")]) 137 ("auc_b", "AUC B2", "{:.3f}"), ("auc_a", "AUC B5", "{:.3f}"), ("d_logloss", "$\\Delta$ log loss", "{:+.4f}"), ("ci", "95\\% CI", "str"), ("gw_p", "GW $p$", "{:.3f}")])
131 fh = pd.read_csv(E / "firm_holdout.csv"); fh["ci"] = fh.apply(lambda r: f"[{r.ci_lo:+.4f}, {r.ci_hi:+.4f}]", axis=1) 138 fh = pd.read_csv(E / "firm_holdout.csv"); fh["ci"] = fh.apply(lambda r: f"[{r.ci_lo:+.4f}, {r.ci_hi:+.4f}]", axis=1)
132 for _, r in fh.iterrows(): 139 for _, r in fh.iterrows():
@@ -136,7 +143,7 @@ def main() -> int:
136 pr = pd.read_csv(E / "panel_regressions.csv") 143 pr = pd.read_csv(E / "panel_regressions.csv")
137 pr = pr[pr["var"].isin(["lfds5", "lfds2", "d_lfds", "dlfds_x_size0", "dlfds_x_size2", "dlfds_x_illiq0", "dlfds_x_illiq2", "dlfds_x_lev0", "dlfds_x_lev2"])].copy() 144 pr = pr[pr["var"].isin(["lfds5", "lfds2", "d_lfds", "dlfds_x_size0", "dlfds_x_size2", "dlfds_x_illiq0", "dlfds_x_illiq2", "dlfds_x_lev0", "dlfds_x_lev2"])].copy()
138 pr["coef_s"] = pr.apply(lambda r: f"{r.coef:.4f}{stars(r.p)}", axis=1); pr["se_s"] = pr.se.map(lambda v: f"({v:.4f})") 145 pr["coef_s"] = pr.apply(lambda r: f"{r.coef:.4f}{stars(r.p)}", axis=1); pr["se_s"] = pr.se.map(lambda v: f"({v:.4f})")
139− tex_table(pr, TB / "tab06_panel.tex", [("spec", "Specification", "str"), ("var", "Regressor", "str"), ("coef_s", "Coef.", "str"), ("se_s", "SE", "str"), ("n", "N", "int"), ("n_firms", "Firms", "int"), ("n_quarters", "Quarters", "int")]) 146+ tex_table(pr, TB / "tab06_panel.tex", group="spec", cols=[("spec", "Specification", "str"), ("var", "Regressor", "str"), ("coef_s", "Coef.", "str"), ("se_s", "SE", "str"), ("n", "N", "int"), ("n_firms", "Firms", "int"), ("n_quarters", "Quarters", "int")])
140 for _, r in pr.iterrows(): 147 for _, r in pr.iterrows():
141 macro(f"pan{r.spec.replace('_', '')}{r['var'].replace('_', '')}", r.coef, "{:+.4f}"); macro(f"pant{r.spec.replace('_', '')}{r['var'].replace('_', '')}", r.t, "{:.2f}") 148 macro(f"pan{r.spec.replace('_', '')}{r['var'].replace('_', '')}", r.coef, "{:+.4f}"); macro(f"pant{r.spec.replace('_', '')}{r['var'].replace('_', '')}", r.t, "{:.2f}")
142 # ---------------- Table 7b: mechanisms / subgroups; Table 8: placebos 149 # ---------------- Table 7b: mechanisms / subgroups; Table 8: placebos
@@ -152,13 +159,13 @@ def main() -> int:
152 for m, v in h5.items(): 159 for m, v in h5.items():
153 for k in ("gross", "net20bps"): 160 for k in ("gross", "net20bps"):
154 a = v[k]; rows.append({"model": m, "variant": k, "alpha": 100 * a["alpha_monthly"], "t": a["alpha_t"], "mean_ls": 100 * a["mean_ls"], "sharpe": a["sharpe_ann"], "T": a["T"], "turnover": v["avg_turnover"]}) 161 a = v[k]; rows.append({"model": m, "variant": k, "alpha": 100 * a["alpha_monthly"], "t": a["alpha_t"], "mean_ls": 100 * a["mean_ls"], "sharpe": a["sharpe_ann"], "T": a["T"], "turnover": v["avg_turnover"]})
155− tex_table(pd.DataFrame(rows), TB / "tab11_economic.tex", [("model", "Score", "str"), ("variant", "Variant", "str"), ("mean_ls", "Mean L/S (\\%/mo)", "{:.2f}"), ("alpha", "FF5+MOM $\\alpha$ (\\%/mo)", "{:+.2f}"), 162+ tex_table(pd.DataFrame(rows), TB / "tab11_economic.tex", group="model", cols=[("model", "Score", "str"), ("variant", "Variant", "str"), ("mean_ls", "Mean L/S (\\%/mo)", "{:.2f}"), ("alpha", "FF5+MOM $\\alpha$ (\\%/mo)", "{:+.2f}"),
156 ("t", "$t$ (HAC)", "{:.2f}"), ("sharpe", "Sharpe (ann.)", "{:.2f}"), ("turnover", "Turnover", "{:.2f}"), ("T", "Months", "int")]) 163 ("t", "$t$ (HAC)", "{:.2f}"), ("sharpe", "Sharpe (ann.)", "{:.2f}"), ("turnover", "Turnover", "{:.2f}"), ("T", "Months", "int")])
157 for m, v in h5.items(): 164 for m, v in h5.items():
158 macro(f"alpha{m}gross", 100 * v["gross"]["alpha_monthly"], "{:+.2f}"); macro(f"alphat{m}gross", v["gross"]["alpha_t"], "{:.2f}") 165 macro(f"alpha{m}gross", 100 * v["gross"]["alpha_monthly"], "{:+.2f}"); macro(f"alphat{m}gross", v["gross"]["alpha_t"], "{:.2f}")
159 macro(f"alpha{m}net", 100 * v["net20bps"]["alpha_monthly"], "{:+.2f}"); macro(f"alphat{m}net", v["net20bps"]["alpha_t"], "{:.2f}") 166 macro(f"alpha{m}net", 100 * v["net20bps"]["alpha_monthly"], "{:+.2f}"); macro(f"alphat{m}net", v["net20bps"]["alpha_t"], "{:.2f}")
160 carr = car[car["var"].isin(["lfds5", "lfds2", "d_lfds"])].copy(); carr["coef_s"] = carr.apply(lambda r: f"{r.coef:.4f}{stars(r.p)}", axis=1); carr["se_s"] = carr.se.map(lambda v: f"({v:.4f})") 167 carr = car[car["var"].isin(["lfds5", "lfds2", "d_lfds"])].copy(); carr["coef_s"] = carr.apply(lambda r: f"{r.coef:.4f}{stars(r.p)}", axis=1); carr["se_s"] = carr.se.map(lambda v: f"({v:.4f})")
161− tex_table(carr, TB / "tab11b_car.tex", [("spec", "Specification", "str"), ("var", "Regressor", "str"), ("coef_s", "Coef.", "str"), ("se_s", "SE", "str"), ("n", "N", "int")]) 168+ tex_table(carr, TB / "tab11b_car.tex", group="spec", cols=[("spec", "Specification", "str"), ("var", "Regressor", "str"), ("coef_s", "Coef.", "str"), ("se_s", "SE", "str"), ("n", "N", "int")])
162 for _, r in carr.iterrows(): 169 for _, r in carr.iterrows():
163 macro(f"car{r.spec.replace('_', '')}{r['var'].replace('_', '')}", r.coef, "{:+.4f}"); macro(f"cart{r.spec.replace('_', '')}{r['var'].replace('_', '')}", r.t, "{:.2f}") 170 macro(f"car{r.spec.replace('_', '')}{r['var'].replace('_', '')}", r.coef, "{:+.4f}"); macro(f"cart{r.spec.replace('_', '')}{r['var'].replace('_', '')}", r.t, "{:.2f}")
164 # continuous + power 171 # continuous + power
modified src/wp39/figures.py +197 −93
@@ -1,5 +1,6 @@
1−"""Journal figures (vector PDF, matplotlib). One axis per panel, thin marks, fixed categorical order, direct labels. 1+"""Journal-style figures (vector PDF): Times/STIX typography, monochrome marks distinguished by shape and line style,
2−Palette validated with the dataviz validator (light surface): blue, orange, aqua, yellow (+ gray for references).""" 2+panel labels, inward ticks, no chart junk. Every exhibit is drawn from saved artifacts; the only schematic (timeline) is
3+labelled as such. Widths follow journal columns: 3.25 in (single) and 6.5 in (double)."""
3 from __future__ import annotations 4 from __future__ import annotations
4 5
5 from pathlib import Path 6 from pathlib import Path
@@ -9,148 +10,251 @@ matplotlib.use("Agg")
9 import matplotlib.pyplot as plt # noqa: E402 10 import matplotlib.pyplot as plt # noqa: E402
10 import numpy as np # noqa: E402 11 import numpy as np # noqa: E402
11 import pandas as pd # noqa: E402 12 import pandas as pd # noqa: E402
13+from matplotlib import ticker # noqa: E402
12 14
13−SERIES = {"daily": "#2a78d6", "30m": "#eb6834", "5m": "#1baf7a", "1m": "#eda100", "fund": "#6250d6", "ref": "#8a8a86", "neg": "#e34948"} 15+INK, INK2, INK3, INK4 = "#000000", "#4d4d4d", "#8c8c8c", "#c8c8c8"
14−MODEL_COLOR = {"B1": SERIES["fund"], "B2": SERIES["daily"], "B3": SERIES["30m"], "B4": SERIES["5m"], "B5": SERIES["1m"], "B5all": "#52514e", 16+ACCENT = "#1f4e79" # single restrained accent (1-minute series) — passes the contrast check on white
15− "LB1": SERIES["fund"], "LB2": SERIES["daily"], "LB3": SERIES["30m"], "LB4": SERIES["5m"], "LB5": SERIES["1m"], "LB5all": "#52514e"}
16−MODEL_LABEL = {"B1": "B1 fundamentals", "B2": "B2 + daily", "B3": "B3 + 30-min", "B4": "B4 + 5-min", "B5": "B5 + 1-min", "B5all": "all frequencies"}
17 17
18−plt.rcParams.update({"font.family": "serif", "font.size": 9, "axes.spines.top": False, "axes.spines.right": False, 18+# Fixed encoding per information set: marker, line style, colour, label
19− "axes.edgecolor": "#8a8a86", "axes.linewidth": 0.6, "xtick.color": "#52514e", "ytick.color": "#52514e", 19+STYLE = {
20− "axes.labelcolor": "#0b0b0b", "grid.color": "#e6e5e1", "grid.linewidth": 0.5, "legend.frameon": False, 20+ "B1": dict(marker="x", ls=":", color=INK3, label="B1 fundamentals"),
21− "figure.dpi": 150, "savefig.bbox": "tight"}) 21+ "B2": dict(marker="o", ls="-", color=INK, label="B2 + daily"),
22+ "B3": dict(marker="s", ls="--", color=INK2, label="B3 + 30-min"),
23+ "B4": dict(marker="^", ls="-.", color=INK3, label="B4 + 5-min"),
24+ "B5": dict(marker="D", ls="-", color=ACCENT, label="B5 + 1-min"),
25+ "B5all": dict(marker="v", ls=":", color=INK2, label="all frequencies"),
26+}
27+for k in list(STYLE):
28+ STYLE["L" + k] = dict(STYLE[k], label=STYLE[k]["label"].replace("B", "LB", 1))
29+
30+plt.rcParams.update({
31+ "font.family": "serif", "font.serif": ["Times New Roman", "Times", "STIX Two Text", "DejaVu Serif"],
32+ "mathtext.fontset": "stix", "font.size": 9, "axes.titlesize": 9, "axes.labelsize": 9, "legend.fontsize": 8,
33+ "xtick.labelsize": 8, "ytick.labelsize": 8,
34+ "axes.spines.top": False, "axes.spines.right": False, "axes.edgecolor": INK, "axes.linewidth": 0.6,
35+ "xtick.direction": "in", "ytick.direction": "in", "xtick.major.size": 3, "ytick.major.size": 3,
36+ "xtick.major.width": 0.6, "ytick.major.width": 0.6, "axes.grid": False, "legend.frameon": False,
37+ "lines.linewidth": 1.1, "lines.markersize": 4.5, "lines.markeredgewidth": 0.8,
38+ "figure.dpi": 150, "savefig.bbox": "tight", "savefig.pad_inches": 0.02, "pdf.fonttype": 42,
39+})
40+W1, W2 = 3.25, 6.5
41+
42+
43+def _panel(ax, letter: str, title: str):
44+ ax.set_title(f"Panel {letter}. {title}", loc="left", fontsize=9, fontweight="bold", pad=6)
45+
46+
47+def _thousands(ax, axis="y"):
48+ (ax.yaxis if axis == "y" else ax.xaxis).set_major_formatter(ticker.FuncFormatter(lambda v, _: f"{v:,.0f}"))
22 49
23 50
24 def _save(fig, out: Path, name: str): 51 def _save(fig, out: Path, name: str):
25 out.mkdir(parents=True, exist_ok=True) 52 out.mkdir(parents=True, exist_ok=True)
26− fig.savefig(out / f"{name}.pdf"); fig.savefig(out / f"{name}.png", dpi=200); plt.close(fig) 53+ fig.savefig(out / f"{name}.pdf")
54+ fig.savefig(out / f"{name}.png", dpi=220)
55+ plt.close(fig)
56+
27 57
58+def _errorbar(ax, x, y, lo, hi, **kw):
59+ ax.errorbar(x, y, yerr=[np.asarray(y) - np.asarray(lo), np.asarray(hi) - np.asarray(y)], capsize=2.5, capthick=0.7,
60+ elinewidth=0.7, **kw)
28 61
62+
63+# ----------------------------------------------------------------------------------------------------- Figure 1
29 def fig_timeline(out: Path, h_list=(40, 20, 10, 5, 1)): 64 def fig_timeline(out: Path, h_list=(40, 20, 10, 5, 1)):
30− """Event-time timeline and the no-leakage rule (schematic drawn from the protocol, labelled as such).""" 65+ """Schematic (not to scale beyond the cutoffs): events after A_q are spread out for legibility."""
31− fig, ax = plt.subplots(figsize=(6.4, 2.2)) 66+ fig, ax = plt.subplots(figsize=(W2, 2.5))
32− ax.set_xlim(-75, 15); ax.set_ylim(0, 3); ax.axis("off") 67+ ax.set_xlim(-74, 24); ax.set_ylim(-1.9, 2.45); ax.axis("off")
33− ax.plot([-75, 12], [1, 1], color="#52514e", lw=1) 68+ ax.annotate("", xy=(22, 0), xytext=(-74, 0), arrowprops=dict(arrowstyle="-|>", lw=0.9, color=INK))
34− for x, lab, c in ((-63, "$A_{q-1}$\nprevious release", SERIES["ref"]), (0, "$A_q$\nrelease of quarter $q$", SERIES["neg"]), 69+ ax.text(22.5, 0, "time", va="center", ha="left", fontsize=7, color=INK2)
35− (1, "$F_q$ filing", SERIES["ref"]), (2, "$V_q=F_q+1$\nfacts available", SERIES["ref"])): 70+ # feature window for h = 20
36− ax.plot([x], [1], marker="|", ms=14, color=c, mew=1.5) 71+ ax.add_patch(plt.Rectangle((-40, -0.26), 20, 0.52, facecolor=INK4, edgecolor="none"))
37− ax.text(x, 0.15 if x not in (1,) else 1.55, lab, ha="center", va="center", fontsize=7, color="#0b0b0b") 72+ ax.text(-30, 0.95, "20-session feature window for $h=20$\n(bars with session $\\leq c$)", ha="center", va="bottom", fontsize=7)
38− for i, h in enumerate(h_list): 73+ # events with alternating label positions and leader lines
39− ax.plot([-h], [1], marker="o", ms=4, color=SERIES["1m"], mec="white", mew=0.8) 74+ events = [(-63, "$A_{q-1}$: previous release", -1.05, INK2), (0, "$A_q$: earnings release\n(reveal date)", -1.05, INK),
40− ax.text(-h, 1.35 + 0.28 * (i % 2), f"$c=A_q-{h}$", ha="center", fontsize=6.5, color="#52514e") 75+ (7, "$F_q$: SEC filing", 1.08, INK2), (12, "$V_q=F_q+1$: facts available", 1.75, INK2)]
41− ax.add_patch(plt.Rectangle((-40, 0.85), 20, 0.3, color=SERIES["1m"], alpha=0.25, lw=0)) 76+ for x, lab, ytxt, c in events:
42− ax.text(-30, 2.6, "20-session feature window ends at $c$; bars after $c$ and facts with $V_j>c$ never enter the information set", 77+ ax.plot([x, x], [-0.42, 0.42], color=c, lw=1.2)
43− ha="center", fontsize=7, color="#52514e") 78+ if ytxt < 0:
44− ax.set_title("Event-time design (schematic of the protocol, not data)", fontsize=8, loc="left") 79+ ax.plot([x, x], [-0.42, ytxt + 0.1], color=c, lw=0.5, ls=":")
80+ ax.text(x, ytxt, lab, ha="center", va="top", fontsize=7, color=c)
81+ else:
82+ ax.plot([x, x], [0.42, ytxt - 0.08], color=c, lw=0.5, ls=":")
83+ ax.text(x, ytxt, lab, ha="center", va="bottom", fontsize=7, color=c)
84+ for h in h_list:
85+ ax.plot([-h], [0], marker="D", ms=4, color=ACCENT, mec="white", mew=0.6, zorder=5)
86+ ax.text(-h, 0.3, f"$-{h}$", ha="center", va="bottom", fontsize=6.5, color=ACCENT)
87+ ax.text(-74, 2.3, "cutoffs $c=A_q-h$ trading days, $h\\in\\{40,20,10,5,1\\}$; admissible iff $c<\\min(A_q,F_q)$ and $c>A_{q-1}$",
88+ ha="left", va="top", fontsize=7.5)
89+ ax.text(22, -1.85, "Schematic of the protocol (not data; spacing after $A_q$ exaggerated).", ha="right", va="bottom", fontsize=6.5, color=INK3, style="italic")
45 _save(fig, out, "fig01_timeline") 90 _save(fig, out, "fig01_timeline")
46 91
47 92
93+# ----------------------------------------------------------------------------------------------------- Figure 2
48 def fig_coverage(out: Path, years: pd.DataFrame, sizes: pd.DataFrame | None = None): 94 def fig_coverage(out: Path, years: pd.DataFrame, sizes: pd.DataFrame | None = None):
49− """years: columns year, firms, firm_quarters, adverse_rate.""" 95+ fig, axes = plt.subplots(1, 2, figsize=(W2, 2.5))
50− fig, axes = plt.subplots(1, 2, figsize=(6.4, 2.4))
51 ax = axes[0] 96 ax = axes[0]
52− ax.bar(years["year"], years["firms"], color=SERIES["daily"], width=0.8) 97+ ax.bar(years["year"], years["firms"], color=INK2, width=0.72, edgecolor="none")
53− ax.set_ylabel("Firms in sample"); ax.set_xlabel("Year of cutoff"); ax.grid(axis="y") 98+ _panel(ax, "A", "Firms in the primary cohort by year")
54− for x, v in zip(years["year"].iloc[::3], years["firms"].iloc[::3]): 99+ ax.set_xlabel("Year of cutoff"); ax.set_ylabel("Number of firms"); _thousands(ax)
55− ax.text(x, v, f"{int(v):,}", ha="center", va="bottom", fontsize=6, color="#52514e") 100+ ax.set_xticks(years["year"][::2]); ax.tick_params(axis="x", rotation=0)
56 ax = axes[1] 101 ax = axes[1]
57− ax.plot(years["year"], years["adverse_rate"], color=SERIES["neg"], lw=2, marker="o", ms=4, mec="white") 102+ ax.plot(years["year"], 100 * years["adverse_rate"], color=INK, marker="o", ms=4, mfc="white")
58− ax.set_ylabel("Adverse-surprise rate"); ax.set_xlabel("Year of cutoff"); ax.grid(axis="y"); ax.set_ylim(0, None) 103+ _panel(ax, "B", "Adverse-surprise rate by year (%)")
104+ ax.set_xlabel("Year of cutoff"); ax.set_ylabel("Share of firm-quarters (%)"); ax.set_ylim(0, None)
105+ ax.set_xticks(years["year"][::2])
106+ ax.axvspan(2020.5, 2026.9, color=INK4, alpha=0.35, lw=0); ax.text(2023.7, 2, "test period", ha="center", fontsize=7, color=INK2)
107+ fig.subplots_adjust(wspace=0.32)
59 _save(fig, out, "fig02_coverage") 108 _save(fig, out, "fig02_coverage")
60 109
61 110
111+# ----------------------------------------------------------------------------------------------------- Figure 3
62 def fig_prevalence(out: Path, prev: pd.DataFrame): 112 def fig_prevalence(out: Path, prev: pd.DataFrame):
63− """prev: columns group (str), label, rate, lo, hi — conditional adverse prevalence by ex-ante characteristics.""" 113+ fig, ax = plt.subplots(figsize=(W2, 2.9))
64− fig, ax = plt.subplots(figsize=(6.0, 2.6))
65 y = np.arange(len(prev)) 114 y = np.arange(len(prev))
66− ax.hlines(y, prev["lo"], prev["hi"], color="#c3c2b7", lw=2) 115+ ax.hlines(y, 100 * prev["lo"], 100 * prev["hi"], color=INK, lw=0.9)
67− ax.plot(prev["rate"], y, "o", color=SERIES["neg"], ms=5, mec="white") 116+ ax.plot(100 * prev["rate"], y, "o", color=INK, ms=5, mfc="white")
117+ for yi, r in zip(y, prev.itertuples()):
118+ ax.text(100 * r.hi + 0.4, yi, f"{100 * r.rate:.1f}", va="center", ha="left", fontsize=7, color=INK2)
68 ax.set_yticks(y); ax.set_yticklabels(prev["label"]); ax.invert_yaxis() 119 ax.set_yticks(y); ax.set_yticklabels(prev["label"]); ax.invert_yaxis()
69− ax.set_xlabel("Share of firm-quarters with an adverse operating-income surprise (test period)"); ax.grid(axis="x") 120+ ax.set_xlabel("Adverse operating-income surprise, share of firm-quarters (%), test period")
121+ ax.axvline(100 * prev["rate"].iloc[0], color=INK4, lw=0.8, zorder=0)
122+ ax.spines["left"].set_visible(False); ax.tick_params(axis="y", length=0)
70 _save(fig, out, "fig03_prevalence") 123 _save(fig, out, "fig03_prevalence")
71 124
72 125
126+# ----------------------------------------------------------------------------------------------------- Figure 4
73 def fig_frequency_gains(out: Path, contrasts: pd.DataFrame): 127 def fig_frequency_gains(out: Path, contrasts: pd.DataFrame):
74− """Paired Δ log loss vs B2 for B3/B4/B5 (logistic and LightGBM), with 95 % block-bootstrap intervals.""" 128+ fams = [("logistic", "", "Penalised logistic regression"), ("lightgbm", "L", "Gradient-boosted trees (equal budget)")]
75− fam = [("logistic", ""), ("lightgbm", "L")] 129+ fig, axes = plt.subplots(1, 2, figsize=(W2, 2.9), sharey=True)
76− fig, axes = plt.subplots(1, 2, figsize=(6.4, 2.6), sharey=True) 130+ order = ["B3", "B4", "B5", "B5all"]
77− for ax, (name, pre) in zip(axes, fam): 131+ for ax, (fam, pre, title), letter in zip(axes, fams, "AB"):
78− sub = contrasts[(contrasts.family == name) & (contrasts.b == pre + "B2") & (contrasts.a.isin([pre + "B3", pre + "B4", pre + "B5", pre + "B5all"]))] 132+ sub = contrasts[(contrasts.family == fam) & (contrasts.b == pre + "B2")].copy()
79− order = [pre + "B3", pre + "B4", pre + "B5", pre + "B5all"] 133+ sub["key"] = sub["a"].str.replace("^L", "", regex=True)
80− sub = sub.set_index("a").reindex([o for o in order if o in sub.a.values]).reset_index() 134+ sub = sub.set_index("key").reindex([o for o in order if o in sub.key.values]).reset_index()
81 x = np.arange(len(sub)) 135 x = np.arange(len(sub))
82 for i, r in sub.iterrows(): 136 for i, r in sub.iterrows():
83− c = MODEL_COLOR.get(r["a"].replace("L", "", 1) if r["a"].startswith("L") else r["a"], "#52514e") 137+ st = STYLE[r["key"]]
84− ax.vlines(i, r["ci_lo"], r["ci_hi"], color=c, lw=2) 138+ _errorbar(ax, [i], [r["d_logloss"]], [r["ci_lo"]], [r["ci_hi"]], fmt=st["marker"], color=st["color"], mfc="white", ms=5.5)
85− ax.plot(i, r["d_logloss"], "o", color=c, ms=6, mec="white") 139+ ax.text(i, r["ci_hi"] + 0.0002, f"{r['d_logloss']:+.4f}", ha="center", va="bottom", fontsize=7)
86− ax.text(i, r["ci_hi"], f"{r['d_logloss']:+.4f}", ha="center", va="bottom", fontsize=6.5, color="#52514e") 140+ ax.axhline(0, color=INK, lw=0.7)
87− ax.axhline(0, color="#8a8a86", lw=0.8) 141+ ax.set_xticks(x); ax.set_xticklabels(["+ 30-min", "+ 5-min", "+ 1-min", "+ all"][: len(sub)])
88− ax.set_xticks(x); ax.set_xticklabels([MODEL_LABEL[a.replace("L", "", 1) if a.startswith("L") else a].replace("B5all", "all") for a in sub.a], fontsize=7) 142+ ax.set_xlim(-0.6, len(sub) - 0.4)
89− ax.set_title(f"{name}", fontsize=8, loc="left"); ax.grid(axis="y") 143+ _panel(ax, letter, title)
90− axes[0].set_ylabel("Δ log loss vs. B2 (daily + accounting)\nnegative = better") 144+ axes[0].set_ylabel("$\\Delta$ log loss relative to B2 (daily + accounting)")
145+ axes[0].text(-0.5, axes[0].get_ylim()[0] * 0.95 if axes[0].get_ylim()[0] < 0 else 0, "", fontsize=7)
146+ axes[0].yaxis.set_major_formatter(ticker.FormatStrFormatter("%+.3f"))
147+ fig.subplots_adjust(wspace=0.12)
91 _save(fig, out, "fig04_frequency_gains") 148 _save(fig, out, "fig04_frequency_gains")
92 149
93 150
151+# ----------------------------------------------------------------------------------------------------- Figure 5
94 def fig_reliability(out: Path, rel: pd.DataFrame): 152 def fig_reliability(out: Path, rel: pd.DataFrame):
95− fig, ax = plt.subplots(figsize=(3.6, 3.2)) 153+ fig, axes = plt.subplots(1, 2, figsize=(W2, 2.9), sharey=True)
96− ax.plot([0, rel[["mean_pred", "obs_rate"]].max().max()], [0, rel[["mean_pred", "obs_rate"]].max().max()], color="#8a8a86", lw=0.8, ls="--") 154+ for ax, models, letter, title in ((axes[0], ("B2", "B5"), "A", "Logistic"), (axes[1], ("LB2", "LB5"), "B", "Gradient-boosted trees")):
97− for m in ("LB2", "LB5", "B2", "B5"): 155+ m = rel[rel.model.isin(models)]
98− s = rel[rel.model == m] 156+ top = max(m["mean_pred"].max(), m["obs_rate"].max()) * 1.05 if len(m) else 1
99− if len(s): 157+ ax.plot([0, top], [0, top], color=INK4, lw=0.8)
100− ax.plot(s["mean_pred"], s["obs_rate"], marker="o", ms=4, mec="white", lw=1.6, color=MODEL_COLOR[m], label=m) 158+ for mod in models:
101− ax.set_xlabel("Mean predicted probability (decile)"); ax.set_ylabel("Observed adverse rate"); ax.legend(fontsize=7); ax.grid() 159+ s = rel[rel.model == mod]
160+ st = STYLE[mod]
161+ ax.plot(s["mean_pred"], s["obs_rate"], marker=st["marker"], ls=st["ls"], color=st["color"], mfc="white", label=st["label"])
162+ ax.set_xlabel("Mean predicted probability (decile)"); _panel(ax, letter, title); ax.legend(loc="upper left")
163+ ax.set_xlim(0, top); ax.set_ylim(0, top)
164+ axes[0].set_ylabel("Observed adverse rate")
165+ fig.subplots_adjust(wspace=0.08)
102 _save(fig, out, "fig05_reliability") 166 _save(fig, out, "fig05_reliability")
103 167
104 168
169+# ----------------------------------------------------------------------------------------------------- Figure 6
105 def fig_signal_evolution(out: Path, ladder: pd.DataFrame): 170 def fig_signal_evolution(out: Path, ladder: pd.DataFrame):
106− """Mean AUC and Δ log loss by horizon (all firm-quarters, no selection).""" 171+ lad = ladder[ladder.h > 0]
107− fig, axes = plt.subplots(1, 2, figsize=(6.4, 2.5)) 172+ hs = [40, 20, 10, 5, 1]
108− for fam, c in (("logistic", SERIES["daily"]), ("lightgbm", SERIES["1m"])): 173+ pos = {h: i for i, h in enumerate(hs)}
109− s = ladder[ladder.family == fam].sort_values("h", ascending=False) 174+ fig, axes = plt.subplots(1, 2, figsize=(W2, 2.9))
110− axes[0].plot(-s["h"], s["auc_a"], marker="o", ms=4, mec="white", lw=1.6, color=c, label=f"{fam}: B5") 175+ ax = axes[0]
111− axes[0].plot(-s["h"], s["auc_b"], marker="s", ms=3.5, mec="white", lw=1.2, ls="--", color=c, label=f"{fam}: B2") 176+ for fam, b, a in (("logistic", "B2", "B5"), ("lightgbm", "LB2", "LB5")):
112− axes[1].errorbar(-s["h"], s["d_logloss"], yerr=[s["d_logloss"] - s["ci_lo"], s["ci_hi"] - s["d_logloss"]], fmt="o-", ms=4, lw=1.4, 177+ s = lad[lad.family == fam].sort_values("h", ascending=False)
113− color=c, capsize=2, label=fam) 178+ x = [pos[h] for h in s["h"]]
114− axes[0].set_xlabel("Trading days before the release (−h)"); axes[0].set_ylabel("Test AUC"); axes[0].legend(fontsize=6); axes[0].grid() 179+ ax.plot(x, s["auc_b"], marker=STYLE[b]["marker"], ls=STYLE[b]["ls"], color=STYLE[b]["color"], mfc="white", label="B2 (+ daily)" if fam == "logistic" else None)
115− axes[1].axhline(0, color="#8a8a86", lw=0.8); axes[1].set_xlabel("Trading days before the release (−h)") 180+ ax.plot(x, s["auc_a"], marker=STYLE[a]["marker"], ls=STYLE[a]["ls"], color=STYLE[a]["color"], mfc="white", label="B5 (+ 1-min)" if fam == "logistic" else None)
116− axes[1].set_ylabel("Δ log loss B5 − B2"); axes[1].legend(fontsize=7); axes[1].grid() 181+ ax.text(len(hs) - 0.9, float(s["auc_b"].iloc[-1]) + 0.012, "gradient-boosted trees" if fam == "lightgbm" else "logistic", ha="right", va="bottom", fontsize=7.5, color=INK2)
182+ ax.set_xlabel("Trading days before the release"); ax.set_ylabel("Test AUC"); _panel(ax, "A", "Discrimination by horizon")
183+ ax.legend(loc="center", fontsize=7.5); ax.set_ylim(0.70, 0.84)
184+ ax = axes[1]
185+ for fam, mk, c, lab, off in (("logistic", "o", INK, "logistic", -0.08), ("lightgbm", "D", ACCENT, "gradient-boosted trees", 0.08)):
186+ s = lad[lad.family == fam].sort_values("h", ascending=False)
187+ x = np.array([pos[h] for h in s["h"]]) + off
188+ _errorbar(ax, x, s["d_logloss"], s["ci_lo"], s["ci_hi"], fmt=mk, color=c, mfc="white", ls="-", lw=0.9, label=lab)
189+ ax.axhline(0, color=INK, lw=0.7); ax.set_xlabel("Trading days before the release")
190+ ax.set_ylabel("$\\Delta$ log loss, B5 $-$ B2"); _panel(ax, "B", "The 1-minute increment by horizon"); ax.legend(loc="upper right", fontsize=7.5)
191+ ax.yaxis.set_major_formatter(ticker.FormatStrFormatter("%+.3f"))
192+ for a_ in axes:
193+ a_.set_xticks(range(len(hs))); a_.set_xticklabels([str(h) for h in hs]); a_.set_xlim(-0.5, len(hs) - 0.5)
194+ fig.subplots_adjust(wspace=0.32)
117 _save(fig, out, "fig06_signal_evolution") 195 _save(fig, out, "fig06_signal_evolution")
118 196
119 197
198+# ----------------------------------------------------------------------------------------------------- Figure 7
120 def fig_heterogeneity(out: Path, sub: pd.DataFrame, groups: list[str]): 199 def fig_heterogeneity(out: Path, sub: pd.DataFrame, groups: list[str]):
200+ labels = {"all": "All firm-quarters", "size_small": "Size: bottom tercile", "size_large": "Size: top tercile",
201+ "illiq_high": "Illiquidity: top tercile", "illiq_low": "Illiquidity: bottom tercile", "lev_high": "Leverage: top tercile",
202+ "lev_low": "Leverage: bottom tercile", "loss_firms": "Loss-making firms", "profit_firms": "Profitable firms",
203+ "covid_2020": "2020", "ex_covid": "Excluding 2020", "window_clean": "Window excludes previous release",
204+ "window_straddles_prev_release": "Window contains previous release", "dense_bars": "Dense bars ($\\geq$ 98%)",
205+ "sparse_bars": "Sparser bars", "sector_finance": "Financials", "sector_nonfinance": "Non-financials",
206+ "after_close_release": "Release after close", "before_open_release": "Release before open"}
121 s = sub[(sub.family == "lightgbm") & sub.subgroup.isin(groups)].set_index("subgroup").reindex(groups).reset_index().dropna(subset=["n"]) 207 s = sub[(sub.family == "lightgbm") & sub.subgroup.isin(groups)].set_index("subgroup").reindex(groups).reset_index().dropna(subset=["n"])
122− fig, ax = plt.subplots(figsize=(6.0, 2.8)) 208+ fig, ax = plt.subplots(figsize=(W2, 3.6))
123 y = np.arange(len(s)) 209 y = np.arange(len(s))
124− ax.hlines(y, s["ci_lo"], s["ci_hi"], color="#c3c2b7", lw=2) 210+ ax.hlines(y, s["ci_lo"], s["ci_hi"], color=INK, lw=0.9)
125− ax.plot(s["mean"], y, "o", color=SERIES["1m"], ms=5, mec="white") 211+ ax.plot(s["mean"], y, "D", color=ACCENT, ms=4.5, mfc="white")
212+ xmax = s["ci_hi"].max()
126 for yi, r in zip(y, s.itertuples()): 213 for yi, r in zip(y, s.itertuples()):
127− ax.text(r.ci_hi, yi, f" n={int(r.n):,}", va="center", fontsize=6, color="#52514e") 214+ ax.text(xmax + 0.0004, yi, f"{int(r.n):,}", va="center", ha="left", fontsize=7, color=INK2)
128− ax.axvline(0, color="#8a8a86", lw=0.8) 215+ ax.text(xmax + 0.0004, -1.0, "$N$", ha="left", va="center", fontsize=7, color=INK2)
129− ax.set_yticks(y); ax.set_yticklabels([g.replace("_", " ") for g in s["subgroup"]]); ax.invert_yaxis() 216+ ax.axvline(0, color=INK, lw=0.7)
130− ax.set_xlabel("Δ log loss LB5 − LB2 (test period), 95 % block-bootstrap CI"); ax.grid(axis="x") 217+ ax.set_yticks(y); ax.set_yticklabels([labels.get(g, g) for g in s["subgroup"]]); ax.invert_yaxis()
218+ ax.set_xlabel("$\\Delta$ log loss, LB5 $-$ LB2 (test period); 95% block-bootstrap interval")
219+ ax.xaxis.set_major_formatter(ticker.FormatStrFormatter("%+.3f"))
220+ ax.spines["left"].set_visible(False); ax.tick_params(axis="y", length=0)
221+ ax.set_ylim(len(s) - 0.4, -1.4)
131 _save(fig, out, "fig07_heterogeneity") 222 _save(fig, out, "fig07_heterogeneity")
132 223
133 224
225+# ----------------------------------------------------------------------------------------------------- Figure 8
134 def fig_rolling(out: Path, wf: pd.DataFrame, wfc: pd.DataFrame): 226 def fig_rolling(out: Path, wf: pd.DataFrame, wfc: pd.DataFrame):
135− fig, axes = plt.subplots(1, 2, figsize=(6.4, 2.5)) 227+ fig, axes = plt.subplots(1, 2, figsize=(W2, 2.9))
136− for m in ("LB2", "LB5", "B2", "B5"): 228+ ax = axes[0]
229+ for m in ("B2", "B5", "LB2", "LB5"):
137 s = wf[wf.model == m].sort_values("year") 230 s = wf[wf.model == m].sort_values("year")
138 if len(s): 231 if len(s):
139− axes[0].plot(s["year"], s["auc"], marker="o", ms=3.5, mec="white", lw=1.4, color=MODEL_COLOR[m], ls="-" if m.startswith("L") else "--", label=m) 232+ st = STYLE[m]
140− axes[0].set_ylabel("Out-of-fold AUC (annual refits)"); axes[0].legend(fontsize=6, ncol=2); axes[0].grid() 233+ ax.plot(s["year"], s["auc"], marker=st["marker"], ls=st["ls"], color=st["color"], mfc="white", label=st["label"])
141− for fam, c in (("lightgbm", SERIES["1m"]), ("logistic", SERIES["daily"])): 234+ ax.set_ylabel("Out-of-fold AUC"); ax.set_xlabel("Year of cutoff"); _panel(ax, "A", "Annual expanding-window refits")
142− s = wfc[(wfc.family == fam) & (wfc.year != "all")].copy(); s["year"] = s["year"].astype(int); s = s.sort_values("year") 235+ ax.legend(ncol=2, fontsize=7, loc="lower center"); ax.set_ylim(0.66, 0.86)
143− axes[1].errorbar(s["year"], s["d_logloss"], yerr=[s["d_logloss"] - s["ci_lo"], s["ci_hi"] - s["d_logloss"]], fmt="o-", ms=3.5, lw=1.2, capsize=2, color=c, label=fam) 236+ ax = axes[1]
144− axes[1].axhline(0, color="#8a8a86", lw=0.8); axes[1].set_ylabel("Δ log loss B5 − B2 by year"); axes[1].legend(fontsize=7); axes[1].grid() 237+ for fam, mk, c, lab, off in (("logistic", "o", INK, "logistic", -0.12), ("lightgbm", "D", ACCENT, "gradient-boosted trees", 0.12)):
238+ s = wfc[(wfc.family == fam) & (wfc.year.astype(str) != "all")].copy(); s["year"] = s["year"].astype(int); s = s.sort_values("year")
239+ _errorbar(ax, s["year"] + off, s["d_logloss"], s["ci_lo"], s["ci_hi"], fmt=mk, color=c, mfc="white", ls="none", label=lab)
240+ ax.axhline(0, color=INK, lw=0.7); ax.set_ylabel("$\\Delta$ log loss, B5 $-$ B2"); ax.set_xlabel("Year of cutoff")
241+ _panel(ax, "B", "The 1-minute increment by year"); ax.legend(loc="upper left"); ax.yaxis.set_major_formatter(ticker.FormatStrFormatter("%+.3f"))
242+ for a in axes:
243+ a.set_xticks(range(2016, 2027, 2))
244+ fig.subplots_adjust(wspace=0.32)
145 _save(fig, out, "fig08_rolling") 245 _save(fig, out, "fig08_rolling")
146 246
147 247
248+# ----------------------------------------------------------------------------------------------------- Figure 9
148 def fig_economic(out: Path, port: pd.DataFrame, alphas: dict): 249 def fig_economic(out: Path, port: pd.DataFrame, alphas: dict):
149− fig, ax = plt.subplots(figsize=(4.2, 2.6)) 250+ fig, ax = plt.subplots(figsize=(W1 + 0.6, 2.7))
150 dm = port.groupby("decile")["ret"].mean() * 100 251 dm = port.groupby("decile")["ret"].mean() * 100
151− ax.bar(dm.index, dm.values, color=SERIES["daily"], width=0.8) 252+ se = port.groupby("decile")["ret"].std() / np.sqrt(port.groupby("decile")["ret"].count()) * 100
152− ax.axhline(0, color="#8a8a86", lw=0.8); ax.set_xlabel("Decile of ex-ante deterioration score (10 = most likely)") 253+ ax.bar(dm.index, dm.values, color=INK3, width=0.7, edgecolor="none")
153− ax.set_ylabel("Mean monthly return (%)"); ax.grid(axis="y") 254+ ax.errorbar(dm.index, dm.values, yerr=1.96 * se.values, fmt="none", ecolor=INK, elinewidth=0.7, capsize=2)
154− ax.set_title(f"L/S 1−10 alpha: {alphas['gross']['alpha_monthly']*100:+.2f}% (t={alphas['gross']['alpha_t']:.1f}) gross, " 255+ ax.axhline(0, color=INK, lw=0.7); ax.set_xticks(range(1, 11))
155− f"{alphas['net20bps']['alpha_monthly']*100:+.2f}% net of 20 bps", fontsize=7, loc="left") 256+ ax.set_xlabel("Decile of ex-ante deterioration score (10 = most likely)"); ax.set_ylabel("Mean monthly return (%)")
257+ g, n = alphas["gross"], alphas["net20bps"]
258+ ax.set_title(f"L/S (1$-$10) FF5+MOM alpha: {100 * g['alpha_monthly']:+.2f}% per month ($t={g['alpha_t']:.2f}$) gross, "
259+ f"{100 * n['alpha_monthly']:+.2f}% net of 20 bps", loc="left", fontsize=7.5)
156 _save(fig, out, "fig09_economic") 260 _save(fig, out, "fig09_economic")
157 261
Menu

Theme