Skip to content

Advanced Insights into Style and Factor Exposures for Portfolio Optimization

Analysis of Factor Investing

Factor investing focuses on identifying variables that significantly influence asset returns. These factors represent common traits in the returns that originate from external sources to the individual asset. Typically, long-term exposure to factor risk offers a reward, known as the risk premium.

Categories of Factors

  1. Macro Factors: These include broad economic elements like industrial growth and inflation.
  2. Statistical Factors: These are derived from data analysis and may or may not have clear identification.
  3. Style (Intrinsic) Factors: Examples include value-growth, momentum, and low-volatility.

For instance, oil prices are often seen as a critical macro factor in determining equity returns, as they can significantly impact stock performance. This external influence categorizes it as a macro factor.

Factor Models

Factor models break down asset returns into several components:

r=β1f1+β2f2+...+βnfn+α+ε,

Here, {βi}i=1,,n are coefficients, and {fi}i=1,,n are factor premia, representing returns obtained in exchange for factor exposure.

Essentially, factor models deconstruct an asset's return into a combination of returns from various other assets.

The Capital Asset Pricing Model (CAPM)

CAPM is an application of factor models used for estimating the appropriate return of an asset for portfolio decisions.

The model considers the asset's sensitivity to market-wide, non-diversifiable risks, represented by beta β, the expected market return, and the expected return of a risk-free asset.

For individual security pricing, CAPM establishes the Security Market Line (SML), calculating the reward-to-risk ratio in comparison to the overall market:

SML:E[ri]=βi(E[rm]rf)+rf,

This equation suggests that an asset's excess return depends on the market's excess return, scaled by the asset's sensitivity to the market β. The sensitivity factor, β, is determined as:

E[ri]rf=βi(E[rm]rf),βi:=Cov(ri,rm)Var(rm),

A higher β indicates strong correlation with market movements. Conversely, a low β suggests minimal correlation with the market.

The expected market return is generally computed as the arithmetic mean of historical market returns. CAPM, being a one-factor model, ties an asset's excess return solely to the market's excess return.

Price-to-Book (P/B) Ratio

The book value in accounting is an asset's value on the balance sheet, adjusted for depreciation, amortization, or impairment. The P/B ratio compares a company's market price to its book value, calculated either by dividing the market capitalization by the total book value or the current share price by the book value per share.

Also known as the market-to-book ratio, it's a critical metric in financial analysis.

Value Stocks vs. Growth Stocks

The inverse of the P/B ratio, the book-to-price (B/P) or book-to-market ratio, categorizes stocks into 'Value' and 'Growth' stocks.

  • Value Stocks: High book-to-market ratios indicate undervaluation by the market.
  • Growth Stocks: Low book-to-market ratios suggest overvaluation, often due to expectations of continuing above-average growth.

Growth stocks are typically linked with robust, successful companies with expected ongoing growth. They are often classified as such if their Return on Equity (ROE) is 15% or higher.

Fama-French Model

The Fama-French model is a three-factor model enhancing the CAPM (one-factor model). The three factors are:

  • The market risk (i.e., as in the CAPM),
  • The outperformance of small versus big companies,
  • The outperformance of high book/market versus low book/market companies.

Fama and French took the entire universe of stocks and categorized them into ten buckets (deciles), sorting them in two ways:

  1. By size (market capitalization), comparing the performance of the bottom 10% of companies to the top 10% in terms of size.
  2. By book-to-market ratios (B/P ratio), comparing the bottom 10% companies (Growth Stocks) to the top 10% (Value Stocks).

They observed that the stock classes consistently outperforming the market were:

  • Small caps (bottom decile with respect to size) and

  • Value Stocks (top decile with respect to B/P ratios).

Consequently, they introduced the size factor and the value factor to the market factor of the CAPM, enhancing the model in 1993:

E[ri]rf=βi,MKTE[rmrf]+βi,SMBE[SMB]+βi,HMSE[HMS]

where:

  • βi,MKT is the CAPM's market-related β,
  • SMB stands for Small (size) Minus Big (size) stocks,
  • HML represents High (B/P ratio) Minus Low (B/P ratio) stocks.

