Quantifying Portfolio Risk Using Python: A Deep Dive into Historical Value at Risk (VaR)
Last Updated on October 18, 2025 by Editorial Team
Author(s): Siddharth Mahato
Originally published on Towards AI.
Risk, the unseen current of finance, flows through every investment decision. To grasp the nature of loss is to truly understand the meaning of gain.
This research describes the quantification of VaR using Python where the historical approach is used for a portfolio of large technology stocks that have equal weights.

Chapter I: The Premise of Precision
The world does not pay for guesswork — especially not in business. And so our story began:
To approximate the maximum loss of a ₹10,00,000 portfolio within a day.
In order to utilize historical daily return logs, without distribution assumptions, reflecting the true picture — peaks, troughs, and the surprises in between.
Why this method? Because historical VaR, as a classical library, has real pages of financial memory, untouched by assumed normalcy. In simpler terms, Var using historical method does not captures non-normal distributions, like fat tails and skewness, because it uses actual past return data directly, without fitting it to a theoretical distribution.
Chapter II: Fetching Time’s Signature
I gathered the dataset using the yfinance API — a gateway into the financial history:
# Importing the relevant libraries
import numpy as np
import pandas as pd
import datetime as dt
import seaborn as sns
import matplotlib.pyplot as plt
import yfinance as yf
from scipy.stats import norm
# Picking up three major IT stocks for further analysis
years = 10
endDate = dt.datetime.now()
startDate = endDate - dt.timedelta(days = 365 * years)
tickers = ['AAPL', 'GOOGL', 'MSFT']
data = yf.download(tickers, start = startDate, end = endDate)
Note: As of recent updates, yfinance returns adjusted prices by default (auto_adjust = True). So, no need to slice for ‘Adj Close’.
Like any good chronicle, the data came multi-indexed: by date and ticker-level fields (Close, Open, Volume, etc.)
To focus our gaze, we used:
data = data.xs('Close', axis = 1, level = 0)
Note: Here we used panda’s .xs function which is like a scalpel — surgically extracting the ‘Close’ prices across the ticker columns.
Whenever we are working with a multi-indexed DataFrame, the .xs() function assists in retrieving a cross-section of data along a particular level of the index. It is especially useful when you want to slice data cleanly across one index level, like extracting all ‘Close’ prices from a multi-level column structure.
Chapter III: Returns- The Pulse of the Portfolio
Returns aren’t values alone; they’re the pulse of volatility.
To respect the continuous nature of compounding, we used logarithmic returns:
# Compute log returns
log_returns = np.log(price_df / price_df.shift(1))
log_returns = log_returns.dropna()
print(log_returns)
Why log returns?
- Additivity over time
- Better normalization for statistical modeling
- Handles skewed distributions with more grace than simple returns
This is precisely what we implemented using NumPy in Python, as shown in the code snippet below, bridging mathematical intuition with practical computation.
Chapter IV: The Architecture of Exposure — Portfolio Construction
Risk is not a standalone number; it’s a function of how your capital is deployed. We chose an equally weighted portfolio with a capital base of ₹10,00,000, treating each stock with equal importance:
# Create an equally weighted porftfolio
portfolio = 1000000
weights = np.array([1/len(tickers)] * len(tickers))
print(weights)
Then, we computed the historical portfolio returns by combining individual asset returns and weights:
# Find the X-day historical return
days = 5
range_returns = historical_returns.rolling(window = days).sum()
range_returns = range_returns.dropna()
print(range_returns)
Note: In Python, we used .rolling(window = days).sum() to compute the historical returns over a specified rolling window. This method aggregates returns over the defined number of days (e.g., 5-day period), helping us capture short-term portfolio movements critical for Value at Risk (VaR) estimation.
Chapter 5: The Heart of the Matter — Value at Risk Computation
Using the distribution of 5-day returns, we compute the Value at Risk (VaR) at a 95% confidence level:
#Specifying the confidence interval and computing the VaR
confidence_level = 0.95
VaR = -np.percentile(range_returns, (100 - confidence_level * 100)) * portfolio
print(f"Value at Risk:", VaR)
Interpretation:
For a portfolio of ₹10,00,000, the Value at Risk is ₹49006.55. This implies there’s a 5% chance the portfolio may lose more than this amount over the next 5 trading days.
This numerical threshold is a risk firewall, enabling us to understand potential downside in terms of numbers.
Chapter 6: Visual Storytelling — Plotting the Risk
To make our insights tangible, we plotted the 5-day rolling returns and overlaid the VaR threshold:
# Plotting the VaR
plt.figure(figsize = (5, 5))
plt.hist(range_returns * portfolio, bins = 50, color ='skyblue', edgecolor ='black')
plt.axvline(-VaR, color ='red', linestyle ='--', linewidth = 2, label = f'VaR (95%) = ₹{VaR:,.2f}')
plt.title('Distribution of Portfolio Returns with 95% Value at Risk')
plt.xlabel('Portfolio Daily Return (₹)')
plt.ylabel('Frequency')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()

Interpretation:
The blue curve traces the 5-day rolling returns over the historical period, showing how the portfolio performed week over week. Notice how the red dashed line — the VaR threshold — marks the statistical danger zone. Points below this line highlight periods of higher potential loss. These visual dips help portfolio managers identify stress periods and reallocate risk if needed.
This visualization not only highlights risk zones but provides a powerful tool for decision-makers to gauge and adjust exposure.
Conclusion: Risk as Ratio Decidendi
Just as judicial argument, wherein ratio decidendi becomes the binding substance of law, VaR becomes the definitive logic in financial decision–making. It delineates intuition from evidence and encapsulates the uncertainty of markets into quantifiable insights. Whereas obiter dicta in a court might influence without binding, other metrics such as CVaR or stress testing can be those influential yet non-binding tools in risk management.
This is more than Python scripting — it’s a consideration of where finance, statistics, and storytelling come together into effective risk stories. The ultimate key to unlocking deeper risk narratives still lies ahead — this journey is only the beginning.
This marks the end of this article. However, the pen continues to write though….
Join thousands of data leaders on the AI newsletter. Join over 80,000 subscribers and keep up to date with the latest developments in AI. From research to projects and ideas. If you are building an AI startup, an AI-related product, or a service, we invite you to consider becoming a sponsor.
Published via Towards AI
Take our 90+ lesson From Beginner to Advanced LLM Developer Certification: From choosing a project to deploying a working product this is the most comprehensive and practical LLM course out there!
Towards AI has published Building LLMs for Production—our 470+ page guide to mastering LLMs with practical projects and expert insights!

Discover Your Dream AI Career at Towards AI Jobs
Towards AI has built a jobs board tailored specifically to Machine Learning and Data Science Jobs and Skills. Our software searches for live AI jobs each hour, labels and categorises them and makes them easily searchable. Explore over 40,000 live jobs today with Towards AI Jobs!
Note: Content contains the views of the contributing authors and not Towards AI.