How to use XIRR from VBA Script - vba

I have an Excel spreadsheet that shows deposits to a bank account, plus periodically I check the balance and put that in the spreadsheet. I'd like to make a macro that calculates the to-date equivalent return (i.e., if I have this sequence of deposits, and then the account balance is $X, then the equivalent rate of interest is Y%). For example, I might have:
1-Jan-2010 $100
22-Apr-2011 $200
14-Feb-2012 $310
28-Aug-2013 $500
9-May-2014 $790 # account lost value!
I can get the dates and deposits into a function (along with the ending balance) but I can't get XIRR to work right. I know this is ugly VBA (not a language I know) but here's what I have:
Public Function MyXIRR(Dates As Range, Trans As Range, Balance As Double)
Dim i As Integer
Dim x As Double
Dim dateArray() As Date
Dim valArray() As Double
ReDim dateArray(Dates.Count + 1)
ReDim valArray(Trans.Count + 1)
For i = 1 To Dates.Count
dateArray(i - 1) = Dates.Item(i)
Next i
For i = 1 To Trans.Count
valArray(i - 1) = Trans.Item(i)
Next i
dateArray(Dates.Count) = DateAdd("d", 1, Dates.Item(Dates.Count))
valArray(Trans.Count) = -1 * Balance
For i = 0 To Dates.Count
dateArray(i) = Format(dateArray(i), "dd/mm/yyyy")
Next i
MsgBox ("Hello")
x = Application.Xirr(valArray, dateArray, 0.01)
MsgBox (x)
' MyXIRR = Dates.Count * 100 + Trans.Count
' MyXIRR = dateArray(6)
MyXIRR = valArray(3)
End Function
(The comments and MsgBox at the end are my various attempts to figure out what's going on; ideally, I'd just have a formula in a cell: =MyXIRR( A2:A6, B2:B6, C6 ) which would return the equivalent interest rate.)
I think what's happening is that when I call Application.Xirr, I'm getting a #VALUE exception (error?) But I don't know how to debug it, examine the arrays passed to Xirr, or figure out what's wrong.

I don't know whether this is The Answer, or whether I've merely swept the problem under a different rug, but I was able to get my function to work reliably by converting the dateArray to an array of Strings, where each String is a parseable date.
Public Function MyXIRR(Dates As Range, Trans As Range, Balance As Double)
Dim i As Integer
Dim x As Double
Dim dateArray() As Date
Dim dateStrings() As String
Dim valArray() As Double
ReDim dateArray(Dates.Count)
ReDim valArray(Trans.Count)
ReDim dateStrings(Dates.Count)
For i = 1 To Dates.Count
dateArray(i - 1) = Dates.Item(i).Value
Next i
For i = 1 To Trans.Count
valArray(i - 1) = Trans.Item(i).Value
Next i
dateArray(Dates.Count) = DateAdd("d", 1, Dates.Item(Dates.Count))
valArray(Trans.Count) = -1 * Balance
For i = 0 To Dates.Count
dateStrings(i) = Format(dateArray(i), "mm/dd/yyyy")
Next i
MyXIRR = Application.WorksheetFunction.Xirr(valArray, dateStrings, 0.01)
End Function

Related

How to create an array of dates spaced 2 weeks apart in VBA?

I have seen examples on how to create an array of all the dates, or a date every month between a start date and end date here. However, I am trying to create an array of dates every 2 weeks specifically between a start and end date.
Ex. if startdate = 7/18/2021 and enddate = 12/28/2025, I want an array that is:
array = {7/18/2021, 8/1/2021, 8/15/2021, ..., 12/28/2025}
How exactly can I do this? I tried using a recorded macro but it only uses the xlFillDefault and doesn't actually specify the algorithm used.
Sub get2weekdates()
'
' get2weekdates Macro
'
'
ActiveCell.FormulaR1C1 = "7/18/2021"
Range("D6").Select
ActiveCell.FormulaR1C1 = "8/1/2021"
Range("D5:D6").Select
Selection.AutoFill Destination:=Range("D5:D121"), Type:=xlFillDefault
Range("D5:D121").Select
End Sub
The idea of the Function ArrayWithDates() is to take 2 parameters - the first day and the size as an optional one (in the example, 14 is 2 weeks, you may write 21 for 3 weeks or 20 for 2 weeks and 6 days).
Once it gets these, it enlarges the array based on the size with this line - ReDim result(size - 1). As the 0th element of the array is known, it is assigned outside the loop - result(0) = firstDay.
Then, in the loop, the other elements are assigned, and each one is the next day, using DateAdd():
Public Sub TestMe()
Dim i As Long
Dim myArray As Variant
myArray = ArrayWithDates(#7/18/2021#)
For i = LBound(myArray) To UBound(myArray)
Debug.Print i; myArray(i)
Next i
End Sub
Public Function ArrayWithDates(firstDay As Date, Optional size As Long = 14) As Variant
Dim i As Long
If size < 1 Then
ArrayWithDates = Array()
Exit Function
End If
ReDim result(size)
result(0) = firstDay
For i = 1 + LBound(result) To UBound(result)
result(i) = DateAdd("D", 1, result(i - 1))
Next i
ArrayWithDates = result
End Function
The result looks as expected:
Dates are just doubles that look like a date. we just need to loop and add 14 to the start till we hit the end date:
Sub get2weekdates()
With ActiveSheet
Dim strtdt As Double
strtdt = .Range("C1").Value2
Dim eddt As Double
eddt = .Range("c2").Value2
Dim nmDts As Long
nmDts = (eddt - strtdt) / 14 + 1
Dim otArray As Variant
ReDim otArray(1 To nmDts, 1 To 1)
Dim i As Long
For i = 1 To nmDts
otArray(i, 1) = strtdt + ((i - 1) * 14)
Next i
.Range("D5").Resize(nmDts, 1).Value = otArray
.Range("D5").Resize(nmDts, 1).NumberFormat = "mm/dd/yyyy"
End With
End Sub
But as stated by #pdtcaskey with Office 365 this can be a simple formula:
=SEQUENCE((C2 - C1)/14 + 1,, C1, 14)

Making Selected Range into an Array and doing stuff with it

I'm trying to teach myself VBA writing some little things. I'm trying to make something that allows you to select some data and then calculates the mean and variance. My code is as follows :
Sub VarianceCalculator()
Dim k As Integer
Dim SelectedData As Range
Dim SelectedDataArray() As Variant
Dim Var As Double
Dim Mu As Double
On Error Resume Next
Set SelectedData = Application.InputBox("Select a range of data to be
calculated", Default:=Selection.Address, Type:=8)
On Error GoTo 0
SelectedDataArray = Range(SelectedData.Address)
k = UBound(SelectedDataArray)
Call VarianceCalculatorWithArray(SelectedDataArray, k)
MsgBox ("The selected data has variance " & Var & " and has mean " & Mu)
End Sub
Sub VarianceCalculatorWithArray(Data() As Variant, k As Integer)
Dim Var As Double
Dim Mu As Double
Dim j As Integer
Dim i As Integer
ReDim Data(k) As Variant
Mu = 0
Var = 0
For j = 0 To k
Mu = Mu + (Data(j)) / (k + 1)
Next j
For i = 0 To k
Var = Var + ((Data(i) - Mu) ^ (2)) / (k + 1)
Next i
End Sub
I think the error is that somehow the data is not getting transferred into the array but I can't find a solution to this.
Thanks!!
There are two major problems:
(1) If you want to pass the variables Var and Mu from one procedure to another you'll have to declare them as public variables before the first procedure. The alternative is to setup VarianceCalculatorWithArray as a function.
(2) The array Data() is 2D as a range consists of rows and columns. So, if you want to use an element from this array, you'll have to address it as Data(1, 1). Also note, that this range array starts with row 1 and column 1. Therefore your for...next statements should start with 1 and not with 0.
Note, that you can always set a breakpoint in order to check if data is transferred into the array.

Consistently returning the entire array in a VBA UDF?

I've written the following simple VBA function to calculate linear progression between two amounts for some amount of intervals and return the progression as an array:
Public Function applyLinear(startAmount As Double, stopAmount As Double, Intervals As Integer)
Dim diff As Double
Dim resultArr() As Double
Dim counter As Integer
diff = stopAmount - startAmount
ReDim resultArr(Intervals, 0)
resultArr(0, 0) = startAmount
For counter = 1 To (Intervals)
resultArr(counter, 0) = resultArr(counter - 1, 0) + diff / Intervals
Next ' counter
counter = 0
applyLinear = resultArr
End Function
Unfortunately, what actually gets outputted to the worksheet depends entirely on how many cells the above formula is called from. (This means that it becomes laughably easy for a user to apply the function incorrectly!)
How can I ensure that the entire array is outputted to the worksheet under all circumstances?
Edit: Or rather, more specifically, how can I get VBA to output the entire array when calling the above UDF from a single cell?
Edit 2: Some more clarification. The function does work as intended if "intervals+1" rows are used when calling it. A problem arises however, if (for example) the user accidentally calls the function from 4 rows when intervals is > 4. In this case only the first four elements of resultArr are outputted to the worksheet, this is an issue because it doesn't represent the full linear progression.
While looking into how to do what I suggested in the comment above, I found some interesting info on the Application.Caller function, which allows you to access the properties of the cell a UDF is entered in. See here: Excel cell from which a Function is called
Here's one way to do this, based on a little bit of further guidance from cpearson [http://www.cpearson.com/excel/returningarraysfromvba.aspx]
Public Function applyLinear(startAmount As Double, stopAmount As Double, Intervals As Integer)
Dim diff As Double
Dim resultArr() As Double
Dim counter As Integer
ReDim resultArr(Intervals, 0)
If Application.Caller.Rows.Count <> Intervals + 1 Then
applyLinear = "ERROR - MUST ENTER THIS FORMULA IN " & Intervals + 1 & " CELLS"
Else
diff = stopAmount - startAmount
resultArr(0, 0) = startAmount
For counter = 1 To (Intervals)
resultArr(counter, 0) = resultArr(counter - 1, 0) + diff / Intervals
TotalityCheck = TotalityCheck + resultArr(counter, 0) 'builds in by the end of the loop the total array value
Next ' counter
applyLinear = resultArr
End If
End Function
Although, now that you have the use of the Application.Caller function, you could perhaps do this a little more simply, by removing the need for the user to enter the number of Intervals - instead, define the number of intervals as the application.caller.rows - 1, as follows:
Public Function applyLinear(startAmount As Double, stopAmount As Double)
Dim diff As Double
Dim resultArr() As Double
Dim counter As Integer
Dim Intervals As Integer
ReDim resultArr(Intervals, 0)
Intervals = Application.Caller.Rows.Count - 1
diff = stopAmount - startAmount
resultArr(0, 0) = startAmount
For counter = 1 To (Intervals)
resultArr(counter, 0) = resultArr(counter - 1, 0) + diff / Intervals
TotalityCheck = TotalityCheck + resultArr(counter, 0) 'builds in by the end of the loop the total array value
Next ' counter
applyLinear = resultArr
End Function

Excel crashes when macro is run from VBA interface, but works when using a button

I have a macro that takes different ranges, performs calculations with values in them, and then copies the value to a cell to be available for use to formulas in the worksheet. I want this macro to run when values in certain ranges are modified. I use this same code in different worksheets, where I initially placed an ActiveX button.
The problem is that when I put the macro in a worksheet_calculate or Worksheet_SelectionChange Excel crashes alltogether. At first I assumed I was causing infinite iterations, but after using EventEnable = FALSE and EnableCalculations = FALSE to avoid that behavior Excel still crashed. I erased the macros in SelectionChange and Calculate, and I further inspected the issue and found that when the macro is run from the VBA interface Excel also crashes, but it works with the ActiveX button in the worksheets that makes reference to the macro.
I just can't seem to find what is wrong with the code, and why it runs perfectly when accessed through the button but crashes when run from the VBA interface.
Here is the code, please help me find what is wrong, or what I can I try so that it runs from VBA because calling it or running it from the interface will crash Excel as of now.
Sub performCalculations()
Dim revenue As Double
Dim expenses As Double
Dim i As Integer
Dim j As Integer
Dim count As Integer
Dim aConst As Double
Dim tempConst As Long
Dim bla As Range
Dim curve As Range
Dim price As Range
Dim finalOut As Range
Dim a As Double
Dim tempString As String
Set bla = ActiveWorkbook.ActiveSheet.Range("D16:D75")
Set curve = ActiveWorkbook.ActiveSheet.Range("G2:WH11")
Set price = ActiveWorkbook.ActiveSheet.Range("G162:WH164")
Set finalOut = ActiveWorkbook.ActiveSheet.Range("G83:WH141")
tempString = ActiveWorkbook.ActiveSheet.Name
tempString = Left(tempString, InStr(1, tempString, " ", vbTextCompare) - 1)
a = ActiveWorkbook.Sheets(tempString).Cells(10, 8).Value
aConst = a / 1000
For i = 1 To bla.Rows.count
count = 1
For j = 1 To curve.Columns.count
If j < i Then
finalOut.Cells(i, j) = 0
Else
If bla.Cells(i, 1) = 0 Then
finalOut.Cells(i, j) = 0
Else
tempConst = curve.Cells(2, count) * aConst * price.Cells(2, j)
revenue = bla.Cells(i, 1) * (curve.Cells(1, count) * price.Cells(1, j) + curve.Cells(3, count) * price.Cells(3, j))
expenses = bla.Cells(i, 1) * curve.Cells(4, count) + bla.Cells(i, 1) * curve.Cells(5, count) + bla.Cells(i, 1) * _
curve.Cells(6, count) + bla.Cells(i, 1) * curve.Cells(7, count) + bla.Cells(i, 1) * curve.Cells(10, count)
expenses = expenses + revenue * curve.Cells(9, count) + tempConst * curve.Cells(8, count)
revenue = revenue + tempConst
finalOut(i, j) = revenue - expenses
count = count + 1
End If
If i > 1 Then
finalOut.Cells(i, j) = finalOut.Cells(i, j) + finalOut.Cells(i - 1, j)
End If
End If
Next j
Next i
End Sub
I am running Excel 2013, and Windows 7 Professional
I am not sure if this is the final answer for your problem but one thing could be affecting you macro, the use of Active Objects such as ActiveWorkbook, ActiveSheet and so on.
My advice for you is to reference the objects you have to use so that if your excel file is not selected or if you select the wrong worksheet, the code still works. To do that, please check the example below:
Dim workingSheet as Worksheet
Dim auxWorksheet as Worksheet
Set workingSheet = ThisWorkbook.Worksheets("The name of the worksheet here")
Set auxWorksheet = ThisWorkbook.Worksheets(tempString)
Set bla = workingSheet.Range("D16:D75")
Set curve = workingSheet.Range("G2:WH11")
Set price = workingSheet.Range("G162:WH164")
Set finalOut = workingSheet.Range("G83:WH141")
tempString = workingSheet.Name
a = auxWorksheet.Cells(10, 8).Value
Please give it a try and give some feedback so that if it does not work, I can keep trying to help you.

VBA Excel Function Error Runtimeerror 13

I cannot find the mistake. The error msg gives me "runtime error '13'" data types don't match. The error seems to be in the function "fillcalweeks".
Fillcalweeks should return an array which is filled with start and end dates which are located in a sheet.
Here's my code:
'For every calenderweek we need the start and end dates in an array to produce the timeline
Sub get_cal_weeks()
Dim weeks As Integer, i As Integer, col As String, weekstart As Date, weekend As Date
'start column is D
col = "D"
'get amount of weeks
weeks = countcalweeks()
'populate array calweeks
calweeks = fillcalweeks(weeks)
For i = 0 To weeks
Sheets("Kalenderwochen").Range("E" & i + 1) = calweeks(i, 1)
Next
End Sub
Function fillcalweeks(weeks As Integer) As String()
Dim i As Integer, datestart As Date, dateend As Date, calweek As Integer, returnarray() As String
For i = 0 To weeks
'date start & date end
datestart = Sheets("Kalenderwochen").Range("A" & i + 1).Value
dateend = Sheets("Kalenderwochen").Range("B" & i + 1).Value
calweek = Sheets("Kalenderwochen").Range("C" & i + 1).Value
returnarray(i, 1) = datestart
returnarray(i, 2) = dateend
returnarray(i, 3) = calweek
fillcalweeks = returnarray
Next
End Function
'Counts the calenderweeks in the Kalenderwochen sheet
Function countcalweeks() As Integer
countcalweeks = Sheets("Kalenderwochen").Range("A2").End(xlDown).row
End Function
Thx for the help
You are getting an error on the line calweeks = fillcalweeks(weeks) because you are assigning a String array (the result of the function fillcalweeks to a Variant.
You'll notice you declared every variable except calweeks. Since VBA doesn't have an explicit declaration for this variable it assigns it to a Variant.
To fix the problem, start by putting Option Explicit at the top of every module. When you compile the project, it'll alert you to errors such as these (Debug -> Compile VBA Project). Then all you need to do is declare calweeks as a String().
There is a second problem you have and that is that you are trying to store a Date data type in your String() array within the fillcalweeks. You either need to convert the datestart, dateend and calweek variables to Strings (you can use the VBA.CStr() function to do this) or change the function fillcalweeks to return a Date array.
Finally, you need to declare a size range for the returnarray() within fillcalweeks. VBA needs to know how big this is before it can fill the values. Since you know how many rows there are (its an input to the function) this is as simple as replacing the declaration of returnarray with ReDim returnarray(0 To weeks - 1, 1 to 3) As String. Note you want to dimension the array to weeks - 1 since you have a base of zero not one.
One more error is that when you output back to the worksheet the loop needs to be For i = 0 To weeks - 1 otherwise the array will be out of bounds...
Thx. I found all the mistakes in the code. 1. declaration of calweeks and 2. the array dimension:
'For every calenderweek we need the start and end dates in an array to produce the timeline
Sub get_cal_weeks()
Dim weeks As Integer, i As Integer, col As String, weekstart As Date, weekend As Date, calweeks() As Variant
'start column is D
col = "D"
'get amount of weeks
weeks = countcalweeks()
'populate array calweeks
calweeks = fillcalweeks(weeks)
For i = 0 To weeks
field = i + i + 4
weekstart = calweeks(i, 0)
weekend = calweeks(i, 1)
Cells(5, field) = monetary_calc_week(weekstart, weekend)
Next
End Sub
Function fillcalweeks(weeks As Integer) As Variant()
Dim i As Integer, datestart As Date, dateend As Date, calweek As Integer, arraysize As Integer, returnarray() As Variant
arraysize = 52
weeks = weeks - 2
ReDim Preserve returnarray(arraysize, 3)
For i = 0 To weeks
If i > arraysize Then
arraysize = arraysize * 2
ReDim Preserve returnarray(arraysize, 3)
End If
'date start & date end
datestart = Sheets("Kalenderwochen").Range("A" & i + 2).Value
dateend = Sheets("Kalenderwochen").Range("B" & i + 2).Value
calweek = Sheets("Kalenderwochen").Range("C" & i + 2).Value
returnarray(i, 0) = datestart
returnarray(i, 1) = dateend
returnarray(i, 2) = calweek
Next
fillcalweeks = returnarray
End Function
'Counts the calenderweeks in the Kalenderwochen sheet
Function countcalweeks() As Integer
countcalweeks = Sheets("Kalenderwochen").Range("A2").End(xlDown).row
End Function