Portfolio optimization and options strategies in JAX — traditional, learning-based, and graph-based methods with impressive dark-themed visualizations.
Project description
jaxfolio
Differentiable portfolio optimization & options strategies, powered by JAX.
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")
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 |
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file jaxfolio-0.1.0.tar.gz.
File metadata
- Download URL: jaxfolio-0.1.0.tar.gz
- Upload date:
- Size: 3.3 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9885320a5520ab3b12d915db7b236bacd99dc0d54a74deebf03c9bb12bf6130a
|
|
| MD5 |
b37a571b210acebf1b7f08153e154c56
|
|
| BLAKE2b-256 |
917dce62445e3a5f04a129e547fddf160bc579a9107316201320e0899bcaa510
|
Provenance
The following attestation bundles were made for jaxfolio-0.1.0.tar.gz:
Publisher:
release.yml on bravant-oss/jaxfolio
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jaxfolio-0.1.0.tar.gz -
Subject digest:
9885320a5520ab3b12d915db7b236bacd99dc0d54a74deebf03c9bb12bf6130a - Sigstore transparency entry: 2195640710
- Sigstore integration time:
-
Permalink:
bravant-oss/jaxfolio@ce04023afb9870eb829d742fb63f41b555760b1d -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/bravant-oss
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ce04023afb9870eb829d742fb63f41b555760b1d -
Trigger Event:
push
-
Statement type:
File details
Details for the file jaxfolio-0.1.0-py3-none-any.whl.
File metadata
- Download URL: jaxfolio-0.1.0-py3-none-any.whl
- Upload date:
- Size: 71.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
666a8b0a637a33ea701819973d50fb6f25a8f13fb0b5954fc51e9ae4fdef39af
|
|
| MD5 |
91021abad8b9afe6ae0f093d3f91af05
|
|
| BLAKE2b-256 |
1f3470f8fad3de92c1cc8070d55369d186c49324119589fb3c382201baf25911
|
Provenance
The following attestation bundles were made for jaxfolio-0.1.0-py3-none-any.whl:
Publisher:
release.yml on bravant-oss/jaxfolio
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
jaxfolio-0.1.0-py3-none-any.whl -
Subject digest:
666a8b0a637a33ea701819973d50fb6f25a8f13fb0b5954fc51e9ae4fdef39af - Sigstore transparency entry: 2195640714
- Sigstore integration time:
-
Permalink:
bravant-oss/jaxfolio@ce04023afb9870eb829d742fb63f41b555760b1d -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/bravant-oss
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ce04023afb9870eb829d742fb63f41b555760b1d -
Trigger Event:
push
-
Statement type: