Quant Finance

What is Piotroski F-Score?

A 9-point accounting-based score that separates financially strong companies from weak ones using signals of profitability, leverage, and operating efficiency.

In Plain English

In the late 1990s, Stanford accounting professor Joseph Piotroski noticed something strange about value stocks. On average they underperformed despite looking cheap by traditional measures. The culprit: a subset of truly distressed, deteriorating companies hiding as value plays. His 2000 paper described a simple 9-point scoring system to separate genuinely improving value companies (high F-Score) from those heading toward bankruptcy (low F-Score).

Think of the F-Score as a physical exam for a company's finances. Instead of looking at valuation multiples, it examines nine binary signals (each scores 0 or 1) across three categories: Is the company profitable and improving? Is it reducing debt and improving liquidity? Is it getting more efficient at turning assets into sales? A company passing all nine tests scores 9. One failing all nine scores 0.

That's the appeal. You don't need to forecast cash flows or estimate discount rates. Just read the financial statements and check if things are improving or deteriorating. A score of 7-9 points to a fundamentally improving company; 0-2 to a deteriorating one. Piotroski showed a long/short strategy based on this score generated meaningful alpha even after accounting for transaction costs.

The F-Score is valuable in a quantitative context because it's entirely rules-based, reproducible, and requires no subjective judgment. Every analyst applying it to the same company at the same time gets the same answer. That makes it ideal as a component in systematic factor models and multi-signal composite scores.

Technical Definition

The F-Score sums nine binary indicators F\_i ∈ \{0, 1\}:

Profitability (4 signals):

  • F₁: ROA > 0 (net income / beginning total assets)
  • F₂: Operating cash flow > 0
  • F₃: Change in ROA > 0 (ROA improved vs prior year)
  • F₄: Accruals < 0 (operating cash flow > ROA; cash earnings exceed accrual earnings)

Leverage, Liquidity, Source of Funds (3 signals):

  • F₅: Change in leverage < 0 (long-term debt ratio decreased)
  • F₆: Change in current ratio > 0 (liquidity improved)
  • F₇: No dilutive share issuance in prior year

Operating Efficiency (2 signals):

  • F₈: Change in gross margin > 0
  • F₉: Change in asset turnover > 0 (revenue / total assets improved)

Total F-Score = Σᵢ₌₁⁹ Fᵢ ∈ \{0, 1, ..., 9\}

The accrual signal (F₄) carries a lot of weight. Companies whose earnings significantly exceed their cash flows are more likely to see earnings reversals, a phenomenon related to the accrual anomaly documented by Sloan (1996).

How VectorFin Uses This

The Piotroski F-Score is one of five signal families in VectorFin's flat signals table, served from the REST API at GET /v1/signals/{ticker}. The nine binary F-Score signals live under components.piotroski.* (9 booleans), and the top-level score field gives the F-Score as a 0-1 fraction (positives / 9). Other families include altman_z, beneish_m, sloan_accrual, and regime.

F-Score components update weekly as new 10-K and 10-Q filings are processed. The bitemporal design (effective_ts + knowledge_ts) means a backtest uses the F-Score that would have been computable from filings available at the backtest date, with no look-ahead bias from restated financials.

text
GET https://api.vectorfinancials.com/v1/signals/{ticker}

Each row includes the components.piotroski object (the 9 booleans) alongside the top-level score fraction.

Code Example

python
import requests
import pandas as pd

API_BASE = "https://api.vectorfinancials.com"
API_KEY = "vf_your_api_key_here"

# Screen for high F-Score companies in the S&P 500 universe
tickers = ["AAPL", "MSFT", "JPM", "JNJ", "XOM", "WMT", "PG", "UNH", "HD", "CVX"]

results = []
for ticker in tickers:
    resp = requests.get(
        f"{API_BASE}/v1/signals/{ticker}",
        params={"limit": 1},
        headers={"X-API-Key": API_KEY},
    )
    if resp.ok and resp.json():
        record = resp.json()[0]
        piotroski = record["components"]["piotroski"]
        # score is the F-Score as a 0-1 fraction (positives / 9)
        f_positives = sum(1 for v in piotroski.values() if v)
        results.append({
            "ticker": ticker,
            "f_score_fraction": record["score"],
            "f_positives": f_positives,
        })

df = pd.DataFrame(results)

# Filter: high F-Score fundamentals (7+ of 9 positives)
quality = df[df["f_positives"] >= 7]
print("High quality candidates (F-Score 7+ of 9):")
print(quality.sort_values("f_score_fraction", ascending=False).to_string(index=False))

Put Piotroski F-Score to work in your pipeline

Pull AI-ready embeddings and signals as Iceberg tables or over the REST API.

Get API Access