Professional Documents
Culture Documents
In the interest of reducing grading workload and managing cross-student sharing (cheating), we provide an example of
automating the construction of Markowitz (1959) efficient frontiers. The program automates the entire procedure from
downloading data for a specified number of securities (using either randomly generated securities from a provided list or
specified tickers) to generating graphs of the efficient frontier. Many commercially available optimization programs are
available, but this program is free to use and works within Microsoft Excel (2007-2010), the software package most
professors use when creating optimization assignments. The program is written in Visual Basic for Applications (VBA) but
does not require any knowledge of VBA to use. The program allows easy creation of random portfolios for student
assignments so that cross-student sharing can be minimized without adding an additional grading burden on the professor or
TA. The professor can also be assured that the stocks selected produce the desired type of optimization (actually work) before
using each set of securities. Where students are allowed to select their own portfolios, the program allows professors to
easily check computations and optimizations without having to manually optimize each portfolio. The program can also be
used to demonstrate the similarity of efficient frontiers across different stocks, portfolio sizes, and dates.
INTRODUCTION
Cutbacks in government funding to public universities
has resulted in reductions across many areas including but
certainly not limited to the hiring of new faculty to keep
class sizes small. As class sizes increase, grading of
assignments becomes more problematic both in the amount
of time required and in the possibility of cross-student
sharing (also referred to as free-riding or cheating). In the
case of investments courses, many professors require
students to complete some type of optimization assignment.
With class sizes exceeding 100 students, however, offering
individual students the opportunity to select their own
stocks makes the grading process difficult or prohibitive. As
a result, students are commonly placed in groups, and entire
classes may be assigned the same set of stocks to optimize.
Although this technique significantly reduces the length of
time needed to evaluate the assignments, it also increases
the probability of cross-student or cross-group sharing
making it difficult to determine which students actually
understand the optimization process. One solution to these
issues is to use an automated system designed to generate
Markowitz (1959) optimizations so that every group or
student could be either assigned a separate set of stocks to
optimize, or be allowed to select their own securities. In this
paper we describe a VBA program that can be used to
automate the optimization of portfolios using Solver in
Microsoft Excel. The object is not to teach VBA but to offer
some typical code that can be used to expedite a professors
use of this type of automation.
DESCRIPTION OF THE "FRONT-END" OF THE
PROGRAM
The program is designed for a personal computer (PC)
running a recent version of Excel (2007 or 2010) with the
10
Use the mouse to select the desired start and end dates.
There are several checks built into the dialog box such as
the date cannot be in the future and the start date must be
before the end date. Once again, the user is reminded that a
minimum of 30 observations must be used. Once the dates
are selected and the OK is clicked, a number of observations
check dialog box is displayed:
Figure 9. Download Initiation Selection Box.
11
12
Demonstrating Retrieval of
Financial Information in
XBRL
REFERENCE
Markowitz, H. (1959). Portfolio Selection: Efficient
Diversification of Investments, John Wiley & Sons, New
York City, New York.
David Porter is a Professor of Finance at the University of
Wisconsin-Whitewater.
Robert Stretcher is a Professor of Finance at Sam Houston
State University.
13
If the random value in cell F1 is not zero, then check that the random value is between 2 and 20 and
redimension the arrays for the appropriate number:
If nRandom <> 0 Then
nTickers = nRandom
If nTickers < 2 Or nTickers > 20 Then
Msg = "You must have between 2 and 20 tickers. Either the " & vbCrLf & _
"random number of tickers must be between 2 and 20, " & vbCrLf & _
"or the number of tickers must meet the same criteria."
Ans = MsgBox(Msg, vbOKOnly + vbExclamation)
Exit Sub
End If
ReDim UsedRow(nTickers)
ReDim Symbols(nTickers)
Count the number of security symbols on the "Stocks" sheet and make sure it is greater than the random
number in cell F1:
Sheets("Stocks").Select
Although it is personal preference, it might be useful to let the user know if there was an issue trying to
download a particular ticker:
If ColNum < nTickers Then
Msg = "Yahoo reports that the ticker '" & Symb & "' does not exist," & vbCrLf & _
"or does not exist for the specified dates." & vbCrLf & _
"This may be from an internet error or a ticker symbol error." & vbCrLf & _
"If you are sure that this ticker symbol is correct then you should" & vbCrLf & _
"close Excel then try running the program again."
Ans = MsgBox(Msg, vbOKOnly + vbExclamation)
Sheets("Explanation").Select
Exit Sub
End If
Call the Returns subroutine (to compute the returns). Again if there is an error, exit the program:
Call Returns
If Error Then Exit Sub
Create named ranges for the returns for each of the stocks:
For k = 1 To nTickers
Sheets("Returns").Range("A2").Offset(0, k - 1).Resize(MinObs).Name = LegalName(k)
Next k
Call each of the subroutines need to compute and chart the optimization:
Call GetStats
Call ModelSheet_Setup
Call ChartDataSheet_Setup
If Error Then Exit Sub
Call UpdateChart
The main program finishes by turning on the display alerts and screen updating, then clears the status
bar and ends the subroutine:
Application.DisplayAlerts = True
Application.ScreenUpdating = True
Application.StatusBar = Empty
End Sub
The main program calls the function SheetExists and returns TRUE if a worksheet exists in the active
workbook. As noted above, if the sheet exists, we delete it so we always start from a clean slate:
Public Function SheetExists(sname) As Boolean
Dim x As Object
On Error Resume Next
Set x = ActiveWorkbook.Sheets(sname)
If Err.Number = 0 Then SheetExists = True _
Else SheetExists = False
End Function
The web query to download data from Yahoo can be found in Bill Jelens book referenced above. We
place the web query in the subroutine GetYahooData. The subroutine begins with variable declarations
and then opens the form frmGetDates. This form requests the start and end dates for the data and does
a few checks such as making sure the end date is not before the begin date and the end date is not in the
future. You could just as easily put the dates in cells on the Explanation sheet but the form is a bit more
professional:
Sub GetYahooData(ColNum As Long)
Dim LastRow As Long
Dim FreqS As String
Show (display) the form GetDates to the user so that they can select the start and end dates for the data:
frmGetDates.Show
If Error Then Exit Sub
For simplicity, we only use monthly data in this example. We also need to adjust the information passed
from the form GetDates so it matches what is expected by Yahoo (January is month 0 and December
month 11):
FreqS = "m"
StartMonth = StartMonth - 1
EndMonth = EndMonth - 1
Then for each ticker, we request the selected data from Yahoo. The counter ColNum is set to zero for
starters:
ColNum = 0
For i = 1 To nTickers
Sheets("Explanation").Select
Range("AA1:AG65000").Clear
Symb = Symbols(i)
Application.StatusBar = "Attempting to Download " & Symb & " ..."
With ActiveSheet.QueryTables.Add(Connection:= _
"URL;http://ichart.finance.yahoo.com/table.csv?s=" & Symb & _
"&a=" & StartMonth & "&b=" & StartDay & "&c=" & StartYear & _
"&d=" & EndMonth & "&e=" & EndDay & "&f=" & EndYear & "&g=" & FreqS _
, Destination:=Range("AA1"))
.RefreshStyle = xlOverwriteCells
.RowNumbers = False
.FillAdjacentFormulas = False
.BackgroundQuery = True
.SavePassword = False
.SaveData = True
.AdjustColumnWidth = False
.RefreshPeriod = 0
.Refresh BackgroundQuery:=False
.WebSelectionType = xlSpecifiedTables
.WebTables = "19"
Most of the options, RefreshStyle, etc., are not needed for the query to work but are usually kept for
clarity. We have run the query with only the last three options. A complete list of options is available at the
Microsoft Developer Library: http://msdn.microsoft.com/enus/library/microsoft.office.interop.excel.querytable_properties.aspx. Note that the WebTables page of 19
requires either some good guessing or an examination of the source code at Yahoo. There is no direct
way to determine the correct web table to download so if Yahoo moves where the table is located, this
number will need to be changed.
The data is downloaded as comma delimited so it must be parsed into columns. There are many ways to
do this but the example below is relatively efficient:
Sheets("Explanation").Range("AA1").Select
Range(Selection, Selection.End(xlDown)).Select
Selection.TextToColumns Destination:=Range("AA1"), DataType:=xlDelimited, _
TextQualifier:=xlDoubleQuote, ConsecutiveDelimiter:=True, Tab:=False, _
Semicolon:=False, Comma:=True, Space:=False, Other:=False, FieldInfo:= _
Array(Array(1, 1), Array(2, 1), Array(3, 1), Array(4, 1), Array(5, 1), Array(6, 1), _
Array(7, 1)), TrailingMinusNumbers:=True
End With
If the query was successful, increment the counter:
ColNum = ColNum + 1
Determine how much data was downloaded and copy the closing prices to the prices sheet:
LastRow = Sheets("Explanation").Cells(Rows.Count, 27).End(xlUp).Row
Sheets("Prices").Range("A1").Offset(0, ColNum) = Symbols(i)
Sheets("Explanation").Range("AG1:AG" & LastRow).Copy _
Sheets("Prices").Range("A2").Offset(0, ColNum)
Next i
After downloading the data for each of the tickers, copy the dates to the Prices sheet, set the column
width and set the correct date format:
Sheets("Explanation").Range("AA1:AA" & LastRow).Copy
Sheets("Prices").Range("A2").PasteSpecial (xlPasteValues)
Sheets("Prices").Columns("A:A").ColumnWidth = 12.86
Selection.QueryTable.Delete
Set the correct date format on worksheet "Prices":
With Sheets("Prices")
.Activate
Range("A1").Select
If FreqS = "m" Then .Range("A3:A" & LastRow).NumberFormat = "[$-409]mmm-yy;@"
Add some other formatting:
Range("B3").Resize(LastRow, nTickers).NumberFormat = "0.00"
With Range("A1").Resize(2, nTickers + 1)
.Interior.Color = 13209
.Font.ThemeColor = xlThemeColorDark1
.Font.Bold = True
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
End With
Range("A1").Interior.ColorIndex = xlNone
With Range("A2:A" & LastRow)
.Font.ThemeColor = xlThemeColorDark1
.Interior.Color = 13209
.Font.Bold = True
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
End With
End With
To complete the subroutine, clear the data range and turn off copy mode:
Sheets("Explanation").Range("AA1:AG65500").Clear
Application.CutCopyMode = False
End Sub
The Returns subroutine computes returns from the prices downloaded from Yahoo. It starts with an
additional variable declaration and sets the Symb variable to null (blank).
Sub Returns()
Dim LastRow As Long
Symb = ""
Determine the minimum number of observations for each ticker:
For i = 1 To nTickers
LastRow = Sheets("Prices").Cells(Rows.Count, i + 1).End(xlUp).Row - 2
If i = 1 Then
MinObs = LastRow
Else
If LastRow < MinObs Then
MinObs = LastRow
Symb = Symbols(i)
End If
End If
Next i
Returns have one less observation than prices:
MinObs = MinObs - 1
If a symbol has fewer observations than the others, report that symbol to the user:
If Not Symb = "" Then
Msg = "Minimum number of observations is " & MinObs & " for symbol " & Symb
Ans = MsgBox(Msg, vbOKOnly)
End If
If a symbol has fewer than 30 observations, report that to the user, set the Error to true, delete the
BasicStats worksheet and exit the subroutine:
If MinObs < 30 Then
Msg = "Minimum number of observations is less than 30" & Chr(10) & _
"Terminating the application."
Ans = MsgBox(Msg, vbOKOnly)
Error = True
If SheetExists("BasicStats") Then Sheets("BasicStats").Delete
Sheets("Explanation").Select
Exit Sub
End If
On the Returns sheet, insert the ticker symbols for the titles:
Sheets("Returns").Select
For i = 1 To nTickers
Range("A1").Offset(0, i - 1) = Symbols(i)
Next i
Compute the returns:
Range("A2") = "=(Prices!B3-Prices!B4)/Prices!B4*100"
Range("A2").AutoFill Destination:=Range("A2").Resize(, nTickers), Type:=xlFillDefault
Range("A2").Resize(, nTickers).AutoFill Destination:=Range("A2").Resize(MinObs, nTickers),
Type:=xlFillDefault
Add some formatting:
Range("A2").Resize(MinObs, nTickers).NumberFormat = "0.0000"
Set FormatRange = ActiveSheet.UsedRange.SpecialCells(xlCellTypeConstants, 2)
With FormatRange
.Interior.Color = 13209
.Font.ThemeColor = xlThemeColorDark1
.Font.Bold = True
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
End With
End Sub
The GetStats subroutine computes the basic statistics from the returns.
Sub GetStats()
The subroutine starts by selecting the BasicStats worksheet, then adds some formatting and the titles:
Sheets("BasicStats").Select
Range("A:A").ColumnWidth = 12
Range("A1:D1").MergeCells = True
Range("A1").Value = "Summary Measures for Stock Returns"
Range("A3").Value = "Stocks"
Range("A4").Value = "Means"
Range("A5").Value = "Std. Dev."
Range("A6").Value = "Min"
Range("A7").Value = "Max"
Range("A8").Value = "Range"
Range("A10").Value = "Correlations"
For each ticker, enter the formulas for the average, standard deviation, minimum, maximum and range of
returns:
For i = 1 To nTickers
Range("A3").Offset(0, i).Value = Symbols(i)
Range("A4").Offset(0, i).Formula = "=Average(" & LegalName(i) & ")"
Range("A5").Offset(0, i).Formula = "=Stdev(" & LegalName(i) & ")"
Range("A6").Offset(0, i).Formula = "=Min(" & LegalName(i) & ")"
Range("A7").Offset(0, i).Formula = "=Max(" & LegalName(i) & ")"
Range("A8").Offset(0, i).FormulaR1C1 = "=R[-1]C-R[-2]C"
Next
Insert the titles (symbols) for the correlation table:
With Range("A10")
For i = 1 To nTickers
With .Offset(i, 0)
.Value = Symbols(i)
End With
With .Offset(0, i)
.Value = Symbols(i)
End With
Next
Compute the correlations:
For i = 1 To nTickers
For j = 1 To nTickers
.Offset(i, j).Formula = "=Correl(" & LegalName(i) & "," & LegalName(j) & ")"
Next
Next
End With
Add some formatting:
Range("B4").Resize(nTickers + 7, nTickers).NumberFormat = "0.0000"
Set FormatRange = ActiveSheet.UsedRange.SpecialCells(xlCellTypeConstants, 2)
With FormatRange
.Interior.Color = 13209
.Font.ThemeColor = xlThemeColorDark1
.Font.Bold = True
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
End With
End Sub
The ModelSheet_Setup subroutine sets up the model worksheet with the necessary data to optimize the
portfolio and calls the subroutine "Solver" to perform the optimization:
Sub ModelSheet_Setup()
Dim modelSheet As Worksheet
Set modelSheet = Worksheets("Model")
Application.StatusBar = "Calculating Covariances"
With modelSheet
.Activate
Add some formatting and setup the titles:
.Columns("A:A").ColumnWidth = 12
.Columns("B:F").ColumnWidth = 9.29
.Range("A1:C1").MergeCells = True
.Range("A1").Value = "Portfolio Selection Model"
With .Range("A3")
.Value = "Stock"
.Offset(1, 0).Value = "Weights"
.Offset(2, 0).Value = "Means"
For i = 1 To nTickers
.Offset(0, i).Value = Symbols(i)
Enter the naive weights as a starting point for the optimizations:
.Offset(1, i).Value = 1 / nTickers
Enter the average returns as the expected returns:
.Offset(2, i).Formula = "=Average(" & LegalName(i) & ")"
Next
End With
Create named ranges for the weights and expected returns:
With .Range("A4")
Range(.Offset(0, 1), .Offset(0, 1).End(xlToRight)).Name = "Weights"
Range(.Offset(1, 1), .Offset(1, 1).End(xlToRight)).Name = "Means"
End With
Sum the weights, add a title, and create a named range:
With .Range("A3").Offset(0, nTickers + 1)
.Value = "Sum"
.Offset(1, 0).Name = "SumWeights"
.Offset(1, 0).Formula = "=Sum(Weights)"
End With
Add the titles for the covariance table:
With .Range("A7")
.Value = "Covariances"
For k = 1 To nTickers
.Offset(k, 0).Value = Symbols(k)
.Offset(0, k).Value = Symbols(k)
Next k
Compute the covariances:
For i = 1 To nTickers
For j = 1 To nTickers
.Offset(i, j).Formula = "=Covar(" & LegalName(i) & "," & LegalName(j) & ")"
Next
Next
Range(.Offset(1, 1), .Offset(nTickers, nTickers)).Name = "Covar"
End With
With .Range("A7").Offset(nTickers + 2, 0)
Add the title for the expected return on the portfolio and enter the formula to compute its value:
.Value = "E(Rp)"
.Offset(0, 1).Name = "ERp"
.Offset(0, 1).Formula = "=Sumproduct(Weights,Means)"
10
Add the title for the target cell and set its starting value to zero. The target cell is used as the target for the
optimization
.Offset(0, 3).Value = "Target"
.Offset(0, 4).Value = 0
.Offset(0, 4).Name = "Target"
Add the title for the variance of the portfolio and enter the formula to compute its value:
.Offset(2, 0).Value = "Var(Rp)"
With .Offset(2, 1)
.FormulaArray = "=MMult(Weights,MMult(Covar,Transpose(Weights)))"
.Name = "VarRp"
End With
Add the title for the standard deviation of the portfolio and enter the formula to compute its value:
.Offset(2, 3).Value = "Sigma(Rp)"
With .Offset(2, 4)
.Formula = "=Sqrt(VarRp)"
.Name = "SigmaP"
End With
End With
End With
Add some formatting and end the subroutine:
Range("B4").Resize(nTickers + 8, nTickers + 1).NumberFormat = "0.0000"
Set FormatRange = ActiveSheet.UsedRange.SpecialCells(xlCellTypeConstants, 2)
With FormatRange
.Interior.Color = 13209
.Font.ThemeColor = xlThemeColorDark1
.Font.Bold = True
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
End With
End Sub
The subroutine ChartDataSheet is used to call RunSolver and record the solutions for each optimization
on the ChartData sheet for charting. It starts with some additional variable declarations:
Sub ChartDataSheet_Setup()
Dim CDSheet As Worksheet
Dim nErr As Integer
Dim MVPRet As Double, Temp1 As Double, Temp2 As Double
Dim SameReturn As Boolean
Dim T1 As Double, T2 As Double, T3 As Double
Set CDSheet = Worksheets("ChartData")
Add some formatting and titles:
With CDSheet
.Select
.Cells.ClearContents
11
.Range("A:Z").Font.Bold = False
.Columns("A:A").ColumnWidth = 15.14
.Columns("B:B").ColumnWidth = 12.14
.Columns("C:C").ColumnWidth = 10.86
.Range("A1").Value = "Efficient Frontier"
.Range("B2").Value = "Port. Std.Dev."
.Range("C2").Value = "Port. Return"
.Range("E1").Value = "Optimal Portfolio Weights"
.Range(Cells(1, 5), Cells(1, nTickers + 4)).MergeCells = True
With .Range("D2")
For k = 1 To nTickers
.Offset(0, k).Value = Symbols(k)
Next
End With
End With
Find the highest return and the matching standard deviation - the endpoint of the efficient frontier:
With Sheets("BasicStats")
.Activate
For i = 1 To nTickers
If i = 1 Then
maxReturn = Range("A4").Offset(0, i).Value
maxStdDev = Range("A5").Offset(0, i).Value
maxTicker = Range("A3").Offset(0, i).Value
Else
If maxReturn < Range("A4").Offset(0, i).Value Then
maxReturn = Range("A4").Offset(0, i).Value
maxStdDev = Range("A5").Offset(0, i).Value
maxTicker = Range("A3").Offset(0, i).Value
End If
End If
Next i
End With
Number of times to run Solver to get a "nice" graph:
nRuns = 15
Compute the minimum variance portfolio:
Range("Target") = 0
Call RunSolver
Copy the expected return, standard deviation and weights to the appropriate cells:
With CDSheet.Range("A" & nRuns + 3)
.Value = "M-V Portfolio"
.Offset(0, 1).Value = Range("SigmaP").Value
.Offset(0, 2).Value = Range("ERp").Value
.Offset(0, 2).Name = "MVPReturn"
For k = 4 To nTickers + 3
.Offset(0, k).Value = Range("Weights").Cells(k - 3).Value
Next
End With
MVPRet = Range("MVPReturn").Value
12
MVPStdDev = Range("SigmaP").Value
Use an exponential function to select more points near the minimum variance point. The divisor "T1"
should scale between 0 and 1. The 1.5 is arbitrary and is used to get the graph to look appropriate:
T1 = 1 / Exp(1 / 1.5)
Run Solver "nRuns" times and record the results. Solver can make errors, so set the error counter to zero
and increment with each error:
For i = nRuns - 1 To 1 Step -1
nErr = 0
Set the target cell for each optimization by altering the standard deviation between the minimum variance
point and the maximum standard deviation:
T2 = 1 / Exp((i + 1) / 1.5)
T3 = T2 / T1
Range("Target") = MVPStdDev + T3 * (maxStdDev - MVPStdDev)
Run Solver with the new target cell. Since Solver can make errors, check for an error after each run and
re-run if necessary:
Do
Call RunSolver
With CDSheet.Range("B4")
.Offset(i - 1, 0).Value = Range("SigmaP").Value
.Offset(i - 1, 1).Value = Range("ERp").Value
End With
Make sure Solver did not get stuck and return an inferior point:
SameReturn = False
Temp1 = Round(CDSheet.Range("C4").Offset(i - 1, 0).Value, 4)
Temp2 = Round(CDSheet.Range("C4").Offset(i, 0).Value, 4)
If Temp1 <= Temp2 Then
SameReturn = True
Sheets("Model").Range("B4").Resize(, nTickers).Value = 0
nErr = nErr + 1
If nErr = 1 Then Sheets("Model").Range("B4") = 1
If nErr = 2 Then Sheets("Model").Range("C4") = 1
If nErr = 3 Then Sheets("Model").Range("D4") = 1
If nErr = 4 Then Sheets("Model").Range("E4") = 1
If nErr = 5 Then Sheets("Model").Range("F4") = 1
If nErr = 6 Then Sheets("Model").Range("B4").Resize(, nTickers).Value = 1/nTickers/2
If nErr = 7 Then Sheets("Model").Range("B4").Resize(, nTickers).Value = 1/nTickers*2
If nErr > 7 Then
Error = True
MsgBox "Error in Solver Solution"
Exit Sub
End If
End If
If the point is an inferior point, run Solver again with the same target cell:
Loop While Same Return
13
Write the optimal weights for each stock for each for each run:
With CDSheet.Range("D4")
For k = 1 To nTickers
.Offset(i - 1, k).Value = Range("Weights").Cells(k).Value
Next
End With
Next i
The maximum return is the last point on the efficient frontier:
CDSheet.Range("B3").Value = maxStdDev
CDSheet.Range("C3").Value = maxReturn
With CDSheet.Range("D2")
For k = 1 To nTickers
If .Offset(0, k).Value = maxTicker Then
.Offset(1, k).Value = 1
Else
.Offset(1, k).Value = 0
End If
Next
End With
Add some formatting and end the subroutine:
CDSheet.Activate
With Range("A1").Resize(nRuns + 3, nTickers + 4)
.NumberFormat = "0.0000"
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
End With
Set FormatRange = ActiveSheet.UsedRange.SpecialCells(xlCellTypeConstants, 2)
With FormatRange
.Interior.Color = 13209
.Font.ThemeColor = xlThemeColorDark1
.Font.Bold = True
End With
End Sub
The subroutine RunSolver runs the Solver optimization routine:
Sub RunSolver()
Sheets("Model").Select
We include the code to run Solver directly but there can be conflicts if Solver is not correctly installed and
referenced. Application.Run does not have as many conflict issues as just using Solver but it runs slower
and is not as intuitive since the options are not referenced with words. For a good description of this issue
see http://peltiertech.com/Excel/SolverVBA.html. We start by resetting Solver:
SolverReset
The first constraint is that the weights must sum to 1. "Relation:=2" refers to "=":
SolverAdd CellRef:=Range("SumWeights"), _
14
Relation:=2, _
FormulaText:=1
The second constraint is that the portfolio standard deviation must equal the target cell:
SolverAdd CellRef:=Range("SigmaP"), _
Relation:=2, _
FormulaText:=Range("Target")
The maximization function will vary depending on the computation. In this example, we maximize the
portfolio expected return by changing the weights. A "MaxMinVal=1" refers to maximizing:
SolverOk SetCell:=Range("ERp"), _
MaxMinVal:=1, _
ByChange:=Range("Weights")
In this example we run Solver with all the options set to the default, (there is no Solver options line). Since
the Solver option AssumeNonNeg is set to True by default, it is not necessary to add the constraint that
the weights are >= 0 when short selling is not allowed. A complete list of options along with their default
values is available at the Microsoft Developer Library, http://msdn.microsoft.com/enus/library/office/ff195446.aspx. You can also find references to the other Solver functions on the same
page.
Run the optimization. "UserFinish:=True" completes the optimization without asking if the user want to
keep the optimization:
SolverSolve UserFinish:=True
Reset the status bar to empty and end the subroutine:
Application.StatusBar = Empty
End Sub
The UpdateChart subroutine plots the chart data. It also starts with the declaration of a few variables
specific to this subroutine:
Sub UpdateChart()
Dim minX As Single, maxX As Single
Dim minY As Single, maxY As Single
Dim xLength As Single, yLength As Single
Application.StatusBar = "Updating Chart"
Activate the chart data sheet, select the chart type, data range, location, style, labels format, axis
captions:
Sheets("ChartData").Activate
ActiveSheet.Shapes.AddChart.Select
ActiveChart.ChartType = xlXYScatterSmooth
ActiveChart.SetSourceData Source:=Range("ChartData!$B$3:$C$" & nRuns + 3)
ActiveChart.Location Where:=xlLocationAsNewSheet
ActiveChart.ChartStyle = 36
ActiveChart.ClearToMatchStyle
ActiveChart.SetElement msoElementLegendNone
15
ActiveChart.Location Where:=xlLocationAsNewSheet
ActiveChart.Axes(xlValue).TickLabels.NumberFormat = "0.00"
ActiveChart.Axes(xlCategory).TickLabels.NumberFormat = "0.00"
ActiveChart.SetElement (msoElementPrimaryCategoryAxisTitleAdjacentToAxis)
ActiveChart.Axes(xlCategory, xlPrimary).AxisTitle.Caption = _
"Standard Deviation of Portfolio Returns %"
ActiveChart.SetElement (msoElementPrimaryValueAxisTitleRotated)
ActiveChart.Axes(xlValue, xlPrimary).AxisTitle.Caption = "E(Rp) %"
ActiveChart.Name = "Graph"
Sheets("ChartData").Move Before:=Sheets("Graph")
Update the chart settings to improve chart spacing and end the subroutine:
minX = Application.WorksheetFunction.Min(Range("B3:B" & 18))
maxX = Application.WorksheetFunction.Max(Range("B3:B" & 18))
minY = Application.WorksheetFunction.Min(Range("C3:C" & 18))
maxY = Application.WorksheetFunction.Max(Range("C3:C" & 18))
xLength = maxX - minX
yLength = maxY - minY
Sheets("Graph").Select
With ActiveChart
With .Axes(xlCategory)
.MinimumScale = minX - 0.1 * xLength
.MaximumScale = maxX + 0.1 * xLength
End With
With .Axes(xlValue)
.MinimumScale = minY - 0.1 * yLength
.MaximumScale = maxY + 0.1 * yLength
End With
End With
Application.StatusBar = Empty
End Sub
16