这个问题经常出现(事实上我自己也问过),所以这里是我在代码中使用的解决方案,以防它对其他人有用。问题是 BDH() 电子表格函数是异步的,即它立即返回,在后台获取数据,然后在完成时告诉 Excel。由于 Excel 只有一个执行线程,因此只有在处理完 VBA 子例程后才会响应此回调。
递归调用 VBA 函数(参见类似问题的其他答案)以等待 BDH 数据返回的替代方法是编写一个同步 VBA 电子表格函数。根据您对 VBA 的信心程度,您可能想尝试这种方法。我也有等效的 BDP/BDS 函数调用,但这里省略了。
此函数直接调用 Bloomberg 数据 API,直到请求完成才返回。 它不提供 BDH 调用的所有选项(但如果需要,可以将这些选项添加到代码中)并且只返回 每日数据,以最近可用的数据结尾。一个优点是数据以数组的形式返回,因此可以用作其他 Excel 函数的数组参数。
用法:
=getBloomiHistory(ticker,fields,startDate,includeDates)
参数:
-
ticker:任何 Bbg Ticker(例如“EUSA2 Curncy”)
字段:任何 Bbg 历史字段,或字段数组(例如“PX_MID”)
startDate:作为 Excel 日期的特定开始日期(例如
'21-May-2021')或字符串(例如,'20210521' 或 '-1CY')
includeDates:默认为 True。如果 False 则返回系列不
有日期,并且是单个值向量。由于这是一个数组函数,返回的数据可以直接作为其他函数的参数。
返回:包含从 startDate 到今天的历史日期的变体。
例子:
注意。为了运行代码,需要在工具/参考中勾选对 Bloomberg COM API 的引用。我更喜欢使用引用进行早期绑定,但您可以使用 VBA CreateObject("blpapicom2.Session") 代替 New() 调用切换到后期绑定,并将所有对象定义为 Object,尽管您需要找出所有 BlpApi 常量定义也可以使用 VBA IDE 中的对象浏览器(按 F2)。
VBA 代码:
Option Explicit
Dim g_Session As blpapicomLib2.Session
Dim g_Service As blpapicomLib2.Service
'Function to set up the Bloomberg session once
Private Function CheckSession() As Boolean
If g_Session Is Nothing Then
Set g_Session = New blpapicomLib2.Session
g_Session.Start
g_Session.OpenService "//blp/refdata"
Set g_Service = g_Session.GetService("//blp/refdata")
End If
CheckSession = True
End Function
Public Function getBloomiHistory(strSecurity As String, rngFields As Variant, vStart As Variant, Optional bIncludeDates As Boolean = True) As Variant
Dim vRet As Variant
CheckSession
Dim req As blpapicomLib2.REQUEST
Set req = g_Service.CreateRequest("HistoricalDataRequest")
req.GetElement("securities").AppendValue strSecurity
Dim vFields As Variant
vFields = rngFields
Dim strFields() As String
Dim lFields As Long
If IsArray(vFields) Then
Dim lr, lc, lRows, lCols As Long
lRows = UBound(vFields, 1)
lCols = UBound(vFields, 2)
lFields = lRows * lCols
ReDim strFields(1 To lFields) As String
Dim lstr As Long
lstr = 1
For lr = 1 To lRows
For lc = 1 To lCols
strFields(lstr) = vFields(lr, lc)
req.GetElement("fields").AppendValue vFields(lr, lc)
lstr = lstr + 1
Next lc
Next lr
Else
ReDim strFields(1) As String
lFields = 1
strFields(1) = vFields
req.GetElement("fields").AppendValue vFields
End If
req.Set "nonTradingDayFillOption", "NON_TRADING_WEEKDAYS"
req.Set "periodicityAdjustment", "ACTUAL"
req.Set "periodicitySelection", "DAILY"
Dim strStart As String
If IsDate(vStart) Then
strStart = Format(vStart, "yyyymmdd")
Else
strStart = vStart
End If
'endDate defaults to Today
req.Set "startDate", strStart
g_Session.SendRequest req
Dim evt As blpapicomLib2.Event
Dim msgIt As blpapicomLib2.MessageIterator
Dim msg As blpapicomLib2.Message
Dim eltRef As blpapicomLib2.Element
Dim eltSecurities As blpapicomLib2.Element
Dim eltSecurity As blpapicomLib2.Element
Dim eltData As blpapicomLib2.Element
Dim eltFieldDataArray As blpapicomLib2.Element
Dim eltDate As blpapicomLib2.Element
Dim eltValue As blpapicomLib2.Element
Dim dt As Date
Dim vt As Variant
Dim bExit As Boolean
bExit = False
Do While Not bExit
Set evt = g_Session.NextEvent
If (evt.EventType = PARTIAL_RESPONSE Or evt.EventType = RESPONSE) Then
Set msgIt = evt.CreateMessageIterator
Do While (msgIt.Next)
Set msg = msgIt.Message
Set eltSecurities = msg.GetElement("securityData")
Set eltFieldDataArray = eltSecurities.GetElement("fieldData")
Dim nItems As Integer
nItems = eltFieldDataArray.NumValues
If bIncludeDates Then
ReDim vRet(1 To nItems, 1 To lFields + 1) As Variant
Else
ReDim vRet(1 To nItems, 1 To lFields) As Variant
End If
Dim nItem As Integer
Dim nValues As Integer
Dim d As Double
For nItem = 0 To nItems - 1
Set eltData = eltFieldDataArray.GetValueAsElement(nItem)
nValues = eltData.NumElements
Dim lOffset As Long
lOffset = 0
If bIncludeDates Then
Set eltDate = eltData.GetElement(0)
dt = eltDate.Value
vRet(nItem + 1, 1) = dt
lOffset = 1
End If
Dim lField As Long
For lField = 1 To lFields
If eltData.HasElement(strFields(lField)) Then
Set eltValue = eltData.GetElement(strFields(lField))
vt = eltValue.Value
vRet(nItem + 1, lField + lOffset) = vt
Else
vRet(nItem + 1, lField + lOffset) = CVErr(xlErrNA)
End If
Next lField
Next nItem
Loop
If (evt.EventType = RESPONSE) Then bExit = True
End If
Loop
getBloomiHistory = vRet
End Function
我说“已经”使用过,因为我现在使用的是已编译的 C++ Excel .xll,它基本上可以做同样的事情,但速度要快一些。