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.
Files changed (369) hide show
  1. pomata-0.1.0/.gitignore +30 -0
  2. pomata-0.1.0/CORRECTNESS.md +297 -0
  3. pomata-0.1.0/LICENSE +21 -0
  4. pomata-0.1.0/PKG-INFO +235 -0
  5. pomata-0.1.0/README.md +207 -0
  6. pomata-0.1.0/pyproject.toml +266 -0
  7. pomata-0.1.0/src/pomata/__init__.py +21 -0
  8. pomata-0.1.0/src/pomata/_expr.py +230 -0
  9. pomata-0.1.0/src/pomata/indicators/__init__.py +181 -0
  10. pomata-0.1.0/src/pomata/indicators/channel.py +563 -0
  11. pomata-0.1.0/src/pomata/indicators/cycle.py +754 -0
  12. pomata-0.1.0/src/pomata/indicators/directional_movement.py +971 -0
  13. pomata-0.1.0/src/pomata/indicators/momentum.py +1994 -0
  14. pomata-0.1.0/src/pomata/indicators/moving_average.py +1245 -0
  15. pomata-0.1.0/src/pomata/indicators/price_transform.py +437 -0
  16. pomata-0.1.0/src/pomata/indicators/statistic.py +787 -0
  17. pomata-0.1.0/src/pomata/indicators/stochastic.py +300 -0
  18. pomata-0.1.0/src/pomata/indicators/trend.py +408 -0
  19. pomata-0.1.0/src/pomata/indicators/volatility.py +503 -0
  20. pomata-0.1.0/src/pomata/indicators/volume.py +864 -0
  21. pomata-0.1.0/src/pomata/metrics/__init__.py +149 -0
  22. pomata-0.1.0/src/pomata/metrics/drawdown.py +570 -0
  23. pomata-0.1.0/src/pomata/metrics/performance.py +451 -0
  24. pomata-0.1.0/src/pomata/metrics/ratio.py +1522 -0
  25. pomata-0.1.0/src/pomata/metrics/relative.py +1408 -0
  26. pomata-0.1.0/src/pomata/metrics/risk.py +2006 -0
  27. pomata-0.1.0/src/pomata/pnl/__init__.py +68 -0
  28. pomata-0.1.0/src/pomata/pnl/accounting.py +916 -0
  29. pomata-0.1.0/src/pomata/pnl/costs.py +674 -0
  30. pomata-0.1.0/src/pomata/pnl/returns.py +183 -0
  31. pomata-0.1.0/src/pomata/py.typed +0 -0
  32. pomata-0.1.0/tests/README.md +32 -0
  33. pomata-0.1.0/tests/conftest.py +33 -0
  34. pomata-0.1.0/tests/indicators/oracles/__init__.py +169 -0
  35. pomata-0.1.0/tests/indicators/oracles/_helpers.py +94 -0
  36. pomata-0.1.0/tests/indicators/oracles/_regenerate.py +42 -0
  37. pomata-0.1.0/tests/indicators/oracles/absolute_price_oscillator.py +45 -0
  38. pomata-0.1.0/tests/indicators/oracles/accumulation_distribution.py +89 -0
  39. pomata-0.1.0/tests/indicators/oracles/accumulation_distribution_oscillator.py +56 -0
  40. pomata-0.1.0/tests/indicators/oracles/adx.py +37 -0
  41. pomata-0.1.0/tests/indicators/oracles/adxr.py +48 -0
  42. pomata-0.1.0/tests/indicators/oracles/aroon.py +71 -0
  43. pomata-0.1.0/tests/indicators/oracles/aroon_oscillator.py +42 -0
  44. pomata-0.1.0/tests/indicators/oracles/atr.py +58 -0
  45. pomata-0.1.0/tests/indicators/oracles/atr_normalized.py +45 -0
  46. pomata-0.1.0/tests/indicators/oracles/awesome_oscillator.py +57 -0
  47. pomata-0.1.0/tests/indicators/oracles/balance_of_power.py +54 -0
  48. pomata-0.1.0/tests/indicators/oracles/bollinger_bands.py +58 -0
  49. pomata-0.1.0/tests/indicators/oracles/cci.py +109 -0
  50. pomata-0.1.0/tests/indicators/oracles/chaikin_money_flow.py +96 -0
  51. pomata-0.1.0/tests/indicators/oracles/chande_momentum_oscillator.py +68 -0
  52. pomata-0.1.0/tests/indicators/oracles/cycle.py +194 -0
  53. pomata-0.1.0/tests/indicators/oracles/dema.py +65 -0
  54. pomata-0.1.0/tests/indicators/oracles/di_minus.py +50 -0
  55. pomata-0.1.0/tests/indicators/oracles/di_plus.py +50 -0
  56. pomata-0.1.0/tests/indicators/oracles/dm_minus.py +44 -0
  57. pomata-0.1.0/tests/indicators/oracles/dm_plus.py +44 -0
  58. pomata-0.1.0/tests/indicators/oracles/donchian_channels.py +80 -0
  59. pomata-0.1.0/tests/indicators/oracles/dx.py +49 -0
  60. pomata-0.1.0/tests/indicators/oracles/ema.py +82 -0
  61. pomata-0.1.0/tests/indicators/oracles/fisher_transform.py +106 -0
  62. pomata-0.1.0/tests/indicators/oracles/hma.py +59 -0
  63. pomata-0.1.0/tests/indicators/oracles/ichimoku.py +107 -0
  64. pomata-0.1.0/tests/indicators/oracles/kama.py +106 -0
  65. pomata-0.1.0/tests/indicators/oracles/keltner_channels.py +71 -0
  66. pomata-0.1.0/tests/indicators/oracles/linear_regression.py +44 -0
  67. pomata-0.1.0/tests/indicators/oracles/linear_regression_angle.py +43 -0
  68. pomata-0.1.0/tests/indicators/oracles/linear_regression_intercept.py +44 -0
  69. pomata-0.1.0/tests/indicators/oracles/linear_regression_slope.py +55 -0
  70. pomata-0.1.0/tests/indicators/oracles/macd.py +64 -0
  71. pomata-0.1.0/tests/indicators/oracles/midpoint.py +53 -0
  72. pomata-0.1.0/tests/indicators/oracles/midprice.py +54 -0
  73. pomata-0.1.0/tests/indicators/oracles/mom.py +55 -0
  74. pomata-0.1.0/tests/indicators/oracles/money_flow_index.py +117 -0
  75. pomata-0.1.0/tests/indicators/oracles/obv.py +82 -0
  76. pomata-0.1.0/tests/indicators/oracles/parabolic_sar.py +100 -0
  77. pomata-0.1.0/tests/indicators/oracles/percentage_price_oscillator.py +58 -0
  78. pomata-0.1.0/tests/indicators/oracles/price_average.py +49 -0
  79. pomata-0.1.0/tests/indicators/oracles/price_median.py +45 -0
  80. pomata-0.1.0/tests/indicators/oracles/price_typical.py +48 -0
  81. pomata-0.1.0/tests/indicators/oracles/price_weighted_close.py +48 -0
  82. pomata-0.1.0/tests/indicators/oracles/rma.py +48 -0
  83. pomata-0.1.0/tests/indicators/oracles/roc.py +66 -0
  84. pomata-0.1.0/tests/indicators/oracles/rsi.py +95 -0
  85. pomata-0.1.0/tests/indicators/oracles/rsi_stochastic.py +43 -0
  86. pomata-0.1.0/tests/indicators/oracles/sma.py +57 -0
  87. pomata-0.1.0/tests/indicators/oracles/standard_deviation_ewma.py +52 -0
  88. pomata-0.1.0/tests/indicators/oracles/standard_deviation_rolling.py +41 -0
  89. pomata-0.1.0/tests/indicators/oracles/stochastic_fast.py +85 -0
  90. pomata-0.1.0/tests/indicators/oracles/stochastic_slow.py +47 -0
  91. pomata-0.1.0/tests/indicators/oracles/supertrend.py +150 -0
  92. pomata-0.1.0/tests/indicators/oracles/t3.py +77 -0
  93. pomata-0.1.0/tests/indicators/oracles/tema.py +66 -0
  94. pomata-0.1.0/tests/indicators/oracles/time_series_forecast.py +45 -0
  95. pomata-0.1.0/tests/indicators/oracles/trima.py +69 -0
  96. pomata-0.1.0/tests/indicators/oracles/trix.py +35 -0
  97. pomata-0.1.0/tests/indicators/oracles/true_range.py +68 -0
  98. pomata-0.1.0/tests/indicators/oracles/ultimate_oscillator.py +130 -0
  99. pomata-0.1.0/tests/indicators/oracles/variance_ewma.py +136 -0
  100. pomata-0.1.0/tests/indicators/oracles/variance_rolling.py +60 -0
  101. pomata-0.1.0/tests/indicators/oracles/vortex.py +96 -0
  102. pomata-0.1.0/tests/indicators/oracles/vwap.py +89 -0
  103. pomata-0.1.0/tests/indicators/oracles/vwma.py +75 -0
  104. pomata-0.1.0/tests/indicators/oracles/williams_r.py +83 -0
  105. pomata-0.1.0/tests/indicators/oracles/wma.py +61 -0
  106. pomata-0.1.0/tests/indicators/test_absolute_price_oscillator.py +334 -0
  107. pomata-0.1.0/tests/indicators/test_accumulation_distribution.py +469 -0
  108. pomata-0.1.0/tests/indicators/test_accumulation_distribution_oscillator.py +416 -0
  109. pomata-0.1.0/tests/indicators/test_adx.py +328 -0
  110. pomata-0.1.0/tests/indicators/test_adxr.py +326 -0
  111. pomata-0.1.0/tests/indicators/test_aroon.py +353 -0
  112. pomata-0.1.0/tests/indicators/test_aroon_oscillator.py +298 -0
  113. pomata-0.1.0/tests/indicators/test_atr.py +472 -0
  114. pomata-0.1.0/tests/indicators/test_atr_normalized.py +315 -0
  115. pomata-0.1.0/tests/indicators/test_awesome_oscillator.py +314 -0
  116. pomata-0.1.0/tests/indicators/test_balance_of_power.py +266 -0
  117. pomata-0.1.0/tests/indicators/test_benchmark.py +246 -0
  118. pomata-0.1.0/tests/indicators/test_bollinger_bands.py +369 -0
  119. pomata-0.1.0/tests/indicators/test_cci.py +428 -0
  120. pomata-0.1.0/tests/indicators/test_chaikin_money_flow.py +573 -0
  121. pomata-0.1.0/tests/indicators/test_chande_momentum_oscillator.py +358 -0
  122. pomata-0.1.0/tests/indicators/test_dema.py +358 -0
  123. pomata-0.1.0/tests/indicators/test_di_minus.py +314 -0
  124. pomata-0.1.0/tests/indicators/test_di_plus.py +314 -0
  125. pomata-0.1.0/tests/indicators/test_differential.py +632 -0
  126. pomata-0.1.0/tests/indicators/test_dm_minus.py +293 -0
  127. pomata-0.1.0/tests/indicators/test_dm_plus.py +291 -0
  128. pomata-0.1.0/tests/indicators/test_dominant_cycle_period.py +259 -0
  129. pomata-0.1.0/tests/indicators/test_dominant_cycle_phase.py +290 -0
  130. pomata-0.1.0/tests/indicators/test_donchian_channels.py +407 -0
  131. pomata-0.1.0/tests/indicators/test_dtype.py +42 -0
  132. pomata-0.1.0/tests/indicators/test_dx.py +317 -0
  133. pomata-0.1.0/tests/indicators/test_ema.py +386 -0
  134. pomata-0.1.0/tests/indicators/test_fisher_transform.py +349 -0
  135. pomata-0.1.0/tests/indicators/test_hilbert_phasor.py +301 -0
  136. pomata-0.1.0/tests/indicators/test_hilbert_trendline.py +277 -0
  137. pomata-0.1.0/tests/indicators/test_hma.py +350 -0
  138. pomata-0.1.0/tests/indicators/test_ichimoku.py +439 -0
  139. pomata-0.1.0/tests/indicators/test_kama.py +336 -0
  140. pomata-0.1.0/tests/indicators/test_keltner_channels.py +392 -0
  141. pomata-0.1.0/tests/indicators/test_linear_regression.py +299 -0
  142. pomata-0.1.0/tests/indicators/test_linear_regression_angle.py +271 -0
  143. pomata-0.1.0/tests/indicators/test_linear_regression_intercept.py +298 -0
  144. pomata-0.1.0/tests/indicators/test_linear_regression_slope.py +294 -0
  145. pomata-0.1.0/tests/indicators/test_macd.py +395 -0
  146. pomata-0.1.0/tests/indicators/test_mama.py +362 -0
  147. pomata-0.1.0/tests/indicators/test_midpoint.py +273 -0
  148. pomata-0.1.0/tests/indicators/test_midprice.py +301 -0
  149. pomata-0.1.0/tests/indicators/test_mom.py +306 -0
  150. pomata-0.1.0/tests/indicators/test_money_flow_index.py +623 -0
  151. pomata-0.1.0/tests/indicators/test_obv.py +483 -0
  152. pomata-0.1.0/tests/indicators/test_parabolic_sar.py +418 -0
  153. pomata-0.1.0/tests/indicators/test_percentage_price_oscillator.py +325 -0
  154. pomata-0.1.0/tests/indicators/test_precision_table.py +82 -0
  155. pomata-0.1.0/tests/indicators/test_price_average.py +338 -0
  156. pomata-0.1.0/tests/indicators/test_price_median.py +299 -0
  157. pomata-0.1.0/tests/indicators/test_price_typical.py +322 -0
  158. pomata-0.1.0/tests/indicators/test_price_weighted_close.py +320 -0
  159. pomata-0.1.0/tests/indicators/test_rma.py +337 -0
  160. pomata-0.1.0/tests/indicators/test_roc.py +300 -0
  161. pomata-0.1.0/tests/indicators/test_rsi.py +337 -0
  162. pomata-0.1.0/tests/indicators/test_rsi_stochastic.py +460 -0
  163. pomata-0.1.0/tests/indicators/test_sine_wave.py +376 -0
  164. pomata-0.1.0/tests/indicators/test_sma.py +274 -0
  165. pomata-0.1.0/tests/indicators/test_standard_deviation_ewma.py +299 -0
  166. pomata-0.1.0/tests/indicators/test_standard_deviation_rolling.py +322 -0
  167. pomata-0.1.0/tests/indicators/test_stochastic_fast.py +390 -0
  168. pomata-0.1.0/tests/indicators/test_stochastic_slow.py +428 -0
  169. pomata-0.1.0/tests/indicators/test_supertrend.py +435 -0
  170. pomata-0.1.0/tests/indicators/test_t3.py +376 -0
  171. pomata-0.1.0/tests/indicators/test_tema.py +371 -0
  172. pomata-0.1.0/tests/indicators/test_time_series_forecast.py +301 -0
  173. pomata-0.1.0/tests/indicators/test_trend_mode.py +289 -0
  174. pomata-0.1.0/tests/indicators/test_trima.py +280 -0
  175. pomata-0.1.0/tests/indicators/test_trix.py +252 -0
  176. pomata-0.1.0/tests/indicators/test_true_range.py +381 -0
  177. pomata-0.1.0/tests/indicators/test_typing.py +74 -0
  178. pomata-0.1.0/tests/indicators/test_ultimate_oscillator.py +419 -0
  179. pomata-0.1.0/tests/indicators/test_variance_ewma.py +303 -0
  180. pomata-0.1.0/tests/indicators/test_variance_rolling.py +318 -0
  181. pomata-0.1.0/tests/indicators/test_vortex.py +329 -0
  182. pomata-0.1.0/tests/indicators/test_vwap.py +361 -0
  183. pomata-0.1.0/tests/indicators/test_vwma.py +503 -0
  184. pomata-0.1.0/tests/indicators/test_williams_r.py +571 -0
  185. pomata-0.1.0/tests/indicators/test_wma.py +318 -0
  186. pomata-0.1.0/tests/metrics/oracles/__init__.py +130 -0
  187. pomata-0.1.0/tests/metrics/oracles/_quantile.py +36 -0
  188. pomata-0.1.0/tests/metrics/oracles/_rolling.py +76 -0
  189. pomata-0.1.0/tests/metrics/oracles/adjusted_sharpe_ratio.py +45 -0
  190. pomata-0.1.0/tests/metrics/oracles/alpha.py +42 -0
  191. pomata-0.1.0/tests/metrics/oracles/alpha_rolling.py +28 -0
  192. pomata-0.1.0/tests/metrics/oracles/beta.py +35 -0
  193. pomata-0.1.0/tests/metrics/oracles/beta_rolling.py +17 -0
  194. pomata-0.1.0/tests/metrics/oracles/burke_ratio.py +36 -0
  195. pomata-0.1.0/tests/metrics/oracles/cagr.py +24 -0
  196. pomata-0.1.0/tests/metrics/oracles/cagr_rolling.py +32 -0
  197. pomata-0.1.0/tests/metrics/oracles/calmar_ratio.py +31 -0
  198. pomata-0.1.0/tests/metrics/oracles/capture_downside_ratio.py +36 -0
  199. pomata-0.1.0/tests/metrics/oracles/capture_ratio.py +34 -0
  200. pomata-0.1.0/tests/metrics/oracles/capture_upside_ratio.py +36 -0
  201. pomata-0.1.0/tests/metrics/oracles/common_sense_ratio.py +27 -0
  202. pomata-0.1.0/tests/metrics/oracles/conditional_drawdown_at_risk.py +30 -0
  203. pomata-0.1.0/tests/metrics/oracles/conditional_value_at_risk.py +29 -0
  204. pomata-0.1.0/tests/metrics/oracles/downside_deviation.py +33 -0
  205. pomata-0.1.0/tests/metrics/oracles/downside_deviation_rolling.py +19 -0
  206. pomata-0.1.0/tests/metrics/oracles/drawdown.py +29 -0
  207. pomata-0.1.0/tests/metrics/oracles/drawdown_rolling.py +30 -0
  208. pomata-0.1.0/tests/metrics/oracles/gain_to_pain_ratio.py +27 -0
  209. pomata-0.1.0/tests/metrics/oracles/information_ratio.py +32 -0
  210. pomata-0.1.0/tests/metrics/oracles/information_ratio_rolling.py +24 -0
  211. pomata-0.1.0/tests/metrics/oracles/kelly_criterion.py +27 -0
  212. pomata-0.1.0/tests/metrics/oracles/kurtosis.py +31 -0
  213. pomata-0.1.0/tests/metrics/oracles/kurtosis_rolling.py +15 -0
  214. pomata-0.1.0/tests/metrics/oracles/max_drawdown.py +24 -0
  215. pomata-0.1.0/tests/metrics/oracles/max_drawdown_duration.py +33 -0
  216. pomata-0.1.0/tests/metrics/oracles/modigliani_risk_adjusted_performance.py +37 -0
  217. pomata-0.1.0/tests/metrics/oracles/omega_ratio.py +30 -0
  218. pomata-0.1.0/tests/metrics/oracles/omega_ratio_rolling.py +17 -0
  219. pomata-0.1.0/tests/metrics/oracles/pain_index.py +26 -0
  220. pomata-0.1.0/tests/metrics/oracles/pain_ratio.py +33 -0
  221. pomata-0.1.0/tests/metrics/oracles/payoff_ratio.py +26 -0
  222. pomata-0.1.0/tests/metrics/oracles/probabilistic_sharpe_ratio.py +47 -0
  223. pomata-0.1.0/tests/metrics/oracles/profit_ratio.py +27 -0
  224. pomata-0.1.0/tests/metrics/oracles/recovery_ratio.py +33 -0
  225. pomata-0.1.0/tests/metrics/oracles/risk_of_ruin.py +30 -0
  226. pomata-0.1.0/tests/metrics/oracles/sharpe_ratio.py +34 -0
  227. pomata-0.1.0/tests/metrics/oracles/sharpe_ratio_rolling.py +19 -0
  228. pomata-0.1.0/tests/metrics/oracles/skewness.py +31 -0
  229. pomata-0.1.0/tests/metrics/oracles/skewness_rolling.py +15 -0
  230. pomata-0.1.0/tests/metrics/oracles/sortino_ratio.py +33 -0
  231. pomata-0.1.0/tests/metrics/oracles/sortino_ratio_rolling.py +19 -0
  232. pomata-0.1.0/tests/metrics/oracles/stability.py +41 -0
  233. pomata-0.1.0/tests/metrics/oracles/sterling_ratio.py +35 -0
  234. pomata-0.1.0/tests/metrics/oracles/tail_ratio.py +30 -0
  235. pomata-0.1.0/tests/metrics/oracles/tail_ratio_rolling.py +15 -0
  236. pomata-0.1.0/tests/metrics/oracles/total_return.py +21 -0
  237. pomata-0.1.0/tests/metrics/oracles/total_return_rolling.py +30 -0
  238. pomata-0.1.0/tests/metrics/oracles/treynor_ratio.py +38 -0
  239. pomata-0.1.0/tests/metrics/oracles/treynor_ratio_rolling.py +28 -0
  240. pomata-0.1.0/tests/metrics/oracles/ulcer_index.py +24 -0
  241. pomata-0.1.0/tests/metrics/oracles/ulcer_performance_ratio.py +34 -0
  242. pomata-0.1.0/tests/metrics/oracles/value_at_risk.py +24 -0
  243. pomata-0.1.0/tests/metrics/oracles/value_at_risk_modified.py +41 -0
  244. pomata-0.1.0/tests/metrics/oracles/value_at_risk_parametric.py +27 -0
  245. pomata-0.1.0/tests/metrics/oracles/value_at_risk_rolling.py +17 -0
  246. pomata-0.1.0/tests/metrics/oracles/volatility.py +27 -0
  247. pomata-0.1.0/tests/metrics/oracles/volatility_rolling.py +17 -0
  248. pomata-0.1.0/tests/metrics/oracles/win_rate.py +24 -0
  249. pomata-0.1.0/tests/metrics/test_adjusted_sharpe_ratio.py +240 -0
  250. pomata-0.1.0/tests/metrics/test_alpha.py +313 -0
  251. pomata-0.1.0/tests/metrics/test_alpha_rolling.py +306 -0
  252. pomata-0.1.0/tests/metrics/test_benchmark.py +209 -0
  253. pomata-0.1.0/tests/metrics/test_beta.py +265 -0
  254. pomata-0.1.0/tests/metrics/test_beta_rolling.py +265 -0
  255. pomata-0.1.0/tests/metrics/test_burke_ratio.py +217 -0
  256. pomata-0.1.0/tests/metrics/test_cagr.py +209 -0
  257. pomata-0.1.0/tests/metrics/test_cagr_rolling.py +227 -0
  258. pomata-0.1.0/tests/metrics/test_calmar_ratio.py +211 -0
  259. pomata-0.1.0/tests/metrics/test_capture_downside_ratio.py +291 -0
  260. pomata-0.1.0/tests/metrics/test_capture_ratio.py +285 -0
  261. pomata-0.1.0/tests/metrics/test_capture_upside_ratio.py +289 -0
  262. pomata-0.1.0/tests/metrics/test_common_sense_ratio.py +199 -0
  263. pomata-0.1.0/tests/metrics/test_conditional_drawdown_at_risk.py +213 -0
  264. pomata-0.1.0/tests/metrics/test_conditional_value_at_risk.py +248 -0
  265. pomata-0.1.0/tests/metrics/test_downside_deviation.py +258 -0
  266. pomata-0.1.0/tests/metrics/test_downside_deviation_rolling.py +250 -0
  267. pomata-0.1.0/tests/metrics/test_drawdown.py +197 -0
  268. pomata-0.1.0/tests/metrics/test_drawdown_rolling.py +214 -0
  269. pomata-0.1.0/tests/metrics/test_dtype.py +42 -0
  270. pomata-0.1.0/tests/metrics/test_gain_to_pain_ratio.py +205 -0
  271. pomata-0.1.0/tests/metrics/test_information_ratio.py +303 -0
  272. pomata-0.1.0/tests/metrics/test_information_ratio_rolling.py +316 -0
  273. pomata-0.1.0/tests/metrics/test_kelly_criterion.py +206 -0
  274. pomata-0.1.0/tests/metrics/test_kurtosis.py +219 -0
  275. pomata-0.1.0/tests/metrics/test_kurtosis_rolling.py +222 -0
  276. pomata-0.1.0/tests/metrics/test_max_drawdown.py +197 -0
  277. pomata-0.1.0/tests/metrics/test_max_drawdown_duration.py +194 -0
  278. pomata-0.1.0/tests/metrics/test_modigliani_risk_adjusted_performance.py +339 -0
  279. pomata-0.1.0/tests/metrics/test_omega_ratio.py +223 -0
  280. pomata-0.1.0/tests/metrics/test_omega_ratio_rolling.py +251 -0
  281. pomata-0.1.0/tests/metrics/test_pain_index.py +206 -0
  282. pomata-0.1.0/tests/metrics/test_pain_ratio.py +214 -0
  283. pomata-0.1.0/tests/metrics/test_payoff_ratio.py +205 -0
  284. pomata-0.1.0/tests/metrics/test_probabilistic_sharpe_ratio.py +279 -0
  285. pomata-0.1.0/tests/metrics/test_profit_ratio.py +211 -0
  286. pomata-0.1.0/tests/metrics/test_recovery_ratio.py +202 -0
  287. pomata-0.1.0/tests/metrics/test_risk_of_ruin.py +211 -0
  288. pomata-0.1.0/tests/metrics/test_sharpe_ratio.py +226 -0
  289. pomata-0.1.0/tests/metrics/test_sharpe_ratio_rolling.py +246 -0
  290. pomata-0.1.0/tests/metrics/test_skewness.py +219 -0
  291. pomata-0.1.0/tests/metrics/test_skewness_rolling.py +222 -0
  292. pomata-0.1.0/tests/metrics/test_sortino_ratio.py +228 -0
  293. pomata-0.1.0/tests/metrics/test_sortino_ratio_rolling.py +270 -0
  294. pomata-0.1.0/tests/metrics/test_stability.py +216 -0
  295. pomata-0.1.0/tests/metrics/test_sterling_ratio.py +218 -0
  296. pomata-0.1.0/tests/metrics/test_tail_ratio.py +220 -0
  297. pomata-0.1.0/tests/metrics/test_tail_ratio_rolling.py +206 -0
  298. pomata-0.1.0/tests/metrics/test_total_return.py +183 -0
  299. pomata-0.1.0/tests/metrics/test_total_return_rolling.py +210 -0
  300. pomata-0.1.0/tests/metrics/test_treynor_ratio.py +357 -0
  301. pomata-0.1.0/tests/metrics/test_treynor_ratio_rolling.py +370 -0
  302. pomata-0.1.0/tests/metrics/test_typing.py +131 -0
  303. pomata-0.1.0/tests/metrics/test_ulcer_index.py +198 -0
  304. pomata-0.1.0/tests/metrics/test_ulcer_performance_ratio.py +237 -0
  305. pomata-0.1.0/tests/metrics/test_value_at_risk.py +227 -0
  306. pomata-0.1.0/tests/metrics/test_value_at_risk_modified.py +239 -0
  307. pomata-0.1.0/tests/metrics/test_value_at_risk_parametric.py +237 -0
  308. pomata-0.1.0/tests/metrics/test_value_at_risk_rolling.py +235 -0
  309. pomata-0.1.0/tests/metrics/test_volatility.py +252 -0
  310. pomata-0.1.0/tests/metrics/test_volatility_rolling.py +237 -0
  311. pomata-0.1.0/tests/metrics/test_win_rate.py +218 -0
  312. pomata-0.1.0/tests/pnl/oracles/__init__.py +49 -0
  313. pomata-0.1.0/tests/pnl/oracles/cost_borrow.py +55 -0
  314. pomata-0.1.0/tests/pnl/oracles/cost_fixed.py +54 -0
  315. pomata-0.1.0/tests/pnl/oracles/cost_funding.py +47 -0
  316. pomata-0.1.0/tests/pnl/oracles/cost_notional.py +58 -0
  317. pomata-0.1.0/tests/pnl/oracles/cost_per_share.py +43 -0
  318. pomata-0.1.0/tests/pnl/oracles/cost_proportional.py +44 -0
  319. pomata-0.1.0/tests/pnl/oracles/cost_slippage.py +44 -0
  320. pomata-0.1.0/tests/pnl/oracles/cumulative_pnl.py +42 -0
  321. pomata-0.1.0/tests/pnl/oracles/dividend.py +46 -0
  322. pomata-0.1.0/tests/pnl/oracles/equity_curve.py +44 -0
  323. pomata-0.1.0/tests/pnl/oracles/pnl_gross.py +61 -0
  324. pomata-0.1.0/tests/pnl/oracles/pnl_gross_inverse.py +70 -0
  325. pomata-0.1.0/tests/pnl/oracles/pnl_net.py +44 -0
  326. pomata-0.1.0/tests/pnl/oracles/returns_gross.py +45 -0
  327. pomata-0.1.0/tests/pnl/oracles/returns_log.py +64 -0
  328. pomata-0.1.0/tests/pnl/oracles/returns_net.py +44 -0
  329. pomata-0.1.0/tests/pnl/oracles/returns_simple.py +58 -0
  330. pomata-0.1.0/tests/pnl/oracles/turnover.py +44 -0
  331. pomata-0.1.0/tests/pnl/test_benchmark.py +124 -0
  332. pomata-0.1.0/tests/pnl/test_cost_borrow.py +301 -0
  333. pomata-0.1.0/tests/pnl/test_cost_fixed.py +253 -0
  334. pomata-0.1.0/tests/pnl/test_cost_funding.py +325 -0
  335. pomata-0.1.0/tests/pnl/test_cost_notional.py +296 -0
  336. pomata-0.1.0/tests/pnl/test_cost_per_share.py +251 -0
  337. pomata-0.1.0/tests/pnl/test_cost_proportional.py +272 -0
  338. pomata-0.1.0/tests/pnl/test_cost_slippage.py +276 -0
  339. pomata-0.1.0/tests/pnl/test_cumulative_pnl.py +268 -0
  340. pomata-0.1.0/tests/pnl/test_dividend.py +284 -0
  341. pomata-0.1.0/tests/pnl/test_dtype.py +42 -0
  342. pomata-0.1.0/tests/pnl/test_equity_curve.py +234 -0
  343. pomata-0.1.0/tests/pnl/test_pnl_gross.py +353 -0
  344. pomata-0.1.0/tests/pnl/test_pnl_gross_inverse.py +414 -0
  345. pomata-0.1.0/tests/pnl/test_pnl_net.py +283 -0
  346. pomata-0.1.0/tests/pnl/test_returns_gross.py +289 -0
  347. pomata-0.1.0/tests/pnl/test_returns_log.py +292 -0
  348. pomata-0.1.0/tests/pnl/test_returns_net.py +289 -0
  349. pomata-0.1.0/tests/pnl/test_returns_simple.py +271 -0
  350. pomata-0.1.0/tests/pnl/test_turnover.py +249 -0
  351. pomata-0.1.0/tests/pnl/test_typing.py +100 -0
  352. pomata-0.1.0/tests/support/__init__.py +124 -0
  353. pomata-0.1.0/tests/support/asserts.py +122 -0
  354. pomata-0.1.0/tests/support/bars.py +47 -0
  355. pomata-0.1.0/tests/support/benchmarks.py +33 -0
  356. pomata-0.1.0/tests/support/columns.py +20 -0
  357. pomata-0.1.0/tests/support/frames.py +107 -0
  358. pomata-0.1.0/tests/support/strategies.py +462 -0
  359. pomata-0.1.0/tests/support/synthesis.py +57 -0
  360. pomata-0.1.0/tests/support/tests/test_asserts.py +73 -0
  361. pomata-0.1.0/tests/support/tests/test_bars.py +41 -0
  362. pomata-0.1.0/tests/support/tests/test_frames.py +61 -0
  363. pomata-0.1.0/tests/support/tests/test_strategies.py +199 -0
  364. pomata-0.1.0/tests/support/tests/test_tolerances.py +92 -0
  365. pomata-0.1.0/tests/support/tolerances.py +114 -0
  366. pomata-0.1.0/tests/test_docs_surface.py +73 -0
  367. pomata-0.1.0/tests/test_expr.py +120 -0
  368. pomata-0.1.0/tests/test_package.py +44 -0
  369. pomata-0.1.0/uv.lock +1197 -0
