Comprehensive Stock Evaluation Framework with Mathematical Precision

| January 3, 2025

Discover a robust stock evaluation framework integrating data acquisition, fundamental analysis, fair price estimation, and portfolio optimization with strict mathematical derivations.

Comprehensive Stock Evaluation with Mathematical Rigor

In the ever-evolving landscape of financial markets, making informed investment decisions is paramount. Leveraging a systematic approach that combines data-driven analysis with mathematical precision can significantly enhance the accuracy and reliability of stock evaluations. In this blog, we’ll delve into a robust stock evaluation framework that integrates financial data acquisition, fundamental analysis, fair price estimation, portfolio optimization, and predictive modeling. Each component is underpinned by rigorous mathematical derivations to ensure a thorough and objective assessment of stock performance.


Table of Contents

Data Acquisition and Caching

Efficient data retrieval and management are foundational to any analytical framework. The Stock Analyzer leverages the Financial Modeling Prep (FMP) API to fetch essential financial data, including income statements, balance sheets, cash flow statements, and historical price data. To optimize performance and adhere to API rate limits, the framework employs a caching mechanism that stores fetched data locally, minimizing redundant API calls.

Caching Mechanism

  • Initialization: Upon instantiation, the analyzer sets up cache directories for different data types (e.g., profiles, income statements).
  • Data Retrieval: Before making an API request, the analyzer checks if the required data is already cached. If available and up-to-date, it utilizes the cached data; otherwise, it fetches fresh data from the API.
  • Rate Limiting: To comply with API usage policies, the analyzer enforces a rate limit, spacing out requests appropriately.

Fundamental Indicators Calculation

Fundamental analysis involves evaluating a company’s financial health and performance to estimate its intrinsic value. The analyzer computes a suite of fundamental indicators derived from the company’s financial statements.

Profitability Metrics

1. Profit Margin (PMPM)

PM=Net IncomeRevenuePM = \frac{\text{Net Income}}{\text{Revenue}}

Measures the percentage of revenue that constitutes net income, indicating overall profitability.

2. Operating Margin (OMOM)

OM=Operating IncomeRevenueOM = \frac{\text{Operating Income}}{\text{Revenue}}

Assesses the efficiency of core business operations by evaluating the proportion of revenue remaining after covering operating expenses.

3. EBITDA Margin (EMEM)

EM=EBITDARevenueEM = \frac{\text{EBITDA}}{\text{Revenue}}

Evaluates a company’s operating performance by focusing on earnings before interest, taxes, depreciation, and amortization.

Return Metrics

1. Return on Equity (ROEROE)

ROE=Net IncomeTotal EquityROE = \frac{\text{Net Income}}{\text{Total Equity}}

Indicates how effectively a company utilizes shareholders’ equity to generate profits.

2. Return on Assets (ROAROA)

ROA=Net IncomeTotal AssetsROA = \frac{\text{Net Income}}{\text{Total Assets}}

Measures the efficiency of asset utilization in generating net income.

3. Return on Invested Capital (ROICROIC)

ROIC=Net IncomeTotal Debt+Total EquityCash and Short-Term InvestmentsROIC = \frac{\text{Net Income}}{\text{Total Debt} + \text{Total Equity} - \text{Cash and Short-Term Investments}}

Approximates the return generated on the capital invested in the business.

Liquidity Metrics

1. Current Ratio (CRCR)

CR=Total Current AssetsTotal Current LiabilitiesCR = \frac{\text{Total Current Assets}}{\text{Total Current Liabilities}}

Assesses a company’s ability to meet short-term obligations with its short-term assets.

2. Quick Ratio (QRQR)

QR=Cash and Short-Term Investments+Net ReceivablesTotal Current LiabilitiesQR = \frac{\text{Cash and Short-Term Investments} + \text{Net Receivables}}{\text{Total Current Liabilities}}

A more stringent measure of liquidity, excluding inventory from current assets.

Leverage Metrics

