A production-grade programmatic wrapper for the Nasdaq IPO calendar API with TLS impersonation.
Project description
pyIPO
A production-grade Python library for fetching and parsing IPO data from the Nasdaq calendar API. Designed for serverless contexts (AWS Lambda) and programmatic pipelines — strictly typed, stateless, and zero-dependency beyond curl_cffi.
Table of Contents
Features
- Strict types — all deals returned as frozen
IPODealdataclass instances; no raw dicts leak through the public surface - 4 deal lifecycle states —
FILING,UPCOMING,PRICED,WITHDRAWN - Chrome TLS impersonation — uses
curl_cffito bypass Akamai/Cloudflare WAF filtering applied to standard HTTP clients - Serverless-safe — 10-second request timeout ceiling, stateless client, no global connections
- Rolling window queries — fetch across N months in one call, with automatic deduplication
- Resilient parsing — null-flag normalisation (
"N/A","TBD",""→None), price-range splitting, shorthand share counts ("1.5M"→1_500_000)
Installation
pip install -e .
Requires Python ≥ 3.10. The only runtime dependency is curl_cffi ≥ 0.6.
Quick Start
from pyipo import NasdaqClient, DealState
client = NasdaqClient()
# All deals across a rolling 3-month window (default)
deals = client.get_ipos()
# Filter by state
upcoming = [d for d in deals if d.deal_state == DealState.UPCOMING]
priced = [d for d in deals if d.deal_state == DealState.PRICED]
for deal in upcoming:
print(f"{deal.company_name} ({deal.symbol}) — {deal.price_low}–{deal.price_high} — {deal.expected_date}")
Single-month query:
deals = client.get_calendar("2024-06")
Public API
NasdaqClient
class NasdaqClient:
def get_ipos(self, lookback_months: int = 3) -> list[IPODeal]: ...
def get_calendar(self, month: str) -> list[IPODeal]: ...
get_ipos(lookback_months=3)
Queries the rolling window of lookback_months calendar months (current month + N−1 prior months). Months are fetched sequentially with a 1-second polite throttle between requests. Deals with the same (company_name, deal_state) appearing in multiple months are deduplicated — only the first occurrence is kept.
| Parameter | Type | Default | Description |
|---|---|---|---|
lookback_months |
int |
3 |
Number of months to look back from today |
Returns: list[IPODeal]
Raises: NasdaqAPIError on network or HTTP errors
get_calendar(month)
Fetches all IPO deals for a single specific month.
| Parameter | Type | Description |
|---|---|---|
month |
str |
Month in 'YYYY-MM' format, e.g. "2024-06" |
Returns: list[IPODeal]
Raises: NasdaqAPIError on network or HTTP errors
IPODeal
Immutable (frozen=True) dataclass representing a single IPO deal.
from pyipo import IPODeal
| Field | Type | States | Description |
|---|---|---|---|
company_name |
str |
all | Legal company name |
deal_state |
DealState |
all | Lifecycle stage |
symbol |
Optional[str] |
all | Proposed ticker symbol |
market |
Optional[str] |
UPCOMING, PRICED | Exchange (NASDAQ, NYSE, …) |
offer_amount |
Optional[float] |
all | Total dollar value of shares offered |
shares_outstanding |
Optional[int] |
UPCOMING, PRICED | Number of shares offered |
price_low |
Optional[float] |
UPCOMING | Lower bound of proposed price range |
price_high |
Optional[float] |
UPCOMING | Upper bound of proposed price range |
actual_price |
Optional[float] |
PRICED | Final offer price |
expected_date |
Optional[date] |
UPCOMING | Expected pricing date |
filing_date |
Optional[date] |
FILING, PRICED | Date of S-1 filing or pricing |
withdrawn_date |
Optional[date] |
WITHDRAWN | Date the deal was pulled |
None values mean the data was absent or a null-flag ("N/A", "TBD", etc.) in the source.
DealState
from pyipo import DealState
DealState.FILING # "filing" — S-1 filed, no firm date yet
DealState.UPCOMING # "upcoming" — On the calendar, roadshow active
DealState.PRICED # "priced" — Final price set, ready to trade
DealState.WITHDRAWN # "withdrawn"— Deal pulled / cancelled
NasdaqAPIError
from pyipo import NasdaqAPIError
try:
deals = client.get_ipos()
except NasdaqAPIError as e:
print(e) # human-readable message
print(e.status_code) # int HTTP status, or None for connection errors
Raised on:
- HTTP responses with status ≠ 200
- Connection timeouts (hard ceiling: 10 seconds)
- Any network-layer failure (DNS, TLS, reset)
Data Model
All four deal states are sourced from a single Nasdaq endpoint:
GET https://api.nasdaq.com/api/ipo/calendar?date=YYYY-MM
The response contains four top-level buckets which map directly to DealState:
| Bucket | Response key | DealState |
|---|---|---|
| Early filings | data.filed.rows |
FILING |
| Active roadshows | data.upcoming.upcomingTable.rows |
UPCOMING |
| Priced deals | data.priced.rows |
PRICED |
| Cancelled deals | data.withdrawn.rows |
WITHDRAWN |
Error Handling
from pyipo import NasdaqClient, NasdaqAPIError
client = NasdaqClient()
try:
deals = client.get_calendar("2024-06")
except NasdaqAPIError as e:
if e.status_code == 429:
# Rate-limited — back off and retry
...
elif e.status_code is None:
# Connection failure (timeout, DNS, TLS)
...
else:
# Unexpected HTTP error
raise
Development
# Clone and install with dev dependencies
pip install -e ".[dev]"
# Run tests (no network calls)
pytest tests/ -v -p no:typeguard
# Run a single test file
pytest tests/test_parsers.py -v -p no:typeguard
Note: The
-p no:typeguardflag works around a brokentypeguardpytest plugin that may be present in some environments. It has no effect iftypeguardis not installed.
Project Structure
pyIPO/
├── src/
│ └── pyipo/
│ ├── __init__.py # Public exports
│ ├── enums.py # DealState enum
│ ├── models.py # IPODeal dataclass
│ ├── client.py # HTTP transport (curl_cffi)
│ ├── parsers.py # JSON → IPODeal conversion
│ └── exceptions.py # NasdaqAPIError
├── tests/
│ ├── conftest.py # Frozen JSON fixtures
│ ├── test_parsers.py # Parser unit tests (36 tests)
│ └── test_client.py # Client integration tests (15 tests)
├── pyproject.toml
└── ipo.py # Original standalone script (kept for reference)
Architecture
NasdaqClient
│
├── get_ipos(lookback_months) ← rolling window, dedup
└── get_calendar(month) ← single month
│
└── _fetch_month(month) ← curl_cffi GET, raises NasdaqAPIError
│
└── _parse_all(data)
├── parse_filings(rows) → list[IPODeal(FILING)]
├── parse_upcoming(rows) → list[IPODeal(UPCOMING)]
├── parse_priced(rows) → list[IPODeal(PRICED)]
└── parse_withdrawn(rows) → list[IPODeal(WITHDRAWN)]
Parsing helpers in parsers.py handle all edge cases internally — callers always receive clean, typed values or None; raw strings never escape the parser layer.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyipo-0.1.1.tar.gz.
File metadata
- Download URL: pyipo-0.1.1.tar.gz
- Upload date:
- Size: 8.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
137eb77930ba9343a60ce75a1938aea2d44f4223a600ef6c2edcb702831b9449
|
|
| MD5 |
951f3e998726e5cbe53b310c7ab01a7b
|
|
| BLAKE2b-256 |
8fd5967f03c12ad50c47bf948603b9d8d2862b4a13be5a55bdc8f28c079dfac6
|
File details
Details for the file pyipo-0.1.1-py3-none-any.whl.
File metadata
- Download URL: pyipo-0.1.1-py3-none-any.whl
- Upload date:
- Size: 9.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
943660ded2a2306eabfa7cda3e970bb406b0d998771bbf41aed8eb092e8a5c5f
|
|
| MD5 |
242d7ce1a10844b0876e25e13bac2345
|
|
| BLAKE2b-256 |
4dafa28279e0803433833c70bb0f2c56af6fb8f2c2c721c063e98b9760dc44fb
|