Stock Split Calendar From a Free SQL API
Build a stock split calendar with one curl call and one SQL query: upcoming forward and reverse splits, ratios, effective dates, and splits per month.
A stock split calendar is a dated list of the share splits companies have carried out recently and the ones already scheduled ahead. Each row carries four things: a ticker, an effective date, a ratio written new-for-old, and a label saying whether the split is forward or reverse. This page rebuilds that calendar from scratch with one curl call, then extends it with your own SQL over global_markets.stocks_splits, the corporate-actions table behind our upcoming stock splits page.
Everything below runs in a fresh Ubuntu container with two packages on it and nothing else:
apt-get update -qq && apt-get install -y --no-install-recommends curl jq
One curl call for the upcoming split calendar
The keyless demo endpoint answers a fixed set of questions over the same data these panels read. One of them is the split calendar. Pipe it through jq and you have the calendar as tab-separated text, ready for a spreadsheet or a cron job:
curl -sS 'https://ai.strasmore.com/api/demo?q=upcoming_splits' \
| jq -r '.rows[] | [.execution_date, .ticker, .split_ratio] | @tsv'
The response is a JSON object with a columns array naming the fields and a rows array of objects, one per split. Write your check against that shape rather than against how many splits happen to be pending on the day you run it, since the pending count turns over every few weeks:
curl -sS 'https://ai.strasmore.com/api/demo?q=upcoming_splits' \
| jq -e '(.rows | type) == "array"
and ((.columns | index("execution_date")) != null)
and ((.columns | index("split_ratio")) != null)' > /dev/null \
&& echo 'calendar shape ok'
That is the whole dependency list: curl, jq, and a URL. No key, no client library, no signup.
What the split calendar actually contains
Here is the same calendar read straight from the table, covering the last three weeks alongside everything already dated ahead. The worked-example column does the arithmetic for a 100-share position, which is the fastest way to see what a ratio means.
| day_label | ticker | direction | ratio | worked_example |
|---|---|---|---|---|
| Dec 17, 2026 | DPU | reverse split | 1-for-50 | 100 shares becomes 2 |
| Nov 5, 2026 | SOXX | forward split | 3-for-1 | 100 shares becomes 300 |
| Oct 23, 2026 | CTAMF | reverse split | 1-for-40 | 100 shares becomes 2.5 |
| Oct 12, 2026 | IDTID | forward split | 192-for-100 | 100 shares becomes 192 |
| Oct 9, 2026 | DWAHY | forward split | 2-for-1 | 100 shares becomes 200 |
| Oct 9, 2026 | DXJ | forward split | 3-for-1 | 100 shares becomes 300 |
| Oct 9, 2026 | IMHDY | forward split | 2-for-1 | 100 shares becomes 200 |
| Oct 9, 2026 | KXIAY | forward split | 3-for-1 | 100 shares becomes 300 |
| Oct 7, 2026 | TKOMY | forward split | 15-for-1 | 100 shares becomes 1500 |
| Oct 7, 2026 | TOPPY | forward split | 2-for-1 | 100 shares becomes 200 |
| Oct 6, 2026 | ETHA | reverse split | 1-for-3 | 100 shares becomes 33.33 |
| Oct 6, 2026 | RUBI | reverse split | 1-for-1 | 100 shares becomes 150 |
| Oct 5, 2026 | TGOSY | forward split | 5-for-1 | 100 shares becomes 500 |
| Oct 2, 2026 | MMSMY | forward split | 2-for-1 | 100 shares becomes 200 |
| Oct 2, 2026 | NGKSY | forward split | 3-for-1 | 100 shares becomes 300 |
| Oct 2, 2026 | NIPMY | forward split | 1-for-1 | 100 shares becomes 150 |
| Oct 2, 2026 | NPNGY | forward split | 1-for-1 | 100 shares becomes 150 |
| Sep 30, 2026 | CETXP | reverse split | 1-for-1 | 100 shares becomes 105 |
| Sep 30, 2026 | DHY | reverse split | 1-for-10 | 100 shares becomes 10 |
| Sep 30, 2026 | IBIDY | forward split | 2-for-1 | 100 shares becomes 200 |
The exact SQL behind every number
SELECT
formatDateTime(execution_date, '%b %e, %Y') AS day_label,
ticker,
any(if(adjustment_type = 'forward_split', 'forward split', 'reverse split')) AS direction,
concat(toString(toInt32(any(split_to))), '-for-',
toString(toInt32(any(split_from)))) AS ratio,
concat('100 shares becomes ',
toString(round(100 * toFloat64(any(split_to)) / toFloat64(any(split_from)), 2))) AS worked_example
FROM global_markets.stocks_splits
WHERE execution_date >= today() - 21
AND split_from > 0
AND split_to > 0
AND ticker NOT IN ('SPCX')
GROUP BY execution_date, ticker
ORDER BY execution_date DESC, ticker
LIMIT 24The panel holds 24 rows. The furthest-out entry on it is DPU, a 1-for-50 reverse split dated Dec 17, 2026. The last row in view is AWBKF on Sep 29, 2026, a split that has already taken effect. Ratios read new-for-old throughout: 3-for-1 turns one share into three, and 1-for-20 turns twenty shares into one.
Two columns do all the work. split_to is the new share count and split_from is the old one, so the multiple applied to your position is simply one divided by the other. adjustment_type carries the vendor's forward or reverse label.
How many stock splits happen per month?
This is the question that keeps readers on the splits page, and it is one line of SQL away. Counting by month over the last three completed years gives both series on one chart.
| month | month_label | forward_splits | reverse_splits |
|---|---|---|---|
| 2023-09-01 | Sep 2023 | 20 | 99 |
| 2023-10-01 | Oct 2023 | 8 | 93 |
| 2023-11-01 | Nov 2023 | 4 | 76 |
| 2023-12-01 | Dec 2023 | 19 | 87 |
| 2024-01-01 | Jan 2024 | 8 | 75 |
| 2024-02-01 | Feb 2024 | 9 | 75 |
| 2024-03-01 | Mar 2024 | 44 | 80 |
| 2024-04-01 | Apr 2024 | 30 | 95 |
| 2024-05-01 | May 2024 | 9 | 100 |
| 2024-06-01 | Jun 2024 | 20 | 86 |
| 2024-07-01 | Jul 2024 | 9 | 84 |
| 2024-08-01 | Aug 2024 | 11 | 93 |
| 2024-09-01 | Sep 2024 | 38 | 81 |
| 2024-10-01 | Oct 2024 | 42 | 91 |
| 2024-11-01 | Nov 2024 | 12 | 132 |
| 2024-12-01 | Dec 2024 | 24 | 70 |
| 2025-01-01 | Jan 2025 | 7 | 89 |
| 2025-02-01 | Feb 2025 | 18 | 110 |
| 2025-03-01 | Mar 2025 | 25 | 92 |
| 2025-04-01 | Apr 2025 | 16 | 94 |
The exact SQL behind every number
SELECT
toString(toStartOfMonth(execution_date)) AS month,
formatDateTime(toStartOfMonth(execution_date), '%b %Y') AS month_label,
countIf(direction = 'forward') AS forward_splits,
countIf(direction = 'reverse') AS reverse_splits
FROM
(
SELECT
execution_date,
ticker,
any(if(adjustment_type = 'forward_split', 'forward', 'reverse')) AS direction
FROM global_markets.stocks_splits
WHERE execution_date >= addMonths(toStartOfMonth(today()), -36)
AND execution_date < toStartOfMonth(today())
AND split_from > 0
AND split_to > 0
GROUP BY execution_date, ticker
)
GROUP BY month, month_label
ORDER BY monthThe panel covers 36 completed months. The earliest, Sep 2023, recorded 20 forward splits alongside 99 reverse ones. The most recent completed month, Aug 2026, recorded 7 and 117. Both series sit on the same axis, so the mix is readable at a glance rather than through a table. For the longer-run version of this count, see how often stocks split.
The inner query deduplicates on the pair of effective date and ticker before counting. Corporate-action feeds can carry a row more than once, and a raw count() would quietly inflate a month.
To run this one yourself you need the keyed endpoint, which accepts arbitrary read-only SQL. The URL and your free key are printed on the free SQL API page. Export both, then post the query as JSON:
SQL_URL='<endpoint from the free SQL API page>'
SQL_KEY='<your free key>'
SQL="SELECT toString(toStartOfMonth(execution_date)) AS month,
countIf(direction = 'forward') AS forward_splits,
countIf(direction = 'reverse') AS reverse_splits
FROM (
SELECT execution_date, ticker,
any(if(adjustment_type = 'forward_split', 'forward', 'reverse')) AS direction
FROM global_markets.stocks_splits
WHERE execution_date >= addMonths(toStartOfMonth(today()), -24)
AND execution_date < toStartOfMonth(today())
AND split_from > 0 AND split_to > 0
GROUP BY execution_date, ticker
)
GROUP BY month
ORDER BY month"
jq -n --arg sql "$SQL" '{sql: $sql}' \
| curl -sS "$SQL_URL" \
-H "Authorization: Bearer $SQL_KEY" \
-H 'Content-Type: application/json' \
--data-binary @- \
| jq -r '.. | objects | select(has("month"))
| [.month, (.forward_splits | tostring), (.reverse_splits | tostring)] | @tsv'
The free tier covers two years of history, so the window here is 24 months. The .. walk in the final jq filter picks out every object carrying a month key wherever the envelope puts it, which keeps the command working if the response wrapper ever gains a field. The same pattern drives our ex-dividend calendar recipe.
One shape assertion is worth adding to any scheduled job. This one says the month key parses as a date and the two counts are non-negative integers, with no reference to today's numbers:
jq -e 'first(.. | objects | select(has("month")))
| (.month | strptime("%Y-%m-%d") | type) == "array"
and (.forward_splits >= 0) and (.reverse_splits >= 0)' > /dev/null \
&& echo 'monthly shape ok'
Forward or reverse: checking the label against the arithmetic
The direction label and the two ratio columns are separate pieces of data, and a careful calendar checks one against the other. A forward split should always have split_to above split_from. Counting how often that holds is a two-line query.
| direction | splits | to_above_from | from_above_to |
|---|---|---|---|
| forward split | 782 | 782 | 0 |
| reverse split | 3602 | 546 | 3056 |
The exact SQL behind every number
SELECT
direction,
count() AS splits,
countIf(new_shares > old_shares) AS to_above_from,
countIf(old_shares > new_shares) AS from_above_to
FROM
(
SELECT
execution_date,
ticker,
any(if(adjustment_type = 'forward_split', 'forward split', 'reverse split')) AS direction,
toFloat64(any(split_to)) AS new_shares,
toFloat64(any(split_from)) AS old_shares
FROM global_markets.stocks_splits
WHERE execution_date >= today() - 1095
AND split_from > 0
AND split_to > 0
GROUP BY execution_date, ticker
)
GROUP BY direction
ORDER BY directionOver the last three years the table holds 782 deduplicated rows labelled forward split. Of those, 782 have a new share count above the old one and 0 run the other way. The reverse split rows mirror it: 3056 of 3602 shrink the share count. That is the check to automate, since it tests meaning rather than volume and stays valid for years.
Which split ratios are most common?
Ratios cluster hard. A handful of shapes cover most of the calendar, and the tail is long and thin.
| label | splits |
|---|---|
| reverse 1-for-10 | 674 |
| reverse 1-for-1 | 289 |
| reverse 1-for-20 | 279 |
| reverse 1-for-5 | 238 |
| forward 2-for-1 | 225 |
| reverse 1-for-4 | 149 |
| forward 3-for-1 | 147 |
| reverse 1-for-15 | 137 |
| reverse 1-for-25 | 129 |
| reverse 1-for-3 | 121 |
| reverse 1-for-100 | 111 |
| forward 5-for-1 | 109 |
The exact SQL behind every number
SELECT
concat(direction, ' ', ratio) AS label,
count() AS splits
FROM
(
SELECT
execution_date,
ticker,
any(if(adjustment_type = 'forward_split', 'forward', 'reverse')) AS direction,
concat(toString(toInt32(any(split_to))), '-for-',
toString(toInt32(any(split_from)))) AS ratio
FROM global_markets.stocks_splits
WHERE execution_date >= today() - 1095
AND split_from > 0
AND split_to > 0
GROUP BY execution_date, ticker
)
GROUP BY label
ORDER BY splits DESC
LIMIT 12The leader is reverse 1-for-10 with 674 of them. Twelfth place, forward 5-for-1, still shows 109. Round forward ratios sit at one end of the chart and deep reverse ratios at the other, which is the practical difference between the two events: a forward split hands you more shares at a lower price, and a reverse split does the reverse, often at companies working to lift a quoted price back over an exchange listing threshold. Whether the price then goes anywhere is a separate question, covered in does a stock go up after a split.
What a split calendar cannot tell you
A scheduled row is a plan, not a settled fact. An announced ratio can be revised, and a split can be withdrawn outright, at any point before the effective date arrives. The table stores the current state of that plan and keeps no memory of what it said last week, so a row that changes overnight changes silently.
Two habits handle it. Snapshot the calendar on the day you pull it and keep the file, which gives you the revision history the table does not carry. And re-pull anything dated ahead close to its effective date rather than trusting a week-old copy. There is also no announcement date on these rows, so the calendar cannot tell you how much notice a split carried.
Field notes on the splits table
Rows are keyed by effective date and ticker, and duplicates do occur, so group before you count. split_from and split_to are the old and new share counts; the position multiple is split_to / split_from. adjustment_type carries the forward or reverse label. historical_adjustment_factor is the cumulative factor used to restate older prices, which matters when you join splits onto a price series. Every panel above excludes rows with a zero or missing share count on either side.
FAQ
How do I find upcoming stock splits?
Query the corporate-actions table for rows with an effective date at or beyond today, or call the keyless demo endpoint shown above, which returns the same list as JSON. Both give you ticker, effective date, and ratio for each scheduled split.
What does a 1-for-10 reverse split mean?
Ratios are written new-for-old. A 1-for-10 reverse split turns every ten shares you hold into one share, at roughly ten times the per-share price, leaving the value of the position unchanged at the moment it takes effect. A 10-for-1 forward split runs the opposite way.
Can I get a stock split calendar for free?
Yes. The demo endpoint in this post needs no key and no account, and a free account adds arbitrary read-only SQL over two years of history at 100 queries a day. The wider catalogue of what the free tier reaches is listed on our free stock market data API page.
Can an announced split ratio change before the effective date?
It can. Ratios are revised and splits are cancelled ahead of their effective dates, and a calendar row reflects only the latest state of the announcement. Re-pull dated rows near their effective date rather than relying on an older copy.
Every panel here ships with the exact SQL beneath it. Expand one, change the window, and it is your query. To ask for a variant in plain English, put the question to the Strasmore terminal.