The Carhart four-factor model further expands on the Fama-French model by adding the Momentum factor. Momentum in a stock is its tendency to continue its current price trend. This factor is computed by subtracting the equal weighted average of the lowest performing firms from that of the highest performing firms, lagged one month.

Factor Benchmark

Factor models can be reinterpreted as benchmarks. For instance, the single CAPM model can be reformulated as follows:

E[rirf]=βE[rmrf]+αE[ri]=βE[rm](β1)E[rf]+α.

This formulation implies that with $1 dollar, one can borrow (β1) dollars and invest β dollars in the market. For example, with β=1.3, it involves borrowing 0.3 dollars and investing 1.3 dollars in the market, yielding a return of 1.3E[rm]0.3E[rf].

If an asset in the market yields a return exceeding this calculation, it indicates the manager's value addition to that asset. Using this model, regression analysis determines the β value, and the search for α reveals the asset's added value. An asset that does not yield any alpha does not add value, as the same return could be achieved without it. A positive alpha indicates value addition, while a negative alpha suggests the asset is detracting value.

Therefore, the factor benchmark is conceptualized as a short position of (β1) dollars in cash (such as T-bills) and a leveraged position of β dollars in the market portfolio.

Style Analysis

Returns-based style analysis, introduced by W. Sharpe (also a contributor to CAPM), can be viewed as a constrained form of a factor model. It has been applied to evaluating the performance of active managers.

Sharpe's approach involved using a factor-model-like structure with explanatory variables instead of actual factors:

rtm=i=1Nβirti+α+ε,

where rtm represents the historical returns of a manager (or an asset), {rti} denotes a set of returns from various indices (serving as explanatory variables), and α is assumed to be the manager's added value. In this analysis, the β coefficients represent exposures to specific market returns and are constrained to sum to 1. This constraint effectively turns the coefficients into portfolio weights within the explanatory variables:

i=1Nβi=1.

Factor Analysis of Warren Buffet's Berkshire Hathaway

To analyze Berkshire Hathaway's performance, the dataset containing daily returns is utilized. These daily returns are compounded into monthly returns for the period from January 1990 to December 2018.

python
# Load monthly returns of Berkshire Hathaway
brka_rets = pok.get_brka_rets(monthly=True)
# Display the first few rows of the data
brka_rets.head()

Next, the factors constituting the Fama-French model are loaded. These factors serve as explanatory variables.

python
# Load the Fama-French factors
fff = pok.get_fff_returns()
# Display the first few rows of the factors
fff.head()

The columns represent the market return minus the risk-free rate, the Small Minus Big (Size) factor, the High Minus Low (Value) factor, and the pure risk-free rate, likely representing T-Bill returns.

A common analysis period, from January 1990 to May 2015, is selected. The first step involves factor analysis using the CAPM model to decompose Berkshire Hathaway's observed return into market-driven and other components.

Rbrka,tRf,t=β(Rmkt,tRf,t)+α+εt.
python
# Calculate the excess return of Berkshire Hathaway over the risk-free rate
brka_excess_rets = brka_rets["1990":"2015-05"] - fff.loc["1990":"2015-05"][["RF"]].values

# Store the excess return of the market over the risk-free rate
mkt_excess_rets  = fff.loc["1990":"2015-05"][["Mkt-RF"]]

# Copy market excess returns and add a constant for regression
factors = mkt_excess_rets.copy()
factors["alpha"] = 1

# Perform Ordinary Least Squares (OLS) regression
lm = sm.OLS(brka_excess_rets, factors).fit()
# Print summary of regression results
print(lm.summary())

# Display the regression coefficients
print("The coefficients of the regression are:")
print(lm.params)

The regression results indicate a β of approximately 0.55 and an α of around 0.006. This implies the CAPM benchmark is composed of $0.46 in Treasury bills and $0.55 in the market for every dollar in the Berkshire Hathaway portfolio. The company's additional monthly return is about 0.6%, albeit with moderate statistical significance.

The analysis then extends to the complete Fama-French model, incorporating additional factors:

python
# Copy market excess returns and add Size and Value factors
factors["Size"] = fff.loc["1990":"2015-05"][["SMB"]]
factors["Value"] = fff.loc["1990":"2015-05"][["HML"]]
factors["alpha"] = 1

# Perform OLS regression with the extended Fama-French model
lm_ff = sm.OLS(brka_excess_rets, factors).fit()
# Print summary of regression results
print(lm_ff.summary())

With the Fama-French model, α decreases from 0.63% to approximately 0.54% per month. The market loading increases from about 0.55 to 0.68, indicating the additional factors' impact. The positive loading on Value (beta coefficient of HML) suggests Berkshire Hathaway's significant inclination towards Value stocks. Conversely, the negative tilt on Size (beta coefficient of SMB) implies a preference for large companies over small ones, characterizing Berkshire Hathaway as a Large Value investor.

This analysis translates each dollar invested in Berkshire Hathaway to approximately:

  • $0.68 in the market and $0.32 in T-Bills,
  • $0.39 in Value stocks and short $0.38 in Growth stocks,
  • Short $0.48 in SmallCap stocks and long $0.50 in LargeCap stocks.

Despite these allocations, there's still an underperformance relative to Berkshire Hathaway by about 0.54% (54 basis points) per month.

The pok.linear_regression(dep_var, expl_vars, alpha=True) method in the toolkit can be used for linear regression.

Sharpe Style Analysis

Sharpe style analysis applies constraints on β coefficients, which must be positive and sum to 1. This approach interprets a manager's observed return coefficients as portfolio weights in a collection of basic assets, collectively emulating the return series. This analysis can unveil shifts in a manager's investment style and offer insights into their return-generating strategies.

To conduct Sharpe style analysis, a quadratic optimizer is used to find the weights that minimize the squared difference between the observed series and the returns of a benchmark portfolio containing the explanatory assets in corresponding weights. The objective is to minimize the tracking error between the two return series:

