(Print this page)

Getting Yahoo stock prices
Published date: Wednesday, March 27, 2019
On: Moer and Éric Moreau's web site

15 years ago, in March 2004, my article was about using the WebRequest object to retrieve stock prices from Yahoo. Back in the days, I was using that data to compare against prices I receive from other vendors. I still need that data from a source like Yahoo (which is not my main source) because even paid data is not a guarantee that it is 100% accurate!

The method described in there does no longer work because Yahoo blocked some mechanism in 2017, namely the Yahoo Finance API. I don’t blame them. They are providing the data for free and they are expecting some real people to see the ads they put in your face while you navigate the site (and hopefully people clicking on them to make some money). Same thing happened to the Google Finance API.

But the good news for us is that a workaround has been found and the Yahoo finance service is still available.

A wrapper library

The workaround is not my own creation. I don’t want to take the credit for it. It wouldn’t be fair!

It is a library created by Karl Wan that he made available on github at https://github.com/lppkarl/YahooFinanceApi.

Available source code

I will provide here some code using the wrapper library in both VB and C#. The solution was created using Visual Studio 2017 but should also work in previous versions as long you can target the .Net Framework 4.6.1 or above (a requirement from the wrapper library).

Adding a reference to the NuGet package

The very first thing you will need to do will be to add a reference to the NuGet package which will also bring some other dependencies as shown in figure 1.

At the time of writing this article, there is a v3 beta2 of the library available. I don’t recommend using it as I don’t have good experiences with it. Better use the last stable version which is (still at the time of writing) 2.1.2.

Figure 1: Adding a reference to the NuGet package

Building the test UI

As shown in figure 2, I have created a simple Windows Forms UI to test the library. The 3 important controls are:

  • A textbox to let you enter tickers (you can enter more then one by using commas)
  • A button to launch the process
  • A listbox to display the results

Figure 2: The demo application in action

My “complex” code

When you hit the “Get prices” button, an asynchronous query is sent to Yahoo requesting specific fields (more on this later).

This is the code calling the library.

Private Async Sub btnGetPrices_Click(sender As Object, e As EventArgs) Handles btnGetPrices.Click
    Dim securities = Await Yahoo.
            Symbols(txtTickers.Text.Split(","c)).
            Fields(Field.Symbol, Field.RegularMarketPrice, Field.RegularMarketTime, Field.Currency, Field.LongName).
            QueryAsync()
    DisplayResults(securities)
End Sub
private async void btnGetPrices_Click(object sender, EventArgs e)
{
    var securities = await Yahoo
        .Symbols(txtTickers.Text.Split(','))
        .Fields(Field.Symbol, Field.RegularMarketPrice, Field.RegularMarketTime, Field.Currency, Field.LongName)
        .QueryAsync();
    DisplayResults(securities);
}

The first thing you notice is that we are specifying the tickers for which we want to get prices. My demo is taking that information from the textbox in which you can set a single ticker as well as a comma separated list of tickers because we are using the Split function to convert the textbox values to an array of string as required by the Symbols method.

The second thing you see is that we are specifying the fields we want to retrieve. If you click on the Field keyword and hit F12 (or right-click the Field keywork and select Go To Definition), you will find the list of the 65 available fields. Just like when you are querying a database, you are trying not to do a SELECT * to reduce the number of columns being returned. Here too you will try to be as specific as needed for the very same reasons. In my example, I am specifically requesting 4 fields. There are also other ways to specify the fields, but I prefer a lot this one. For example, you can pass an array of strings, but this method can lead to typos.

The query is returning a dictionary containing objects of type Security in the values. The demo application here will simply display these values into the listbox like this:

Private Sub DisplayResults(securities As IReadOnlyDictionary(Of String, Security))
    If lstResults.Items.Count > 0 Then lstResults.Items.Add(String.Empty)

    For Each objSecurity As Security In securities.Values
        Dim strCurrency As String
        Try
            'for some reason, the currency is not always available!
            strCurrency = objSecurity.Currency
        Catch
            strCurrency = "(unknown)"
        End Try

        Dim strLongName As String
        Try
            strLongName = objSecurity.LongName
        Catch
            strLongName = "(unknown)"
        End Try

        Dim dtLatestPrice As DateTime = UnixTimeStampToDateTime(objSecurity.RegularMarketTime)
        lstResults.Items.Add($"{objSecurity.Symbol} @ {objSecurity.RegularMarketPrice} {strCurrency} - {dtLatestPrice} - {strLongName}")
    Next

    lstResults.SelectedIndex = lstResults.Items.Count - 1
End Sub
private void DisplayResults(IReadOnlyDictionary<string, Security> securities)
{
    if (lstResults.Items.Count > 0)
        lstResults.Items.Add(string.Empty);

    foreach (Security objSecurity in securities.Values)
    {
        string strCurrency;
        try
        {
            //for some reason, the currency is not always available!
            strCurrency = objSecurity.Currency;
        }
        catch
        {
            strCurrency = "(unknown)";
        }

        string strLongName;
        try
        {
            strLongName = objSecurity.LongName;
        }
        catch
        {
            strLongName = "(unknown)";
        }

        DateTime dtLatestPrice = UnixTimeStampToDateTime(objSecurity.RegularMarketTime);

        lstResults.Items.Add($"{objSecurity.Symbol} @ {objSecurity.RegularMarketPrice} {strCurrency} - {dtLatestPrice:F} - {strLongName}");
    }

    lstResults.SelectedIndex = lstResults.Items.Count - 1;
}

It is a simple for each loop looping through the values of the dictionary.

Some fields are to be used with extreme caution. For example, Currency and LongName don’t always have a value, they can throw exceptions when you try to get their values, so you must deal with it.

Other fields like the date of the latest quote (RegularMarketTime) is not in a datetime format. The field contains a long integer representing the number of seconds after January 1st, 1970. To convert it to a more human readable value, you can use a small function like this one:

Public Shared Function UnixTimeStampToDateTime(ByVal unixTimeStamp As Long) As DateTime
    Dim dtDateTime As DateTime = New DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)
    dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime()
    Return dtDateTime
End Function
public static DateTime UnixTimeStampToDateTime(long unixTimeStamp)
{
    DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
    dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime();
    return dtDateTime;
}

If you are trying to request an invalid ticker, Yahoo will just skip that ticker and it won’t include it in the returned values.

Conclusion

The library does a lot more than what is demoed here. For example, it has features to retrieve historical prices, splits history and dividends history.

I don’t blame the free data provider to change their mechanism every so often. It is part of the agreement that the data from Yahoo is not supposed to be used as your main data source of stock prices. And I only use their data to compare against my paid sources to see if they stand correct. I found errors in the past and now need to protect my … reports!

Anyway, anything sites like Yahoo are doing to change their mechanisms, somebody will always find a workaround to it! Hopefully, that won’t happen too soon.

One last thing, if you are curious about the data exchanged between your application and Yahoo (the URL sent to Yahoo and the JSON answer), you can always rely on tools like the free Fiddler Web Debugger.


(Print this page)