Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,6 @@ RELEASE_CHECKLIST.md
invoice_template.html
AI Business Idea Generation.pdf
*.pdf

# The typeset working paper is a deliverable, not a build artifact.
!docs/working_paper/detection_timing_backtest_2026-07.pdf
10 changes: 6 additions & 4 deletions docs/working_paper/README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
# Working Paper

**Detection-Timing Backtest of a Deterministic Downside-Risk Screen Across Three Modern Financial Crises**
Zarif Latif. Revision 2, October 2026 (first version July 2026).
Md. Zarif Latif (BRAC University). Working paper, October 2026.

Revision 2's headline finding: the pipeline flagged all 9 affected names before their formal resolution dates, but **no first flag came before the first headline reporting the stock already falling** (Section 4.5). The paper presents the system as fast, auditable triage, not anticipation.
Headline finding: the pipeline flagged all 9 affected names before their formal resolution dates, but **no first flag came before the first headline reporting the stock already falling** (Section 4.5). The paper presents the system as fast, auditable triage, not anticipation.

## Files

- `detection_timing_backtest_2026-07.md` — the paper. Renders on GitHub, Substack, arXiv-style preprint sites. **This is the primary artifact.**
- `detection_timing_backtest_2026-07.pdf` — the typeset PDF (the version posted on SSRN).
- `detection_timing_backtest_2026-07.html` — self-contained HTML. Open in any browser. Save-as-PDF via browser print dialog for a shareable attachment.

## Rebuild the HTML

```bash
python scripts/render_working_paper.py
python scripts/render_working_paper.py # HTML only
python scripts/render_working_paper.py --pdf # HTML + typeset PDF (WeasyPrint)
```

The HTML has all CSS inlined and no external assets, so a single file emailed / posted anywhere renders correctly on any modern browser.
Expand Down Expand Up @@ -49,6 +51,6 @@ pytest aetherius/tests/test_market_reaction_check.py -v

## Posting as a preprint

