Class PortfolioConstruction
double[] w = PortfolioConstruction.zScoreWeights(scores, 1.0, 0.05); w = PortfolioConstruction.sectorNeutralize(w, sectors); w = PortfolioConstruction.betaNeutralize(w, betas); w = PortfolioConstruction.inverseVolBudget(w, vols, 1.0);
All functions take and return weight arrays aligned with the
AlphaContext symbol order; NaN scores become zero weight. Inputs
are never mutated. Weights are fractions of equity (0.05 = 5%), signed
(short = negative), and each step documents what it preserves and what
it re-normalizes — neutralization steps change gross exposure, so gross
targeting is a final step or a re-application.
-
Method Summary
Modifier and TypeMethodDescriptionstatic double[]betaNeutralize(double[] weights, double[] betas) Beta neutrality: removes the market-beta component by projecting the weight vector orthogonal to the beta vector —w − β · (w·β)/(β·β)— soΣ wᵢβᵢ = 0exactly and the book's P&L stops being a leveraged market bet.static double[]capWeights(double[] weights, double maxWeight, double grossTarget) Caps each |weight| atmaxWeight, then re-normalizes the rest towardgrossTargetwithout breaching the cap (single pass of redistribution; residual gross shortfall stays in cash — honest, rather than looping until the cap itself binds everywhere).static double[]inverseVolBudget(double[] weights, double[] vols, double grossTarget) Inverse-volatility risk budgeting: rescales each position by1/σᵢ(keeping its sign and relative signal strength), so every name contributes comparably to portfolio risk instead of the volatile names dominating — the first-order version of equal risk contribution, exact when correlations are equal.static double[]meanVarianceTilt(double[] alphas, double[][] covariance, double grossTarget) Unconstrained mean-variance tilt:w ∝ Σ⁻¹ α(the Markowitz solution up to scale), solved via Gaussian elimination and normalized togrossTarget.static double[]sectorNeutralize(double[] weights, String[] sectors) Sector neutrality: demeans weights within each sector, so every sector's net weight is exactly zero and the book carries stock selection, not sector bets.static double[]sectorNeutralize(AlphaContext ctx, double[] weights, Map<String, String> sectorBySymbol) sectorNeutralize(double[], String[])with alignment by construction: sector labels come as a map keyed by symbol and are resolved against the context's frozen (sorted!)static double[]trailingBetas(AlphaContext ctx, int index, int lookback) Trailing OLS betas of each symbol against the equal-weight universe return — the in-panel market proxy when no index series is supplied.static double[]trailingVols(AlphaContext ctx, int index, int lookback) Trailing return volatilities per symbol atindex— the standard input toinverseVolBudget(double[], double[], double)(per-bar σ; the common scale cancels in the renormalization).static double[]zScoreWeights(double[] scores, double grossTarget) zScoreWeights(double[], double, double)without a per-name cap.static double[]zScoreWeights(double[] scores, double grossTarget, double maxWeight) Z-score sizing, the workhorse: demean scores cross-sectionally, scale by their dispersion, clamp at ±3σ (a single outlier must not own the book), then normalize toΣ|w| = grossTargetand cap per-name weight atmaxWeight.
-
Method Details
-
zScoreWeights
public static double[] zScoreWeights(double[] scores, double grossTarget) zScoreWeights(double[], double, double)without a per-name cap. -
zScoreWeights
public static double[] zScoreWeights(double[] scores, double grossTarget, double maxWeight) Z-score sizing, the workhorse: demean scores cross-sectionally, scale by their dispersion, clamp at ±3σ (a single outlier must not own the book), then normalize toΣ|w| = grossTargetand cap per-name weight atmaxWeight.Demeaning makes the book dollar-neutral by construction whenever scores are symmetric; the clamp bounds concentration before the cap even applies.
-
capWeights
public static double[] capWeights(double[] weights, double maxWeight, double grossTarget) Caps each |weight| atmaxWeight, then re-normalizes the rest towardgrossTargetwithout breaching the cap (single pass of redistribution; residual gross shortfall stays in cash — honest, rather than looping until the cap itself binds everywhere). -
inverseVolBudget
public static double[] inverseVolBudget(double[] weights, double[] vols, double grossTarget) Inverse-volatility risk budgeting: rescales each position by1/σᵢ(keeping its sign and relative signal strength), so every name contributes comparably to portfolio risk instead of the volatile names dominating — the first-order version of equal risk contribution, exact when correlations are equal. Re-normalized togrossTarget.Related but different:
backtest.portfolio.PositionSizing. inverseVolatilityWeightsbuilds long-only weights FROM vols alone and silently equal-weights on a degenerate vol; this method rescales an existing signed book and throws on unusable vols — a flat (σ = 0) name inside a signal-weighted book is a data problem to surface, not to paper over. -
trailingVols
Trailing return volatilities per symbol atindex— the standard input toinverseVolBudget(double[], double[], double)(per-bar σ; the common scale cancels in the renormalization). -
sectorNeutralize
public static double[] sectorNeutralize(AlphaContext ctx, double[] weights, Map<String, String> sectorBySymbol) sectorNeutralize(double[], String[])with alignment by construction: sector labels come as a map keyed by symbol and are resolved against the context's frozen (sorted!) symbol order —AlphaContext.ofre-sorts symbols, so a hand-built array in the caller's insertion order would silently demean against permuted labels. Symbols missing from the map keep their own singleton sector (i.e. they demean to zero). -
sectorNeutralize
Sector neutrality: demeans weights within each sector, so every sector's net weight is exactly zero and the book carries stock selection, not sector bets. Names with weight 0 stay 0 (they are not dragged in to fund their sector's offset). Gross exposure changes — re-target gross afterwards if it matters.- Parameters:
sectors- sector label per symbol, aligned with the weights — which followAlphaContext.symbols()order (SORTED, not your input map's order); prefer thesectorNeutralize(AlphaContext, double[], java.util.Map)overload, which cannot misalign
-
betaNeutralize
public static double[] betaNeutralize(double[] weights, double[] betas) Beta neutrality: removes the market-beta component by projecting the weight vector orthogonal to the beta vector —w − β · (w·β)/(β·β)— soΣ wᵢβᵢ = 0exactly and the book's P&L stops being a leveraged market bet. Gross changes; re-target afterwards if needed. -
trailingBetas
Trailing OLS betas of each symbol against the equal-weight universe return — the in-panel market proxy when no index series is supplied. -
meanVarianceTilt
public static double[] meanVarianceTilt(double[] alphas, double[][] covariance, double grossTarget) Unconstrained mean-variance tilt:w ∝ Σ⁻¹ α(the Markowitz solution up to scale), solved via Gaussian elimination and normalized togrossTarget. Unlike z-score sizing this uses the correlation structure: two highly correlated names with the same alpha share one bet instead of doubling it. Feed a shrunk/regularized covariance — the raw sample matrix near-singular universe inverts into garbage, which is a data problem no solver fixes.- Parameters:
alphas- expected-return proxy per symbol (z-scored factor scores work); NaN = excludecovariance- per-bar return covariance, aligned both ways
-