Coverage for book/marimo/notebooks/preamble.py: 100%
21 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-09 08:57 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-09 08:57 +0000
1"""Shared data loading for marimo experiment notebooks.
3This module also hosts :func:`load_notebook`, the single place that executes a
4sibling notebook (or ``optimize.py``) via :func:`runpy.run_path` and returns its
5namespace. The experiment notebooks are not an importable package, so both
6``optimize.py`` and the test suite need to read symbols (the signal ``f``, the
7``build_exp*`` builders, …) out of a freshly executed notebook namespace;
8centralizing that here keeps the ``runpy`` call in one place.
9"""
11import runpy
12from pathlib import Path
13from typing import Any
15import plotly.io as pio
16import polars as pl
17from jquantstats import interpolate
19pio.renderers.default = "plotly_mimetype"
21date_col = "date"
23#: Directory holding the marimo notebooks (this file's own directory).
24NOTEBOOK_DIR = Path(__file__).resolve().parent
26#: Price file every notebook reads, from the ``public/`` directory beside it.
27PRICES_CSV = "Prices_hashed.csv"
30def load_notebook(name: str) -> dict[str, Any]:
31 """Execute sibling notebook ``name`` (e.g. ``"Experiment1.py"``) and return its namespace.
33 The returned dict maps top-level names defined by the notebook to their
34 values, so callers can pull out the signal function with
35 ``load_notebook("Experiment1.py")["f"]``.
37 Executing this module itself is the cheapest demonstration of that contract —
38 the shared helpers come back as ordinary entries in the namespace:
40 >>> namespace = load_notebook("preamble.py")
41 >>> sorted(name for name in namespace if name in {"date_col", "load_prices"})
42 ['date_col', 'load_prices']
43 """
44 return runpy.run_path(str(NOTEBOOK_DIR / name))
47def load_prices(notebook_file: str) -> pl.DataFrame:
48 """Load and preprocess prices from the standard CSV file.
50 ``notebook_file`` is the *caller's* own path — the notebooks pass ``__file__`` —
51 and the CSV is read from its ``public/`` sibling directory. The frame comes back
52 with the date column first as nanosecond datetimes, every remaining column (one
53 per asset) cast to ``Float64``, and gaps interpolated:
55 >>> prices = load_prices(str(NOTEBOOK_DIR / "preamble.py"))
56 >>> prices.columns[0]
57 'date'
58 >>> prices[date_col].dtype
59 Datetime(time_unit='ns', time_zone=None)
60 >>> set(prices.drop(date_col).dtypes) == {pl.Float64}
61 True
63 An absent CSV is checked for here rather than left to ``pl.read_csv``, so the
64 error names the file that was looked for — the file ships with the repository,
65 so its absence is a setup problem with an obvious remedy:
67 >>> try:
68 ... load_prices(str(NOTEBOOK_DIR / "elsewhere" / "preamble.py"))
69 ... except FileNotFoundError as error:
70 ... PRICES_CSV in str(error)
71 True
72 """
73 path = Path(notebook_file).parent / "public" / PRICES_CSV
74 if not path.is_file():
75 msg = f"Price data not found: {path} — it ships with the repository; check the checkout is complete."
76 raise FileNotFoundError(msg)
77 dframe = pl.read_csv(str(path), try_parse_dates=True)
78 dframe = dframe.with_columns(pl.col(date_col).cast(pl.Datetime("ns")))
79 dframe = dframe.with_columns([pl.col(col).cast(pl.Float64) for col in dframe.columns if col != date_col])
80 return interpolate(dframe)