Fundamental screening methods

Magic Formula screener: earnings yield, capital returns and ranking

A Magic Formula screener ranks the same eligible companies twice: once by EBIT divided by enterprise value, and once by EBIT divided by net working capital plus net fixed assets. Higher ratios receive better ranks; adding the two ranks gives a score where lower is better. The example below shows the calculations and an executable ranking procedure. It uses explicit research policies rather than reproducing the official Magic Formula Investing database or its accounting adjustments.

Explore Market Screener

Separate the published framework from this implementation

Joel Greenblatt's book describes the two ratios and the sum of their ranks. Its appendix uses trailing 12-month operating earnings, the latest balance sheet and the latest closing price. The official site's FAQ excludes financial companies and utilities because their accounts differ. The formulas are a starting point; matching them does not establish that a replica matches the official screen.

  • Choose the exchange set and a minimum market capitalization before collecting inputs. For this example the floor is USD 300 million, inclusive. This is our illustrative threshold, not an official minimum or a liquidity guarantee.
  • One issuer receives one row. Use one selected common listing for identification and the market value of all common share classes for EV. Document the sector taxonomy and issuer classification.
  • This is an independent educational implementation. It does not reproduce proprietary stock lists, claim affiliation with Joel Greenblatt or present Pineify as an authorized Magic Formula product.
DecisionPublished frameworkThis page's research policy
Measure cheapnessEBIT / enterprise value.Use the EV bridge below, with an explicit cash convention.
Measure capital productivityEBIT / tangible capital employed.Use the operating working-capital proxy and net PP&E below.
CombineAdd the two descending ranks.Use average ranks for ties; equal sums remain equal scores.
EligibilityExclude financials and utilities; require usable information.Also restrict to reviewed U.S. domestic common operating companies, excluding funds, REITs, ADRs and duplicate share classes.
Invalid economicsAccounting details can differ between implementations.Require EBIT > 0, EV > 0 and total tangible capital > 0. Defer missing or ambiguous inputs.

Fix the accounting bridge before calculating ratios

Use the same reporting currency and scale for every monetary input within a company. The worksheet below uses USD millions. Treat EBIT as pretax operating earnings after depreciation and amortization, using four nonoverlapping fiscal quarters. Reconcile vendor EBIT with operating income and the issuer filing; do not silently substitute EBITDA, net income, EPS yield, ROE or a generic after-tax ROIC field.

  • For fictional A: M = 500, D = 150, P = 0, N = 0 and C = 50, so EV = 600. CA = 200, CL = 100 and SD = 20 give NWC = 200 - 50 - (100 - 20) = 70. With net PP&E = 130, capital = 200. EBIT = 60 gives EY = 10% and ROC = 30%.
  • This EV bridge adds noncontrolling interests and defines a cash deduction explicitly. These are our implementation choices, not a claim about the official provider's adjustments. Restricted cash is not automatically deductible.
  • Reconcile lease liabilities and right-of-use assets before including lease-heavy issuers. Avoid counting current debt or lease debt twice. Defer material classifications you cannot harmonize rather than mixing accounting bases.
  • Keep a separate note for asset disposals, restructuring, acquisition changes and unusually strong cyclical earnings. This example uses reviewed reported operating earnings without discretionary add-backs; any normalized-earnings variant must receive a new label and ranking run.
Input or formulaDefinition used hereRequired review
EV = M + D + P + N - CM: common equity market value at the selected close. D: interest-bearing debt. P: preferred equity value. N: noncontrolling interests. C: unrestricted cash, equivalents and nonoperating short-term investments.Include N when using consolidated EBIT. Prefer market values for P/N when observable; disclose any carrying-value proxy. Verify zero balances rather than filling blanks with zero.
NWC = CA - C - (CL - SD)CA: current assets. CL: current liabilities. SD: current interest-bearing debt already included in D. C is the same reviewed cash/investment amount.This proxy removes all specified cash rather than estimating excess cash. It can understate operating capital when cash is needed in the business.
Capital = NWC + net PP&ENet PP&E is property, plant and equipment after accumulated depreciation. Exclude goodwill and intangible assets.Use the latest disclosed balance sheet, not a sum of four quarterly balance sheets. Do not substitute total noncurrent assets.
EY = EBIT / EV; ROC = EBIT / CapitalBoth are pretax ratios; multiply by 100 only for display.Tiny positive denominators can create extreme ratios. Inspect the accounts before accepting their ranks.

Recalculate the five-company ranking

All five companies are fictional and already pass the same eligibility review. Each has market capitalization of at least USD 300 million. Monetary values are USD millions; EBIT covers the latest available four standalone quarters, and NWC/net PP&E use the latest disclosed balance sheet. These numbers demonstrate arithmetic, not current stocks, expected returns or a tested portfolio.

  • EY order is D, A, B/C, E. B and C share positions 3 and 4, so each receives (3 + 4) / 2 = 3.5. ROC order is B/C, A, E, D; B and C each receive 1.5.
  • A, B and C share the best composite score of 5. D has the highest earnings yield but a weaker capital-return rank, giving 6. Adding 15% to 15% would be a different model; this method adds ranks.
  • Sort tied composite scores by an identifier only to make the display stable. Our shortlist policy retains all companies tied at the cutoff. A request for the top two therefore returns A, B and C, not an arbitrary two.
  • E shows why negative NWC alone is not an automatic rejection: total capital is still positive at 150. Negative or zero total capital would make the row ineligible under this policy.
CompanyEBITEVNWC + net PP&EEY; rankROC; rankRank sum
A6060070 + 130 = 20010%; 230%; 35
B801,00080 + 120 = 2008%; 3.540%; 1.55
C4050040 + 60 = 1008%; 3.540%; 1.55
D75500200 + 300 = 50015%; 115%; 56
E30500-50 + 200 = 1506%; 520%; 49

Run the ranking on reviewed inputs in Python

This standard-library example ranks the normalized worksheet, not raw vendor accounts. Before setting reviewed to true, verify the universe, market-cap floor, currency, accounting bridge and source versions above. available_at is the latest verified publication or observation timestamp among all required inputs. The code rejects unavailable rows and invalid ratios, uses exact fractions for decimal inputs, and returns an exclusion log. It does not download data or verify a filing on your behalf.

  • The reviewed flag is an explicit manual prerequisite, not an automated certification. Preserve each source line and the person or process responsible for that review in the separate worksheet.
  • A blank, null, NaN, infinity or malformed decimal is excluded. A confirmed zero cash balance is valid in the upstream bridge; zero EV, zero EBIT or zero total capital is not valid for this screen.
  • The code compares exact fractions derived from the supplied decimal strings. Values displayed as 8.00% can still rank differently when their underlying fractions differ. Preserve provider precision before display rounding.
Python 3: exact ratio comparisons, average ties and an auditable exclusion listpython
from datetime import datetime
from decimal import Decimal, InvalidOperation
from fractions import Fraction


def timestamp(value):
    if not isinstance(value, str):
        raise ValueError("timestamp must be a string")
    result = datetime.fromisoformat(value.replace("Z", "+00:00"))
    if result.utcoffset() is None:
        raise ValueError("timestamp requires a UTC offset")
    return result


def number(value):
    if not isinstance(value, str):
        raise ValueError("numeric inputs must be decimal strings")
    result = Decimal(value)
    if not result.is_finite():
        raise ValueError("non-finite input")
    return Fraction(result)


def screen(rows, cutoff):
    cutoff = timestamp(cutoff)
    valid, excluded, seen = [], [], set()
    for row in rows:
        symbol = row["symbol"]
        if symbol in seen:
            raise ValueError("duplicate issuer identifier: " + symbol)
        seen.add(symbol)
        try:
            if row.get("reviewed") is not True:
                raise ValueError("eligibility/accounting review incomplete")
            if row["sector"] in {"Financial Services", "Utilities"}:
                raise ValueError("excluded sector")
            if timestamp(row["available_at"]) > cutoff:
                raise ValueError("inputs unavailable at cutoff")
            ebit, ev, nwc, nfa = [number(row[k])
                                  for k in ("ebit", "ev", "nwc", "nfa")]
            capital = nwc + nfa
            if ebit <= 0 or ev <= 0 or capital <= 0 or nfa < 0:
                raise ValueError("invalid EBIT, EV or tangible capital")
            valid.append(dict(symbol=symbol, ey=ebit / ev,
                              roc=ebit / capital))
        except (KeyError, TypeError, ValueError, InvalidOperation) as error:
            excluded.append((symbol, str(error)))
    for field in ("ey", "roc"):
        for row in valid:
            better = sum(other[field] > row[field] for other in valid)
            equal = sum(other[field] == row[field] for other in valid)
            row[field + "_rank"] = better + Fraction(equal + 1, 2)
    for row in valid:
        row["score"] = row["ey_rank"] + row["roc_rank"]
    return sorted(valid, key=lambda r: (r["score"], r["symbol"])), excluded


def shortlist(ranked, count):
    if not isinstance(count, int) or isinstance(count, bool) or count < 1:
        raise ValueError("count must be a positive integer")
    if not ranked:
        return []
    boundary = ranked[min(count, len(ranked)) - 1]["score"]
    return [row for row in ranked if row["score"] <= boundary]


inputs = [
    ("A", "60", "600", "70", "130"),
    ("B", "80", "1000", "80", "120"),
    ("C", "40", "500", "40", "60"),
    ("D", "75", "500", "200", "300"),
    ("E", "30", "500", "-50", "200"),
]
rows = [dict(symbol=s, ebit=e, ev=v, nwc=w, nfa=f,
             sector="Industrials", reviewed=True,
             available_at="2026-10-16T20:00:00Z")
        for s, e, v, w, f in inputs]
