Skip to main content

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 IPODeal dataclass instances; no raw dicts leak through the public surface
  • 4 deal lifecycle statesFILING, UPCOMING, PRICED, WITHDRAWN
  • Chrome TLS impersonation — uses curl_cffi to 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:typeguard flag works around a broken typeguard pytest plugin that may be present in some environments. It has no effect if typeguard is 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyipo-0.1.1.tar.gz (8.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyipo-0.1.1-py3-none-any.whl (9.7 kB view details)

Uploaded Python 3

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

Hashes for pyipo-0.1.1.tar.gz
Algorithm Hash digest
SHA256 137eb77930ba9343a60ce75a1938aea2d44f4223a600ef6c2edcb702831b9449
MD5 951f3e998726e5cbe53b310c7ab01a7b
BLAKE2b-256 8fd5967f03c12ad50c47bf948603b9d8d2862b4a13be5a55bdc8f28c079dfac6

See more details on using hashes here.

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

Hashes for pyipo-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 943660ded2a2306eabfa7cda3e970bb406b0d998771bbf41aed8eb092e8a5c5f
MD5 242d7ce1a10844b0876e25e13bac2345
BLAKE2b-256 4dafa28279e0803433833c70bb0f2c56af6fb8f2c2c721c063e98b9760dc44fb

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page