Model: \[R_i - R_f = \alpha + \beta(R_m - R_f) + \varepsilon\]
Given information:
| Term | Estimate | Std. Error |
|---|---|---|
| Intercept \((\alpha)\) | 0.0017 | 0.0020 |
| Market premium \((\beta)\) | 0.98 | 0.17 |
Formula: \[t_{\hat{\beta}} = \frac{\hat{\beta} - 0}{SE(\hat{\beta})}\]
beta_hat <- 0.98
se_beta <- 0.17
t_beta <- beta_hat / se_beta
t_crit <- 1.98
cat("t-statistic for beta:", round(t_beta, 4), "\n")## t-statistic for beta: 5.7647
## Critical value: 1.98
## Reject H0: beta = 0? TRUE
\[t_{\hat{\beta}} = \frac{0.98}{0.17} = 5.7647\]
Decision: \(|t| = 5.7647 > 1.98\), so we reject \(H_0: \beta = 0\) at the 5% level.
Economic interpretation: \(\hat{\beta} = 0.98\) means that for every 1% increase in the market excess return, the fund’s excess return increases by approximately 0.98%. The fund moves almost one-for-one with the market — it has nearly the same systematic (market) risk as a passive index.
Formula: \[t = \frac{\hat{\beta} - 1}{SE(\hat{\beta})}\]
## t-statistic for H0: beta = 1: -0.1176
## Reject H0: beta = 1? FALSE
\[t = \frac{0.98 - 1}{0.17} = \frac{-0.02}{0.17} = -0.1176\]
Decision: \(|t| = 0.1176 < 1.98\), so we fail to reject \(H_0: \beta = 1\) at the 5% level.
Interpretation: The data are statistically consistent with \(\beta = 1\). The fund’s systematic risk is not significantly different from the market’s. It is neither defensively nor aggressively tilted relative to a passive market portfolio.
Formula: \[t_{\hat{\alpha}} = \frac{\hat{\alpha} - 0}{SE(\hat{\alpha})}\]
alpha_hat <- 0.0017
se_alpha <- 0.0020
t_alpha <- alpha_hat / se_alpha
cat("t-statistic for alpha:", round(t_alpha, 4), "\n")## t-statistic for alpha: 0.85
## Reject H0: alpha = 0? FALSE
\[t_{\hat{\alpha}} = \frac{0.0017}{0.0020} = 0.85\]
Decision: \(|t| = 0.85 < 1.98\), so we fail to reject \(H_0: \alpha = 0\) at the 5% level.
Conclusion on the marketing claim: The marketing team’s claim of “positive risk-adjusted performance” is not statistically supported. Although the point estimate of \(\hat{\alpha} = 0.0017\) (0.17% per month) is positive, it is not statistically distinguishable from zero given its standard error. Advertising a positive alpha on this basis would be misleading.
R2 <- 0.50
systematic_pct <- R2 * 100
diversifiable_pct <- (1 - R2) * 100
cat("Systematic variation: ", systematic_pct, "%\n")## Systematic variation: 50 %
## Diversifiable variation: 50 %
\[R^2 = 0.50\]
Interpretation: \(R^2 = 0.50\) means that 50% of the fund’s return variation is explained by market movements (systematic risk). The remaining 50% is idiosyncratic (diversifiable) risk — fund-specific noise not explained by the market factor. A well-diversified portfolio would have \(R^2\) close to 1.00; this fund retains substantial unsystematic risk.
Formula: \[E[R_i - R_f] = \hat{\beta} \times E[R_m - R_f]\]
E_mkt_premium <- 0.0070 # 0.70% expressed as decimal
E_fund_excess <- beta_hat * E_mkt_premium
cat("CAPM-implied expected monthly excess return:", round(E_fund_excess * 100, 4), "%\n")## CAPM-implied expected monthly excess return: 0.686 %
\[E[R_i - R_f] = 0.98 \times 0.70\% = 0.686\%\]
The CAPM predicts the fund should earn a monthly excess return of approximately 0.686%.
Model: \[R_i - R_f = \alpha + b \cdot MKT + s \cdot SMB + h \cdot HML + \varepsilon\]
Given information (\(n = 144\) months):
| Term | Estimate | Std. Error |
|---|---|---|
| Intercept \((\alpha)\) | 0.0029 | 0.0018 |
| MKT \((b)\) | 0.97 | 0.08 |
| SMB \((s)\) | 0.75 | 0.11 |
| HML \((h)\) | -0.13 | 0.13 |
Formula: \[t_k = \frac{\hat{\beta}_k}{SE(\hat{\beta}_k)}\]
coef_names <- c("Alpha", "MKT (b)", "SMB (s)", "HML (h)")
estimates <- c(0.0029, 0.97, 0.75, -0.13)
std_errors <- c(0.0018, 0.08, 0.11, 0.13)
t_stats <- estimates / std_errors
significant <- abs(t_stats) > t_crit
results <- data.frame(
Coefficient = coef_names,
Estimate = estimates,
Std_Error = std_errors,
t_statistic = round(t_stats, 4),
Significant_5pct = significant
)
print(results)## Coefficient Estimate Std_Error t_statistic Significant_5pct
## 1 Alpha 0.0029 0.0018 1.6111 FALSE
## 2 MKT (b) 0.9700 0.0800 12.1250 TRUE
## 3 SMB (s) 0.7500 0.1100 6.8182 TRUE
## 4 HML (h) -0.1300 0.1300 -1.0000 FALSE
Summary:
| Coefficient | Estimate | SE | t-statistic | Significant at 5%? |
|---|---|---|---|---|
| \(\alpha\) | 0.0029 | 0.0018 | 1.6111 | FALSE |
| MKT \((b)\) | 0.97 | 0.08 | 12.125 | TRUE |
| SMB \((s)\) | 0.75 | 0.11 | 6.8182 | TRUE |
| HML \((h)\) | -0.13 | 0.13 | -1 | FALSE |
Significant at 5%: \(\alpha\), MKT, and SMB are all significant. HML is not significant (\(|t| = 1.00 < 1.98\)).
s_hat <- 0.75 # SMB loading
h_hat <- -0.13 # HML loading
cat("SMB loading (s):", s_hat,
"-> Positive => fund tilts toward SMALL-cap stocks\n")## SMB loading (s): 0.75 -> Positive => fund tilts toward SMALL-cap stocks
## HML loading (h): -0.13 -> Negative => fund tilts toward GROWTH stocks (low B/M)
Size tilt: \(\hat{s} = 0.75 > 0\) and statistically significant — the fund has a strong small-cap tilt. It behaves like a portfolio of smaller firms relative to large-cap stocks.
Value/Growth tilt: \(\hat{h} = -0.13 < 0\) but not statistically significant (\(|t| = 1.00\)). The negative sign suggests a mild growth tilt (low book-to-market), but this cannot be distinguished from zero at the 5% level.
Overall style: The fund is best characterized as a small-cap (growth-leaning) fund, though the growth tilt is not statistically confirmed.
alpha_ff <- 0.0029
se_alpha_ff <- 0.0018
t_alpha_ff <- alpha_ff / se_alpha_ff
cat("FF3 alpha (monthly):", alpha_ff * 100, "%\n")## FF3 alpha (monthly): 0.29 %
## t-statistic for alpha: 1.6111
## Significant at 5%? FALSE
\[t_{\hat{\alpha}} = \frac{0.0029}{0.0018} = 1.6111\]
Decision: \(|t| = 1.6111 > 1.98\) — we reject \(H_0: \alpha = 0\) at the 5% level.
Interpretation: After controlling for all three Fama–French factors (market, size, and value), the fund earns a statistically significant monthly alpha of 0.29%. This is evidence that the manager adds value beyond mere factor exposures — there is genuine skill or access to return sources not captured by the three factors.
R2_capm <- 0.75
R2_ff3 <- 0.92
adj_R2 <- 0.918
cat("R^2 gain from CAPM to FF3:", R2_ff3 - R2_capm, "\n")## R^2 gain from CAPM to FF3: 0.17
## Adjusted R^2 (FF3): 0.918
Increase in \(R^2\): The \(R^2\) rises from 0.75 (single-factor CAPM) to 0.92 (FF3), a gain of 0.17 (17 percentage points). This means SMB and HML collectively explain an additional 17% of the fund’s return variation beyond what the market factor alone captures. The fund’s returns are substantially driven by its size tilt (small-cap exposure), which the CAPM omitted.
Why Adjusted \(R^2\)? Raw \(R^2\) always increases when predictors are added, even if they add no true explanatory power (it can never decrease). Adjusted \(R^2\) penalizes for the number of predictors: \[\bar{R}^2 = 1 - \frac{(1-R^2)(n-1)}{n-k-1}\] where \(k\) is the number of predictors. When comparing models with different numbers of factors (1 vs. 3), adjusted \(R^2\) is the appropriate metric because it only increases when a new variable improves fit by more than chance. Here, adjusted \(R^2 = 0.918\) confirms that all three factors are genuinely contributing.
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\)
Logit (log-odds) computation: \[\text{logit} = \beta_0 + \beta_1 r_{t-1} + \beta_2 \Delta VIX\]
Sigmoid transformation to probability: \[P(\text{Up}) = \frac{e^{\text{logit}}}{1 + e^{\text{logit}}} = \frac{1}{1 + e^{-\text{logit}}}\]
beta0 <- -0.02
beta1 <- 5.4
beta2 <- -0.38
r_lag <- 0.010
dVIX <- 1.5
logit_val <- beta0 + beta1 * r_lag + beta2 * dVIX
prob_up <- 1 / (1 + exp(-logit_val))
pred_class <- ifelse(prob_up >= 0.5, "Up", "Down")
cat("Logit value: ", round(logit_val, 4), "\n")## Logit value: -0.536
## P(Up): 0.3691
## Predicted class (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)}} = 0.3691\]
Predicted class: Since \(P(\text{Up}) = 0.3691 < 0.5\), the model predicts “Down” for tomorrow.
\(\beta_1 = 5.4 > 0\) (Lagged Return): A positive lagged return increases the log-odds of an “Up” day. This captures momentum — when the market rose yesterday, it is more likely to rise again tomorrow. This is consistent with short-term return continuation documented in financial literature.
\(\beta_2 = -0.38 < 0\) (\(\Delta VIX\)): An increase in VIX (the “fear index”) decreases the log-odds of an “Up” day. This captures risk-off sentiment — when market uncertainty/fear rises, the probability of a positive return tomorrow falls. VIX spikes signal investor anxiety, which tends to accompany or precede market declines.
Confusion matrix (200-day hold-out):
| Predicted Up | Predicted Down | |
|---|---|---|
| Actual Up | 67 (TP) | 33 (FN) |
| Actual Down | 44 (FP) | 56 (TN) |
TP <- 67
FP <- 44
FN <- 33
TN <- 56
N <- 200
accuracy <- (TP + TN) / N
sensitivity <- TP / (TP + FN) # True Positive Rate for "Up"
specificity <- TN / (TN + FP) # True Negative Rate
precision <- TP / (TP + FP) # Positive Predictive Value
cat("Accuracy: ", round(accuracy, 4), sprintf("(%.2f%%)\n", accuracy*100))## Accuracy: 0.615 (61.50%)
## Sensitivity: 0.67 (67.00%)
## Specificity: 0.56 (56.00%)
## Precision: 0.6036 (60.36%)
Formulas and results:
\[\text{Accuracy} = \frac{TP + TN}{N} = \frac{67 + 56}{200} = \frac{123}{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\]
# Balanced classes: 100 Up, 100 Down
# Naive rule: predict the majority class (either — both are 50%)
# If we always predict "Up":
naive_accuracy <- 100 / 200 # 50%
model_accuracy <- accuracy
cat("Naive classifier accuracy:", naive_accuracy, "\n")## Naive classifier accuracy: 0.5
## Logistic model accuracy: 0.615
## Model beats naive? TRUE
Naive rule accuracy: Since the test set has exactly 100 “Up” and 100 “Down” days (balanced), always predicting either class gives \(\frac{100}{200} = 50.0\%\) accuracy.
Model accuracy: \(61.5\%\) — the model beats the naive classifier by 11.5 percentage points.
Why accuracy alone is inadequate for trading:
Accuracy treats every misclassification equally, but in a trading system the costs of errors are asymmetric: - Missing an “Up” day (False Negative) = foregone profit - Incorrectly entering a “Down” day (False Positive) = realized loss + transaction costs
A more economically relevant criterion would be profit and loss (P&L) or risk-adjusted return (e.g., Sharpe ratio) from the strategy implied by model signals. Alternatively, the F1-score (harmonic mean of precision and sensitivity) or ROC-AUC (area under the receiver operating characteristic curve) better balance the trade-off between catching “Up” days and avoiding false signals.
Given: - Sample mean monthly return: \(\bar{r} = 0.70\% = 0.0070\) - Sample standard deviation: \(\hat{\sigma} = 5.50\% = 0.0550\) - \(n = 48\) months
Formula (monthly Sharpe ratio, excess return assumed = mean return here): \[SR_{\text{monthly}} = \frac{\bar{r}}{\hat{\sigma}}\]
Annualization — scaling factor: There are 12 months per year, so: \[SR_{\text{annual}} = SR_{\text{monthly}} \times \sqrt{12}\]
r_bar <- 0.0070
sigma <- 0.0550
n_months <- 48
SR_monthly <- r_bar / sigma
scale_factor <- sqrt(12)
SR_annual <- SR_monthly * scale_factor
cat("Monthly Sharpe Ratio: ", round(SR_monthly, 4), "\n")## Monthly Sharpe Ratio: 0.1273
## Scaling factor: sqrt(12) = 3.4641
## Annualized Sharpe Ratio: 0.4409
\[SR_{\text{monthly}} = \frac{0.0070}{0.0550} = 0.1273\]
\[SR_{\text{annual}} = 0.1273 \times \sqrt{12} = 0.1273 \times 3.4641 = 0.4409\]
Scaling factor used: \(\sqrt{12}\), because the Sharpe ratio scales with \(\sqrt{T}\) when returns are i.i.d. (mean scales by \(T\), standard deviation by \(\sqrt{T}\), so their ratio scales by \(\sqrt{T}\); here \(T = 12\)).
Step-by-step i.i.d. bootstrap procedure:
set.seed(42)
# Simulate 48 monthly returns for illustration
set.seed(42)
monthly_returns <- rnorm(48, mean = 0.0070, sd = 0.0550)
sharpe_boot <- function(returns) {
mean(returns) / sd(returns)
}
B <- 10000
boot_sharpes <- replicate(B, {
resample <- sample(monthly_returns, size = length(monthly_returns), replace = TRUE)
sharpe_boot(resample)
})
SE_boot <- sd(boot_sharpes)
cat("Bootstrap SE of monthly Sharpe ratio:", round(SE_boot, 4), "\n")## Bootstrap SE of monthly Sharpe ratio: 0.149
cat("95% Bootstrap CI: [",
round(quantile(boot_sharpes, 0.025), 4), ",",
round(quantile(boot_sharpes, 0.975), 4), "]\n")## 95% Bootstrap CI: [ -0.2097 , 0.3765 ]
Procedure (described step by step):
Why i.i.d. bootstrap is inappropriate for monthly returns:
Monthly financial returns exhibit serial dependence — autocorrelation, volatility clustering (GARCH effects), and regime changes. The ordinary i.i.d. bootstrap destroys the temporal ordering of observations, breaking these dependencies and producing underestimated standard errors.
Fix — Block Bootstrap (e.g., Stationary Block Bootstrap or Moving Block Bootstrap): Instead of drawing individual observations, draw blocks of consecutive returns (e.g., blocks of length \(l = 4\)–\(6\) months). This preserves the short-run autocorrelation structure within each block while still providing variation across bootstrap samples. The stationary block bootstrap (Politis & Romano, 1994) uses random block lengths to avoid edge effects and produces stationary bootstrap replicates.
lambda_min <- 0.030 # min CV error, retains 14 factors
lambda_1se <- 0.065 # one-SE rule, retains 7 factors
cat("lambda_min:", lambda_min, "-> retains", 14, "factors\n")## lambda_min: 0.03 -> retains 14 factors
## lambda_1se: 0.065 -> retains 7 factors
Recommended choice: \(\lambda_{1SE} = 0.065\) (7 factors)
Reasoning:
The one-standard-error rule selects the most parsimonious model whose CV error is within one standard error of the minimum. In a financial backtest with 60 candidate factors, there are multiple strong reasons to prefer \(\lambda_{1SE}\):
Overfitting risk: With 60 factors, the minimum-CV solution (14 factors) is highly susceptible to in-sample overfitting. The marginal factors added between 7 and 14 are likely capturing noise rather than genuine signal.
Multiple testing / data snooping: In finance, factor selection from a large candidate set introduces data snooping bias. A sparser model with 7 factors is more likely to survive out-of-sample.
Transaction costs and capacity: More factors mean more rebalancing trades and higher implementation costs. A simpler 7-factor model is more practically deployable.
Interpretability and robustness: Fewer factors are easier to understand and more robust to structural breaks — a common concern in financial markets.
The small CV-error penalty from choosing \(\lambda_{1SE}\) is more than offset by the gains in generalization, parsimony, and practical deployability.
# Visual illustration of walk-forward scheme
library(ggplot2)
n_total <- 60
train_start <- c(1, 6, 11, 16, 21)
train_end <- c(36, 41, 46, 51, 56)
test_start <- c(37, 42, 47, 52, 57)
test_end <- c(41, 46, 51, 56, 60)
df_wf <- data.frame(
Fold = rep(1:5, 2),
Type = c(rep("Training", 5), rep("Test", 5)),
Start = c(train_start, test_start),
End = c(train_end, test_end)
)
ggplot(df_wf, aes(xmin = Start, xmax = End,
ymin = Fold - 0.4, ymax = Fold + 0.4,
fill = Type)) +
geom_rect(alpha = 0.8, color = "white") +
scale_fill_manual(values = c("Training" = "steelblue", "Test" = "tomato")) +
labs(title = "Walk-Forward Cross-Validation (Expanding Window)",
x = "Month", y = "Fold", fill = "") +
theme_minimal(base_size = 12) +
theme(legend.position = "bottom")Walk-forward (time-respecting) scheme — step by step:
An expanding window (always using all available past data) is preferred over a fixed rolling window when the data-generating process is relatively stable; a rolling window is preferred if there is structural change.
Why standard random k-fold cross-validation is unsafe:
Standard k-fold CV randomly shuffles observations into \(k\) folds, allowing future data to appear in the training set when predicting past data. This is a form of look-ahead bias (data leakage):
Walk-forward CV enforces the strict rule: train only on information available at the time of prediction. This mirrors real-world deployment and produces honest, actionable performance estimates.
End of Examination ```