1. Debt to Equity Ratio (D/ED/E)

D/E=Total DebtTotal EquityD/E = \frac{\text{Total Debt}}{\text{Total Equity}}

Indicates the proportion of debt financing relative to shareholders’ equity.

2. Net Debt to EBITDA Ratio (NDE/EBITDANDE/EBITDA)

NDE/EBITDA=Net DebtEBITDANDE/EBITDA = \frac{\text{Net Debt}}{\text{EBITDA}}

Evaluates a company’s ability to pay off its net debt with its earnings before interest, taxes, depreciation, and amortization.

Efficiency Metrics

Free Cash Flow Margin (FCFMFCFM)

FCFM=Free Cash FlowRevenueFCFM = \frac{\text{Free Cash Flow}}{\text{Revenue}}

Measures the proportion of revenue that translates into free cash flow, indicating financial flexibility.


Fair Price Calculation

Determining the fair price per share of a stock is pivotal for investors aiming to assess whether a stock is undervalued or overvalued. This process integrates multiple financial ratios to ensure precision and reliability. Below is a comprehensive mathematical framework accompanied by corresponding Python code snippets that illustrate each step of the calculation.

1. Establishing Share Count

The foundation of per-share valuations lies in accurately determining the share count. This is calculated by dividing the Market Capitalization (mktCap) by the Price per Share (price).

Share Count=Market CapitalizationPrice per Share\text{Share Count} = \frac{\text{Market Capitalization}}{\text{Price per Share}}

Python Code:

mkt_cap = self.fundamentals.get(ticker, {}).get('mktCap', 0)
price = self.fundamentals.get(ticker, {}).get('price', 0)
# Validate mkt_cap and price
if mkt_cap <= 0 or price <= 0:
logger.error(f"Invalid mktCap ({mkt_cap}) or price ({price}) for {ticker} on {metrics['Date']}")
fair_prices.append({
'Date': metrics['Date'],
'fair_price': None,
'lower_bound': None,
'upper_bound': None
})
continue
share_count = mkt_cap / price
logger.debug(f"Share Count for {ticker} on {metrics['Date']}: {share_count}")

2. Price-to-Earnings (P/E) Valuation

First, calculate the Earnings Per Share (EPS):

EPS=Net IncomeShare Count\text{EPS} = \frac{\text{Net Income}}{\text{Share Count}}

Then, derive the P/E Valuation using the Average P/E Ratio (average_pe):

P/E Valuation=EPS×Average P/E Ratio\text{P/E Valuation} = \text{EPS} \times \text{Average P/E Ratio}

Python Code:

net_income = metrics.get('NetIncome', 0)
if share_count > 0 and net_income > 0:
eps = net_income / share_count
if eps > 0:
pe_valuation = eps * average_pe
valuations['PE'] = pe_valuation
weights['PE'] = 0.30
logger.debug(f"P/E Valuation: eps={eps}, average_pe={average_pe}, pe_valuation={pe_valuation}")
else:
logger.warning(f"P/E Valuation not applicable due to zero EPS for {ticker} on {metrics['Date']}.")
else:
logger.warning(f"P/E Valuation not applicable due to zero share count or net income for {ticker} on {metrics['Date']}.")

3. Price-to-Book (P/B) Valuation

Determine the Book Value per Share:

Book Value per Share=Total EquityShare Count\text{Book Value per Share} = \frac{\text{Total Equity}}{\text{Share Count}}

Subsequently, compute the P/B Valuation using the Average P/B Ratio (average_pb):

P/B Valuation=Book Value per Share×Average P/B Ratio\text{P/B Valuation} = \text{Book Value per Share} \times \text{Average P/B Ratio}

Python Code:

