Verifiably-correct, Polars-native quant toolkit: technical indicators, performance & risk metrics, and PnL accounting.
Project description
pomata
A Polars-native quant toolkit — technical indicators, PnL accounting, and performance & risk metrics. Each is a
composable pl.Expr, so an entire study is one lazy Polars pipeline, from price to performance.
And it doesn't ask you to trust its numbers — it proves them: every function is verified to the float64 floor
against an independent reference, under 100% branch coverage.
Alpha. The API is not frozen until
1.0; expect refinement. The correctness bar holds at every commit regardless.
From price to performance, in one query
Signal, PnL, and metrics are all plain pl.Expr, so an entire study is a single Polars pipeline — no glue code, no
DataFrame ping-pong, no second dependency between the steps:
import polars as pl
from pomata.indicators import rsi
from pomata.pnl import returns_simple, returns_gross, returns_net, cost_proportional, equity_curve
from pomata.metrics import sharpe_ratio, max_drawdown
report = (
frame # a DataFrame (or LazyFrame) with a "close" column
.with_columns(
weight=(rsi(pl.col("close"), 14) < 30).cast(pl.Float64).shift(1), # go long when oversold, act next bar
asset_returns=returns_simple(pl.col("close")),
)
.with_columns(
net=returns_net(
returns_gross(pl.col("weight"), pl.col("asset_returns")),
cost_proportional(pl.col("weight"), rate=0.001),
),
)
.select(
sharpe=sharpe_ratio(pl.col("net"), periods_per_year=252),
max_drawdown=max_drawdown(equity_curve(pl.col("net"))),
)
)
The indicator feeds the signal, the signal feeds the PnL, the PnL feeds the metrics — every arrow is a pl.Expr, so it
all fuses into one Polars query (eager or lazy, single series or a multi-asset panel via .over). The .shift(1) is
the whole no-look-ahead story: a signal computed at the close acts on the next bar, by construction.
Install
The only runtime dependency is Polars; Python 3.12+.
# from source (today)
git clone https://github.com/ilpomo/pomata
cd pomata && uv sync
Once published to PyPI, the install will be pip install pomata (or uv add pomata).
Every function is a free-standing pl.Expr factory — name it, compose it, run it in any Polars context. Warm-up rows
are null until the window fills, never a fabricated value:
import polars as pl
from pomata.indicators import rsi
frame = pl.DataFrame({"close": [44.34, 44.09, 44.15, 43.61, 44.33, 44.83, 45.10, 45.42, 45.84, 46.08, 45.89, 46.03,
45.61, 46.28, 46.28, 46.00, 46.03, 46.41, 46.22, 45.64, 46.21, 46.25, 45.71, 46.45]})
frame.select(rsi(pl.col("close"), 14).round(2).alias("rsi"))["rsi"].to_list()
# [None, None, ..., 57.92, 62.88, 63.21, 56.01, 62.34]
What's inside
Three families, one package. They share a grammar (pure pl.Expr factories, one canonical name per concept) and a
handoff: pnl emits exactly the return and equity series metrics consumes.
indicators — 75 functions
The technical-analysis layer, each indicator a pl.Expr checked against TA-Lib to the float64 floor — most bar-for-bar
from the first emitted value, a documented minority only on the converged tail (the differential tier is non-gating).
Multi-output indicators (bollinger_bands, macd, stochastic_slow, …) return a single pl.Struct —
pick a line with .struct.field(...) or expand with .struct.unnest().
from pomata.indicators import bollinger_bands
frame.select(bollinger_bands(pl.col("close"), 20).alias("bb")).unnest("bb")
All 75 indicators, by category
- channel (5) —
donchian_channels,ichimoku,keltner_channels,midpoint,midprice - cycle (7) —
dominant_cycle_period,dominant_cycle_phase,hilbert_phasor,hilbert_trendline,mama,sine_wave,trend_mode - directional movement (8) —
adx,adxr,di_minus,di_plus,dm_minus,dm_plus,dx,vortex - momentum (17) —
absolute_price_oscillator,aroon,aroon_oscillator,awesome_oscillator,balance_of_power,cci,chande_momentum_oscillator,fisher_transform,macd,mom,percentage_price_oscillator,roc,rsi,rsi_stochastic,trix,ultimate_oscillator,williams_r - moving average (11) —
dema,ema,hma,kama,rma,sma,t3,tema,trima,vwma,wma - price transform (4) —
price_average,price_median,price_typical,price_weighted_close - statistic (9) —
linear_regression,linear_regression_angle,linear_regression_intercept,linear_regression_slope,standard_deviation_ewma,standard_deviation_rolling,time_series_forecast,variance_ewma,variance_rolling - stochastic (2) —
stochastic_fast,stochastic_slow - trend (2) —
parabolic_sar,supertrend - volatility (4) —
atr,atr_normalized,bollinger_bands,true_range - volume (6) —
accumulation_distribution,accumulation_distribution_oscillator,chaikin_money_flow,money_flow_index,obv,vwap
pnl — 18 functions
Profit-and-loss accounting in two flows: returns (a signed weight of capital and asset returns) and cash
(a quantity of units and a price), with composable transaction-cost models, dividends, and inverse contracts. Every
degenerate input (null / NaN / 0 / ±inf / warm-up) has a defined, documented, tested behavior.
from pomata.pnl import returns_net, returns_gross, cost_proportional, equity_curve
gross = returns_gross(pl.col("weight"), pl.col("asset_returns"))
frame.select(equity_curve(returns_net(gross, cost_proportional(pl.col("weight"), rate=0.001))))
All 18 PnL functions
- cash flow —
cost_borrow,cost_fixed,cost_funding,cost_notional,cost_per_share,cumulative_pnl,dividend,pnl_gross,pnl_gross_inverse,pnl_net - returns flow —
cost_proportional,cost_slippage,equity_curve,returns_gross,returns_log,returns_net,returns_simple,turnover
metrics — 60 functions
Performance & risk statistics as reducing pl.Expr: point one at a return series (e.g. pomata.pnl.returns_net) or an
equity curve (e.g. pomata.pnl.equity_curve). Sharpe, Sortino, Calmar, drawdown, VaR/CVaR, capture, benchmark-relative
(alpha/beta/Treynor/information ratio), and a rolling twin for every windowed form.
from pomata.metrics import sharpe_ratio, max_drawdown
frame.select(sharpe_ratio(pl.col("returns"), periods_per_year=252))
All 60 metrics
- drawdown —
conditional_drawdown_at_risk,drawdown,drawdown_rolling,max_drawdown,max_drawdown_duration,pain_index,ulcer_index - performance —
cagr,cagr_rolling,stability,total_return,total_return_rolling - ratio —
adjusted_sharpe_ratio,burke_ratio,calmar_ratio,common_sense_ratio,gain_to_pain_ratio,omega_ratio,omega_ratio_rolling,pain_ratio,probabilistic_sharpe_ratio,recovery_ratio,sharpe_ratio,sharpe_ratio_rolling,sortino_ratio,sortino_ratio_rolling,sterling_ratio,ulcer_performance_ratio - relative —
alpha,alpha_rolling,beta,beta_rolling,capture_downside_ratio,capture_ratio,capture_upside_ratio,information_ratio,information_ratio_rolling,modigliani_risk_adjusted_performance,treynor_ratio,treynor_ratio_rolling - risk —
conditional_value_at_risk,downside_deviation,downside_deviation_rolling,kelly_criterion,kurtosis,kurtosis_rolling,payoff_ratio,profit_ratio,risk_of_ruin,skewness,skewness_rolling,tail_ratio,tail_ratio_rolling,value_at_risk,value_at_risk_modified,value_at_risk_parametric,value_at_risk_rolling,volatility,volatility_rolling,win_rate
Correctness
Verified, not asserted. Every function is checked against an independent reference — a second code path that shares nothing with the implementation — plus frozen golden-master values and property-based invariants, under 100% branch coverage. A function ships only when that suite is green.
For indicators there is also a public reference to meet: TA-Lib. Here is one figure to every digit a float64 holds —
rsi(14), the last value of a deterministic 400-bar series:
pomata 85.20908701341023
reference 85.20908701341023 ← independent reimplementation: identical, to the last bit
TA-Lib 85.20908701341024 ← fifteen figures identical; differs only at the float64 floor
The same five indicators on the same series — most reproduce the reference exactly, the rest land at the noise floor:
| indicator | pomata | vs reimplementation | vs TA-Lib |
|---|---|---|---|
sma(20) |
105.15146076264764 |
exact | 1e-13 |
ema(20) |
107.7299930892346 |
1e-13 |
1e-14 |
rsi(14) |
85.20908701341023 |
exact | 1e-14 |
atr(14) |
1.904174462198776 |
9e-16 |
4e-15 |
macd(12,26,9) |
2.523444380829531 |
1e-13 |
1e-14 |
The pomata and reference columns are pinned in the test suite; regenerate the full table — including the TA-Lib column
(which needs the optional differential dependency) — from a fresh clone with
uv run --group differential python scripts/precision_table.py.
pnl and metrics are proven on a different axis — every degenerate input has a defined behavior, matched against an
independent reference oracle — because their math is simple and their correctness lives at the edges, not in the digits.
The full method (the precision guarantee, the test-sizing derivations, exactly what is and is not claimed) is in
CORRECTNESS.md.
Where pomata fits
pomata is for the quant already working in Polars. Each function is a free-standing pl.Expr with polars as the only
runtime dependency, composable across eager, lazy, single-series, and grouped (.over) contexts — so the everyday
primitives live in one coherent toolkit instead of a wired-together stack.
It is vectorized analytics and accounting: indicators, total mark-to-market PnL, and metrics. It is not an execution engine — no order fills, no event loop, no lot accounting.
Project
- Requirements — Python ≥ 3.12, Polars ≥ 1.40.
- Contributing — see CONTRIBUTING.md; the full gate (lint, three gating type checkers plus an advisory fourth, doctests, 100% branch coverage) runs on every commit.
- License — MIT, see LICENSE.
- Citation — CITATION.cff.
Project details
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 pomata-0.1.1.tar.gz.
File metadata
- Download URL: pomata-0.1.1.tar.gz
- Upload date:
- Size: 547.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6354632c4a084102f4d6b3df13d36cb285359c2780868d243b4a9c32304c2eef
|
|
| MD5 |
69247cbd3dc5146d115de59f455fd54d
|
|
| BLAKE2b-256 |
e3c4f2f5c23ccdbf98340736ed72c61c1304047d240e62a658e560a565fbd3bf
|
Provenance
The following attestation bundles were made for pomata-0.1.1.tar.gz:
Publisher:
release.yml on ilpomo/pomata
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pomata-0.1.1.tar.gz -
Subject digest:
6354632c4a084102f4d6b3df13d36cb285359c2780868d243b4a9c32304c2eef - Sigstore transparency entry: 1987800509
- Sigstore integration time:
-
Permalink:
ilpomo/pomata@baef494ae7d7877018dc3e7fd174a13453063a3f -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/ilpomo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@baef494ae7d7877018dc3e7fd174a13453063a3f -
Trigger Event:
release
-
Statement type:
File details
Details for the file pomata-0.1.1-py3-none-any.whl.
File metadata
- Download URL: pomata-0.1.1-py3-none-any.whl
- Upload date:
- Size: 162.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
708aa8f5227bdb243e229283594a446d7be88d9a111b578e86c515392ccdf860
|
|
| MD5 |
daca7a080a17394bb5e1a4cfd74dfe45
|
|
| BLAKE2b-256 |
dbbe0f624592d45d4c97a753231083c03e79b517abfd2662034e276e0a8789ba
|
Provenance
The following attestation bundles were made for pomata-0.1.1-py3-none-any.whl:
Publisher:
release.yml on ilpomo/pomata
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pomata-0.1.1-py3-none-any.whl -
Subject digest:
708aa8f5227bdb243e229283594a446d7be88d9a111b578e86c515392ccdf860 - Sigstore transparency entry: 1987800581
- Sigstore integration time:
-
Permalink:
ilpomo/pomata@baef494ae7d7877018dc3e7fd174a13453063a3f -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/ilpomo
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@baef494ae7d7877018dc3e7fd174a13453063a3f -
Trigger Event:
release
-
Statement type: