1. Introduction
Forecasting financial markets is a sophisticated fusion of quantitative precision and global economic nuance. In this quest, the Monte Carlo simulation stands out as a premier statistical instrument, guiding our understanding of future stock prices.
Named after the famous Monte Carlo Casino in Monaco, this method doesn't bank on luck but is rooted in rigorous probabilistic modelling. Imagine orchestrating thousands of experiments in a controlled environment, with each one unfolding a different story of stock price movement. That's the power of the Monte Carlo simulation.
In this article, our approach is twofold:
- We delve into historical and implied volatilities β their distinctions, implications, and relevance
- We employ the Monte Carlo simulation using both volatility measures to extrapolate potential future paths
Central to our analysis will be the derivation of probabilities. By observing the simulation's multitude of potential outcomes, we'll compute the likelihood of the stock price landing within specific ranges.
π¬ Get the full Google Colab notebook free at AlgoEdge Insights
2. Volatility Analysis
2.1 Historical Volatility
Historical volatility quantifies the variability of a stock's returns over a past period. It gives us an idea of how much a stock's price deviated from its average value during that timeframe.
To annualise: HV_annual = HV_daily Γ β252
This formula is key when you want to estimate volatility over a given horizon.
2.2 Implied Volatility
The implied volatility can be extracted from the Black-Scholes option pricing model:
Where:
- C = call option price
- Sβ = current stock price
- X = strike price
- t = time to expiration
- r = risk-free rate
- q = dividend yield
- N() = CDF of the standard normal distribution
To extract implied volatility (Ο), one would reverse engineer the model using the observed market option price. Many traders prefer sourcing implied volatility directly from platforms like Yahoo Finance or their brokers.
2.3 Historical vs. Implied Volatility
| Historical | Implied | |
|---|---|---|
| Perspective | Backward-looking | Forward-looking |
| Application | Risk assessment from past behavior | Options pricing, market sentiment |
| Determination | Statistical β from past price data | Extracted from market option prices |
3. Monte Carlo Simulation for Price Forecasting
The Monte Carlo simulation leverages probability theory to model the evolution of stock prices based on two key components:
3.1 Drift and Volatility
Drift (deterministic component):
drift = (ΞΌ - ΟΒ²/2) Γ Ξt
Volatility term (stochastic component):
vol_term = Ο Γ Ξ΅ Γ βΞt
Where Ξ΅ is a random value drawn from a standard normal distribution.
3.2 Stock Price Path Simulation
Starting from Sβ, we compute successive prices using:
S(t) = S(t-1) Γ exp(drift + vol_term)
Running thousands of simulations gives a spectrum of possible outcomes β a probability distribution for future stock prices.
4. Python Implementation
4.1 Setup
import numpy as np
import pandas as pd
import yfinance as yf
import matplotlib.pyplot as plt
from datetime import datetime, timedelta
from scipy.stats import norm
4.2 Download Historical Data
symbol = "ASML.AS"
start_date = "2020-01-01"
end_date = "2023-12-30"
df = yf.download(symbol, start=start_date, end=end_date)
4.3 Compute Daily Returns
log_returns = np.log(df['Close'] / df['Close'].shift(1))
π¬ Get the full Google Colab notebook free at AlgoEdge Insights
4.4 Simulation Parameters
days_to_forecast = 20
num_simulations = 10000
dt = 1 # 1 trading day
# Price thresholds
threshold1 = 650 # Upper threshold
threshold2 = 540 # Lower threshold
# BSM implied volatility
volatility_bsm = 0.29 # 29% annualized
print("BSM Implied Volatility:", volatility_bsm)
4.5 Run the Simulations
def run_simulation(volatility, dt, annualized=False):
simulated_prices = np.zeros((days_to_forecast, num_simulations))
simulated_prices[0] = df['Close'][-1]
if annualized:
volatility = volatility / np.sqrt(252)
for t in range(1, days_to_forecast):
random_walk = np.random.normal(
loc=log_returns.mean() * dt,
scale=volatility * np.sqrt(dt),
size=num_simulations
)
simulated_prices[t] = simulated_prices[t - 1] * np.exp(random_walk)
return simulated_prices
simulated_prices_historical = run_simulation(log_returns.std(), dt)
simulated_prices_bsm = run_simulation(volatility_bsm, dt, annualized=True)
print("Historical Volatility:", log_returns.std())
4.6 Visualization β Confidence Cones
fig, axs = plt.subplots(1, 2, figsize=(30, 7))
for simulated_prices, ax, title in zip(
[simulated_prices_historical, simulated_prices_bsm],
axs,
['Historical Volatility', 'BSM Volatility']
):
mean_price_path = np.mean(simulated_prices, axis=1)
median_price_path = np.median(simulated_prices, axis=1)
lower_bound_68 = np.percentile(simulated_prices, 16, axis=1)
upper_bound_68 = np.percentile(simulated_prices, 84, axis=1)
lower_bound_95 = np.percentile(simulated_prices, 2.5, axis=1)
upper_bound_95 = np.percentile(simulated_prices, 97.5, axis=1)
ax.plot(simulated_prices, color='lightgray', alpha=0.1)
ax.plot(mean_price_path, color='black', label='Mean Price Path')
ax.plot(median_price_path, color='blue', label='Median Price Path')
ax.plot(lower_bound_68, color='green', linestyle='--', label='68% CI (Lower)')
ax.plot(upper_bound_68, color='green', linestyle='--', label='68% CI (Upper)')
ax.plot(lower_bound_95, color='blue', linestyle='--', label='95% CI (Lower)')
ax.plot(upper_bound_95, color='red', linestyle='--', label='95% CI (Upper)')
ax.axhline(y=threshold1, color='purple', linestyle='--', alpha=0.25, label='Threshold 1')
ax.axhline(y=threshold2, color='orange', linestyle='--', alpha=0.25, label='Threshold 2')
# Probability annotations
above_prob = (simulated_prices[-1] > threshold1).sum() / num_simulations
below_prob = (simulated_prices[-1] < threshold2).sum() / num_simulations
between_prob = 1 - above_prob - below_prob
props = dict(boxstyle='round', facecolor='wheat', alpha=0.5)
ax.text(0.02, 0.05,
f'P(>{threshold1:.2f}): {above_prob:.2%}\n'
f'P(<{threshold2:.2f}): {below_prob:.2%}\n'
f'P({threshold2:.2f}-{threshold1:.2f}): {between_prob:.2%}',
transform=ax.transAxes, fontsize=15,
verticalalignment='bottom', bbox=props)
ax.set_xlabel('Days')
ax.set_ylabel('Stock Price')
ax.set_title(f'Monte Carlo Confidence Cone for {symbol} using {title}', fontsize=25)
ax.legend(fontsize=15)
plt.show()
4.7 Derived Probabilities
From the simulation results, we calculate three key probabilities at the end of the forecast period:
above_threshold1_prob = (simulated_prices[-1] > threshold1).sum() / num_simulations
below_threshold2_prob = (simulated_prices[-1] < threshold2).sum() / num_simulations
between_thresholds_prob = 1 - above_threshold1_prob - below_threshold2_prob
These probabilities answer:
- P(> threshold1) β likelihood the stock exceeds a favorable level
- P(< threshold2) β risk of dropping below a concerning level
- P(between) β probability of staying within the defined range
5. Results: Historical vs. Implied Volatility
Our simulation produced two sets of potential future price paths:
Historical Volatility: Derived from past price movements β a projection rooted in historical behavior. Reliable when market conditions remain consistent.
BSM Implied Volatility: Represents market expectations β more relevant when future events or market regime changes are anticipated.
Interpreting the comparison:
- Distributions align closely β consensus between past behavior and market expectations
- Distributions diverge significantly β external factors or events not captured by historical data
6. Concluding Remarks
Monte Carlo simulation empowers investors to peer into a range of potential tomorrows. Key takeaways:
Appreciate uncertainty β the wide range of simulated paths captures the inherent unpredictability of stock prices, emphasizing the need for risk management.
Informed decision-making β use the probability zones to set stop-loss orders, determine entry/exit points, or adjust portfolio risk.
Use both volatility measures β historical vol shows what happened; implied vol shows what the market expects. Together, they give a more complete picture.
This is a step away from mere speculation and a leap towards informed, data-driven decision-making.
Get the Full Notebook
Every week I publish one Python quant finance tool like this β fully coded, backtested, and ready to run in Google Colab for free.
Subscribe free: AlgoEdge Insights

Top comments (1)
Clean implementation. The comparison between historical and BSM implied vol is really useful for anyone building quantitative tools.
One thing I have been experimenting with in my own work (tracking institutional 13F filings) is using regime-switching models to dynamically blend historical and implied vol based on market conditions. During earnings seasons or major macro events, implied vol tends to diverge significantly from historical, and a static choice between the two can be misleading.
Have you tried extending this to portfolio-level simulations with correlated assets? The correlation structure between holdings is where things get really interesting -- especially when you want to model how institutional portfolios (the kind visible in 13F filings) behave under stress scenarios.