Model: \(R_i - R_f = \alpha + \beta(R_m - R_f) + \varepsilon\)
Given:
\[t_{\hat{\beta}} = \frac{\hat{\beta} - 0}{SE(\hat{\beta})}\]
beta_hat <- 0.98
se_beta <- 0.17
t_crit <- 1.98
# t-statistic for H0: beta = 0
t_beta0 <- (beta_hat - 0) / se_beta
cat("t-statistic for H0: beta = 0:", round(t_beta0, 4), "\n")## t-statistic for H0: beta = 0: 5.7647
## Critical value: 1.98
## Reject H0: TRUE
Interpretation: The t-statistic is 5.7647, which exceeds the critical value of 1.98 in absolute value. We reject \(H_0: \beta = 0\) at the 5% significance level.
Economically, \(\hat{\beta} = 0.98\) means the fund moves almost one-for-one with the market. A 1% increase in the market risk premium is associated with approximately a 0.98% increase in the fund’s excess return. The fund has near-market systematic risk.
\[t_{\hat{\beta}=1} = \frac{\hat{\beta} - 1}{SE(\hat{\beta})}\]
# t-statistic for H0: beta = 1
t_beta1 <- (beta_hat - 1) / se_beta
cat("t-statistic for H0: beta = 1:", round(t_beta1, 4), "\n")## t-statistic for H0: beta = 1: -0.1176
## Critical value: 1.98
## Reject H0: FALSE
Interpretation: The t-statistic is -0.1176, which does not exceed 1.98 in absolute value. We fail to reject \(H_0: \beta = 1\). This means the fund’s systematic risk is statistically indistinguishable from that of the market portfolio — it behaves like a near-passive market fund.
\[t_{\hat{\alpha}} = \frac{\hat{\alpha} - 0}{SE(\hat{\alpha})}\]
alpha_hat <- 0.0017
se_alpha <- 0.0020
# t-statistic for Jensen's alpha
t_alpha <- (alpha_hat - 0) / se_alpha
cat("t-statistic for alpha:", round(t_alpha, 4), "\n")## t-statistic for alpha: 0.85
## Critical value: 1.98
## Reject H0 (alpha = 0): FALSE
Interpretation: The t-statistic for \(\hat{\alpha}\) is 0.85, which is below 1.98. We fail to reject \(H_0: \alpha = 0\).
The marketing team’s claim of “positive risk-adjusted performance” is not statistically justified. Although \(\hat{\alpha} = 0.0017\) is positive, it is not significantly different from zero — the result is consistent with luck rather than genuine managerial skill.
## R-squared (systematic fraction): 0.5
## Diversifiable fraction: 0.5
Interpretation: \(R^2 = 0.50\) means 50% of the fund’s return variation is explained by market risk (systematic). The remaining 50% is idiosyncratic (diversifiable) risk, which could be reduced by holding a broader portfolio or attributing it to active bets.
\[E[R_i - R_f] = \hat{\beta} \times E[R_m - R_f]\]
E_mkt_premium <- 0.0070 # 0.70% in decimal
# CAPM implied expected excess return
E_fund_excess <- beta_hat * E_mkt_premium
cat("CAPM-implied expected monthly excess return:", round(E_fund_excess, 4), "\n")## CAPM-implied expected monthly excess return: 0.0069
## In percentage: 0.686 %
Result: The CAPM predicts a monthly excess return of 0.686% for the fund, given its \(\hat{\beta} = 0.98\) and a market risk premium of 0.70% per month.
Model: \(R_i - R_f = \alpha + b \cdot MKT + s \cdot SMB + h \cdot HML + \varepsilon\)
Given (\(n = 144\) months):
| Term | Estimate | Std. Error |
|---|---|---|
| \(\alpha\) | 0.0029 | 0.0018 |
| \(b\) (MKT) | 0.97 | 0.08 |
| \(s\) (SMB) | 0.75 | 0.11 |
| \(h\) (HML) | -0.13 | 0.13 |
\(R^2 = 0.92\), Adjusted \(R^2 = 0.918\). Critical \(|t| \approx 1.98\).
\[t_j = \frac{\hat{\theta}_j}{SE(\hat{\theta}_j)}\]
coef_ff3 <- c(alpha = 0.0029, b_MKT = 0.97, s_SMB = 0.75, h_HML = -0.13)
se_ff3 <- c(alpha = 0.0018, b_MKT = 0.08, s_SMB = 0.11, h_HML = 0.13)
t_stats_ff3 <- coef_ff3 / se_ff3
significant <- abs(t_stats_ff3) > t_crit
results_ff3 <- data.frame(
Coefficient = names(coef_ff3),
Estimate = coef_ff3,
Std_Error = se_ff3,
t_stat = round(t_stats_ff3, 4),
Significant = significant
)
print(results_ff3)## Coefficient Estimate Std_Error t_stat Significant
## alpha alpha 0.0029 0.0018 1.6111 FALSE
## b_MKT b_MKT 0.9700 0.0800 12.1250 TRUE
## s_SMB s_SMB 0.7500 0.1100 6.8182 TRUE
## h_HML h_HML -0.1300 0.1300 -1.0000 FALSE
Summary:
## SMB loading (s): 0.75 -> Positive: tilts toward SMALL-cap stocks
## HML loading (h): -0.13 -> Negative: tilts toward GROWTH stocks
Style Classification:
Overall: The fund is best characterized as a small-cap growth fund.
alpha_ff3 <- coef_ff3["alpha"]
se_alpha_ff3 <- se_ff3["alpha"]
t_alpha_ff3 <- t_stats_ff3["alpha"]
cat("Alpha (monthly):", alpha_ff3, "\n")## Alpha (monthly): 0.0029
## t-statistic for alpha: 1.6111
## Annualized alpha (approx): 3.48 % per year
## Significant at 5%: FALSE
Interpretation: \(\hat{\alpha} = 0.0029\) corresponds to approximately 3.48% per year of excess return after controlling for all three Fama–French factors. The t-statistic is 1.6111, which is marginally above 1.98.
Since \(|t| > 1.98\), we reject \(H_0: \alpha = 0\) at the 5% level (barely). This provides modest evidence that the manager adds value beyond passive exposure to market, size, and value factors. However, given the borderline significance, caution is warranted — it may be sensitive to the sample period.
## Single-factor R-squared: 0.75
## Three-factor R-squared: 0.92
## Three-factor Adjusted R-squared: 0.918
## Improvement in R-squared: 0.17
Interpretation:
Model: \(\text{logit}\, P(\text{Up}) = \beta_0 + \beta_1 r_{t-1} + \beta_2 \Delta VIX_{t-1}\)
Given: \(\beta_0 = -0.02\), \(\beta_1 = 5.4\), \(\beta_2 = -0.38\)
Today: \(r_{t-1} = 0.010\), \(\Delta VIX = 1.5\)
\[\text{logit} = \beta_0 + \beta_1 r_{t-1} + \beta_2 \Delta VIX\]
\[P(\text{Up}) = \frac{e^{\text{logit}}}{1 + e^{\text{logit}}} = \frac{1}{1 + e^{-\text{logit}}}\]
b0 <- -0.02
b1 <- 5.4
b2 <- -0.38
r_lag <- 0.010
dVIX <- 1.5
# Compute log-odds
log_odds <- b0 + b1 * r_lag + b2 * dVIX
cat("Log-odds (logit):", round(log_odds, 4), "\n")## Log-odds (logit): -0.536
# Predicted probability
p_up <- 1 / (1 + exp(-log_odds))
cat("Predicted P(Up):", round(p_up, 4), "\n")## Predicted P(Up): 0.3691
# Predicted class at 0.5 threshold
predicted_class <- ifelse(p_up >= 0.5, "Up", "Down")
cat("Predicted class (threshold = 0.5):", predicted_class, "\n")## Predicted class (threshold = 0.5): Down
Result: \[\text{logit} = -0.02 + 5.4(0.010) + (-0.38)(1.5) = -0.536\] \[P(\text{Up}) = \frac{1}{1 + e^{0.536}} = 0.3691\]
The predicted probability is 0.3691 and the predicted class is “Down” at the 0.5 threshold.
## beta_1 (lagged return): 5.4 -> Positive: momentum effect
## beta_2 (delta VIX): -0.38 -> Negative: fear/uncertainty dampens up probability
Economic Interpretation:
Given the 200-day hold-out confusion matrix:
| Actual Up | Actual Down | Total | |
|---|---|---|---|
| Predicted Up | 67 | 44 | 111 |
| Predicted Down | 33 | 56 | 89 |
| Total | 100 | 100 | 200 |
\[\text{Accuracy} = \frac{TP + TN}{N}, \quad \text{Sensitivity} = \frac{TP}{TP + FN}\] \[\text{Specificity} = \frac{TN}{TN + FP}, \quad \text{Precision} = \frac{TP}{TP + FP}\]
TP <- 67 # Predicted Up, Actual Up
FP <- 44 # Predicted Up, Actual Down
FN <- 33 # Predicted Down, Actual Up
TN <- 56 # Predicted Down, Actual Down
N <- 200
accuracy <- (TP + TN) / N
sensitivity <- TP / (TP + FN) # True-positive rate for "Up"
specificity <- TN / (TN + FP)
precision <- TP / (TP + FP)
cat("Accuracy: ", round(accuracy, 4), "\n")## Accuracy: 0.615
## Sensitivity: 0.67
## Specificity: 0.56
## Precision: 0.6036
Results:
| Metric | Formula | Value |
|---|---|---|
| Accuracy | \((TP + TN)/N\) | 0.615 |
| Sensitivity | \(TP / (TP + FN)\) | 0.67 |
| Specificity | \(TN / (TN + FP)\) | 0.56 |
| Precision | \(TP / (TP + FP)\) | 0.6036 |
The dataset has 100 Up and 100 Down days — balanced classes. The naive majority-class rule predicts “Up” always.
# Naive rule: always predict majority class (Up or Down, both = 100)
# With balanced classes, naive accuracy = 100/200
naive_accuracy <- 100 / 200
cat("Naive majority-class accuracy:", round(naive_accuracy, 4), "\n")## Naive majority-class accuracy: 0.5
## Model accuracy: 0.615
## Model beats naive rule: TRUE
Does the model beat the naive rule? Yes — the model achieves 61.5% accuracy versus the naive benchmark of 50%.
Why accuracy alone is inadequate for a trading system:
Given: \(\bar{r} = 0.70\%\) per month, \(\hat{\sigma} = 5.50\%\) per month, \(n = 48\) months.
\[SR_{\text{monthly}} = \frac{\bar{r}}{\hat{\sigma}}, \qquad SR_{\text{annual}} = SR_{\text{monthly}} \times \sqrt{12}\]
mean_ret <- 0.0070 # 0.70% monthly
sd_ret <- 0.0550 # 5.50% monthly
n_months <- 48
# Monthly Sharpe ratio
SR_monthly <- mean_ret / sd_ret
cat("Monthly Sharpe ratio:", round(SR_monthly, 4), "\n")## Monthly Sharpe ratio: 0.1273
# Annualized Sharpe ratio (scaling by sqrt(12))
SR_annual <- SR_monthly * sqrt(12)
cat("Annualized Sharpe ratio:", round(SR_annual, 4), "\n")## Annualized Sharpe ratio: 0.4409
## Scaling factor used: sqrt(12) = 3.4641
Result: \[SR_{\text{monthly}} = \frac{0.0070}{0.0550} = 0.1273\] \[SR_{\text{annual}} = 0.1273 \times \sqrt{12} = 0.4409\]
The scaling factor is \(\sqrt{12}\) because returns are i.i.d. monthly — variance scales linearly with time, so volatility scales with \(\sqrt{T}\), and the Sharpe ratio scales with \(\sqrt{T}\) when moving from monthly to annual.
Step-by-step i.i.d. bootstrap:
set.seed(42)
# Simulate 48 monthly returns consistent with given stats
monthly_returns <- rnorm(n_months, mean = mean_ret, sd = sd_ret)
# Bootstrap function
sharpe_boot <- function(returns, B = 10000) {
n <- length(returns)
boot_sr <- numeric(B)
for (b in 1:B) {
resample <- sample(returns, size = n, replace = TRUE)
boot_sr[b] <- mean(resample) / sd(resample)
}
return(boot_sr)
}
boot_sr_iid <- sharpe_boot(monthly_returns, B = 10000)
se_boot_iid <- sd(boot_sr_iid)
cat("Bootstrap SE of monthly Sharpe (i.i.d.):", round(se_boot_iid, 4), "\n")## Bootstrap SE of monthly Sharpe (i.i.d.): 0.149
Procedure description:
Why i.i.d. bootstrap is inappropriate for monthly returns:
Monthly financial returns typically exhibit serial dependence (autocorrelation, volatility clustering via GARCH effects). The i.i.d. bootstrap destroys the temporal dependence structure by sampling observations independently, producing underestimated variance.
Fix — Block Bootstrap: Use the stationary block bootstrap (Politis & Romano, 1994) or the moving block bootstrap: instead of sampling individual observations, sample blocks of consecutive returns (e.g., overlapping blocks of length \(l \approx \sqrt{n}\)). This preserves short-run autocorrelation within blocks.
# Illustration of block bootstrap concept
block_bootstrap_sr <- function(returns, block_len = 4, B = 10000) {
n <- length(returns)
boot_sr <- numeric(B)
for (b in 1:B) {
# Draw starting indices for blocks
starts <- sample(1:(n - block_len + 1), size = ceiling(n / block_len), replace = TRUE)
indices <- unlist(lapply(starts, function(s) s:(s + block_len - 1)))
resamp <- returns[indices[1:n]]
boot_sr[b] <- mean(resamp) / sd(resamp)
}
return(boot_sr)
}
boot_sr_block <- block_bootstrap_sr(monthly_returns, block_len = 4, B = 10000)
se_boot_block <- sd(boot_sr_block)
cat("Block Bootstrap SE of monthly Sharpe:", round(se_boot_block, 4), "\n")## Block Bootstrap SE of monthly Sharpe: 0.1601
lambda_min <- 0.030; factors_min <- 14
lambda_1se <- 0.065; factors_1se <- 7
cat("Lambda (min CV error):", lambda_min, "| Factors retained:", factors_min, "\n")## Lambda (min CV error): 0.03 | Factors retained: 14
## Lambda (1-SE rule): 0.065 | Factors retained: 7
Recommended choice: \(\lambda = 0.065\) (the one-standard-error rule, retaining 7 factors).
Rationale:
Walk-forward scheme:
# Illustration of walk-forward splits
n_total <- 120 # hypothetical number of monthly observations
train_init <- 60 # initial training window
test_size <- 12 # 1-year test window
cat("Walk-forward cross-validation scheme:\n")## Walk-forward cross-validation scheme:
## Fold Training window Test window
fold <- 1
start <- 1
while (start + train_init + test_size - 1 <= n_total) {
train_end <- start + train_init - 1
test_start <- train_end + 1
test_end <- test_start + test_size - 1
cat(sprintf("%-8d [%3d, %3d] [%3d, %3d]\n",
fold, start, train_end, test_start, test_end))
start <- start + test_size
fold <- fold + 1
}## 1 [ 1, 60] [ 61, 72]
## 2 [ 13, 72] [ 73, 84]
## 3 [ 25, 84] [ 85, 96]
## 4 [ 37, 96] [ 97, 108]
## 5 [ 49, 108] [109, 120]
Step-by-step procedure:
Why standard random k-fold CV is unsafe for financial time series:
summary_df <- data.frame(
Question = c("Q1(a)", "Q1(b)", "Q1(c)", "Q1(e)", "Q2(f) alpha",
"Q2(f) MKT", "Q2(f) SMB", "Q2(f) HML",
"Q3(j)", "Q3(l) Accuracy", "Q4(n) SR Monthly", "Q4(n) SR Annual"),
Result = c(
paste0("t = ", round((0.98)/0.17, 4), " → Reject H0"),
paste0("t = ", round((0.98-1)/0.17, 4), " → Fail to Reject H0"),
paste0("t = ", round(0.0017/0.0020, 4), " → Not significant"),
paste0(round(0.98 * 0.0070 * 100, 4), "% per month"),
paste0("t = ", round(0.0029/0.0018, 4), " → Marginal"),
paste0("t = ", round(0.97/0.08, 4), " → Significant"),
paste0("t = ", round(0.75/0.11, 4), " → Significant"),
paste0("t = ", round(-0.13/0.13, 4), " → Not significant"),
paste0("P(Up) = ", round(1/(1+exp(-(-0.02+5.4*0.01-0.38*1.5))), 4)),
paste0(round((67+56)/200, 4)),
paste0(round(0.0070/0.0550, 4)),
paste0(round(0.0070/0.0550 * sqrt(12), 4))
)
)
knitr::kable(summary_df, caption = "Key Numerical Results")| Question | Result |
|---|---|
| Q1(a) | t = 5.7647 → Reject H0 |
| Q1(b) | t = -0.1176 → Fail to Reject H0 |
| Q1(c) | t = 0.85 → Not significant |
| Q1(e) | 0.686% per month |
| Q2(f) alpha | t = 1.6111 → Marginal |
| Q2(f) MKT | t = 12.125 → Significant |
| Q2(f) SMB | t = 6.8182 → Significant |
| Q2(f) HML | t = -1 → Not significant |
| Q3(j) | P(Up) = 0.3691 |
| Q3(l) Accuracy | 0.615 |
| Q4(n) SR Monthly | 0.1273 |
| Q4(n) SR Annual | 0.4409 |
End of Examination