Paper extraction - sbOogway/sbt2 GitHub Wiki
Paper extraction
Turning a research paper into a structured, source-grounded record. The record is what a later step turns into an sbt2 strategy, so a published strategy can be reproduced on its own period and then judged on data after publication. It is the sbt2-papers distribution of the workspace, imported as sbt2.papers, and installed with pip install "sbt2[papers]" (see Library). It and the backtester never import each other, by import-linter contract: the only link between them is the record's format.
Pipeline
paper id / PDF
β
1. acquire βββΊ PDF + metadata (title, authors, first-version date, DOI)
2. parse βββΊ Document: sections, paragraphs with character offsets, tables as grids, equations as LaTeX
3. triage βββΊ keep / reject + tags (asset class, frequency, data needed)
4. extract βββΊ draft record: every field with evidence spans and a status
5. verify βββΊ every field checked against its spans; gaps and conflicts listed
6. review βββΊ a person fills the gaps; each one becomes a recorded assumption
β
frozen, versioned record (JSON) βββΊ codegen (separate step) βββΊ sbt2
- Acquire. The PDF and metadata from arXiv, SSRN, a DOI or a local file. The first public version's date is recorded: everything the paper reports is in-sample for it, so the sbt2 test part starts after that date. Published anomalies are known to weaken after publication (McLean & Pontiff 2016).
- Parse. Behind a
Parserinterface: MinerU or Docling for the body (formulas as LaTeX, tables as cell grids), GROBID for the paper's structure and references. TheDocumentkeeps character offsets, pages and bounding boxes, so every later claim points back to its source. - Triage. A cheap model answers fixed questions: is there a tradable rule, which asset class and rebalancing frequency, is the data in the catalog. Rejected papers stop here, before the expensive stages.
- Extract. Schema-driven and grounded, in the style of LangExtract: one pass per section, then a merge. Every field has a value, its evidence (the source spans) and a status:
stated,inferredormissing. - Verify. An independent check of every field against its spans, with a different model or prompt from the extraction; numbers must parse and reported results must agree with each other. It produces a completeness score and the list of gaps.
- Review. Required before a record is frozen. A person resolves every gap and every
inferredfield; each resolution is stored as an assumption with its reason. An assumption is a design choice, so it counts as a trial when the strategy's results are deflated in sbt2 (see Results).
Record
A generic core, so papers outside trading fit too, and domain schemas on top. The schemas are pydantic models, which give both the LLM's structured output and validation.
- Core:
Paper,Document,Span,Evidence,Field[T](value, evidence, status),Assumption. TradingStrategy, the first domain schema:
| Group | Fields |
|---|---|
| Universe and data | instruments, filters, data frequency, point-in-time requirements |
| Signal | formula (LaTeX), description, lookbacks |
| Portfolio | construction, rebalancing, execution timing (e.g. next open) |
| Costs | what the paper assumed |
| Sample | the paper's in-sample and out-of-sample periods, first-version date |
| Parameters | the values used, and any ranges the authors searched (they count as trials) |
| Reported results | metric Γ period Γ variant, the numbers a reproduction is compared with |
Storage
The same approach as sbt2's results store: one folder per paper, one immutable artifact per stage. Each artifact is keyed by the hash of its inputs plus the parser, model and prompt versions, so rerunning a stage whose inputs haven't changed is a cache hit, a prompt change reruns only what depends on it, and every record names what produced it.
Modules
sbt2/papers/
acquire/ Source ABC: arxiv, ssrn, doi, local
parse/ Parser ABC: mineru, docling, grobid; the Document model
llm/ Model ABC over providers with structured output; the only code that calls an API
schema/ core (Span, Evidence, Field, Assumption) and domains/trading.py
extract/ triage, extract, merge
verify/ grounding, consistency, completeness
store/ per-paper artifacts and cache keys
api.py extract(paper_id, schema) -> Record, the one public entry point
cli.py
Parsers, sources and models are abstract base classes with an explicit-registry factory, like sbt2's variants. The LLM provider is pluggable behind llm; one provider is implemented first.
Tests
- Gold set: 10β20 strategy papers annotated by hand, e.g. from Papers With Backtest, whose code already exists.
- Measured: field accuracy, grounding rate (the share of
statedfields whose spans really support them) and gap detection. - Golden tests as in sbt2: fixed PDFs and cached model responses must keep producing the same records, so a prompt or parser change shows up as a diff.
Why structural guards
GenΓ§ay 2026 found that every LLM-discovered strategy failed a leakage-safe, search-aware evaluation, and that deflation can't catch look-ahead: a planted oracle with a Sharpe of 35 passed the deflated Sharpe ratio and PBO. A published rule implemented as written is close to a pre-registered hypothesis and faces little deflation; every change made to match the paper's numbers is another trial. Hence the evidence on every field, the recorded assumptions, and test dates after publication. Look-ahead in the generated code needs a structural guard in the codegen step, not a statistical one.
Related work
PaperCoder / Paper2Code (planning β analysis β generation) is the model for the codegen step. PaperQA2 and OpenScholar search and answer across many papers, for finding candidates before stage 1. RD-Agent runs report β factor β Qlib backtest, for factors only. ORKG models a paper as problem, method and results. See References.