In 2023, we executed a comprehensive process to estimate the expected return on equity for the project. This analysis was designed to support our strategic investment decisions by ensuring that the project’s return exceeds the minimum acceptable hurdle rate. We employed a two-stage approach that integrated advanced quantitative techniques with empirical financial data.
Process Overview
In the first stage, we derived an unbiased measure of systematic risk, known as the unlevered beta, by analyzing a carefully curated peer group of companies. Given the absence of direct competitors, we selected companies that exhibited similar operational characteristics and risk exposures. The peer group included entities such as Maisons du Monde S.A. (“ZMM.F”), Fiskars Oyj Abp (“FSKRS.HE”), Arhaus, Inc. (“ARHS”), Topps Tiles Plc (“929.F”), and others. We used historical market data spanning a five-year period to perform regression analysis, thereby estimating the individual betas. Adjustments were made to account for differences in cash holdings, ensuring that our unlevered beta truly reflected the operational risk.
In the second stage, we re-levered the unlevered beta using IGNI’s specific capital structure parameters, namely the effective tax rate and Debt/Equity ratio. The levered beta, reflecting both business and financial risk, was then applied within the Capital Asset Pricing Model (CAPM) framework to calculate the expected return on equity. Key parameters, such as the risk-free rate and the market risk premium, were defined based on prevailing market conditions in 2023.
Detailed Implementation
Below is the complete code used in our analysis. The process is divided into two modules: one for the CAPM-based expected return calculation and another for the pure beta play methodology.
Pure Beta Play Module:
from datetime import datetime, timedelta
from typing import Tuple, List
import statsmodels.api as sm
import yfinance as yf
TIME_HORIZON_DAYS = 5 * 365 # Historical data period of five years
INTERVAL = '1mo' # Monthly interval for data sampling
def calculate_stock_beta(stock: str, market: str) -> float:
"""
Calculate the beta for a given stock using a specific market index.
:param stock: Ticker symbol of the stock.
:param market: Ticker symbol of the market index.
:return: Beta value of the stock.
"""
end_date = datetime.now()
start_date = end_date - timedelta(days=TIME_HORIZON_DAYS)
data = yf.download([stock, market], start=start_date, end=end_date, interval=INTERVAL)['Adj Close']
returns = data.pct_change().dropna()
y = returns[stock]
X = sm.add_constant(returns[market])
model = sm.OLS(y, X)
results = model.fit()
return results.params[market]
def calculate_debt_to_equity(stock: str) -> float:
"""
Calculate the debt-to-equity ratio for a given stock.
:param stock: Ticker symbol of the stock.
:return: Debt-to-equity ratio.
"""
balance_sheet = yf.Ticker(stock).balance_sheet
total_debt = balance_sheet.loc['Total Debt'].iloc[0]
total_shareholders_equity = balance_sheet.loc['Stockholders Equity'].iloc[0]
return total_debt / total_shareholders_equity
def calculate_effective_tax_rate(ticker_symbol: str) -> float:
"""
Calculate the average effective tax rate for a given stock.
:param ticker_symbol: Ticker symbol of the stock.
:return: Average effective tax rate.
"""
income_statement = yf.Ticker(ticker_symbol).income_stmt
tax_provision = income_statement.loc['Tax Provision']
pretax_income = income_statement.loc['Pretax Income']
effective_tax_rate = tax_provision / pretax_income
return (effective_tax_rate * 100).mean()
def calculate_average_cash_and_marketable_securities(stock: str) -> float:
"""
Calculate the average cash and marketable securities for a given stock.
:param stock: Ticker symbol of the stock.
:return: Average cash and marketable securities value.
"""
balance_sheet = yf.Ticker(stock).balance_sheet
return balance_sheet.loc['Cash And Cash Equivalents'].mean()
def calculate_enterprise_value(stock: str) -> float:
"""
Calculate the enterprise value for a given stock.
:param stock: Ticker symbol of the stock.
:return: Enterprise value.
"""
ticker_info = yf.Ticker(stock).info
market_value_of_equity = ticker_info['marketCap']
total_debt = ticker_info['totalDebt']
total_cash = ticker_info['totalCash']
return market_value_of_equity + total_debt - total_cash
def calculate_unlevered_beta(index: str, *companies: str) -> Tuple[float, List[str]]:
"""
Calculate the unlevered beta for a given index and list of companies.
:param index: Ticker symbol of the market index.
:param companies: List of ticker symbols for the companies.
:return: Tuple containing the average unlevered beta corrected for cash and a list of warnings.
"""
unlevered_betas_corrected_for_cash = []
warnings = []
for company in companies:
try:
beta = calculate_stock_beta(company, index)
debt_to_equity = calculate_debt_to_equity(company)
tax_rate = calculate_effective_tax_rate(company)
average_cash_and_securities = calculate_average_cash_and_marketable_securities(company)
enterprise_value = calculate_enterprise_value(company)
unlevered_beta = beta / (1 + (1 - tax_rate / 100) * debt_to_equity)
unlevered_beta_corrected_for_cash = unlevered_beta / (1 - average_cash_and_securities / enterprise_value)
unlevered_betas_corrected_for_cash.append(unlevered_beta_corrected_for_cash)
except Exception as e:
warnings.append(f"Warning: Missing values for {company}. Excluded from calculation. Error: {str(e)}")
avg_unlevered_beta_corrected_for_cash = sum(unlevered_betas_corrected_for_cash) / len(unlevered_betas_corrected_for_cash)
return avg_unlevered_beta_corrected_for_cash, warnings
# List of companies (peer group for beta estimation)
companies = [
"ZMM.F", # Maisons du Monde S.A.
"FSKRS.HE", # Fiskars Oyj Abp
"ARHS", # Arhaus, Inc.
"929.F", # Topps Tiles Plc
"0599.HK", # E. Bon Holdings Limited
"HOFT", # Hooker Furnishings Corporation
"HBB", # Hamilton Beach Brands Holding Company
"LCUT", # Lifetime Brands, Inc.
"WHR", # Whirlpool Corporation
"COOK", # Traeger, Inc.
"MIDD" # Middleby Corporation
]
# Market index used as benchmark
index = "SPY" # SPDR S&P 500 ETF Trust
# Calculate unlevered beta for the peer group
avg_unlevered_beta_corrected_for_cash, warnings = calculate_unlevered_beta(index, *companies)
# Output the unlevered beta result
print("The pure beta play is:", avg_unlevered_beta_corrected_for_cash)
if warnings:
print("\nWarnings:")
for warning in warnings:
print(warning)
CAPM Module:
from pure_beta_play import calculate_unlevered_beta
RISK_FREE_RATE = 10.80 - 2.45 # Risk-free rate as a percentage (adjusted)
EXPECTED_RISK_PREMIUM = 8.47 # Expected risk premium as a percentage
# Firm specific data
TAX_RATE = 0.32 # Effective tax rate (Lucro Presumido)
DEBT_TO_EQUITY_RATIO = 0.01 # Capital structure ratio
def calculate_expected_return(risk_free_rate: float, expected_risk_premium: float, levered_beta: float) -> float:
"""
Calculate the expected return on equity using the Capital Asset Pricing Model (CAPM).
:param risk_free_rate: Risk-free rate as a percentage.
:param expected_risk_premium: Expected risk premium as a percentage.
:param levered_beta: Levered beta of the company or portfolio.
:return: Expected return as a percentage.
"""
return risk_free_rate + levered_beta * expected_risk_premium
def calculate_levered_beta(unlevered_beta: float, tax_rate: float, debt_equity_ratio: float) -> float:
"""
Re-lever the unlevered beta using the firm's tax rate and Debt/Equity ratio.
:param unlevered_beta: Unlevered beta of the company or portfolio.
:param tax_rate: Firm's tax rate.
:param debt_equity_ratio: Firm's Debt/Equity ratio.
:return: Levered beta.
"""
return unlevered_beta * (1 + (1 - tax_rate) * debt_equity_ratio)
def main():
# List of companies (peer group for beta estimation)
companies = [
"ZMM.F", # Maisons du Monde S.A.
"FSKRS.HE", # Fiskars Oyj Abp
"ARHS", # Arhaus, Inc.
"929.F", # Topps Tiles Plc
"0599.HK", # E. Bon Holdings Limited
"HOFT", # Hooker Furnishings Corporation
"HBB", # Hamilton Beach Brands Holding Company
"LCUT", # Lifetime Brands, Inc.
"WHR", # Whirlpool Corporation
"COOK", # Traeger, Inc.
"MIDD" # Middleby Corporation
]
# Market index used as benchmark
index = "SPY" # SPDR S&P 500 ETF Trust
# Calculate unlevered beta using the pure beta play methodology
avg_unlevered_beta_corrected_for_cash, warnings = calculate_unlevered_beta(index, *companies)
# Output the pure beta result
print("The pure beta play is:", avg_unlevered_beta_corrected_for_cash)
if warnings:
print("\nWarnings:")
for warning in warnings:
print(warning)
# Re-lever the icculated beta using igni's capital structure
levered_beta = calculate_levered_beta(avg_unlevered_beta_corrected_for_cash, TAX_RATE, DEBT_TO_EQUITY_RATIO)
print("\nThe levered beta is:", levered_beta)
# Calculate the expected return using CAPM
expected_return = calculate_expected_return(RISK_FREE_RATE, EXPECTED_RISK_PREMIUM, levered_beta)
print("\nThe expected return on equity is:", expected_return, "%")
if __name__ == "__main__":
main()
Strategic Implications
This process, completed in 2023, provided us with a defensible, quantitative basis for investment decisions. By leveraging historical market data and performing statistical analyses, we extracted a reliable measure of systematic risk using a straightforward methodology based on models popularized by Aswath Damodaran. The simplicity of our approach, which employed well-established techniques such as CAPM and pure beta estimation, enabled us to bypass the limitations imposed by the absence of direct competitors. By using a carefully selected peer group, we ensured that our beta estimation was both empirically sound and reflective of industry norms.
Subsequently, the re-leveraging process adjusted the risk measure to incorporate IGNI’s unique capital structure, and the CAPM framework then delivered a clear, risk-adjusted expected return figure. This return estimate served as a critical input in our investment evaluation process, enabling us to determine with confidence whether IGNI’s project met our stringent hurdle rate criteria.
Moreover, the framework proved highly adaptable. It not only allowed for the analysis of different capital structure scenarios by adjusting inputs such as the Debt/Equity ratio and effective tax rate, but it also dynamically refined its predictions as market volatility and yield conditions evolved. This inherent flexibility, grounded in Damodaran’s models, ensured that our model remained robust and responsive to changing market environments, providing a powerful, forward-looking tool for strategic decision-making.