Skip to main content

Correlation-aware portfolio optimization and analytics for Python.

Project description

Rhiza Logo Basanos

Correlation-aware portfolio optimization and analytics for Python.

Synced with Rhiza License: MIT Python versions CI Code style: ruff uv Last Updated


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

  • 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:

  1. Volatility adjustment — Log returns are normalized by an EWMA volatility estimate and clipped at cfg.clip standard deviations to limit the influence of outliers.

  2. Correlation estimation — An EWMA correlation matrix is computed from the vol-adjusted returns using a lookback of cfg.corr days. The matrix is shrunk toward the identity matrix with intensity cfg.shrink:

    C_shrunk = (1 - shrink) · C_ewma + shrink · I
    

    Shrinkage stabilizes the matrix when assets are few or the lookback is short.

  3. Position solving — For each timestamp, the system C_shrunk · x = mu is solved for x (the risk position vector). The solution is normalized by the inverse-matrix norm of mu, 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


Download files

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

Source Distribution

basanos-0.2.2.tar.gz (238.5 kB view details)

Uploaded Source

Built Distribution

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

basanos-0.2.2-py3-none-any.whl (24.7 kB view details)

Uploaded Python 3

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

Hashes for basanos-0.2.2.tar.gz
Algorithm Hash digest
SHA256 df7d14bcc61525be5fff7d888ffcf5c30f462e1a2797775c1e6092651eddb8b8
MD5 1345dfb625e26e5b9246d3c5a3ca51a8
BLAKE2b-256 29286aab7e3e42006afe13c40db208addb1c1e603b7b9e1c28ae226a07bf36d7

See more details on using hashes here.

Provenance

The following attestation bundles were made for basanos-0.2.2.tar.gz:

Publisher: rhiza_release.yml on Jebel-Quant/basanos

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

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

Hashes for basanos-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 688b39c2da5b92a1614b0f148a44a06448f2e3f5b7604ca35f88983cc7fc49c7
MD5 fa33016b6e30c65490cc69af00ad987e
BLAKE2b-256 271c2c69601db075e15dbc896b1624e171e22d24b31abc74fa4ebf6f32028650

See more details on using hashes here.

Provenance

The following attestation bundles were made for basanos-0.2.2-py3-none-any.whl:

Publisher: rhiza_release.yml on Jebel-Quant/basanos

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