Skip to main content

Analytics for quants

Project description

jQuantStats: Portfolio Analytics for Quants

PyPI version Python Coverage Downloads License CodeFactor Rhiza

Open in Marimo

📊 Overview

jQuantStats is a Python library for portfolio analytics that helps quants and portfolio managers understand their performance through in-depth analytics and risk metrics. It provides tools for calculating various performance metrics and visualizing portfolio performance using interactive Plotly charts.

The library is inspired by QuantStats, but focuses on providing a clean, modern API with enhanced visualization capabilities. Key improvements include:

  • Polars-native design with zero pandas runtime dependency
  • Modern interactive visualizations using Plotly
  • Comprehensive test coverage with pytest
  • Clean, well-documented API
  • Efficient data processing with polars

⚡ jQuantStats vs QuantStats

Feature jQuantStats QuantStats
DataFrame engine Polars (zero pandas at runtime) pandas
Visualisation Interactive Plotly charts Static matplotlib / seaborn
Input format polars.DataFrame pandas.Series / pandas.DataFrame
Entry point — positions Portfolio.from_cash_position(prices, cash_position, aum)
Entry point — returns Data.from_returns(returns, benchmark) qs.reports.full(returns)
HTML report portfolio.report.full() qs.reports.html(returns)
Snapshot chart data.plots.plot_snapshot() qs.plots.snapshot(returns)
Sharpe ratio data.stats.sharpe() qs.stats.sharpe(returns)
Sortino ratio data.stats.sortino() qs.stats.sortino(returns)
Max drawdown data.stats.max_drawdown() qs.stats.max_drawdown(returns)
Python version 3.11+ 3.7+
Type annotations Full (py.typed) Partial
Test coverage Coverage

✨ Features

  • Performance Metrics: Calculate key metrics like Sharpe ratio, Sortino ratio, drawdowns, volatility, and more
  • Risk Analysis: Analyze risk through metrics like Value at Risk (VaR), Conditional VaR, and drawdown analysis
  • Interactive Visualizations: Create interactive plots for portfolio performance, drawdowns, and return distributions
  • Benchmark Comparison: Compare your portfolio performance against benchmarks
  • Polars-native: Pure polars at runtime; pandas is not required and not supported as input

🖼️ Dashboard Preview

Portfolio Performance Dashboard

Interactive Plotly dashboard — cumulative returns, drawdowns, and monthly return heatmaps in a single view. Charts are fully interactive (zoom, pan, hover tooltips) when rendered in a browser.

📦 Installation

Using pip:

pip install jquantstats

Using conda (via conda-forge):

conda install -c conda-forge jquantstats

For development:

pip install jquantstats[dev]

🚀 Quick Start

Five lines to your first analytics result:

import polars as pl
from jquantstats import Data

returns = pl.DataFrame({
    "Date": ["2023-01-01", "2023-01-02", "2023-01-03", "2023-01-04", "2023-01-05"],
    "Strategy": [0.01, -0.03, 0.02, -0.01, 0.04],
}).with_columns(pl.col("Date").str.to_date())

benchmark = pl.DataFrame({
    "Date": ["2023-01-01", "2023-01-02", "2023-01-03", "2023-01-04", "2023-01-05"],
    "Benchmark": [0.005, -0.01, 0.008, -0.005, 0.015],
}).with_columns(pl.col("Date").str.to_date())

data = Data.from_returns(returns=returns, benchmark=benchmark)

print(data.stats.sharpe())
# {'Strategy': 4.24, 'Benchmark': 4.94}

print(data.stats.max_drawdown())
# {'Strategy': 0.03, 'Benchmark': 0.01}

fig = data.plots.plot_snapshot(title="Strategy vs Benchmark")
fig.show()  # opens an interactive Plotly chart in your browser

If you have price series and position sizes (recommended):

import polars as pl
from jquantstats import Portfolio

prices = pl.DataFrame({
    "date": ["2023-01-01", "2023-01-02", "2023-01-03"],
    "Asset1": [100.0, 101.0, 99.5],
}).with_columns(pl.col("date").str.to_date())

positions = pl.DataFrame({
    "date": ["2023-01-01", "2023-01-02", "2023-01-03"],
    "Asset1": [1000.0, 1000.0, 1200.0],
}).with_columns(pl.col("date").str.to_date())

pf = Portfolio.from_cash_position(prices=prices, cash_position=positions, aum=1_000_000)

sharpe = pf.stats.sharpe()
fig = pf.plots.snapshot()  # call fig.show() to display

If you already have a returns series:

import polars as pl
from jquantstats import Data

returns = pl.DataFrame({
    "Date": ["2023-01-01", "2023-01-02", "2023-01-03"],
    "Asset1": [0.01, -0.02, 0.03],
    "Asset2": [0.02, 0.01, -0.01]
}).with_columns(pl.col("Date").str.to_date())

data = Data.from_returns(returns=returns)

sharpe = data.stats.sharpe()
fig = data.plots.plot_snapshot(title="Portfolio Performance")  # call fig.show() to display

Risk metrics and drawdown analysis:

import polars as pl
from jquantstats import Data

returns = pl.DataFrame({
    "Date": ["2023-01-01", "2023-01-02", "2023-01-03", "2023-01-04", "2023-01-05"],
    "Strategy": [0.01, -0.03, 0.02, -0.01, 0.04],
}).with_columns(pl.col("Date").str.to_date())

data = Data.from_returns(returns=returns)

sharpe = data.stats.sharpe()
sortino = data.stats.sortino()
max_dd = data.stats.max_drawdown()
vol = data.stats.volatility()
var = data.stats.value_at_risk()
cvar = data.stats.conditional_value_at_risk()
calmar = data.stats.calmar()
win = data.stats.win_rate()

Benchmark comparison:

import polars as pl
from jquantstats import Data

returns = pl.DataFrame({
    "Date": ["2023-01-01", "2023-01-02", "2023-01-03"],
    "Strategy": [0.01, -0.02, 0.03],
}).with_columns(pl.col("Date").str.to_date())

benchmark = pl.DataFrame({
    "Date": ["2023-01-01", "2023-01-02", "2023-01-03"],
    "Benchmark": [0.005, -0.01, 0.015],
}).with_columns(pl.col("Date").str.to_date())

data = Data.from_returns(returns=returns, benchmark=benchmark)

ir = data.stats.information_ratio()
greeks = data.stats.greeks()
alpha = greeks["Strategy"]["alpha"]
beta = greeks["Strategy"]["beta"]
fig = data.plots.plot_snapshot(title="Strategy vs Benchmark")

Generate a full HTML report:

import polars as pl
from jquantstats import Portfolio

prices = pl.DataFrame({
    "date": ["2023-01-01", "2023-01-02", "2023-01-03"],
    "AAPL": [150.0, 152.0, 149.5],
    "MSFT": [250.0, 253.0, 251.0],
}).with_columns(pl.col("date").str.to_date())

positions = pl.DataFrame({
    "date": ["2023-01-01", "2023-01-02", "2023-01-03"],
    "AAPL": [500.0, 500.0, 600.0],
    "MSFT": [300.0, 300.0, 300.0],
}).with_columns(pl.col("date").str.to_date())

pf = Portfolio.from_cash_position(prices=prices, cash_position=positions, aum=1_000_000)

# Save a complete HTML performance report
html = pf.report.to_html()
with open("report.html", "w") as f:
    f.write(html)

🏗️ Architecture

jQuantStats has two layered entry points:

flowchart TD
    A["prices_df + cash_position_df + aum"] --> B["Portfolio.from_cash_position(...)
    NAV compiler"]
    B --> C[".stats  —  full stats suite"]
    B --> D[".plots.snapshot()  —  portfolio plots"]
    B --> E[".report.full()  —  HTML report"]
    B --> F[".data"]

    G["returns_df [+ benchmark_df]"] --> H["Data.from_returns(returns=..., benchmark=...)
    Data object"]
    H --> I[".stats  —  full stats suite"]
    H --> J[".plots.plot_snapshot()  —  snapshot chart"]

    F --> H

Entry point 1 (Portfolio) is for active portfolios where you have price series and position sizes. It compiles the NAV curve and exposes the full analytics suite.

Entry point 2 (Data.from_returns) is for arbitrary return streams — e.g. returns downloaded from a data vendor — with optional benchmark comparison.

The two APIs are layered: portfolio.data returns a Data object, so you can always drop from a Portfolio into the returns-series API.

📚 Documentation

For detailed documentation, visit jQuantStats Documentation.

🔧 Requirements

  • Python 3.11+
  • numpy
  • polars
  • plotly
  • scipy

👥 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📝 Citing

If you use jQuantStats in academic work or research reports, please cite it using the CITATIONS.bib file provided in this repository:

@software{jquantstats,
  author    = {Schmelzer, Thomas},
  title     = {jQuantStats: Portfolio Analytics for Quants},
  url       = {https://github.com/tschm/jquantstats},
  version   = {0.1.1},
  year      = {2026},
  license   = {MIT}
}

⚖️ License

This project is licensed under the MIT License - see the LICENSE file for details.

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

jquantstats-0.3.1.tar.gz (57.0 kB view details)

Uploaded Source

Built Distribution

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

jquantstats-0.3.1-py3-none-any.whl (62.1 kB view details)

Uploaded Python 3

File details

Details for the file jquantstats-0.3.1.tar.gz.

File metadata

  • Download URL: jquantstats-0.3.1.tar.gz
  • Upload date:
  • Size: 57.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for jquantstats-0.3.1.tar.gz
Algorithm Hash digest
SHA256 9b167483545fa602d7823b1d30c38ca5f0e0ea4f71d84ddf4a6d73abc1f5504f
MD5 61e4c37d3ae4546ab7074770bef6471b
BLAKE2b-256 ad0278533d7605bc49dcd032f8e8d72871f02b4eeaf7b91875c24f1f747602df

See more details on using hashes here.

Provenance

The following attestation bundles were made for jquantstats-0.3.1.tar.gz:

Publisher: rhiza_release.yml on tschm/jquantstats

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

File details

Details for the file jquantstats-0.3.1-py3-none-any.whl.

File metadata

  • Download URL: jquantstats-0.3.1-py3-none-any.whl
  • Upload date:
  • Size: 62.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for jquantstats-0.3.1-py3-none-any.whl
Algorithm Hash digest
SHA256 13f183c2f79004ceb6ef025ee7bcd4adf94c3b9f475447f7de863da735571ece
MD5 5cdcc4605b4671a06f6e74deda2626e2
BLAKE2b-256 ce7a5181d409fabf7945a472bc5737d8edd90b78c82d6be913c59974a2efc91d

See more details on using hashes here.

Provenance

The following attestation bundles were made for jquantstats-0.3.1-py3-none-any.whl:

Publisher: rhiza_release.yml on tschm/jquantstats

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