Buy Like Warren Buffett: Build a Python Stock Screener with Yahoo Finance

Share
Buy Like Warren Buffett: Build a Python Stock Screener with Yahoo Finance

In our previous B+ investor exercise, we used Berkshire Hathaway’s public 13F filings to build a smaller universe of stocks worth studying. While this provided a starting point distilled from one of the world’s most experienced investment organizations, it did not tell us which company best suited our own investment criteria, or whether its current share price offered a reasonable margin of safety. So our next question became:

How can we distill Buffett’s investing principles into a practical checklist, apply it to these companies, and determine whether any of them is still available at a fair price today?

Most stock screeners gave us a wall of ratios: price-to-earnings, revenue growth, gross margin, debt, return on equity, and dozens more. The data was available, but the investment question was still hiding somewhere inside it.

We did not want another dashboard full of loosely connected numbers. We wanted a repeatable checklist: a five-question distillation of Buffett’s investing principles that we could test with Python.

  1. Know the company: Can we explain how the business makes money?
  2. Economic moat: Do margins, returns on invested capital, cash conversion, and free cash flow suggest a durable advantage?
  3. Financial discipline: Are debt and interest obligations manageable?
  4. Predictable earnings: Has the company produced reasonably stable profits?
  5. Fair price: Does the market price provide a margin of safety against our estimated value?

So in this post, we are going to create a Python screener that retrieves company and financial statement data through Yahoo Finance and organizes it around those five questions.

Python Screener is the Business Analyst, Not the Investment Manager

Think of our Buffett-style stock screener as a business analyst preparing a company assessment.

Working from our five-question distillation, the analyst retrieves publicly available data, performs the financial checks, and organizes the findings into a consistent report. It is not distracted by a rising share price, a fashionable product, or a ticker trending on social media. The analyst provides company information and one-to-five-star assessments of its economic moat, financial discipline, predictable earnings, and fair price. More importantly, it displays the underlying figures so we can understand what influenced each score.

However, our Python analyst is not the investment manager. It can identify patterns, compare results with predefined thresholds, and highlight areas that need attention. It cannot explain every unusual result. You, my friend, are still the key person to interpret the findings and make the investment decision.

Implementing the Buffett-style Python Screener

Let's bring our Python analyst to life. Here is how we can implement a clean, automated Buffett-style stock screener using Python with pandas, numpy and yfinance.

Like last time, let's install the required libraies with uv:

uv add yfinance pandas numpy

Our program file is named as bbuy.py , because we are the B+ investor that follow Buffett-style buying screener :]].

The screener structure is:

bbuy.py
└── screen_buffett_stock()           # Entry point of the stock
    ├── get_stock_info()             # Gets company general info
    ├── score_economic_moat()        # Evaluates competitive advantage
    ├── score_financial_discipline() # Assesses debt levels
    ├── score_predictable_earnings() # Measures historical earnings stability
    └── score_fair_price()           # Determines intrinsic value

We start from the screen_buffett_stock() function. Our screener obtains the data from the trailing 12 months, and financial statement, balance sheet and cashflow from last 4 fiscal years using Yahoo Finance. Then we use the data to analyze, score and print out the stock info, economic moat, financial discipline, predictable earnings and fair price.

import yfinance as yf

ticker_symbol="AAPL"

stock = yf.Ticker(ticker_symbol)
info = stock.info
financial_statement = stock.financials
balance_sheet = stock.balance_sheet
cashflow = stock.cashflow

company_info = get_stock_info(info)
print(f"Company Description: {company_info['summary']}")

results = score_economic_moat(financial_statement, balance_sheet, cashflow, info)
print(f"Score for {results.name}: {_score_to_stars(results.score)}")
print(f"Gross Margin: {format_percentage(results.detail.get('gross_margin'))}, ROIC Avg: {format_percentage(results.detail.get('roic_avg'))}, Ýear(s) of Revenue Decline: {results.detail.get('revenue_down_years')}, Free Cash Flow Margin: {format_percentage(results.detail.get('free_cash_flow_margin'))}")

results = score_financial_discipline(financial_statement, info)
results = score_predictable_earnings(financial_statement)
results = score_fair_price(financial_statement, cashflow, info)  

"Never invest in a business you cannot understand."

The first Buffett-style question is also the least mathematical: Can you explain how the business makes money? Our screener retrieves several pieces of basic company information from Yahoo Finance:

def get_stock_info(info) -> dict:
    company_name = info.get("longName") or info.get("shortName")
    stock_price = info.get("currentPrice") or info.get("regularMarketPrice")
    officers = info.get("companyOfficers", [])
    top_officer = officers[0] if officers else None
    ...
    return {
        "name": company_name,
        "current_price": stock_price,
        "summary": short_summary,
        "website": info.get("website", "None"),
    } 

It provides generic information like, company name, current share price, industry, company website, the top office, etc.. When you see the company information and don't know what the company is, well, just stop it here and select another company. If you do know the company then try to answer the following questions:

  • What does the company sell?
  • Who pays the company?
  • Why do customers choose it?
  • What are its largest costs?
  • How does it produce more profit over time?
  • What could cause the business to lose money?

If you know the answers, good, if you don't, go find them. The aim of this exercise is to help us to value our investment target with confidence.

"In business, I look for economic castles protected by unbreachable moats."

Once we find a company that we understand, the next Buffett-style action is, find a company surrounded by an economic moat. An economic moat is a durable advantage that helps a company defend its customers, profits, and returns against competitors.

Our measurement of economic moat are based on:

  • Gross Margin
  • Return On Invested Capital (ROIC)
  • Revenue Consistency
  • Cash Conversion Ratio
  • Free Cash Flow Margin

Gross Margin

Gross margin measures how much revenue remains after the direct cost of producing a company’s goods or services. A strong gross margin may suggest pricing power, low production costs, differentiated products, valuable intellectual property, or some combination of these advantages. Our screener does not examine only the latest gross margin. It also measures how stable the margin has been across multiple reporting periods. That stability check (using standard deviation) is important. One unusually profitable year may result from a temporary shortage, a favorable product mix, or a short-lived price increase. A durable advantage should normally leave evidence across several years.

def score_economic_moat(financial_statement, balance_sheet, cashflow, info) -> BRuleScore:
    gross_margins_latest = float(info.get("grossMargins")) if info.get("grossMargins") is not None else None
    gross_margin_score = _rate(gross_margins_latest, 0.50, 0.42, 0.35, 0.20)

    revenue = financial_statement.loc["Total Revenue"]
    gross_profit = financial_statement.loc["Gross Profit"]
    if revenue is not None and gross_profit is not None:
        aligned = _align(revenue, gross_profit)
        if len(aligned) == 2:
            aligned_revenue, aligned_gross_profit = aligned
            gross_margin_series = (aligned_gross_profit / aligned_revenue).replace([np.inf, -np.inf], np.nan).dropna()
    gross_margin_std = float(gross_margin_series.std()) if gross_margin_series is not None and len(gross_margin_series) >= 3 else None
    gross_margin_stability_score = _rate(gross_margin_std, 0.03, 0.05, 0.08, 0.15, higher_is_better=False)

ROIC

ROIC asks how efficiently a company turns invested money into profit. Our screener estimates:

Invested Capital ≈ Shareholders’ Equity + Debt − Cash

It then compares net income, the return, with that invested capital. A company that repeatedly produces attractive returns without constantly demanding more capital may possess a strong economic engine. While a good ROIC varies by industry, as B+ investors, we follow a simple rule of thumb: an average ROIC of 16% or higher is good, while 20% or more is superior for mature companies.

    net_income = financial_statement.loc["Net Income"]
    equity = balance_sheet.loc["Stockholders Equity"]
    debt = balance_sheet.loc["Total Debt"]
    cash = balance_sheet.loc["Cash Cash Equivalents And Short Term Investments"]

    roic_series = []
    if net_income is not None and equity is not None:
        invested_capital = equity.copy()
        if debt is not None:
            invested_capital = invested_capital.add(debt.reindex(invested_capital.index), fill_value=0)
        if cash is not None:
            invested_capital = invested_capital.sub(cash.reindex(invested_capital.index), fill_value=0)
        aligned = _align(net_income, invested_capital)
        if len(aligned) == 2:
            aligned_net_income, aligned_invested_capital = aligned
            roic_series = (aligned_net_income / aligned_invested_capital.replace(0, np.nan)).replace([np.inf, -np.inf], np.nan).dropna()   
    roic_avg = float(roic_series.mean()) if len(roic_series) else None
    roic_score = _rate(roic_avg, 0.20, 0.16, 0.12, 0.08)

Revenue Consistency

Our screener counts how many years reported declining revenue. This is one of the simplest checks in our screener, but it can still provide useful clues about the stability of a business. A company that grows or maintains revenue through most years may have products, services, or customer relationships that remain in demand across different economic conditions. On the other hand, repeated revenue declines may suggest a weakening competitive position, shrinking demand, or exposure to a highly cyclical industry.

    negative_growth_years = None
    if revenue is not None and len(revenue) >= 3:
        revenue_growth = revenue.sort_index().pct_change().dropna()
        negative_growth_years = (revenue_growth < 0).sum()
    revenue_growth_score = _rate(negative_growth_years, 0, 1, 2, 3, higher_is_better=False) if negative_growth_years is not None else 0.0

Cash Conversion Ratio

Companies pay employees, suppliers, lenders, and shareholders with cash. Our screener measures how efficiently a company turns its accounting profits into usable cash by comparing operating cash flow with net income:

Cash Conversion Ratio ≈ Operating Cash Flow ÷ Net Income

Healthy cash conversion ratio (value ≥ 1.0) suggests that reported profits are becoming real operating cash rather than remaining trapped in receivables or depending heavily on accounting adjustments. Poor cash conversion ratio (value < 1.0) does not automatically prove that something is wrong. A growing business may temporarily invest in inventory or offer customers longer payment terms. However, persistently weak cash conversion deserves investigation.

    cash_conversion = None
    operating_cash_flow = cashflow.loc["Operating Cash Flow"]
    if operating_cash_flow is not None and net_income is not None:
        aligned = _align(operating_cash_flow, net_income)
        if len(aligned) == 2:
            aligned_operating_cash_flow, aligned_net_income = aligned
            has_net_income_from_series = aligned_net_income > 0
            if has_net_income_from_series.any():
                cash_conversion = float((aligned_operating_cash_flow[has_net_income_from_series] / aligned_net_income[has_net_income_from_series]).mean())
    cash_conversion_score = _rate(cash_conversion, 1.20, 1.05, 0.90, 0.60)

Free Cash Flow Margin

Free cash flow is the cash left after operating expenses and capital expenditure. And the free cash flow margin tells us exactly how many cents of every dollar of sales are turned into cash available for debt reduction, dividends, share repurchases, acquisitions, or reinvestment. Our screener calculates an average free cash flow margin:

Free Cash Flow Margin = Free Cash Flow ÷ Revenue

A strong free cash flow margin suggests that the company can convert a meaningful portion of sales into cold, hard cash. A margin of ≥ 20% is excellent, a high cash-generating powerhouse that can fund growth or return capital with ease, 15% to 19% is strong, 10% to 14% is typical of well-run, stable companies with efficient operations. While 5% to 9% clears the "there is ome free cash" bar, and < 5% or negative is a warning indicating heavy investment or a struggling business.

   free_cash_flow = cashflow.loc["Free Cash Flow"] if "Free Cash Flow" in cashflow.index else None
    free_cash_flow_margin = None
    if free_cash_flow is not None and revenue is not None:
        aligned = _align(free_cash_flow, revenue)
        if len(aligned) == 2:
            aligned_free_cash_flow, aligned_revenue = aligned
            free_cash_flow_margin = float((aligned_free_cash_flow / aligned_revenue.replace(0, np.nan)).mean())
    free_cash_flow_score = _rate(free_cash_flow_margin, 0.20, 0.15, 0.10, 0.05)

Together, these measurements create our screener’s economic moat score:

rated_moat_score = float(np.mean([gross_margin_score, gross_margin_stability_score, roic_score, revenue_growth_score, cash_conversion_score, free_cash_flow_score]))

"Only when the tide goes out do you discover who’s been swimming naked."

Financial discipline is the ultimate defense mechanism for a long-term value investor. When economic cycles turn down, companies burdened by excessive debt and weak balance sheets are quickly exposed. To ensure we only select companies capable of weathering any financial storm, our screener focuses on three core pillars:

  • Debt Burden to Earning Power
  • Interest Coverage
  • Debt to Equity Ratio

Debt Burden to Earning Power

Our screener calculates this metric by dividing total debt by average net income:

Debt Burden to Earnings ≈ Total Debt ÷ Average Net Income

This formula is inspired by a classic Buffett-style question:

"Does the company have enough earning power to pay off its long-term debt within three or four years?"

While companies ultimately repay debt using real cash flow rather than accounting net income, this metric provides a simple way to assess whether the company's debt burden is reasonable relative to its normal earning power. Our screener awards its highest score when total debt is roughly two years or less of average net income. As debt grows larger relative to earnings, the score becomes progressively weaker.

