Skip to main content

A modernized backtest report module powered by Polars

Project description

Katsustats

License Python CI Ruff Sponsor

katsustats is a Polars-powered analytics and reporting library for daily return series, inspired by quantstats.

Pass a DataFrame with date and returns, and get summary metrics, drawdown analysis, key metrics with visualizations, and a self-contained HTML report.

Highlights:

  • Polars-first API with pandas input support
  • Benchmark-aware performance comparison
  • Self-contained offline HTML reports
  • AI-friendly structured JSON reports
  • Readable Markdown summaries for humans and agents
  • Functional modules for stats, plots, and reports

Preview

Cumulative returns Daily returns
Cumulative returns preview Daily returns preview
Drawdowns Monthly returns
Drawdown preview Monthly returns preview
Yearly returns Rolling Sharpe
Yearly returns preview Rolling Sharpe preview
Rolling volatility Day-of-week returns
Rolling volatility preview Day-of-week preview

Those figures are also available in an HTML report generated by katsustats.reports.html().

How to use

Installation

As a Python library:

pip install katsustats
# or
uv add katsustats

As a standalone CLI (no script needed — just install and run):

pipx install katsustats   # recommended for CLI-only use
# or
uv tool install katsustats

Standalone binary (no Python needed at all):

Download a pre-built binary for your platform from the GitHub Releases page, make it executable, and run it directly:

# macOS / Linux
chmod +x katsustats-linux-x86_64
./katsustats-linux-x86_64 report trades.csv -o report.html

Try it online

Data format

katsustats accepts either a Polars or pandas DataFrame with two required columns:

column type description
date date-like Trading date
returns float-like Daily return (e.g. 0.01 = +1%)

When a pandas DataFrame or Series is passed, katsustats converts it to Polars at the start of processing.

If date is datetime-like, it is normalized to pl.Date before analysis.

If multiple rows share the same date, katsustats compounds those same-day returns values into one daily return, emits a warning, and continues.

Quantstats-style inputs (pd.Series with a DatetimeIndex, or a pd.DataFrame with a DatetimeIndex and a returns column) are accepted automatically — the index is promoted to the date column.

Basic usage

import polars as pl
import katsustats

# Build your return series
returns = pl.DataFrame({
    "date": pl.date_range(pl.date(2020, 1, 1), pl.date(2023, 12, 31), "1d", eager=True),
    "returns": your_daily_returns,   # list / numpy array of floats
})

# Generate the full report (prints metrics + shows all plots)
results = katsustats.reports.full(returns)

Pandas inputs work too:

import pandas as pd

returns = pd.DataFrame({
    "date": dates,
    "returns": your_daily_returns,
})

results = katsustats.reports.full(returns)

Migrating from quantstats

If your existing code passes a pd.Series or a pd.DataFrame with a DatetimeIndex (the quantstats convention), both work without modification:

import pandas as pd

# pd.Series with DatetimeIndex
returns = pd.Series(your_daily_returns, index=date_index, name="returns")
results = katsustats.reports.full(returns)

# pd.DataFrame with DatetimeIndex
returns = pd.DataFrame({"returns": your_daily_returns}, index=date_index)
results = katsustats.reports.full(returns)

See also the runnable examples in examples/quickstart.py, examples/with_benchmark.py, and examples/html_report.py.

results is a dict with the following keys:

key type description
summary dict[str, float] Raw numeric summary values
metrics pl.DataFrame Summary metrics table
drawdowns pl.DataFrame Top-5 drawdown periods
dow_stats pl.DataFrame Day-of-week statistics
figures dict[str, Figure] All 8 matplotlib figures

With a benchmark

benchmark = pl.DataFrame({
    "date": pl.date_range(pl.date(2020, 1, 1), pl.date(2023, 12, 31), "1d", eager=True),
    "returns": benchmark_daily_returns,
})

results = katsustats.reports.full(returns, benchmark=benchmark)

When a benchmark is provided, the metrics table also includes Alpha, Beta, Correlation, Information Ratio, and Excess Return.

Advanced options

results = katsustats.reports.full(
    returns,
    benchmark=benchmark,
    rf=0.04,          # annualized risk-free rate (default 0.0)
    periods=252,      # trading days per year (default 252)
    show=False,       # suppress inline plot display
)

CLI

Generate an HTML tearsheet directly from a CSV or Parquet file — no script needed:

# From a CSV file (date and returns columns)
katsustats report trades.csv -o report.html

# Structured JSON for AI agents / downstream tooling
katsustats report trades.csv --format json -o report.json

# Markdown summary for humans and agents
katsustats report trades.csv --format markdown -o report.md

# Custom column names
katsustats report trades.csv --date-col day --returns-col pnl -o report.html

# With a benchmark and a custom title
katsustats report trades.csv --benchmark benchmark.csv --title "My Strategy" -o report.html

# From a Parquet file with a custom risk-free rate
katsustats report trades.parquet --rf 0.04 -o report.html

# Crypto data (365 days/year) with Monte Carlo
katsustats report crypto.csv --periods 365 --monte-carlo --mc-method bootstrap -o report.html

If -o is omitted the report is written alongside the input file (for example trades.html, trades.json, or trades.md).

HTML report

Generate a self-contained HTML report (similar to qs.reports.html()):

# Save to file
katsustats.reports.html(returns, benchmark=benchmark, title="My Strategy", output="report.html")

# Or get HTML string
html_str = katsustats.reports.html(returns, title="My Strategy")

The report includes headline metric cards, performance tables, period performance, drawdown analysis, day-of-week statistics, and all 8 charts embedded as images — all in a single .html file that works offline.

When a benchmark is provided, the HTML report also includes regime analysis.

View a BTC vs ETH backtest report.

JSON report

Generate an AI-friendly structured JSON report (see example):

# Save to file
katsustats.reports.json(returns, benchmark=benchmark, title="My Strategy", output="report.json")

# Or get JSON string
json_str = katsustats.reports.json(returns, title="My Strategy")

The JSON output is optimized for LLMs, agents, and other automation tools. It includes raw numeric metrics, structured period performance, top drawdowns, day-of-week statistics, and regime analysis when a benchmark is provided.

Markdown report

Generate a Markdown backtest summary (see example):

# Save to file
katsustats.reports.markdown(returns, benchmark=benchmark, title="My Strategy", output="report.md")

# Or get Markdown string
md_str = katsustats.reports.markdown(returns, title="My Strategy")

The Markdown output is designed to be readable in editors, GitHub, chat tools, and agent workflows. It includes an overview, headline metrics, performance tables, period performance, top drawdowns, day-of-week statistics, and optional regime analysis.

Monte Carlo simulation

Enable Monte Carlo analysis by passing monte_carlo=True to reports.html() or reports.full(). The simulation resamples your historical returns thousands of times (bootstrap by default) to show the range of outcomes that luck alone could have produced — keeping the same return distribution while breaking the original time order.

katsustats.reports.html(
    returns,
    output="report.html",
    monte_carlo=True,
    mc_sims=1000,       # number of simulated paths (default 1000)
    mc_bust=-0.20,      # optional: probability of hitting this drawdown
    mc_goal=0.50,       # optional: probability of reaching this return
    mc_seed=42,         # optional: reproducibility seed
    mc_method="bootstrap",  # "bootstrap" (default) or "shuffle"
)

The default bootstrap method samples returns with replacement, producing genuine variation in terminal return, Sharpe, and CAGR across paths. Use mc_method="shuffle" for the classic permutation approach (sampling without replacement), where terminal return is identical across all paths but max drawdown still varies due to path-dependent ordering.

The HTML report adds two panels to the Monte Carlo section:

Simulated paths Max drawdown distribution
Monte Carlo paths Max drawdown distribution
  • Simulated paths — fan of simulated cumulative return paths with the original path highlighted. Shows how differently the same returns could have played out under alternative orderings.
  • Max drawdown distribution — histogram of the worst drawdown across each simulated path. Because drawdown is path-dependent (a run of losses early hurts far more than the same losses late), this distribution has genuine spread and tells you how lucky or unlucky your drawdown sequence was.

You can also call the underlying stats directly:

# Raw simulation paths as a wide Polars DataFrame
paths = katsustats.stats.monte_carlo_paths(returns, sims=1000, seed=42, method="bootstrap")

# Probabilistic summary: terminal return, max drawdown, Sharpe, CAGR distributions
summary = katsustats.stats.monte_carlo_summary(
    returns,
    sims=1000,
    bust=-0.20,   # drawdown threshold for bust probability
    goal=0.50,    # return threshold for goal probability
    seed=42,
    method="bootstrap",
)

Using individual modules

You can also call the lower-level APIs directly:

import katsustats

# --- Stats ---
katsustats.stats.total_return(returns)
katsustats.stats.cagr(returns)
katsustats.stats.sharpe(returns, rf=0.0)
katsustats.stats.sortino(returns)
katsustats.stats.max_drawdown(returns)
katsustats.stats.calmar(returns)
katsustats.stats.volatility(returns)
katsustats.stats.win_rate(returns)
katsustats.stats.profit_factor(returns)
katsustats.stats.value_at_risk(returns, alpha=0.05)

katsustats.stats.drawdown_details(returns, top_n=5)      # pl.DataFrame
katsustats.stats.day_of_week_stats(returns)              # pl.DataFrame
katsustats.stats.summary_metrics(returns, benchmark)     # pl.DataFrame

# --- Plots ---
katsustats.plots.plot_cumulative_returns(returns, benchmark)
katsustats.plots.plot_drawdown(returns)
katsustats.plots.plot_monthly_heatmap(returns)
katsustats.plots.plot_yearly_returns(returns, benchmark)
katsustats.plots.plot_return_distribution(returns, benchmark)
katsustats.plots.plot_rolling_sharpe(returns, benchmark)
katsustats.plots.plot_rolling_volatility(returns, benchmark)
katsustats.plots.plot_dow_returns(returns)

Metrics produced

metric description
Total Return Compounded return over the full period
CAGR Compound Annual Growth Rate
Sharpe Ratio Annualized risk-adjusted return
Sortino Ratio Sharpe using only downside deviation
Max Drawdown Largest peak-to-trough decline
Calmar Ratio CAGR / |Max Drawdown|
Volatility (ann.) Annualized standard deviation
Win Rate % of days with positive returns
Profit Factor Gross profit / gross loss
Best / Worst Day Largest single-day gain / loss
Avg Win / Avg Loss Mean return on winning / losing days
Daily VaR (95%) 5th-percentile daily return
CVaR (95%) Mean return in the worst 5% tail
Recovery Factor Total return / |Max Drawdown|
Skewness / Kurtosis Distribution shape statistics
Best / Worst Month Largest / smallest monthly return
Best / Worst Year Largest / smallest yearly return
Positive Months / Years Share of profitable months / years

When a benchmark is provided, katsustats also reports Alpha, Beta, Correlation, Information Ratio, and Excess Return.

Project details


Download files

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

Source Distribution

katsustats-0.6.2.tar.gz (856.5 kB view details)

Uploaded Source

Built Distribution

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

katsustats-0.6.2-py3-none-any.whl (42.0 kB view details)

Uploaded Python 3

File details

Details for the file katsustats-0.6.2.tar.gz.

File metadata

  • Download URL: katsustats-0.6.2.tar.gz
  • Upload date:
  • Size: 856.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for katsustats-0.6.2.tar.gz
Algorithm Hash digest
SHA256 1f92782fd3442c52ab5b5a32115e1b9d0fba797df6682c9ae0e002b920f479ea
MD5 5a7ae63861fbdf1155607a4481a2f508
BLAKE2b-256 1c02b3eb0c937b0168458f39549dd42d09031de81405304de626b75fef1d3dfd

See more details on using hashes here.

Provenance

The following attestation bundles were made for katsustats-0.6.2.tar.gz:

Publisher: publish.yml on katsu1110/katsustats

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

File details

Details for the file katsustats-0.6.2-py3-none-any.whl.

File metadata

  • Download URL: katsustats-0.6.2-py3-none-any.whl
  • Upload date:
  • Size: 42.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for katsustats-0.6.2-py3-none-any.whl
Algorithm Hash digest
SHA256 2f2cba120928fe3439139f2b032499742f294a847cd8bc0b887a038a58f50db6
MD5 ccb07e61277de7b211dbc69ff0381980
BLAKE2b-256 4085d22f39b16e1cc1f2c5fa4a710398951a9ea56b1f0904eaa72c32655ce517

See more details on using hashes here.

Provenance

The following attestation bundles were made for katsustats-0.6.2-py3-none-any.whl:

Publisher: publish.yml on katsu1110/katsustats

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

Supported by

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