Selecting a Stock Market Data (Web) API: Not So Simple

10 minute read

I am sometimes asked if I recommend any stock market data (web) API for a personal use, especially because I mention Alpha Vantage and Tiingo in a couple of posts.

In this post, I will describe part of the thought process and of the due diligence which led me to select these financial market data providers.

Notes:

  • Some code snippets for pulling data out of different stock market data web APIs are provided in this post, but they require an API key to function. So, if you want to experiment with them, you will need to register on their websites.

List of requirements

Let’s start with the list of requirements that a stock market data API should fulfill:

  • The API must provide current and historical end of day close prices1 for the U.S. stock market

This choice is representative of my audience, because most people I discuss with are mainly interested in the daily close prices of U.S. ETFs.

  • The API must be accessible through a web API

While I agree that scheduling a cron job to connect to an FTP server and retrieve the latest end of day prices every night could have been fun in the 2000s, a web API is now the best interface for doing so.

In addition, having a web API to retrieve stock market data interacts nicely with Portfolio Optimizer :wink:!

This requirement eliminates companies like Norgate Data.

  • The API must properly manage corporate actions impacting prices, like stock splits and dividends2

Failing to account for such corporate actions will distort the calculation of any return or risk indicator.

For example, Hartford Funds establish that dividends accounted for ~40% of the total S&P 500 return on average over the last century3, as illustrated on figure 1.

Dividends Contribution to S&P 500 Total Return By Decade
Figure 1. Dividends Contribution to S&P 500 Total Return by Decade. Source: Hartford Funds

For people using Google Sheets, this eliminates Google Finance, because dividends are not properly managed[^1].

  • The API must provide a generous free use plan

Paying hundreds or thousands of dollars monthly for a personal use is not the best possible deal…

This eliminates fintechs like Intrinio or more established mastondonts like Refinitiv, which do not provide free use plans.

In addition, depending on what is considered as generous, this might also eliminate companies offering a very restricted free use plan like EOD Historical Data.

List of providers

Browsing Reddit, reading the numerous What are the best stock market APIs in 202x? web pages4, I came up with the following list of stock market data web API providers matching the requirements:

I know that some other providers are missing, like Polygon.io, but please bear with me.

First test - Consistency of SPY ETF close and adjusted close prices

I will analyze the daily close prices of the SPY ETF, adjusted for dividends, over the period May 5, 2021 - May 4, 2022.

The SPY ETF being the largest ETF in the world5, any issue with these tests will result in an immediate elimination of the provider.

Alpha Vantage

The endpoint to retrieve daily adjusted close prices is:

    GET https://www.alphavantage.co/query?function=TIME_SERIES_DAILY_ADJUSTED&symbol=SPY&outputsize=full&apikey=KEY

Unfortunately, this endpoint was restricted a couple of months ago to either:

This restriction eliminates Alpha Vantage6, but more on Alpha Vantage later.

EOD Historical Data

The endpoint to retrieve daily adjusted close prices is:

    GET https://eodhistoricaldata.com/api/eod/SPY.US?api_token=KEY&period=d&fmt=json

No particular remark, prices seem fine at first sight.

Finnhub

The endpoint to retrieve daily adjusted close prices is:

    GET https://finnhub.io/api/v1/stock/candle?symbol=SPY&resolution=D&from=1588660370&to=1631627048&token=KEY

Problem is, daily prices are adjusted for stock splits but not for dividends, and the dividends endpoint is restricted to users of a paid usage plan.

So, it is not possible to retrieve daily adjusted close prices with Finnhub.

This restriction eliminates Finnhub.

IEX Cloud

The endpoint to retrieve daily adjusted close prices is:

    GET https://cloud.iexapis.com/stable/stock/spy/chart/1y?token=KEY

No particular remark, prices seem fine at first sight.

Marketstack

The endpoint to retrieve daily adjusted close prices is:

    GET http://api.marketstack.com/v1/eod?access_key=KEY&symbols=SPY&date_from=2020-04-23&date_to=2022-05-03&limit=1000

Some remarks:

  • I made the API call for Marketstack at ~08:30 GMT+2, on May, 5 2022, but I received no close price for May 4, 2022