def score_financial_discipline(financial_statement, info) -> BRuleScore:
    net_income = financial_statement.loc["Net Income"]
    interest = financial_statement.loc["Interest Expense"]
    ebit = financial_statement.loc["EBIT"]
    
    total_debt = float(info.get("totalDebt")) if info.get("totalDebt") is not None else None
    net_income_avg = float(net_income.mean()) if net_income is not None and len(net_income) else None
    debt_over_net_income = (total_debt / net_income_avg) if (total_debt is not None and net_income_avg) else None
    debt_over_net_income_score = (_rate(debt_over_net_income, 2.0, 3.0, 4.0, 7.0, higher_is_better=False) if debt_over_net_income is not None and debt_over_net_income >= 0 else (5 if total_debt == 0 else 1))

Interest Coverage

Interest Coverage compares operating profit with interest expense:

Interest Coverage = Earnings Before Interest and Taxes (EBIT) ÷ Interest Expense

It provides an approximation of the operating profit available to meet interest payments. A high coverage ratio suggests that operating profit comfortably exceeds borrowing costs. A low ratio indicates that a relatively small decline in profit could make debt servicing uncomfortable.

    profit_cover_interest = None
    if ebit is not None and interest is not None:
        aligned = _align(ebit, interest)
        if len(aligned) == 2:
            aligned_ebit, aligned_interest = aligned
            aligned_interest = aligned_interest.abs()
            with_aligned_interest = aligned_interest > 0
            if with_aligned_interest.any():
                profit_cover_interest = float((aligned_ebit[with_aligned_interest] / aligned_interest[with_aligned_interest]).mean())
            elif float(aligned_ebit.mean()) > 0:
                profit_cover_interest = 99.0  #no interest expense, but profitable = very strong
    profit_cover_interest_score = _rate(profit_cover_interest, 8.0, 6.5, 5.0, 3.0) 

Debt to Equity Ratio

Debt to equity ratio measures a company's financial leverage by comparing its total borrowings against shareholders' equity:

Debt to Equity Ratio = Total debt ÷ Shareholders’ Equity

The rule of thumb: Less is more. Buffett-style investing heavily favors companies with a a debt to equity ratio below 0.5. A ratio this low ensures that the company cleanly owns significantly more than it owes, shielding it from financial distress during market downturns.

    debt_to_equity = info.get("debtToEquity")
    debt_to_equity_ratio = None
    if debt_to_equity is not None:
        # > 5 means the value is in percentage (500% is extremely rare), so we convert it to ratio
        debt_to_equity_ratio = float(debt_to_equity) / 100.0 if float(debt_to_equity) > 5 else float(debt_to_equity)
    debt_to_equity_score = _rate(debt_to_equity_ratio, 0.30, 0.40, 0.50, 1.00, higher_is_better=False) if debt_to_equity_ratio is not None else 0.0

Our screener combines several measurements to define a company's financial discipline:

rated_financial_discipline_score = float(np.mean([debt_over_net_income_score, profit_cover_interest_score, debt_to_equity_score]))

“If you don't feel comfortable making a rough estimate of an asset's future earnings, just forget it and move on.”

Predictable earnings do not have to rise in a perfectly straight line. Real businesses have product cycles, recessions, raw-material changes, currency movements, and occasional management mistakes. What we want is a business whose economics are understandable enough for us to make a cautious estimate of normal earning power. Our screener examines several indicators:

  • Negative Earnings Years
  • Years of Declining Earnings
  • Earnings Volatility
  • Trend Fit

Negative Earnings Years

A history of positive earnings is easier to analyze than repeated movement between profit and loss. Years of losses suggest that we need to determine whether the problem is cyclical, structural, or caused by poor execution. Our screener gives its best result when every available year reports positive net income.

def score_predictable_earnings(financial_statement) -> BRuleScore:
    net_income = financial_statement.loc["Net Income"]
    if net_income is None or len(net_income) < 3:
        return BRuleScore("Predictable Earnings", 1.0, 5.0, {"note": "need >=3 years of net income"})

    net_income = net_income.sort_index().astype(float)
    year_of_neg_net_income = int((net_income <= 0).sum())
    year_of_neg_net_income_score = 5 if year_of_neg_net_income == 0 else 1

Years of Declining Earnings

Our screener also counts how often net income declined from one year to the next. A single decline may be harmless. Repeated declines may indicate weakening demand, rising costs, declining margins, or the loss of a competitive advantage.

    yoy_net_income_growth = net_income.pct_change().dropna()
    year_of_neg_growth = int((yoy_net_income_growth < 0).sum())
    year_of_neg_growth_score = _rate(year_of_neg_growth, 0, 1, 2, 3, higher_is_better=False) if year_of_neg_growth is not None else 0.0

