1 Question 1: Single-Factor (Market) Model [25 points]

Model: \[R_i - R_f = \alpha + \beta(R_m - R_f) + \varepsilon\]

Given data:

Term Estimate Std. Error
Intercept (α) 0.0017 0.0020
Market premium (β) 0.98 0.17
  • \(R^2 = 0.50\), \(n = 96\) months, \(E[R_m - R_f] = 0.70\%\), critical \(|t| \approx 1.98\)

1.1 Part (a): t-statistic for β and test H₀: β = 0

Formula: \[t_{\hat{\beta}} = \frac{\hat{\beta} - 0}{SE(\hat{\beta})}\]

beta_hat  <- 0.98
se_beta   <- 0.17
t_crit    <- 1.98

t_beta <- beta_hat / se_beta
t_beta <- round(t_beta, 4)
cat("t-statistic for beta:", t_beta, "\n")
## t-statistic for beta: 5.7647
cat("Critical |t|:", t_crit, "\n")
## Critical |t|: 1.98
cat("|t_beta| > t_crit?", abs(t_beta) > t_crit, "-> Reject H0: beta = 0\n")
## |t_beta| > t_crit? TRUE -> Reject H0: beta = 0

\[t_{\hat{\beta}} = \frac{0.98}{0.17} = 5.7647\]

Decision: \(|5.7647| > 1.98\)Reject H₀ at the 5% level.

Economic interpretation: \(\hat{\beta} = 0.98\) means for every 1% increase in the market excess return, the fund’s excess return increases by approximately 0.98% on average. The fund moves nearly one-for-one with the market, indicating market-like systematic risk.


1.2 Part (b): Test H₀: β = 1

Formula: \[t = \frac{\hat{\beta} - 1}{SE(\hat{\beta})}\]

t_beta1 <- (beta_hat - 1) / se_beta
t_beta1 <- round(t_beta1, 4)
cat("t-statistic for H0: beta = 1:", t_beta1, "\n")
## t-statistic for H0: beta = 1: -0.1176
cat("|t| =", abs(t_beta1), "> 1.98?", abs(t_beta1) > t_crit, "-> Fail to Reject H0\n")
## |t| = 0.1176 > 1.98? FALSE -> Fail to Reject H0

\[t = \frac{0.98 - 1}{0.17} = \frac{-0.02}{0.17} = -0.1176\]

Decision: \(|-0.1176| < 1.98\)Fail to reject H₀: β = 1 at the 5% level.

Interpretation: The fund’s systematic risk is statistically indistinguishable from the market. It neither amplifies nor dampens market swings in a statistically meaningful way.


1.3 Part (c): t-statistic for α (Jensen’s Alpha)

Formula: \[t_{\hat{\alpha}} = \frac{\hat{\alpha}}{SE(\hat{\alpha})}\]

alpha_hat <- 0.0017
se_alpha  <- 0.0020

t_alpha <- alpha_hat / se_alpha
t_alpha <- round(t_alpha, 4)
cat("t-statistic for alpha:", t_alpha, "\n")
## t-statistic for alpha: 0.85
cat("|t_alpha| > 1.98?", abs(t_alpha) > t_crit, "-> Fail to Reject H0: alpha = 0\n")
## |t_alpha| > 1.98? FALSE -> Fail to Reject H0: alpha = 0

\[t_{\hat{\alpha}} = \frac{0.0017}{0.0020} = 0.85\]

Decision: \(|0.85| < 1.98\)Fail to reject H₀: α = 0.

Conclusion: The data do not statistically justify the marketing team’s claim of “positive risk-adjusted performance.” The estimated alpha of 0.0017 is economically small and statistically indistinguishable from zero — the apparent outperformance is consistent with sampling noise.


1.4 Part (d): Interpret R² = 0.50

R2 <- 0.50
systematic   <- R2 * 100
diversifiable <- (1 - R2) * 100
cat("Systematic (market) variation:", systematic, "%\n")
## Systematic (market) variation: 50 %
cat("Diversifiable (idiosyncratic) variation:", diversifiable, "%\n")
## Diversifiable (idiosyncratic) variation: 50 %

