Skip to content
All library documents

Why a Minimum Active Share Constraint Is Nonconvex

Article Quant Q&A · Author: ThatQuantDude

Summary

The document considers whether a portfolio optimizer can impose a minimum active share relative to a benchmark. Active share is expressed as half the sum of absolute weight differences, or equivalently as one minus the sum of the smaller portfolio and benchmark weights. The central point is that requiring this distance to exceed a threshold creates a nonconvex feasible set, so the constraint does not fit disciplined convex programming rules.

A two-asset example demonstrates the issue: two portfolios can each meet the minimum distance while their midpoint, which equals the benchmark, does not. The response contrasts this with an upper bound on active share, which is convex and can be expressed using the listed formulations. A second answer sketches a quadratic-programming setup with auxiliary variables, but the document does not explain that construction in detail or fully specify its constraints. The discussion therefore clarifies the convexity limitation, rather than offering a general convex solution for enforcing a minimum degree of benchmark deviation.

Key ideas

  • Active share measures portfolio distance from benchmark weights using half the sum of absolute weight differences.
  • A minimum active share threshold generally defines a nonconvex feasible region.
  • The two-asset example shows that averaging two feasible portfolios can produce an infeasible benchmark portfolio.
  • An upper bound on active share is convex and can be represented with convex constraints.

Tags

Full text
# Active share portfolio constraint


# Active share portfolio constraint












I was reading a paper from Cremers and Petajisto, called

> How Active is Your Fund Manager? A New Measure That Predicts Performance

In the original paper from 2009 they have the following measure for active share

```
1-sum(min(w_pfo, w_bench))
```

which they later revised to

```
sum(abs(w_pfo-w_bench))*0.5
```

where in my understanding, the only difference is basically how you treat underweighting the benchmark, i.e. negative cancels out positive.

So now to my problem, I want to implement this in my portfolio optimisation code and I am struggling how to formulate the constraint in a DCP conform way. How can I get a convex formulation with a lower limit threshold. I basically want to ensure that my portfolio has a minimum % of active positions outside the benchmark. In CVXPY I am trying s.th. like

```
sum_entries(abs(w_pfo - w_bench))*0.5 >= 0.8
```

and

```
1-sum_entries(min_elemwise(w_pfo,w_bench)) >= 0.8
```

where w_pfo is my solver variable, I know that it will be a convex problem when I flip the inequality to <= 0.8.

Any inputs how to implement this would be great.

## Answer by josliber (score 2, accepted)

https://quant.stackexchange.com/a/34980

The problem you are proposing has a non-convex feasible set, so you can't formulate it in a DCP-conforming way. To see this, consider a 2-element benchmark portfolio [1, 1]. You are optimizing over portfolios [x, y] such that |x-1| + |y-1| >= 1.6. Note that [2, 0] and [0, 2] are both feasible portfolios, but the convex combination 0.5[2, 0] + 0.5[0, 2] = [1, 1] is not feasible. Thus, your feasible set is non-convex.

As you note, if you instead constrained portfolios to be sufficiently similar to the benchmark portfolio instead of sufficiently dissimilar, then the problem is convex and it is easy to formulate this in a DCP-conforming way using the exact constraints you listed in your question.

## Answer by AK88 (score 0)

https://quant.stackexchange.com/a/34938

I initially thought that this could be done in R using `fPortfolio` package, but couldn't figure it out. Then @josliber from SO suggested the following with `quadprog` package:

```
library(quadprog)
set.seed(1111)
df = matrix(rnorm(3*100), 100)

cov.mat = cov(df)
wbench <- c(0.4, 0.5, 0.1)
n <- length(wbench)

cov.mat.exp <- cbind(rbind(cov.mat, matrix(0, n, n)), matrix(0, 2*n, n))
cov.mat.exp <- cov.mat.exp + 1e-8*diag(2*n)  ## make it positive definite

consts <- rbind(rep(c(1, 0), c(n, n)),
                 rep(c(0, 1), c(n, n)),
                 cbind(matrix(0, n, n), -diag(n)),
                 cbind(diag(n), -diag(n)))

rhs <- c(1, 0.7, -w.bench, rep(0, n))

mod <- solve.QP(Dmat = cov.mat.exp,
                dvec = rep(0, 2*n),
                Amat = t(consts),
                bvec = rhs,
                meq = 1)
wpf <- head(mod$solution, n)
y <- tail(mod$solution, n)
wpf
y
```

Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.