[R-SIG-Finance] Implementing Sharpe's style analysis with solve.QP

Dale Smith dsmith at viciscapital.com
Tue Feb 26 12:36:10 CET 2008


Try it with monthly returns of a mutual fund, including bond & stock
indices. Bond mutual funds should have little weight for stocks and
vice-versa.

You may want to do a principal components analysis first as there can be
multicollinearity in market indices.

Dale Smith, Ph.D.
Vicis Capital, LLC
-----Original Message-----
From: r-sig-finance-bounces at stat.math.ethz.ch
[mailto:r-sig-finance-bounces at stat.math.ethz.ch] On Behalf Of Peter Carl
Sent: Monday, February 25, 2008 7:35 PM
To: r-finance
Subject: [R-SIG-Finance] Implementing Sharpe's style analysis with
solve.QP

I've recently become interested in implementing style analysis as
described by 
William Sharpe in:
  http://www.stanford.edu/~wfsharpe/art/sa/sa.htm

In particular, I want to implement the 'constrained' method he discusses
using 
the quadratic programming approach he advocates.  I thought that this
might 
be easy to do in R using the function 'solve.QP' in the package
'quadprog'.  
Easy, of course, is a relative term.  I've found the documentation
somewhat 
oblique, and Googling hasn't produced much in the way of help.  

So I thought I would offer what I've been able to figure out and try to
get 
some feedback.  What I have here *seems* to work, although I haven't had
to 
specify this kind of a problem myself in, well, decades.

According to Sharpe (1994) what we're trying to do is minimize the
variance of 
the difference between the observed returns and the modeled returns:
min VAR(R.f - SUM[w_i * R.s_i]) = min VAR(F - w*S)
  s.t. SUM(w_i) = 1; w_i > 0

where:
R.f   Fund returns
R.s   Style returns
w_i   Style weights

Remembering that VAR(aX + bY) = a^2 VAR(X) + b^2 VAR(Y) + 2ab COV(X,Y), 
we can refactor the problem as:

= VAR(R.f) + w'*V*w - 2*w'*COV(R.f,R.s)

where:
V     Variance-covariance matrix of style index matrix
C     Vector of covariances between the style index and the fund

At this point, we can drop VAR[R.f] as it isn't a function of weights
and 
multiply both sides by 1/2 to get:

min (1/2) w'*V*w - C'w
  s.t. w'*e = 1, w_i > 0

where:
e     Vector of 1's

Now, map that to the inputs of solve.QP, which is specified as:
min(-d' b + 1/2 b' D b) with the constraints A' b >= b_0

so:
b is the weight vector,
D is the variance-covariance matrix of the styles
d is the covariance vector between the fund and the styles

Here is the function.  The comments embedded within the function discuss
how 
each of the components is constructed for solve.QP.

