Download Every US IPO from LSEG in Python

LSEG’s SDC New-Issues database holds every US initial public offering (IPO) since 1988, delisted and merged issuers included. The documentation does not give you a script that pulls them and cleans them down to the universe academics use. So, I wrote one.
It runs in five steps. I pull the deals by issue date, keyed to a permanent SDC package number, so companies that later delisted or merged stay in the sample. That makes the pull survivorship-free. Then I filter the raw deals to major-exchange common stock and check the count against Jay Ritter’s public series.
One caveat before you start. The deals database sits behind a separate LSEG entitlement called DEALSEQ. If you do not have it, Step 1 returns zero rows, and you fall back to Ritter’s free IPOALL.xlsx. I give the fallback at the end.
What you end up with
- Every US IPO since 1988, active, delisted, and merged
- One row per deal, keyed to its SDC package number
- Enrichment columns: issuer, exchange, security type, offer price, proceeds, SPAC and BDC flags
- A cleaned operating-company universe: major exchange, common stock, offer price at least $5, ex-SPAC, ex-BDC
- A monthly IPO count that tracks Ritter’s NET series at 0.98 correlation
- The S&P 500 total-return series as a benchmark
Step 1. Open a session and test access
The deals screens are large, so raise the HTTP timeout before the session opens. The default is a few seconds, and the screens time out under it.
import warnings; warnings.filterwarnings("ignore") # silence lseg/pandas noise
import time
import numpy as np, pandas as pd
import lseg.data as ld
ld.get_config().set_param("http.request-timeout", 300) # must precede open_session
ld.open_session()Run one recent year first. If it returns rows, your account has DEALSEQ. If it errors with an access message, it does not, and you use Ritter instead.
Step 2. Pull every US IPO deal, year by year
The universe is IN(DEALS) with the DEALSEQ package hint. The old DEALSECM token returns zero rows, so avoid it. The TR.NI* fields are the New-Issues set: issue date, issuer, package number, security type, exchange, price, size, and the SPAC and BDC flags.
ISSUER_NATION = "US"
# The SDC deals screen: every US IPO in [a, b], with dates as YYYYMMDD.
# Universe is IN(DEALS) with the DEALSEQ package hint. TR.NIIssueDate gates the window.
def deals_screen(a, b):
return ("SCREEN(U(IN(DEALS)/*UNV:DEALSEQ*/),"
f'IN(TR.NIIssuerNation,"{ISSUER_NATION}"),'
"TR.NIIsECM=true,TR.NIIsIPO=true,"
f"BETWEEN(TR.NIIssueDate,{a},{b}))")
# Enrichment fields, in the order get_data returns them.
DEAL_FIELDS = ["TR.NIIssueDate","TR.NIIssuer","TR.NISdcPackageNumber","TR.NIIssueType",
"TR.NIIssueDesc","TR.NIListingNation","TR.NIProceedsAmtAllMkts","TR.NIOfferPrice",
"TR.NIIsBlankCheckCompany","TR.NIIsBusinessDevelopmentCompany","TR.NIIssuerPrimarySic"]The screen keys on issuer nation. Change ISSUER_NATION to another country code and the screen pulls that country’s IPOs instead. The clean-up in Step 4 is US-specific: the major-exchange names and the Ritter cross-check are built for the US, so another market needs its own exchange list and its own benchmark count.
Pull one year at a time. Issue dates are selective, so each screen is small and stays under the result cap. I set Curn to USD, which applies to the money fields.
frames = []
for yr in range(1988, 2027): # SDC US IPO coverage is solid from 1988
df = ld.get_data(deals_screen(f"{yr}0101", f"{yr}1231"),
DEAL_FIELDS, parameters={"Curn": "USD"})
if df is not None and len(df):
frames.append(df)
deals = pd.concat(frames, ignore_index=True)
print(f"raw deal rows pulled: {len(deals):,}")The raw pull returns more than 17,000 deal rows across the 39 years. That number is before any cleaning, so it still holds units, ADRs, MLPs, closed-end funds, and off-exchange listings.
Step 3. Dedupe on the SDC package number
One IPO can appear on several rows, for example a US and an international tranche of the same deal. The SDC package number is the permanent key for the deal, so dropping duplicates on it gives one row per IPO.
COLS = ["instrument","issue_date","issuer","pkg","issue_type","issue_desc","listing",
"proceeds","offer_price","is_spac","is_bdc","sic"]
deals.columns = COLS[:deals.shape[1]] # positional rename, in get_data field order
deals["issue_date"] = pd.to_datetime(deals["issue_date"], errors="coerce")
deals = deals.dropna(subset=["issue_date"]).drop_duplicates("pkg") # one row per SDC packageStep 4. Clean to the operating-company universe
The conventional academic IPO count, Ritter’s NET series, keeps major-exchange common stock with an offer price of at least $5, and drops blank-check companies (SPACs) and business-development companies (BDCs). TR.NIListingNation and TR.NIIssueDesc return as columns. They do not work as screen predicates, so I filter them here in pandas.
MAJOR_EXCHANGES = {"Nasdaq","New York Stock Exchange","American","NYSE Amex","NYSE MKT","NYSE Arca"}
COMMON_TYPES = {"Common Shares","Class A Shares","Ord/Common Shs.","Ordinary Shares",
"Class A Ord Shs","Class B Shares"}
d = deals.copy()
d["desc_clean"] = d["issue_desc"].astype(str).str.replace(r"^[\d,\.]+\s*","",regex=True).str.strip()
d["offer_price"] = pd.to_numeric(d["offer_price"], errors="coerce").replace(-999, np.nan)
# Major exchange, common stock, offer price at least $5, no SPAC, no BDC.
keep = d["listing"].isin(MAJOR_EXCHANGES) & d["desc_clean"].isin(COMMON_TYPES)
keep &= d["offer_price"] >= 5.0
keep &= d["is_spac"] != True
keep &= d["is_bdc"] != True
clean = d[keep].copy()
# The monthly frequency series, ready for any timing study.
cnt = clean.set_index("issue_date").resample("ME").size().rename("n_ipos")
print(f"clean operating-company IPOs: {len(clean):,} (of {len(d):,} raw)")Set DROP_SPAC to False to keep the blank-check wave. That pushes the 2021 count from about 330 to about 1,100. I drop them by default so the 2021 spike does not drive a boom study.
Step 5. The S&P 500 total-return benchmark
Most IPO timing questions compare the IPO rate to the market, so pull the S&P 500 total-return series alongside the counts. It starts in 1988, the same year the deals coverage does.
today = str(pd.Timestamp.now().date())
h = ld.get_history(universe=".SPXTR", fields=["TRDPRC_1"], start="1986-12-31", end=today)
sp = h.copy(); sp.columns = ["spxtr"]; sp.index.name = "date"
sp = sp[pd.to_numeric(sp["spxtr"], errors="coerce").notna()] # drop non-trading rowsValidation against Ritter’s series
Jay Ritter of the University of Florida publishes an annual IPO count that the literature treats as the standard. His NET column is the operating-company definition this pipeline reproduces. Over 1988 to 2025, the cleaned LSEG universe holds 8,175 IPOs against Ritter’s 7,507, and the two annual series correlate at 0.98. So, the pull lands on the same universe the academic count uses, built survivorship-free from the deal records.
Download IPOALL.xlsx from site.warrington.ufl.edu/ritter to cross-check. The GROSS column counts everything including SPACs, and the NET column is the one this notebook matches.
The counts feed two later analysis posts, one on what IPO size says about value and one on whether an IPO boom marks a market top.
What you need
- An LSEG Workspace license with the separate DEALSEQ (SDC deals) entitlement
- The
lseg.dataPython API, bundled with Workspace - The HTTP timeout raised before the session opens, or the large screens fail
Without DEALSEQ, the deals screen returns zero rows. In that case, download Ritter’s free IPOALL.xlsx and read the NET column. It gives you the annual count this pipeline reproduces, without the deal-level detail, the monthly frequency, or the delisted issuers.
Read next
- IPO boom = market top? The data says otherwise A timing study built on the monthly IPO counts from this pipeline.
- Expensive IPOs are a free lunch for about a day What debut size and valuation say about IPO returns, using this data.
Disclaimer: this is a data-pipeline walkthrough for discussion purposes only. Not investment advice.
Data: US IPO deal records from LSEG’s SDC New-Issues database and the S&P 500 total-return index from LSEG Workspace, both licensed. Because the data is licensed, I cannot share the raw file.