\[R^2 = 0.50\]

  • Systematic risk: \(R^2 = 50\%\) of the fund’s total return variation is explained by market (systematic) risk.
  • Diversifiable risk: The remaining \(1 - R^2 = 50\%\) is idiosyncratic — specific to this fund and not rewarded by the market in equilibrium.

1.5 Part (e): CAPM-Implied Expected Monthly Excess Return

Formula: \[E[R_i - R_f] = \hat{\beta} \times E[R_m - R_f]\]

mkt_premium <- 0.70  # percent

capm_return <- round(beta_hat * mkt_premium, 4)
cat("CAPM-implied expected monthly excess return:", capm_return, "%\n")
## CAPM-implied expected monthly excess return: 0.686 %

\[E[R_i - R_f] = 0.98 \times 0.70\% = 0.686\%\]


2 Question 2: Fama–French Three-Factor Model [25 points]

Model: \[R_i - R_f = \alpha + b \cdot MKT + s \cdot SMB + h \cdot HML + \varepsilon\]

Given data (\(n = 144\)):

Term Estimate Std. Error
Intercept (α) 0.0029 0.0018
MKT (b) 0.97 0.08
SMB (s) 0.75 0.11
HML (h) −0.13 0.13
  • \(R^2 = 0.92\), Adjusted \(R^2 = 0.918\), critical \(|t| \approx 1.98\)

2.1 Part (f): t-statistics for All Four Coefficients

Formula: \[t_j = \frac{\hat{\theta}_j}{SE(\hat{\theta}_j)}\]

estimates <- c(alpha = 0.0029, MKT = 0.97, SMB = 0.75, HML = -0.13)
std_errors <- c(alpha = 0.0018, MKT = 0.08, SMB = 0.11, HML = 0.13)
t_crit <- 1.98

t_stats <- round(estimates / std_errors, 4)
significant <- abs(t_stats) > t_crit

results_q2f <- data.frame(
  Term      = names(estimates),
  Estimate  = estimates,
  Std_Error = std_errors,
  t_stat    = t_stats,
  Significant_5pct = significant
)
rownames(results_q2f) <- NULL
print(results_q2f)
##    Term Estimate Std_Error  t_stat Significant_5pct
## 1 alpha   0.0029    0.0018  1.6111            FALSE
## 2   MKT   0.9700    0.0800 12.1250             TRUE
## 3   SMB   0.7500    0.1100  6.8182             TRUE
## 4   HML  -0.1300    0.1300 -1.0000            FALSE

\[t_\alpha = \frac{0.0029}{0.0018} = 1.6111, \quad t_b = \frac{0.97}{0.08} = 12.125, \quad t_s = \frac{0.75}{0.11} = 6.8182, \quad t_h = \frac{-0.13}{0.13} = -1\]

Significant at 5%: MKT (\(|t| = 12.125\)) and SMB (\(|t| = 6.8182\)).
Not significant: α (\(|t| = 1.6111\)) and HML (\(|t| = 1\)).


2.2 Part (g): Investment Style Classification

s_loading <- 0.75   # SMB
h_loading <- -0.13  # HML

cat("SMB loading:", s_loading, "-> Positive and significant -> Small-cap tilt\n")
## SMB loading: 0.75 -> Positive and significant -> Small-cap tilt
cat("HML loading:", h_loading, "-> Negative (not significant) -> Growth tilt\n")
## HML loading: -0.13 -> Negative (not significant) -> Growth tilt
cat("Style classification: Small-cap Growth\n")
## Style classification: Small-cap Growth
  • SMB loading = +0.75 (large, positive, significant): The fund has a strong small-cap tilt — it behaves like a portfolio of small-capitalization stocks.
  • HML loading = −0.13 (small, negative, not significant): A mild growth tilt (negative HML corresponds to growth stocks), though the effect is statistically indistinguishable from zero.

Overall style classification: Small-Cap Growth Fund.


2.3 Part (h): Intercept Interpretation

alpha_ff3 <- 0.0029
se_alpha_ff3 <- 0.0018
t_alpha_ff3 <- round(alpha_ff3 / se_alpha_ff3, 4)
cat("FF3 alpha:", alpha_ff3, "\n")
## FF3 alpha: 0.0029
cat("t-statistic:", t_alpha_ff3, "\n")
## t-statistic: 1.6111
cat("Significant at 5%?", abs(t_alpha_ff3) > t_crit, "\n")
## Significant at 5%? FALSE