For an end of day endpoint, such a lag is annoying.

  • The output fields close and adj_close contain the same price, for example on May 5, 2021:
    {..., "data":[{"open":417.38,"high":417.63,"low":415.15,"close":415.75,"volume":39960661.0,"adj_high":null,"adj_low":null,"adj_close":415.75,"adj_open":null,"adj_volume":null,"split_factor":1.0,"dividend":0.0,"symbol":"SPY","exchange":"ARCX","date":"2021-05-05T00:00:00+0000"},...]}

This is incorrect, because several dividend distributions took place over the period May 5, 2021 - May 4, 2022, as illustrated in figure 2 taken from the SPY ETF issuer’s website.

SPY ETF dividends from  September 17, 2021
Figure 2. SPY ETF dividends from September 17, 2021. Source: SPDR

These dividends should have resulted in adjusted close prices being different from close prices!

The issue seems to come from the dividend information stored on Marketstack side, because the latest dividend reported by Marketstack for the SPY ETF is the dividend distributed on September 17, 2021:

    GET http://api.marketstack.com/v1/dividends?access_key=KEY&symbols=SPY&date_from=2020-01-01
    {..., "data":[{"date":"2021-09-17","dividend":1.43,"symbol":"SPY"},...]}

In other words, two dividend distributions seem to be missing from Marketstack’s database.

This lack of data quality eliminates Maketstack.

Tiingo

The endpoint to retrieve daily adjusted close prices is:

    GET https://api.tiingo.com/tiingo/daily/spy/prices?startDate=2020-1-1&format=json&resampleFreq=daily&token=KEY

No particular remark, prices seem fine at first sight.

Twelve Data

The endpoint to retrieve daily adjusted close prices is:

    GET https://api.twelvedata.com/time_series?apikey=KEY&interval=1day&symbol=SPY&type=etf&format=JSON&start_date=2020-12-29

From the Twelve Data documentation, the provided close prices should be adjusted to both splits and dividends.

Problem is, they are not, for example on May 5, 2021:

    {..., "values": [{"datetime":"2021-05-05","open":"417.38000","high":"417.63000","low":"415.14999","close":"415.75000","volume":"60162200"},...]}

By the way, it is not possible to check if the issue is with dividend information stored on Twelve Data side, because the dividend endpoint is restricted to users subscribing a paid usage plan.

So, it seems not possible to retrieve daily adjusted close prices with Twelve Data, which eliminates this provider.

Second test - Deep dive into SPY ETF adjusted close prices computation

I will now check whether the three remaining stock market data web API providers compute adjusted close prices according to the industry standards, established by the Center for Research in Security Prices (CRSP).

In particular:

In daily databases, dividends are reinvested in the security on the Ex-Distribution Date.

For this, I will use:

  • The unadjusted close prices of the SPY ETF over the period May 5, 2021 - May 4, 2022 from each provider
  • The SPY ETF dividends over the period May 5, 2021 - May 4, 2022, taken from the SPY ETF issuer’s website
  • The Portfolio Optimizer endpoint /assets/prices/adjusted, which computes adjusted close prices from unadjusted close prices and dividends following the CRSP methodology

EOD Historical Data

The SPY ETF adjusted close price on May 5, 2021 provided by EOD Historical Data is 410.347$:

    [{"date":"2021-05-05","open":417.38,"high":417.63,"low":414.94,"close":415.75,"adjusted_close":410.347,"volume":60162207},...]

By comparison, the reference adjusted close price on this date, as computed by Portfolio Optimizer, should be 410.3167201489105$:

    {assets:[ {assetAdjustedPrices: [{ date: '2021-05-05', dividendAdjustedClose: 410.3167201489105, fullyAdjustedClose: 410.3167201489105 },...]} ]}

So, while the EOD Historical Data documentation states that their calculations follow the CRSP guidelines, it either does not seem so or there is a rounding error somewhere!

IEX Cloud

The SPY ETF adjusted close price on May 5, 2021 provided by IEX Cloud is 410.3496$:

    [{"close":415.75,"high":417.63,"low":415.15,"open":417.38,"symbol":"SPY","volume":39960661,"id":"HISTORICAL_PRICES","key":"SPY","subkey":"","date":"2021-05-05","updated":1648509657000,"changeOverTime":0,"marketChangeOverTime":0,"uOpen":417.38,"uClose":415.75,"uHigh":417.63,"uLow":415.15,"uVolume":39960661,"fOpen":411.9584,"fClose":410.3496,"fHigh":412.2052,"fLow":409.7574,"fVolume":39960661,"label":"May 5, 21","change":0,"changePercent":0},...]