@@ -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
+ [![CI](https://img.shields.io/github/actions/workflow/status/ilpomo/pomata/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/ilpomo/pomata/actions/workflows/ci.yml)
38
+ [![coverage](https://img.shields.io/codecov/c/github/ilpomo/pomata?style=flat-square&label=coverage)](https://codecov.io/gh/ilpomo/pomata)
39
+ [![ruff](https://img.shields.io/badge/ruff-261230?style=flat-square&logo=ruff&logoColor=D7FF64)](https://github.com/astral-sh/ruff)
40
+ [![ty](https://img.shields.io/badge/ty-261230?style=flat-square&logo=ty&logoColor=D7FF64)](https://github.com/astral-sh/ty)
41
+ [![mypy](https://img.shields.io/badge/mypy-4B6BFB?style=flat-square)](https://www.mypy-lang.org)
42
+ [![pyright](https://img.shields.io/badge/pyright-4B6BFB?style=flat-square)](https://github.com/microsoft/pyright)
43
+ [![pyrefly](https://img.shields.io/badge/pyrefly-4B6BFB?style=flat-square)](https://pyrefly.org)
44
+
45
+ ![Linux](https://img.shields.io/badge/Linux-505050?style=flat-square&logo=linux&logoColor=white)
46
+ ![macOS](https://img.shields.io/badge/macOS-505050?style=flat-square&logo=apple&logoColor=white)
47
+ ![Windows](https://custom-icon-badges.demolab.com/badge/Windows-505050.svg?style=flat-square&logo=windows11&logoColor=white)
48
+ [![python](https://img.shields.io/badge/python-3.12%20|%203.13%20|%203.14-3776AB?style=flat-square&logo=python&logoColor=white)](https://www.python.org/downloads/)
49
+ [![Polars](https://img.shields.io/badge/Polars-%E2%89%A51.40-CD792C?style=flat-square)](https://pola.rs)
50
+ [![license](https://img.shields.io/badge/license-MIT-750014?style=flat-square)](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).