{minimizeE(βi)i=1Nβi=1,

The tracking error is defined as:

E(βi):=t(rtmi=1Nβirti)2.

Initially, industry returns are utilized:

python
# Retrieving industry returns from 2000 onwards
ind_rets = pok.get_ind_returns()["2000":]
# Displaying the first few rows of industry returns
print(ind_rets.head())

An artificial manager return series is then constructed, assuming investments in specific industry proportions:

python
# Creating an artificial manager return series
mgr_rets = 0.3*ind_rets["Beer"] + 0.5*ind_rets["Smoke"] + 0.2*np.random.normal(loc=0.0, scale=0.15/np.sqrt(12), size=ind_rets.shape[0])
# Displaying the first few rows of the manager returns
print(mgr_rets.head())

Without prior knowledge of this manager's investments, the goal is to deduce the investment pattern from observed returns using Sharpe style analysis:

python
# Running Sharpe style analysis to determine investment pattern
weights = pok.style_analysis(mgr_rets, ind_rets)
# Visualizing the weights in a bar chart
weights.sort_values(ascending=False).plot.bar(grid=True, figsize=(11,4))
plt.show()

The results approximate the manager's actual investments, although some industries might appear falsely due to the synthetic nature of the return series.

A linear regression approach could also be applied, but it would lack the constraints of style analysis:

python
# Performing linear regression to compare with style analysis
betas = pok.linear_regression(mgr_rets, ind_rets).params
# Visualizing the beta coefficients in a bar chart
betas.sort_values(ascending=False).plot.bar(grid=True, figsize=(11,4))
plt.show()

Linear regression might yield negative coefficients, which can be challenging to interpret, especially with real-life data.

Warning: Potential Misuse of Style Analysis

Style analysis is most effective when the explanatory indices accurately reflect the underlying strategy. It provides valuable insights when using broad market indices or ETFs. However, it will always produce a portfolio, no matter how unreasonable, and the reliability of the results can sometimes be questionable.

To analyze major industries that Buffet invested in since 2000:

python
# Retrieving Berkshire Hathaway's returns from 2000 onwards
brka_rets = pok.get_brka_rets(monthly=True)["2000":]
# Applying style analysis to Berkshire Hathaway's returns
weights = pok.style_analysis(brka_rets, ind_rets["2000":])
# Visualizing the weights in a bar chart
weights.sort_values(ascending=False).plot.bar(grid=True, figsize=(11,4))
plt.show()

If only returns from 2009 are considered:

python
# Applying style analysis to Berkshire Hathaway's returns from 2009
weights = pok.style_analysis(brka_rets["2009":], ind_rets["2009":])
# Visualizing the weights in a bar chart
weights.sort_values(ascending=False).plot.bar(grid=True, figsize=(11,4))
plt.show()

While the analysis can be informative, caution should be exercised in interpreting the results, especially when the specification might not be accurate.

Style Drift: Time Varying Exposures using Style Analysis

Sharpe style analysis can be used to detect style drift by analyzing style exposures over a rolling window of 1 to 5 years. This can reveal changes in a manager's investment approach.

Comparing Equally Weighted (EW) and Cap-Weighted (CW) Portfolios

Loading the Equally-Weighted (EW) and Value-Weighted (VW) versions of industry portfolio returns:

python
# Loading EW and VW industry portfolio returns
ind_rets_cw = pok.get_ind_file(filetype="rets", nind=30, ew=False)
ind_rets_ew = pok.get_ind_file(filetype="rets", nind=30, ew=True)

It's important to note that "Value-Weighted" here refers to market capitalization weighting, not weighting by value stocks.

Comparing Sharpe ratios of these portfolios:

python
# Setting the risk-free rate for Sharpe ratio calculation
risk_free_rate = 0.03
# Calculating Sharpe ratios for both CW and EW portfolios
sr = pd.DataFrame({
    "CW": pok.sharpe_ratio(ind_rets_cw["1945":], risk_free_rate, 12), 
    "EW": pok.sharpe_ratio(ind_rets_ew["1945":], risk_free_rate, 12)
})
# Calculating the percentage of times EW Sharpe ratio outperforms CW
pew_er = (sr["EW"] > sr["CW"]).sum()/sr.shape[0] * 100
# Visualizing Sharpe ratios in a bar chart
ax = sr.plot.bar(grid=True, figsize=(12,5))
ax.set_title("EW-SR higher than VW-SR: {:.1f}%".format(pew_er))
plt.show()

Improving EW Schemes with CapWeight Tethering

Equally weighted portfolios often undergo modifications to improve tradeability or reduce tracking error relative to Cap-Weighted indices. Modifications may include limiting exposure to microcap stocks and ensuring that no stock's weight exceeds a certain multiple of its Cap-Weighted proportion.

python
nind = 49

ind_rets = pok.get_ind_file(filetype="rets", nind=nind)["1974":]
ind_mcap = pok.get_ind_market_caps(nind=nind, weights=True)["1974":]
ind_mcap.head()

# Setting the window for the backtest
window = 52
# Backtesting various weighting schemes
ew_rets    = pok.backtest_weight_scheme(ind_rets, window=window, weight_scheme=pok.weight_ew)
ew_tr_rest = pok.backtest_weight_scheme(ind_rets, window=window, weight_scheme=pok.weight_ew, cap_ws=ind_mcap, max_cw_mult=5, microcap_thr=0.005)
cw_rets    = pok.backtest_weight_scheme(ind_rets, window=window, weight_scheme=pok.weight_cw, cap_ws=ind_mcap)
# Combining the returns for comparison
bt_rets    = pd.DataFrame({"EW": ew_rets, "EW-Tethered": ew_tr_rest, "CW": cw_rets})
# Calculating cumulative growth of each strategy
bt_growth = (1 + bt_rets).cumprod()
# Plotting the growth of strategies
bt_growth.plot(grid=True, figsize=(11,6), title="49 Industries - CapWeighted vs Equally Weighted vs Tethered EW")
plt.show()
# Displaying summary statistics
print(pok.summary_stats( bt_rets.dropna() ))

After earning certification from EDHEC Business School, I translated complex financial theories into practical Python modules, openly shared under the MIT License.