Again, the adjusted close price is incorrect when compared to the reference adjusted close price computed by Portfolio Optimizer, but as there is no mention of how adjusted close prices are computed in the IEX Cloud documentation, it could perfectly be that IEX Cloud implements its own different-but-correct logic.

Digging deeper and reverse-engineering IEX Cloud adjusted close prices, their logic seems to be to reinvest a dividend at the close price of the day before the ex-distribution date minus the dividend.

This is obviously not possible in practice and will artificially distort the total returns!

Tiingo

The SPY ETF adjusted close price on May 5, 2021 provided by Tiingo is 410.3167201489$:

    [{"date":"2021-05-05T00:00:00.000Z","close":415.75,"high":417.63,"low":415.15,"open":417.38,"volume":39960661,"adjClose":410.3167201489,"adjHigh":412.1721511384,"adjLow":409.7245613225,"adjOpen":411.9254182941,"adjVolume":39960661,"divCash":0.0,"splitFactor":1.0},..]

This matches with the reference adjusted close price computed by Portfolio Optimizer and confirms that:

  • Tiingo is following the CRSP guidelines for computing adjusted close prices

This in itself is already a feat v.s. EOD Historical Data and IEX Cloud!

  • Tiingo is computing adjusted close prices with no rounding errors, as stated on their website

Tiingo offers the full precisions of prices, dividends, and distributions. This means we don’t just stop at 3, 4, 5 decimals places, but actually show you the entire picture. We know how important backtesting is, so we understand the importance of ensuring distributions remain accurate.

Third test - Consistency of Realty Income stock close and adjusted close prices

Time to get a little more picky :smiling_imp:.

I will proceed with the same tests as before, this time using the stock of the Realty Income company.

Realty Income being an S&P500 company, any important issue with these tests will result in an immediate elimination of the web API provider.

EOD Historical Data

The adjusted and unadjusted close prices on May 10, 2021 provided by EOD Historical Data are:

    [{"date":"2021-05-10","open":66.276,"high":66.6149,"low":65.3657,"close":65.4045,"adjusted_close":62.9144,"volume":3342600},...]

Here, the close price of 65.4045$ does not match the official close price of 67.54$ published on the Realty Income website, as illustrated on figure 3 taken from this website.

Realty Income close stock price on May 10, 2021
Figure 3. Realty Income close stock price on May 10, 2021. Source: Realty Income website

This lack of data quality eliminates EOD Historical Data.

IEX Cloud

The adjusted and unadjusted close prices on May 10, 2021 provided by IEX Cloud are:

    [{"close":67.54,"high":68.79,"low":67.5,"open":68.44,"symbol":"O","volume":3342600,"id":"HISTORICAL_PRICES","key":"O","subkey":"","date":"2021-05-10","updated":1651529845000,"changeOverTime":0.21518531845987782,"marketChangeOverTime":0.21518531845987782,"uOpen":68.44,"uClose":67.54,"uHigh":68.79,"uLow":67.5,"uVolume":3342600,"fOpen":65.6546,"fClose":64.7912,"fHigh":65.9903,"fLow":64.7528,"fVolume":3342600,"label":"May 10, 21","change":-0.5599999999999881,"changePercent":-0.0082},...]

The close price of 67.54$ is fine.

The adjusted close price of 64.7912$ is different from the reference adjusted close price of 64.7657704126792$ computed by Portfolio Optimizer using Realty Income dividend information and IEX Cloud unadjusted close prices, but since IEX Cloud has its own logic to adjust prices, such a difference is expected.

So, is everything all right? Unfortunately, no.

There was a corporate action on November 15, 2021, with the spin-off of Orion Office REIT Inc..

This spin-off resulted in the distribution of one Orion share per ten shares of Realty Income, as illustrated in figure 4 taken from the Realty Income website.

Realty Income spin-off
Figure 4. Realty Income spin-off. Source: Realty Income website

Problem is, the result of this spin-off has not been taken into account by IEX Cloud in the computation of the adjusted close prices…

This lack of a proper management of corporate actions eliminates IEX Cloud.

Tiingo