\[t_\alpha = \frac{0.0029}{0.0018} = 1.6111\]

Interpretation: The intercept α = 0.0029 (monthly) represents the average return not explained by the three factor exposures. However, \(|t| = 1.6111 < 1.98\)not statistically significant at 5%. There is no statistically credible evidence that the manager adds value beyond the compensation for MKT, SMB, and HML factor loadings. The apparent 0.29%/month outperformance is plausibly attributable to sampling variation.


2.4 Part (i): R² Rise from 0.75 (CAPM) to 0.92 (FF3)

R2_capm <- 0.75
R2_ff3  <- 0.92
adjR2_ff3 <- 0.918

delta_R2 <- round(R2_ff3 - R2_capm, 4)
cat("CAPM R²:", R2_capm, "\n")
## CAPM R²: 0.75
cat("FF3 R²:", R2_ff3, "\n")
## FF3 R²: 0.92
cat("Increase:", delta_R2, "\n")
## Increase: 0.17
cat("FF3 Adjusted R²:", adjR2_ff3, "\n")
## FF3 Adjusted R²: 0.918

\[\Delta R^2 = 0.92 - 0.75 = 0.17\]

Interpretation of the rise: The additional 17 percentage points of explained variation captured by SMB and HML reveals that a substantial portion of the fund’s returns previously attributed to idiosyncratic noise are actually driven by its systematic small-cap and growth tilts.

Why Adjusted R² is the correct comparison metric: Adding any predictor to a regression mechanically increases raw \(R^2\), even if the predictor has no true explanatory power. Adjusted \(R^2\) penalizes each additional predictor through a degrees-of-freedom correction:

\[\bar{R}^2 = 1 - \frac{(1 - R^2)(n - 1)}{n - k - 1}\]

A rise in adjusted \(R^2\) from 0.75 to 0.918 is genuine evidence that the two additional factors improve fit beyond what random chance would produce, making it the appropriate metric for comparing models with different numbers of predictors.


3 Question 3: Logistic Regression for Market Direction [25 points]

Model: \[\text{logit}\, P(\text{Up}) = \beta_0 + \beta_1 r_{t-1} + \beta_2 \Delta VIX_{t-1}\]

Coefficients: \(\beta_0 = -0.02\), \(\beta_1 = 5.4\), \(\beta_2 = -0.38\)
Today’s inputs: \(r_{t-1} = 0.010\), \(\Delta VIX = 1.5\)


3.1 Part (j): Predicted Probability and Class

Formula: \[\text{logit} = \beta_0 + \beta_1 r_{t-1} + \beta_2 \Delta VIX\] \[P(\text{Up}) = \frac{1}{1 + e^{-\text{logit}}}\]

b0 <- -0.02
b1 <-  5.4
b2 <- -0.38
r_lag   <- 0.010
dvix    <- 1.5

logit_val <- b0 + b1 * r_lag + b2 * dvix
logit_val <- round(logit_val, 4)

prob_up <- round(1 / (1 + exp(-logit_val)), 4)
pred_class <- ifelse(prob_up >= 0.5, "Up", "Down")

cat("logit =", b0, "+", b1, "*", r_lag, "+", b2, "*", dvix, "=", logit_val, "\n")
## logit = -0.02 + 5.4 * 0.01 + -0.38 * 1.5 = -0.536
cat("P(Up) = 1 / (1 + exp(-", logit_val, ")) =", prob_up, "\n")
## P(Up) = 1 / (1 + exp(- -0.536 )) = 0.3691
cat("Predicted class (threshold = 0.5):", pred_class, "\n")
## Predicted class (threshold = 0.5): Down

\[\text{logit} = -0.02 + 5.4(0.010) + (-0.38)(1.5) = -0.02 + 0.054 - 0.57 = -0.536\]

\[P(\text{Up}) = \frac{1}{1 + e^{-(-0.536)}} = \frac{1}{1 + e^{0.536}} = 0.3691\]

Predicted class: Since \(0.3691 < 0.5\)“Down”


3.2 Part (k): Economic Interpretation of β₁ and β₂