`style.QPfit` <-
function(R.fund, R.style, model=FALSE, ...) 
{
# INPUTS
# R.fund   Vector of a fund return time series
# R.style   Matrix of a style return time series
# 
# 
# OUTPUTS
# weights   Vector of optimal style index weights
# @ todo: TE  Tracking error between the calc'd and actual fund
# @ todo: Fp  Vector of calculated fund time series
# R^2  Coefficient of determination
#
    # Check to see if the required libraries are loaded
    if(!require("quadprog", quietly=TRUE))
        stop("package", sQuote("quadprog"), "is needed.  Stopping")

    R.fund = checkData(R.fund[,1,drop=FALSE], method="matrix")
    R.style = checkData(R.style, method="matrix")

    # @todo: Missing data is not allowed, use = "pairwise.complete.obs"
?
    style.rows = dim(R.style)[1]
    style.cols = dim(R.style)[2]

    # Calculate D and d
    Dmat = cov(R.style)
    dvec = cov(R.fund, R.style)

    # To specify A' b >= b_0, we create an nxn matrix Amat and the
constraints 
    # vector b0.  First we tackle Amat.  The first constraint listed
above is 
    # SUM[w_i] = 1.  The left side of the constraint is expressed as a
vector 
    # of 1's:
    a1 = rep(1, style.cols)

    # In b0, which represents the right side of the equation, this
vector is 
    # paired with the value '1'.

    # The second constraint sets the lower bound of the weights to zero.

    # First, we set up an identity matrix.  
    a2 = matrix(0, style.cols, style.cols)
    diag(a2) = 1

    # It's paired in b0 with a vector of lower bounds set to zeros:
    w.min = rep(0, style.cols)

    # Construct A from the two components a1 and a2
    Amat = t(rbind(a1, a2))

    # Construct b_0
    b0 = c(1, w.min)


    # This is where 'meq' comes in.  The ?solve.QP page says:
    #     meq: the first 'meq' constraints are treated as equality
    #     constraints, all further as inequality constraints (defaults
    #     to 0).
    # I think that the way to interpret this is: if it is set to a value

    #'q' <= n, the ordered constraints numbered less than or equal to
'q' are
    # treated as an equality constraint.  In this case, we only want to
first
    # constraint to be treated as an equality, so that the weights would
sum
    # to exactly 1.  So we set meq = 1.

    # With 'meq' set to 1, the second constraint (a2) is treated as an
    # inequality, so each weight is constrainted to be greater than or
equal
    # to zero.
    optimal <- solve.QP(Dmat, dvec, Amat, bvec=b0, meq=1)

    weights = as.data.frame(optimal$solution)
    rownames(weights) = colnames(R.style)
    colnames(weights) = colnames(R.fund)[1]

    # Calculate metrics for the quality of the fit
    R.fitted = rowSums(R.style*matrix(rep(t(weights),style.rows), 
byrow=T,ncol=style.cols))
    R.nonfactor = R.fund - R.fitted

    R.squared = as.data.frame(1 - (var(R.nonfactor)/var(R.fund)))
    adj.R.squared = as.data.frame(1 - (1 - R.squared)*(style.rows - 
1)/(style.rows - style.cols - 1))

    rownames(R.squared) = "R-squared"
    rownames(adj.R.squared) = "Adj R-squared"

    if(model) 
        # returns the entire output of the model
        result = optimal
    else 
        result = list(weights = weights, R.squared = R.squared,
adj.R.squared 
= adj.R.squared )

    # @todo: retain the labels on the weights
    # @todo: add other values to output, e.g., 
    #    result <- list(weights = optimal$solution, R.squared = , 
tracking.error = )

    return(result)
}

Here's a contrived example.  Using 10 years of monthly index data, I use
the 
Russell 1000 Growth and Value indexes to "explain" the returns of the
S&P.  
My prior is that it should roughly split the S&P index in half along the

value and growth lines given how these indexes are constructed, and with
a 
very high R^2.

Here's the data: 

> head(R.fund)
         SP500.TR 
Jan 1996   0.0340 
Feb 1996   0.0093 
Mar 1996   0.0096 
Apr 1996   0.0147 
May 1996   0.0258 
Jun 1996   0.0038 
> head(R.style)
         Russell.1000.Growth Russell.1000.Value
Jan 1996              0.0335             0.0312
Feb 1996              0.0183             0.0076
Mar 1996              0.0013             0.0170
Apr 1996              0.0263             0.0038
May 1996              0.0349             0.0125
Jun 1996              0.0014             0.0008

And here's the execution:

> style.QPfit(R.fund, R.style)
$weights
                     SP500.TR
Russell.1000.Growth 0.5047724
Russell.1000.Value  0.4952276

$R.squared
           SP500.TR
R-squared 0.9880977

$adj.R.squared
              SP500.TR
Adj R-squared  0.98793

So, this appears to work (at least for this simple example), and
produces a 
high R^2 like we would expect.  To see the whole output of the solve.QP 
model, I can specify 'model' = TRUE:

> style.QPfit(R.fund[,1,drop=FALSE], R.style, model=TRUE)
$solution
[1] 0.5047724 0.4952276

$value
[1] -0.0008888153

$unconstrainted.solution
[1] 0.5040111 0.4815228

$iterations
[1] 2 0

$iact
[1] 1


Make sense?

pcc
-- 
Peter Carl
145 Scottswood Rd
Riverside, IL 60546
312 307 6346
http://www.braverock.com/~peter

_______________________________________________
R-SIG-Finance at stat.math.ethz.ch mailing list
https://stat.ethz.ch/mailman/listinfo/r-sig-finance
-- Subscriber-posting only. 
-- If you want to post, subscribe first.

All e-mail sent to or from this address will be received or otherwise recorded by Vicis Capital, LLC and is subject to archival, monitoring and/or review, by and/or disclosure to, someone other than the recipient.  This message is intended only for the use of the person(s) ("intended recipient") to whom it is addressed.  It may contain information that is privileged and confidential.  If you are not the intended recipient, please contact the sender as soon as possible and delete the message without reading it or making a copy.  Any dissemination, distribution, copying, or other use of this message or any of its content by any person other than the intended recipient is strictly prohibited.  Vicis Capital, LLC only transacts business in states where it is properly registered or notice filed, or excluded or exempted from registration or notice filing requirements.



More information about the R-SIG-Finance mailing list