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.

start_date = "1995-12-01"
end_date = "2026-01-01"
ticker = "FDVLX"

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.

  1. Download the Fama-French and momentum factors for the period.
  2. Download the fund net asset values (NAV) and compute monthly fund returns.
  3. Merge both datasets in a single dataframe.
  4. Run a regression of monthly fund returns on the Fama-French and momentum factors.
    1. Has the fund outperformed or underperformed the augmented Fama-French factor model during the period?
    2. Do the factor loadings on value and size correspond to the fund description?
    3. 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"
  1. 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')
  2. 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"})
  3. 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()
    )
  4. 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.
  5. 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.

  6. 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.

  7. The momentum loading (0.0389) is small and statistically insignificant (p = 0.717), meaning the fund does not display a significant momentum tilt.