cat("beta_1 =", b1, "(positive): Momentum effect\n")
## beta_1 = 5.4 (positive): Momentum effect
cat("  A positive lagged return increases P(Up) -> markets tend to continue upward\n\n")
##   A positive lagged return increases P(Up) -> markets tend to continue upward
cat("beta_2 =", b2, "(negative): Risk-off / fear effect\n")
## beta_2 = -0.38 (negative): Risk-off / fear effect
cat("  Rising VIX (fear) decreases P(Up) -> higher uncertainty predicts down days\n")
##   Rising VIX (fear) decreases P(Up) -> higher uncertainty predicts down days
  • \(\beta_1 = +5.4\) (positive): A positive lagged return increases the probability of an “Up” day. This captures price momentum — markets that went up yesterday tend to continue upward in the short term.

  • \(\beta_2 = -0.38\) (negative): An increase in VIX (rising market fear/uncertainty) decreases the probability of an “Up” day. This captures risk-off sentiment — spikes in implied volatility are associated with negative subsequent market returns.


3.3 Part (l): Confusion Matrix Metrics

Confusion matrix (200-day hold-out, threshold = 0.5):

Actual Up Actual Down Total
Predicted Up 67 44 111
Predicted Down 33 56 89
Total 100 100 200
TP <- 67; FP <- 44
FN <- 33; TN <- 56
N  <- TP + FP + FN + TN

accuracy    <- round((TP + TN) / N, 4)
sensitivity <- round(TP / (TP + FN), 4)   # true-positive rate
specificity <- round(TN / (TN + FP), 4)   # true-negative rate
precision   <- round(TP / (TP + FP), 4)

cat("Accuracy    = (TP + TN) / N  =", paste0("(", TP, " + ", TN, ") / ", N), "=", accuracy, "\n")
## Accuracy    = (TP + TN) / N  = (67 + 56) / 200 = 0.615
cat("Sensitivity = TP / (TP + FN) =", paste0(TP, " / (", TP, " + ", FN, ")"), "=", sensitivity, "\n")
## Sensitivity = TP / (TP + FN) = 67 / (67 + 33) = 0.67
cat("Specificity = TN / (TN + FP) =", paste0(TN, " / (", TN, " + ", FP, ")"), "=", specificity, "\n")
## Specificity = TN / (TN + FP) = 56 / (56 + 44) = 0.56
cat("Precision   = TP / (TP + FP) =", paste0(TP, " / (", TP, " + ", FP, ")"), "=", precision, "\n")
## Precision   = TP / (TP + FP) = 67 / (67 + 44) = 0.6036

Formulas and results:

\[\text{Accuracy} = \frac{TP + TN}{N} = \frac{67 + 56}{200} = 0.615\]

\[\text{Sensitivity} = \frac{TP}{TP + FN} = \frac{67}{67 + 33} = \frac{67}{100} = 0.67\]

\[\text{Specificity} = \frac{TN}{TN + FP} = \frac{56}{56 + 44} = \frac{56}{100} = 0.56\]

\[\text{Precision} = \frac{TP}{TP + FP} = \frac{67}{67 + 44} = \frac{67}{111} = 0.6036\]


3.4 Part (m): Naive Classifier Comparison

# 100 Up, 100 Down -> balanced classes -> majority class = 50%
naive_accuracy <- round(100 / 200, 4)
model_accuracy <- accuracy

cat("Naive (majority class) accuracy:", naive_accuracy, "\n")
## Naive (majority class) accuracy: 0.5
cat("Model accuracy:", model_accuracy, "\n")
## Model accuracy: 0.615
cat("Model beats naive?", model_accuracy > naive_accuracy, "\n")
## Model beats naive? TRUE

\[\text{Naive accuracy} = \frac{100}{200} = 0.5\]

The classes are balanced (100 Up, 100 Down), so the naive majority-class rule achieves 50% accuracy.

The model accuracy of 0.615 > 0.5 → the model beats the naive classifier.

Why accuracy alone is inadequate for a trading system:

  1. On imbalanced data, a model predicting only the majority class can achieve high accuracy while being useless.
  2. Misclassification costs are asymmetric — a false “Up” signal causing a losing trade has different P&L impact than a missed “Up” (false “Down”).
  3. Accuracy treats all errors equally, ignoring the magnitude and direction of returns.

