Your first verdict
This walkthrough starts with an empty Penrose clone: no local data, no catalog entries, and no
corpus. It installs the package, checks the referee, downloads a keyless public factor series,
registers that series, and submits one structured claim. The result is a real research verdict,
not a needs_data routing stop.
The commands and output below were verified on 2026-07-24 with Python 3.11.15. The data window is pinned through 2026-05-29 so the artifact hash and expected verdict are reproducible. Ken French data are free and require no API key. Network access is required for the first download.
A verdict reports whether a claim survived Penrose's falsification process. It is never a statement that a strategy is profitable, tradeable, or suitable for capital.
1. Install from the clone
Start in the repository root. Use Python 3.11; that is the version used to verify this walkthrough.
python3.11 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -e .
python --version
Expected version line:
Python 3.11.15
2. Verify the referee
Run the invariant suite before trusting a verdict:
python scripts/eval_suite.py
The suite prints its individual controls and must finish with:
142/142 passed
Do not continue if it exits nonzero.
3. Initialize an empty catalog
make init creates the ignored, repository-local catalog scaffold. Point this shell explicitly at
that directory so there is no ambiguity about which catalog Penrose will load:
make init
export PENROSE_DATA_DIR="$PWD/data"
mkdir -p "$PENROSE_DATA_DIR/files"
cp examples/reference_loader/loader.py "$PENROSE_DATA_DIR/loader.py"
The loader.py copy implements Penrose's catalog contract. catalog.yaml declares metadata; the
loader delivers the named CSV as a sorted UTC pandas.Series.
Expected make init output in a fresh clone:
Created /path/to/Penrose/data/catalog.yaml
Add series files under /path/to/Penrose/data/files/ and register them in catalog.yaml.
To use another catalog directory, set PENROSE_DATA_DIR=/path/to/catalog.
4. Download and register free data
This command uses Penrose's keyless Ken French adapter to download the daily U.S. momentum factor. It stores the decimal returns as a local CSV, hashes the exact served bytes, and replaces the empty catalog with one registered series:
PENROSE_SKIP_DOTENV=1 python - <<'PY'
import hashlib
import os
from pathlib import Path
import pandas as pd
import yaml
from penrose.data.vendors import kenfrench
data_dir = Path(os.environ["PENROSE_DATA_DIR"])
result = kenfrench.fetch({
"dataset": "F-F_Momentum_Factor_daily",
"column": "Mom",
"start": "2000-01-01",
"end": "2026-05-29",
})
if result is None:
raise SystemExit("Ken French download failed")
series, provenance = result
artifact = data_dir / "files" / "kenfrench_mom_daily.csv"
pd.DataFrame({"date": series.index, "value": series.to_numpy()}).to_csv(
artifact, index=False
)
sha256 = hashlib.sha256(artifact.read_bytes()).hexdigest()
catalog = {
"series": {
"kenfrench_mom_daily": {
"file": "files/kenfrench_mom_daily.csv",
"domain": "equity",
"unit": "decimal_return",
"frequency": "daily",
"provenance": provenance,
"provenance_grade": "as_displayed",
"agg": "last",
"artifact_sha256": sha256,
"description": (
"Daily U.S. equity momentum factor return from the "
"Ken French Data Library."
),
}
}
}
(data_dir / "catalog.yaml").write_text(
yaml.safe_dump(catalog, sort_keys=False), encoding="utf-8"
)
print(f"registered kenfrench_mom_daily: {len(series)} rows")
print(f"range: {series.index.min().date()} through {series.index.max().date()}")
print(f"provenance: {provenance}")
print(f"artifact_sha256: {sha256}")
PY
Expected output:
registered kenfrench_mom_daily: 6641 rows
range: 2000-01-03 through 2026-05-29
provenance: kenfrench-csv:F-F_Momentum_Factor_daily:Mom
artifact_sha256: 15a64f55260d94063a116c91fed28c33c9aad4e1f7c42d06e760292050d25794
The resulting catalog.yaml is:
series:
kenfrench_mom_daily:
file: files/kenfrench_mom_daily.csv
domain: equity
unit: decimal_return
frequency: daily
provenance: kenfrench-csv:F-F_Momentum_Factor_daily:Mom
provenance_grade: as_displayed
agg: last
artifact_sha256: 15a64f55260d94063a116c91fed28c33c9aad4e1f7c42d06e760292050d25794
description: Daily U.S. equity momentum factor return from the Ken French Data
Library.
as_displayed is intentional: this public file is the library's current research series, not a
point-in-time vintage.
5. Declare the claim
Save the following as claims.json in the repository root:
[
{
"claim_id": "first-verdict-kenfrench-momentum",
"statement": "The mean of the declared series kenfrench_mom_daily is greater than zero.",
"mechanism": "Cross-sectional momentum may persist because information diffuses gradually and investors underreact.",
"scope": "U.S. equity momentum factor returns published by the Ken French Data Library",
"horizon": "daily observations from 2000-01-01 onward",
"source_id": "your-first-verdict",
"source_span": "Test whether the mean of the declared series kenfrench_mom_daily is greater than zero.",
"claimed_metric_quote": "mean daily return > 0",
"applicable_strategy_class": "provided_series_statistic",
"source_type": "external_source",
"search_cohort_id": "your-first-verdict-singleton",
"search_denominator": 1,
"data_provenance": {
"claim_type": "provided_series_statistic",
"series": [
"kenfrench_mom_daily"
],
"provider": "Ken French Data Library",
"provenance_grade": "as_displayed"
},
"sample_period": {
"start": "2000-01-01",
"end": "2026-05-29"
},
"claim_kind": "relationship"
}
]
The binding is literal: kenfrench_mom_daily appears in both the catalog and the claim. The
provided_series_statistic executor tests the already-constructed factor-return series directly;
it does not invent a trading overlay.
6. Run the claim
Use the explicit offline classifier for this keyless structured run:
penrose run --claims claims.json --no-llm --json
--claims skips prose claim extraction. --no-llm also selects P3's documented offline
falsifiability fallback, so this guaranteed path requires neither a model key nor a data key. The
claim type, data binding, executor, robustness stack, and verdict logic still run.
Observed output:
[penrose] claim workers: resolved=4 requested=4 bound=requested ceilings(cpu=0, ram=0, cap=16)
[penrose] WARNING: real catalog series 'kxfed_signal' unavailable - PENROSE_ALLOW_SYNTHETIC is not set; marking series UNAVAILABLE
[penrose] WARNING: real catalog series 'kxrecssnber_signal' unavailable - PENROSE_ALLOW_SYNTHETIC is not set; marking series UNAVAILABLE
{"principle": null, "source_id": "claims", "status": "complete", "verdicts": [{"claim_id": "first-verdict-kenfrench-momentum", "execution_evidence": "modeled", "kill_reason": "no_oos_edge", "oos_sharpe": 0.01, "resolution": null, "verdict": "kill"}]}
The two warnings concern unrelated built-in macro series; they do not mean
kenfrench_mom_daily was missing. The JSON is the result to inspect:
status: completemeans the run finished.verdict: killis a real research verdict, not a routing state.kill_reason: no_oos_edgerecords which configured falsification gate failed.execution_evidence: modeledis a separate evidence axis.principle: nullmeans this single result proposed no corpus principle.
Here, kill means this claim failed the configured process on the pinned sample. It does not say
that momentum is always false, and it is not trading advice.
7. Read the recorded result
The compact verdict view should now contain the claim:
penrose verdicts
Observed output:
surface statistical execution syn claim_id statement
---------------------------------------------------------------------------------------------------------------------------------
backtested kill modeled no first-verdict-kenfrench-mo The mean of the declared series kenfrench_mom_dail
triage is for claims that stopped without a research verdict. This run produced none:
penrose triage
Observed output:
No no-verdict decisions found yet (No traces or decisions found yet to triage).
Launch the read-only local dashboard to inspect the result and its report:
make dash
The verified launch served http://127.0.0.1:8077/. The command stays in the foreground; press
Ctrl-C when finished.
8. Stop at P9
Stop here. P8 has emitted a verdict, but promoting any survivor or proposed principle into the
approved corpus is P9: a human-only review decision. Do not automate or bypass that gate. This
example is a kill, so there is nothing to promote.
Optional: ingest a paper instead
Paper or Markdown ingestion needs a configured OpenAI-compatible model because P2 must extract
grounded claims from prose. Put the PENROSE_LLM_API_KEY, PENROSE_LLM_BASE_URL, and
PENROSE_LLM_DEFAULT_MODEL values in .env, then use:
set -a
. ./.env
set +a
penrose run --paper path/to/source.pdf --json
This command shape was verified with a local Markdown fixture and a configured model. Its output is
source-dependent, so no universal verdict is expected; the verified fixture honestly routed to
needs_data. Use the structured path above when you need the reproducible first verdict.
Optional: bring premium data
Penrose can also use licensed premium data, including Databento, when you supply your own entitlement and credentials. Premium adapters are not required for this walkthrough and must not be treated as redistributable merely because they can be loaded locally. Start with Work with data and the repository's adapter guide.