start_date = "1995-12-01"
end_date = "2026-01-01"
ticker = "FDVLX"Analyzing Mutual Fund Returns
Mutual Funds
One of the main uses of asset pricing models is to analyze the performance of mutual funds. A mutual fund is a type of investment vehicle that pools money from many individual investors to purchase a diversified portfolio of stocks, bonds, or other securities. The fund is managed by a professional portfolio manager who invests the money in accordance with the fund’s investment objectives and strategy.
There are many different types of mutual funds, including equity funds, bond funds, balanced funds, index funds, and specialty funds. Each type of fund has its own investment strategy and risk profile, and investors should carefully consider their investment objectives and risk tolerance before choosing a mutual fund to invest in.
In this notebook we will analyze the Fidelity Value Fund (Ticker: FDVLX). According to the fund description, the fund invests in securities of companies that possess valuable fixed assets or that the fund manager believes are undervalued in the marketplace in relation to factors such as assets, earnings, or growth potential (stocks of these companies are often called “value” stocks). Normally investing primarily in common stocks.
We are interested in two related questions. First, how are the fund’s returns generated? That is, does the fund tilt toward small or large stocks, value or growth stocks, profitable firms, conservative or aggressive investors, and past winners or losers? Second, after accounting for these systematic exposures, does the fund earn a positive alpha — evidence of genuine skill beyond passive factor exposure?
One important caveat before we begin: we are studying a fund that still exists and has a long return history available. This introduces survivorship bias — funds that performed poorly were more likely to be closed or merged, and are absent from our sample. Focusing on surviving funds therefore tends to overstate the average alpha in the mutual fund industry. The Fama-French factors, by contrast, are constructed from a CRSP universe that includes delisted firms, so the factor data does not share this problem. Keep this asymmetry in mind when interpreting the alpha estimate: part of any positive alpha we find may reflect the fact that we selected a fund precisely because it survived.
The Augmented Five-Factor Model
We will estimate the Fama-French five-factor model augmented by the momentum factor, \[ R_{i} = a_{i} + b_{i} R_{m} + s_{i} \mathit{SMB} + h_{i} \mathit{HML} + r_{i} \mathit{RMW} + c_{i} \mathit{CMA} + m_{i} \mathit{MOM} + e_{i} \] where \(R_{i} = r_{i} - r_{f}\) represents the monthly excess returns for security \(i\) over the risk-free rate, and \(R_{m} = r_{m} - r_{f}\) represents the monthly excess returns of the market.
The estimation of \(a_{i}\), which is commonly called the alpha, now controls for many important sources of variation in the investment opportunity set. If the model is correct then the alpha should be close to zero.
The different loadings on the factors also have a clear interpretation. For a mutual fund we have that: - \(b_{i} < 1\) implies that the fund has less systematic risk than the market whereas a \(b_{i} > 1\) implies that the fund takes more systematic risk than the market. - \(s_{i} > 0\) implies that the fund loads mostly on small stocks whereas \(s_{i} < 0\) implies that the fund loads on large stocks. - \(h_{i} > 0\) implies that the fund loads on value stocks whereas \(h_{i} < 0\) implies that the fund loads on growth stocks. - \(r_{i} > 0\) implies that the fund loads on stocks with robust operating profitability whereas \(r_{i} < 0\) implies that the fund loads on stocks with weak operating profitability. - \(c_{i} > 0\) implies that the fund loads on stocks with low investment whereas \(c_{i} < 0\) implies that the fund loads on stocks with high investments. - \(m_{i} > 0\) implies that the fund loads on winners whereas \(m_{i} < 0\) implies that the fund loads on losers, i.e. follows a contrarian strategy.
The CAPM can be obtained by restricting \(s_{i} = 0\), \(h_{i} = 0\), \(r_{i} = 0\), \(c_{i} = 0\), and \(m_{i} = 0\). Other nested models can be obtained in a similar way.
Python Packages
import pandas as pd
from getfactormodels import FamaFrenchFactors, CarhartFactors
import yfinance as yf
import statsmodels.formula.api as smf
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme()Getting the Factors
The following code summarizes what we did in the previous notebook. Note that getfactormodels already returns factors in decimal form, so they are directly comparable to the fund returns computed by pct_change().
ff = (
FamaFrenchFactors(model='5', frequency='m', start_date=start_date, end_date=end_date)
.load()
.to_pandas()
.rename(columns={"Mkt-RF": "RMRF"})
)
mom = (
CarhartFactors(frequency='m', start_date=start_date, end_date=end_date)
.load()
.to_pandas()
[["MOM"]]
)
ff_mom = pd.merge(left=ff, right=mom, left_index=True, right_index=True)
ff_mom.index = pd.to_datetime(ff_mom.index).to_period('M')
display(ff_mom)| RMRF | SMB | HML | RMW | CMA | RF | MOM | |
|---|---|---|---|---|---|---|---|
| date | |||||||
| 1995-12 | 0.0104 | 0.0065 | 0.0024 | -0.0144 | 0.0302 | 0.0049 | 0.0248 |
| 1996-01 | 0.0226 | -0.0264 | 0.0046 | 0.0026 | 0.0230 | 0.0043 | 0.0057 |
| 1996-02 | 0.0133 | 0.0177 | -0.0105 | 0.0016 | -0.0189 | 0.0039 | 0.0057 |
| 1996-03 | 0.0074 | 0.0156 | 0.0042 | 0.0144 | -0.0095 | 0.0039 | -0.0185 |
| 1996-04 | 0.0205 | 0.0470 | -0.0397 | -0.0020 | -0.0203 | 0.0046 | -0.0094 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 2025-08 | 0.0185 | 0.0488 | 0.0442 | -0.0068 | 0.0208 | 0.0038 | -0.0354 |
| 2025-09 | 0.0339 | -0.0218 | -0.0105 | -0.0206 | -0.0222 | 0.0033 | 0.0463 |
| 2025-10 | 0.0196 | -0.0130 | -0.0310 | -0.0521 | -0.0403 | 0.0037 | 0.0027 |
| 2025-11 | -0.0013 | 0.0147 | 0.0376 | 0.0142 | 0.0068 | 0.0030 | -0.0180 |
| 2025-12 | -0.0036 | -0.0022 | 0.0242 | 0.0040 | 0.0037 | 0.0034 | -0.0240 |
361 rows × 7 columns
Getting the Data Ready
Fund Returns
We download the adjusted close price series from Yahoo! Finance. Even though returns are computed internally within the fund by reinvesting dividends from its holdings, the fund itself makes periodic distributions (dividends and capital gains) to shareholders that cause the NAV to drop on the ex-distribution date. Using adjusted close prices ensures these distributions are captured in the return computation. Note that all subsequent conclusions rely on this adjustment being accurate — if any distributions are missing or incorrectly recorded, the alpha estimate could be either overstated or understated.
stock = yf.download(tickers=ticker, start=start_date, end=end_date, auto_adjust=True, progress=False, multi_level_index=False).loc[:, ["Close"]]
ax = stock.plot(title="Price of " + ticker)
plt.show()We compute monthly returns and use RET to denote the column containing the returns.
ret = stock.resample("ME").last().pct_change()
ret.index = ret.index.to_period("M")
ret = ret.rename(columns={"Close": "RET"})
ax = ret.plot(title="Monthly Return on " + ticker)
plt.show()Merge with the Factors
Finally, we merge the returns series that we want to analyze with the six factors. Additionally, we compute excess returns for the security.
merged = (
pd.merge(left=ret, right=ff_mom, left_index=True, right_index=True)
.assign(RETRF=lambda d: d["RET"] - d["RF"])
.drop(columns=["RET", "RF"])
.dropna()
)
display(merged)| RMRF | SMB | HML | RMW | CMA | MOM | RETRF | |
|---|---|---|---|---|---|---|---|
| Date | |||||||
| 1996-01 | 0.0226 | -0.0264 | 0.0046 | 0.0026 | 0.0230 | 0.0057 | 0.022694 |
| 1996-02 | 0.0133 | 0.0177 | -0.0105 | 0.0016 | -0.0189 | 0.0057 | -0.000565 |
| 1996-03 | 0.0074 | 0.0156 | 0.0042 | 0.0144 | -0.0095 | -0.0185 | 0.021124 |
| 1996-04 | 0.0205 | 0.0470 | -0.0397 | -0.0020 | -0.0203 | -0.0094 | 0.021721 |
| 1996-05 | 0.0236 | 0.0323 | -0.0090 | 0.0003 | -0.0033 | 0.0162 | 0.009367 |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 2025-08 | 0.0185 | 0.0488 | 0.0442 | -0.0068 | 0.0208 | -0.0354 | 0.046998 |
| 2025-09 | 0.0339 | -0.0218 | -0.0105 | -0.0206 | -0.0222 | 0.0463 | -0.006753 |
| 2025-10 | 0.0196 | -0.0130 | -0.0310 | -0.0521 | -0.0403 | 0.0027 | -0.012016 |
| 2025-11 | -0.0013 | 0.0147 | 0.0376 | 0.0142 | 0.0068 | -0.0180 | 0.035435 |
| 2025-12 | -0.0036 | -0.0022 | 0.0242 | 0.0040 | 0.0037 | -0.0240 | 0.109415 |
360 rows × 7 columns
Estimating the Model
CAPM
We start by estimating a CAPM regression, \[ R_{i} = a_{i} + b_{i} R_{m} + e_{i} \]
results = smf.ols("RETRF ~ RMRF", data=merged).fit()
print(results.summary(slim=True)) OLS Regression Results
==============================================================================
Dep. Variable: RETRF R-squared: 0.634
Model: OLS Adj. R-squared: 0.633
No. Observations: 360 F-statistic: 620.4
Covariance Type: nonrobust Prob (F-statistic): 3.68e-80
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
Intercept 0.0046 0.002 2.359 0.019 0.001 0.008
RMRF 1.0473 0.042 24.908 0.000 0.965 1.130
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
The results indicate that the CAPM alpha is positive (a = 0.0046) and statistically significant at the 5% level (p-value = 0.019). This means that during the period the fund outperformed the market on a simple risk-adjusted basis, earning about \(0.46 \times 12 = 5.5\%\) per year above what market exposure alone would predict.
The Augmented Five-Factor Model
results = smf.ols("RETRF ~ RMRF + SMB + HML + RMW + CMA + MOM", data=merged).fit()
print(results.summary(slim=True)) OLS Regression Results
==============================================================================
Dep. Variable: RETRF R-squared: 0.763
Model: OLS Adj. R-squared: 0.759
No. Observations: 360 F-statistic: 189.5
Covariance Type: nonrobust Prob (F-statistic): 3.87e-107
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
Intercept 0.0029 0.002 1.790 0.074 -0.000 0.006
RMRF 1.0656 0.040 26.689 0.000 0.987 1.144
SMB 0.3111 0.057 5.466 0.000 0.199 0.423
HML 0.3939 0.067 5.864 0.000 0.262 0.526
RMW 0.2904 0.072 4.032 0.000 0.149 0.432
CMA 0.0874 0.096 0.915 0.361 -0.100 0.275
MOM -0.1040 0.035 -3.005 0.003 -0.172 -0.036
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
After controlling for the other factors, the alpha shrinks to 0.0029 (p-value = 0.075). While it is no longer statistically significant at the 5% level, the estimate remains positive and economically meaningful, as we discuss below.
We learn the following from the coefficient estimates.
| Factor | Coefficient | Significance | Interpretation |
|---|---|---|---|
| SMB | 0.3111 | 1% | The fund invested in small stocks |
| HML | 0.3940 | 1% | The fund invested in value stocks |
| RMW | 0.2904 | 1% | The fund invested in stocks with strong profitability |
| CMA | 0.0873 | Not significant | The fund invested in both high and low investment stocks |
| MOM | -0.1040 | 1% | The fund followed a contrarian strategy, tilting toward past losers |
Therefore, the results from the regression agree with the fund description. The fund loads positively and significantly on HML, consistent with a value strategy. The positive SMB loading suggests the fund tilts toward smaller stocks within the value universe. The negative momentum loading is typical for value strategies, since value stocks tend to be recent underperformers. Overall, the fund appears to have performed well. The alpha of 0.29% per month (\(0.29 \times 12 \approx 3.5\%\) per year) remains positive after controlling for all six factors. Although it is not statistically significant at conventional levels (p-value = 0.075), its economic magnitude is meaningful. The evidence is consistent with a fund that delivers on its mandate and may generate modest value beyond passive factor exposure, even if we cannot rule out sampling uncertainty as an explanation.
Practice Problems
Problem 1 In this problem we will analyze the Federated Hermes MDT Mid Cap Growth Fund Institutional Shares (Ticker: FGSIX) using data from December 1999 until January 2026.
- Download the Fama-French and momentum factors for the period.
- Download the fund net asset values (NAV) and compute monthly fund returns.
- Merge both datasets in a single dataframe.
- Run a regression of monthly fund returns on the Fama-French and momentum factors.
- Has the fund outperformed or underperformed the augmented Fama-French factor model during the period?
- Do the factor loadings on value and size correspond to the fund description?
- Does the fund load a lot on momentum?
Solution
The following assumes that you already have loaded the relevant libraries. We start by defining the start and end dates, and the ticker of the fund.
start_date = "1999-12-01"
end_date = "2026-01-01"
ticker = "FGSIX"We can now proceed to get the Fama-French factors.
ff = ( FamaFrenchFactors(model='5', frequency='m', start_date=start_date, end_date=end_date) .load() .to_pandas() .rename(columns={"Mkt-RF": "RMRF"}) ) mom = ( CarhartFactors(frequency='m', start_date=start_date, end_date=end_date) .load() .to_pandas() [["MOM"]] ) ff_mom = pd.merge(left=ff, right=mom, left_index=True, right_index=True) ff_mom.index = pd.to_datetime(ff_mom.index).to_period('M')We can now get the data for the fund and merge it with the Fama-French factors.
stock = yf.download(tickers=ticker, start=start_date, end=end_date, auto_adjust=True, progress=False, multi_level_index=False).loc[:, ["Close"]] ret = stock.resample("ME").last().pct_change() ret.index = ret.index.to_period("M") ret = ret.rename(columns={"Close": "RET"})merged = ( pd.merge(left=ret, right=ff_mom, left_index=True, right_index=True) .assign(RETRF=lambda d: d["RET"] - d["RF"]) .drop(columns=["RET", "RF"]) .dropna() )We can now run the regression of fund returns on the six factors.
results = smf.ols("RETRF ~ RMRF + SMB + HML + RMW + CMA + MOM", data=merged).fit() print(results.summary(slim=True))OLS Regression Results ============================================================================== Dep. Variable: RETRF R-squared: 0.533 Model: OLS Adj. R-squared: 0.518 No. Observations: 191 F-statistic: 35.00 Covariance Type: nonrobust Prob (F-statistic): 4.77e-28 ============================================================================== coef std err t P>|t| [0.025 0.975] ------------------------------------------------------------------------------ Intercept 0.0074 0.003 2.154 0.033 0.001 0.014 RMRF 1.0679 0.084 12.688 0.000 0.902 1.234 SMB 0.2688 0.151 1.785 0.076 -0.028 0.566 HML -0.2696 0.145 -1.858 0.065 -0.556 0.017 RMW 0.1114 0.184 0.606 0.545 -0.251 0.474 CMA 0.2214 0.212 1.042 0.299 -0.198 0.641 MOM 0.0389 0.107 0.364 0.716 -0.172 0.250 ============================================================================== Notes: [1] Standard Errors assume that the covariance matrix of the errors is correctly specified.The results indicate that the alpha of the fund is positive (0.74% per month) and statistically significant at the 5% level (p-value = 0.033). Therefore, the fund has outperformed the augmented Fama-French model during the period.
None of the factor loadings are individually significant at the 5% level. Even so, the SMB coefficient (0.2685, p = 0.076) suggests a marginal tilt toward smaller stocks, and the HML coefficient (-0.2694, p = 0.065) is negative, indicating a tilt toward growth stocks. Both signs are consistent with the fund’s description as a mid-cap growth fund.
The momentum loading (0.0389) is small and statistically insignificant (p = 0.717), meaning the fund does not display a significant momentum tilt.