- **SSRN** (ssrn.com, free): good fit under Financial Economics → Risk Management. Upload a PDF made from the HTML (browser print → Save as PDF). No affiliation is required.
- **SSRN** (ssrn.com, free): good fit under Financial Economics → Risk Management. Upload `detection_timing_backtest_2026-07.pdf` (build it with `python scripts/render_working_paper.py --pdf`).
- **arXiv q-fin.RM / q-fin.CP**: a first-time submitter usually needs an endorsement from an existing arXiv author in that category. Ask a professor or a researcher whose work you cite.
- In the abstract, keep the Revision 2 framing: triage at the market reaction, not anticipation.
25 changes: 11 additions & 14 deletions docs/working_paper/detection_timing_backtest_2026-07.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8">
<title>Detection-Timing Backtest of a Systematic Downside-Risk Screen Across Three Modern Financial Crises</title>
<title>Detection-Timing Backtest of a Deterministic Downside-Risk Screen Across Three Modern Financial Crises</title>
<style>
:root { color-scheme: light dark; }
body {
Expand Down Expand Up @@ -38,20 +38,17 @@
</head>
<body>
<h1 id="detection-timing-backtest-of-a-deterministic-downside-risk-screen-across-three-modern-financial-crises">Detection-Timing Backtest of a Deterministic Downside-Risk Screen Across Three Modern Financial Crises</h1>
<p><strong>A working paper. Revision 2.1, October 2026 (first version July 2026).</strong></p>
<p><strong>Author:</strong> Zarif Latif, independent researcher · zarif@aetheriusresearch.tech</p>
<p><strong>Keywords:</strong> risk screening, event study, entity resolution, textual analysis, GDELT, SEC EDGAR, bank failures, backtesting</p>
<p><strong>JEL codes:</strong> G14, G17, G21</p>
<blockquote>
<p><strong>What changed in Revision 2.</strong> Revision 1 reported that the pipeline flagged all nine affected names "before the realized event date," with a median lead of about 2.5 days. That is true, but the realized event date was the <em>formal resolution</em> (receivership, insolvency filing, Chapter 11), not the market's reaction. Revision 2 adds a test of timing against the first headline reporting each stock already falling (Section 4.5). Measured that way, <strong>no first flag came before the market reaction.</strong> Revision 2 also adds confidence intervals, states the hindsight selection of events and watchlists, documents two known false-positive classes, removes commercial framing, and describes a pre-registered live forward test (Section 8). Every number in this revision is reproduced by pinned tests.</p>
<p><strong>What changed in Revision 2.1.</strong> A pre-publication check found that <strong>no control name appears in any of the three corpora</strong>, so the "0 false positives on controls" result was never actually tested. Text in Revisions 1 and 2 describing control headlines was wrong and has been removed. Section 4.6 explains what happened. Revision 2.1 also adds related work and references, and discloses that the Section 4.5 price-reaction pattern was written after the headlines had been read.</p>
</blockquote>
<hr />
<p><strong>Md. Zarif Latif</strong><br />
Undergraduate Student, BRAC University, Dhaka, Bangladesh<br />
md.zarif.latif@g.bracu.ac.bd</p>
<p>Working paper · October 2026</p>
<h2 id="abstract">Abstract</h2>
<p>We replay a deterministic entity-mapping and severity-scoring pipeline against real news headlines from three well-studied financial crises: the March 2023 U.S. regional-bank contagion, the June 2020 Wirecard AG accounting fraud and the November 2022 FTX collapse. For each affected name we report detection timing and how it compares with the market's own reaction.</p>
<p>On 161 archived observations from GDELT DOC 2.0, the pipeline flagged <strong>all 9 affected names</strong> at "elevated" or "high" severity before each name's formal resolution date, with a median lead of 2.48 days. With only nine names, the 95% confidence interval for recall is 66–100%. The study's three control names never appear in the corpus, so it provides <strong>no evidence about false positives</strong>.</p>
<p>The more important finding is about timing. In all 9 detections, the first flag arrived <strong>at the same time as, not before,</strong> the first headline reporting that the stock was already falling. In 8 of 9 it <em>was</em> that headline. The pipeline is therefore evidence of fast, auditable <strong>triage</strong> of a market reaction already underway. It is not evidence of anticipating the market. We describe the pipeline, the corpus and the ground truth, discuss the limitations, and describe a pre-registered live forward test started on 2026-10-16. All code and data are Apache-2.0 licensed and reproducible.</p>
<p><strong>This is detection-timing evidence on frozen historical windows. It is not a return forecast, a trading signal, or a causal claim.</strong></p>
<p><strong>Keywords:</strong> risk screening, event study, entity resolution, textual analysis, GDELT, SEC EDGAR, bank failures, backtesting</p>
<p><strong>JEL codes:</strong> G14, G17, G21</p>
<hr />
<h2 id="1-motivation">1. Motivation</h2>
<p>Concentrated public-equity books, such as those of search funds, small family offices, litigation-finance analysts and small credit shops, carry large single-name risk. Professional surveillance products are priced and configured for large institutional teams.</p>
Expand Down Expand Up @@ -89,7 +86,7 @@ <h3 id="23-severity-scoring">2.3 Severity scoring</h3>
</ul>
<p><code>severity_label()</code> bins risk into <code>low</code>, <code>moderate</code>, <code>elevated</code> or <code>high</code>. Every weight is a constant, and there are no learned parameters.</p>
<h3 id="24-adverse-language-gate">2.4 Adverse-language gate</h3>
<p><code>adverse_severity(raw_text)</code> (same module) counts matches against a fixed list of about 40 downside terms (<code>loss</code>, <code>plunge</code>, <code>withdrawals</code>, <code>deposit run</code>, <code>receivership</code>, <code>collapse</code>, <code>funding pressure</code>, <code>distress</code>, <code>insolven…</code>, …) and maps the count to an event-severity score. An observation is a <strong>shippable flag</strong> only when its severity label is <code>elevated</code> or <code>high</code> <strong>and</strong> at least one adverse term is present. Unit tests show the gate holding a benign direct mention below shippable severity (for example, "Microsoft expands Copilot across product suite"). The backtest corpora contain no such case for the controls (Section 4.6). Since Revision 2, the live API, the backtest and the forward test all import this single implementation.</p>
<p><code>adverse_severity(raw_text)</code> (same module) counts matches against a fixed list of about 40 downside terms (<code>loss</code>, <code>plunge</code>, <code>withdrawals</code>, <code>deposit run</code>, <code>receivership</code>, <code>collapse</code>, <code>funding pressure</code>, <code>distress</code>, <code>insolven…</code>, …) and maps the count to an event-severity score. An observation is a <strong>shippable flag</strong> only when its severity label is <code>elevated</code> or <code>high</code> <strong>and</strong> at least one adverse term is present. Unit tests show the gate holding a benign direct mention below shippable severity (for example, "Microsoft expands Copilot across product suite"). The backtest corpora contain no such case for the controls (Section 4.6). The live API, the backtest and the forward test all import this single implementation.</p>
<h3 id="25-backtest-harness">2.5 Backtest harness</h3>
<p><code>simulations/backtest/run_backtest.py</code> replays each event's <code>observations.jsonl</code> in chronological order against its <code>watchlist.json</code> and <code>ground_truth.json</code>, records the first shippable flag per name, and computes:</p>
<ul>
Expand Down Expand Up @@ -371,10 +368,10 @@ <h3 id="45-timing-relative-to-the-market-reaction">4.5 Timing relative to the ma
</tbody>
</table>
<p><strong>No first flag came before the market reaction.</strong> The pipeline flagged each name at the moment the press reported its shares falling. This is expected given how it works: adverse-language scoring of headlines fires most strongly on headlines <em>about</em> a sell-off.</p>
<p>That is still useful for a small team. Out of hundreds of headlines, the names that need attention today were surfaced within the same news cycle, with an auditable reason. But it is <strong>triage, not anticipation</strong>, and Revision 1's framing of "lead time" overstated what the evidence supports.</p>
<p>That is still useful for a small team. Out of hundreds of headlines, the names that need attention today were surfaced within the same news cycle, with an auditable reason. But it is <strong>triage, not anticipation</strong>, and lead time measured to formal resolution overstates what the evidence supports.</p>
<h3 id="46-the-control-test-was-not-exercised">4.6 The control test was not exercised</h3>
<p>Each fixture includes one control name (MSFT twice, DTE once) that the pipeline should not flag. None did, but none could have: <strong>no observation in any corpus maps to a control name.</strong> The fixture builder did query GDELT for each control ("Microsoft Corporation", "Deutsche Telekom"), and GDELT returned articles. GDELT matches article bodies, but the pipeline stores and scores headlines only, and none of those headlines names the control. <code>market_reaction_check.py</code> prints this coverage, and a test (<code>test_controls_never_appear_in_the_frozen_corpora</code>) pins it so the paper must be updated if a fixture is ever rebuilt.</p>
<p>Revisions 1 and 2 reported "0 false positives on 3 controls" as evidence of specificity, and described control headlines that do not exist in the data. Both were errors. This study therefore says nothing about how often the screen fires on companies that turned out fine. The same fact makes the 100% mapping precision uninformative. Measuring false positives is the purpose of the forward test (Section 8).</p>
<p>Earlier project materials (the repository README and website) presented "0 false positives on 3 controls" as evidence of specificity; that claim is withdrawn. This study therefore says nothing about how often the screen fires on companies that turned out fine. The same fact makes the 100% mapping precision uninformative. Measuring false positives is the purpose of the forward test (Section 8).</p>
<hr />
<h2 id="5-limitations">5. Limitations</h2>
<h3 id="51-what-this-measures-and-what-it-does-not">5.1 What this measures and what it does not</h3>
Expand Down Expand Up @@ -533,7 +530,7 @@ <h2 id="appendix-c-primary-sources">Appendix C — Primary sources</h2>
<h2 id="appendix-d-reproducibility">Appendix D — Reproducibility</h2>
<ul>
<li><strong>License:</strong> Apache-2.0.</li>
<li><strong>Tests:</strong> the full suite (122 tests at Revision 2.1) pins the backtest metrics, the Section 4.5 timing result, the Section 4.6 control coverage, and the parity between live and backtest scoring.</li>
<li><strong>Tests:</strong> the full suite (122 tests at the time of writing) pins the backtest metrics, the Section 4.5 timing result, the Section 4.6 control coverage, and the parity between live and backtest scoring.</li>
<li><strong>Determinism:</strong> scoring weights are constants with no learned parameters, and reruns are bit-identical.</li>
<li><strong>Rebuild:</strong> corpora can be regenerated from GDELT DOC 2.0 with <code>simulations/backtest/build_fixture.py</code>.</li>
</ul>
Expand Down
Loading
Loading