Earings Volatility

Our screener calculates volatility using a coefficient of variation for net income:

Coefficient of Variation = Standard Deviation of Net Income ÷ Average Net Income

A lower result indicates greater stability. This prevents us from being overly impressed by an attractive average that was produced by one exceptional year surrounded by several weak ones.

    net_income_mean = float(net_income.mean())
    net_income_std = float(net_income.std())
    net_income_coefficient_of_variation = (net_income_std / abs(net_income_mean)) if net_income_mean else None
    net_income_coefficient_of_variation_score = _rate(net_income_coefficient_of_variation, 0.20, 0.27, 0.35, 0.55, higher_is_better=False) if net_income_coefficient_of_variation is not None else 0.0

Trend Fit

Our screener fits a straight trend line through the historical net income values and calculates an R-squared result. R-squared is a value from 0 to 1 that measures how well a straight line describes a set of numbers (net income values in our case). A lower value means that earnings moved around less predictably. This can be useful as a consistency measurement.

    y = net_income.values
    x = np.arange(len(y), dtype=float)
    if len(y) >= 3 and np.std(y) > 0:
        coeff = np.polyfit(x, y, 1) # plotting y = ax + b
        slope = float(coeff[0]) # slope = a = coeff[0]
        estimated_y = np.polyval(coeff, x)
        rss_y = float(np.sum((y - estimated_y) ** 2)) # residual sum of squares of y, 0 = prefect fit
        tss_y = float(np.sum((y - y.mean()) ** 2)) # total sum of squares of y, 0 = all data points are the same
        r_squared = 1.0 - rss_y / tss_y if tss_y else None

    r_squared_score = _rate(r_squared, 0.80, 0.70, 0.60, 0.40) if r_squared is not None else 0.0

The predictable earnings score is the average of the four checks:

    rated_predictable_earnings_score = float(np.mean([year_of_neg_net_income_score, year_of_neg_growth_score, net_income_coefficient_of_variation_score, r_squared_score]))

"Price is what you pay. Value is what you get."

We can admire a company and still decline to buy its shares. A wonderful business does not automatically become a wonderful investment at every possible price. The fair-price section of our screener estimates value using:

  • Discount to the Estimated Value
  • Earnings Yield

Discount to the Estimated Value

This is actually the margin of safety explained in simple terms. The concept of a margin of safety lies at the heart of value investing. Rather than attempting to predict the future with precision, we assume that our valuation may be wrong. A margin of safety provides a buffer against mistakes in our assumptions. To estimate intrinsic value, the program first calculates an approximation of owner earnings:

Owner Earnings ≈ Net Income + Depreciation and Amortization − Capital Expenditure

def score_fair_price(financial_statement, cashflow, info) -> BRuleScore:
    net_income = financial_statement.loc["Net Income"]
    capital_expenditure = cashflow.loc["Capital Expenditure"]
    depreciation_amortization = cashflow.loc["Depreciation And Amortization"]
    if depreciation_amortization is None:
        depreciation_amortization = financial_statement.loc["Reconciled Depreciation"]  

    owner_earnings = None
    if net_income is not None:
        owner_earnings = net_income.copy()
        if depreciation_amortization is not None:
            aligned = _align(owner_earnings, depreciation_amortization)
            if len(aligned) == 2:
                aligned_owner_earnings, aligned_depreciation_amortization = aligned
                owner_earnings = aligned_owner_earnings + aligned_depreciation_amortization
        if capital_expenditure is not None:
            aligned = _align(owner_earnings, capital_expenditure)
            if len(aligned) == 2:
                aligned_owner_earnings, aligned_capital_expenditure = aligned
                owner_earnings = aligned_owner_earnings - aligned_capital_expenditure.abs()

    owner_earnings_avg = float(owner_earnings.mean()) if owner_earnings is not None and len(owner_earnings) else None

The idea is to estimate how much actual cash the owners of the business could receive after funding the investments required to maintain and operate the company. Our screener then estimates a conservative long-term growth rate based on historical revenue growth. Using a required return of 10%, our screener applies a simplified Gordon Growth Model-style formula:

Intrinsic Value Per Share ≈ (Owner Earnings × (1 + Growth Rate) ÷ (Required Return − Growth Rate)) ÷ Outstanding Shares

The margin of safety is then calculated as:

