Handling stock splits in financial applications

In Apple's (AAPL) most recent quarterly filing on July 31st, they announced their fifth ever stock split, this time as a 4-to-1 split. While companies like Amazon have historically decided against stock splits, in favor of a higher price (which may discourage short-term traders and thus reduce volatility of a stock), Apple has liked to keep their share price accessible, right around that $100 range.

Stock splits 101

Stock splits are fairly simple concepts: on a specified day, the number of shares outstanding for a company are multiplied by the split ratio (in Apple's case, 4-to-1, or just 4), with the resulting stock price being multiplied by the inverse of that ratio (in this case 1/4 = 0.25). As an investor, if you owned 1 share of Apple on August 24th, 2020 (the planned split date), and for the sake of argument, say AAPL was trading at $400 that day, then after the split, you would have 4 shares each worth $100. In this arrangement, no value is created or destroyed as the resulting market capitalization (outstanding shares * share price) is still equal.

Options?

If you own options for an underlying stock, the process is a little different. Option contracts undergo an adjustment called "being made whole". This adjustment follows the same principle as the stock split: no additional value is being created or destroyed.

Since options cover 100 shares of an underlying stock, we have to multiply those 100 shares by the split ratio, and multiply by the inverse to adjust the strike price. Using AAPL again, you may have bought 1 Call option with a strike price of $500 (expiry date is irrelevant in this calculation as long as the contract is not already expired). After the split, your 1 Call option now controls 400 shares (recall the 4-to-1 split), each with a strike price of $125 ($500 * 1/4).

Reverse splits

There are occasions where a reverse stock split may happen where the split ratio is less than 1, such as a 1:5 split. In this arrangement, if you owned 5 shares of a stock trading at $1, after the split you'd own 1 share @ $5. The same math for options is true for reverse splits.

Edge case: fractional shares

Lastly, there are cases for both regular and reverse stock splits that may leave an investor with fractional shares, such as a 3:2 split with too few shares to evenly resolve to a single number. Or a reverse split, say 1:5, wherein an investor owns less than 5 shares. In both cases, any "fractional" shares are automatically liquidated into cash.


Adjusted Data

A company's share price drives a lot of financial calculations. We already touched on options, but there are other values such as volume, dividends, and historical prices that should be adjusted. Some might argue that historical values should be left alone, though I'm of the belief that calculations, technical indicators, or other signals may be mislead by dramatic changes in the share price. Could you imagine if you woke up tomorrow, and AAPL was worth 1/4 of what it was today? If a stock chart showed an overnight drop of 75%, I would be concerned. You also wouldn't be able to do any sort of technical analysis since you couldn't realistically compare historical days to present and future days due to the disparity. To correct this, I'll adjust all historical data accordingly. The below is written from the perspective of the database structure of Carbon:

Sprint This is the concept at Carbon which effectively represents the asset (stock) and underlying options contracts utilized to hedge the equity.
Stock Quote As above, we'll divide the stock quote by the split ratio.
Shares Same here, we'll multiply by the split ratio
Call Strike We'll divide the strike by the split ratio
Call Price Although this cost is in the past, like the dividends, I'll adjust it so it's still accurate to make sure the total (see below) remains the same
Call Total This calculation is a little unique to Carbon. Yes, options typically cover 100 shares of a stock, but in the case of Carbon, we only buy/sell a "fraction" of the contract equal to the underlying shares purchased. I won't get into why this is here, and I know there's really no precedent for this on the markets today, but the idea here being that we want "Call Total" to be the same as it was previously, since this is the effective "price" we paid.
Put Strike Same as above, but for the put contract
Put Price Same as above, but for the put contract
Put Total Same as above, but for the put contract
Equity Total This is just stock quote * shares. Since we're updating both of these, we could recalculate this, but it should always equal the same as before since no value is being created or destroyed.

Option Strike Prices: this is a table that lists all of the strikes for each stock during some period. It's refreshed pretty regularly so there isn't much historical data here, but rather than relying on scheduled refresh of the data, I can update the actual strike values relative to the split ratio. For AAPL, this means I'd take each strike price and divide by 4 (recall the 4-to-1 split).

Stock Price Historicals: this table houses the open/ high/ low/ close and volume figures necessary to produce historical stock charts. For the stock prices (open/ high/ low/ close) we'll divide by the split ratio (4 for AAPL). For volume, we'll do the opposite - multiplying by the split ratio. So if a day had 100k volume, we'd adjust that to 400k volume to maintain the liquidity (since everyone now owns 4x the amount of shares the did previously, it stands to reason that the volume traded on the market in a normal day would be 4x what it was prior to the split - not because of increased activity, but because there are now 4x as many shares available in the market).

Stock Prices (Realtime): this is a bit of a temporary table that stores live prices as they are fetched throughout the day, at the end of the week, this data is purged. I've found this is far more efficient than a single table of stock prices since you would need to query the massive historicals table for a single price quite often, just to load your dashboard, which has the latest price for all stocks in your portfolio and your watchlist. Although this data is short lived, it should be easy enough to update the "value" and "change amount" for the stock in question. The "change percentage" doesn't need to be recalculated since this percent would not change.

Dividends: Although I've seen this one both ways, I feel that historical dividend figures should be adjusted. Carbon calculates dividend yield based upon the last paid dividend (if it's within the past year), the expected frequency (almost always quarterly), then dividing that by the last price of a stock. For AAPL, they will pay a $0.82 dividend on August 13th (before the stock split). $0.82 * 4 (expected frequency) = $3.28 annually / $444.45 (last AAPL price as of this writing) = Dividend Yield of ~0.74%. Consider if you left the dividend amount of $0.82 alone, but then utilized the post-split AAPL share price in a few weeks. That would move the dividend yield calculation to ~2.95% annually, which is misleading. I'll update each of the historical dividend amounts by the split amount.

Stock Metadata: I maintain a list of the shares outstanding on a stock to support many calculations in the application. As discussed previously, we'll multiply the shares outstanding by the split ratio.


Programmatically splitting

Ideally speaking, I could use an API with a predictable output that gets queried on a regular basis that provides me a list of upcoming stock splits. I could ingest that data, and convert it into a job to be processed the morning of the effective date. Unfortunately I couldn't find an endpoint with the few API's Carbon utilizes that provides this info in a predictable fashion. The closest I can get is a news API, but I'd need to have a lot of rules to make sure I was extracting the correct information.

In lieu of over-engineering this rare event, I created a command that generates the job necessary for the split and delays it until the morning of the effective date provided. Here's a glimpse at that command:

When that job processes, all of the above data will get updated according to the split ratio entered. The job took a little less than a minute to process on my local machine, so I'd expect it to take no more than a few seconds running in production. We'll find out come August 31st!

If splits like this become more common, I may have to find a way to include a "warning" message, or otherwise provide some sort of feedback to the user in a period both before and immediately after a split.