A more economically relevant criterion: The Sharpe ratio of the trading strategy generated by acting on the model’s signals — it directly measures risk-adjusted profitability and captures both the quality of signals and the asymmetric payoffs of correct vs. incorrect predictions.


4 Question 4: Resampling and Regularization in a Backtest [25 points]

Given: Sample mean return \(\hat{\mu} = 0.70\%\), sample SD \(\hat{\sigma} = 5.50\%\), \(n = 48\) months.


4.1 Part (n): Monthly and Annualized Sharpe Ratio

Formula: \[SR_{\text{monthly}} = \frac{\hat{\mu}}{\hat{\sigma}}, \qquad SR_{\text{annual}} = SR_{\text{monthly}} \times \sqrt{12}\]

mu_hat    <- 0.70   # percent
sigma_hat <- 5.50   # percent
n_months  <- 48

SR_monthly  <- round(mu_hat / sigma_hat, 4)
scale_factor <- sqrt(12)
SR_annual   <- round(SR_monthly * scale_factor, 4)

cat("Monthly Sharpe Ratio  = ", mu_hat, "/", sigma_hat, "=", SR_monthly, "\n")
## Monthly Sharpe Ratio  =  0.7 / 5.5 = 0.1273
cat("Scaling factor        = sqrt(12) =", round(scale_factor, 4), "\n")
## Scaling factor        = sqrt(12) = 3.4641
cat("Annualized Sharpe     =", SR_monthly, "* sqrt(12) =", SR_annual, "\n")
## Annualized Sharpe     = 0.1273 * sqrt(12) = 0.441

\[SR_{\text{monthly}} = \frac{0.70\%}{5.50\%} = 0.1273\]

\[SR_{\text{annual}} = 0.1273 \times \sqrt{12} = 0.1273 \times 3.4641 = 0.441\]

Scaling factor used: \(\sqrt{12}\), because returns are monthly and the Sharpe ratio scales with the square root of the sampling frequency (mean scales by \(T\), standard deviation scales by \(\sqrt{T}\), so their ratio scales by \(\sqrt{T}\)).


4.2 Part (o): Bootstrap SE for the Sharpe Ratio

set.seed(42)

# Simulate 48 monthly returns consistent with given mu and sigma
sim_returns <- rnorm(n_months, mean = mu_hat/100, sd = sigma_hat/100)

# Bootstrap procedure
B <- 10000
boot_SR <- numeric(B)

for (i in seq_len(B)) {
  resample   <- sample(sim_returns, size = n_months, replace = TRUE)
  boot_SR[i] <- mean(resample) / sd(resample)
}

boot_SE <- round(sd(boot_SR), 4)
cat("Bootstrap SE of monthly Sharpe Ratio (i.i.d.):", boot_SE, "\n")
## Bootstrap SE of monthly Sharpe Ratio (i.i.d.): 0.149
cat("(Note: illustrative only — see discussion for block bootstrap)\n")
## (Note: illustrative only — see discussion for block bootstrap)

Step-by-step i.i.d. bootstrap procedure:

  1. Treat the \(n = 48\) observed monthly returns as the empirical distribution \(\hat{F}\).
  2. Draw a resample of size 48 with replacement from the 48 observations to obtain \(r^*_1, \ldots, r^*_{48}\).
  3. Compute the bootstrap Sharpe ratio: \(SR^* = \bar{r}^* / s^*\) where \(\bar{r}^*\) and \(s^*\) are the mean and SD of the resample.
  4. Repeat steps 2–3 \(B = 10{,}000\) times, storing each \(SR^*_b\).
  5. The bootstrap SE is: \(\widehat{SE}_{boot} = \text{sd}(SR^*_1, \ldots, SR^*_B)\)

Why i.i.d. bootstrap is inappropriate:

Monthly financial returns typically exhibit serial correlation (autocorrelation) — return shocks cluster over time (momentum, mean-reversion, volatility clustering). The i.i.d. bootstrap randomly shuffles observations, destroying this temporal dependence structure and producing a standard error that is biased downward (underestimates true uncertainty).

The fix — Block Bootstrap:

The stationary block bootstrap (Politis & Romano, 1994) or circular block bootstrap resamples contiguous blocks of \(l\) consecutive months rather than individual observations. By choosing block length \(l\) large enough to span the autocorrelation horizon, the bootstrap resamples preserve the dependence structure within blocks, producing an asymptotically valid SE for dependent time-series data.


