This analysis compares three investment portfolios using their discrete return distributions. It calculates expected return, variance, and standard deviation, then uses Monte Carlo simulation to estimate return distributions and the probability of a loss. The results support recommendations for conservative, moderate, and aggressive investors.
The probabilities for each portfolio should sum to 1.
portfolio_returns <- data.frame(
Portfolio = c("A", "A", "A", "B", "B", "B", "C", "C", "C"),
Return = c(0.15, 0.08, -0.05, 0.10, 0.06, -0.02, 0.07, 0.05, -0.01),
Probability = c(0.30, 0.50, 0.20, 0.40, 0.40, 0.20, 0.60, 0.30, 0.10)
)
knitr::kable(portfolio_returns, digits = 3,
caption = "Portfolio return outcomes and probabilities")
| Portfolio | Return | Probability |
|---|---|---|
| A | 0.15 | 0.3 |
| A | 0.08 | 0.5 |
| A | -0.05 | 0.2 |
| B | 0.10 | 0.4 |
| B | 0.06 | 0.4 |
| B | -0.02 | 0.2 |
| C | 0.07 | 0.6 |
| C | 0.05 | 0.3 |
| C | -0.01 | 0.1 |
probability_check <- aggregate(Probability ~ Portfolio,
data = portfolio_returns, sum)
knitr::kable(probability_check, digits = 2,
caption = "Probability totals by portfolio")
| Portfolio | Probability |
|---|---|
| A | 1 |
| B | 1 |
| C | 1 |
For a discrete return variable \(X\), expected return is
\[E(X) = \sum_i x_i p_i\]
Variance is
\[\operatorname{Var}(X) = \sum_i p_i(x_i - E(X))^2\]
Standard deviation is the square root of variance:
\[SD(X) = \sqrt{\operatorname{Var}(X)}\]
portfolio_summary <- do.call(rbind, lapply(
split(portfolio_returns, portfolio_returns$Portfolio),
function(d) {
expected_return <- sum(d$Return * d$Probability)
variance <- sum(d$Probability * (d$Return - expected_return)^2)
data.frame(
Portfolio = d$Portfolio[1],
Expected_Return = expected_return,
Variance = variance,
Standard_Deviation = sqrt(variance),
Probability_of_Loss = sum(d$Probability[d$Return < 0])
)
}
))
rownames(portfolio_summary) <- NULL
formatted_summary <- data.frame(
Portfolio = portfolio_summary$Portfolio,
`Expected return` = sprintf("%.2f%%", 100 * portfolio_summary$Expected_Return),
Variance = sprintf("%.6f", portfolio_summary$Variance),
`Standard deviation` = sprintf("%.2f%%", 100 * portfolio_summary$Standard_Deviation),
`Probability of loss` = sprintf("%.1f%%", 100 * portfolio_summary$Probability_of_Loss),
check.names = FALSE
)
knitr::kable(formatted_summary, caption = "Analytical risk and return measures")
| Portfolio | Expected return | Variance | Standard deviation | Probability of loss |
|---|---|---|---|---|
| A | 7.50% | 0.004825 | 6.95% | 20.0% |
| B | 6.00% | 0.001920 | 4.38% | 20.0% |
| C | 5.60% | 0.000564 | 2.37% | 10.0% |
Portfolio A has the highest expected return, while Portfolio C has the lowest variance and standard deviation. Portfolio B falls between A and C in expected return and risk. Expected return is a probability-weighted average, not a guaranteed result.
Each simulation draws 10,000 returns from each portfolio’s discrete distribution using the supplied probabilities. This simulates repeated one-period outcomes and does not assume normally distributed returns.
n_simulations <- 10000
portfolio_groups <- split(portfolio_returns, portfolio_returns$Portfolio)
simulated_returns <- lapply(portfolio_groups, function(d) {
sample(d$Return, size = n_simulations, replace = TRUE, prob = d$Probability)
})
simulation_summary <- data.frame(
Portfolio = names(simulated_returns),
Simulated_Mean = sapply(simulated_returns, mean),
Simulated_SD = sapply(simulated_returns, sd),
Estimated_Loss_Probability = sapply(simulated_returns, function(x) mean(x < 0)),
row.names = NULL
)
comparison <- merge(portfolio_summary, simulation_summary,
by = "Portfolio", sort = TRUE)
simulation_table <- data.frame(
Portfolio = comparison$Portfolio,
`Expected return (analytical)` = sprintf("%.2f%%", 100 * comparison$Expected_Return),
`Mean return (simulated)` = sprintf("%.2f%%", 100 * comparison$Simulated_Mean),
`SD (analytical)` = sprintf("%.2f%%", 100 * comparison$Standard_Deviation),
`SD (simulated)` = sprintf("%.2f%%", 100 * comparison$Simulated_SD),
`Loss probability (analytical)` = sprintf("%.2f%%", 100 * comparison$Probability_of_Loss),
`Loss probability (simulated)` = sprintf("%.2f%%", 100 * comparison$Estimated_Loss_Probability),
check.names = FALSE
)
knitr::kable(simulation_table,
caption = "Analytical results compared with Monte Carlo estimates")
| Portfolio | Expected return (analytical) | Mean return (simulated) | SD (analytical) | SD (simulated) | Loss probability (analytical) | Loss probability (simulated) |
|---|---|---|---|---|---|---|
| A | 7.50% | 7.50% | 6.95% | 6.94% | 20.00% | 19.98% |
| B | 6.00% | 6.01% | 4.38% | 4.36% | 20.00% | 19.70% |
| C | 5.60% | 5.62% | 2.37% | 2.37% | 10.00% | 9.88% |
plot(NULL, xlim = range(portfolio_returns$Return) * 100,
ylim = c(0, 0.7),
xlab = "Return (%)", ylab = "Simulated probability",
main = "Monte Carlo Return Distributions")
plot_colors <- c(A = "steelblue", B = "darkorange", C = "darkgreen")
for (p in names(simulated_returns)) {
outcomes <- sort(unique(portfolio_groups[[p]]$Return))
probs <- vapply(outcomes, function(outcome) {
mean(simulated_returns[[p]] == outcome)
}, numeric(1))
points(outcomes * 100, probs, type = "b", pch = 16, col = plot_colors[p])
}
legend("topright", legend = paste("Portfolio", names(plot_colors)),
col = plot_colors, pch = 16, lty = 1, bty = "n")
A loss is defined as a return strictly below zero.
barplot(
height = 100 * simulation_summary$Estimated_Loss_Probability,
names.arg = simulation_summary$Portfolio,
xlab = "Portfolio",
ylab = "Estimated probability of loss (%)",
main = "Monte Carlo Estimated Probability of Loss",
ylim = c(0, max(25, 100 * simulation_summary$Estimated_Loss_Probability + 5))
)
The simulation estimates should be close to the theoretical probabilities: 20% for Portfolio A, 20% for Portfolio B, and 10% for Portfolio C. Small differences are expected because the simulation uses random sampling.
recommendations <- data.frame(
Investor = c("Conservative", "Moderate", "Aggressive"),
Portfolio = c("C", "B", "A")
)
for (i in seq_len(nrow(recommendations))) {
p <- recommendations$Portfolio[i]
row <- portfolio_summary[portfolio_summary$Portfolio == p, ]
cat(
"**", recommendations$Investor[i], " investors — Portfolio ", p, "**\n\n",
"Expected return: ", sprintf("%.2f%%", 100 * row$Expected_Return),
"; standard deviation: ", sprintf("%.2f%%", 100 * row$Standard_Deviation),
"; probability of loss: ", sprintf("%.1f%%", 100 * row$Probability_of_Loss),
".\n\n", sep = ""
)
}
Conservative investors — Portfolio C
Expected return: 5.60%; standard deviation: 2.37%; probability of loss: 10.0%.
Moderate investors — Portfolio B
Expected return: 6.00%; standard deviation: 4.38%; probability of loss: 20.0%.
Aggressive investors — Portfolio A
Expected return: 7.50%; standard deviation: 6.95%; probability of loss: 20.0%.
Portfolio C is most suitable for conservative investors because it has the lowest standard deviation and probability of loss. Portfolio B may suit moderate investors who want a somewhat higher expected return while accepting more variability. Portfolio A may suit aggressive investors seeking the highest expected return and willing to tolerate the greatest variation in outcomes.
These recommendations are based on the supplied one-period return distributions. Real investment decisions should also consider diversification, fees, liquidity needs, investment horizon, and the investor’s ability to withstand losses.
Portfolio A has the highest expected return, Portfolio C has the lowest variability and lowest probability of loss, and Portfolio B occupies an intermediate position. The Monte Carlo simulation provides estimates that can be compared with the analytical results to check that the simulation behaves as expected.