mirror of
https://github.com/RoryLee1/Validus-Risk-Management-Junior-Quant-Case-Study.git
synced 2026-08-13 01:48:05 +00:00
392 KiB
392 KiB
In [1]:
import math as m
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy_financial as npfIn [2]:
GBPHUF = 455.25 #start amount
sigma = 0.093 #this is the annual volatility
mu = 0 #drift
nr = 1000 #number of simulations
T = 5 #time to maturity
n = 5*252 #number of time steps
nu = mu - 0.5 * sigma**2
dt = 1/252
GBPHUF_val = np.zeros((nr,n+1))
epsilon = np.random.randn(nr,n)
GBPHUF_val[:,0] = GBPHUF
for i in range(nr):
for j in range(1,n+1):
GBPHUF_val[i,j] = GBPHUF_val[i,j-1] * m.exp(nu*dt + sigma * dt**0.5 * epsilon[i,j-1])In [3]:
plt.figure()
for i in range(nr):
plt.plot(GBPHUF_val[i,:])
plt.title('Monte Carlo Method simulations - GBM Paths');In [4]:
data = pd.read_excel(r"C:\Users\roryx\Downloads\Junior_Quant_Case_Study_Aug_2025_-_Cashflow_Model.xlsx")In [5]:
dataOut [5]:
| Date | Fund | Cashflow Type | Cashflow Amount (in Local Asset Currecny) | Local Asset Currency | Fund Currency | Base Case IRR | |
|---|---|---|---|---|---|---|---|
| 0 | 2025-08-01 | Validus V | Equity | -100000000 | HUF | GBP | 0.149914 |
| 1 | 2026-08-01 | Validus V | Proceeds | 15000000 | HUF | GBP | NaN |
| 2 | 2027-08-01 | Validus V | Proceeds | 15000000 | HUF | GBP | NaN |
| 3 | 2028-08-01 | Validus V | Proceeds | 15000000 | HUF | GBP | NaN |
| 4 | 2029-08-01 | Validus V | Proceeds | 15000000 | HUF | GBP | NaN |
| 5 | 2030-08-01 | Validus V | Proceeds | 115000000 | HUF | GBP | NaN |
In [6]:
Years = []
for i in range(0,6):
Years.append(i*252)
print(f'Year {i} is after {i*252} Working Days')Year 0 is after 0 Working Days Year 1 is after 252 Working Days Year 2 is after 504 Working Days Year 3 is after 756 Working Days Year 4 is after 1008 Working Days Year 5 is after 1260 Working Days
In [7]:
YearsOut [7]:
[0, 252, 504, 756, 1008, 1260]
In [8]:
GBPHUF_val_aug = GBPHUF_val[:, Years] #These are the simulated spot rate values as at the date of the cashflowIn [9]:
#Converting HUF Cashflows to GBP
GBP_CF = np.zeros_like(GBPHUF_val_aug)
for i in range(6):
GBP_CF[:, i] = data.iloc[i,3]/GBPHUF_val_aug[:,i]In [10]:
#IRR Calculation
irr = np.zeros(len(GBP_CF))
for i in range(len(GBP_CF)):
irr[i] = npf.irr(GBP_CF[i,:])In [11]:
#Finding the percentiles
p5, p50, p95 = np.percentile(irr, [5, 50, 95])
print(f"5th Percentile: {p5}")
print(f"50th Percentile: {p50}")
print(f"95th Percentile: {p95}")5th Percentile: 0.07799262116281874 50th Percentile: 0.15586083188072664 95th Percentile: 0.23716705852217057
In [12]:
#Plotting the distribution
plt.hist(irr, bins=40, color='teal', edgecolor='black')
plt.axvline(p5, color='blue', linestyle='--', label='5th Pctl')
plt.axvline(p50, color='black', linestyle='-', label='50th Pctl')
plt.axvline(p95, color='red', linestyle='--', label='95th Pctl ')
plt.xlabel('Internal Rate of Return')
plt.ylabel('Frequency')
plt.title('Distribution Of IRR Values - Unhedged Portfolio')
plt.legend()
plt.show()In [13]:
S_T = GBPHUF_val_aug[:,4]
K = 455.25
N_HUF = 100000000
N_GBP = N_HUF/K
FinPayOff_HUF = N_GBP * np.maximum(K-S_T, 0) #N_GBP*np.maximum((K-S_T), 0)
FinPayOff_GBP = FinPayOff_HUF/S_T
price_gbp = FinPayOff_GBP.mean()
print(f"The price of our Put Option in GBP is: {price_gbp}")The price of our Put Option in GBP is: 20721.234535433607
In [14]:
#New Cashflows of the Hedged Portfolio
H_GBP_CF = GBP_CF.copy()
H_GBP_CF[:,0] = GBP_CF[:,0] - price_gbp
H_GBP_CF[:,4] = GBP_CF[:,4] + FinPayOff_GBP In [15]:
#IRR Calculation
irr_hp = np.zeros(len(H_GBP_CF))
for i in range(len(H_GBP_CF)):
irr_hp[i] = npf.irr(H_GBP_CF[i,:])In [16]:
hp5, hp50, hp95 = np.percentile(irr_hp, [5, 50, 95])
print(f"5th Percentile of Hedged Portfolio: {hp5:.2f}. 5th Percentile of Unhedged Portfolio: {p5:.2f}.")
print(f"50th Percentile of Hedged Portfolio: {hp50:.2f}. 50th Percentile of Unhedged Portfolio: {p50:.2f}.")
print(f"95th Percentile of Hedged Portfolio: {hp95:.2f}. 95th Percentile of Unhedged Portfolio: {p95:.2f}.")5th Percentile of Hedged Portfolio: 0.05. 5th Percentile of Unhedged Portfolio: 0.08. 50th Percentile of Hedged Portfolio: 0.13. 50th Percentile of Unhedged Portfolio: 0.16. 95th Percentile of Hedged Portfolio: 0.25. 95th Percentile of Unhedged Portfolio: 0.24.
In [17]:
std_dev_hp = np.std(irr_hp)
std_dev_p = np.std(irr)
print(f"The standard deviation of the Hedged Portfolio is: {std_dev_hp:.5f}")
print(f"The standard deviation of the Unhedged Portfolio is: {std_dev_p:.5f}")The standard deviation of the Hedged Portfolio is: 0.06233 The standard deviation of the Unhedged Portfolio is: 0.04870
In [18]:
plt.hist(irr_hp, bins=40, color='teal',alpha=0.5, label='Hedged IRR', edgecolor='black')
plt.hist(irr, bins=40, color='blue',alpha=0.5, label='Unhedged IRR', edgecolor='black')
plt.xlabel('Internal Rate of Return')
plt.ylabel('Frequency')
plt.title('Distribution Of IRR Values - Hedged vs Unhedged Portfolio')
plt.legend()
plt.show()In [21]:
FinPayOffCall_HUF = N_GBP * np.maximum(S_T-K, 0)
FinPayOffCall_GBP = FinPayOffCall_HUF/S_T
pricecall_gbp = FinPayOffCall_GBP.mean()
Call_GBP_CF = GBP_CF.copy()
Call_GBP_CF[:,0] = GBP_CF[:,0] - pricecall_gbp
Call_GBP_CF[:,4] = GBP_CF[:,4] + FinPayOffCall_GBP
irr_call = np.zeros(len(Call_GBP_CF))
for i in range(len(Call_GBP_CF)):
irr_call[i] = npf.irr(Call_GBP_CF[i,:])
plt.hist(irr, bins=40, color='teal',alpha=0.5, label='Unhedged IRR', edgecolor='black')
plt.hist(irr_call, bins=40, color='blue',alpha=0.5, label='Call Hedged IRR', edgecolor='black')
plt.xlabel('Internal Rate of Return')
plt.ylabel('Frequency')
plt.title('Distribution Of IRR Values- Hedged vs Unhedged Portfolio')
plt.legend()
plt.show()In [48]:
hpc5, hpc50, hpc95 = np.percentile(irr_call, [5, 50, 95])
print(f"5th Percentile of Call Hedged Portfolio: {hpc5:.3f}. 5th Percentile of Unhedged Portfolio: {p5:.2f}.")
print(f"50th Percentile of Call Hedged Portfolio: {hpc50:.3f}. 50th Percentile of Unhedged Portfolio: {p50:.2f}.")
print(f"95th Percentile of Call Hedged Portfolio: {hpc95:.3f}. 95th Percentile of Unhedged Portfolio: {p95:.2f}.")5th Percentile of Call Hedged Portfolio: 0.103. 5th Percentile of Unhedged Portfolio: 0.08. 50th Percentile of Call Hedged Portfolio: 0.142. 50th Percentile of Unhedged Portfolio: 0.16. 95th Percentile of Call Hedged Portfolio: 0.219. 95th Percentile of Unhedged Portfolio: 0.24.
In [49]:
std_dev_hpc = np.std(irr_call)
std_dev_p = np.std(irr)
print(f"The standard deviation of the Call Hedged Portfolio is: {std_dev_hpc:.5f}")
print(f"The standard deviation of the Unhedged Portfolio is: {std_dev_p:.5f}")The standard deviation of the Call Hedged Portfolio is: 0.03667 The standard deviation of the Unhedged Portfolio is: 0.04870
In [37]:
GBPHUF_val_2029 = GBPHUF_val[:, 0:1009]
sim, time = GBPHUF_val_2029.shape
N = 115000000
threshold = 70000
margin_call = np.zeros(sim, dtype=bool)
for i in range(sim):
for j in range(time):
mtm = N * (1/GBPHUF_val_2029[i, j] - 1/K)
if mtm < -threshold:
margin_call[i] = True
break
prob_margin_call = margin_call.mean()*100
print(f"The probability of at least one margin call over the life of the trade is {prob_margin_call:.2f}%")The probability of at least one margin call over the life of the trade is 6.70%
In [47]:
MTM = N * (1 / GBPHUF_val_2029 - 1 / K)
n_paths_to_plot = 20
paths = np.random.choice(MTM.shape[0], n_paths_to_plot, replace=False)
plt.figure(figsize=(10, 5))
for i in paths:
if np.any(MTM[i, :] < -70000):
plt.plot(MTM[i, :], color="red", alpha=0.8)
else:
plt.plot(MTM[i, :], color="teal", alpha=0.8)
# margin thresholds
plt.axhline(-70000)
plt.xlabel("Time step")
plt.ylabel("MTM (GBP)")
plt.title("Forward MTM Paths and Margin Call Thresholds")
plt.show()