4.3 Part (p): Choosing Between λ = 0.030 and λ = 0.065

lambda_min <- 0.030
lambda_1se <- 0.065
factors_min <- 14
factors_1se <- 7

cat("lambda_min =", lambda_min, "-> retains", factors_min, "factors\n")
## lambda_min = 0.03 -> retains 14 factors
cat("lambda_1SE =", lambda_1se, "-> retains", factors_1se, "factors\n")
## lambda_1SE = 0.065 -> retains 7 factors
cat("\nRecommended: lambda_1SE =", lambda_1se, "\n")
## 
## Recommended: lambda_1SE = 0.065
cat("Reason: parsimony without statistically meaningful loss in predictive accuracy\n")
## Reason: parsimony without statistically meaningful loss in predictive accuracy

Recommended choice: \(\lambda = 0.065\) (one-standard-error rule), retaining 7 factors.

Rationale:

The one-standard-error rule selects the most parsimonious model whose cross-validation error is within one standard error of the minimum. Since the true out-of-sample error at \(\lambda = 0.030\) and \(\lambda = 0.065\) are statistically indistinguishable, the sparser model is preferred for four reasons:

  1. Reduced overfitting: 14 factors on 60 candidates risks capturing noise, especially in noisy financial data; 7 factors are more likely to reflect genuine signal.
  2. Lower transaction costs: Fewer factor positions mean lower rebalancing costs in live deployment.
  3. Robustness to regime change: Sparser models are more stable when factor relationships shift across market regimes.
  4. Interpretability: 7 factors are easier to explain to risk management and investors than 14.

4.4 Part (q): Walk-Forward (Time-Respecting) Evaluation Scheme

# Illustrative walk-forward timeline
total_months   <- 60
init_train     <- 36
test_window    <- 1

steps <- total_months - init_train
cat("Walk-forward scheme:\n")
## Walk-forward scheme:
cat("  Initial training window:", init_train, "months\n")
##   Initial training window: 36 months
cat("  Test window per step:", test_window, "month(s)\n")
##   Test window per step: 1 month(s)
cat("  Number of out-of-sample steps:", steps, "\n")
##   Number of out-of-sample steps: 24
cat("  Total OOS observations:", steps, "\n\n")
##   Total OOS observations: 24
# Show the rolling window structure
for (i in 1:min(5, steps)) {
  train_end <- init_train + i - 1
  test_obs  <- init_train + i
  cat(sprintf("  Step %d: Train months 1–%d | Test month %d\n", i, train_end, test_obs))
}
##   Step 1: Train months 1–36 | Test month 37
##   Step 2: Train months 1–37 | Test month 38
##   Step 3: Train months 1–38 | Test month 39
##   Step 4: Train months 1–39 | Test month 40
##   Step 5: Train months 1–40 | Test month 41
cat("  ...\n")
##   ...
cat(sprintf("  Step %d: Train months 1–%d | Test month %d\n",
            steps, total_months - 1, total_months))
##   Step 24: Train months 1–59 | Test month 60

Walk-forward scheme — step by step:

  1. Initialize: Fix a training window of, say, the first 36 months of data.
  2. Fit: Estimate the lasso model (with the chosen \(\lambda\)) on the training window.
  3. Predict: Generate the prediction for month 37 (the first out-of-sample step). Record the prediction and the actual outcome.
  4. Roll forward: Expand (or roll) the training window by one month (months 1–37), re-fit the model, predict month 38.
  5. Repeat until all months are exhausted.
  6. Evaluate: Compute strategy performance metrics (Sharpe ratio, hit rate, drawdown) using only the out-of-sample predictions from steps 3–5.

Why standard random \(k\)-fold cross-validation is unsafe:

Random \(k\)-fold assigns observations to folds randomly, so any fold’s test observations can be temporally adjacent to — or interspersed among — its training observations. This introduces look-ahead bias: the model is trained on future data when predicting past periods, inflating apparent predictive accuracy. In financial backtesting, this directly mimics the ability to “see the future,” producing falsely optimistic performance estimates that will not replicate in live trading.

Walk-forward CV enforces temporal causality: training always precedes testing, mimicking the information available at real deployment time.


End of Examination