Margin of Safety = 1 − Current Share Price ÷ Intrinsic Value Per Share

    revenue = financial_statement.loc["Total Revenue"]
    growth_rate = 0.03
    if revenue is not None and len(revenue) >= 2:
        revenue_sorted = revenue.sort_index()
        first, last = float(revenue_sorted.iloc[0]), float(revenue_sorted.iloc[-1])
        years = max(len(revenue_sorted) - 1, 1)
        if first > 0 and last > 0:
            growth_rate = (last / first) ** (1 / years) - 1
    growth_rate = float(np.clip(growth_rate, 0.0, 0.06))
    returned_rate = 0.10 
    intrinsic_value = None  
    intrinsic_value_per_share = None
    margin_of_safety = None
    if owner_earnings_avg and owner_earnings_avg > 0 and shares_on_the_market:
        intrinsic_value = owner_earnings_avg * (1 + growth_rate) / (returned_rate - growth_rate)
        intrinsic_value_per_share = intrinsic_value / float(shares_on_the_market)
        if current_price:
            margin_of_safety = 1.0 - float(current_price) / intrinsic_value_per_share
    margin_of_safety_score = _rate(margin_of_safety, 0.30, 0.20, 0.15, 0.00) if margin_of_safety is not None else 0.0

Our screener awards its strongest score when the estimated margin of safety is approximately 30% or greater. Smaller discounts receive progressively lower scores. A negative margin of safety means the current market price exceeds our estimate of intrinsic value.

Earnings Yield

While the margin of safety calculation focuses on intrinsic value, earnings yield offers a simpler valuation perspective. Our screener converts the trailing price to earnings ratio into an earnings yield:

Earnings Yield = 1 ÷ Trailing P/E Ratio

Earnings yield answers a straightforward question:

How much annual earnings am I receiving for every dollar invested at today's price?

A higher earnings yield generally points to an attractive valuation, while a lower yield suggests investors are paying more for each dollar of earnings. However, a company with a high yield may deserve a low valuation because its earnings are declining. Conversely, a company with a lower yield may justify a premium valuation because of its growth prospects. Therefore, our screener treats earnings yield as a supporting valuation check, rather than a complete valuation method.

    trailing_pe = info.get("trailingPE", None) # current price divided by the last twelve months of earnings per share
    earning_yield = (1.0 / float(trailing_pe)) if trailing_pe and float(trailing_pe) > 0 else None
    earning_yield_score = _rate(earning_yield, 0.08, 0.07, 0.06, 0.04) if earning_yield is not None else 0.0

Put the two measurements together to justify the price:

    rated_predictable_earnings_score = float(np.mean([year_of_neg_net_income_score, year_of_neg_growth_score, net_income_coefficient_of_variation_score, r_squared_score]))

Talk the Talk and Walk the Walk

The fun part of coding is running it to see the output. We’ve talked enough about how our screener works, let's actually run it and see it in action. We'll use one of the largest holdings in Berkshire Hathaway's portfolio to test it out: Apple Inc.

$uv run bbuy.py AAPL

First, we pull the company information. If you know, you know. If you don't, give up and pick another.

Next, we evaluate Apple's Economic Moat:

Other than the 1 year of revenue decline, Apple stands strong across every econmic moat indicator, easily scoring 4 out of 5 stars in this cateogry.

Mocing on Financial Discipline:

Apple earns more than it owes and can cover its interest effortlessly, allowing it to shine in Finanical Discipline.

How does Apple's earnings hold up historically?

Apple hasn't faced net loss in recent years, but it recorded 2 net income declines. Both trend predictability and volatility look remarkably stable for a mature company.

Up to this point, Apple scores high in all indicators, indicating a good company we should put our money on. Then that brings us to the ultimate question, the company is great, but is the price right?

Well, the figures say it all. Not today.

What We Learned and What We Should Watch

This exercise is an educational experiment, not investment advice. We used a reverse-engineering approach to program Buffett-style buying principles. Running this script won't automatically turn you into Warren Buffett, instead, it borrows from the concept of model distillation - following the successful footsteps of a well-defined mechanism rather than training from scratch.

I found myself spending more time on this exercise than on many pure technical topics, mostly because several financial terms and concepts were entirely new to an engineering background like mine. To borrow a classic quote from Warren Buffett:

"The more you learn, the more earn."

Let's keep on learning and building something new.

(The complete source for our B+ screener can be found at https://github.com/codeastar/bplus_investor. If you found this guide helpful, consider giving the repo a star!)

Read more