Getting Started

Py-vAllocation ships with a compact API, extensive docstrings, and production-oriented tutorials. This page shows how to install the package, exercise the core wrapper, and run the end-to-end ETF quickstart.

Installation

python -m pip install py-vallocation

The base install pulls in numpy, pandas, scipy and cvxopt. If you depend on nonlinear shrinkage, POET, or the graphical lasso extras, install the optional bundle:

python -m pip install "py-vallocation[robust]"

Verify the install

The snippet below ingests scenario data into pyvallocation.portfolioapi.AssetsDistribution, wires it to a pyvallocation.portfolioapi.PortfolioWrapper, and computes a compact mean-variance frontier.

import pandas as pd
from pyvallocation.portfolioapi import AssetsDistribution, PortfolioWrapper

scenarios = pd.DataFrame(
    {
        "Equity_US": [0.021, -0.013, 0.018, 0.007, 0.011],
        "Equity_EU": [0.017, -0.009, 0.014, 0.004, 0.006],
        "Credit_US": [0.009, 0.008, 0.007, 0.006, 0.008],
        "Govt_Bonds": [0.004, 0.003, 0.005, 0.002, 0.004],
    }
)

wrapper = PortfolioWrapper.from_scenarios(scenarios)

frontier = wrapper.variance_frontier(num_portfolios=5)
weights, expected_return, risk = frontier.min_risk()

print(weights.round(4))
print(f"Expected return: {expected_return:.4%} | Volatility: {risk:.4%}")

Run the ETF quickstart

For a full workflow–from data ingestion to trade lots–run the ETF quickstart script. See the <no title> notebook for the full walkthrough with output and plots.

python examples/quickstart_etf_allocation.py

The tutorial explains each stage, referencing pyvallocation.moments.estimate_moments(), pyvallocation.views.FlexibleViewsProcessor, pyvallocation.ensembles.assemble_portfolio_ensemble(), and pyvallocation.discrete_allocation.discretize_weights().

From prices (simplest entry point)

For users with raw price data, from_prices computes simple returns and builds the distribution in one line:

from pyvallocation import PortfolioWrapper

wrapper = PortfolioWrapper.from_prices(price_df)
frontier = wrapper.variance_frontier()
w, ret, risk = frontier.tangency(risk_free_rate=0.04)

From invariants (Meucci Prayer pipeline)

For log-return invariants (or yield changes, vol changes), from_invariants chains projection (P3) and repricing (P4) into P&L scenarios for optimisation:

import numpy as np
from pyvallocation import PortfolioWrapper

log_rets = np.log(prices / prices.shift(1)).dropna()
wrapper = PortfolioWrapper.from_invariants(log_rets, horizon=52, seed=42)
frontier = wrapper.cvar_frontier(alpha=0.05)

For mixed portfolios (stocks + bonds + options), specify per-instrument repricing:

from pyvallocation import reprice_exp, reprice_taylor

wrapper = PortfolioWrapper.from_invariants(
    invariants_df,  # DataFrame: equity log-return, yield change, vol change
    reprice={
        "SPY":  reprice_exp,
        "TLT":  (["yield_10y"], lambda dy: reprice_taylor(dy, delta=-17, gamma=200)),
        "Call": (["equity_lr", "iv_chg"], my_greeks_fn),
    },
    horizon=52, seed=42,
)

Views with helpers

Express views in plain terms with at_least, at_most, between:

from pyvallocation import FlexibleViewsProcessor, at_least, at_most, between

ep = FlexibleViewsProcessor(
    prior_risk_drivers=invariants_df,
    mean_views={"SPY": at_least(0.05), ("SPY", "TLT"): at_least(0.02)},
    vol_views={"TLT": between(0.08, 0.15)},
    rank_mean=["SPY", "TLT", "GLD"],   # E[SPY] >= E[TLT] >= E[GLD]
)

# Pass tilted probabilities into the Prayer pipeline
wrapper = PortfolioWrapper.from_invariants(
    ep.get_scenarios(),
    p=ep.get_posterior_probabilities(),
    reprice=reprice_exp, horizon=52, seed=42,
)

Invariant-level stress testing

Test portfolio resilience to risk-driver shocks (not just P&L shocks):

from pyvallocation import stress_invariants, at_least

report = stress_invariants(
    invariants_df, weights=optimal_weights,
    reprice={"SPY": reprice_exp, "TLT": (["yield_10y"], bond_fn)},
    stress_views={"yield_10y": at_least(0.005)},  # yields up 50bp
    horizon=52, seed=42,
)
print(report)   # nominal vs stressed: return, vol, VaR, CVaR

Relaxed risk parity

Gambeta & Kwon’s relaxed risk parity solver is exposed through the same interface. It solves for the benchmark risk parity weights then sweeps relaxed targets sized by multiplier m.

import numpy as np
from pyvallocation.portfolioapi import AssetsDistribution, PortfolioWrapper

mu = np.array([0.08, 0.06, 0.05, 0.03])
cov = np.array(
    [
        [0.090, 0.040, 0.025, 0.010],
        [0.040, 0.070, 0.020, 0.015],
        [0.025, 0.020, 0.060, 0.018],
        [0.010, 0.015, 0.018, 0.045],
    ]
)

wrapper = PortfolioWrapper.from_moments(mu, cov)

frontier = wrapper.relaxed_risk_parity_frontier(
    num_portfolios=4,
    max_multiplier=1.5,
    lambda_reg=0.25,
)
print(frontier.to_frame().round(4))

Metadata attached to frontier.metadata documents the multiplier, target, and objective value per point–ideal inputs for dashboarding or plotting.

Where to go next

  • Browse Tutorials for narrative walkthroughs.

  • Run additional examples from pyvallocation.examples:

    • mean_variance_frontier.py - classical efficient frontier summary.

    • cvar_allocation.py - minimum-CVaR portfolio and tangency allocation.

    • robust_frontier.py - Meucci-style robust tau-frontier.

    • relaxed_risk_parity_frontier.py - relaxed risk parity diagnostics.

    • discrete_allocation.py - map continuous weights to share counts.

    • portfolio_ensembles.py - blend multiple frontiers into a single allocation.

    • stress_and_pnl.py - probability tilts, linear shocks, and performance summaries.

  • Dive into the API reference starting at Portfolio API; docstrings are synchronised with the published documentation.