Penrose can read local, pre-collected data through PENROSE_DATA_DIR. Point the variable at a
directory containing loader.py; Penrose imports that module and calls a small catalog surface:
load_series(name: str) -> tuple[pandas.Series, str] | None
available() -> list[str]
domain_of(name: str) -> str | None
domains() -> list[str]
describe(name: str) -> dict
describe_brief(name: str) -> strThe typed reference is penrose.data.loader_protocol.CatalogLoaderProtocol.
When PENROSE_DATA_DIR is unset, the catalog directory defaults to the repository-local data/.
Run make init to create a valid empty data/catalog.yaml, then follow data/README.md to populate
it. For one migration release, Penrose warns and uses the old sibling ../penrose-data only when the
repo-local catalog is absent or invalid and the sibling has a valid catalog. Set PENROSE_DATA_DIR
or move that catalog into data/; the sibling fallback will be removed in the next breaking release.
When PENROSE_DATA_DIR is unset, points at the wrong directory, or a catalog-only series cannot be
delivered, Penrose marks that series Unavailable and dependent claims route to needs_data.
Synthetic substitution is opt-in for calibration/dev runs only:
PENROSE_ALLOW_SYNTHETIC=1Leave PENROSE_ALLOW_SYNTHETIC unset or 0 for real adjudication.
load_series() returns (series, provenance) or None if the series cannot be delivered. The series
must be a scalar daily time series:
pandas.SeriesDatetimeIndex, UTC daily timestamps- float values
- sorted, deterministic, point-in-time
If a returned index is timezone-naive, Penrose localizes it to UTC before placing it in a
penrose.data.contract.Series. Return None for missing files, missing columns, empty data, or
unsupported names. Do not fabricate fallback values in a loader.
There is no OHLC or bar primitive in this catalog seam. OHLC is four scalar daily series by convention:
asset_open, asset_high, asset_low, and asset_close.
Vendor bad prints, including zero or near-zero futures prices, must be repaired before serving as a versioned, content-addressed derived cleaned artifact. Preserve and hash the raw bytes, write and hash the cleaned bytes actually served, and record the deterministic repair in a manifest. The repair must be causal: it may use only information available at or before the repaired observation, never future data.
The serve-time loader is a non-mutating sentry. It validates the cleaned artifact and flags residual
anomalies loudly; it never repairs, substitutes, clips, or fabricates values while loading. This
reconciles the no-fallback rule above with data cleaning: a repair belongs in a new derived vintage,
not in the loader. A loader that silently repairs is a loader that disagrees with its own hash, because
the catalog's artifact_sha256 would no longer describe the bytes actually served.
Collapse intraday data to daily before returning it from load_series(), and declare the aggregation in
catalog metadata. Supported aggregation names for scalar series should be explicit:
lastsummeancountfirstmaxmin
For OHLC input, use the usual daily collapse: open = first, high = max, low = min, close = last. Penrose
also exposes penrose.data.granularity.resample_ohlc(frame) for that generic conversion.
The reference implementation is in examples/reference_loader/:
PENROSE_DATA_DIR=examples/reference_loader penrose run --claims claims.jsonIt contains:
loader.pyimplementingCatalogLoaderProtocolcatalog.yamlwith names, domains, units, provenance, aggregation, and descriptions- tiny CSV files under
data/
The sample catalog publishes equity_spy_close and btc_spot_close as daily scalar close series.
Use it as a template for private catalogs; keep private data outside the repository and point
PENROSE_DATA_DIR at that directory.