Building a ROIC-WACC Spread Screener
The ROIC-WACC spread is arguably the most important number in fundamental analysis. A positive spread means a company earns more on its invested capital than its cost of capital — it's creating economic value. This tutorial builds a screener that ranks the entire market by value creation.
~12 min read · Intermediate Python
The Theory
ROIC (Return on Invested Capital) measures how efficiently a company deploys capital. WACC (Weighted Average Cost of Capital) is the minimum return investors demand.
McKinsey's research (Valuation, 7th ed.) shows that long-run stock returns are driven almost entirely by two factors: revenue growth and ROIC relative to WACC. Companies that maintain ROIC > WACC for 10+ years (like Visa, Costco, MSCI) compound extraordinary shareholder value.
Value Creator
Marginal
Value Destroyer
Step 1: Fetch Quant Health Data
import requests
import time
def get_quant_health(ticker):
"""Fetch quant health scores including ROIC-WACC spread."""
url = f"https://securitiesdb.com/api/v1/stocks/{ticker}/quant-health"
r = requests.get(url)
if r.status_code != 200:
return None
return r.json()["data"]
# Test with a known value creator
data = get_quant_health("V")
vc = data["value_creation"]
print(f"Visa ROIC: {vc['roic']*100:.1f}%")
print(f"Visa WACC: {vc['wacc']*100:.1f}%")
print(f"Visa Spread: {vc['roic_wacc_spread']*100:.1f}%")
# Expected: massive positive spread (Visa has very high ROIC)Step 2: Screen the Universe
# Screen a universe of blue chips
universe = [
"AAPL", "MSFT", "GOOGL", "AMZN", "META", "NVDA",
"V", "MA", "COST", "UNH", "JNJ", "PG", "KO", "PEP",
"JPM", "BAC", "GS", "WMT", "HD", "LOW",
"TSLA", "NFLX", "CRM", "ADBE", "INTC", "T", "VZ",
]
results = []
for ticker in universe:
data = get_quant_health(ticker)
if not data or not data.get("value_creation"):
continue
vc = data["value_creation"]
scores = data["scores"]
results.append({
"ticker": ticker,
"roic": vc.get("roic", 0),
"wacc": vc.get("wacc", 0),
"spread": vc.get("roic_wacc_spread", 0),
"piotroski": scores.get("piotroski_f", 0),
"altman_z": scores.get("altman_z", 0),
})
time.sleep(0.5) # Be polite to the API
# Sort by spread descending
results.sort(key=lambda x: x["spread"], reverse=True)Step 3: Analyze with DuPont Decomposition
The quant-health endpoint also returns a DuPont decomposition, telling you why ROIC is high: is it margin-driven (pricing power) or asset-turnover-driven (asset-light)?
# Deep-dive into the top 5 value creators
print("\n🏆 Top Value Creators — DuPont Decomposition:")
print(f"{'Ticker':8s} {'Spread':>8s} {'Margin':>8s} {'Turnover':>8s} {'Leverage':>8s} {'F-Score':>8s}")
print("-" * 48)
for r in results[:5]:
data = get_quant_health(r["ticker"])
dupont = data.get("dupont", {})
print(f"{r['ticker']:8s} "
f"{r['spread']*100:>7.1f}% "
f"{dupont.get('net_margin', 0)*100:>7.1f}% "
f"{dupont.get('asset_turnover', 0):>7.2f}x "
f"{dupont.get('equity_multiplier', 0):>7.2f}x "
f"{r['piotroski']:>7d}/9")
time.sleep(0.5)Step 4: Combine with DCF for a Buy Signal
Find value creators that are also undervalued by DCF:
# Cross-reference with DCF valuation
buy_candidates = []
for r in results:
if r["spread"] <= 0.05: # Only positive spread > 5%
continue
dcf = requests.get(
f"https://securitiesdb.com/api/v1/stocks/{r['ticker']}/dcf"
).json().get("data", {})
upside = dcf.get("upside_pct", 0)
if upside > 15: # At least 15% DCF upside
buy_candidates.append({
**r,
"dcf_upside": upside,
"fair_value": dcf.get("fair_value"),
})
time.sleep(0.5)
print("\n💎 Buy Candidates (Value Creator + DCF Undervalued):")
for c in buy_candidates:
print(f" {c['ticker']}: Spread={c['spread']*100:.1f}%, "
f"DCF Upside={c['dcf_upside']:.0f}%, "
f"Piotroski={c['piotroski']}/9")What You Built
- A ROIC-WACC spread screener for any stock universe
- DuPont decomposition to understand why returns are high
- Cross-referencing with DCF for a complete value + growth signal
- Quality filter using Piotroski F-Score (>=7) and Altman Z-Score (>3)
This is the same analysis Bloomberg Terminal users pay $25,000/year for. The SecuritiesDB API makes it free and programmable.