ranked, excluded = screen(rows, "2026-10-16T20:00:00Z")
print([(r["symbol"], str(r["score"])) for r in ranked])
# [('A', '5'), ('B', '5'), ('C', '5'), ('D', '6'), ('E', '9')]
print([r["symbol"] for r in shortlist(ranked, 2)])
# ['A', 'B', 'C']
print(excluded)  # []

Keep financial reporting time separate from period end

A June 30 balance sheet was not necessarily available on June 30. For the example cutoff T = October 16, 2026 at 20:00 UTC, a September 30 report first published October 20 is unavailable. Use the last complete compatible set that was both public and observed by T, or defer the issuer. Preserve the original version even if a later filing restates it.

  • Record issuer ID, security type, sector classification, universe membership, quote time, filing URL, fiscal period, publication time, observation time, accounting definitions and each raw amount. Store both rank columns and all exclusions with the final list.
  • Check that quarterly earnings are standalone, not year-to-date totals. Do not combine a later EBIT figure with an older capital figure simply because the older denominator improves the rank.
  • A current API response can contain revisions and surviving companies only. Historical tests also need historical universe membership, delisted securities, corporate actions and execution costs. This worksheet does not supply that archive or demonstrate profitability.
  • Recompute both ranks when the eligible universe changes. A company's score can change even when its own two ratios do not.
CaseTreatment before ranking
June 30 period; filed August 7; captured August 7Usable at T if all other required fields and the four-quarter earnings series were also available.
September 30 period; filed October 20Do not use at T. A period-end date cannot replace a release timestamp.
Published before T; first observed after TUnavailable to this research process at T. Preserve both timestamps.
Required balance-sheet field is missingDefer and record the missing field; remove the row from both rankings.
Negative EBIT or nonpositive EV/capitalExclude under this policy; never convert a negative denominator into a favorable score.
Financial company or utility with attractive ratiosExclude before ranking; ordinary operating-company ratios are not comparable.

Use Pineify for research and chart review

AI Finance Agent has tools for company screening, income statements, balance sheets and enterprise values. These provide research inputs, but coverage and accounting definitions require checking. Pineify Market Screener currently provides supported technical snapshots and saved symbol collections; its catalog does not offer EBIT, operating working capital or a native Magic Formula rank sum.

  • Research prompt: For my supplied issuer list and cutoff, retrieve available income statements, balance sheets and enterprise-value components. Reconcile EBIT, cash, current debt, net PP&E, preferred equity and NCI against issuer filings. Return source URLs, fiscal periods, publication and retrieval times, units and missing fields. Do not treat current downloads as historical snapshots.
  • Save the dated ranked worksheet separately. Open each supported candidate on its matching TradingView exchange, inspect the completed daily bar and earnings events, and note any technical rejection separately from its fundamental score. A saved collection can change as new snapshots arrive.
  • A high rank can reflect peak-cycle profits, a shrinking asset base or accounting differences. Inspect debt obligations and business economics before treating a shortlist as investable. The ranking supplies candidates, not price targets or buy instructions.
TaskSupported workflowBoundary
Seed a universeFinance Agent company screener: market capitalization, exchange, country and sector filters.The returned list is limited; a small response is not proof of a complete market universe. Perform security and issuer exclusions explicitly.
Check earnings and capitalIncome statement: ebit, operatingIncome, filingDate and acceptedDate. Balance sheet: current assets/liabilities, cash, debt and propertyPlantEquipmentNet.Reconcile the issuer statement. A field name alone does not prove matching periods or the correct operating definition.
Reconcile enterprise valueEnterprise values tool exposes market capitalization, cash, total debt and enterpriseValue.Its returned date and EV do not certify this page's preferred-equity, NCI, investment or historical cutoff treatment. Rebuild the bridge if necessary.
Calculate the shortlistApply the reviewed worksheet and Python example above.Neither a natural-language request nor a technical screener collection is a native cross-company rank engine.
Review candidatesAdd available symbols to Market Screener collections; compare Close or RSI 14 on a consistent interval.Technical confirmation is a separate research overlay. It changes selection if used as another filter.
Where Pineify fits

Pineify Market Screener

Review supported technical fields for researched candidates and save available symbols in collections. Keep the fundamental rankings and dated financial evidence in your worksheet.

Also useful: AI Finance Agent. Research financial statements and enterprise-value components, then verify accounting definitions and publication dates against issuer documents.

Explore Market Screener

Frequently asked questions

Educational research method, not investment advice. All companies and calculations in the example are fictional. Screening ranks do not predict returns, and investing can lose capital. Pineify is not affiliated with or endorsed by Magic Formula Investing or Joel Greenblatt.

Sources and verification

Published by Pineify. Pineify is an independent product and is not affiliated with TradingView. Sources support the technical explanations; examples do not establish trading performance.