Correlation-aware portfolio optimization and analytics for Python.
Project description
Basanos computes correlation-adjusted risk positions from price data and expected-return signals. It estimates time-varying EWMA correlations, applies shrinkage towards the identity matrix, and solves a normalized linear system per timestamp to produce stable, scale-invariant positions — implementing a first hurdle for expected returns.
Table of Contents
- Features
- Installation
- Quick Start
- How It Works
- API Reference
- Configuration Reference
- Development
- License
Features
- Correlation-Aware Optimization — EWMA correlation estimation with shrinkage towards identity
- Dynamic Risk Management — Volatility-normalized positions with configurable clipping and variance scaling
- Portfolio Analytics — Sharpe, VaR, CVaR, drawdown, skew, kurtosis, and more
- Performance Attribution — Tilt/timing decomposition to isolate allocation vs. selection effects
- Interactive Visualizations — Plotly dashboards for NAV, drawdown, lead/lag analysis, and correlation heatmaps
- Polars-Native — Built on Polars DataFrames for high-performance, memory-efficient computation
Installation
pip install basanos
Or with uv:
uv add basanos
Quick Start
Portfolio Optimization
import numpy as np
import polars as pl
from basanos.math import BasanosConfig, BasanosEngine
n_days = 100
dates = pl.date_range(
pl.date(2023, 1, 1),
pl.date(2023, 1, 1) + pl.duration(days=n_days - 1),
eager=True,
)
rng = np.random.default_rng(42)
prices = pl.DataFrame({
"date": dates,
"AAPL": 100.0 + np.cumsum(rng.normal(0, 1.0, n_days)),
"GOOGL": 150.0 + np.cumsum(rng.normal(0, 1.2, n_days)),
})
# Expected-return signals in [-1, 1] (e.g. from a forecasting model)
mu = pl.DataFrame({
"date": dates,
"AAPL": np.tanh(rng.normal(0, 0.5, n_days)),
"GOOGL": np.tanh(rng.normal(0, 0.5, n_days)),
})
cfg = BasanosConfig(
vola=16, # EWMA lookback for volatility (days)
corr=32, # EWMA lookback for correlation (days, must be >= vola)
clip=3.5, # Clipping threshold for vol-adjusted returns
shrink=0.5, # Shrinkage intensity towards identity [0, 1]
aum=1e6, # Assets under management
)
engine = BasanosEngine(prices=prices, mu=mu, cfg=cfg)
positions = engine.cash_position # pl.DataFrame of optimized cash positions
portfolio = engine.portfolio # Portfolio object for analytics
Portfolio Analytics
import numpy as np
import polars as pl
from basanos.analytics import Portfolio
n_days = 60
dates = pl.date_range(
pl.date(2023, 1, 1),
pl.date(2023, 1, 1) + pl.duration(days=n_days - 1),
eager=True,
)
rng = np.random.default_rng(42)
prices = pl.DataFrame({
"date": dates,
"AAPL": 100.0 * np.cumprod(1 + rng.normal(0.001, 0.020, n_days)),
"GOOGL": 150.0 * np.cumprod(1 + rng.normal(0.001, 0.025, n_days)),
})
positions = pl.DataFrame({
"date": dates,
"AAPL": np.full(n_days, 10_000.0),
"GOOGL": np.full(n_days, 15_000.0),
})
portfolio = Portfolio.from_cash_position(prices=prices, cash_position=positions, aum=1e6)
# Performance metrics
nav = portfolio.nav_accumulated # Cumulative additive NAV
returns = portfolio.returns # Daily returns scaled by AUM
drawdown = portfolio.drawdown # Distance from high-water mark
# Statistics
stats = portfolio.stats
sharpe = stats.sharpe()["returns"]
vol = stats.volatility()["returns"]
Visualizations
fig = portfolio.plots.snapshot() # NAV + drawdown dashboard
fig = portfolio.plots.lead_lag_ir_plot(start=-10, end=20) # Sharpe across position lags
fig = portfolio.plots.lagged_performance_plot(lags=[0, 1, 2, 3, 4])
fig = portfolio.plots.correlation_heatmap()
# fig.show()
How It Works
The optimizer implements a three-step pipeline per timestamp:
-
Volatility adjustment — Log returns are normalized by an EWMA volatility estimate and clipped at
cfg.clipstandard deviations to limit the influence of outliers. -
Correlation estimation — An EWMA correlation matrix is computed from the vol-adjusted returns using a lookback of
cfg.corrdays. The matrix is shrunk toward the identity matrix with intensitycfg.shrink:C_shrunk = (1 - shrink) · C_ewma + shrink · IShrinkage stabilizes the matrix when assets are few or the lookback is short.
-
Position solving — For each timestamp, the system
C_shrunk · x = muis solved forx(the risk position vector). The solution is normalized by the inverse-matrix norm ofmu, making positions scale-invariant with respect to signal magnitude. Positions are further scaled by a running profit-variance estimate to adapt risk dynamically.
Cash positions are obtained by dividing risk positions by per-asset EWMA volatility.
API Reference
basanos.math
from basanos.math import BasanosConfig, BasanosEngine
| Class | Description |
|---|---|
BasanosConfig |
Immutable configuration (Pydantic model) |
BasanosEngine |
Core optimizer; produces positions and a Portfolio |
BasanosEngine properties
| Property | Returns | Description |
|---|---|---|
assets |
list[str] |
Numeric asset column names |
ret_adj |
pl.DataFrame |
Vol-adjusted, clipped log returns |
vola |
pl.DataFrame |
Per-asset EWMA volatility |
cor |
dict[date, np.ndarray] |
EWMA correlation matrices keyed by date |
cash_position |
pl.DataFrame |
Optimized cash positions |
portfolio |
Portfolio |
Ready-to-use portfolio for analytics |
basanos.analytics
from basanos.analytics import Portfolio
| Class | Description |
|---|---|
Portfolio |
Central data model for P&L, NAV, and attribution |
Stats |
Statistical risk/return metrics |
Plots |
Plotly-based interactive visualizations |
Portfolio properties
| Property | Description |
|---|---|
profits |
Per-asset daily P&L |
profit |
Aggregate daily portfolio profit |
nav_accumulated |
Cumulative additive NAV |
nav_compounded |
Compounded NAV |
returns |
Daily returns scaled by AUM |
monthly |
Monthly compounded returns |
highwater |
Running NAV maximum |
drawdown |
Drawdown from high-water mark |
tilt |
Static allocation (average position) |
timing |
Dynamic timing (deviation from average) |
stats |
Stats instance |
plots |
Plots instance |
Stats methods
| Method | Description |
|---|---|
sharpe(periods) |
Annualized Sharpe ratio |
volatility(periods, annualize) |
Standard deviation of returns |
skew() |
Skewness |
kurtosis() |
Excess kurtosis |
value_at_risk(alpha, sigma) |
Parametric VaR |
conditional_value_at_risk(alpha, sigma) |
Expected shortfall (CVaR) |
avg_return() |
Mean return (zeros excluded) |
avg_win() |
Mean positive return |
avg_loss() |
Mean negative return |
best() |
Maximum single-period return |
worst() |
Minimum single-period return |
Configuration Reference
| Parameter | Type | Constraint | Description |
|---|---|---|---|
vola |
int |
> 0 |
EWMA lookback for volatility (days) |
corr |
int |
>= vola |
EWMA lookback for correlation (days) |
clip |
float |
> 0 |
Clipping threshold for vol-adjusted returns |
shrink |
float |
[0, 1] |
Shrinkage intensity — 0 = no shrinkage, 1 = identity |
aum |
float |
> 0 |
Assets under management for position scaling |
from basanos.math import BasanosConfig
# Conservative — longer lookbacks, stronger shrinkage
conservative = BasanosConfig(vola=32, corr=64, clip=3.0, shrink=0.7, aum=1e6)
# Responsive — shorter lookbacks, lighter shrinkage
responsive = BasanosConfig(vola=8, corr=16, clip=4.0, shrink=0.3, aum=1e6)
Development
git clone https://github.com/Jebel-Quant/basanos.git
cd basanos
uv sync
| Command | Action |
|---|---|
make test |
Run the test suite |
make fmt |
Format and lint with ruff |
make typecheck |
Static type checking |
make deptry |
Audit declared dependencies |
Before submitting a PR, ensure all checks pass:
make fmt && make test && make typecheck
License
See LICENSE for details.
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 basanos-0.2.2.tar.gz.
File metadata
- Download URL: basanos-0.2.2.tar.gz
- Upload date:
- Size: 238.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
df7d14bcc61525be5fff7d888ffcf5c30f462e1a2797775c1e6092651eddb8b8
|
|
| MD5 |
1345dfb625e26e5b9246d3c5a3ca51a8
|
|
| BLAKE2b-256 |
29286aab7e3e42006afe13c40db208addb1c1e603b7b9e1c28ae226a07bf36d7
|
Provenance
The following attestation bundles were made for basanos-0.2.2.tar.gz:
Publisher:
rhiza_release.yml on Jebel-Quant/basanos
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
basanos-0.2.2.tar.gz -
Subject digest:
df7d14bcc61525be5fff7d888ffcf5c30f462e1a2797775c1e6092651eddb8b8 - Sigstore transparency entry: 1076745999
- Sigstore integration time:
-
Permalink:
Jebel-Quant/basanos@8dfed03a7c3eff42cbf8a654c2e3fb20c2a46814 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/Jebel-Quant
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
rhiza_release.yml@8dfed03a7c3eff42cbf8a654c2e3fb20c2a46814 -
Trigger Event:
push
-
Statement type:
File details
Details for the file basanos-0.2.2-py3-none-any.whl.
File metadata
- Download URL: basanos-0.2.2-py3-none-any.whl
- Upload date:
- Size: 24.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
688b39c2da5b92a1614b0f148a44a06448f2e3f5b7604ca35f88983cc7fc49c7
|
|
| MD5 |
fa33016b6e30c65490cc69af00ad987e
|
|
| BLAKE2b-256 |
271c2c69601db075e15dbc896b1624e171e22d24b31abc74fa4ebf6f32028650
|
Provenance
The following attestation bundles were made for basanos-0.2.2-py3-none-any.whl:
Publisher:
rhiza_release.yml on Jebel-Quant/basanos
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
basanos-0.2.2-py3-none-any.whl -
Subject digest:
688b39c2da5b92a1614b0f148a44a06448f2e3f5b7604ca35f88983cc7fc49c7 - Sigstore transparency entry: 1076746016
- Sigstore integration time:
-
Permalink:
Jebel-Quant/basanos@8dfed03a7c3eff42cbf8a654c2e3fb20c2a46814 -
Branch / Tag:
refs/tags/v0.2.2 - Owner: https://github.com/Jebel-Quant
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
rhiza_release.yml@8dfed03a7c3eff42cbf8a654c2e3fb20c2a46814 -
Trigger Event:
push
-
Statement type: