Skip to main content

Portfolio optimization and options strategies in JAX — traditional, learning-based, and graph-based methods with impressive dark-themed visualizations.

Project description

jaxfolio

jaxfolio

Differentiable portfolio optimization & options strategies, powered by JAX.

docs python jax license


Why jaxfolio

Portfolio construction has splintered into many methods — mean-variance, risk parity, hierarchical clustering, learned policies — and, increasingly, options overlays layered on top of an equity book. Each is powerful, but in practice they arrive as disconnected tools: a QP solver here, a clustering script there, a separate options pricer, each with its own inputs, quirks, and no common way to compare them or hedge across them.

That fragmentation is the real cost. Swapping one strategy for another means rewriting glue code; comparing them fairly means re-implementing the same backtest three times; and taking a gradient through an allocation — the thing modern, learning-based methods depend on — is simply impossible when the pieces don't share a numerical foundation.

jaxfolio unifies them on a single differentiable core. Sixteen optimizers — classical, learning-based, and graph-based — sit behind one interface, method(returns) → PortfolioResult, and every constrained method is the same jit-compiled projected-gradient solver with a different objective. Because the whole pipeline (moment estimation → optimization → backtest) is JAX, it is end-to-end differentiable and fast: you can backtest thousands of rebalances, differentiate through an optimizer to train an allocation policy, and get exact option Greeks for an entire chain from the same autodiff that prices it.


Install

uv add jaxfolio            # core
uv add "jaxfolio[data]"    # + Yahoo Finance / Parquet loaders

Quickstart

import jaxfolio as jf
from jaxfolio.backtest import compare
from jaxfolio import viz

returns = jf.generate_returns(n_assets=10, seed=7)      # or load_yfinance / load_csv

results = compare(returns, {
    "Max Sharpe":  jf.maximum_sharpe,
    "HRP":         jf.hierarchical_risk_parity,
    "Risk Parity": jf.risk_parity,
    "1/N":         jf.equal_weight,
})

viz.save(viz.dashboard(results, returns), "dashboard.png")

jaxfolio dashboard

Capabilities

Traditional min-variance · mean-variance · max-Sharpe · max-diversification · risk parity (ERC) · Kelly · min-CVaR · Black–Litterman
Learning differentiable MLP Sharpe policy · online exponentiated-gradient
Graph hierarchical risk parity (HRP) · HERC · MST centrality
LLM (local) LLM→Black-Litterman views · news-sentiment tilt · multi-agent debate — all on a local Ollama model, no API keys
Options Black-Scholes pricing · Greeks via autodiff · implied vol · 10+ multi-leg strategies · collar / covered-call overlays
Extensible register your own strategy · a toolkit of reusable building blocks · works everywhere the built-ins do
Backtest walk-forward engine · costs & turnover · Sharpe / Sortino / Calmar / VaR / CVaR / drawdown
Data synthetic GBM · CSV · Parquet · Yahoo Finance · option chains

Benchmark

How does jaxfolio compare to the established Python optimizers? On the workload that matters — a rolling-rebalance backtest — its cached JIT kernel compiles once and is then reused across every solve, so jaxfolio is the fastest on minimum-variance and risk parity and competitive on maximum-Sharpe, while landing on the same optimum as the dedicated QP solvers.

jaxfolio vs other portfolio-optimization libraries

Amortized solve time (ms, lower is better) over a 60-rebalance backtest of 20 assets. Every library is fed identical sample moments, so the inputs — and the resulting portfolios — are the same:

ms / solve Min-Variance Max-Sharpe Risk-Parity
jaxfolio 0.22 2.82 0.57
CVXPY 1.59 1.61 2.28
PyPortfolioOpt 1.52 2.00
skfolio 3.15 3.70 3.82
Riskfolio-Lib 8.61 9.45 9.42
SciPy (SLSQP) 19.67 4.68 5.33

All methods reach the same optimum on min-variance and max-Sharpe (max weight difference < 0.001); jaxfolio's risk parity uses exact ERC coordinate descent. It's a fair scoreboard, not a victory lap — CVXPY edges jaxfolio on max-Sharpe. Reproduce it — plus a single cold-solve comparison and the per-library adapters — in examples/benchmark. (Numbers from an Apple-silicon run; your absolute times will differ, the ranking holds.)

Options

from jaxfolio.options import collar
from jaxfolio import viz

strat = collar(spot=100, put_strike=95, call_strike=110, expiry=0.25, vol=0.22)
strat.greeks(spot=100, vol=0.22)                 # net delta / gamma / vega / theta / rho
viz.save(viz.plot_payoff(strat, spot=100), "collar.png")

