Home > OS >  VBA Function to Calculate API calls in second workbook
VBA Function to Calculate API calls in second workbook

Time:04-13

Scenario: I have two workbooks, one of them contains VBA (WB1) code and the other (WB2) contains calls to an API for data collection (e.g. Bloomberg Add in function). When the code in WB1 is run, it starts a loop of identifiers, for each, it opens an instance of WB2, and tries to recalculate all functions. Once that is done, it copies some of the data of WB2 to WB1, saves WB2 with the identifier name, closes it and moves on to the next identifier.

Issue: As each of the API calls in WB2 take some time to process ad retrieve data, the VBA script does not wait for the functions to be calculated, it just copies the same data and moves on in the loop. Consequently, the data copied to WB1 is incorrect.

What was tried so far: I used a series of Calculate commands in VBA, also used loops to with DoEvents and tried to set up a counter in WB2 with the number of cells with data still pending calculation. In all these cases, the functions are still not fully calculated.

Obs. In this case, as this is not a specific problem to a single API (e.g. Bloomberg) the solution needs to come from a VBA command in the script of WB1.

Code so far:

Private Sub DownloadData()

    Application.ScreenUpdating = True
    Application.DisplayAlerts = True

    ' clear raw data sheets
    Dim mainwb As Workbook
    Set mainwb = ThisWorkbook
    ThisWorkbook.Worksheets(wsRawClassData.Name).UsedRange.ClearContents

    Dim wsas As Variant
    wsas = Evaluate(ThisWorkbook.Names("WSATickers").Value)

    ' path
    Dim xlsPath As String
    xlsPath = Evaluate(ThisWorkbook.Names("Path").Value)
    If xlsPath = "" Then
        xlsPath = ThisWorkbook.Path
    End If

    Dim c As Integer
    For c = 1 To 100
        If wsas(c, 1) = "" Then Exit For

        Dim objXL
        Set objXL = CreateObject("Excel.Application")
    
        Dim objXLWB
        Set objXLWB = Workbooks.Open(xlsPath & "WB2.xlsm")
        
        objXLWB.Worksheets("Data").Range("Identifier").Value = wsas(c, 1)
        Application.Calculation = xlManual
        Application.Calculation = xlAutomatic
        
        'wait for initial calculations
        Do While objXL.CalculationState <> xlDone
            DoEvents
        Loop
               
        ' Recalculation forcing:
        objXLWB.Activate
        Application.Calculation = xlManual
        Application.Calculation = xlAutomatic
        Application.CalculateFull
        Application.Calculation = xlAutomatic
        Dim wsobj As Variant
        For Each wsobj In objXLWB.Worksheets
            wsobj.Calculate
                Do While objXLWB.Worksheets("Data").Range("calcpend").Value <> 0
                    Application.Wait (Now   TimeValue("0:00:02"))
                Loop
        Next wsobj
        Application.Calculation = xlAutomatic
        mainwb.Activate
    
        Do Until objXLWB.Worksheets("Data").Range("calcpend").Value = 0
            DoEvents
        Loop

        ThisWorkbook.Worksheets(wsRawData.Name).Range("A" & (c   1)).Value = wsas(c, 1)
    
        ' save, close, quit
        objXLWB.SaveAs Filename:=xlsPath & wsas(c, 1) & ".xlsm", FileFormat:=xlOpenXMLWorkbookMacroEnabled
        objXLWB.Close
        objXL.Quit
    
    Next c

End Sub

CodePudding user response:

It has been a while (like 20 years) since I worked with VBA and Bloomberg, but as I recall, you can make calls to the Bloomberg API directly from VBA. A quick google search of "call bloomberg api from vba" led me to this page that seems promising:

https://github.com/tebbb/VBA-Bloomberg-API-Wrapper

CodePudding user response:

The solution below takes the following approach.

  1. make a copy of the formulas in all the cells you need to wait on
  2. clear those cells so their values are empty strings
  3. replace the formulas causing them to need to be recalculated
  4. use application.onTime to check every second to see if all formulas have received values

This approach assumes that the formulas that rely on add-ins to fetch data will return not return a value until they have received thier data. If this is not true, you'll need to adjust the code accordingly.

I think the key making this work for you is the use of application.onTime becuase that allows all VBA to finish running which seems to be important to allow the data to be fully retrieved.

Option Explicit
Dim formulas As New Collection

Sub wait_until_filled()
    Dim x As Long
    Dim range_to_check  As range
    Dim sheet_to_check As Worksheet
    Dim workbook_to_check
    Dim cell As range
    
    ' specify the workbook that holds the formulas that we want to check
    Set workbook_to_check = Workbooks("Book3.xlsx")
    
    ' specify the sheet that holds the formulas that we want to check
    Set sheet_to_check = workbook_to_check.Worksheets("Sheet1")
    
    ' specify the set of cells that contain the formulas we are waiting for.
    Set range_to_check = sheet_to_check.range("D1:D2,E1")
    
    ' clear out old formulas in case we have had a prior run
    Do Until formulas.Count = 0
        formulas.Remove 1
    Loop
    
    ' remember each formula, then clear it
    For Each cell In range_to_check
        formulas.Add Array(workbook_to_check.Name, sheet_to_check.Name, cell.Address, cell.Formula)
        cell.Formula = ""
    Next
    
    
    ' replace the formulas, causing them to recalculate
    For x = 1 To formulas.Count
        Workbooks(formulas(x)(0)).Worksheets(formulas(x)(1)).range(formulas(x)(2)).Formula = formulas(x)(3)
    Next
    
    'wait a second then call the sub that checks to see if the data has returned
    Application.OnTime DateAdd("s", 1, Now), "wait_for_data"
    
    
End Sub


Sub wait_for_data()
    Dim x As Long
    'check to see of all formulas have a value
    For x = 1 To formulas.Count
        ' this if statement assumes that a formula that relies on an addin will produce
        ' a blank value until it has been filled in, which may not be true.
        If Workbooks(formulas(x)(0)).Worksheets(formulas(x)(1)).range(formulas(x)(2)).Value = "" Then
            'we have found a cell that has not updated, check again in another second
            Application.OnTime DateAdd("s", 1, Now), "wait_for_data"
            Exit Sub
        End If
    Next
    
    ' put code here to execute once all cells have received thier values
    MsgBox "all cells have received thier values"
    
    
End Sub
  • Related