working cva computation using quantlib
This commit is contained in:
109
python/cvatesting/main.py
Normal file
109
python/cvatesting/main.py
Normal 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()
|
||||
Reference in New Issue
Block a user