working cva computation using quantlib

This commit is contained in:
local
2026-02-08 13:40:35 +00:00
parent d484f9c236
commit 8b5eb8797f
6 changed files with 1337 additions and 0 deletions

109
python/cvatesting/main.py Normal file
View File

@@ -0,0 +1,109 @@
import QuantLib as ql
import numpy as np
import matplotlib.pyplot as plt
# --- UNIT 1: Instrument Setup ---
def create_30y_swap(today, rate_quote):
ql.Settings.instance().evaluationDate = today
# We use a Relinkable handle to swap curves per path/time-step
yield_handle = ql.RelinkableYieldTermStructureHandle()
yield_handle.linkTo(ql.FlatForward(today, rate_quote, ql.Actual365Fixed()))
calendar = ql.TARGET()
settle_date = calendar.advance(today, 2, ql.Days)
maturity_date = calendar.advance(settle_date, 30, ql.Years)
index = ql.Euribor6M(yield_handle)
fixed_schedule = ql.Schedule(settle_date, maturity_date, ql.Period(ql.Annual),
calendar, ql.ModifiedFollowing, ql.ModifiedFollowing,
ql.DateGeneration.Forward, False)
float_schedule = ql.Schedule(settle_date, maturity_date, ql.Period(ql.Semiannual),
calendar, ql.ModifiedFollowing, ql.ModifiedFollowing,
ql.DateGeneration.Forward, False)
swap = ql.VanillaSwap(ql.VanillaSwap.Payer, 1e6, fixed_schedule, rate_quote,
ql.Thirty360(ql.Thirty360.BondBasis), float_schedule,
index, 0.0, ql.Actual360())
# Pre-calculate all fixing dates needed for the life of the swap
fixing_dates = [index.fixingDate(d) for d in float_schedule]
return swap, yield_handle, index, fixing_dates
# --- UNIT 2: The Simulation Loop ---
def run_simulation(n_paths=50):
today = ql.Date(27, 1, 2026)
swap, yield_handle, index, fixing_dates = create_30y_swap(today, 0.03)
# HW Parameters: a=mean reversion, sigma=vol
model = ql.HullWhite(yield_handle, 0.01, 0.01)
process = ql.HullWhiteProcess(yield_handle, 0.01, 0.01)
times = np.arange(0, 31, 1.0) # Annual buckets
grid = ql.TimeGrid(times)
rng = ql.GaussianRandomSequenceGenerator(ql.UniformRandomSequenceGenerator(
len(grid)-1, ql.UniformRandomGenerator()))
seq = ql.GaussianMultiPathGenerator(process, grid, rng, False)
npv_matrix = np.zeros((n_paths, len(times)))
for i in range(n_paths):
path = seq.next().value()[0]
# 1. Clear previous path's fixings to avoid data pollution
ql.IndexManager.instance().clearHistories()
for j, t in enumerate(times):
if t >= 30: continue
eval_date = ql.TARGET().advance(today, int(t), ql.Years)
ql.Settings.instance().evaluationDate = eval_date
# 2. Update the curve with simulated short rate rt
rt = path[j]
# Use pillars up to 35Y to avoid extrapolation crashes
tenors = [0, 1, 2, 5, 10, 20, 30, 35]
dates = [ql.TARGET().advance(eval_date, y, ql.Years) for y in tenors]
discounts = [model.discountBond(t, t + y, rt) for y in tenors]
sim_curve = ql.DiscountCurve(dates, discounts, ql.Actual365Fixed())
sim_curve.enableExtrapolation()
yield_handle.linkTo(sim_curve)
# 3. MANUAL FIXING INJECTION
# For every fixing date that has passed or is 'today'
for fd in fixing_dates:
if fd <= eval_date:
# Direct manual calculation to avoid index.fixing() error
# We calculate the forward rate for the 6M period starting at fd
start_date = fd
end_date = ql.TARGET().advance(start_date, 6, ql.Months)
# Manual Euribor rate formula: (P1/P2 - 1) / dt
p1 = sim_curve.discount(start_date)
p2 = sim_curve.discount(end_date)
dt = ql.Actual360().yearFraction(start_date, end_date)
fwd_rate = (p1 / p2 - 1.0) / dt
index.addFixing(fd, fwd_rate, True) # Force overwrite
# 4. Valuation
swap.setPricingEngine(ql.DiscountingSwapEngine(yield_handle))
npv_matrix[i, j] = max(swap.NPV(), 0)
ql.Settings.instance().evaluationDate = today
return times, npv_matrix
# --- UNIT 3: Visualization ---
times, npv_matrix = run_simulation(n_paths=100)
ee = np.mean(npv_matrix, axis=0)
plt.figure(figsize=(10, 5))
plt.plot(times, ee, lw=2, label="Expected Exposure (EE)")
plt.fill_between(times, ee, alpha=0.3)
plt.title("30Y Swap EE Profile - Hull White")
plt.xlabel("Years"); plt.ylabel("Exposure"); plt.grid(True); plt.legend()
plt.show()