total_equity = metrics.get('TotalEquity', 0)
if share_count > 0 and total_equity > 0:
book_value_ps = total_equity / share_count
if book_value_ps > 0:
pb_valuation = book_value_ps * average_pb
valuations['PB'] = pb_valuation
weights['PB'] = 0.20
logger.debug(f"P/B Valuation: book_value_ps={book_value_ps}, average_pb={average_pb}, pb_valuation={pb_valuation}")
else:
logger.warning(f"P/B Valuation not applicable due to zero book value per share for {ticker} on {metrics['Date']}.")
else:
logger.warning(f"P/B Valuation not applicable due to zero share count or TotalEquity for {ticker} on {metrics['Date']}.")

4. Enterprise Value to EBITDA (EV/EBITDA) Valuation

Calculate the Enterprise Value (EV):

Enterprise Value=Market Capitalization+Net Debt\text{Enterprise Value} = \text{Market Capitalization} + \text{Net Debt}

Then, determine the EV/EBITDA Valuation on a per-share basis using the Average EV/EBITDA Ratio (average_ev_ebitda):

EV/EBITDA Valuation per Share=EBITDAAverage EV/EBITDA Ratio×Share Count\text{EV/EBITDA Valuation per Share} = \frac{\text{EBITDA}}{\text{Average EV/EBITDA Ratio} \times \text{Share Count}}

Python Code:

ebitda = metrics.get('EBITDA', 0)
net_debt = metrics.get('NetDebt', 0)
if ebitda > 0:
ev = mkt_cap + net_debt
ev_ebitda_valuation = ebitda / average_ev_ebitda
if ev_ebitda_valuation > 0:
per_share_ev_ebitda = ev_ebitda_valuation / share_count
valuations['EVEBITDA'] = per_share_ev_ebitda
weights['EVEBITDA'] = 0.25
logger.debug(f"EV/EBITDA Valuation: ev={ev}, ebitda={ebitda}, average_ev_ebitda={average_ev_ebitda}, per_share_ev_ebitda={per_share_ev_ebitda}")
else:
logger.warning(f"EV/EBITDA Valuation not applicable due to zero EV/EBITDA for {ticker} on {metrics['Date']}.")
else:
logger.warning(f"EV/EBITDA Valuation not applicable due to missing or zero EBITDA for {ticker} on {metrics['Date']}.")

5. Price-to-Free Cash Flow (P/FCF) Valuation

Compute the Free Cash Flow per Share:

Free Cash Flow per Share=Free Cash FlowShare Count\text{Free Cash Flow per Share} = \frac{\text{Free Cash Flow}}{\text{Share Count}}

Then, calculate the P/FCF Valuation using the Average P/FCF Ratio (average_p_fcf):

P/FCF Valuation=Free Cash Flow per Share×Average P/FCF Ratio\text{P/FCF Valuation} = \text{Free Cash Flow per Share} \times \text{Average P/FCF Ratio}

Python Code:

fcf = metrics.get('FreeCashFlow', 0)
if share_count > 0 and fcf > 0:
fcf_ps = fcf / share_count
if fcf_ps > 0:
fcf_valuation = fcf_ps * average_p_fcf
valuations['FCF'] = fcf_valuation
weights['FCF'] = 0.25
logger.debug(f"FCF Valuation: fcf_ps={fcf_ps}, average_p_fcf={average_p_fcf}, fcf_valuation={fcf_valuation}")
else:
logger.warning(f"FCF Valuation not applicable due to zero FCF per share for {ticker} on {metrics['Date']}.")
else:
logger.warning(f"FCF Valuation not applicable due to missing or zero FCF/share count for {ticker} on {metrics['Date']}.")

6. Weighted Fair Price Calculation

After obtaining individual valuations, assign weights to each based on their significance. The fair price is then calculated as the weighted sum of these valuations:

Fair Price=i=1n(Valuationi×Weighti)\text{Fair Price} = \sum_{i=1}^{n} (\text{Valuation}_i \times \text{Weight}_i)

Additionally, to establish a valuation range, determine the lower bound and upper bound using the minimum and maximum of the individual valuations:

Lower Bound=min(Valuation1,Valuation2,,Valuationn)\text{Lower Bound} = \min(\text{Valuation}_1, \text{Valuation}_2, \dots, \text{Valuation}_n) Upper Bound=max(Valuation1,Valuation2,,Valuationn)\text{Upper Bound} = \max(\text{Valuation}_1, \text{Valuation}_2, \dots, \text{Valuation}_n)

Python Code:

if not valuations:
logger.error(f"No valid valuation metrics available for {ticker} on {metrics['Date']}. Fair price cannot be computed.")
fair_prices.append({
'Date': metrics['Date'],
'fair_price': None,
'lower_bound': None,
'upper_bound': None
})
continue
# Adjust weights if some valuations are missing
total_weight = sum(weights.values())
normalized_weights = {k: v / total_weight for k, v in weights.items()}
# Debug: Print valuations and normalized_weights
logger.debug(f"Valuations: {valuations}")
logger.debug(f"Normalized Weights: {normalized_weights}")
# Compute weighted fair price
fair_price = sum(valuations[k] * normalized_weights[k] for k in valuations)
logger.info(f"Fair Price for {ticker} on {metrics['Date']}: {fair_price}")
# Dynamic Bounds based on individual valuations
min_valuation = min(valuations.values())
max_valuation = max(valuations.values())
logger.debug(f"Valuation Range: {min_valuation} - {max_valuation}")
# Set dynamic bounds
lower_bound = min_valuation
upper_bound = max_valuation
# Handle cases where min and max are the same
if lower_bound == upper_bound:
lower_bound = fair_price * 0.95 # Default to -5%
upper_bound = fair_price * 1.05 # Default to +5%
fair_prices.append({
'Date': metrics['Date'],
'fair_price': fair_price,
'lower_bound': lower_bound,
'upper_bound': upper_bound
})

7. Consolidating Fair Price Estimates

Finally, compile the calculated fair price, lower bound, and upper bound into a DataFrame for each ticker:

Fair Price Estimates=[Datefair_pricelower_boundupper_bound]\text{Fair Price Estimates} = \begin{bmatrix} \text{Date} & \text{fair\_price} & \text{lower\_bound} & \text{upper\_bound} \\ \vdots & \vdots & \vdots & \vdots \\ \end{bmatrix}

Python Code:

self.fair_price_estimates[ticker] = pd.DataFrame(fair_prices)
logger.info(f"Fair Price Estimates for {ticker}:\n{self.fair_price_estimates[ticker].head()}")

8.Dynamic Bounds

To account for variability and uncertainty, dynamic upper and lower bounds are established based on the range of individual valuations:

Lower Bound=min(Vi)\text{Lower Bound} = \min(V_i) Upper Bound=max(Vi)\text{Upper Bound} = \max(V_i)

If the bounds are identical (indicating uniform valuations), a default fluctuation is applied:

Lower Bound=Fair Price×0.95\text{Lower Bound} = \text{Fair Price} \times 0.95 Upper Bound=Fair Price×1.05\text{Upper Bound} = \text{Fair Price} \times 1.05

These bounds provide a confidence interval within which the fair price is expected to lie.


Fundamental Scoring

To distill the multitude of fundamental indicators into a single actionable metric, the analyzer assigns a numeric score ranging from 0 to 100. This score is a composite of various fundamental aspects, such as profitability, leverage, liquidity, and free cash flow margins.

Scoring Methodology

Each fundamental metric is evaluated against predefined criteria, contributing incrementally to the overall score:

  • Profit Margin:

    • 10%: +10 points

    • 5%: +5 points

  • Debt to Equity Ratio:

    • <0.5: +10 points
    • <1.0: +5 points
  • Current Ratio:

    • 2.0: +10 points

    • 1.0: +5 points

  • Return on Equity (ROE):

    • 15%: +10 points

    • 8%: +5 points

  • Free Cash Flow Margin:

    • 8%: +10 points

    • 3%: +5 points

The cumulative score offers a quick snapshot of the company’s fundamental health, facilitating comparisons across different stocks.


Price Data and Forecasting

Historical price data is integral to understanding stock performance and projecting future trends. The analyzer retrieves historical price data, updates it regularly, and employs predictive modeling to forecast future prices.

Data Retrieval and Caching

  • Historical Data: Fetches comprehensive historical prices, storing them locally to minimize API calls.
  • Update Mechanism: Ensures that the latest price data is incorporated by checking the last cached date and fetching new data as needed.

Predictive Modeling with Prophet

To anticipate future price movements, the analyzer utilizes Prophet, a robust forecasting tool developed by Facebook. Prophet models time-series data, accounting for trends and seasonality, to predict future prices along with confidence intervals.


Portfolio Optimization

Diversifying investments across multiple assets can optimize returns while managing risk. The analyzer employs modern portfolio theory to construct optimal portfolios, focusing on two primary objectives:

  1. Efficient Frontier: The set of optimal portfolios offering the highest expected return for a defined level of risk.
  2. Optimal Portfolios:
    • Minimum Variance Portfolio: The portfolio with the lowest possible volatility.
    • Maximum Sharpe Ratio Portfolio: The portfolio that maximizes return per unit of risk, considering the risk-free rate.

Efficient Frontier

The Efficient Frontier is derived by solving a series of optimization problems, each targeting a different level of expected return. For each target return RtR_t, the objective is to minimize portfolio volatility σp\sigma_p:

Minimize σp=wΣwSubject to wμ=Rti=1nwi=10wi1i\begin{align*} \text{Minimize } & \sigma_p = \sqrt{\mathbf{w}^\top \mathbf{\Sigma} \mathbf{w}} \\ \text{Subject to } & \mathbf{w}^\top \mathbf{\mu} = R_t \\ & \sum_{i=1}^{n} w_i = 1 \\ & 0 \leq w_i \leq 1 \quad \forall i \end{align*}

Where:

  • w\mathbf{w} = Vector of portfolio weights
  • μ\mathbf{\mu} = Vector of expected returns
  • Σ\mathbf{\Sigma} = Covariance matrix of asset returns
  • nn = Number of assets

Minimum Variance Portfolio

The Minimum Variance Portfolio is the point on the Efficient Frontier with the lowest volatility. It is obtained by solving the optimization problem without constraining the expected return, focusing solely on minimizing risk.

Maximum Sharpe Ratio Portfolio

The Maximum Sharpe Ratio Portfolio seeks to maximize the Sharpe Ratio (SRSR), defined as:

SR=RpRfσpSR = \frac{R_p - R_f}{\sigma_p}

Where:

  • RpR_p = Expected portfolio return
  • RfR_f = Risk-free rate
  • σp\sigma_p = Portfolio volatility

By maximizing SRSR, the portfolio achieves the highest return per unit of risk, balancing performance and stability.


Visualization

Visual representations enhance the interpretability of complex data and models. The analyzer employs advanced plotting libraries to generate interactive and insightful visualizations.

Fair Price vs. Actual Price

Plots juxtapose the actual stock prices against the computed fair prices, along with dynamic upper and lower bounds. This visualization aids in identifying overvalued or undervalued stocks relative to their intrinsic value.

Efficient Frontier

The Efficient Frontier plot showcases the trade-off between risk and return for various portfolios, highlighting optimal investment strategies. Individual assets are plotted to illustrate their contribution to portfolio diversification.

Interactive features, such as hover information, allow users to glean detailed insights directly from the plots.


Conclusion

This comprehensive stock evaluation framework marries data acquisition with fundamental analysis and mathematical optimization to empower investors with actionable insights. By meticulously computing fundamental indicators, estimating fair prices through multiple valuation approaches, and optimizing portfolios based on modern portfolio theory, the analyzer provides a holistic tool for informed investment decision-making. The incorporation of predictive modeling and dynamic visualization further augments the analytical capabilities, ensuring that investors are well-equipped to navigate the complexities of the financial markets.