LLM strategies (local models)

State-of-the-art LLM-driven allocation, running entirely on a local model via Ollama — no API keys, no data leaving your machine. Each strategy elicits per-asset views from the model and routes them through Black-Litterman, so they inherit the equilibrium prior and constraints.

uv add "jaxfolio[llm]"        # adds the local-model client
ollama serve && ollama pull llama3.1
import jaxfolio as jf
from jaxfolio.llm import OllamaClient

client = OllamaClient("llama3.1")          # any local model: mistral, qwen2.5, gemma…
returns = jf.generate_returns(n_assets=8, seed=7)

# 1 — LLM-enhanced Black-Litterman (ICLR 2025): sampled views, confidence from variance.
bl = jf.llm_black_litterman(returns, client=client, samples=5)

# 2 — News-sentiment tilt from a local model.
news = {"AAPL": "record revenue, raised guidance", "TSLA": "recall concerns"}
sent = jf.llm_sentiment_portfolio(returns, news, client=client)

# 3 — Multi-agent debate (bull / bear / risk agents negotiate the views).
agents = jf.llm_agent_portfolio(returns, client=client)

print(bl.metadata["llm_views"], bl.metadata["llm_confidence"])

No model installed? Every strategy accepts an injected client, so a FakeLLM runs the whole flow offline (this is how the tests and examples/04_llm_strategies.py work). References: LLM-BLM (ICLR 2025) · AlphaAgents · HARLF.

Custom strategies

Write your own strategy and it works everywhere the built-ins do — backtester, compare(), and the plots. Register it by name, or hand the shared solver a JAX objective via the toolkit.

import numpy as np, jax.numpy as jnp
import jaxfolio as jf
from jaxfolio.custom import custom_strategy, CustomStrategy

# Mode 1 — return weights directly (a momentum tilt).
momentum = custom_strategy(
    "momentum",
    lambda r: np.clip(((1 + r).prod() - 1).to_numpy(), 0, None),
    register=True,
)

# Mode 2 — supply a JAX objective; reuse the shared projected-gradient solver.
def entropy_minvar(w, ctx):                       # ctx exposes mu, cov, returns, assets, n
    return w @ ctx.cov @ w - 0.002 * -jnp.sum(w * jnp.log(w + 1e-9))

strat = CustomStrategy.from_objective("entropy_minvar", entropy_minvar, register=True)

jf.list_strategies(custom_only=True)              # ['entropy_minvar', 'momentum']
jf.get_strategy("momentum")(returns)              # a full PortfolioResult

The jaxfolio.toolkit module exposes the reusable building blocks — moments, make_projection, solve_projected_gradient, the projections, and finalize_result — so custom strategies are written the same idiomatic way as the built-ins.

Development

uv sync --all-extras
uv run pytest
uv run ruff check . && uv run ruff format --check .

MIT © jaxfolio contributors

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

jaxfolio-0.1.1.tar.gz (3.4 MB view details)

Uploaded Source

Built Distribution

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

jaxfolio-0.1.1-py3-none-any.whl (75.9 kB view details)

Uploaded Python 3

File details

Details for the file jaxfolio-0.1.1.tar.gz.

File metadata

  • Download URL: jaxfolio-0.1.1.tar.gz
  • Upload date:
  • Size: 3.4 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for jaxfolio-0.1.1.tar.gz
Algorithm Hash digest
SHA256 14fdeedcc4dfa2215994310078634cf6bda706fa6fde0d96c0eb9c1e264fb243
MD5 b3cd69193c11b55832f97e639e3d729d
BLAKE2b-256 4fba9f346953e084ce85492bb6c7b6fe402550112865b5f6306d7be1595b1b46

See more details on using hashes here.

Provenance

The following attestation bundles were made for jaxfolio-0.1.1.tar.gz:

Publisher: release.yml on bravant-oss/jaxfolio

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

File details

Details for the file jaxfolio-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: jaxfolio-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 75.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for jaxfolio-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 c239d5417a497b58436753ee5542689256c774397ef5d8f95680de9465957af2
MD5 c82788a7c59071afdaa8f83810eb13f2
BLAKE2b-256 aaa2512032774524dbba03d3f14a706c7c91af387031be7b327f4d3b69c944fc

See more details on using hashes here.

Provenance

The following attestation bundles were made for jaxfolio-0.1.1-py3-none-any.whl:

Publisher: release.yml on bravant-oss/jaxfolio

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