Option Explicit
Sub GetYahooDataFromJSON()
Dim DesiredFields As Variant, NameOfStock As String
Dim RangeField As String, RangeUnit As String, s As Variant, IntervalField As String, DateOrder As Integer
Dim DateOffset As Double, ErrorMsg As String, FilterFilterOutRows As Boolean
Dim BeginDate As Date, EndDate As Date
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.DisplayStatusBar = False
Application.DisplayAlerts = False
With ActiveSheet
NameOfStock = .Range("StockSymbol")
RangeField = CStr(.Range("Range"))
s = CStr(.Range("RangeUnit"))
Select Case s:
Case "Maximum": RangeUnit = "max": RangeField = ""
Case "YTD": RangeUnit = "ytd": RangeField = ""
Case "Years": RangeUnit = "y"
Case "Months": RangeUnit = "mo"
Case "Weeks": RangeUnit = "wk"
Case "Days": RangeUnit = "d"
Case "Hours": RangeUnit = "h"
Case "Minutes": RangeUnit = "m"
End Select
RangeField = RangeField & RangeUnit
s = CStr(.Range("Interval"))
Select Case s:
Case "1 minute": IntervalField = "1m"
Case "2 minutes": IntervalField = "2m"
Case "5 minutes": IntervalField = "5m"
Case "10 minutes": IntervalField = "10m"
Case "15 minutes": IntervalField = "15m"
Case "30 minutes": IntervalField = "30m"
Case "1 hour": IntervalField = "1h"
Case "2 hour": IntervalField = "2h"
Case "4 hour": IntervalField = "4h"
Case "6 hour": IntervalField = "6h"
Case "1 day": IntervalField = "1d"
Case "1 week": IntervalField = "1wk"
Case "1 month": IntervalField = "1mo"
Case "3 months": IntervalField = "3mo"
End Select
s = CStr(.Range("DateOrder"))
If s = "Ascending" Then DateOrder = xlAscending Else DateOrder = xlDescending
DateOffset = CDbl(.Range("TimeOffset"))
If (.Range("FilterBadRows")) = "Yes" Then FilterFilterOutRows = True Else FilterFilterOutRows = False
BeginDate = .Range("StartingDate"): EndDate = .Range("EndingDate")
DesiredFields = Array("timestamp", "open", "high", "low", "close", "volume")
If Right(IntervalField, 1) <> "m" And Right(IntervalField, 1) <> "h" Then
DesiredFields = Array("timestamp", "open", "high", "low", "close", "adjclose", "volume")
End If
.Shapes("Shape1").Fill.ForeColor.RGB = RGB(0, 200, 0)
.Shapes("Shape1").TextFrame.Characters.Text = "Asking for data..."
Application.ScreenUpdating = True
ErrorMsg = GetYahooRequest(.Range("TableOrigin"), DesiredFields, NameOfStock, RangeField, _
IntervalField, DateOrder, DateOffset, FilterFilterOutRows, BeginDate, EndDate)
If ErrorMsg <> "" Then MsgBox (ErrorMsg)
.Shapes("Shape1").Fill.ForeColor.RGB = RGB(200, 0, 0)
.Shapes("Shape1").TextFrame.Characters.Text = "Click to get historic data"
Application.ScreenUpdating = True
End With
Range("A2", Range("A2").End(xlDown).End(xlToRight)).Copy
End Sub
Function GetYahooRequest( _
DestinationRange As Range, _
FieldsToExtract As Variant, _
StockSymbol As String, _
RangePart As String, _
IntervalPart As String, _
Optional DateOrder As Integer = xlDescending, _
Optional TOffset As Double = 0, _
Optional FilterBadPrices As Boolean = False, _
Optional StartingDate As Date = 0, _
Optional EndingDate As Date = 0 _
) As String
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.DisplayStatusBar = False
Application.DisplayAlerts = False
On Error GoTo ErrorHandler
Dim strUrl As String
strUrl = "https://query1.finance.yahoo.com/v7/finance/chart/" & StockSymbol & "?range=" & RangePart & "&interval=" & IntervalPart & "&indicators=quote&includeTimestamps=true"
Dim objRequest As WinHttp.WinHttpRequest
Dim strResponse As String
Dim N() As String
Dim RawOutputArray() As Variant, FilteredOutputArray() As Variant
Dim FilterOutRows() As Variant, FilteredRowCount As Integer: FilteredRowCount = 0
Dim FieldName As Variant
Dim xRow As Integer: xRow = 0
Dim RowCount As Integer, ColumnCount As Integer
Dim xColumn As Integer
Dim StrX As String
Dim ParseError As String
Dim DecSeparator As String: DecSeparator = Application.International(xlDecimalSeparator)
Dim FilterOutput As Boolean
If FilterBadPrices = True Or StartingDate <> 0 Or EndingDate <> 0 Then FilterOutput = True
Set objRequest = New WinHttp.WinHttpRequest
With objRequest
.Open "GET", strUrl, True
.setRequestHeader "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"
.send
.waitForResponse
strResponse = .responseText
End With
DestinationRange.CurrentRegion.ClearContents
For Each FieldName In FieldsToExtract
Dim HoldArray() As String
ParseError = ParseField(strResponse, CStr(FieldName), HoldArray)
If ParseError <> "" Then GoTo ErrorHandler
If xColumn = 0 Then
RowCount = UBound(HoldArray) + 1
ColumnCount = UBound(FieldsToExtract) + 1
ReDim RawOutputArray(RowCount, ColumnCount - 1)
ReDim FilterOutRows(RowCount)
ReDim DateFilter(RowCount)
For xRow = 1 To RowCount: FilterOutRows(xRow) = False: Next xRow
End If
RawOutputArray(0, xColumn) = StrConv(FieldName, vbProperCase)
For xRow = 1 To RowCount
StrX = HoldArray(xRow - 1)
Select Case FieldName
Case "timestamp":
Dim LocalDate As Date
If IsNumeric(StrX) Then
LocalDate = CDate(StrX / 86400) + 25569 + (TOffset / 24)
RawOutputArray(xRow, xColumn) = LocalDate
If StartingDate <> 0 Then If LocalDate <= StartingDate Then FilterOutRows(xRow) = True
If EndingDate <> 0 Then If LocalDate >= EndingDate Then FilterOutRows(xRow) = True
Else
RawOutputArray(xRow, xColumn) = StrX
FilterOutRows(xRow) = True
End If
Case Else:
If IsNumeric(StrX) Then
RawOutputArray(xRow, xColumn) = CDbl(Replace(StrX, ".", DecSeparator))
If FieldName <> "volume" And RawOutputArray(xRow, xColumn) = 0 Then FilterOutRows(xRow) = True
Else
RawOutputArray(xRow, xColumn) = (HoldArray(xRow - 1))
FilterOutRows(xRow) = True
End If
End Select
Next xRow
xColumn = xColumn + 1
Next
For xRow = 1 To RowCount
If FilterOutRows(xRow) = True Then FilteredRowCount = FilteredRowCount + 1
Next xRow
If FilterOutput = False Or FilteredRowCount = 0 Then
DestinationRange.Resize(UBound(RawOutputArray, 1) + 1, UBound(RawOutputArray, 2) + 1) = RawOutputArray
Else
ReDim FilteredOutputArray(RowCount - FilteredRowCount, ColumnCount - 1)
Dim xPointer As Integer: xPointer = 0
For xRow = 0 To RowCount
If FilterOutRows(xRow) = False Then
For xColumn = 0 To ColumnCount - 1
FilteredOutputArray(xPointer, xColumn) = RawOutputArray(xRow, xColumn)
Next xColumn
xPointer = xPointer + 1
End If
Next xRow
DestinationRange.Resize(UBound(FilteredOutputArray, 1) + 1, UBound(FilteredOutputArray, 2) + 1) = FilteredOutputArray
End If
DestinationRange.CurrentRegion.Sort key1:=DestinationRange.Offset(, 0), order1:=DateOrder, Header:=xlYes
GetYahooRequest = ""
Exit Function
ErrorHandler:
If xColumn = 0 Then
If ParseError <> "" Then ParseError = ParseError & " Maybe its a bad symbol." Else ParseError = "No response. Maybe the network is unavailable."
End If
GetYahooRequest = ParseError
End Function
Function ParseField(ByRef ResponseString As String, strKeyName As String, ByRef strHoldArray() As String) As String
Dim strDQ As String: strDQ = """"
Dim StartPos As Long, EndPos As Long, x As Long
Dim StringToFind As String
Dim CSVField As String
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.DisplayStatusBar = False
Application.DisplayAlerts = False
StringToFind = """" & strKeyName & """" & ":["
Do
StartPos = x
x = InStr(StartPos + 1, ResponseString, StringToFind, vbBinaryCompare)
Loop While x <> 0
If StartPos = 0 Then
ParseField = "Unable to find key " & strKeyName & " in JSON response."
GoTo ErrorHandler
End If
StartPos = StartPos + Len(StringToFind)
EndPos = InStr(StartPos, ResponseString, "]", vbBinaryCompare)
If EndPos = 0 Then
ParseField = "Unable to find closing ] for " & strKeyName & " in JSON response."
GoTo ErrorHandler
End If
CSVField = Mid(ResponseString, StartPos, EndPos - StartPos)
strHoldArray = Split(CSVField, ",")
ParseField = ""
ErrorHandler:
End Function