Skip to content

Forensic Screening

EqtyTrk's Health Check card computes three academic forensic scores — Altman Z, Piotroski F, and Beneish M — and classifies each company into a risk zone. Together, the three scores cover different failure modes: financial distress (Altman), fundamental deterioration (Piotroski), and earnings quality (Beneish). No single score is sufficient; the three together form a triangulated view of financial health.

Altman Z-Score

Definition

The Altman Z-Score (1968) is a discriminant-analysis model trained to separate manufacturing companies that went bankrupt within two years from those that survived. The model combines five accounting ratios into a single weighted score:

Z=1.2X1+1.4X2+3.3X3+0.6X4+1.0X5

Decomposition

VariableFormulaInterpretation
X1Working Capital / Total AssetsShort-term liquidity relative to asset base
X2Retained Earnings / Total AssetsCumulative profitability (also proxies for age — young firms have low RE)
X3EBIT / Total AssetsOperating efficiency, unleveraged
X4Market Value of Equity / Total LiabilitiesMarket-based solvency cushion
X5Revenue / Total AssetsAsset utilization (total asset turnover)

Where:

  • Working Capital = Current Assets − Current Liabilities
  • EBIT is mapped from EDGAR's OperatingIncomeLoss (i.e., Operating Income)
  • Market Value of Equity = market_cap from companies (refreshed on each ingest)
  • Total Liabilities from us-gaap:Liabilities
  • Revenue from the canonical revenue chain

Thresholds

ZoneScoreInterpretation
Safe> 2.99Low predicted bankruptcy risk
Gray1.81 – 2.99Ambiguous; monitor closely
Distress< 1.81High predicted bankruptcy risk

Implementation detail

altman_z_score() requires a non-None, positive market_cap. It also requires positive total assets and positive total liabilities (zero-debt companies produce a degenerate X4 term; they are returned as None rather than a misleading extreme). All five accounting inputs must be present; any missing concept returns None (ungradable rather than false positive).

Piotroski F-Score

Definition

The Piotroski F-Score (2000) is a nine-point binary scoring system designed to identify companies with improving fundamentals among high book-to-market (value) stocks. Each of the nine criteria earns one point if met, zero otherwise. The score ranges from 0 to 9.

Decomposition

The nine signals span three categories:

Profitability (4 points)

SignalConditionInterpretation
P1Net Income > 0Currently profitable
P2Operating Cash Flow > 0Cash generation positive
P3ROA improving YoYAsset returns trending up
P4OCF > Net IncomeAccruals small (cash quality)

ROA for the signal is computed inline as Net Income / Total Assets (not the averaged version used in the standalone roa() metric).

Leverage and Liquidity (3 points)

SignalConditionInterpretation
P5Long-Term Debt decreased YoYDeleveraging
P6Current Ratio improved YoYLiquidity improving
P7Shares outstanding not increased YoYNo dilution

The shares comparison uses _per_share_denominator() at both the current and prior FY end to tolerate companies that switch between weighted-average-diluted and shares-outstanding tagging across filing years.

Operating Efficiency (2 points)

SignalConditionInterpretation
P8Gross Margin improved YoYPricing power or cost discipline
P9Asset Turnover (Revenue/TA) improved YoYMore productive asset base

Thresholds

ZoneScoreInterpretation
Strong≥ 7Improving fundamentals across the board
Mid4 – 6Mixed signals
Weak≤ 3Broad deterioration

Note: Piotroski's original paper used 8–9 as "strong" and 0–2 as "weak." EqtyTrk widens the strong zone to ≥ 7 to avoid the card reading "Mid" for companies with eight of nine signals positive. The two boundaries in the frontend code are ≥ 7 for Strong and ≥ 4 for Mid.

Implementation detail

piotroski_f_score() requires the prior fiscal year anchor, resolved via _prior_fy_end("assets", ...). If no prior-year fact exists within a ±60-day window, the function returns None — six of the nine signals require YoY comparisons and cannot be computed without a prior anchor. Signals that depend on balance-sheet items with no prior fact (e.g., P5, P6, P7) default to 0 rather than propagating None to avoid losing the profitability signals.

Beneish M-Score

Definition

The Beneish M-Score (1999) is an eight-variable probabilistic model trained to detect companies that manipulated their reported earnings. The model output is a continuous score; a cutoff of −1.78 separates likely manipulators from companies likely reporting cleanly.

M=4.84+0.920DSRI+0.528GMI+0.404AQI+0.892SGI+0.115DEPI0.172SGAI0.327LVGI+4.679TATA

Decomposition

Each index compares this year to the prior year to identify anomalous changes:

IndexFormulaAnomaly signal
DSRI(ARt/Revt)/(ARt1/Revt1)Days sales in receivables rising (revenue pulled forward)
GMIGMt1/GMtGross margin deteriorating
AQI(1(CAt+PPEt)/TAt)/(1(CAt1+PPEt1)/TAt1)Asset quality declining (more soft assets)
SGIRevt/Revt1Sales growth accelerating (high-growth firms more likely to manipulate)
DEPI(DAt1/(PPEt1+DAt1))/(DAt/(PPEt+DAt))Depreciation rate slowing (extending useful lives)
SGAI(SGAt/Revt)/(SGAt1/Revt1)SG&A efficiency deteriorating
LVGI(TLt/TAt)/(TLt1/TAt1)Leverage increasing
TATA(NItOCFt)/TAtTotal accruals relative to assets (core manipulation proxy)

Where: AR = Accounts Receivable, GM = Gross Margin, CA = Current Assets, PPE = Net PP&E, TA = Total Assets, DA = Depreciation & Amortization, SGA = SG&A expense, TL = Total Liabilities, NI = Net Income, OCF = Operating Cash Flow.

Thresholds

ZoneScoreInterpretation
Flagged (likely manipulator)> −1.78Elevated earnings-manipulation risk
Clean≤ −1.78Consistent with honest reporting

Implementation detail

beneish_m_score() requires every index to be computable: if any of the eight canonical concepts is absent for either the current or prior year, the function returns None. The function also returns None when any ratio involves division by zero or when an index denominator is non-positive (a ratio of ratios with a zero denominator produces infinite or undefined results that are not meaningful as manipulation signals). This strictness means the M-Score is frequently None for companies in capital-light industries without conventional PPE or SG&A line items.

The TATA index is computed as (NIOCF)/TA — total accruals scaled by assets. A large positive TATA means reported net income substantially exceeds cash from operations, which can indicate aggressive revenue recognition, understated expenses, or other manipulation tactics.

Unified Health Check card

The three scores are displayed together in the Health Check card in the Fundamentals bento. The card uses color coding consistent across all three:

  • Forest green — favorable zone (Safe / Strong / Clean)
  • Neutral gray — ambiguous zone (Gray / Mid)
  • Brick red — adverse zone (Distress / Weak / Flagged)

Note the direction reversal for Beneish: a higher M-Score is worse (more likely to be a manipulator), so Flagged maps to brick even though the numeric value is higher.

Limitations

  • Manufacturing-firm bias in Altman Z. The original 1968 model was calibrated on manufacturing companies. It tends to produce low scores for asset-light firms (technology, software, professional services) that have high revenue-to-assets ratios but low physical asset bases. Altman later published modified versions for non-manufacturing and private firms; EqtyTrk uses only the original public-company manufacturing variant.
  • Value-stock context for Piotroski F. Piotroski designed the F-Score to separate winners from losers within the high-book-to-market population. Applied to growth stocks (low book-to-market), the signals — especially P1 (positive net income) — may penalize early-stage, pre-profitability companies that are otherwise healthy.
  • Base rate for Beneish M. Actual earnings manipulation is rare (single-digit percentage of public companies in any given year). Even a well-calibrated model applied to a base rate of ~3% will produce a substantial fraction of false positives at any reasonable sensitivity threshold. A "Flagged" score is a prompt to investigate further, not a confirmed finding of manipulation.
  • Lookback only one year. All three scores compare the current FY to the immediately prior FY. Longer deterioration patterns (gradual margin compression over three to five years) are not captured.
  • All three return None for some companies. Financial-sector companies (banks, insurance) have fundamentally different balance-sheet structures. The formulas were not designed for them and often produce None due to degenerate denominators or missing canonical concepts. The card renders "—" for missing scores rather than substituting a misleading value.

Implementation notes

  • altman_z_score(), piotroski_f_score(), beneish_m_score() in src/eqtytrk/metrics/ratios.py (forensic scores section)
  • Threshold constants are encoded in frontend rendering functions altmanTone(), piotroskiTone(), beneishTone() in frontend/src/components/FundamentalsBento.tsx
  • Piotroski is displayed as {score}/9 (integer/9 format); Altman and Beneish as two-decimal floats
  • All three are included in the standard metrics engine output and stored in company_metrics_cache

References

  • Altman, E.I. (1968). "Financial Ratios, Discriminant Analysis and the Prediction of Corporate Bankruptcy." Journal of Finance, 23(4), 589–609.
  • Piotroski, J.D. (2000). "Value Investing: The Use of Historical Financial Statement Information to Separate Winners from Losers." Journal of Accounting Research, 38(Supplement), 1–41.
  • Beneish, M.D. (1999). "The Detection of Earnings Manipulation." Financial Analysts Journal, 55(5), 24–36.

EqtyTrk methodology reference. Data from SEC EDGAR.