Fundamental Analysis with edgarfundamentals

Overview

edgarfundamentals provides a simple, ticker-based interface for retrieving fundamental financial ratios directly from SEC EDGAR 10-K filings. No API key or paid subscription is required. All data comes from the SEC’s official XBRL API at data.sec.gov, which is free, stable, and government-maintained.

The package provides four functions:

Setup

Before making any API calls, set your User-Agent string. The SEC requests that automated tools identify themselves so they can contact you if your scripts cause issues with their servers.

library(edgarfundamentals)

options(edgarfundamentals.user_agent = "Jane Smith jane@example.com")

Replace the name and email with your own. You only need to do this once per R session.

Looking Up a CIK

Every EDGAR API call requires a CIK number. get_cik() handles this translation automatically inside the other functions, but you can call it directly if you need the CIK for other purposes.

get_cik("LLY")   # Eli Lilly
get_cik("LMT")   # Lockheed Martin

Fundamentals for a Single Stock

get_fundamentals() returns a named vector of key financial ratios derived from the most recent 10-K that had been filed on or before to_date, so the result contains only information that was public on that date.

get_fundamentals("LLY", to_date = "2024-12-31")

The returned vector contains:

Name Description
CIK SEC Central Index Key
EPS Diluted Earnings Per Share (USD)
NetIncome Net Income (USD)
Revenue Total Revenue (USD)
ROE Return on Equity (%)
ROA Return on Assets (%)
DE Debt-to-Equity ratio
CurrentRatio Current Assets / Current Liabilities
GrossMargin Gross Profit as a percentage of Revenue (%)
OperatingMargin Operating Income as a percentage of Revenue (%)
NetMargin Net Income as a percentage of Revenue (%)
PE Price-to-Earnings ratio
PB Price-to-Book ratio
DIV Dividend Yield (%)

Because ratios come from annual 10-K filings, they reflect the most recent fiscal year whose 10-K was public on to_date – not real-time values. Every value is taken from that one fiscal year, so a concept the company did not report that year is NA rather than a figure from an earlier year. PE, PB and DIV use the closing price on or just before to_date (via Yahoo Finance), so with the default to_date they change daily even when the underlying 10-K data does not.

Fundamentals for a Portfolio

get_fundamentals_batch() accepts a vector of tickers and returns a tidy data frame with one row per stock. If retrieval fails for any ticker, that row contains NA values and a message is printed – the batch continues rather than stopping.

healthcare <- c("UNH", "PFE", "MRK", "ABT", "LLY", "CVS", "AMGN")
defense    <- c("LMT", "RTX", "NOC", "GD",  "HII", "LHX", "LDOS")

healthcare.fund <- get_fundamentals_batch(healthcare, to_date = "2024-12-31")
defense.fund    <- get_fundamentals_batch(defense,    to_date = "2024-12-31")

healthcare.fund
defense.fund

Expect retrieval to take approximately 20–30 seconds for 14 stocks. A 0.5 second pause is inserted after each API call to respect the SEC rate limit.

Screening by Fundamental Criteria

Once you have the data frame, standard dplyr operations apply directly.

library(dplyr)

# Growth screen: high EPS stocks relative to peers signal strong earnings
healthcare.fund |> arrange(desc(EPS))

# Value screen: low PE and low PB suggest the market is pricing the stock cheaply
# High dividend yield is also characteristic of value stocks
defense.fund |> filter(PE < 20 & PB < 3) |> select(symbol, PE, PB, DIV, ROE)

# GARP screen (Growth at a Reasonable Price): PE below EPS
# Stocks where the market is not overcharging for growth
healthcare.fund |> filter(EPS > 0 & PE < EPS) |> select(symbol, PE, EPS)

# Profitability screen: strong margins signal pricing power and operational efficiency
bind_rows(healthcare.fund, defense.fund) |>
  filter(GrossMargin > 40 & OperatingMargin > 15) |>
  arrange(desc(NetMargin)) |>
  select(symbol, GrossMargin, OperatingMargin, NetMargin, ROA)

# Liquidity screen: current ratio above 1.5 and low leverage
bind_rows(healthcare.fund, defense.fund) |>
  filter(CurrentRatio > 1.5 & DE < 1) |>
  arrange(desc(CurrentRatio)) |>
  select(symbol, CurrentRatio, DE, ROE)

Checking Filing History

get_filing_history() retrieves recent EDGAR filings for a company, which is useful for verifying data availability or for finding the accession numbers needed to access the full text of specific reports.

# Five most recent annual reports for Lockheed Martin
get_filing_history("LMT", form_type = "10-K", n = 5)

# Four most recent quarterly reports for Eli Lilly
get_filing_history("LLY", form_type = "10-Q", n = 4)

A Note on Data Limitations

EDGAR XBRL data is only as good as what companies report. A small number of companies use non-standard XBRL tag names for common concepts. The package automatically tries fallback tags when the primary tag returns no data, but in rare cases a ratio may still be NA. When this occurs, verifying the company’s most recent 10-K directly on edgar.sec.gov will confirm whether the data exists under a different tag name.

Additionally, EDGAR does not provide market-based ratios directly. PE, Price-to-Book and Dividend Yield all require a share price, which comes from Yahoo Finance via tidyquant. The package uses the unadjusted closing price on or just before to_date against the latest annual filing available on that date, which is standard practice for trailing ratios. When a stock split falls between the 10-K and to_date, per-share figures are put on the same share basis as the price.