The adjusted and unadjusted close prices on May 10, 2021 provided by Tiingo are:

    [{"date":"2021-05-10T00:00:00.000Z","close":67.54,"high":68.79,"low":67.5,"open":68.44,"volume":3342600,"adjClose":62.7575294697,"adjHigh":63.919017652,"adjLow":62.7203618478,"adjOpen":63.593800961,"adjVolume":3449563,"divCash":0.0,"splitFactor":1.0},..]

The close price of 67.54$ is fine.

The result of the Orion Office REIT Inc. spin-off is materialized by a stock split of 1 to 1.0327 on November 15, 2021:

    [{"date":"2021-11-15T00:00:00.000Z","close":71.14,"high":71.37,"low":70.41,"open":71.37,"volume":7029408,"adjClose":69.6314631499,"adjHigh":69.8565859573,"adjLow":68.9169429348,"adjOpen":69.8565859573,"adjVolume":7029408,"divCash":0.0,"splitFactor":1.032},..]

The adjusted close price of 62.7575294697$ matches the adjusted close price of 62.75752946965038$ computed by Portfolio Optimizer using this stock split information, Realty Income dividend information and Tiingo unadjusted close prices.

All good for Tiingo!

Fourth test - Access to historical data

The only remaining stock market data web API provider is Tiingo, so, I could stop here.

Nevertheless, I would like to highlight some differences between EOD Historical Data, IEX Cloud and Tiingo w.r.t. the access to historical data.

Historical prices

For various reasons (backtesting activities, computation of misc. indicators…), access to historical prices is sometimes required.

With a free use plan:

  • EOD Historical Data provides access to one year of price history
  • IEX Cloud provides access to five year of price history, although accessing it is not feasible in practice due to the IEX Cloud credit system
  • Tiingo provides access to the whole (available) price history8

Historical dividends and splits

The previous tests demonstrated that adjusted close prices vary between providers.

In order to be independent of the calculations of any provider, as well as to possibly implement a specific adjustment logic - like what AllocateSmartly people are doing by delaying a dividend until 3-days after the ex-dividend date -, one solution is to compute oneself adjusted prices from unadjusted prices and corporate actions.

This requires access to:

  • Historical prices
  • Historical dividends and stock splits
  • A logic to adjust prices

The first point has already been discussed in the previous section.

The second point relies on what is accessible through the stock market data web API provider.

For example, for dividends:

The third point is dependent on the exact use-case, but the Portfolio Optimizer endpoint /assets/prices/adjusted provides a CRSP-compliant logic usable for free.

Conclusion

The tl;dr of this post is simple - I wholeheartedly recommend Tiingo as a U.S. stock market data web API provider for a personal use.

Now, Tiingo does not support international stock markets10, which might be problematic for some users.

If you are in this case, I would then recommend Alpha Vantage as an international stock market data web API provider for a personal use.

This recommendation seems to come out of nowhere, because Alpha Vantage was eliminated, but:

  • Alpha Vantage was eliminated because of the restriction on the daily adjusted close prices endpoint, which can be lifted by earning some Alpha Vantage Coin tokens for free though Alpha tournament daily challenges
  • Alpha Vantage data quality is on par with Tiingo’s on the U.S. stock market

Happy web API testing!

  1. In particular, there is no need for fundamental data (company earnings, etc.). 

  2. While far more corporate actions than only splits and dividends exist - bonus issues, spin-offs, rights offerings… - any of these actions is equivalent, in terms of total return, to a change in the number of shares and/or to a dividend distribution. 

  3. See the article The Power of Dividends: Past, Present, and Future

  4. Some of them being written by people affiliated to a stock market data provider or by a stock market data provider itself! 

  5. See S&P 500 ETFs: What Every Investor Should Know

  6. Alpha Vantage also provides an endpoint to retrieve end of week prices, updated every day at the close! So, if no historical data is required, this endpoint could be used as a kind of workaround. 

  7. The outstanding question would be to double check if this split factor is correct. While I am no corporate actions specialist, a quick computation using Realty Income close stock price of 71.14$ and Orion opening stock price of 22.15$ - both on November 15, 2021 - gives a split factor of ~1.03213…, so pretty close enough! 

  8. For example, access to the SPY ETF historical prices is possible up to the SPY inception date on January 29, 1993. 

  9. Although I had access to more than one dividend for the SPY ETFs! So, I am not sure how exactly this restriction is applied on IEX Cloud side… 

  10. Except for the Chinese stock market.