How to aggregate returns in an excel UDF using the product formula - vba

I am trying to put the below formula into a UDF so that I can get a cumulative return when I aggregate monthly returns.
In excel the formula has to be recognized as an array so when I type it in I press Ctrl + Shift + Enter to get the {} brackets around the formula.
Does anyone know how to do this?
I want to be able to just type in returns_calc() and select the range that would fit into the returns variable below.
{=(PRODUCT(1+returns/100)-1)*100}

You can use the [ ] notation in Application.Evaluate to calculate Array Formulas in VBA. Your above formula can be called in VBA in just 1 line as shown below
Sub Sample()
MsgBox Application.Evaluate(["=(PRODUCT(1+returns/100)-1)*100"])
End Sub
Now modifying it to accept a range in a function, you may do this as well
Function returns_calc(rng As Range) As Variant
On Error GoTo Whoa
Dim frmulaStr As String
frmulaStr = "=(PRODUCT(1+(" & rng.Address & ")/100)-1)*100"
returns_calc = Application.Evaluate([frmulaStr])
Exit Function
Whoa:
returns_calc = "Please check formula string" 'or simply returns_calc = ""
End Function
EXAMPLE SCREENSHOT

Something like this
Public Function Range_Product(theRange As Variant)
Dim var As Variant
Dim j As Long
var = theRange.Value2
Range_Product = 1#
For j = LBound(var) To UBound(var)
Range_Product = Range_Product * (1 + var(j, 1) / 100)
Next j
Range_Product = (Range_Product - 1) * 100
End Function

Related

How to return 2 values and rounding them? Excel VBA

I am working on a code that should calculate simple foundations, and in order to do that I have to return 2 values with my function -preferably in two different columns.
Function FundacaoSimples(b, l, carga) As Variant
tensao = Sheets("Tabelas e Constantes").Range("tensao").Value
Dim area As Double
Dim Bs As Single
Dim Ls As Single
Dim Resultado(1 To 2) As String
If b = l Then
area = (1.1 * carga) / tensao
Bs = Sqr(area)
Ls = Bs
ElseIf b <> l Then
area = (1.1 * carga) / tensao
Bs = Sqr((2 * area) / 3)
Ls = (3 * Bs) / 2
End If
Resultado(1) = Round(Bs, 2)
Resultado(2) = Round(Ls, 2)
FundacaoSimples = (Resultado(1) & " / " & Resultado(2))
End Function
This rounding I am using it just to get a value rounded with 2 decimals, e.g: 2,73 to 2,75; 0,89 to 0,90.
I tried working with ActiveCells.Offset(0,1), but the statement isn't valid.
Is it possible to to just jump one column to the right?
You could use ActiveCell.Offset(0, 1).value = SomeValue, however - That's when writing a regular Sub. You're writing a Function / User Defined Function.
Within a UDF it is not possible to alter different cells.
However, a workaround is to have the UDF and when it's entered in a cell, you can then use the Worksheet_Change event to alter the cell next to the Target parameter of that event.
Edit:
Some sample code:
In a regular module:
Public Function MyUDF(param1 as integer, param2 as integer) as Integer
MyUDF = param1 + param2
End Function
In the Worksheet where you want the offset:
Private Sub Worksheet_Change(Byval Target as Range)
If Left(Target.Formula, 6) = "=MyUDF" Then
Target.Offset(0, 1).value = "somevalue at the offset cells"
End If
End Sub
In general, functions should not be writing values or accessing values from a spreadsheet. They should access their parameters and return result.
Try like this, an oversimplified version of what you need:
Option Explicit
Public Sub TestMe()
ActiveCell = FundacaoSimples(0)
ActiveCell.Offset(0, 1) = FundacaoSimples(1)
End Sub
Function FundacaoSimples() As Variant
ReDim varResult(1)
varResult(0) = 55
varResult(1) = 100
FundacaoSimples = varResult
End Function
Then you can edit the function a bit with your own parameters and use it further.

User-defined function returns non-formattable dates Excel VBA

I created a UDF in Excel VBA that returns an multi-dimensional array of dates and doubles. The problem is that I cannot format the dates that are returned.
Here is a simplified example:
Function test(dates as Range)
Dim results
Dim i As Integer
ReDim results(1 To dates.Cells.Count)
For i = 1 To dates.Cells.Count
results(i) = dates.Cells(i).Value
Next
test = Application.WorksheetFunction.Transpose(results)
End Function
The transpose at the end is just for convenience to have an column output (I press Ctrl+Shift+enter). I you use this simplified example, you will not be able to format the output and it will not be considered as dates stricto sensu.
Any ideas?
Change the results array to doubles:
Function test(dates As Range)
Dim results() As Double
Dim i As Integer
ReDim results(1 To dates.Cells.Count)
For i = 1 To dates.Cells.Count
results(i) = dates.Cells(i).Value
Next
test = Application.WorksheetFunction.Transpose(results)
End Function
Or change the dates.Cells(i).Value to dates.Cells(i).Value2 which will return the double not the date string:
Function test(dates As Range)
Dim results
Dim i As Integer
ReDim results(1 To dates.Cells.Count)
For i = 1 To dates.Cells.Count
results(i) = dates.Cells(i).Value2
Next
test = Application.WorksheetFunction.Transpose(results)
End Function
Then format the cells as you desire.
You may try something like this.
Results(i) = CDate(dates.Cells(i).Value)

Custom Function Entering the Result Array into a Different Cell

I have created my own function to determine count the values in between to given values in increments of 30 as seen here
Function InBetween(First As Integer, Last As Integer)
Dim i As Long, F As String, a() As String
F = First
For i = First + 30 To Last Step 30
F = F & "|" & i
Next i
InBetween = F
End Function
When I use this function, I currently have it returning the result array in the cell the formula was entered into in the format of "1|2|3|4". Is there a way I can get this array to populate into the cell below the one containing the formula?
Note: I don't want the formula in the cell as I need to refer to the cell in a future equation that will use the result and not the equation.
This was surprisingly difficult. At first I tried calling a sub from the function to affect the cell below using application.caller but this always returned a #value error. It seems a UDF can't run anything that affects the worksheet.
Eventually I came up with this:
Create a worksheet change event by pasting this into the worksheet object in vb:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
On Error Resume Next
If Left(Target.Offset(-1, 0).Formula, 10) = "=InBetween" Then Call DoX(Target.Offset(-1, 0), InBetween(10, 60))
On Error GoTo 0
End Sub
Then paste this into a module
Sub DoX(r As Range, val As String)
Sheets(r.Parent.Name).Cells(r.Row, r.Column) = ""
Sheets(r.Parent.Name).Cells(r.Row + 1, r.Column) = val
End Sub
Then use your function as normal, but remember to hit return after you enter it so the active cell is the cell below where you entered the formula.

Excel formula calculating once then deleting

I have an excel formula:
=SplitKey(GetSysCd(INDEX([ReportValue],MATCH("mtr_make_model",[FieldName],0)),INDEX([ListName],MATCH("mtr_make_model",[FieldName],0))), 0)
which is running a few subroutines in VBA, but mainly matching values and inserting those values into a cell. When it finds a value for "mtr_make_model" it runs and matches the values inside a sys codes table. The issue I am having is that it is calculating once and then it removes the formula and now has solely the value... In the event that I go to the mtr_make_model field and change the value, the formula does not recalculate. Has anyone heard of this happening? Is this due to something in the VBA code? How do I make that formula stay put and if certain values change, the formula recalculates?
Thanks in advance.
Here are the two functions:
Public Function GetSysCd(ByVal name As String, sysCdTableName As String) As String
Dim r As Integer
Dim sysCdTable As Range
Dim nameList As Variant
Dim sysCd As String
On Error GoTo GetSysCd_Error
Set sysCdTable = Worksheets("sys_cd").Range(sysCdTableName)
nameList = WorksheetFunction.Index(sysCdTable, 0, 2)
r = WorksheetFunction.Match(name, nameList, 0)
sysCd = WorksheetFunction.Index(sysCdTable, r, 1)
GetOutOfHere:
On Error GoTo 0
GetSysCd = sysCd
Exit Function
GetSysCd_Error:
sysCd = ""
GoTo GetOutOfHere
End Function
Public Function SplitKey(s As String, v As Integer)
Dim aString As Variant
Dim r As Integer
If Len(s) > 2 Then
aString = Split(s, "_")
If v = 0 Or v = 1 Then
SplitKey = aString(v)
Else
SplitKey = aString(0)
End If
Else
SplitKey = ""
End If
End Function
I don't think the functions are relevant at this point, but rather just a matter of the function not recalculating when a variable in the formula changes...
The problem could be that Excel only recalculates functions when one of their arguments changes, and your GetSysCd function is referring to a range that is not in its argument list
Set sysCdTable = Worksheets("sys_cd").Range(sysCdTableName)
where sysCdTableName is just a string rather than a reference.
You can make the functions recalculate in real time by adding Application.Volatile True to the top of each function.

Convert VBA Macro to Function

I have been trying to create a function to retrieve column titles found in row four in an excel sheet. This is what I have so far, can anybody help me please?
Sub Test_Click()
Dim text As String
Dim titles(200) As String
Dim nTitles As Integer
For i = 1 To 199
If Trim(Sheets("Sheet1").Cells(4, i).Value) = "" Then
nTitles = i - 1
Exit For
End If
titles(i - 1) = Sheets("Sheet1").Cells(4, i).Value
Next
For i = 0 To nTitles
Sheets("Sheet1").Cells(20 + i, 1).Value = titles(i)
Next
End Sub
You need to make an array function for this. So your function will take in inputs through a range
Function ReturnArray(Input as Range) as Variant
' Do stuff with the Input range
Dim Output(m,n) as Variant
'Loop through m,n to fill in the output values as you would in a range
ReturnArray = Output
End Function
And when you put in the function in excel, type it in the cell after highlighting where you want the output and press Ctrl-Shift-Return
Just as you write a Sub you can write a Function, just substitute the words at the beginning and at the end of your code.
Now, about how to return the values, obviously it will be an array, so you'll need to declare the array, set its size, fill its cells and return it. This can be done like this:
Function yourFunction() as String()
' You already have an array named "titles" which stores the values you want
' to return. Fill it exactly as you do in your original code.
yourFunction = titles ' This is the way to return the array.
End Function
If you want to use this function in a worksheet (as a formula), remember that this is an array-function, so you'll need to press Ctrl+Shitf+Enter after you enter the function in the cell instead of just [Enter].