http://mikejuniperhill.blogspot.fi/2013/05/bloomberg-v3com-api-wrapper-for-vba.html
http://mikejuniperhill.blogspot.fi/2013/06/bloomberg-v3com-api-wrapper-update-for.html
Three interface functions
With this updated version, I have now decided to break the class public interface function (previously getData function) into three separate functions. Handling all those different mandatory/optional input parameters for all different types of market data started to be a bit too messy operation to handle and public interface function mutated itself into a scary-looking monster. However, I still have not compromised the basic principle which says, that the wrapper is a compact one-module entity, which can be imported easily into your new VBA project. So, everything is (and hopefully will be) inside one class module. Anyway, to be more specific about the new public function interfaces, we have now three separate public functions for different types of data queries:
- referenceData
- bulkReferenceData
- historicalData
For referenceData, the new class interface function has been defined to be the following:
Public Function referenceData(ByRef securities As Variant, _ ByRef fields As Variant, _ Optional ByRef overrideFields As Variant, _ Optional ByRef overrideValues As Variant) As Variant
Needless to say, we still need to give arrays for securities (Bloomberg tickers with yellow key) and fields (Bloomberg field names).
Field override in Bloomberg
What is new here, is the override optionality. To implement an override to any field, we need to set up one array for override field names and another for override values. Excellent source for investigating possible override options for a field, is Bloomberg itself and its FLDS function. For example, you can test the override in your Bloomberg with the following commands:
IBM US Equity <GO>
FLDS <GO>
write best eps into FLDS query input box and press ENTER
mouse-click BEST_EPS
You should now have a view for all the fields, which can be overriden for this specific field (BEST_EPS). Just for an example, if you select BEST_FPERIOD_OVERRIDE (default value = 1FY) to be 3FY, you can see that the value for BEST_EPS also changes. And so on. If you play with this FLDS for a while, you should become pretty comfortable with this override possibility in Bloomberg. Personally I have to give a credit for Bloomy people for giving out this function, since it is a really great tool, which truly increases your productivity. There is an example tester program given for reference data retrieving in the code section below, with and without override. If you already did not know, you can retrieve multiple fields for multiple securities, as that example program shows.
Next, we have bulkReferenceData. The new class public interface function has been defined to be the following:
Public Function bulkReferenceData(ByRef securities As Variant, _ ByRef fields As Variant, _ Optional ByRef overrideFields As Variant, _ Optional ByRef overrideValues As Variant) As Variant
Nothing else has been changed, except we have now override possibility also for this type of data retrieving. Using override follows exactly, what has been presented above for reference data. I guess that most of the people will retrieve option chains, bond chains or curve member chains from Bloomberg. If you are not familiar what kind of overrides you can have for a chain, use FLDS again (BOND_CHAIN, OPT_CHAIN, INDX_MEMBERS). There is an example tester program given for bulk reference data retrieving in the code section below, with and without override. It should be noted also, that you can also retrieve chains for multiple securities, as that example program shows. Because the function returns a multidimensional array, there might be some further labour needed for handling this array for empty items. However, if you are comfortable enough with VBA arrays, this should not be any tombstone for your project.
Finally, we have the trickiest one, historicalData. Function interface has been defined to be the following:
Public Function historicalData(ByRef securities As Variant, _ ByRef fields As Variant, _ ByVal startDate As Date, _ ByVal endDate As Date, _ Optional ByVal calendarCodeOverride As String, _ Optional ByVal currencyCode As String, _ Optional ByVal nonTradingDayFillOption As String, _ Optional ByVal nonTradingDayFillMethod As String, _ Optional ByVal periodicityAdjustment As String, _ Optional ByVal periodicitySelection As String, _ Optional ByVal maxDataPoints As Integer, _ Optional ByVal pricingOption As String, _ Optional ByRef overrideFields As Variant, _ Optional ByRef overrideValues As Variant) As Variant
Note the large amount of optional parameters for historical data. If you are familiar with Bloomberg BDH function, you may notice, that these optional parameters above are exactly the same what are being used in that BDH function. Let us go through the optional parameters:
- calendarCodeOverride - Returns the data based on the calendar of the specified country, exchange or religion from CDR <GO>. Taking a two character calendar code null terminated string. This will cause the data to be aligned according to the calendar and including calendar holidays. This only applies to daily requests.
- currencyCode - Amends the value from local currency of the security to the desired currency. Currency of the ISO code. Eg. USD, GBP.
- nonTradingDayFillOption - Sets whether to include or exclude Non-Trading Days when no data is available. NON_TRADING_WEEKDAYS, ALL_CALENDAR_DAYS or ACTIVE_DAYS_ONLY (default).
- nonTradingDayFillMethod - Formats the type of data returned for non-trading days. PREVIOUS_VALUE (default) or NIL_VALUE.
- periodicityAdjustment - Sets the periodicity of the data. ACTUAL, CALENDAR (default) or FISCAL.
- periodicitySelection - DAILY (default), WEEKLY, MONTHLY, QUARTERLY, SEMI_ANNUALLY or YEARLY.
- maxDataPoints - The number of periods to download from the end date. The response will contain up to X data points, where X is the integer specified. If the original data set is larger than X, the response will be a subset, containing the last X data points. Hence the first range of data points will be removed. Any positive integer.
- pricingOption - Sets quote to Price or Yield for a debt instrument. PRICING_OPTION_PRICE or PRICING_OPTION_YIELD (default for debt instrument).
Data array inconsistency for historical data - the problem
With this updated version for historical data, it is now possible to retrieve historical data also for multiple fields and multiple securities. However, one really annoying feature for this historical data retrieving for multiple securities is the fact, that the dates for different securities are not necessarily matching inside arrays. I mean, that for an array item n
- security A has a date of 12.8.2013 for the item
- security B has a date of 15.8.2013 for the item
- security C has a date of 13.8.2013 for the item
The solution
Because of all those ingenious optional parameters implemented, there is an elegant way to overcome this problem. We give the following optional parameters for wrapper when retrieving historical data
- nonTradingDayFillOption = ALL_CALENDAR_DAYS
- nonTradingDayFillMethod = PREVIOUS_VALUE.
Afterthoughts
The biggest improvement for this version has been the optionality for field value overrides. Second improvement has been the possibility for retrieving data for multiple securities (bulk reference and historical data) and multiple fields (historical data). I have tested example programs and they should be working correctly. If you observe anything unusual going on with wrapper, just leave a comment for me.
My personal Thank You this time goes to Faizal from Singapore. As I have been gradually working with this wrapper, he has been giving me 1) some proper motivation to work, 2) valuable suggestions and comments, and 3) extremely valuable help for testing this wrapper with real-life data sets.
Anyway, have a great start for autumn and thanks for reading my blog. I hope you got something to make your life a bit easier when working with Bloomberg market data in VBA.
-Mike
' VBA standard module Option Explicit ' Private b As BCOM_wrapper Private r As Variant Private s() As String Private f() As String Private overrideFields() As String Private overrideValues() As String ' Sub tester_referenceData() ' ' create wrapper object Set b = New BCOM_wrapper ' ' create 3 securities and 4 fields ReDim s(0 To 2): s(0) = "GS US Equity": s(1) = "DBK GR Equity": s(2) = "JPM US Equity" ReDim f(0 To 3): f(0) = "SECURITY_NAME": f(1) = "BEST_EPS": f(2) = "BEST_PE_RATIO": f(3) = "BEST_DIV_YLD" ' ' retrieve result from wrapper into array and print r = b.referenceData(s, f) printReferenceData r ' ' create 1 override for fields ReDim overrideFields(0 To 0): overrideFields(0) = "BEST_FPERIOD_OVERRIDE" ReDim overrideValues(0 To 0): overrideValues(0) = "3FY" ' ' retrieve result from wrapper into array and print r = b.referenceData(s, f, overrideFields, overrideValues) printReferenceData r ' ' release wrapper object Set b = Nothing End Sub ' Sub tester_bulkReferenceData() ' ' create wrapper object Set b = New BCOM_wrapper ' ' create 3 securities and 1 fields ReDim s(0 To 2): s(0) = "GS US Equity": s(1) = "DBK GR Equity": s(2) = "JPM US Equity" ReDim f(0 To 0): f(0) = "BOND_CHAIN" ' ' retrieve result from wrapper into array and print r = b.bulkReferenceData(s, f) printBulkReferenceData r ' ' create 2 overrides for chain ReDim overrideFields(0 To 1): overrideFields(0) = "CHAIN_CURRENCY": overrideFields(1) = "CHAIN_COUPON_TYPE" ReDim overrideValues(0 To 1): overrideValues(0) = "JPY": overrideValues(1) = "FLOATING" ' ' retrieve result from wrapper into array and print r = b.bulkReferenceData(s, f, overrideFields, overrideValues) printBulkReferenceData r ' ' release wrapper object Set b = Nothing End Sub ' Sub tester_historicalData() ' ' create wrapper object Set b = New BCOM_wrapper ' ' create 3 securities and 4 fields ReDim s(0 To 2): s(0) = "GS US Equity": s(1) = "DBK GR Equity": s(2) = "JPM US Equity" ReDim f(0 To 3): f(0) = "PX_OPEN": f(1) = "PX_LOW": f(2) = "PX_HIGH": f(3) = "PX_LAST" ' ' retrieve result from wrapper into array r = b.historicalData(s, f, CDate("21.8.2008"), CDate("21.8.2013"), , , "ALL_CALENDAR_DAYS", "PREVIOUS_VALUE") printHistoricalData r ' ' release wrapper object Set b = Nothing End Sub ' Private Function printReferenceData(ByRef data As Variant) ' Dim rng As Range: Set rng = Sheets("Sheet1").Range("A1") rng.CurrentRegion.ClearContents Dim i As Long, j As Long ' On Error Resume Next For i = 0 To UBound(data, 1) For j = 0 To UBound(data, 2) rng(i + 1, j + 1) = data(i, j) Next j Next i End Function ' Private Function printBulkReferenceData(ByRef data As Variant) ' Dim rng As Range: Set rng = Sheets("Sheet1").Range("A1") rng.CurrentRegion.ClearContents Dim i As Long, j As Long ' On Error Resume Next For i = 0 To UBound(data, 1) For j = 0 To UBound(data, 2) rng(j + 1, i + 1) = data(i, j) Next j Next i End Function ' Private Function printHistoricalData(ByRef data As Variant) ' Dim rng As Range: Set rng = Sheets("Sheet1").Range("A1") rng.CurrentRegion.ClearContents Dim i As Long, j As Long, k As Long: k = 1 ' On Error Resume Next For i = 0 To UBound(data, 1) For j = 0 To UBound(data, 2) rng(j + 1, i + k) = data(i, j)(0) rng(j + 1, i + k + 1) = data(i, j)(1) rng(j + 1, i + k + 2) = data(i, j)(2) rng(j + 1, i + k + 3) = data(i, j)(3) Next j ' k = k + 3 Next i End Function '
' VBA Class module, name = BCOM_wrapper Option Explicit ' ' public enumerator for request type Public Enum ENUM_REQUEST_TYPE REFERENCE_DATA = 1 HISTORICAL_DATA = 2 BULK_REFERENCE_DATA = 3 End Enum ' ' constants Private Const CONST_SERVICE_TYPE As String = "//blp/refdata" Private Const CONST_REQUEST_TYPE_REFERENCE As String = "ReferenceDataRequest" Private Const CONST_REQUEST_TYPE_BULK_REFERENCE As String = "ReferenceDataRequest" Private Const CONST_REQUEST_TYPE_HISTORICAL As String = "HistoricalDataRequest" ' ' private data structures Private bInputSecurityArray() As String Private bInputFieldArray() As String Private bOutputArray As Variant Private bOverrideFieldArray() As String Private bOverrideValueArray() As String ' ' BCOM objects Private bSession As blpapicomLib2.session Private bService As blpapicomLib2.Service Private bRequest As blpapicomLib2.request Private bSecurityArray As blpapicomLib2.element Private bFieldArray As blpapicomLib2.element Private bEvent As blpapicomLib2.Event Private bIterator As blpapicomLib2.MessageIterator Private bIteratorData As blpapicomLib2.Message Private bSecurities As blpapicomLib2.element Private bSecurity As blpapicomLib2.element Private bSecurityName As blpapicomLib2.element Private bSecurityField As blpapicomLib2.element Private bFieldValue As blpapicomLib2.element Private bSequenceNumber As blpapicomLib2.element Private bFields As blpapicomLib2.element Private bField As blpapicomLib2.element Private bDataPoint As blpapicomLib2.element Private bOverrides As blpapicomLib2.element Private bOverrideArray() As blpapicomLib2.element ' ' class non-object data members Private bStartDate As String Private bEndDate As String Private bRequestType As ENUM_REQUEST_TYPE Private nSecurities As Long Private nSecurity As Long Private bCalendarCodeOverride As String Private bCurrencyCode As String Private bNonTradingDayFillOption As String Private bNonTradingDayFillMethod As String Private bPeriodicityAdjustment As String Private bPeriodicitySelection As String Private bMaxDataPoints As Integer Private bPricingOption As String ' Public Function referenceData(ByRef securities As Variant, _ ByRef fields As Variant, _ Optional ByRef overrideFields As Variant, _ Optional ByRef overrideValues As Variant) As Variant ' ' mandatory user input parameters bRequestType = REFERENCE_DATA bInputSecurityArray = securities bInputFieldArray = fields ' ' field names and values for overrides If Not (VBA.IsMissing(overrideFields)) Then bOverrideFieldArray = overrideFields If Not (VBA.IsMissing(overrideValues)) Then bOverrideValueArray = overrideValues ' processDataRequest referenceData = bOutputArray End Function ' Public Function bulkReferenceData(ByRef securities As Variant, _ ByRef fields As Variant, _ Optional ByRef overrideFields As Variant, _ Optional ByRef overrideValues As Variant) As Variant ' ' mandatory user input parameters bRequestType = BULK_REFERENCE_DATA bInputSecurityArray = securities bInputFieldArray = fields ' ' field names and values for overrides If Not (VBA.IsMissing(overrideFields)) Then bOverrideFieldArray = overrideFields If Not (VBA.IsMissing(overrideValues)) Then bOverrideValueArray = overrideValues ' processDataRequest bulkReferenceData = bOutputArray End Function ' Public Function historicalData(ByRef securities As Variant, _ ByRef fields As Variant, _ ByVal startDate As Date, _ ByVal endDate As Date, _ Optional ByVal calendarCodeOverride As String, _ Optional ByVal currencyCode As String, _ Optional ByVal nonTradingDayFillOption As String, _ Optional ByVal nonTradingDayFillMethod As String, _ Optional ByVal periodicityAdjustment As String, _ Optional ByVal periodicitySelection As String, _ Optional ByVal maxDataPoints As Integer, _ Optional ByVal pricingOption As String, _ Optional ByRef overrideFields As Variant, _ Optional ByRef overrideValues As Variant) As Variant ' ' mandatory user input parameters bRequestType = HISTORICAL_DATA bInputSecurityArray = securities bInputFieldArray = fields bStartDate = startDate bEndDate = endDate ' ' checks and conversions for user-defined dates If ((startDate = CDate(0)) Or (endDate = CDate(0))) Then _ Err.Raise vbObjectError, "Bloomberg API", "Date parameters missing for historical data query" ' If (startDate > endDate) Then _ Err.Raise vbObjectError, "Bloomberg API", "Incorrect date parameters for historical data query" ' bStartDate = convertDateToBloombergString(startDate) bEndDate = convertDateToBloombergString(endDate) ' ' optional user input parameters bCalendarCodeOverride = calendarCodeOverride bCurrencyCode = currencyCode bNonTradingDayFillOption = nonTradingDayFillOption bNonTradingDayFillMethod = nonTradingDayFillMethod bPeriodicityAdjustment = periodicityAdjustment bPeriodicitySelection = periodicitySelection bMaxDataPoints = maxDataPoints bPricingOption = pricingOption ' ' field names and values for overrides If Not (VBA.IsMissing(overrideFields)) Then bOverrideFieldArray = overrideFields If Not (VBA.IsMissing(overrideValues)) Then bOverrideValueArray = overrideValues ' processDataRequest historicalData = bOutputArray End Function ' Private Function processDataRequest() ' openSession sendRequest catchServerEvent releaseObjects End Function ' Private Function openSession() ' Set bSession = New blpapicomLib2.session bSession.Start bSession.OpenService CONST_SERVICE_TYPE Set bService = bSession.GetService(CONST_SERVICE_TYPE) End Function ' Private Function sendRequest() ' Select Case bRequestType Case ENUM_REQUEST_TYPE.HISTORICAL_DATA ReDim bOutputArray(0 To UBound(bInputSecurityArray, 1), 0 To 0) Set bRequest = bService.CreateRequest(CONST_REQUEST_TYPE_HISTORICAL) ' ' set mandatory user input parameter bRequest.Set "startDate", bStartDate bRequest.Set "endDate", bEndDate ' ' set optional user input parameter If (bNonTradingDayFillOption <> "") Then bRequest.Set "nonTradingDayFillOption", bNonTradingDayFillOption If (bNonTradingDayFillMethod <> "") Then bRequest.Set "nonTradingDayFillMethod", bNonTradingDayFillMethod If (bPeriodicityAdjustment <> "") Then bRequest.Set "periodicityAdjustment", bPeriodicityAdjustment If (bPeriodicitySelection <> "") Then bRequest.Set "periodicitySelection", bPeriodicitySelection If (bCalendarCodeOverride <> "") Then bRequest.Set "calendarCodeOverride", bCalendarCodeOverride If (bCurrencyCode <> "") Then bRequest.Set "currency", bCurrencyCode If (bMaxDataPoints <> 0) Then bRequest.Set "maxDataPoints", bMaxDataPoints If (bPricingOption <> "") Then bRequest.Set "pricingOption", bPricingOption ' Case ENUM_REQUEST_TYPE.REFERENCE_DATA Dim nSecurities As Long: nSecurities = UBound(bInputSecurityArray) Dim nFields As Long: nFields = UBound(bInputFieldArray) ReDim bOutputArray(0 To nSecurities, 0 To nFields) Set bRequest = bService.CreateRequest(CONST_REQUEST_TYPE_REFERENCE) ' Case ENUM_REQUEST_TYPE.BULK_REFERENCE_DATA ReDim bOutputArray(0 To UBound(bInputSecurityArray, 1), 0 To 0) Set bRequest = bService.CreateRequest(CONST_REQUEST_TYPE_BULK_REFERENCE) ' End Select ' Set bSecurityArray = bRequest.GetElement("securities") Set bFieldArray = bRequest.GetElement("fields") appendRequestItems setOverrides bSession.sendRequest bRequest End Function ' Private Function setOverrides() ' On Error GoTo errorHandler ' If (UBound(bOverrideFieldArray) <> UBound(bOverrideValueArray)) Then Exit Function Set bOverrides = bRequest.GetElement("overrides") ' ReDim bOverrideArray(LBound(bOverrideFieldArray) To UBound(bOverrideFieldArray)) Dim i As Integer For i = 0 To UBound(bOverrideFieldArray) ' If ((Len(bOverrideFieldArray(i)) > 0) And (Len(bOverrideValueArray(i)) > 0)) Then ' Set bOverrideArray(i) = bOverrides.AppendElment() bOverrideArray(i).SetElement "fieldId", bOverrideFieldArray(i) bOverrideArray(i).SetElement "value", bOverrideValueArray(i) End If Next i Exit Function ' errorHandler: Exit Function End Function ' Private Function appendRequestItems() ' Dim nSecurities As Long: nSecurities = UBound(bInputSecurityArray) Dim nFields As Long: nFields = UBound(bInputFieldArray) Dim i As Long Dim nItems As Integer: nItems = getMax(nSecurities, nFields) For i = 0 To nItems If (i <= nSecurities) Then bSecurityArray.AppendValue CStr(bInputSecurityArray(i)) If (i <= nFields) Then bFieldArray.AppendValue CStr(bInputFieldArray(i)) Next i End Function ' Private Function catchServerEvent() ' Dim bExit As Boolean Do While (bExit = False) Set bEvent = bSession.NextEvent If (bEvent.EventType = PARTIAL_RESPONSE Or bEvent.EventType = RESPONSE) Then ' Select Case bRequestType Case ENUM_REQUEST_TYPE.REFERENCE_DATA: getServerData_reference Case ENUM_REQUEST_TYPE.HISTORICAL_DATA: getServerData_historical Case ENUM_REQUEST_TYPE.BULK_REFERENCE_DATA: getServerData_bulkReference End Select ' If (bEvent.EventType = RESPONSE) Then bExit = True End If Loop End Function ' Private Function getServerData_reference() ' Set bIterator = bEvent.CreateMessageIterator Do While (bIterator.Next) Set bIteratorData = bIterator.Message Set bSecurities = bIteratorData.GetElement("securityData") Dim offsetNumber As Long, i As Long, j As Long nSecurities = bSecurities.Count ' For i = 0 To (nSecurities - 1) Set bSecurity = bSecurities.GetValue(i) Set bSecurityName = bSecurity.GetElement("security") Set bSecurityField = bSecurity.GetElement("fieldData") Set bSequenceNumber = bSecurity.GetElement("sequenceNumber") offsetNumber = CInt(bSequenceNumber.Value) ' For j = 0 To UBound(bInputFieldArray) If (bSecurityField.HasElement(bInputFieldArray(j))) Then Set bFieldValue = bSecurityField.GetElement(bInputFieldArray(j)) bOutputArray(offsetNumber, j) = bFieldValue.Value End If Next j Next i Loop End Function ' Private Function getServerData_bulkReference() ' Set bIterator = bEvent.CreateMessageIterator nSecurity = nSecurity + 1 ' Do While (bIterator.Next) Set bIteratorData = bIterator.Message Set bSecurities = bIteratorData.GetElement("securityData") Dim offsetNumber As Long, i As Long, j As Long Dim nSecurities As Long: nSecurities = bSecurities.Count ' Set bSecurity = bSecurities.GetValue(0) Set bSecurityField = bSecurity.GetElement("fieldData") ' If (bSecurityField.HasElement(bInputFieldArray(0))) Then Set bFieldValue = bSecurityField.GetElement(bInputFieldArray(0)) ' If ((bFieldValue.numValues - 1) > UBound(bOutputArray, 2)) Then _ ReDim Preserve bOutputArray(0 To UBound(bOutputArray, 1), 0 To bFieldValue.numValues - 1) ' For i = 0 To bFieldValue.numValues - 1 Set bDataPoint = bFieldValue.GetValue(i) bOutputArray(nSecurity - 1, i) = bDataPoint.GetElement(0).Value Next i End If Loop End Function ' Private Function getServerData_historical() ' Set bIterator = bEvent.CreateMessageIterator Do While (bIterator.Next) Set bIteratorData = bIterator.Message Set bSecurities = bIteratorData.GetElement("securityData") Dim nSecurities As Long: nSecurities = bSecurityArray.Count Set bSecurityField = bSecurities.GetElement("fieldData") Dim nItems As Long, offsetNumber As Long, nFields As Long, i As Long, j As Long nItems = bSecurityField.numValues If (nItems = 0) Then Exit Function If ((nItems > UBound(bOutputArray, 2))) Then _ ReDim Preserve bOutputArray(0 To nSecurities - 1, 0 To nItems - 1) ' Set bSequenceNumber = bSecurities.GetElement("sequenceNumber") offsetNumber = CInt(bSequenceNumber.Value) ' If (bSecurityField.Count > 0) Then For i = 0 To (nItems - 1) ' If (bSecurityField.Count > i) Then Set bFields = bSecurityField.GetValue(i) If (bFields.HasElement(bFieldArray(0))) Then ' Dim d As Variant: ReDim d(0 To bFields.NumElements - 1) For j = 0 To bFields.NumElements - 1 d(j) = bFields.GetElement(j).GetValue(0) Next j ' bOutputArray(offsetNumber, i) = d End If End If Next i End If Loop End Function ' Private Function releaseObjects() ' nSecurity = 0 Set bDataPoint = Nothing Set bFieldValue = Nothing Set bSequenceNumber = Nothing Set bSecurityField = Nothing Set bSecurityName = Nothing Set bSecurity = Nothing Set bOverrides = Nothing Set bSecurities = Nothing Set bIteratorData = Nothing Set bIterator = Nothing Set bEvent = Nothing Set bFieldArray = Nothing Set bSecurityArray = Nothing Set bRequest = Nothing Set bService = Nothing bSession.Stop Set bSession = Nothing End Function ' Private Function convertDateToBloombergString(ByVal d As Date) As String ' Dim dayString As String: dayString = VBA.CStr(VBA.Day(d)): If (VBA.Day(d) < 10) Then dayString = "0" + dayString Dim MonthString As String: MonthString = VBA.CStr(VBA.Month(d)): If (VBA.Month(d) < 10) Then MonthString = "0" + MonthString Dim yearString As String: yearString = VBA.Year(d) convertDateToBloombergString = yearString + MonthString + dayString End Function ' Private Function getMax(ByVal a As Long, ByVal b As Long) As Long ' getMax = a: If (b > a) Then getMax = b End Function '