pomata 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pomata-0.1.0/.gitignore +30 -0
- pomata-0.1.0/CORRECTNESS.md +297 -0
- pomata-0.1.0/LICENSE +21 -0
- pomata-0.1.0/PKG-INFO +235 -0
- pomata-0.1.0/README.md +207 -0
- pomata-0.1.0/pyproject.toml +266 -0
- pomata-0.1.0/src/pomata/__init__.py +21 -0
- pomata-0.1.0/src/pomata/_expr.py +230 -0
- pomata-0.1.0/src/pomata/indicators/__init__.py +181 -0
- pomata-0.1.0/src/pomata/indicators/channel.py +563 -0
- pomata-0.1.0/src/pomata/indicators/cycle.py +754 -0
- pomata-0.1.0/src/pomata/indicators/directional_movement.py +971 -0
- pomata-0.1.0/src/pomata/indicators/momentum.py +1994 -0
- pomata-0.1.0/src/pomata/indicators/moving_average.py +1245 -0
- pomata-0.1.0/src/pomata/indicators/price_transform.py +437 -0
- pomata-0.1.0/src/pomata/indicators/statistic.py +787 -0
- pomata-0.1.0/src/pomata/indicators/stochastic.py +300 -0
- pomata-0.1.0/src/pomata/indicators/trend.py +408 -0
- pomata-0.1.0/src/pomata/indicators/volatility.py +503 -0
- pomata-0.1.0/src/pomata/indicators/volume.py +864 -0
- pomata-0.1.0/src/pomata/metrics/__init__.py +149 -0
- pomata-0.1.0/src/pomata/metrics/drawdown.py +570 -0
- pomata-0.1.0/src/pomata/metrics/performance.py +451 -0
- pomata-0.1.0/src/pomata/metrics/ratio.py +1522 -0
- pomata-0.1.0/src/pomata/metrics/relative.py +1408 -0
- pomata-0.1.0/src/pomata/metrics/risk.py +2006 -0
- pomata-0.1.0/src/pomata/pnl/__init__.py +68 -0
- pomata-0.1.0/src/pomata/pnl/accounting.py +916 -0
- pomata-0.1.0/src/pomata/pnl/costs.py +674 -0
- pomata-0.1.0/src/pomata/pnl/returns.py +183 -0
- pomata-0.1.0/src/pomata/py.typed +0 -0
- pomata-0.1.0/tests/README.md +32 -0
- pomata-0.1.0/tests/conftest.py +33 -0
- pomata-0.1.0/tests/indicators/oracles/__init__.py +169 -0
- pomata-0.1.0/tests/indicators/oracles/_helpers.py +94 -0
- pomata-0.1.0/tests/indicators/oracles/_regenerate.py +42 -0
- pomata-0.1.0/tests/indicators/oracles/absolute_price_oscillator.py +45 -0
- pomata-0.1.0/tests/indicators/oracles/accumulation_distribution.py +89 -0
- pomata-0.1.0/tests/indicators/oracles/accumulation_distribution_oscillator.py +56 -0
- pomata-0.1.0/tests/indicators/oracles/adx.py +37 -0
- pomata-0.1.0/tests/indicators/oracles/adxr.py +48 -0
- pomata-0.1.0/tests/indicators/oracles/aroon.py +71 -0
- pomata-0.1.0/tests/indicators/oracles/aroon_oscillator.py +42 -0
- pomata-0.1.0/tests/indicators/oracles/atr.py +58 -0
- pomata-0.1.0/tests/indicators/oracles/atr_normalized.py +45 -0
- pomata-0.1.0/tests/indicators/oracles/awesome_oscillator.py +57 -0
- pomata-0.1.0/tests/indicators/oracles/balance_of_power.py +54 -0
- pomata-0.1.0/tests/indicators/oracles/bollinger_bands.py +58 -0
- pomata-0.1.0/tests/indicators/oracles/cci.py +109 -0
- pomata-0.1.0/tests/indicators/oracles/chaikin_money_flow.py +96 -0
- pomata-0.1.0/tests/indicators/oracles/chande_momentum_oscillator.py +68 -0
- pomata-0.1.0/tests/indicators/oracles/cycle.py +194 -0
- pomata-0.1.0/tests/indicators/oracles/dema.py +65 -0
- pomata-0.1.0/tests/indicators/oracles/di_minus.py +50 -0
- pomata-0.1.0/tests/indicators/oracles/di_plus.py +50 -0
- pomata-0.1.0/tests/indicators/oracles/dm_minus.py +44 -0
- pomata-0.1.0/tests/indicators/oracles/dm_plus.py +44 -0
- pomata-0.1.0/tests/indicators/oracles/donchian_channels.py +80 -0
- pomata-0.1.0/tests/indicators/oracles/dx.py +49 -0
- pomata-0.1.0/tests/indicators/oracles/ema.py +82 -0
- pomata-0.1.0/tests/indicators/oracles/fisher_transform.py +106 -0
- pomata-0.1.0/tests/indicators/oracles/hma.py +59 -0
- pomata-0.1.0/tests/indicators/oracles/ichimoku.py +107 -0
- pomata-0.1.0/tests/indicators/oracles/kama.py +106 -0
- pomata-0.1.0/tests/indicators/oracles/keltner_channels.py +71 -0
- pomata-0.1.0/tests/indicators/oracles/linear_regression.py +44 -0
- pomata-0.1.0/tests/indicators/oracles/linear_regression_angle.py +43 -0
- pomata-0.1.0/tests/indicators/oracles/linear_regression_intercept.py +44 -0
- pomata-0.1.0/tests/indicators/oracles/linear_regression_slope.py +55 -0
- pomata-0.1.0/tests/indicators/oracles/macd.py +64 -0
- pomata-0.1.0/tests/indicators/oracles/midpoint.py +53 -0
- pomata-0.1.0/tests/indicators/oracles/midprice.py +54 -0
- pomata-0.1.0/tests/indicators/oracles/mom.py +55 -0
- pomata-0.1.0/tests/indicators/oracles/money_flow_index.py +117 -0
- pomata-0.1.0/tests/indicators/oracles/obv.py +82 -0
- pomata-0.1.0/tests/indicators/oracles/parabolic_sar.py +100 -0
- pomata-0.1.0/tests/indicators/oracles/percentage_price_oscillator.py +58 -0
- pomata-0.1.0/tests/indicators/oracles/price_average.py +49 -0
- pomata-0.1.0/tests/indicators/oracles/price_median.py +45 -0
- pomata-0.1.0/tests/indicators/oracles/price_typical.py +48 -0
- pomata-0.1.0/tests/indicators/oracles/price_weighted_close.py +48 -0
- pomata-0.1.0/tests/indicators/oracles/rma.py +48 -0
- pomata-0.1.0/tests/indicators/oracles/roc.py +66 -0
- pomata-0.1.0/tests/indicators/oracles/rsi.py +95 -0
- pomata-0.1.0/tests/indicators/oracles/rsi_stochastic.py +43 -0
- pomata-0.1.0/tests/indicators/oracles/sma.py +57 -0
- pomata-0.1.0/tests/indicators/oracles/standard_deviation_ewma.py +52 -0
- pomata-0.1.0/tests/indicators/oracles/standard_deviation_rolling.py +41 -0
- pomata-0.1.0/tests/indicators/oracles/stochastic_fast.py +85 -0
- pomata-0.1.0/tests/indicators/oracles/stochastic_slow.py +47 -0
- pomata-0.1.0/tests/indicators/oracles/supertrend.py +150 -0
- pomata-0.1.0/tests/indicators/oracles/t3.py +77 -0
- pomata-0.1.0/tests/indicators/oracles/tema.py +66 -0
- pomata-0.1.0/tests/indicators/oracles/time_series_forecast.py +45 -0
- pomata-0.1.0/tests/indicators/oracles/trima.py +69 -0
- pomata-0.1.0/tests/indicators/oracles/trix.py +35 -0
- pomata-0.1.0/tests/indicators/oracles/true_range.py +68 -0
- pomata-0.1.0/tests/indicators/oracles/ultimate_oscillator.py +130 -0
- pomata-0.1.0/tests/indicators/oracles/variance_ewma.py +136 -0
- pomata-0.1.0/tests/indicators/oracles/variance_rolling.py +60 -0
- pomata-0.1.0/tests/indicators/oracles/vortex.py +96 -0
- pomata-0.1.0/tests/indicators/oracles/vwap.py +89 -0
- pomata-0.1.0/tests/indicators/oracles/vwma.py +75 -0
- pomata-0.1.0/tests/indicators/oracles/williams_r.py +83 -0
- pomata-0.1.0/tests/indicators/oracles/wma.py +61 -0
- pomata-0.1.0/tests/indicators/test_absolute_price_oscillator.py +334 -0
- pomata-0.1.0/tests/indicators/test_accumulation_distribution.py +469 -0
- pomata-0.1.0/tests/indicators/test_accumulation_distribution_oscillator.py +416 -0
- pomata-0.1.0/tests/indicators/test_adx.py +328 -0
- pomata-0.1.0/tests/indicators/test_adxr.py +326 -0
- pomata-0.1.0/tests/indicators/test_aroon.py +353 -0
- pomata-0.1.0/tests/indicators/test_aroon_oscillator.py +298 -0
- pomata-0.1.0/tests/indicators/test_atr.py +472 -0
- pomata-0.1.0/tests/indicators/test_atr_normalized.py +315 -0
- pomata-0.1.0/tests/indicators/test_awesome_oscillator.py +314 -0
- pomata-0.1.0/tests/indicators/test_balance_of_power.py +266 -0
- pomata-0.1.0/tests/indicators/test_benchmark.py +246 -0
- pomata-0.1.0/tests/indicators/test_bollinger_bands.py +369 -0
- pomata-0.1.0/tests/indicators/test_cci.py +428 -0
- pomata-0.1.0/tests/indicators/test_chaikin_money_flow.py +573 -0
- pomata-0.1.0/tests/indicators/test_chande_momentum_oscillator.py +358 -0
- pomata-0.1.0/tests/indicators/test_dema.py +358 -0
- pomata-0.1.0/tests/indicators/test_di_minus.py +314 -0
- pomata-0.1.0/tests/indicators/test_di_plus.py +314 -0
- pomata-0.1.0/tests/indicators/test_differential.py +632 -0
- pomata-0.1.0/tests/indicators/test_dm_minus.py +293 -0
- pomata-0.1.0/tests/indicators/test_dm_plus.py +291 -0
- pomata-0.1.0/tests/indicators/test_dominant_cycle_period.py +259 -0
- pomata-0.1.0/tests/indicators/test_dominant_cycle_phase.py +290 -0
- pomata-0.1.0/tests/indicators/test_donchian_channels.py +407 -0
- pomata-0.1.0/tests/indicators/test_dtype.py +42 -0
- pomata-0.1.0/tests/indicators/test_dx.py +317 -0
- pomata-0.1.0/tests/indicators/test_ema.py +386 -0
- pomata-0.1.0/tests/indicators/test_fisher_transform.py +349 -0
- pomata-0.1.0/tests/indicators/test_hilbert_phasor.py +301 -0
- pomata-0.1.0/tests/indicators/test_hilbert_trendline.py +277 -0
- pomata-0.1.0/tests/indicators/test_hma.py +350 -0
- pomata-0.1.0/tests/indicators/test_ichimoku.py +439 -0
- pomata-0.1.0/tests/indicators/test_kama.py +336 -0
- pomata-0.1.0/tests/indicators/test_keltner_channels.py +392 -0
- pomata-0.1.0/tests/indicators/test_linear_regression.py +299 -0
- pomata-0.1.0/tests/indicators/test_linear_regression_angle.py +271 -0
- pomata-0.1.0/tests/indicators/test_linear_regression_intercept.py +298 -0
- pomata-0.1.0/tests/indicators/test_linear_regression_slope.py +294 -0
- pomata-0.1.0/tests/indicators/test_macd.py +395 -0
- pomata-0.1.0/tests/indicators/test_mama.py +362 -0
- pomata-0.1.0/tests/indicators/test_midpoint.py +273 -0
- pomata-0.1.0/tests/indicators/test_midprice.py +301 -0
- pomata-0.1.0/tests/indicators/test_mom.py +306 -0
- pomata-0.1.0/tests/indicators/test_money_flow_index.py +623 -0
- pomata-0.1.0/tests/indicators/test_obv.py +483 -0
- pomata-0.1.0/tests/indicators/test_parabolic_sar.py +418 -0
- pomata-0.1.0/tests/indicators/test_percentage_price_oscillator.py +325 -0
- pomata-0.1.0/tests/indicators/test_precision_table.py +82 -0
- pomata-0.1.0/tests/indicators/test_price_average.py +338 -0
- pomata-0.1.0/tests/indicators/test_price_median.py +299 -0
- pomata-0.1.0/tests/indicators/test_price_typical.py +322 -0
- pomata-0.1.0/tests/indicators/test_price_weighted_close.py +320 -0
- pomata-0.1.0/tests/indicators/test_rma.py +337 -0
- pomata-0.1.0/tests/indicators/test_roc.py +300 -0
- pomata-0.1.0/tests/indicators/test_rsi.py +337 -0
- pomata-0.1.0/tests/indicators/test_rsi_stochastic.py +460 -0
- pomata-0.1.0/tests/indicators/test_sine_wave.py +376 -0
- pomata-0.1.0/tests/indicators/test_sma.py +274 -0
- pomata-0.1.0/tests/indicators/test_standard_deviation_ewma.py +299 -0
- pomata-0.1.0/tests/indicators/test_standard_deviation_rolling.py +322 -0
- pomata-0.1.0/tests/indicators/test_stochastic_fast.py +390 -0
- pomata-0.1.0/tests/indicators/test_stochastic_slow.py +428 -0
- pomata-0.1.0/tests/indicators/test_supertrend.py +435 -0
- pomata-0.1.0/tests/indicators/test_t3.py +376 -0
- pomata-0.1.0/tests/indicators/test_tema.py +371 -0
- pomata-0.1.0/tests/indicators/test_time_series_forecast.py +301 -0
- pomata-0.1.0/tests/indicators/test_trend_mode.py +289 -0
- pomata-0.1.0/tests/indicators/test_trima.py +280 -0
- pomata-0.1.0/tests/indicators/test_trix.py +252 -0
- pomata-0.1.0/tests/indicators/test_true_range.py +381 -0
- pomata-0.1.0/tests/indicators/test_typing.py +74 -0
- pomata-0.1.0/tests/indicators/test_ultimate_oscillator.py +419 -0
- pomata-0.1.0/tests/indicators/test_variance_ewma.py +303 -0
- pomata-0.1.0/tests/indicators/test_variance_rolling.py +318 -0
- pomata-0.1.0/tests/indicators/test_vortex.py +329 -0
- pomata-0.1.0/tests/indicators/test_vwap.py +361 -0
- pomata-0.1.0/tests/indicators/test_vwma.py +503 -0
- pomata-0.1.0/tests/indicators/test_williams_r.py +571 -0
- pomata-0.1.0/tests/indicators/test_wma.py +318 -0
- pomata-0.1.0/tests/metrics/oracles/__init__.py +130 -0
- pomata-0.1.0/tests/metrics/oracles/_quantile.py +36 -0
- pomata-0.1.0/tests/metrics/oracles/_rolling.py +76 -0
- pomata-0.1.0/tests/metrics/oracles/adjusted_sharpe_ratio.py +45 -0
- pomata-0.1.0/tests/metrics/oracles/alpha.py +42 -0
- pomata-0.1.0/tests/metrics/oracles/alpha_rolling.py +28 -0
- pomata-0.1.0/tests/metrics/oracles/beta.py +35 -0
- pomata-0.1.0/tests/metrics/oracles/beta_rolling.py +17 -0
- pomata-0.1.0/tests/metrics/oracles/burke_ratio.py +36 -0
- pomata-0.1.0/tests/metrics/oracles/cagr.py +24 -0
- pomata-0.1.0/tests/metrics/oracles/cagr_rolling.py +32 -0
- pomata-0.1.0/tests/metrics/oracles/calmar_ratio.py +31 -0
- pomata-0.1.0/tests/metrics/oracles/capture_downside_ratio.py +36 -0
- pomata-0.1.0/tests/metrics/oracles/capture_ratio.py +34 -0
- pomata-0.1.0/tests/metrics/oracles/capture_upside_ratio.py +36 -0
- pomata-0.1.0/tests/metrics/oracles/common_sense_ratio.py +27 -0
- pomata-0.1.0/tests/metrics/oracles/conditional_drawdown_at_risk.py +30 -0
- pomata-0.1.0/tests/metrics/oracles/conditional_value_at_risk.py +29 -0
- pomata-0.1.0/tests/metrics/oracles/downside_deviation.py +33 -0
- pomata-0.1.0/tests/metrics/oracles/downside_deviation_rolling.py +19 -0
- pomata-0.1.0/tests/metrics/oracles/drawdown.py +29 -0
- pomata-0.1.0/tests/metrics/oracles/drawdown_rolling.py +30 -0
- pomata-0.1.0/tests/metrics/oracles/gain_to_pain_ratio.py +27 -0
- pomata-0.1.0/tests/metrics/oracles/information_ratio.py +32 -0
- pomata-0.1.0/tests/metrics/oracles/information_ratio_rolling.py +24 -0
- pomata-0.1.0/tests/metrics/oracles/kelly_criterion.py +27 -0
- pomata-0.1.0/tests/metrics/oracles/kurtosis.py +31 -0
- pomata-0.1.0/tests/metrics/oracles/kurtosis_rolling.py +15 -0
- pomata-0.1.0/tests/metrics/oracles/max_drawdown.py +24 -0
- pomata-0.1.0/tests/metrics/oracles/max_drawdown_duration.py +33 -0
- pomata-0.1.0/tests/metrics/oracles/modigliani_risk_adjusted_performance.py +37 -0
- pomata-0.1.0/tests/metrics/oracles/omega_ratio.py +30 -0
- pomata-0.1.0/tests/metrics/oracles/omega_ratio_rolling.py +17 -0
- pomata-0.1.0/tests/metrics/oracles/pain_index.py +26 -0
- pomata-0.1.0/tests/metrics/oracles/pain_ratio.py +33 -0
- pomata-0.1.0/tests/metrics/oracles/payoff_ratio.py +26 -0
- pomata-0.1.0/tests/metrics/oracles/probabilistic_sharpe_ratio.py +47 -0
- pomata-0.1.0/tests/metrics/oracles/profit_ratio.py +27 -0
- pomata-0.1.0/tests/metrics/oracles/recovery_ratio.py +33 -0
- pomata-0.1.0/tests/metrics/oracles/risk_of_ruin.py +30 -0
- pomata-0.1.0/tests/metrics/oracles/sharpe_ratio.py +34 -0
- pomata-0.1.0/tests/metrics/oracles/sharpe_ratio_rolling.py +19 -0
- pomata-0.1.0/tests/metrics/oracles/skewness.py +31 -0
- pomata-0.1.0/tests/metrics/oracles/skewness_rolling.py +15 -0
- pomata-0.1.0/tests/metrics/oracles/sortino_ratio.py +33 -0
- pomata-0.1.0/tests/metrics/oracles/sortino_ratio_rolling.py +19 -0
- pomata-0.1.0/tests/metrics/oracles/stability.py +41 -0
- pomata-0.1.0/tests/metrics/oracles/sterling_ratio.py +35 -0
- pomata-0.1.0/tests/metrics/oracles/tail_ratio.py +30 -0
- pomata-0.1.0/tests/metrics/oracles/tail_ratio_rolling.py +15 -0
- pomata-0.1.0/tests/metrics/oracles/total_return.py +21 -0
- pomata-0.1.0/tests/metrics/oracles/total_return_rolling.py +30 -0
- pomata-0.1.0/tests/metrics/oracles/treynor_ratio.py +38 -0
- pomata-0.1.0/tests/metrics/oracles/treynor_ratio_rolling.py +28 -0
- pomata-0.1.0/tests/metrics/oracles/ulcer_index.py +24 -0
- pomata-0.1.0/tests/metrics/oracles/ulcer_performance_ratio.py +34 -0
- pomata-0.1.0/tests/metrics/oracles/value_at_risk.py +24 -0
- pomata-0.1.0/tests/metrics/oracles/value_at_risk_modified.py +41 -0
- pomata-0.1.0/tests/metrics/oracles/value_at_risk_parametric.py +27 -0
- pomata-0.1.0/tests/metrics/oracles/value_at_risk_rolling.py +17 -0
- pomata-0.1.0/tests/metrics/oracles/volatility.py +27 -0
- pomata-0.1.0/tests/metrics/oracles/volatility_rolling.py +17 -0
- pomata-0.1.0/tests/metrics/oracles/win_rate.py +24 -0
- pomata-0.1.0/tests/metrics/test_adjusted_sharpe_ratio.py +240 -0
- pomata-0.1.0/tests/metrics/test_alpha.py +313 -0
- pomata-0.1.0/tests/metrics/test_alpha_rolling.py +306 -0
- pomata-0.1.0/tests/metrics/test_benchmark.py +209 -0
- pomata-0.1.0/tests/metrics/test_beta.py +265 -0
- pomata-0.1.0/tests/metrics/test_beta_rolling.py +265 -0
- pomata-0.1.0/tests/metrics/test_burke_ratio.py +217 -0
- pomata-0.1.0/tests/metrics/test_cagr.py +209 -0
- pomata-0.1.0/tests/metrics/test_cagr_rolling.py +227 -0
- pomata-0.1.0/tests/metrics/test_calmar_ratio.py +211 -0
- pomata-0.1.0/tests/metrics/test_capture_downside_ratio.py +291 -0
- pomata-0.1.0/tests/metrics/test_capture_ratio.py +285 -0
- pomata-0.1.0/tests/metrics/test_capture_upside_ratio.py +289 -0
- pomata-0.1.0/tests/metrics/test_common_sense_ratio.py +199 -0
- pomata-0.1.0/tests/metrics/test_conditional_drawdown_at_risk.py +213 -0
- pomata-0.1.0/tests/metrics/test_conditional_value_at_risk.py +248 -0
- pomata-0.1.0/tests/metrics/test_downside_deviation.py +258 -0
- pomata-0.1.0/tests/metrics/test_downside_deviation_rolling.py +250 -0
- pomata-0.1.0/tests/metrics/test_drawdown.py +197 -0
- pomata-0.1.0/tests/metrics/test_drawdown_rolling.py +214 -0
- pomata-0.1.0/tests/metrics/test_dtype.py +42 -0
- pomata-0.1.0/tests/metrics/test_gain_to_pain_ratio.py +205 -0
- pomata-0.1.0/tests/metrics/test_information_ratio.py +303 -0
- pomata-0.1.0/tests/metrics/test_information_ratio_rolling.py +316 -0
- pomata-0.1.0/tests/metrics/test_kelly_criterion.py +206 -0
- pomata-0.1.0/tests/metrics/test_kurtosis.py +219 -0
- pomata-0.1.0/tests/metrics/test_kurtosis_rolling.py +222 -0
- pomata-0.1.0/tests/metrics/test_max_drawdown.py +197 -0
- pomata-0.1.0/tests/metrics/test_max_drawdown_duration.py +194 -0
- pomata-0.1.0/tests/metrics/test_modigliani_risk_adjusted_performance.py +339 -0
- pomata-0.1.0/tests/metrics/test_omega_ratio.py +223 -0
- pomata-0.1.0/tests/metrics/test_omega_ratio_rolling.py +251 -0
- pomata-0.1.0/tests/metrics/test_pain_index.py +206 -0
- pomata-0.1.0/tests/metrics/test_pain_ratio.py +214 -0
- pomata-0.1.0/tests/metrics/test_payoff_ratio.py +205 -0
- pomata-0.1.0/tests/metrics/test_probabilistic_sharpe_ratio.py +279 -0
- pomata-0.1.0/tests/metrics/test_profit_ratio.py +211 -0
- pomata-0.1.0/tests/metrics/test_recovery_ratio.py +202 -0
- pomata-0.1.0/tests/metrics/test_risk_of_ruin.py +211 -0
- pomata-0.1.0/tests/metrics/test_sharpe_ratio.py +226 -0
- pomata-0.1.0/tests/metrics/test_sharpe_ratio_rolling.py +246 -0
- pomata-0.1.0/tests/metrics/test_skewness.py +219 -0
- pomata-0.1.0/tests/metrics/test_skewness_rolling.py +222 -0
- pomata-0.1.0/tests/metrics/test_sortino_ratio.py +228 -0
- pomata-0.1.0/tests/metrics/test_sortino_ratio_rolling.py +270 -0
- pomata-0.1.0/tests/metrics/test_stability.py +216 -0
- pomata-0.1.0/tests/metrics/test_sterling_ratio.py +218 -0
- pomata-0.1.0/tests/metrics/test_tail_ratio.py +220 -0
- pomata-0.1.0/tests/metrics/test_tail_ratio_rolling.py +206 -0
- pomata-0.1.0/tests/metrics/test_total_return.py +183 -0
- pomata-0.1.0/tests/metrics/test_total_return_rolling.py +210 -0
- pomata-0.1.0/tests/metrics/test_treynor_ratio.py +357 -0
- pomata-0.1.0/tests/metrics/test_treynor_ratio_rolling.py +370 -0
- pomata-0.1.0/tests/metrics/test_typing.py +131 -0
- pomata-0.1.0/tests/metrics/test_ulcer_index.py +198 -0
- pomata-0.1.0/tests/metrics/test_ulcer_performance_ratio.py +237 -0
- pomata-0.1.0/tests/metrics/test_value_at_risk.py +227 -0
- pomata-0.1.0/tests/metrics/test_value_at_risk_modified.py +239 -0
- pomata-0.1.0/tests/metrics/test_value_at_risk_parametric.py +237 -0
- pomata-0.1.0/tests/metrics/test_value_at_risk_rolling.py +235 -0
- pomata-0.1.0/tests/metrics/test_volatility.py +252 -0
- pomata-0.1.0/tests/metrics/test_volatility_rolling.py +237 -0
- pomata-0.1.0/tests/metrics/test_win_rate.py +218 -0
- pomata-0.1.0/tests/pnl/oracles/__init__.py +49 -0
- pomata-0.1.0/tests/pnl/oracles/cost_borrow.py +55 -0
- pomata-0.1.0/tests/pnl/oracles/cost_fixed.py +54 -0
- pomata-0.1.0/tests/pnl/oracles/cost_funding.py +47 -0
- pomata-0.1.0/tests/pnl/oracles/cost_notional.py +58 -0
- pomata-0.1.0/tests/pnl/oracles/cost_per_share.py +43 -0
- pomata-0.1.0/tests/pnl/oracles/cost_proportional.py +44 -0
- pomata-0.1.0/tests/pnl/oracles/cost_slippage.py +44 -0
- pomata-0.1.0/tests/pnl/oracles/cumulative_pnl.py +42 -0
- pomata-0.1.0/tests/pnl/oracles/dividend.py +46 -0
- pomata-0.1.0/tests/pnl/oracles/equity_curve.py +44 -0
- pomata-0.1.0/tests/pnl/oracles/pnl_gross.py +61 -0
- pomata-0.1.0/tests/pnl/oracles/pnl_gross_inverse.py +70 -0
- pomata-0.1.0/tests/pnl/oracles/pnl_net.py +44 -0
- pomata-0.1.0/tests/pnl/oracles/returns_gross.py +45 -0
- pomata-0.1.0/tests/pnl/oracles/returns_log.py +64 -0
- pomata-0.1.0/tests/pnl/oracles/returns_net.py +44 -0
- pomata-0.1.0/tests/pnl/oracles/returns_simple.py +58 -0
- pomata-0.1.0/tests/pnl/oracles/turnover.py +44 -0
- pomata-0.1.0/tests/pnl/test_benchmark.py +124 -0
- pomata-0.1.0/tests/pnl/test_cost_borrow.py +301 -0
- pomata-0.1.0/tests/pnl/test_cost_fixed.py +253 -0
- pomata-0.1.0/tests/pnl/test_cost_funding.py +325 -0
- pomata-0.1.0/tests/pnl/test_cost_notional.py +296 -0
- pomata-0.1.0/tests/pnl/test_cost_per_share.py +251 -0
- pomata-0.1.0/tests/pnl/test_cost_proportional.py +272 -0
- pomata-0.1.0/tests/pnl/test_cost_slippage.py +276 -0
- pomata-0.1.0/tests/pnl/test_cumulative_pnl.py +268 -0
- pomata-0.1.0/tests/pnl/test_dividend.py +284 -0
- pomata-0.1.0/tests/pnl/test_dtype.py +42 -0
- pomata-0.1.0/tests/pnl/test_equity_curve.py +234 -0
- pomata-0.1.0/tests/pnl/test_pnl_gross.py +353 -0
- pomata-0.1.0/tests/pnl/test_pnl_gross_inverse.py +414 -0
- pomata-0.1.0/tests/pnl/test_pnl_net.py +283 -0
- pomata-0.1.0/tests/pnl/test_returns_gross.py +289 -0
- pomata-0.1.0/tests/pnl/test_returns_log.py +292 -0
- pomata-0.1.0/tests/pnl/test_returns_net.py +289 -0
- pomata-0.1.0/tests/pnl/test_returns_simple.py +271 -0
- pomata-0.1.0/tests/pnl/test_turnover.py +249 -0
- pomata-0.1.0/tests/pnl/test_typing.py +100 -0
- pomata-0.1.0/tests/support/__init__.py +124 -0
- pomata-0.1.0/tests/support/asserts.py +122 -0
- pomata-0.1.0/tests/support/bars.py +47 -0
- pomata-0.1.0/tests/support/benchmarks.py +33 -0
- pomata-0.1.0/tests/support/columns.py +20 -0
- pomata-0.1.0/tests/support/frames.py +107 -0
- pomata-0.1.0/tests/support/strategies.py +462 -0
- pomata-0.1.0/tests/support/synthesis.py +57 -0
- pomata-0.1.0/tests/support/tests/test_asserts.py +73 -0
- pomata-0.1.0/tests/support/tests/test_bars.py +41 -0
- pomata-0.1.0/tests/support/tests/test_frames.py +61 -0
- pomata-0.1.0/tests/support/tests/test_strategies.py +199 -0
- pomata-0.1.0/tests/support/tests/test_tolerances.py +92 -0
- pomata-0.1.0/tests/support/tolerances.py +114 -0
- pomata-0.1.0/tests/test_docs_surface.py +73 -0
- pomata-0.1.0/tests/test_expr.py +120 -0
- pomata-0.1.0/tests/test_package.py +44 -0
- pomata-0.1.0/uv.lock +1197 -0
pomata-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
.ruff_cache/
|
|
10
|
+
.hypothesis/
|
|
11
|
+
.benchmarks/
|
|
12
|
+
.coverage
|
|
13
|
+
htmlcov/
|
|
14
|
+
|
|
15
|
+
# Environments / uv
|
|
16
|
+
.venv/
|
|
17
|
+
|
|
18
|
+
# Rust (added when the native extension lands)
|
|
19
|
+
target/
|
|
20
|
+
|
|
21
|
+
# Docs site build
|
|
22
|
+
site/
|
|
23
|
+
|
|
24
|
+
# OS / IDE
|
|
25
|
+
.DS_Store
|
|
26
|
+
.idea/
|
|
27
|
+
.vscode/
|
|
28
|
+
|
|
29
|
+
# Sphinx build output
|
|
30
|
+
docs/_build/
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
# Correctness
|
|
2
|
+
|
|
3
|
+
pomata's first promise is **verifiable correctness**. The weight is on *verifiable*: not "trust us, it is right", but
|
|
4
|
+
"here is exactly how we know, and here is everything you need to check it yourself." This document is that explanation —
|
|
5
|
+
the method behind every number, and the reason each test is the size it is rather than a figure picked by feel.
|
|
6
|
+
|
|
7
|
+
It is also an honest document. We do not claim the code is free of bugs; no one can claim that. We claim something
|
|
8
|
+
narrower and checkable: that every indicator agrees with an independent transcription of its published formula,
|
|
9
|
+
satisfies a set of stated mathematical invariants, and matches the industry reference where the two are meant to match —
|
|
10
|
+
and that the tests proving this are sized by derivation, not by superstition.
|
|
11
|
+
|
|
12
|
+
## Why a technical indicator is hard to get right
|
|
13
|
+
|
|
14
|
+
`prices.rolling_mean(14)` looks trivial, and the happy path is. The cost — the part that takes months, and that almost
|
|
15
|
+
no in-house implementation pays in full — is everything around it:
|
|
16
|
+
|
|
17
|
+
- **Transcription.** The published formula has to become code with no dropped term, no swapped index, no coefficient off
|
|
18
|
+
by a digit. A wrong constant is still "an indicator"; it is just the wrong one.
|
|
19
|
+
- **Floating-point artifacts.** Real inputs reach the corners of IEEE-754: a squared value that underflows to zero, a
|
|
20
|
+
difference of large numbers that loses all its significant digits, two values so close that rounding reorders them.
|
|
21
|
+
Code that is algebraically right can still disagree with itself there.
|
|
22
|
+
- **Boundaries.** The warmup before the first defined value; a window equal to one, equal to the series length, or
|
|
23
|
+
larger than it; a single row; an empty series. Each is an off-by-one waiting to happen.
|
|
24
|
+
- **Missing data.** A leading `null`, an interior `null`, a `NaN` — does it propagate, latch, or get silently skipped?
|
|
25
|
+
|
|
26
|
+
Each of these is a place a correct-looking implementation is quietly wrong on the inputs you did not think to try. The
|
|
27
|
+
method below exists to make each one impossible to skip.
|
|
28
|
+
|
|
29
|
+
## The method
|
|
30
|
+
|
|
31
|
+
### An independent oracle
|
|
32
|
+
|
|
33
|
+
Every indicator is written twice. The shipped version is tuned to vectorize; the reference is a second, deliberately
|
|
34
|
+
naive derivation of the same published formula that shares no code with it. The two are derived independently from the
|
|
35
|
+
source mathematics, so when they agree to within a stated floating-point tolerance, that agreement is evidence: a single
|
|
36
|
+
bug would have to occur *identically* in two unrelated computations to hide, which is vanishingly unlikely. The oracle
|
|
37
|
+
exists only to be the second witness.
|
|
38
|
+
|
|
39
|
+
This holds literally for the great majority of indicators — anything expressible as composed Polars expressions, where
|
|
40
|
+
the naive loop and the vectorized expression graph are genuinely unrelated. It holds, too, for the exponential averages
|
|
41
|
+
(the unadjusted EMA, Wilder's RMA, and the ATR / RSI / MACD / multi-EMA family built on them): although the shipped code
|
|
42
|
+
runs as a single sequential pass, the linear recurrence has a closed form, so the oracle is computed as an independent
|
|
43
|
+
unrolled weighted sum rather than a transcription of the same loop — a forward-carry error cannot hide in it.
|
|
44
|
+
|
|
45
|
+
A few indicators are irreducibly sequential with no such closed form, so their oracle necessarily resembles the shipped
|
|
46
|
+
pass and confirms internal consistency, not independence — and we say so rather than overstate the guarantee:
|
|
47
|
+
|
|
48
|
+
- **KAMA** — the efficiency ratio and adaptive smoothing constant are derived independently, but the recurrence they
|
|
49
|
+
drive is one-shape with the implementation;
|
|
50
|
+
- **the parabolic SAR** — a path-dependent stop-and-reverse state machine the oracle must replay branch for branch;
|
|
51
|
+
- **the Hilbert-transform cycle cluster** (the dominant-cycle period and phase, the phasor, the trendline, the sine
|
|
52
|
+
wave, the trend flag, and MAMA) — the FIR and quadrature stages are independent, but the adaptive dominant-cycle
|
|
53
|
+
period feeds back into its own measurement and the stages built on it.
|
|
54
|
+
|
|
55
|
+
Their second witness is elsewhere: the parabolic SAR is anchored to golden masters hand-computed from Wilder's published
|
|
56
|
+
rules — including the seeding and reversal branches the recurrence cannot reach by symmetry — while KAMA and the cycle
|
|
57
|
+
cluster are pinned to frozen golden masters that catch any drift and are checked against TA-Lib in the non-gating
|
|
58
|
+
differential tier.
|
|
59
|
+
|
|
60
|
+
### A laddered test suite
|
|
61
|
+
|
|
62
|
+
Four tiers, each aimed at a different threat:
|
|
63
|
+
|
|
64
|
+
- **Contract** — shape, dtype, length, laziness, and that the indicator partitions independently under `.over(...)`. The
|
|
65
|
+
structural promises the type system cannot state.
|
|
66
|
+
- **Edge** — the dangerous regimes, pinned by hand and checked **deterministically**: the exact warmup count; an empty
|
|
67
|
+
series; an all-`null` series; a single row; a window longer than the series; an interior `null`; a `NaN`. Each of
|
|
68
|
+
these is its own named test with a known answer on every indicator. The regimes that bite only some indicators — a
|
|
69
|
+
constant or monotone series, a window of one or of the series length, a leading `null` — are pinned where they bite.
|
|
70
|
+
These are not left to chance — and that fact is what lets the random tier below stay small.
|
|
71
|
+
- **Correctness** — agreement with the oracle on a fixed realistic series, plus a frozen golden master so a value can
|
|
72
|
+
never drift unnoticed between versions.
|
|
73
|
+
- **Properties** — the oracle-agreement again, and the mathematical invariants (scale behavior, boundedness, null
|
|
74
|
+
handling), now over inputs drawn at random.
|
|
75
|
+
|
|
76
|
+
### A differential against the industry reference
|
|
77
|
+
|
|
78
|
+
Separately, and without gating the build, each indicator is compared to TA-Lib — the reference the industry has used
|
|
79
|
+
since 2007. With the canonical seeding most indicators match TA-Lib **bar for bar, from the first defined value**, so
|
|
80
|
+
the comparison runs over the whole series at the same `1e-10` band as the internal oracle. A documented minority is
|
|
81
|
+
checked only on the converged tail — each a case where TA-Lib itself deviates from the indicator's author over the
|
|
82
|
+
warm-up (Wilder's first true range, the independent MACD / Chaikin EMAs) or carries a long, implementation-specific
|
|
83
|
+
lead-in (the Hilbert cycle pipeline, the Parabolic SAR cold start). Where pomata chooses a different default, the
|
|
84
|
+
divergence is documented and justified against the charting authorities, not hidden.
|
|
85
|
+
|
|
86
|
+
## How much precision we guarantee, and where
|
|
87
|
+
|
|
88
|
+
A correctness claim needs a number. pomata's is **ten significant figures**: every indicator reproduces its independent
|
|
89
|
+
oracle to a relative `1e-10` on any finite input within a sane dynamic range. That single promise is the headline; the
|
|
90
|
+
test ladder above is how it is checked, and `tests/support/tolerances.py` is its machine-readable home. In practice the
|
|
91
|
+
agreement is far tighter than the promise: about half of the indicator outputs reproduce the oracle to the last bit (a
|
|
92
|
+
relative difference of exactly zero), and the rest land at the `float64` noise floor — typically thirteen to fifteen
|
|
93
|
+
figures, never fewer than the guaranteed ten.
|
|
94
|
+
|
|
95
|
+
Why `1e-10`, and why it is "safe no matter what" for this library:
|
|
96
|
+
|
|
97
|
+
- **It dwarfs the data.** Market feeds carry four to eight significant figures (a price like `123.45`, a volume); ten
|
|
98
|
+
figures means the indicator never adds error you could observe — it is lossless against its own input, with two to
|
|
99
|
+
six orders of headroom to spare.
|
|
100
|
+
- **It sits far above the float-64 noise floor.** A `float64` holds about sixteen significant figures, so legitimate
|
|
101
|
+
rounding between the streaming implementation and the two-pass oracle lands around `1e-15`. `1e-10` is five orders
|
|
102
|
+
above that: tight enough to reject any real coding error, loose enough that a last-bit difference never flakes.
|
|
103
|
+
- **It is verified, not asserted.** The property tier holds every indicator to `1e-10` over the full random fuzz domain,
|
|
104
|
+
and that bound is the enforced guarantee. The realized *headroom* under it is recomputable from a clean clone with
|
|
105
|
+
`scripts/calibrate_tolerances.py`, which fuzzes a representative well-conditioned set across multiple seeds and reports
|
|
106
|
+
the worst relative residual — it lands around `1e-14` (a handful of `float64` noise-floor ULPs), about four orders
|
|
107
|
+
inside the guarantee. The bound was sized to that measured headroom, not picked by feel.
|
|
108
|
+
|
|
109
|
+
### Where it stops: the float-conditioning limit
|
|
110
|
+
|
|
111
|
+
The one place `1e-10` cannot hold is also the one place no real market reaches. A rolling-sum statistic loses precision
|
|
112
|
+
only when an entire window collapses to the float-precision floor of a much larger value that recently passed through it
|
|
113
|
+
— summing a term of magnitude `1e6` beside residue thirteen orders smaller, an effective dynamic range past about
|
|
114
|
+
thirteen orders. This is `float64` obeying IEEE-754, not a defect in the library or tests: the small term is absorbed
|
|
115
|
+
into the mantissa of the large one, and no amount of careful coding recovers digits the format never stored. Random
|
|
116
|
+
inputs do not build that pattern — the property tier verifies `1e-10` across the full `[-1e6, 1e6]` fuzz domain under
|
|
117
|
+
stress — so it
|
|
118
|
+
takes a deliberately adversarial series (a price dropping seven orders of magnitude bar-to-bar) to construct it. pomata
|
|
119
|
+
documents this limit in the affected indicators rather than papering over it, and clamps the oscillators whose bound is
|
|
120
|
+
unconditional — the Chande momentum oscillator, the money flow index, Chaikin money flow — to that bound, so an
|
|
121
|
+
out-of-domain input degrades in precision rather than escaping the range.
|
|
122
|
+
|
|
123
|
+
A related caveat applies wherever an output cancels toward zero, not only to one family: the squaring statistics
|
|
124
|
+
(variance, standard deviation, Bollinger bands) as their near-constant-window output approaches zero, and equally any
|
|
125
|
+
mean or sum (an SMA over a window straddling large values of either sign, the price transforms) at a near-zero result.
|
|
126
|
+
A relative error is amplified as the denominator vanishes, so the agreement there is held to an absolute floor sized to
|
|
127
|
+
the input magnitude rather than the bare relative `1e-10`. The relative bound is the guarantee everywhere the output is
|
|
128
|
+
meaningfully non-zero — which is everywhere real market data lands.
|
|
129
|
+
|
|
130
|
+
## How big a test has to be — and why exactly that big
|
|
131
|
+
|
|
132
|
+
This is the part most suites leave to feel. We derive it. There are two separate questions with two separate answers:
|
|
133
|
+
how *long* the input series must be, and how *many* random inputs to draw.
|
|
134
|
+
|
|
135
|
+
### Input length: read off the indicator
|
|
136
|
+
|
|
137
|
+
An indicator produces no defined value until its **warmup** of `W` rows, and reaches steady behavior only after its
|
|
138
|
+
**memory** `M`. A meaningful test series is therefore
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
S = W + M + margin
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
and none of the three terms is guessed:
|
|
145
|
+
|
|
146
|
+
- `W`, the warmup, is an exact property of the indicator — the number of leading rows it returns as `null`. For an
|
|
147
|
+
`n`-period RSI, `W = n`; for the dominant-cycle period, `W = 32`; for the phase-derived cycle indicators, `W = 63`.
|
|
148
|
+
- `M`, the memory, is the window width for a windowed indicator (`M = w`: the output is fully formed once the window is
|
|
149
|
+
full). For a recursion that retains a fraction `r = 1 - alpha` of its state each step, the seed's influence decays as
|
|
150
|
+
`r^t`, so it falls below a chosen `epsilon` after
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
M = ceil( ln(1/epsilon) / ln(1/r) )
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
steps. This term only bites when a test must outlast a transient — comparing two differently-seeded implementations,
|
|
157
|
+
for instance; an oracle that shares pomata's seeding agrees from the first defined row and needs only `M = w`.
|
|
158
|
+
- `margin` is a handful of rows: enough to leave several defined values for an invariant to act on, and enough spread in
|
|
159
|
+
length to exercise the warmup count at more than one total size.
|
|
160
|
+
|
|
161
|
+
So a short series suffices for RSI (`W = n`, same-seeded oracle, `S = n + a few`), while a cycle indicator compared on
|
|
162
|
+
its converged tail needs the settling term as well. The number is computed, not chosen.
|
|
163
|
+
|
|
164
|
+
### Number of random examples: derived from what the draw is *for*
|
|
165
|
+
|
|
166
|
+
Here is the figure everyone picks by feel — "let's run 500 to be safe" — and here is why that instinct is a trap.
|
|
167
|
+
|
|
168
|
+
A property test that draws `N` random inputs is **sampling, not proving**. If a bad input occupies a fraction `p` of the
|
|
169
|
+
input space, the chance of drawing it at least once in `N` tries is
|
|
170
|
+
|
|
171
|
+
```
|
|
172
|
+
1 - (1 - p)^N
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
and to hit it with confidence `1 - delta` you need
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
N >= ln(1/delta) / p (for small p)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The floating-point artifacts that bite real indicators are *rare* — a `p` on the order of `1e-4`. Catching one reliably
|
|
182
|
+
(95% of runs) would take `N` in the tens of thousands. A "safety" run of a few hundred catches it about five percent of
|
|
183
|
+
the time — that is, by luck of the seed, not by design. A fixed mid-sized `N` is therefore the worst of both worlds: far
|
|
184
|
+
too small to be a reliable net for rare artifacts, far larger than needed for anything else. 500 is not safer than 470
|
|
185
|
+
or 240; all three are arbitrary.
|
|
186
|
+
|
|
187
|
+
The way out is not a bigger `N`. It is to drive `p` toward zero — leave as little bad input to draw as possible — and
|
|
188
|
+
then size `N` for what is actually left:
|
|
189
|
+
|
|
190
|
+
- **The artifacts are driven out by construction, not hunted.** Inputs are drawn from the indicator's valid domain
|
|
191
|
+
(coherent OHLC bars, never impossible ones); scale tests rescale by a lossless power of two, so an ordinary rounding
|
|
192
|
+
cannot flip a comparison; and the implementation guards the true singularities (a flat window, a zero denominator). A
|
|
193
|
+
residual can remain — a few indicators carry an intrinsic phase-branch discontinuity whose sub-ULP cancellation a
|
|
194
|
+
rescale can still flip — but it is driven so rare (order `1e-4` per draw) that `N` is sized deliberately *below* that
|
|
195
|
+
flake floor, where a larger `N` would be likelier, not less likely, to trip it.
|
|
196
|
+
- **The dangerous regimes are covered deterministically** by the Edge tier — the empty and single-row series, the
|
|
197
|
+
all-`null` series and over-long windows, the nulls and the `NaN` each have their own pinned test with a known answer
|
|
198
|
+
on every indicator, and the regimes that bite only some (a constant or monotone series, the window boundaries) are
|
|
199
|
+
pinned where they bite. The random draw is not what protects them.
|
|
200
|
+
|
|
201
|
+
What is left for `N` to do is narrow: cover the general interior of the input space, and catch a *systematic* mistake. A
|
|
202
|
+
systematic mistake — a wrong coefficient, a shifted index — is wrong on almost every input, so the chance it survives
|
|
203
|
+
even a handful of independent draws is negligible. Covering a few qualitative regimes of the interior to high confidence
|
|
204
|
+
is the same coupon-collector arithmetic, and lands in the same range: `N` in the low tens to about a hundred. Once the
|
|
205
|
+
property is proven and the edges are pinned, a hundred draws is already generous; a larger number buys wall-clock, not
|
|
206
|
+
confidence. The figure itself is a single shared number — set once in the test configuration, identical in a local run
|
|
207
|
+
and in CI — that an individual family raises only when its parameter space is genuinely larger.
|
|
208
|
+
|
|
209
|
+
### Tolerances: how close is close enough, by conditioning
|
|
210
|
+
|
|
211
|
+
When the implementation and the oracle agree they still round differently, and how far they drift depends on the
|
|
212
|
+
statistic's *conditioning*, not on a round number. Each tolerance is a named constant whose value is the worst-case
|
|
213
|
+
implementation-vs-oracle residual the statistic's conditioning predicts on degenerate inputs, plus a margin:
|
|
214
|
+
|
|
215
|
+
- **degree-2, two-pass** (variance): the two forms differ by about half a ULP at worst — a tight band.
|
|
216
|
+
- **degree-1, square-root-amplified** (standard deviation, the EWMA / MACD signal): the square root blows the relative
|
|
217
|
+
error up as the variance approaches zero, worst residual about `1e-8` — a looser band (std is looser than variance,
|
|
218
|
+
precisely for this reason).
|
|
219
|
+
- **degree-1, well-conditioned** (the recursive, windowed, and stateless means): the residual is at most a few ULP — a
|
|
220
|
+
tight band.
|
|
221
|
+
- **scale-invariant** (a bounded ratio, a cycle period, a `0/1` flag): the output is `O(1)` at any input magnitude, so
|
|
222
|
+
the band is *absolute* — sizing an `O(1)` value to the input magnitude is meaningless.
|
|
223
|
+
|
|
224
|
+
A magnitude-dependent band is sized to the data (`input_scale ** degree * factor`), not fixed, so it is right at every
|
|
225
|
+
scale. Where one indicator legitimately departs — a difference of large terms that cancels, a band that would underflow
|
|
226
|
+
at a subnormal input — the departure carries a one-line comment saying why. A bare, unexplained tolerance is treated as
|
|
227
|
+
a defect, not a detail.
|
|
228
|
+
|
|
229
|
+
### Benchmark size: derived from measurement stability
|
|
230
|
+
|
|
231
|
+
Timing has its own sizing. A throughput measurement is meaningful only where the per-row cost dominates the fixed
|
|
232
|
+
overhead of dispatching the expression — `c * S` well above a roughly constant `overhead`, i.e. `S >> overhead / c`.
|
|
233
|
+
Because the slow recursions have a large `c`, they reach a stable read in *fewer* rows, not more; a million rows on a
|
|
234
|
+
kernel that already takes over a second per evaluation buys no extra precision, only minutes. The complexity guard needs
|
|
235
|
+
only a single decade: a 10x increase in rows multiplies a linear cost by 10 and a quadratic one by 100, so one 10x step
|
|
236
|
+
separates them with a wide margin and an additive floor absorbs the overhead-bound cheap cases.
|
|
237
|
+
|
|
238
|
+
## By family
|
|
239
|
+
|
|
240
|
+
The method, the ladder, the sizing, and the tolerance rules above are family-agnostic — they apply unchanged to
|
|
241
|
+
indicators, PnL, and metrics. What differs per family is only the *characteristic invariants*; the exact per-primitive
|
|
242
|
+
figures (warmup, parameter regimes, the tolerance factor) live in the test files, declared in a uniform "Test sizing"
|
|
243
|
+
header, and are not duplicated here.
|
|
244
|
+
|
|
245
|
+
<details>
|
|
246
|
+
<summary><b>Indicators</b> — the technical-analysis layer (the used set of TA-Lib)</summary>
|
|
247
|
+
|
|
248
|
+
The characteristic invariants are scale behavior (homogeneity of degree 1 for a price-level output; invariance for a
|
|
249
|
+
bounded ratio, a cycle period, or a flag), boundedness where it applies (RSI in `[0, 100]`, Williams %R in `[-100, 0]`),
|
|
250
|
+
the exact warmup, and `null` / `NaN` propagation — each proven against the independent oracle and, in the non-gating
|
|
251
|
+
differential tier, against TA-Lib. Adding an indicator is a copy job: the file's "Test sizing" header states three facts
|
|
252
|
+
(warmup, parameter regimes, valid domain) and the rest of the property tier follows the same shape as every sibling.
|
|
253
|
+
|
|
254
|
+
</details>
|
|
255
|
+
|
|
256
|
+
<details>
|
|
257
|
+
<summary><b>PnL</b> — accounting and transaction costs</summary>
|
|
258
|
+
|
|
259
|
+
Shipped. The same machine applies — an independent oracle, the four-tier ladder, golden masters, and the missing-data /
|
|
260
|
+
large-magnitude robustness tiers. The characteristic invariants are cost monotonicity (more cost, less PnL), the
|
|
261
|
+
additive-vs-compounded cumulation split (`cumulative_pnl` sums currency P&L, `equity_curve` compounds returns),
|
|
262
|
+
no look-ahead (every bar uses only past data), and a defined, documented behavior for every degenerate input
|
|
263
|
+
(`null` / `NaN` / `0` / `±inf` / warm-up).
|
|
264
|
+
|
|
265
|
+
</details>
|
|
266
|
+
|
|
267
|
+
<details>
|
|
268
|
+
<summary><b>Metrics</b> — performance & risk statistics</summary>
|
|
269
|
+
|
|
270
|
+
Shipped. The same machine applies unchanged — an independent oracle, the four-tier ladder, derived sizing, named
|
|
271
|
+
tolerances. The characteristic invariants are the annualization identities, scale-equivariance (a Sharpe ratio is
|
|
272
|
+
invariant to leverage), closed-form checks (the Sharpe of constant returns, the drawdown of a monotone series), and a
|
|
273
|
+
defined, documented behavior for every degenerate input (`null` skipped, a non-null `NaN` poisoning the result, and a
|
|
274
|
+
degenerate denominator reported as `±inf` / `NaN`, never clipped).
|
|
275
|
+
|
|
276
|
+
</details>
|
|
277
|
+
|
|
278
|
+
## What we claim, precisely
|
|
279
|
+
|
|
280
|
+
We prove, and you can re-run:
|
|
281
|
+
|
|
282
|
+
- the output's shape, dtype, length, and laziness;
|
|
283
|
+
- the exact warmup, and that a `null` or `NaN` propagates as specified;
|
|
284
|
+
- agreement with an independent oracle to a stated floating-point tolerance, across the valid input domain;
|
|
285
|
+
- the documented invariants — scale behavior, bounds, monotonicity where it applies;
|
|
286
|
+
- parity with TA-Lib from the first defined value (a documented minority only on the converged tail), every deliberate
|
|
287
|
+
divergence documented.
|
|
288
|
+
|
|
289
|
+
We do **not** claim the absence of all bugs, or correctness on inputs outside the documented domain. One limit is worth
|
|
290
|
+
naming plainly: for the irreducibly-sequential indicators (KAMA, the parabolic SAR, the Hilbert cycle cluster) the oracle
|
|
291
|
+
shares its structure with the implementation by construction, and most of their golden masters are the implementation's
|
|
292
|
+
own frozen output — so in principle a transcription error in a seed or warm-up *value* (the counts
|
|
293
|
+
are pinned independently) could slip past those two tiers. Two things close that gap: the differential against TA-Lib —
|
|
294
|
+
an independent C implementation — now agrees **bar for bar from the first defined value** for most indicators (the
|
|
295
|
+
canonical seeding makes the warm-up match too, not just the tail), and a handful of golden masters are hand-computed
|
|
296
|
+
from the published definition. "Verifiable" is a promise about evidence, not omniscience: everything above is a test you
|
|
297
|
+
can read and re-run, and a number you can recompute for yourself.
|
pomata-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thomas Cercato
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
pomata-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pomata
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Verifiably-correct, Polars-native quant toolkit: technical indicators, performance & risk metrics, and PnL accounting.
|
|
5
|
+
Project-URL: Changelog, https://github.com/ilpomo/pomata/releases
|
|
6
|
+
Project-URL: Documentation, https://ilpomo.github.io/pomata
|
|
7
|
+
Project-URL: Homepage, https://github.com/ilpomo/pomata
|
|
8
|
+
Project-URL: Issues, https://github.com/ilpomo/pomata/issues
|
|
9
|
+
Project-URL: Repository, https://github.com/ilpomo/pomata
|
|
10
|
+
Author: Thomas Cercato
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: finance,indicators,metrics,pnl,polars,quant,technical-analysis
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.12
|
|
26
|
+
Requires-Dist: polars>=1.42.0
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# pomata
|
|
30
|
+
|
|
31
|
+
**A Polars-native quant toolkit — technical indicators, PnL accounting, and performance & risk metrics.** Each is a
|
|
32
|
+
composable `pl.Expr`, so an entire study is one lazy Polars pipeline, from price to performance.
|
|
33
|
+
|
|
34
|
+
And it doesn't ask you to trust its numbers — it **proves** them: every function is verified to the `float64` floor
|
|
35
|
+
against an independent reference, under 100% branch coverage.
|
|
36
|
+
|
|
37
|
+
[](https://github.com/ilpomo/pomata/actions/workflows/ci.yml)
|
|
38
|
+
[](https://codecov.io/gh/ilpomo/pomata)
|
|
39
|
+
[](https://github.com/astral-sh/ruff)
|
|
40
|
+
[](https://github.com/astral-sh/ty)
|
|
41
|
+
[](https://www.mypy-lang.org)
|
|
42
|
+
[](https://github.com/microsoft/pyright)
|
|
43
|
+
[](https://pyrefly.org)
|
|
44
|
+
|
|
45
|
+

|
|
46
|
+

|
|
47
|
+

|
|
48
|
+
[](https://www.python.org/downloads/)
|
|
49
|
+
[](https://pola.rs)
|
|
50
|
+
[](LICENSE)
|
|
51
|
+
|
|
52
|
+
> **Alpha.** The API is not frozen until `1.0`; expect refinement. The correctness bar holds at every commit regardless.
|
|
53
|
+
|
|
54
|
+
## From price to performance, in one query
|
|
55
|
+
|
|
56
|
+
Signal, PnL, and metrics are all plain `pl.Expr`, so an entire study is a single Polars pipeline — no glue code, no
|
|
57
|
+
DataFrame ping-pong, no second dependency between the steps:
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import polars as pl
|
|
61
|
+
from pomata.indicators import rsi
|
|
62
|
+
from pomata.pnl import returns_simple, returns_gross, returns_net, cost_proportional, equity_curve
|
|
63
|
+
from pomata.metrics import sharpe_ratio, max_drawdown
|
|
64
|
+
|
|
65
|
+
report = (
|
|
66
|
+
frame # a DataFrame (or LazyFrame) with a "close" column
|
|
67
|
+
.with_columns(
|
|
68
|
+
weight=(rsi(pl.col("close"), 14) < 30).cast(pl.Float64).shift(1), # go long when oversold, act next bar
|
|
69
|
+
asset_returns=returns_simple(pl.col("close")),
|
|
70
|
+
)
|
|
71
|
+
.with_columns(
|
|
72
|
+
net=returns_net(
|
|
73
|
+
returns_gross(pl.col("weight"), pl.col("asset_returns")),
|
|
74
|
+
cost_proportional(pl.col("weight"), rate=0.001),
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
.select(
|
|
78
|
+
sharpe=sharpe_ratio(pl.col("net"), periods_per_year=252),
|
|
79
|
+
max_drawdown=max_drawdown(equity_curve(pl.col("net"))),
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The indicator feeds the signal, the signal feeds the PnL, the PnL feeds the metrics — every arrow is a `pl.Expr`, so it
|
|
85
|
+
all fuses into one Polars query (eager or lazy, single series or a multi-asset panel via `.over`). The `.shift(1)` is
|
|
86
|
+
the whole no-look-ahead story: a signal computed at the close acts on the next bar, by construction.
|
|
87
|
+
|
|
88
|
+
## Install
|
|
89
|
+
|
|
90
|
+
The only runtime dependency is **Polars**; Python **3.12+**.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# from source (today)
|
|
94
|
+
git clone https://github.com/ilpomo/pomata
|
|
95
|
+
cd pomata && uv sync
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Once published to PyPI, the install will be `pip install pomata` (or `uv add pomata`).
|
|
99
|
+
|
|
100
|
+
Every function is a free-standing `pl.Expr` factory — name it, compose it, run it in any Polars context. Warm-up rows
|
|
101
|
+
are `null` until the window fills, never a fabricated value:
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
import polars as pl
|
|
105
|
+
from pomata.indicators import rsi
|
|
106
|
+
|
|
107
|
+
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,
|
|
108
|
+
45.61, 46.28, 46.28, 46.00, 46.03, 46.41, 46.22, 45.64, 46.21, 46.25, 45.71, 46.45]})
|
|
109
|
+
frame.select(rsi(pl.col("close"), 14).round(2).alias("rsi"))["rsi"].to_list()
|
|
110
|
+
# [None, None, ..., 57.92, 62.88, 63.21, 56.01, 62.34]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## What's inside
|
|
114
|
+
|
|
115
|
+
Three families, one package. They share a grammar (pure `pl.Expr` factories, one canonical name per concept) and a
|
|
116
|
+
handoff: `pnl` emits exactly the return and equity series `metrics` consumes.
|
|
117
|
+
|
|
118
|
+
### indicators — 75 functions
|
|
119
|
+
|
|
120
|
+
The technical-analysis layer, each indicator a `pl.Expr` checked against TA-Lib to the `float64` floor — most bar-for-bar
|
|
121
|
+
from the first emitted value, a documented minority only on the converged tail (the differential tier is non-gating).
|
|
122
|
+
Multi-output indicators (`bollinger_bands`, `macd`, `stochastic_slow`, …) return a single `pl.Struct` —
|
|
123
|
+
pick a line with `.struct.field(...)` or expand with `.struct.unnest()`.
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from pomata.indicators import bollinger_bands
|
|
127
|
+
frame.select(bollinger_bands(pl.col("close"), 20).alias("bb")).unnest("bb")
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
<details><summary>All 75 indicators, by category</summary>
|
|
131
|
+
|
|
132
|
+
- **channel** (5) — `donchian_channels`, `ichimoku`, `keltner_channels`, `midpoint`, `midprice`
|
|
133
|
+
- **cycle** (7) — `dominant_cycle_period`, `dominant_cycle_phase`, `hilbert_phasor`, `hilbert_trendline`, `mama`, `sine_wave`, `trend_mode`
|
|
134
|
+
- **directional movement** (8) — `adx`, `adxr`, `di_minus`, `di_plus`, `dm_minus`, `dm_plus`, `dx`, `vortex`
|
|
135
|
+
- **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`
|
|
136
|
+
- **moving average** (11) — `dema`, `ema`, `hma`, `kama`, `rma`, `sma`, `t3`, `tema`, `trima`, `vwma`, `wma`
|
|
137
|
+
- **price transform** (4) — `price_average`, `price_median`, `price_typical`, `price_weighted_close`
|
|
138
|
+
- **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`
|
|
139
|
+
- **stochastic** (2) — `stochastic_fast`, `stochastic_slow`
|
|
140
|
+
- **trend** (2) — `parabolic_sar`, `supertrend`
|
|
141
|
+
- **volatility** (4) — `atr`, `atr_normalized`, `bollinger_bands`, `true_range`
|
|
142
|
+
- **volume** (6) — `accumulation_distribution`, `accumulation_distribution_oscillator`, `chaikin_money_flow`, `money_flow_index`, `obv`, `vwap`
|
|
143
|
+
|
|
144
|
+
</details>
|
|
145
|
+
|
|
146
|
+
### pnl — 18 functions
|
|
147
|
+
|
|
148
|
+
Profit-and-loss accounting in two flows: **returns** (a signed `weight` of capital and asset returns) and **cash**
|
|
149
|
+
(a `quantity` of units and a price), with composable transaction-cost models, dividends, and inverse contracts. Every
|
|
150
|
+
degenerate input (`null` / `NaN` / `0` / `±inf` / warm-up) has a defined, documented, tested behavior.
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
from pomata.pnl import returns_net, returns_gross, cost_proportional, equity_curve
|
|
154
|
+
gross = returns_gross(pl.col("weight"), pl.col("asset_returns"))
|
|
155
|
+
frame.select(equity_curve(returns_net(gross, cost_proportional(pl.col("weight"), rate=0.001))))
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
<details><summary>All 18 PnL functions</summary>
|
|
159
|
+
|
|
160
|
+
- **cash flow** — `cost_borrow`, `cost_fixed`, `cost_funding`, `cost_notional`, `cost_per_share`, `cumulative_pnl`, `dividend`, `pnl_gross`, `pnl_gross_inverse`, `pnl_net`
|
|
161
|
+
- **returns flow** — `cost_proportional`, `cost_slippage`, `equity_curve`, `returns_gross`, `returns_log`, `returns_net`, `returns_simple`, `turnover`
|
|
162
|
+
|
|
163
|
+
</details>
|
|
164
|
+
|
|
165
|
+
### metrics — 60 functions
|
|
166
|
+
|
|
167
|
+
Performance & risk statistics as reducing `pl.Expr`: point one at a return series (e.g. `pomata.pnl.returns_net`) or an
|
|
168
|
+
equity curve (e.g. `pomata.pnl.equity_curve`). Sharpe, Sortino, Calmar, drawdown, VaR/CVaR, capture, benchmark-relative
|
|
169
|
+
(alpha/beta/Treynor/information ratio), and a rolling twin for every windowed form.
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from pomata.metrics import sharpe_ratio, max_drawdown
|
|
173
|
+
frame.select(sharpe_ratio(pl.col("returns"), periods_per_year=252))
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
<details><summary>All 60 metrics</summary>
|
|
177
|
+
|
|
178
|
+
- **drawdown** — `conditional_drawdown_at_risk`, `drawdown`, `drawdown_rolling`, `max_drawdown`, `max_drawdown_duration`, `pain_index`, `ulcer_index`
|
|
179
|
+
- **performance** — `cagr`, `cagr_rolling`, `stability`, `total_return`, `total_return_rolling`
|
|
180
|
+
- **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`
|
|
181
|
+
- **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`
|
|
182
|
+
- **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`
|
|
183
|
+
|
|
184
|
+
</details>
|
|
185
|
+
|
|
186
|
+
## Correctness
|
|
187
|
+
|
|
188
|
+
**Verified, not asserted.** Every function is checked against an *independent* reference — a second code path that shares
|
|
189
|
+
nothing with the implementation — plus frozen golden-master values and property-based invariants, under **100% branch
|
|
190
|
+
coverage**. A function ships only when that suite is green.
|
|
191
|
+
|
|
192
|
+
For indicators there is also a public reference to meet: TA-Lib. Here is one figure to every digit a `float64` holds —
|
|
193
|
+
`rsi(14)`, the last value of a deterministic 400-bar series:
|
|
194
|
+
|
|
195
|
+
```text
|
|
196
|
+
pomata 85.20908701341023
|
|
197
|
+
reference 85.20908701341023 ← independent reimplementation: identical, to the last bit
|
|
198
|
+
TA-Lib 85.20908701341024 ← fifteen figures identical; differs only at the float64 floor
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The same five indicators on the same series — most reproduce the reference *exactly*, the rest land at the noise floor:
|
|
202
|
+
|
|
203
|
+
| indicator | pomata | vs reimplementation | vs TA-Lib |
|
|
204
|
+
| --- | --- | :-: | :-: |
|
|
205
|
+
| `sma(20)` | `105.15146076264764` | exact | `1e-13` |
|
|
206
|
+
| `ema(20)` | `107.7299930892346` | `1e-13` | `1e-14` |
|
|
207
|
+
| `rsi(14)` | `85.20908701341023` | exact | `1e-14` |
|
|
208
|
+
| `atr(14)` | `1.904174462198776` | `9e-16` | `4e-15` |
|
|
209
|
+
| `macd(12,26,9)` | `2.523444380829531` | `1e-13` | `1e-14` |
|
|
210
|
+
|
|
211
|
+
The `pomata` and reference columns are pinned in the test suite; regenerate the full table — including the TA-Lib column
|
|
212
|
+
(which needs the optional `differential` dependency) — from a fresh clone with
|
|
213
|
+
`uv run --group differential python scripts/precision_table.py`.
|
|
214
|
+
|
|
215
|
+
`pnl` and `metrics` are proven on a different axis — every degenerate input has a defined behavior, matched against an
|
|
216
|
+
independent reference oracle — because their math is simple and their correctness lives at the edges, not in the digits.
|
|
217
|
+
The full method (the precision guarantee, the test-sizing derivations, exactly what is and is not claimed) is in
|
|
218
|
+
**[CORRECTNESS.md](CORRECTNESS.md)**.
|
|
219
|
+
|
|
220
|
+
## Where pomata fits
|
|
221
|
+
|
|
222
|
+
pomata is for the quant already working in Polars. Each function is a free-standing `pl.Expr` with `polars` as the only
|
|
223
|
+
runtime dependency, composable across eager, lazy, single-series, and grouped (`.over`) contexts — so the everyday
|
|
224
|
+
primitives live in one coherent toolkit instead of a wired-together stack.
|
|
225
|
+
|
|
226
|
+
It is vectorized analytics and accounting: indicators, total mark-to-market PnL, and metrics. It is **not** an execution
|
|
227
|
+
engine — no order fills, no event loop, no lot accounting.
|
|
228
|
+
|
|
229
|
+
## Project
|
|
230
|
+
|
|
231
|
+
- **Requirements** — Python ≥ 3.12, Polars ≥ 1.40.
|
|
232
|
+
- **Contributing** — see [CONTRIBUTING.md](CONTRIBUTING.md); the full gate (lint, three gating type checkers plus an
|
|
233
|
+
advisory fourth, doctests, 100% branch coverage) runs on every commit.
|
|
234
|
+
- **License** — MIT, see [LICENSE](LICENSE).
|
|
235
|
+
- **Citation** — [CITATION.cff](CITATION.cff).
|