Python research SDK for Muninn — event-native market-data feature computation infrastructure.
Project description
muninn-py
Python research SDK for Muninn — an event-native market-data feature computation platform that emphasises deterministic replay and live/historical parity. Part of the Norse Stack.
muninn-py is the notebook-side companion. It pulls features computed by a running Muninn query-api into Polars or pandas DataFrames, with a typed client that maps the server's contracts to pydantic models.
What you get
from muninn import MuninnClient
with MuninnClient() as m: # zero config: defaults to http://localhost:8080
df = m.get_features(
instrument="BTC-USDT",
features=["vwap.1m", "obi", "vpin"],
start="2026-05-10T14:00:00Z",
end="2026-05-10T15:00:00Z",
)
df.head() # Polars DataFrame indexed by event_time
- Typed responses via pydantic —
FeatureValue,FeatureDefinition,ReplayJob. - Polars-first, pandas-friendly. Switch with
.to_pandas()at the boundary. - Multi-feature joins on
event_timein one call (get_features); outer or inner. - Replay-job orchestration — submit, poll, and reason about a backtest from the notebook.
- Typed exception hierarchy —
MuninnNotFoundError,MuninnValidationError,MuninnTimeoutError,MuninnAPIError.
Install
pip install muninn-py
# or, with notebook extras:
pip install "muninn-py[notebooks]"
Python 3.10+ is required.
Quickstart
-
Start a Muninn server locally. From the main repo:
docker compose up -d --wait ./scripts/smoke.sh
The Query API listens on
http://localhost:8080by default. -
From a Python shell or Jupyter:
from muninn import MuninnClient with MuninnClient() as m: for feat in m.list_features(): print(feat.name, feat.version)
-
Run the bundled notebook for an end-to-end demo (signal IC + replay):
jupyter lab notebooks/alpha_backtest_demo.ipynb
API
| Method | Returns | Description |
|---|---|---|
MuninnClient(host="...", timeout=30.0, headers=None, max_workers=None) |
client | Construct a sync client. Use as a context manager to auto-close. |
list_features() |
list[FeatureDefinition] |
Discover registered feature schemas. |
get_feature(name, *, instrument, start, end, limit=None) |
pl.DataFrame |
One feature's time-series, sorted by event_time. |
get_features(instrument, features, start, end, *, limit=None, join="outer", parallel=True) |
pl.DataFrame |
Multi-feature panel; joined on event_time. Fans out across a thread pool when parallel=True (default). |
get_panel(instruments, features, start, end, *, limit=None, join="outer", parallel=True) |
pl.DataFrame |
Multi-instrument, multi-feature panel. Long-form: columns are instrument, event_time, then one per feature. |
submit_replay_job(*, start, end, topics=None, feature_version=None) |
ReplayJob |
Submit a new replay; returns the initial PENDING state. |
get_replay_job(job_id) |
ReplayJob |
Poll a single job's status. |
list_replay_jobs() |
list[ReplayJob] |
All jobs the server is currently tracking. |
start and end accept either ISO-8601 strings ("2026-05-10T14:00:00Z") or datetime instances.
CLI
Installed automatically with the package:
muninn features list
muninn features get vwap.1m \
--instrument BTC-USDT \
--start 2026-05-10T14:00:00Z \
--end 2026-05-10T15:00:00Z
muninn replay submit --start 2026-05-10T14:00:00Z --end 2026-05-10T15:00:00Z
muninn replay status <jobid>
muninn stream listen --feature vwap.1m
Default host is http://localhost:8080; override with --host or MUNINN_HOST. Output is JSON by default — composable with jq and shell pipelines — with --format table for human-readable display.
Notebook helpers
from muninn import MuninnClient
from muninn.notebook import forward_returns, information_coefficient
with MuninnClient() as m:
df = m.get_features(
instrument="BTC-USDT",
features=["vwap.1m", "obi", "vpin"],
start="2026-05-10T14:00:00Z",
end="2026-05-10T18:00:00Z",
)
df = forward_returns(df, price_col="vwap.1m", periods=[1, 5])
ic = information_coefficient(df, signals=["obi", "vpin"], return_col="fwd_return_1")
Pure functions, Polars-in/Polars-out, no wall-clock reads. Includes forward_returns, information_coefficient, rolling_corr, hit_rate.
Resilient by default
The clients retry transient failures (5xx, connection errors, timeouts) with exponential backoff and disable that behaviour when you want it disabled:
from muninn import MuninnClient, RetryConfig
with MuninnClient(retry=RetryConfig(max_attempts=5, initial_backoff=0.5)) as m:
...
# Disable retry entirely:
with MuninnClient(retry=RetryConfig(max_attempts=1)) as m:
...
For finer-grained control of how long each phase of an HTTP call is allowed to take, pass an httpx.Timeout:
import httpx
client = MuninnClient(timeout=httpx.Timeout(connect=2.0, read=30.0, write=10.0, pool=5.0))
Connection-pool tunables are exposed for operators fronting the API behind a load balancer:
client = MuninnClient(
max_connections=50,
max_keepalive_connections=10,
keepalive_expiry=2.0,
)
Disk-cache for closed windows
Feature time-series over closed event-time windows are deterministic on the server side. Opt into a local on-disk cache so notebook iteration doesn't re-fetch the same range every time:
pip install 'muninn-py[cache]'
with MuninnClient(cache_dir="~/.muninn/cache") as m:
df = m.get_feature(
"vwap.1m", instrument="BTC-USDT",
start="2026-05-10T14:00:00Z", end="2026-05-10T15:00:00Z",
) # one HTTP call
df = m.get_feature(
"vwap.1m", instrument="BTC-USDT",
start="2026-05-10T14:00:00Z", end="2026-05-10T15:00:00Z",
) # cache hit, no HTTP
The cache:
- Stores only closed windows — anything with
end > nowis fetched fresh every time. - Survives process restart. Same
cache_dirbetween runs reuses entries. - Does not version on
code_version. If the server is upgraded with new feature logic, callclient.clear_cache()after.
Pandas-first surface
Already wedded to pandas? Reach the .pandas accessor on any client — every method returns pandas.DataFrame instead of Polars:
with MuninnClient() as m:
df = m.pandas.get_features(
instrument="BTC-USDT",
features=["vwap.1m", "obi"],
start="2026-05-10T14:00:00Z",
end="2026-05-10T15:00:00Z",
)
df.head() # pandas.DataFrame
Available on both MuninnClient.pandas and AsyncMuninnClient.pandas. The conversion is pyarrow-free — no extra heavy deps to install.
Async client
For cooperative-multitasking contexts (FastAPI handlers, async notebooks, integration with other async tooling), use the async sibling — same surface, same return types, httpx.AsyncClient underneath:
from muninn import AsyncMuninnClient
async with AsyncMuninnClient() as m:
df = await m.get_features(
instrument="BTC-USDT",
features=["vwap.1m", "obi", "vpin"],
start="2026-05-10T14:00:00Z",
end="2026-05-10T15:00:00Z",
)
AsyncMuninnClient.get_features always fans out via asyncio.gather. The sync client uses a thread pool with the same effect — both eliminate the serial latency cost of multi-feature fetches.
Live streaming (Server-Sent Events)
get_feature answers historical questions by reading the warehouse. To watch a feature evolve, attach to the server's live stream (GET /api/v1/features/stream, muninn ADR-0009) and receive each value sub-second after its window closes:
from muninn.streaming import MuninnStreamClient
with MuninnStreamClient() as stream:
for event in stream.stream(feature="vwap.1m"): # omit feature= for all features
print(event.feature_name, event.value, event.window_end)
Async sibling — same surface, async for:
from muninn.streaming import AsyncMuninnStreamClient
async with AsyncMuninnStreamClient() as stream:
async for event in stream.stream(feature="vwap.1m"):
...
Each yielded object is a FeatureValue. The stream is a live tail with no backfill — for "last hour, then live", page get_feature for history and then attach the stream. From the shell: muninn stream listen --feature vwap.1m prints newline-delimited JSON.
Why "zero configuration"
Defaults match the Muninn server's local profile (http://localhost:8080, no auth). For deployments behind a reverse proxy, override:
client = MuninnClient(
host="https://muninn.example.internal",
timeout=60.0,
headers={"Authorization": "Bearer <token>"},
)
Authentication itself is an operator concern on the server side — see SECURITY_MODEL.md on the main repo.
Determinism guarantee — what this SDK preserves
Any value you pull through get_feature was emitted by a pure-function computer in the Muninn server (see DETERMINISTIC_REPLAY.md). The SDK does not transform values — it deserializes, joins, and sorts. A replay of the same input range through the same feature_version returns identical numbers. Notebook reproducibility follows from that property as long as you record (start, end, feature_version) for any number you report.
Per ADR-0002, event_id is provenance metadata and may differ across runs; the computational fields are what the determinism claim covers.
Development
git clone https://github.com/lgreene03/muninn-py.git
cd muninn-py
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev,notebooks]"
pytest
The test suite uses respx to mock the Muninn HTTP API — no running server required.
Non-goals
muninn-py is intentionally narrow:
- Not a backtesting framework. It is a data-access library. Use it inside whatever research framework you prefer.
- Not a trading client. No order routing, no execution, no portfolio state. The Muninn server itself is also not these things — see NON_GOALS.md.
- Not a streaming client (yet). Polling-and-DataFrame is the primary mode. A streaming/async path is a possible follow-up if a real use case appears.
License
Apache 2.0. See NOTICE on the main repo for attribution.
Releasing
Publishes go to PyPI via Trusted Publishing (OIDC) on tag push. See docs/RELEASING.md for the one-time setup and the cut-a-release flow.
Related
- Muninn — the server / platform this SDK targets.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file muninn_py-0.1.0.tar.gz.
File metadata
- Download URL: muninn_py-0.1.0.tar.gz
- Upload date:
- Size: 98.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c1c6da82d40ab162b211abf693698a7090c8d5e563d20c3874c51f68c97d758
|
|
| MD5 |
42851fe6ade88845aec07e4865066f01
|
|
| BLAKE2b-256 |
394571c378743786e59cc50b4d2f0be5efb381e4fc21f73c3adcbdd66ad54323
|
Provenance
The following attestation bundles were made for muninn_py-0.1.0.tar.gz:
Publisher:
release.yml on lgreene03/muninn-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
muninn_py-0.1.0.tar.gz -
Subject digest:
8c1c6da82d40ab162b211abf693698a7090c8d5e563d20c3874c51f68c97d758 - Sigstore transparency entry: 1864684635
- Sigstore integration time:
-
Permalink:
lgreene03/muninn-py@1f5891e858c0c4fa6938306da8eac16b69ed45ce -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/lgreene03
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@1f5891e858c0c4fa6938306da8eac16b69ed45ce -
Trigger Event:
push
-
Statement type:
File details
Details for the file muninn_py-0.1.0-py3-none-any.whl.
File metadata
- Download URL: muninn_py-0.1.0-py3-none-any.whl
- Upload date:
- Size: 44.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bd64d875551b2793cbf7da3c4e1546cf44da50dedc7bfe6e4d6b49693c4d8f02
|
|
| MD5 |
e54a1051caa74d5f4870a01356bd57a1
|
|
| BLAKE2b-256 |
c8a870e3c613191d6e7056708b92ba79ce627a482586b3644f4892327669fe69
|
Provenance
The following attestation bundles were made for muninn_py-0.1.0-py3-none-any.whl:
Publisher:
release.yml on lgreene03/muninn-py
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
muninn_py-0.1.0-py3-none-any.whl -
Subject digest:
bd64d875551b2793cbf7da3c4e1546cf44da50dedc7bfe6e4d6b49693c4d8f02 - Sigstore transparency entry: 1864684638
- Sigstore integration time:
-
Permalink:
lgreene03/muninn-py@1f5891e858c0c4fa6938306da8eac16b69ed45ce -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/lgreene03
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@1f5891e858c0c4fa6938306da8eac16b69ed45ce -
Trigger Event:
push
-
Statement type: