Skip to main content

jointview

A small marimo app for looking at two price or NAV series of a Polars DataFrame at once.

Every numeric column is a series you can pick, one on the left and one on the right. The middle holds both as two lines on one pair of axes; underneath each dropdown sits the summary of exactly the rows in that plot.

┌──────────┬───────────────────────────┬──────────┐
│ left     │      two-line chart       │ right    │
│ dropdown │   (indexed to 100)        │ dropdown │
│ ─────────│                           │──────────│
│ summary  │                           │ summary  │
│ table    │                           │ table    │
└──────────┴───────────────────────────┴──────────┘

Both lines share one y-axis: a second scale would invent a relationship that is not in the data. So the default is to index both series to 100 at the first date they have in common, which is how a fund priced at 1.02 and one priced at 1,450 end up comparable. The switch above the plot turns that off when the levels already share a scale.

Run it

There is nothing to install: the app ships with the package, and uvx fetches both for the length of the run.

uvx jointview                            # generated demo frame
uvx jointview navs.parquet               # your own data
uvx jointview navs.parquet --height 900  # taller plot for a taller screen

Until the package is on PyPI, point uvx at the repository — or at a checkout of it:

uvx --from git+https://github.com/jebel-quant/jointview jointview navs.parquet
uv run jointview navs.parquet            # from a clone
uv run jointview --edit                  # open the notebook itself

--edit only makes sense from a clone: it opens the notebook marimo is serving, and under uvx that is a copy in a throwaway environment.

Arguments after a bare -- belong to marimo rather than to the app, which is how the server itself is configured:

uvx jointview navs.parquet -- --port 8080 --headless

The plot takes the full width of the window and the tables sit either side of it. Its height is the one thing the page cannot work out for itself — 700px suits a laptop, and --height is there for a monitor that has more to give.

The file reads as .parquet, .csv, .tsv, .json, .ndjson, .arrow, .ipc and .feather. The first temporal column becomes the x-axis; without one the rows are numbered. Every numeric column is offered as a series. Given no file you get jointview.data.demo_frame(): daily NAVs for six made-up funds that share a market factor and start anywhere between 1 and 1,450.

The summary

Both tables are computed from the common sample — the dates where both series are present — so the numbers always describe the lines you are looking at.

Observations, Start, End the extent of the series
Total return, Annual return end/start, and the same compounded to a year
Annual volatility, Sharpe ratio of the period returns, at 252 periods a year, cash at zero
Max drawdown the deepest fall below the running peak
Hit rate, Best period, Worst period the shape of the period returns

A figure that cannot be formed — a Sharpe ratio for a flat series, a growth rate for a series that starts at zero — shows as rather than blanking the table.

Use the pieces on their own

Neither the chart nor the statistics need marimo:

import polars as pl
from jointview import line_chart, summary, metrics

frame = pl.read_parquet("navs.parquet")
line_chart(frame, "tech_fund", "balanced").save("lines.html")

summary(frame["tech_fund"])              # a formatted two-column frame
metrics(frame["tech_fund"])["Sharpe ratio"]   # the raw number

line_chart is a plain Altair chart: two 2px lines, a legend and a label at the end of each line, and a crosshair that reads both series at the hovered date. Curves longer than max_points (4,000 by default) are thinned by a fixed stride — the last point always survives, so the endpoints and the summary agree.

Develop

uv sync
uv run pytest

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

jointview-0.2.0.tar.gz (18.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

jointview-0.2.0-py3-none-any.whl (21.4 kB view details)

Uploaded Python 3

File details

Details for the file jointview-0.2.0.tar.gz.

File metadata

  • Download URL: jointview-0.2.0.tar.gz
  • Upload date:
  • Size: 18.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for jointview-0.2.0.tar.gz
Algorithm Hash digest
SHA256 8247374bf6fed56b826ec589b52a0458329509aa81e72043cc32ae46922e32d8
MD5 f4e5f6458accb012ce54d7be83ffb438
BLAKE2b-256 3961d1ca7ebf05ea5de73713dc7a22c7f01048b6c4bf85b37b6a57d03ca78075

See more details on using hashes here.

Provenance

The following attestation bundles were made for jointview-0.2.0.tar.gz:

Publisher: rhiza_release.yml on Jebel-Quant/jointview

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file jointview-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: jointview-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 21.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for jointview-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 19113e481768e3c9d64d58abee323e4f55a20b989cbcac5adc68cf91faebd829
MD5 d7fbe8ff97ca1159713f8e2d5a5e7530
BLAKE2b-256 14bbd62bfb5e49cc5162335bfb4710b5ae132f22c550d267a206aab92ffb5017

See more details on using hashes here.

Provenance

The following attestation bundles were made for jointview-0.2.0-py3-none-any.whl:

Publisher: rhiza_release.yml on Jebel-Quant/jointview

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page