FMP
Jan 06, 2026
Evaluating upcoming IPOs is often a fragmented process. Key information such as listing dates, exchanges, and company context tends to be scattered across filings, news articles, and ad-hoc spreadsheets. For analysts and engineers, this makes it difficult to apply consistent criteria when comparing multiple IPO candidates.
This article presents a data-driven workflow for evaluating upcoming IPOs using Financial Modeling Prep (FMP) as the primary data source. The workflow starts with structured IPO calendar data and incrementally adds context and scoring logic to produce a ranked view of upcoming listings. The objective is not to forecast performance, but to systematically organize and prioritize IPO candidates using fields that are available before listing.
The implementation is intentionally modular. The workflow is split into discrete processing steps that handle data collection, enrichment, and scoring. Each step operates on well-defined inputs and outputs, making the pipeline easy to inspect, debug, and extend. All decisions in the workflow are derived directly from FMP data, ensuring that the resulting scores remain explainable and reproducible.
This modular, agent-style design aligns with other explainable financial pipelines built on FMP data, such as multi-agent systems for fraud detection and structured financial analysis.
By the end of the article, you will have a practical IPO evaluation pipeline that converts raw IPO calendar entries into a structured, scored dataset suitable for screening, comparison, and downstream research.
The IPO evaluator follows a linear, data-driven flow where each step transforms FMP data into a more structured and decision-ready form. The design prioritizes clarity and traceability over abstraction, making it suitable for research pipelines where intermediate outputs matter as much as the final score.
At a high level, the workflow consists of three sequential stages:
Each stage operates on a shared dataset and passes its output to the next stage. This approach avoids hidden state and keeps the entire pipeline inspectable at every step.
The first stage focuses on identifying IPO candidates. It consumes IPO calendar data from Financial Modeling Prep and produces a normalized list of upcoming listings. The output at this stage is intentionally minimal, containing only fields that are reliably available before listing, such as symbols, dates, and exchanges.
The second stage enriches each candidate with company metadata. This step adds contextual fields—such as sector, country, and exchange details—that are useful for comparison but may be partially available or missing for some IPOs. The pipeline treats enrichment as optional and nullable, allowing downstream logic to adapt to incomplete data rather than failing on it.
The final stage applies deterministic scoring logic. Scores are computed using transparent rules tied directly to available FMP fields. Each rule contributes a bounded component to the overall score, making it possible to understand why one IPO ranks higher than another. The scoring output remains descriptive and comparative, not predictive.
This separation of responsibilities keeps the workflow easy to reason about. Each stage can be tested independently, intermediate datasets can be inspected or stored, and new FMP-based signals can be added without restructuring the entire pipeline.
The first step in the workflow is to establish a reliable universe of upcoming IPOs. This is handled by querying the IPO Calendar endpoint from Financial Modeling Prep, which provides structured information about scheduled listings before they go live.
The IPO Calendar data typically includes fields such as the expected IPO date, company name, symbol, exchange, and offering-related attributes. At this stage, the goal is not to enrich or interpret the data, but to normalize it into a stable candidate table that downstream steps can consume consistently.
To make this API call, you'll need your own Financial Modeling Prep API key. You can generate a key by creating an FMP account and selecting a plan that includes IPO calendar access. Rate limits and data availability depend on your plan tier, so results may vary across accounts.
You can review plans and generate an API key here.
A minimal Python implementation looks like this:
|
import requests import pandas as pd API_KEY = "YOUR_FMP_API_KEY" url = "https://financialmodelingprep.com/stable/ipos-calendar" response = requests.get(url, params={"apikey": API_KEY}, timeout=30) response.raise_for_status() ipo_df = pd.DataFrame(response.json()) |
This call fetches the raw IPO calendar and converts it into a tabular structure. The pipeline treats this response defensively—no assumptions are made about field presence or completeness.
From here, basic filtering and normalization are applied. Common examples include:
|
def normalize_ipo_calendar(ipo_df: pd.DataFrame) -> pd.DataFrame: df = ipo_df.copy() if df.empty: return df # Defensive: use .get() so missing columns don't raise KeyError. df["symbol"] = df.get("symbol") df["companyName"] = df.get("companyName") df["exchange"] = df.get("exchange") df["ipoDate"] = pd.to_datetime(df.get("date"), errors="coerce") # Keep only rows with a usable symbol. df = df.dropna(subset=["symbol"]) df["symbol"] = df["symbol"].astype(str).str.strip() return df ipo_candidates = normalize_ipo_calendar(ipo_df) |
This step produces a clean IPO candidate dataset that serves as shared state for the rest of the workflow. By keeping this dataset intentionally minimal and well-defined, later stages can focus on enrichment and scoring without reinterpreting raw calendar data.
Once the candidate list is established, the pipeline is ready to add company-level context using additional FMP endpoints.
The IPO calendar gives you the candidate list, but it rarely provides enough context to compare listings consistently. The next step enriches each IPO symbol using FMP's Company Profile data, which adds fields like sector, country, exchange, and descriptive metadata when available.
For newly listed companies, these enrichment fields should be treated as partial and non-authoritative, which is why the pipeline is designed to tolerate missing or incomplete metadata during scoring.
|
import requests import pandas as pd from typing import Iterable, Dict, Any, List API_KEY = "YOUR_FMP_API_KEY" PROFILE_URL = "https://financialmodelingprep.com/stable/profile/{symbol}" def fetch_company_profile(symbol: str, api_key: str) -> Dict[str, Any]: """ Fetch a single company profile from FMP. Returns an empty dict if the response is missing or not in the expected shape. """ try: r = requests.get( PROFILE_URL.format(symbol=symbol), params={"apikey": api_key}, timeout=20 ) r.raise_for_status() data = r.json() # Stable profile endpoint typically returns a list with one object if isinstance(data, list) and data: return data[0] if isinstance(data[0], dict) else {} if isinstance(data, dict): return data return {} except Exception: return {} |
This block adds a small wrapper around the Company Profile call so the pipeline keeps running even when a symbol has no profile data or the response shape differs.
|
def enrich_ipo_candidates( ipo_candidates: pd.DataFrame, api_key: str ) -> pd.DataFrame: symbols: List[str] = ( ipo_candidates.get("symbol", pd.Series(dtype=str)) .dropna() .astype(str) .str.strip() .unique() .tolist() ) profiles = [] for sym in symbols: ppop = fetch_company_profile(sym, api_key) if ppop: ppop["symbol"] = sym # ensure join key exists profiles.append(ppop) profile_df = pd.DataFrame(profiles) # Keep enrichment nullable: left join preserves IPO candidates even if profile is missing. enriched = ipo_candidates.merge( profile_df, on="symbol", how="left", suffixes=("", "_profile") ) return enriched |
This block enriches IPO candidates via a left join so missing profiles don't drop IPO rows. The result stays usable for scoring even if some metadata fields remain null.
To avoid carrying dozens of profile columns, select only what the scorer will consume.
|
def select_enrichment_columns(enriched: pd.DataFrame) -> pd.DataFrame: keep_cols = [ # From IPO calendar (kept from earlier step) "symbol", "ipoDate", "exchange", "companyName", # From company profile (nullable enrichment) "sector", "industry", "country", "currency", "isEtf", "isActivelyTrading", ] existing = [c for c in keep_cols if c in enriched.columns] return enriched.loc[:, existing] |
This block narrows the enriched dataset to a stable, defensible schema, which makes scoring logic simpler and reduces surprises from shifting upstream fields.
At the end of Agent 2, you have an enriched IPO table where every IPO candidate remains present, and enrichment fields are populated when FMP has coverage. Next, we'll turn this table into a ranked view by applying explicit, rule-based scoring.
At this stage, you have a normalized IPO candidate list enriched with company metadata where available. The goal now is to convert that table into a ranked, comparable view using explicit, deterministic scoring rules tied to the fields you already collected from FMP.
These scoring rules reflect workflow and research prioritization preferences rather than market predictions or performance forecasts.
Rule-based scoring like this is commonly used in screening and prioritization workflows where interpretability matters more than prediction, such as market trend analysis and structured pre-event evaluation pipelines.
A practical scoring model for upcoming IPOs should do two things well:
Below is a scoring approach that stays auditable by returning both a score and a per-rule breakdown.
|
SCORING_CONFIG = { # Exchange weights are a screening preference, not a quality claim. "exchange_weights": { "NYSE": 10, "NASDAQ": 10, }, "default_exchange_weight": 5, # Sector weights are optional; keep them conservative. "sector_weights": { "Technology": 6, "Healthcare": 5, "Financial Services": 4, }, "default_sector_weight": 3, # Penalize crowded weeks (more IPOs = more competition for attention). "max_weekly_penalty": 8, # Coverage points based on presence of key metadata fields. "coverage_fields": ["sector", "country", "industry", "exchange"], "coverage_points_per_field": 3, "coverage_max": 12, } |
This config defines weights in one place so you can adjust preferences without rewriting logic. It also keeps the scoring criteria transparent.
|
import pandas as pd def safe_str(x) -> str: return str(x).strip() if x is not None and pd.notna(x) else "" def compute_coverage_score(row: pd.Series, cfg: dict) -> int: fields = cfg.get("coverage_fields", []) pts = int(cfg.get("coverage_points_per_field", 0)) max_cap = int(cfg.get("coverage_max", 0)) present = 0 for f in fields: if f in row.index and safe_str(row.get(f)): present += 1 score = present * pts return min(score, max_cap) def compute_exchange_score(row: pd.Series, cfg: dict) -> int: ex = safe_str(row.get("exchange")) weights = cfg.get("exchange_weights", {}) return int(weights.get(ex, cfg.get("default_exchange_weight", 0))) def compute_sector_score(row: pd.Series, cfg: dict) -> int: sec = safe_str(row.get("sector")) weights = cfg.get("sector_weights", {}) return int(weights.get(sec, cfg.get("default_sector_weight", 0))) |
These functions compute rule-specific scores while treating missing fields as normal. Nothing hard-fails if a column is absent or null.
|
def add_weekly_ipo_pressure(enriched: pd.DataFrame, cfg: dict) -> pd.DataFrame: df = enriched.copy() # If ipoDate isn't available or parseable, skip the penalty safely. if "ipoDate" not in df.columns: df["weekly_ipo_count"] = pd.NA df["weekly_pressure_penalty"] = 0 return df df["ipoDate"] = pd.to_datetime(df["ipoDate"], errors="coerce") df["ipo_week"] = df["ipoDate"].dt.to_period("W").astype(str) weekly_counts = ( df.groupby("ipo_week")["symbol"] .count() .rename("weekly_ipo_count") ) df = df.merge( weekly_counts, left_on="ipo_week", right_index=True, how="left" ) # Simple, bounded penalty: scale with count, cap at max_weekly_penalty. cap = int(cfg.get("max_weekly_penalty", 0)) df["weekly_pressure_penalty"] = ( df["weekly_ipo_count"].fillna(0).astype(int) - 1 ).clip(lower=0, upper=cap) return df |
This block introduces a mild penalty when multiple IPOs cluster in the same week. It's a workflow signal (attention bandwidth), not an investment thesis.
|
def score_ipos(enriched: pd.DataFrame, cfg: dict) -> pd.DataFrame: df = add_weekly_ipo_pressure(enriched, cfg) # Compute component scores row-wise for interpretability. df["coverage_score"] = df.apply(lambda r: compute_coverage_score(r, cfg), axis=1) df["exchange_score"] = df.apply(lambda r: compute_exchange_score(r, cfg), axis=1) df["sector_score"] = df.apply(lambda r: compute_sector_score(r, cfg), axis=1) df["score"] = ( df["coverage_score"].fillna(0) + df["exchange_score"].fillna(0) + df["sector_score"].fillna(0) - df["weekly_pressure_penalty"].fillna(0) ) # Sort with stable fallbacks. sort_cols = [c for c in ["score", "ipoDate"] if c in df.columns] df = df.sort_values( sort_cols, ascending=[False, True] if len(sort_cols) == 2 else [False] ) return df |
This block produces a final, ranked dataset that includes both the overall score and the components that contributed to it. That breakdown is what makes the evaluator useful in real research workflows—teams can tweak the config, rerun the pipeline, and understand what changed.
With discovery, enrichment, and scoring separated into focused steps, orchestration becomes straightforward: run each step in order, pass the evolving dataset forward, and keep the output table easy to inspect or persist.
In practice, this orchestration layer can be wrapped in a scheduled job, a notebook-driven research workflow, or an automated data pipeline depending on the operational use case.
A minimal orchestration layer looks like this:
|
import pandas as pd def run_ipo_evaluator(api_key: str) -> pd.DataFrame: """ Runs the IPO evaluation pipeline end-to-end: 1) fetch upcoming IPOs (calendar), 2) enrich with company profiles, 3) score and rank candidates. """ # Agent 1: discovery ipo_df = fetch_ipo_calendar(api_key=api_key) # returns DataFrame ipo_candidates = normalize_ipo_calendar(ipo_df) # filters + adds ipoDate # Agent 2: enrichment enriched = enrich_ipo_candidates(ipo_candidates, api_key=api_key) enriched_selected = select_enrichment_columns(enriched) # Agent 3: scoring scored = score_ipos(enriched_selected, SCORING_CONFIG) return scored |
This block wires the pipeline together without hiding any step. Each function can be run independently during debugging, and intermediate DataFrames can be inspected or saved if the workflow needs auditability.
To keep the pipeline resilient in real usage, add lightweight checks at boundaries rather than strict assertions. For example, if the calendar response is empty, the pipeline should return an empty scored table instead of failing.
|
def normalize_ipo_calendar(ipo_df: pd.DataFrame) -> pd.DataFrame: df = ipo_df.copy() if df.empty: return df # Defensive: use .get() so missing columns don't raise KeyError. df["symbol"] = df.get("symbol") df["companyName"] = df.get("companyName") df["exchange"] = df.get("exchange") df["ipoDate"] = pd.to_datetime(df.get("date"), errors="coerce") # Keep only rows with a usable symbol. df = df.dropna(subset=["symbol"]) df["symbol"] = df["symbol"].astype(str).str.strip() return df |
This block standardizes core fields while allowing the raw FMP response schema to evolve. It avoids hard assumptions and keeps the pipeline stable even if certain columns appear or disappear.
In a production setting, orchestration usually includes:
Those additions don't change the structure of the evaluator. They simply make the same FMP-driven workflow easier to operate repeatedly.
This article demonstrated how upcoming IPOs can be evaluated using a structured, repeatable workflow built entirely on Financial Modeling Prep data. By starting with the IPO Calendar and incrementally enriching and scoring candidates, the pipeline converts raw listings into a ranked dataset that supports consistent screening and prioritization.
The approach remains intentionally deterministic. Every transformation and score is derived from explicit rules applied to observable FMP fields. This makes the evaluator easy to audit, adapt, and extend as new IPOs appear or additional FMP data becomes available.
For analysts and engineers, this kind of workflow fits naturally into broader research pipelines. It reduces manual effort, improves comparability across listings, and keeps decision logic grounded in structured market data rather than ad-hoc interpretation.
Introduction In corporate finance, assessing how effectively a company utilizes its capital is crucial. Two key metri...
Bank of America analysts reiterated a bullish outlook on data center and artificial intelligence capital expenditures fo...
Pinduoduo Inc., listed on the NASDAQ as PDD, is a prominent e-commerce platform in China, also operating internationally...