FMP
Jan 27, 2026
ETF risk analysis often starts and ends at the ticker level. Metrics such as historical volatility, beta, tracking error, or NAV movement are commonly used to assess risk. While these indicators describe how an ETF has behaved, they do not explain why that behavior occurs or where the underlying risk actually comes from.
An ETF is not a single asset. It is a weighted portfolio of securities, and its risk profile is driven by the composition of those holdings. Two ETFs tracking similar themes or indices can exhibit very different risk characteristics depending on concentration, sector exposure, and dependence on a small set of underlying companies. Ticker-level analysis masks these structural differences.
This article approaches ETF risk from the holdings level rather than the ticker level. Using Financial Modeling Prep APIs and Python, we break ETFs into their underlying components and analyze risk through exposure, concentration, and company-level dependencies. The focus remains strictly data-driven, showing how ETF risk can be measured and interpreted by looking through the fund, not at the label.
This article relies on the following Financial Modeling Prep APIs to decompose ETFs into their underlying holdings and quantify risk at the portfolio and company level.
These APIs form the data foundation for all holdings-based risk calculations shown later in the article. If you scale this analysis across many ETFs or run it frequently, API plan limits will determine your practical throughput.
Ticker-level ETF analysis compresses a multi-asset portfolio into a single time series. Holdings inspection closes that gap by showing what actually drives exposure inside the fund, not just how the ticker moved historically. Metrics such as volatility, beta, or drawdown describe historical price movement, but they do not expose the underlying sources of risk inside the fund. As a result, materially different risk profiles can appear similar when viewed only through the ticker.
An ETF's behavior is driven by the distribution of weights across its holdings. Concentration in a small number of stocks, heavy exposure to a single sector, or reliance on correlated companies can dominate risk outcomes without being visible at the ticker level. These structural characteristics are not observable from price-based metrics alone.
Ticker-based comparisons also obscure changes in risk over time. Holdings rebalance, weights shift, and constituent composition evolves, often without a corresponding signal in headline ETF metrics. Without inspecting holdings directly, risk analysis remains backward-looking and incomplete.
To assess ETF risk accurately, analysis must move beyond the ticker and into the portfolio structure itself. The next sections focus on how holdings composition reveals risk that ticker-level metrics consistently miss.
ETF risk materializes from how capital is distributed across underlying securities. Holdings-level structure determines whether risk is diversified or concentrated, stable or fragile. This section focuses on the specific composition effects that shape ETF risk, independent of ticker behavior.
A small number of holdings often account for a disproportionate share of an ETF's weight. When top positions dominate the portfolio, ETF performance becomes sensitive to idiosyncratic risk from a few companies. This exposure can exist even in broadly labeled or index-tracking ETFs and is not visible from ticker-level volatility alone.
Holdings may appear diversified by count but remain concentrated by exposure. Multiple constituents from the same sector or highly correlated industries amplify downside risk during sector-specific drawdowns. Sector skew increases correlation within the portfolio, reducing the benefits of diversification despite a large number of holdings.
ETF risk also changes as weights drift between rebalancing cycles. Strong performers gain influence, while weaker names lose weight, altering the fund's risk profile over time. These shifts occur without changing the ETF's ticker or mandate, making holdings inspection necessary for up-to-date risk assessment.
Understanding ETF risk requires analyzing these composition effects directly. The next section demonstrates how to extract and inspect ETF holdings using FMP data and Python.
Weight drift matters because it quietly changes risk between rebalance events, even when the ETF mandate stays the same.
In this section, we move from structure to execution. The goal is to extract ETF holdings data and inspect how weight distribution drives risk inside the fund. All analysis starts at the holdings level.
To run the codes in this section, you will need an API key from Financial Modeling Prep. You can create a free account and generate an API key by signing up on the Financial Modeling Prep website. Once registered, the API key is available in your account dashboard and can be used to authenticate all API requests shown below.
We begin by pulling the full holdings list for a given ETF using the FMP ETF Holdings API.
|
import os import requests import pandas as pd BASE_URL = "https://financialmodelingprep.com/stable" API_KEY = os.getenv("FMP_API_KEY") def fetch(endpoint, params): params["apikey"] = API_KEY r = requests.get(f"{BASE_URL}{endpoint}", params=params, timeout=20) r.raise_for_status() return r.json() etf_symbol = "SPY" # example ETF holdings = fetch("/etf-holdings", {"symbol": etf_symbol}) df_holdings = pd.DataFrame(holdings) df_holdings.head() |
This dataset provides each constituent symbol along with its portfolio weight. At this stage, no assumptions are made about risk. We only inspect how exposure is distributed.
Next, we examine how much of the ETF is driven by its largest holdings.
|
df_holdings["weight"] = pd.to_numeric(df_holdings["weightPercentage"], errors="coerce") top_weights = ( df_holdings.sort_values("weight", ascending=False) .head(10)[["symbol", "weight"]] ) top_weights |
A high cumulative weight in the top holdings indicates concentration risk, even when the ETF tracks a broad index.
To quantify concentration, we compute the cumulative weight of the top holdings.
|
top_10_weight = df_holdings.sort_values("weight", ascending=False)["weight"].head(10).sum() top_20_weight = df_holdings.sort_values("weight", ascending=False)["weight"].head(20).sum() top_10_weight, top_20_weight |
These values provide a direct, holdings-based view of ETF concentration. ETFs with similar tickers or mandates can exhibit materially different concentration profiles when analyzed this way.
At this point, risk assessment shifts from descriptive labels to measurable exposure. The next section builds on this by quantifying concentration and exposure risk more formally.
Once ETF holdings are decomposed, concentration and exposure risk can be quantified directly. This section focuses on simple, repeatable measures that highlight where ETF risk is actually concentrated.
A common source of hidden ETF risk is dependence on a small number of holdings. This can be measured by evaluating how much of the portfolio is driven by the largest positions.
|
df_holdings_sorted = df_holdings.sort_values("weight", ascending=False) df_holdings_sorted["cum_weight"] = df_holdings_sorted["weight"].cumsum() df_holdings_sorted.head(10)[["symbol", "weight", "cum_weight"]] |
A rapidly rising cumulative weight curve indicates that a small subset of holdings dominates ETF exposure.
HHI provides a single-number summary of concentration by squaring and summing portfolio weights. Higher values indicate greater concentration.
|
df_holdings["weight_decimal"] = df_holdings["weight"] / 100 hhi = (df_holdings["weight_decimal"] ** 2).sum() hhi |
HHI allows ETFs to be compared on concentration without relying on subjective labels such as “diversified” or “broad-based.” The same holdings dataset also supports exposure explanations (concentration, sector dependence, and overlap) when you want a narrative layer on top of the metrics.
ETF risk often clusters at the sector level rather than at the individual stock level. To evaluate this, holdings can be mapped to company sectors.
|
def get_company_profile(symbol): data = fetch("/profile", {"symbol": symbol}) return data[0] if data else {} profiles = [] for sym in df_holdings["symbol"].unique(): p = get_company_profile(sym) profiles.append({ "symbol": sym, "sector": p.get("sector") }) df_sector = pd.DataFrame(profiles) df_holdings_sector = df_holdings.merge(df_sector, on="symbol", how="left") sector_exposure = ( df_holdings_sector.groupby("sector")["weight"] .sum() .sort_values(ascending=False) ) sector_exposure |
High sector concentration increases correlation risk during sector-specific drawdowns, even when individual holdings appear diversified.
These measures provide a concrete view of ETF risk that ticker-level metrics cannot capture. The next section looks through ETFs to the fundamentals of their underlying companies to understand how company-level risk propagates into the portfolio.
Holdings-level exposure explains where ETF risk is concentrated, but it does not explain what kind of risk the ETF is exposed to. To complete the analysis, ETF holdings must be mapped to company-level fundamentals. This step reveals how weak balance sheets, valuation excesses, or size concentration propagate into the ETF.
We enrich ETF holdings with company-level attributes using FMP company profile and market capitalization data.
|
profiles = [] for sym in df_holdings["symbol"].unique(): p = fetch("/profile", {"symbol": sym}) mc = fetch("/market-capitalization", {"symbol": sym}) profile = p[0] if p else {} market_cap = mc[0] if mc else {} profiles.append({ "symbol": sym, "sector": profile.get("sector"), "industry": profile.get("industry"), "country": profile.get("country"), "marketCap": market_cap.get("marketCap") }) df_companies = pd.DataFrame(profiles) df_enriched = df_holdings.merge(df_companies, on="symbol", how="left") df_enriched.head() |
This dataset allows ETF exposure to be analyzed beyond weights, incorporating business classification and size.
ETFs often carry implicit large-cap or mega-cap risk even when marketed as diversified. This can be measured by examining how portfolio weight is distributed across market-cap buckets.
|
df_enriched["marketCap"] = pd.to_numeric(df_enriched["marketCap"], errors="coerce") df_enriched["cap_bucket"] = pd.cut( df_enriched["marketCap"], bins=[0, 2e9, 10e9, 50e9, 200e9, float("inf")], labels=["Small", "Mid", "Upper Mid", "Large", "Mega"] ) cap_exposure = ( df_enriched.groupby("cap_bucket")["weight"] .sum() .sort_values(ascending=False) ) cap_exposure |
A heavy tilt toward large or mega-cap companies concentrates ETF risk in a narrow segment of the equity market.
When ETF exposure is concentrated in companies with similar business models, balance sheet structures, or valuation profiles, company-level risk compounds at the portfolio level. Weak earnings quality or overvaluation in a few dominant holdings can materially influence ETF drawdowns.
By mapping holdings to company fundamentals, ETF risk analysis shifts from surface-level exposure to a deeper understanding of how underlying businesses drive portfolio behavior.
Once ETF risk is decomposed at the holdings and company level, the final challenge is interpretation. The objective is to extract signal from the data without forcing conclusions or turning structural exposure into false alpha.
ETF risk should be evaluated using distributions rather than categorical labels such as “low risk” or “high risk.” Holdings-based metrics like concentration ratios, HHI, and sector exposure form ranges that describe structural behavior.
|
risk_summary = { "top_10_weight": df_holdings_sorted["weight"].head(10).sum(), "hhi": hhi, "max_sector_weight": sector_exposure.iloc[0] } risk_summary |
These values are descriptive, not predictive. They define the ETF's risk structure rather than forecasting returns.
Risk comparison becomes meaningful when ETFs are compared on comparable structural metrics instead of historical performance. Two ETFs with similar volatility can carry very different concentration or sector risks.
|
risk_df = pd.DataFrame([risk_summary]) risk_df |
This approach avoids ranking ETFs based on short-term outcomes and focuses on persistent exposure characteristics.
Minor differences in concentration or exposure rarely justify strong conclusions. Structural risk signals matter when they are large, persistent, and reinforced across multiple metrics. Precision beyond that point introduces noise rather than insight.
Interpreting ETF risk correctly requires restraint. Holdings-based analysis is most effective when used to understand exposure boundaries and downside sensitivity, not to generate overstated signals.
ETF risk cannot be fully understood by looking at the ticker alone. Price-based metrics describe historical behavior, but they do not explain how risk is distributed inside the fund. A holdings-first approach exposes concentration, sector skew, and company-level dependencies that remain hidden at the surface.
By using Financial Modeling Prep APIs and Python, ETF risk can be decomposed into measurable components driven by actual portfolio structure. This approach replaces assumption-based analysis with data-backed inspection, allowing risk to be evaluated consistently across ETFs with similar mandates.
Understanding ETF risk through holdings shifts analysis from labels to exposure. It focuses attention on where risk resides, how it evolves, and how it propagates through the underlying companies that make up the fund.
The traditional data procurement cycle is broken. It usually involves three discovery calls, a signed NDA, and a two-wee...
Financial ratios are often treated as objective measures of business performance. Profitability, leverage, and valuation...