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>
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 | ||||