Strasmore Research
Learn Matt ConnorBy Matt Connor · data as of September 23, 2026 · refreshed weekly

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.

QueryThe stock split calendar: recent and scheduled
24 rows (showing 20)
day_labeltickerdirectionratioworked_example
Dec 17, 2026DPUreverse split1-for-50100 shares becomes 2
Nov 5, 2026SOXXforward split3-for-1100 shares becomes 300
Oct 23, 2026CTAMFreverse split1-for-40100 shares becomes 2.5
Oct 12, 2026IDTIDforward split192-for-100100 shares becomes 192
Oct 9, 2026DWAHYforward split2-for-1100 shares becomes 200
Oct 9, 2026DXJforward split3-for-1100 shares becomes 300
Oct 9, 2026IMHDYforward split2-for-1100 shares becomes 200
Oct 9, 2026KXIAYforward split3-for-1100 shares becomes 300
Oct 7, 2026TKOMYforward split15-for-1100 shares becomes 1500
Oct 7, 2026TOPPYforward split2-for-1100 shares becomes 200
Oct 6, 2026ETHAreverse split1-for-3100 shares becomes 33.33
Oct 6, 2026RUBIreverse split1-for-1100 shares becomes 150
Oct 5, 2026TGOSYforward split5-for-1100 shares becomes 500
Oct 2, 2026MMSMYforward split2-for-1100 shares becomes 200
Oct 2, 2026NGKSYforward split3-for-1100 shares becomes 300
Oct 2, 2026NIPMYforward split1-for-1100 shares becomes 150
Oct 2, 2026NPNGYforward split1-for-1100 shares becomes 150
Sep 30, 2026CETXPreverse split1-for-1100 shares becomes 105
Sep 30, 2026DHYreverse split1-for-10100 shares becomes 10
Sep 30, 2026IBIDYforward split2-for-1100 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 24
Run this yourself

The 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.

QueryForward and reverse splits per month, last 36 months
36 rows (showing 20)
monthmonth_labelforward_splitsreverse_splits
2023-09-01Sep 20232099
2023-10-01Oct 2023893
2023-11-01Nov 2023476
2023-12-01Dec 20231987
2024-01-01Jan 2024875
2024-02-01Feb 2024975
2024-03-01Mar 20244480
2024-04-01Apr 20243095
2024-05-01May 20249100
2024-06-01Jun 20242086
2024-07-01Jul 2024984
2024-08-01Aug 20241193
2024-09-01Sep 20243881
2024-10-01Oct 20244291
2024-11-01Nov 202412132
2024-12-01Dec 20242470
2025-01-01Jan 2025789
2025-02-01Feb 202518110
2025-03-01Mar 20252592
2025-04-01Apr 20251694
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 month
Run this yourself

The 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.

QueryDo the direction labels agree with the ratio columns?
directionsplitsto_above_fromfrom_above_to
forward split7827820
reverse split36025463056
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 direction
Run this yourself

Over 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.

QueryMost common split ratios, last three years
labelsplits
reverse 1-for-10674
reverse 1-for-1289
reverse 1-for-20279
reverse 1-for-5238
forward 2-for-1225
reverse 1-for-4149
forward 3-for-1147
reverse 1-for-15137
reverse 1-for-25129
reverse 1-for-3121
reverse 1-for-100111
forward 5-for-1109
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 12
Run this yourself

The 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.