I see there's some old stuff about this, but i'm hoping someone has found a new solution.
with my routine, I update the dates in 3 workbooks, so that the BDH commands get the latest prices, then i copy the results from those 3 workbooks into a separate summary workbook. However, as common apparently, The BDH function doesn't refresh/update, even after a timepause in there.
What are the latest findings on refreshing or re-requesting the BDH commands before i Copy paste them to the results sheet?
===========
Twenty.Worksheets("Portfolio_2016").Activate
[K3].Value = TradeDay
[L3].Value = PrevTradeDay
'Japan.Worksheets("Portfolio_2016").Activate
'[K3].Value = TradeDay
'[L3].Value = PrevTradeDay
'AAR.Worksheets("Portfolio_2016").Activate
'[K3].Value = TradeDay
'[L3].Value = PrevTradeDay
Call RefreshStaticLinks
End Sub
Public Sub RefreshStaticLinks()
Call Twenty.Worksheets("Portfolio_2016").Range("K7:Q26").Select
Call Application.Run("RefreshCurrentSelection")
Call Application.OnTime(Now + TimeValue("00:00:01"), "ProcessData")
End Sub
Private Sub ProcessData()
Dim c As Range
For Each c In Selection.Cells
If c.Value = "#N/A Requesting Data..." Then
Call Application.OnTime(Now + TimeValue("00:00:01"), "ProcessData")
Exit Sub
End If
Next c
Call CopyPaste
End Sub
You need to use Application.OnTime to achieve this. Bloomberg formulas will not update while a macro is paused. There are examples in the FAQ section on WAPI on Bloomberg. The below is taken from there where you will also find an example spreadsheet.
[Download Example! Download a working Excel VBA example here]
The data returned by any of our BDx() family of functions is in an asynchronous manner. Therefore, if your Excel VBA application [macro] is dependent upon returned Bloomberg data, we recommend that you incorporate the following logic into your VBA code that will, essentially, pause your application while the data is populated in your Excel cells:
Option Explicit
Private Const MAXROWS = 25
Sub fillFormula()
Range("B2:B" & MAXROWS).Clear
Range("B2").Formula = "=BDP($A2,B$1)"
Range("B2").AutoFill Range("B2:B" & MAXROWS), xlFillDefault
checkStatus
End Sub
Sub checkStatus()
Dim i
For i = 2 To MAXROWS
' Check to make sure that the cells are NOT requesting data
If "#N/A Requesting Data..." = Cells(i, 2) Then
Application.OnTime (Now + TimeValue("00:00:02")), "checkStatus"
Exit Sub
End If
Next i
MsgBox "We just finished getting the values"
' Add code here that would process the data now that it's updated
End Sub
The above code can be added to a blank module and the following code will be added to the click event handler, for instance, of a button on the worksheet:
fillFormula
Related
I have two rather large excel sheets that are near identical and I'm trying to take turn the Static Data version into a copy of MAIN. I also check for changes in MAIN to be written to this Static Data sheet every second:
Sub macro_timer()
'Tells Excel when to next run the macro.
Application.OnTime Now + TimeValue("00:00:01"), "CopyMain"
End Sub
Public Sub CopyMain()
Dim i As Long
i = 1
Worksheets("Static Data").Cells.ClearContents
With Worksheets("Static Data")
Worksheets(i).Range("A1").CurrentRegion.Copy .Range("A1")
.Range("A1").CurrentRegion.Value = .Range("A1").CurrentRegion.Value
End With
Call macro_timer
End Sub
This works on a small table but it seems to crash after a while. Is there a way that I don't need to call ClearContents and only write the changes to the Static Data sheet.
I have an add-in that creates a report on a file, the report has a pivot table like this:
The above is now sorted on the leftmost column. To make it easier for the user I want to add a Worksheet_SelectionChange event code that if I click on a cell with "Kolli" or "Vikt" in this range visible in the image sort the table on this column.
The sorting is not the issue, but can a add-in file notice selection change?
I would need something like a ACTIVEsheet_SelectionChange, and then read the Target so that it's the correct workbook, sheet and range.
Is that even possible? Or do I need to somehow write the code in the "target" workbook worksheet?
Please, try the next way:
Put the next declaration on top of the add-in ThisWorkbook code module (in the declarations area):
Public WithEvents appEvHandler As Application
Put in Workbook_Open event the next code, to activate the event handler:
Private Sub Workbook_Open()
Set appEvHandler = Application 'this code line can be placed in any standard event, when need to activate the `appEvHandler` events
End Sub
Copy this new event code in ThisWorkbook code module:
Private Sub appEvHandler_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
If Sh.Parent.Name <> ThisWorkbook.Name Then
MsgBox Sh.Parent.Name & " workbook, sheet " & Sh.Name & " changed selection to " & Target.Address & "..."
Else
Debug.Print "Selection changed in this workbook..."
End If
End Sub
You can also filter the sheet (name) where the event to do something, on a similar mechanism.
I could also suggest the specific event code, but I could not understand what "I click on a cell with "Kolli" or "Vikt" in this range visible" does mean. No rows headers, I cannot understand which range to be the one triggering what you need... I mean to restrict the range where the event to be used. Is that row part of the table header? Anyhow, this part should be easy to handle, I think.
In case anyone else is looking for a code to sort a pivot table on column header when you click on it this is the code I use now thanks to FaneDuru.
Private Sub appEvHandler_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
If Sh.Parent.Name <> ThisWorkbook.Name Then
If Sh.Name = "Resultat" And Sh.Range("B3").Value = "Plockare" And Sh.Range("B4").Value = "Snitt" And Sh.Range("B5").Value = "Per timme" And Sh.Range("A11").Value = "Namn" And Sh.Range("B11").Value = "Plockare" Then
If Target.Row = 11 Then
If Target.Column > 2 And Target.Column <= Sh.Cells(4, 2).End(xlToRight).Column Then
On Error Resume Next
Sh.PivotTables("Pivottabell1").PivotFields("Kvittering av").AutoSort xlDescending, Target.Value, Sh.PivotTables("Pivottabell1").PivotColumnAxis.PivotLines(Target.Column - 2), 1
On Error GoTo 0
End If
End If
End If
End If
End Sub
The Pivot table is placed in cell B11, that is why target.column -2 is correct for me.
If yours is in A, then you probably want target.column -1
And the header is on row 11, that is why the code only reacts to target.row = 11
my purpose is to update Bloomberg data and do some calculatations with different tickers. But it seems that VBA will run all the calculations without waiting the data to be updated.
Here is the code:
Application.Calculation = xlCalculationAutomatic
For i = 1 To 3
Call Worksheets("Sheet1").Range("data1").Select 'the cells "data1" contains the function =BDH(ticker, field, start date, end date) to get the information from Bloomberg'
Call Application.Run("RefreshCurrentSelection")
Worksheets("sheet1").Range("d3").Value = Worksheets("sheet1").Range("sum") 'the cells "sum" takes the sum of all BB info'
Anyone know how to fix it?
Bloomberg formulae update is asynchronous so your code won't wait until the update is over. You need to schedule the rest of your code to a later time, check if the data has been updated.
It would look like this:
Application.Run "RefreshCurrentSelection"
update
Sub update()
If (check if the links have been updated) Then
Worksheets("sheet1").Range("d3").Value = Worksheets("sheet1").Range("sum")
Else
Application.OnTime earliestTime:= Date + Time + timeserial(0, 0, 1), _
procedure:="update", Schedule:=True
End if
End Sub
That would ask the Bloomberg API to refresh the data, then wait 1 second, check if the data is updated, wait another second if not etc. until the data is updated and then it would run the range assignment.
You have to split it up between a check and a refresh.
Sub RefreshData()
Application.Calculation = xlCalculationAutomatic
Worksheets("Sheet1").Range("A1:A4").Select 'the cells "data1" contains the function =BDH(ticker, field, start date, end date) to get the information from Bloomberg'
Application.Run "RefreshCurrentSelection"
'Check to see if it filled
Call Check_API
End Sub
Sub Check_API()
If Application.WorksheetFunction.CountIfs(Range("A1:A4"), "#N/A Requesting Data...") > 0 Then
'Check every 3 seconds
Application.OnTime Now + TimeValue("00:00:03"), "Check_API"
Else
'What to do after API filled
Worksheets("sheet1").Range("D3").Value = Application.WorksheetFunction.Sum(Worksheets("Sheet1").Range("A1:A4")) 'the cells "sum" takes the sum of all BB info'
End If
End Sub
Also, instead of focusing on the selection, you can do:
Refresh based on default option setting:
Application.Run "RefreshData"
Refresh current selection:
Application.Run "RefreshCurrentSelection"
Refresh current worksheet:
Application.Run "RefreshEntireWorksheet"
Refresh current workbook:
Application.Run "RefreshEntireWorkbook"
Refresh all workbooks:
Application.Run "RefreshAllWorkbooks"
If you were interested. Still the better option is implementing the v3 COM API class that can be found in Bloomberg's SDK with VBA examples.
Even though your Calls seems odd, you should be able to make use of the value from Application.CalculationState.
There are other ways to "pause" Excel, but you can do a simple While loop that do nothing except waiting for value of Application.CalculationState becomes xlDone.
Do Until Application.CalculationState=xlDone
Loop
I have a workbook with about 50 sheets to be refreshed in a certain order (to avoid #rfr errors, since the sheets build off of one another).
The refresh is done via the EPM add in for Excel. I have activated the FPMXLclient functions and have attempted to write some code. I am very inexperienced with coding and logic. In the workbook the macro needs to start at the last tab, wait for the sheet to refresh, then move on to the next tab (and so on...). Below is an example of some of the VBA code I have written:
Dim refreshList
refreshList = Array("BS Analytic", "Balance Sheet")
'There are more than just the 2 in the array (~50)
Sub test_loop()
Dim I
For I = LBound(refreshList) To UBound(refreshList)
MsgBox refreshList(I)
Next I
End Sub
'Vba to refresh data
Dim client As New EPMAddInAutomation
Sub Refresh_Click()
client.Refresh
End Sub
Sub AFTER_REFRESH()
MsgBox "done"
End Sub
Other info: This involves BPC and SAP too.
Assuming that your addin refreshes the active sheet, something like this in your loop might work:
Dim Sh As Worksheet
Set Sh = WorkSheets(RefreshList(I))
Sh.Activate
Client.Refresh
Why not have each sheet number in the array refreshList and then use For each I in refreshList. That will then propagate the refreshList in the order for each update. If it is also moving from the last sheet backwards you could always do the following:
Sub Refresh_Click
Dim refreshList() As Integer
reDim refreshList(50)
for i = 0 to 49
refreshList(i) = 50 - i
next
For each I in refreshList
Sheets(I).EnableCalculation = false
Sheets(I).EnableCalculation = true
Next
MsgBox "Done"
End Sub
I'm running a macro in a blank Excel 2007 workbook on a PC with a Bloomberg license. The macro inserts Bloomberg functions into sheet1 that pull yield curve data. Some additional functions' results are dependent on the first functions finishing and correctly displaying the Bberg data. When I step through the program it only displays '#N/A Requesting Data . . .' instead of the results of the query, no matter how slowly I go. Because some of the functions are dependent on string and numeric field results being populated, the program hits a run-time error at that code. When I stop debugging -- fully ending running the program -- all the Bberg values that should have populated then appear. I want these values to appear while the program is still running.
I've tried using a combination of DoEvents and Application.OnTime() to return control to the operating system and to get the program to wait for a long time for the data update, but neither worked. Any ideas would be helpful. My code is below. wb is a global-level workbook and ws1 is a global level worksheet.
Public Sub Run_Me()
'Application.DisplayAlerts = False
'Application.ScreenUpdating = False
Call Populate_Me
Call Format_Me
'Application.DisplayAlerts = True
'Application.ScreenUpdating = True
End Sub
Private Sub Populate_Me()
Dim lRow_PM As Integer
Dim xlCalc As XlCalculation
Set wb = ThisWorkbook
Set ws1 = wb.Sheets(1)
'clear out any values from previous day
If wb.Sheets(ws1.Name).Range("A1").Value <> "" Then
wb.Sheets(ws1.Name).Select
Selection.ClearContents
End If
xlCalc = Application.Calculation
Application.Calculation = xlCalculationAutomatic
Range("A1").Value = "F5"
Range("B1").Value = "Term"
Range("C1").Value = "PX LAST"
Range("A2").Select
ActiveCell.FormulaR1C1 = "=BDS(""YCCF0005 Index"",""CURVE_MEMBERS"",""cols=1;rows=15"")"
BloombergUI.RefreshAllStaticData
Range("B2").Select
ActiveCell.FormulaR1C1 = "=BDS(""YCCF0005 Index"",""CURVE_TERMS"",""cols=1;rows=15"")"
BloombergUI.RefreshAllStaticData
Application.OnTime Now + TimeValue("00:00:10"), "HardCode"
'******more code*******'
End Sub
Sub HardCode()
Range("C2").Select
ActiveCell.FormulaR1C1 = "=BDP($A2,C$1)"
BloombergUI.RefreshAllStaticData
End Sub
A way to get around this issue is to put all subs, etc that you want to run after pulling the bloomberg data into a different sub. You must do this each time you call Bloomberg information. If you call another sub in the "master" sub after the Application.OnTime Now +TimeValue("00:00:15"), it will fail- you must put all subs following into a new master sub.
For example:
Instead of
Sub Master1()
Application.Run "RefreshAllStaticData"
Application.OnTime Now + TimeValue("00:00:15"), "OtherSub1"
'This will cause the Bloomberg Data to not refresh until OtherSub2 and 3 have run
OtherSub2
OtherSub3
End Sub
It should be
Sub Master1()
Application.Run "RefreshAllStaticData"
Application.OnTime Now + TimeValue("00:00:15"), "Master2"
End Sub
Sub Master2()
OtherSub1
OtherSub2
OtherSub3
End Sub
Hope that helps
I googled for BloombergUI.RefreshAllStaticData and was immediately taken to this Mr Excel page: http://www.mrexcel.com/forum/showthread.php?t=414626
We are not supposed post answers which are only links in case that link disappears and takes the answer with it. However, I am not sure I understand the question or the answer well enough to summarise it.
The Google link will probably exist for the forseeable future.
Within Mr Excel, the chain is: MrExcel Message Board > Question Forums > Excel Questions > Bloomberg links and macros.
The key information appears to be:
On your Bloomberg terminal if you type in WAPI < GO > you will find listings of the Bloomberg API and downloadable examples.
Using the helpfile information in that area we can build a more robust solution to this using the Bloomberg Data Type Library. Go to Tools | References and add a reference to this library. This code can then be used to populate the cells:
Sub Test2()
Dim vResults, vSecurities, vFields
Dim objBloomberg As BLP_DATA_CTRLLib.BlpData
'fill our arrays - must be 1 dimension so we transpose from the worksheet
With Application.WorksheetFunction
vSecurities = .Transpose(Sheet1.Range("B2:B4").Value)
vFields = .Transpose(.Transpose(Range("C1:H1").Value))
End With
Set objBloomberg = New BLP_DATA_CTRLLib.BlpData
objBloomberg.AutoRelease = False
objBloomberg.Subscribe _
Security:=vSecurities, _
cookie:=1, _
Fields:=vFields, _
Results:=vResults
Sheet1.Range("C2:H4").Value = vResults
End Sub
Once you have tried out Mr Excel's solution, perhaps you could update this answer for the benefit of future visitors.
I gathered some information from around the web and wrote what imho is an improved version in comparison with everything I have found so far:
Private WaitStartedAt As Double
Private Const TimeOut As String = "00:02:00"
Public Function BloomCalc(Callback As String) As Boolean
Dim rngStillToReceive As Range
Dim StillToReceive As Boolean
Dim ws As Worksheet
StillToReceive = False
If WaitStartedAt = 0 Then
WaitStartedAt = TimeValue(Now())
End If
If TimeValue(Now()) >= WaitStartedAt + TimeValue(TimeOut) Then
GoTo errTimeOut
End If
For Each ws In ActiveWorkbook.Worksheets
Set rngStillToReceive = ws.UsedRange.Find("*Requesting Data*", LookIn:=xlValues)
StillToReceive = StillToReceive Or (Not rngStillToReceive Is Nothing)
Next ws
If StillToReceive Then
BloomCalc = False
Application.OnTime Now + (TimeSerial(0, 0, 1)), Callback
Else
WaitStartedAt = 0
BloomCalc = True
End If
Exit Function
errTimeOut:
Err.Raise -1, , "BloomCalc: Timed Out. Callback = " & Callback
End Function
It should an arbitrary task by calling a sub like DoSomething()
Public Sub DoSomething()
DoSomethingCallback
End Function
That calls a "callback" function that will call itself until either the data has been refreshed or the time limit reached
Public Sub AutoRunLcbCallback()
If BloomCalc("AutoRunLcbCallback") Then
MsgBox "Here I can do what I need with the refreshed data"
' for instance I can close and save the workbook
ActiveWorkbook.Close True
End If
End Sub
Any comment is appreciated. A possible improvement might be to allow the workbook and / or worksheet to be an input of the function but I really didn't see the need for that.
Cheers
Hello there I think I have found a solution to this problem and I really want to share this with you guys.
Before starting with the real answer I want to make sure everyone understands how Application.OnTime actually works. And If you already know then you can safely skip to THE SOLUTION below.
Let's make a TOY EXAMLPE example with two subroutines Sub First() and Sub Second() and one variable x that is declared outside, so that it has scope inside the whole Module
Dim x as integer
Sub First()
x = 3
Application.OnTime Now + TimeSerial(0, 0, 2), "Sub2"
x = 2*x
End Sub
Sub Second()
x = x + 1
End Sub
I thought that the commands were executed in the following order:
x = 3
Application.OnTime Now + TimeSerial(0, 0, 2), "Sub2"
Then after 2 seconds of wait, in Sub Second() x = x + 1, hence 4
Finally we go back to Sub First() where x = 2*x , so that in the end x is equal to 8.
It turns out that this is not the way VBA operates; what happens instead is:
x = 3
Application.OnTime Now + TimeSerial(0, 0, 2), "Sub2"
Here the remaing code in Sub First() is executed until THE END, before switching to Sub Second().
So x = 2*x is executed right away along with every line of code that appears until the end of Sub First(). Now x is equal to 6.
Finally, after 2 seconds of waiting it executes the instruction in Sub Second(), x = x + 1, so that in the end x is equal to 7
This happens independently of how much time you make the application wait. So for instance if in my example, after
Application.OnTime Now + TimeSerial(0, 0, 2), "Sub2"
VBA took 10 seconds to execute the line
x = 2*x
it would still have to finish the execution of that command before switching to Sub Second().
WHY IS THIS IMPORTANT?
Because in the light of what I just explained I can now show you my solution to the OP question. Then you can adapt it to your needs.
And YES!!! This works with For Loops too!
THE SOLUTION
I have two subroutines:
BLPDownload() one where I refresh a workbook and I have to wait for the values to be dowloaded in order to execute some other code ...
BLPCheckForRefresh() where I check if all data have been downloaded
So just like before, I declare two variables with Module-Level Scope
Dim firstRefreshDone As Boolean, Refreshing As Boolean
Sub BLPDownload()
CHECK:
What I do right below is to:
check if I already told VBA to Refresh the workbook. Of course the first time you run the macro you have not; hence firstRefreshDone = False and it steps into this block of the if statement.
next it calls the other Sub BLPCheckForRefresh() and it exits the current Subroutine.
And this is the trick. To Exit the Subroutine after calling Application.OnTime*
Inside BLPCheckForRefresh() what happens is
that I set the value of firstRefreshDone = True
check if, in the UsedRange, I have cells wiht #N/A Requesting Data. If I have, the value of Refreshing = True.
finally I call back the Sub BLPDownload()
If Not firstRefreshDone Then
Application.Run "RefreshEntireWorkbook"
Application.Run "RefreshAllStaticData"
Application.OnTime Now + TimeSerial(0, 0, 4), "BLPCheckForRefresh"
Exit Sub
This time though, firstRefreshDone = True so, if also the refreshing is finished it goes to AFTER_REFRESH where you can put all the code you want, else ...
ElseIf Not Refreshing Then
GoTo AFTER_REFRESH
if the refresh is not finished, i.e. if I have cells wiht #N/A Requesting Data it calls the other Sub BLPCheckForRefresh() and it exits the current Subroutine again.
This funny game goes on and on until we have no more #N/A Requesting Data in our UsedRange
Else
Refreshing = False
Application.OnTime Now + TimeSerial(0, 0, 4), "BLPCheckForRefresh"
Exit Sub
End If
AFTER:
some code ...
End Sub
This is the sub where I check if refreshing is done.
Sub BLPCheckForRefresh()
Dim rng As Range, cl As Range
Set rng = Foglio1.UsedRange
As explained above here I set the value of firstRefreshDone = True
firstRefreshDone = True
And this is the loop where i go through each cell in the usedrange looking for #N/A Requesting Data
On Error Resume Next
For Each cl In rng
If InStr(cl.Value2, "#N/A Request") > 0 Then
Refreshing = True
Exit For
End If
Next cl
On Error GoTo 0
Finally I call back the Sub BLPDownload()
Call BLPDownload
End Sub
So this is my solution. I works for me and with another dirty trick that exploits always the GoTo statements and another Module-Level Scope variable that keeps count of the number of iteration it is possible to use this structure in For Loops too.
That being said I want to point out that in my opinion the best solution to this problem is to use Bloomberg API as suggested by Tony Dallimore.
Hope this helps!!