Coverage for rivapy / marketdata / bootstrapping.py: 94%
216 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-27 14:36 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-11-27 14:36 +0000
1# 2025.07.23 Hans Nguyen
2# Boostrapping in rivapy indepedent of pyvacon
5from rivapy.marketdata._logger import logger
8##########
9# Modules
10import copy
11from datetime import datetime, date
12from dateutil.relativedelta import relativedelta
13from typing import Union as _Union, List as _List
15# from rivapy.instruments.specifications import DepositSpecification,
16from rivapy.instruments.deposit_specifications import DepositSpecification
17from rivapy.instruments.fra_specifications import ForwardRateAgreementSpecification
18from rivapy.instruments.ir_swap_specification import (
19 InterestRateSwapSpecification,
20 IrFixedLegSpecification,
21 IrFloatLegSpecification,
22 IrSwapLegSpecification,
23 InterestRateBasisSwapSpecification,
24)
25from rivapy.marketdata import DiscountCurve
26from rivapy.marketdata.fixing_table import FixingTable
27from rivapy.tools.enums import DayCounterType, RollConvention, RollRule, InterpolationType, ExtrapolationType, Instrument
28from rivapy.tools.datetools import DayCounter
30from scipy.optimize import brentq
33# import quote calculators # TODO SUBJECT TO CHANGE based on architecture
34from rivapy.pricing.deposit_pricing import DepositPricer
35from rivapy.pricing.fra_pricing import ForwardRateAgreementPricer
36from rivapy.pricing.interest_rate_swap_pricing import InterestRateSwapPricer
39##########
40# Classes
43######################################################
44##########
45# Functions
46def bootstrap_curve(
47 ref_date: _Union[date, datetime],
48 curve_id: str,
49 day_count_convention: _Union[DayCounterType, str],
50 instruments: _List,
51 quotes: _List,
52 curves: dict = None,
53 # discount_curve: DiscountCurve = None,
54 # basis_curve: DiscountCurve = None,
55 interpolation_type: InterpolationType = InterpolationType.LINEAR,
56 extrapolation_type: ExtrapolationType = ExtrapolationType.LINEAR,
57 tolerance: float = 1.0e-6,
58 max_iterations: int = 10000,
59) -> DiscountCurve:
60 """
62 Args:
63 ref_date (_Union[date, datetime]): the reference for the new curve
64 curve_id (str): Id for the new Curve
65 day_count_convention (_Union[DayCounterType, str]): daycounter for the new curve
66 instruments (_List): instrument specifications that are used in the calibration (deposits, FRAs, and swaps allowed atm)
67 quotes (_List): the rate quotes for the instruments (deposit rates, FRAs and swap rates)
68 curves (dict): curves to be used during bootstrapping such as discount curve and forward curve if given. Defaults to Empty
69 interpolation_type (InterpolationType): interpolation method to be used by the final curve. defaults to LINEAR
70 extrapolation_type (ExtrapolationType): extrapolation method to be used by the final curve. defaults to LINEAR
71 tolerance (float): tolerance value used in refinement of the zero rates
72 max_iterations (int): the maximim number of iterations (after that the bootstrapper fails)
75 Returns:
76 DiscountCurve: bootstrapped discount curve
77 """
78 # print("USING BOOTSTRAPPER V1----------------########################------------") # DEBUG and REMOVE
79 logger.info("Starting bootstrapper.")
81 # Sanity checks:
82 logger.info("Input sanity checks")
83 assert len(instruments) == len(quotes), "Number of quotes does not equal number of instruments."
84 # TODO implement more input quality checks:
85 # curves given of correct type that match instrument type - or will this be done in the "market container" class?
87 logger.info("Curve dictionary import")
88 if curves == None:
89 curves = {}
90 logger.info("* curves dictionary is empty, will bootstrap single discount curve")
91 else:
92 logger.info("* curves dictionary provided, will bootstrap forward curve")
94 #############################################################
95 # initialize:
96 logger.info("discount curve value init")
98 yc_dates = [ref_date]
99 dfs = [1.0]
101 #############################################################
102 # NEW FEATURE: Handle curve extension via curves["initial_curve"] - if provided
103 # useful for using e.g. TBS to extend existing curves
104 initial_curve = None
105 initial_dates_set = set()
106 if curves is not None and "initial_curve" in curves and curves["initial_curve"] is not None:
107 initial_curve = curves["initial_curve"]
108 if not isinstance(initial_curve, DiscountCurve):
109 raise Exception("initial_curve must be a DiscountCurve instance")
110 ic_dates = initial_curve.get_dates()
111 ic_dfs = initial_curve.get_df()
112 if len(ic_dates) == 0 or len(ic_dfs) == 0:
113 raise Exception("initial_curve must contain at least one date and discount factor")
114 for d, df in zip(ic_dates, ic_dfs):
115 if d <= ref_date:
116 continue
117 yc_dates.append(d)
118 dfs.append(df)
119 initial_dates_set.add(d)
120 logger.info(f"Prepopulated curve with {len(initial_dates_set)} initial points from initial_curve.")
122 # init DCC
123 dcc = DayCounter(day_count_convention)
124 if isinstance(day_count_convention, str): # normalizes type
125 day_count_convention = DayCounterType(day_count_convention)
127 #############################################################
128 # Sort instruments # Obtain dates
129 # check for instruments with duplicate end dates # for now, through exceptiion if there is
130 #
131 logger.info("Duplicate instrument date filter")
132 instruments_by_date = {}
133 for i, inst in enumerate(instruments):
134 end_date = inst.get_end_date()
135 if end_date in instruments_by_date:
136 raise Exception(f"Duplicate expiry date found: {end_date}")
137 instruments_by_date[end_date] = (quotes[i], inst)
139 if initial_dates_set:
140 filtered_instruments_by_date = {}
141 for d, val in instruments_by_date.items():
142 if d in initial_dates_set:
143 logger.info(f"Skipping instrument at {d} because it exists in initial_curve.")
144 continue
145 filtered_instruments_by_date[d] = val
146 instruments_by_date = filtered_instruments_by_date
148 #############################################################
149 # Determine instrument types provided
150 logger.info("Determine instrument types provided")
151 ins_types = set(inst.ins_type() for inst in instruments)
152 has_fra = Instrument.FRA in ins_types
153 has_irs = Instrument.IRS in ins_types
154 has_bs = Instrument.BS in ins_types
155 has_deposit = Instrument.DEPOSIT in ins_types
157 # Determine single vs multi-curve bootstrap
158 if "discount_curve" not in curves:
159 # Single-curve bootstrap: create discount curve from instruments
160 flag_multi_curve = False
161 curves["discount_curve"] = DiscountCurve(
162 "bootstrapped_discount",
163 ref_date,
164 yc_dates,
165 dfs,
166 interpolation_type,
167 extrapolation_type,
168 day_count_convention,
169 )
170 # Forward curve for IRS = discount curve (if any IRS present)
171 flag_irs_bootstrapped_as_fwd = has_irs or has_bs
172 if flag_irs_bootstrapped_as_fwd:
173 curves["fixing_curve"] = curves["discount_curve"]
175 else:
176 # Multi-curve bootstrap: discount curve is provided, we bootstrap forward curve
177 flag_multi_curve = True
179 # FRAs and IRS will build forward curve
180 if "fixing_curve" not in curves:
181 # Create empty forward curve placeholder
182 curves["fixing_curve"] = DiscountCurve(
183 "bootstrapped_forward",
184 ref_date,
185 yc_dates,
186 dfs, # optionally start with empty DFs
187 interpolation_type,
188 extrapolation_type,
189 day_count_convention,
190 )
191 flag_irs_bootstrapped_as_fwd = False
193 # Sanity checks
194 if has_deposit and flag_multi_curve:
195 raise Exception("Deposits cannot be used in multi-curve bootstrapping")
197 # Logging
198 logger.info(f"Bootstrap mode: {'multi-curve' if flag_multi_curve else 'single-curve'}")
199 logger.info(f"Instruments present: {ins_types}")
200 logger.info(f"IRS uses bootstrapped curve as forward: {flag_irs_bootstrapped_as_fwd}")
202 #############################################################
203 # # start with loglinear interpolation to obtain good initial values for all dates
205 lower = 1.0e-5 # initial bracket values for brentq
206 upper = 5.0
208 logger.info("Sort instruments by date and start bootstrapping")
209 for end_date in sorted(instruments_by_date):
210 # If end_date already exists (from initial_curve), skip solving it
211 # if end_date in yc_dates:
212 # logger.info(f"Skipping instrument at {end_date}: already present in initial_curve.")
213 # continue
214 quote, inst = instruments_by_date[end_date] # use the market quote to compare with brentq
215 yc_dates.append(end_date) # next date
216 prev_df = dfs[-1] # previous discount factor for bracket search
217 dfs.append(prev_df) # append a dummy value for the next date
219 # arguments to be passed to the error function for the brentq root solver
220 ARGS = (
221 -1, # since we will look at the latest addition to our discount curve.
222 dfs,
223 yc_dates,
224 inst,
225 ref_date,
226 quote,
227 curves,
228 InterpolationType.LINEAR_LOG,
229 ExtrapolationType.LINEAR_LOG,
230 day_count_convention,
231 flag_irs_bootstrapped_as_fwd,
232 flag_multi_curve,
233 )
235 try:
237 # solution = brentq(error_fn, lower, upper, ARGS, xtol=1e-6)
238 # dfs[-1] = solution
239 # logger.debug(f"Bootstrapped DF for {end_date}: {solution} for {inst.ins_type()}")
241 # - DEBUG 11.2025
242 # just before calling find_bracket for the failing end_date
243 # print("INSIDE LOOP FOR BOOTSTRAP")
244 # print("---- DEBUG START for end_date:", end_date, "quote(raw):", quote)
245 # print("prev_df (guess):", prev_df)
247 # # Evaluate error_fn at a few DF points (inside realistic DF support (1e-8, 1.0]))
248 # test_dfs = [max(1e-10, prev_df * 0.5), max(1e-10, prev_df * 0.9), min(0.9999999, prev_df * 1.0), min(0.9999999, prev_df * 1.1)]
249 # for td in test_dfs:
250 # try:
251 # val = error_fn(
252 # td,
253 # -1,
254 # dfs,
255 # yc_dates,
256 # inst,
257 # ref_date,
258 # quote,
259 # curves,
260 # # InterpolationType.HAGAN_DF,
261 # # ExtrapolationType.CONSTANT_DF,
262 # InterpolationType.LINEAR_LOG,
263 # ExtrapolationType.LINEAR_LOG,
264 # day_count_convention,
265 # flag_irs_bootstrapped_as_fwd,
266 # flag_multi_curve,
267 # )
268 # except Exception as e:
269 # val = f"EXC:{e}"
270 # print(f"error_fn({td:.12f}) = {val}")
272 # Also quickly check the sign/units of quote here:
273 # print("Raw quote value (from market):", quote, "— are these bps? If so, convert: quote = quote*1e-4")
274 # -
276 lower, upper = find_bracket(error_fn, prev_df, ARGS)
277 # lower, upper = prev_df * 0.8, prev_df * 1.2 # this is not good enouhg to work for all cases c.f. above
279 logger.debug(f"Finding lower: {lower} and upper: {upper} bracket for root finding")
281 # - DEBUG 11.2025
282 # print(
283 # f"-----------------------------------------[BOOTSTRAP] Solving for DF of {end_date}, initial guess: {prev_df}, market quote: {quote}"
284 # )
285 # # - DEBUG 11.2025
287 solution, result = brentq(
288 error_fn,
289 lower,
290 upper,
291 args=ARGS,
292 xtol=1e-6,
293 full_output=True, # <--- enables access to iteration info
294 disp=True, # optional: prints if solver fails
295 )
296 dfs[-1] = solution
297 # Log detailed solver diagnostics
298 logger.debug(
299 f"Bootstrapped DF for {end_date}: {solution:.10f} "
300 f"for {inst.ins_type()} | "
301 f"iterations={result.iterations}, "
302 f"function_calls={result.function_calls}, "
303 f"converged={result.converged}"
304 )
306 # - DEBUG 11.2025
307 # print(f"-----------------------------------------[BOOTSTRAP] Solved DF({end_date}) = {solution}")
309 except Exception as e:
310 raise Exception(f"Initial bootstrap failed at {end_date}: {str(e)}")
312 # In principle, this will have produced a curve. It can be improved upon with refinement
313 logger.info("Initial curve produced")
314 #############################################################
315 # Iterative refinement with real interpolator
316 # this is to improve the values for the whole curve, as each subsequent point is dependant on the previous ones
317 # check for convergence: max change in zero rate estimate must be below tolerance.
318 # max_diff = float("inf")
319 logger.info("Iterative refinement step")
320 max_diff = 0.0
321 iteration = 0
322 # --- NEW: map end_date to correct index in dfs --- for the case of extension via initial_curve creating offset
323 end_date_to_index = {d: yc_dates.index(d) for d in yc_dates if d in instruments_by_date}
324 while iteration < max_iterations and (max_diff > tolerance or iteration == 0):
326 total_evals = 0 #
328 # for i, end_date in enumerate(sorted(instruments_by_date), start=1): # iterate through all end dates
330 # --- Use only end_dates from instruments_by_date, get correct dfs index ---
331 for end_date in sorted(instruments_by_date): # iterate through all end dates
332 i = end_date_to_index[end_date] # <--- CHANGED: dynamically compute index instead of enumerate
333 quote, inst = instruments_by_date[end_date] # use the quote to compare with brentq
335 ARGS = (
336 i,
337 dfs, # At this stage, these are all the solved for discount factors
338 yc_dates, # At this stage, this is the full list of dates of the discount curve
339 inst,
340 ref_date,
341 quote,
342 curves,
343 interpolation_type,
344 extrapolation_type,
345 day_count_convention,
346 flag_irs_bootstrapped_as_fwd,
347 flag_multi_curve,
348 )
350 try:
351 # used to determine the tolerance for brentq - scaled by discount factor and maturity and a heuristic 10% ontop to keep from over fitting
352 tol_brent = dfs[i] * tolerance * dcc.yf(ref_date, end_date) * 0.1
353 # print("------------------------refinement tolerance:")
354 # print(f"{i} DF:{dfs[i]} * {tolerance} * {dcc.yf(ref_date, end_date)} * 0.1 = {tol_brent}")
355 dfs[i] = brentq(error_fn, 0.00001, 5.0, ARGS, xtol=tol_brent)
356 total_evals += 1
357 logger.debug(f" refinement DF for {end_date}: {dfs[i]} for {inst.ins_type()} with total evals: {total_evals}")
359 except Exception as e:
360 raise Exception(f"Refinement failed at {end_date}: {str(e)} total evals: {total_evals}")
362 max_diff = 0.0
363 # Convergence check
364 # for i, end_date in enumerate(sorted(instruments_by_date), start=1):
365 for end_date in sorted(instruments_by_date):
366 i = end_date_to_index[end_date] # <--- CHANGED: use mapped index
368 # calculate derivative dq/dr using finite differences
369 # (q=quote, r=zero rate)
370 quote, inst = instruments_by_date[end_date]
371 yc = DiscountCurve("dummy_id", ref_date, yc_dates, dfs, interpolation_type, extrapolation_type, day_count_convention)
373 # Multi-curve logic possible logic and single curve
374 bootstrap_context = {} # only need to do one time for base and epsilon
375 if flag_multi_curve:
376 # This is a forward curve — use Given discount curve for discounting
377 bootstrap_context["flag_multi_curve"] = True
378 curves["fixing_curve"] = yc
379 # Keep discount_curve unchanged
380 else:
381 # Single-curve: updating discount curve itself
382 curves["discount_curve"] = yc
383 if flag_irs_bootstrapped_as_fwd: # if it is an irs instrument that needs the forward curve as well as it was not provided
384 curves["fixing_curve"] = yc
386 q_model = get_quote(ref_date, inst, curves, bootstrap_context) # this curves dict needs to have the updated YC
388 epsilon = 1e-6
389 dfs_perturbed = dfs.copy()
390 dfs_perturbed[i] += epsilon # perturb only at position = i
391 yc_perturbed = DiscountCurve(
392 "dummy_id_perturbed", ref_date, yc_dates, dfs_perturbed, interpolation_type, extrapolation_type, day_count_convention
393 )
395 # Multi-curve and single curve flag logic
396 if flag_multi_curve:
397 # This is a forward curve — use Given discount curve for discounting
398 curves["fixing_curve"] = yc_perturbed
399 # Keep discount_curve unchanged
400 else:
401 # Single-curve: updating discount curve itself
402 curves["discount_curve"] = yc_perturbed
403 if flag_irs_bootstrapped_as_fwd: # if it is an irs instrument that needs the forward curve as well as it was not provided
404 curves["fixing_curve"] = yc_perturbed
406 q_model_eps = get_quote(ref_date, inst, curves, bootstrap_context)
408 dq = (q_model_eps - q_model) / epsilon
409 # print(f"dq: {dq}")
410 # print(f"yf: {dcc.yf(ref_date, end_date)}")
411 # print(f"df: {dfs[i]}")
412 dr = abs((quote - q_model) / (dq * dcc.yf(ref_date, end_date) * dfs[i]))
413 max_diff = max(max_diff, dr)
415 iteration += 1
417 if max_diff > tolerance:
418 raise Exception("Bootstrapping did not converge within tolerance.")
420 # TODO adding 150Y pillar to avoid explicit extrapolation???
422 # create final discount curve
423 logger.info("Curve Complete and output")
424 curve = DiscountCurve(
425 id=curve_id,
426 refdate=ref_date,
427 dates=yc_dates, # populate with correct dates
428 df=dfs, # populated with corresponding discount factors
429 interpolation=interpolation_type,
430 extrapolation=extrapolation_type,
431 daycounter=day_count_convention,
432 )
434 return curve
437# Compute Error - This method computes the diff between market quote and candidate
438def error_fn(
439 df_val: float,
440 index: int,
441 dfs: _List,
442 yc_dates: _List,
443 instrument_spec: _Union[DepositSpecification, ForwardRateAgreementSpecification, InterestRateSwapSpecification],
444 ref_date: _Union[date, datetime],
445 ref_quote: float,
446 curves: dict, # or should it be dictionary?
447 interpolation_type: InterpolationType,
448 extrapolation_type: ExtrapolationType,
449 day_count_convention: DayCounterType = DayCounterType.ACT360,
450 flag_irs_bootstrapped_as_fwd: bool = False,
451 flag_multi_curve: bool = False,
452):
453 """Error function used for the bootstrapper using a brentq solver.
454 Returns the differnce between an input target value and calculated
455 model value.
457 Given a list of corresponding dates and discount factors, create a disount curve object
458 and update the curve dictionary necessary.
460 Pass relevant instrument information in order to calculate the fair rate given the current
461 curve data.
463 #TODO think about how to IMPROVE implementation in the case where forward curve is the same as discount curve
466 Args:
467 df_val (float): discount factor value used as guess for next value of the bootstrapped discount curve
468 index (int): list index of where to insert df_val. usually -1 is passed to ensure it is the last entry
469 dfs (_List): list of predetermined discount factors
470 yc_dates (_List): corresponding datetime objects
471 instrument_spec (): instrument specific data
472 ref_date (_Union[date, datetime]): reference date
473 ref_quote (float): target quote to compare to
474 curves (dict): dictionary of relevant curve data
475 interpolation_type (InterpolationType): the interpolation method to be used by the curves
476 extrapolation_type (ExtrapolationType): the extrapolation method to be used by the curves
477 day_count_convention: day coutn convention to be used for the dummy curve built
478 flag_irs_bootstrapped_as_fwd (bool): Flag to trigger if fixing curve is the same as discount curve
479 flag_multi_curve (bool): Flag to trigger if multi-curve bootstrapping is there
481 Returns:
482 float: difference between target quote and calculated quote
483 """
484 df_tmp = copy.deepcopy(dfs)
485 df_tmp[index] = df_val
486 # here reference date is used as placeholder
487 yc = DiscountCurve("bootstrappedYC", ref_date, yc_dates, df_tmp, interpolation_type, extrapolation_type, day_count_convention)
488 curves_copy = copy.deepcopy(curves)
489 bootstrap_context = {"flag_multi_curve": flag_multi_curve}
490 # Multi-curve logic possible logic and ssingle curve
491 if flag_multi_curve:
492 # This is a forward curve — use Given discount curve for discounting
493 curves_copy["fixing_curve"] = yc
494 # Keep discount_curve unchanged
495 # print("UPDATING ONLY FIXING CURVE IN MULTI CURVE BOOTSTRAP")
496 else:
497 # Single-curve: updating discount curve itself
498 curves_copy["discount_curve"] = yc
499 if flag_irs_bootstrapped_as_fwd: # if it is an irs instrument that needs the forward curve as well or TBS
500 curves_copy["fixing_curve"] = yc
502 calc_quote = get_quote(ref_date, instrument_spec, curves_copy, bootstrap_context)
504 # # DEBUG statement
505 # print("----------------")
506 # print("Error function trial curve")
507 # print(yc.get_df())
508 # print("----------------")
509 # print(f"using {df_val} -> calc_quote: {calc_quote} - ref_quote: {ref_quote} = {calc_quote - ref_quote}")
511 # inside error_fn, after constructing yc and curves_copy and computing calc_quote
512 # compute model_residual = calc_quote - ref_quote or whichever sign convention you use
513 # print(f"[DEBUG error_fn] df_val={df_val:.12f}, calc_quote={calc_quote}, ref_quote={ref_quote}, residual={calc_quote - ref_quote}")
514 # optionally print underlying leg PVs (you can return them from compute_basis_spread or log inside).
516 return calc_quote - ref_quote
519def find_bracket(error_fn, guess, args, expand=2.0, max_tries=10, min_lower=1e-8, max_upper=10.0):
520 """
521 Tries to find a [lower, upper] bracket where error_fn(lower) and error_fn(upper)
522 have opposite signs, indicating a root lies between them.
524 Parameters
525 ----------
526 error_fn : callable
527 Your pricing error function (same as passed to brentq).
528 guess : float
529 A rough estimate for the root (e.g., previous DF).
530 args : tuple
531 Extra arguments passed to error_fn.
532 expand : float
533 Factor by which to widen the bracket each iteration.
534 max_tries : int
535 Maximum number of expansions before giving up.
536 min_lower, max_upper : float
537 Hard limits to keep brackets within safe numeric bounds.
538 """
539 lower = max(guess * 0.8, min_lower)
540 upper = min(guess * 1.2, max_upper)
542 f_lower = error_fn(lower, *args)
543 f_upper = error_fn(upper, *args)
545 tries = 0
546 while f_lower * f_upper > 0 and tries < max_tries:
547 # Expand symmetrically outward
548 lower = max(lower / expand, min_lower)
549 upper = min(upper * expand, max_upper)
550 f_lower = error_fn(lower, *args)
551 f_upper = error_fn(upper, *args)
552 tries += 1
554 if f_lower * f_upper > 0:
555 raise RuntimeError("Could not find valid bracket for brentq")
557 return lower, upper
560def get_quote(
561 ref_date: _Union[date, datetime],
562 instrument_spec: _Union[
563 DepositSpecification, ForwardRateAgreementSpecification, InterestRateSwapSpecification, InterestRateBasisSwapSpecification
564 ],
565 curve_dict: dict,
566 bootstrap_context=None,
567):
568 """Get the instrument specific fair quote calculation result to be used in the bootstrapper.
570 Args:
571 ref_date (_Union[date, datetime]):
572 instrument_spec (_Union[DepositSpecification, ForwardRateAgreementSpecification, InterestRateSwapSpecification]):
573 curve_dict (dict): Dictionary containing the market data curves needed for discounting or fwd rates.
575 Returns:
576 float: calculated fair rate
577 """
579 quote = 0.0
580 if instrument_spec.ins_type() == Instrument.DEPOSIT:
582 # old
583 discount_curve = curve_dict["discount_curve"]
584 # spread_curve=curve_dict["spread_curve"]
585 quote = DepositPricer.get_implied_simply_compounded_rate(ref_date, instrument_spec, discount_curve) # TODO assumes no spread curve for now
587 elif instrument_spec.ins_type() == Instrument.FRA:
588 if bootstrap_context is None or bootstrap_context.get("flag_multi_curve", False) == False:
589 # single curve case
590 curve_used = curve_dict["discount_curve"]
591 else:
592 # multicurve, where discount given, FRA builds forward
593 curve_used = curve_dict["fixing_curve"]
594 quote = ForwardRateAgreementPricer.compute_fair_rate(ref_date, instrument_spec, discount_curve=curve_used)
596 elif instrument_spec.ins_type() == Instrument.IRS:
598 yc_discount = curve_dict["discount_curve"]
599 yc_forward = curve_dict["fixing_curve"]
600 # according to pyvayon example, the fixing table is assumed to default to empty to allow the code to run...
601 fixing_table = FixingTable()
603 float_leg = instrument_spec.get_float_leg()
604 fixed_leg = instrument_spec.get_fixed_leg()
605 fixing_grace_period = 0 # TODO take in as parameter? in pyvacon example, the extra swap parameters are assumed to be empty, only the curves were passed as arguments...
607 # parameters specific to ir swap bootstrapping, in regards to the fixed leg for calculating the fair swap rate
608 # needed to pass these settings onto the InterestRateSwapPricer.price_leg
609 pricing_params = {"fixing_grace_period": fixing_grace_period, "set_rate": True, "desired_rate": 1.0}
611 quote = InterestRateSwapPricer.compute_swap_rate(ref_date, yc_discount, yc_forward, float_leg, fixed_leg, fixing_table, pricing_params)
613 elif instrument_spec.ins_type() == Instrument.BS: # basis swap # E.g. tenor basis swap
615 yc_discount = curve_dict["discount_curve"]
616 yc_forward = curve_dict["fixing_curve"] # THIS IS THE CURVE TO BE SOLVED
617 yc_basis_curve = curve_dict.get("basis_curve", None) # THIS IS THE EXISTING KNOWN CURVE - assume is for SHORT
618 # NOTE - the rerquirement is that error_fn has the flags to determine if discoutn curve is the same as fixing curve or not already
620 if yc_basis_curve is None:
621 raise Exception("Missing basis curve for pricing TBS")
623 fixing_table = FixingTable()
625 pay_leg = instrument_spec.get_pay_leg()
626 receive_leg = instrument_spec.get_receive_leg()
627 spread_leg = instrument_spec.get_spread_leg()
628 fixing_grace_period = 0 # TODO take in as parameter? in pyvacon example, the extra swap parameters are assumed to be empty, only the curves were passed as arguments...
629 pricing_params = {
630 "fixing_grace_period": fixing_grace_period,
631 "set_rate": True,
632 "desired_rate": 1.0,
633 } # need annuity again for spread_leg(modeled as fixed leg)
635 quote = InterestRateSwapPricer.compute_basis_spread(
636 ref_date,
637 discount_curve=yc_discount,
638 payLegFixingCurve=yc_basis_curve,
639 receiveLegFixingCurve=yc_forward,
640 pay_leg=pay_leg,
641 receive_leg=receive_leg,
642 spread_leg=spread_leg,
643 fixing_map=fixing_table,
644 pricing_params=pricing_params,
645 )
647 # # DEBUG
648 # print(f"Calculated quote for {instrument_spec.ins_type()} is {quote}")
649 return quote
652def bootstrap_curve_from_quote_table(input_data):
653 pass
656# Main
658if __name__ == "__main__":
659 pass