Class PortfolioConstruction

java.lang.Object
com.quantfinlib.alpha.PortfolioConstruction

public final class PortfolioConstruction extends Object
Turns raw factor scores into tradeable weight vectors — deliberately a chain of small, composable, pure functions so a construction pipeline reads as what it does:
   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 Type
    Method
    Description
    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ᵢβᵢ = 0 exactly and the book's P&L stops being a leveraged market bet.
    static double[]
    capWeights(double[] weights, double maxWeight, double grossTarget)
    Caps each |weight| at maxWeight, then re-normalizes the rest toward grossTarget without 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 by 1/σᵢ (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 to grossTarget.
    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 at index — the standard input to inverseVolBudget(double[], double[], double) (per-bar σ; the common scale cancels in the renormalization).
    static double[]
    zScoreWeights(double[] scores, double grossTarget)
    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| = grossTarget and cap per-name weight at maxWeight.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Method Details

    • zScoreWeights

      public static double[] zScoreWeights(double[] scores, double grossTarget)
    • 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| = grossTarget and cap per-name weight at maxWeight.

      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| at maxWeight, then re-normalizes the rest toward grossTarget without 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 by 1/σᵢ (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 to grossTarget.

      Related but different: backtest.portfolio.PositionSizing. inverseVolatilityWeights builds 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

      public static double[] trailingVols(AlphaContext ctx, int index, int lookback)
      Trailing return volatilities per symbol at index — the standard input to inverseVolBudget(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.of re-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

      public 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. 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 follow AlphaContext.symbols() order (SORTED, not your input map's order); prefer the sectorNeutralize(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ᵢβᵢ = 0 exactly and the book's P&L stops being a leveraged market bet. Gross changes; re-target afterwards if needed.
    • trailingBetas

      public 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.
    • 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 to grossTarget. 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 = exclude
      covariance - per-bar return covariance, aligned both ways