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 |
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
## Critical |t|: 1.98
## |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.
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
## |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.
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
## |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.
R2 <- 0.50
systematic <- R2 * 100
diversifiable <- (1 - R2) * 100
cat("Systematic (market) variation:", systematic, "%\n")## Systematic (market) variation: 50 %
## Diversifiable (idiosyncratic) variation: 50 %
\[R^2 = 0.50\]
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\%\]
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 |
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\)).
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
## HML loading: -0.13 -> Negative (not significant) -> Growth tilt
## Style classification: Small-cap Growth
Overall style classification: Small-Cap Growth Fund.
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
## t-statistic: 1.6111
## 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.
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
## FF3 R²: 0.92
## Increase: 0.17
## 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.
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\)
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
## P(Up) = 1 / (1 + exp(- -0.536 )) = 0.3691
## 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”
## beta_1 = 5.4 (positive): Momentum effect
## A positive lagged return increases P(Up) -> markets tend to continue upward
## beta_2 = -0.38 (negative): Risk-off / fear effect
## 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.
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
## 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\]
# 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
## Model accuracy: 0.615
## 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:
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.
Given: Sample mean return \(\hat{\mu} = 0.70\%\), sample SD \(\hat{\sigma} = 5.50\%\), \(n = 48\) months.
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
## Scaling factor = sqrt(12) = 3.4641
## 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}\)).
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
## (Note: illustrative only — see discussion for block bootstrap)
Step-by-step i.i.d. bootstrap procedure:
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.
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
## lambda_1SE = 0.065 -> retains 7 factors
##
## Recommended: lambda_1SE = 0.065
## 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:
# 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:
## Initial training window: 36 months
## Test window per step: 1 month(s)
## Number of out-of-sample steps: 24
## 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(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:
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