Coverage for rivapy / pricing / interest_rate_swap_pricing.py: 66%
331 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.24 Hans Nguyen
2from datetime import datetime, date, timedelta
3from scipy.optimize import brentq
4from rivapy.tools.interfaces import BaseDatedCurve
5from rivapy.instruments.bond_specifications import DeterministicCashflowBondSpecification
6from rivapy.marketdata import DiscountCurveParametrized, ConstantRate, DiscountCurve
7from rivapy.pricing.pricing_request import PricingRequest
8from rivapy.pricing._logger import logger
9from rivapy.instruments.deposit_specifications import DepositSpecification
10from rivapy.instruments.fra_specifications import ForwardRateAgreementSpecification
11from rivapy.instruments.ir_swap_specification import (
12 IrFixedLegSpecification,
13 IrFloatLegSpecification,
14 IrOISLegSpecification,
15 InterestRateSwapSpecification,
16 IrSwapLegSpecification,
17)
19# from rivapy.pricing.pricing_data import InterestRateSwapPricingData, InterestRateSwapLegPricingData, InterestRateSwapFloatLegPricingData
20from rivapy.pricing.pricing_data import (
21 InterestRateSwapPricingData_rivapy,
22 InterestRateSwapLegPricingData_rivapy,
23 InterestRateSwapFloatLegPricingData_rivapy,
24)
25from rivapy.pricing.pricing_request import InterestRateSwapPricingRequest
26from typing import List as _List, Union as _Union, Tuple, Dict, Any
27from rivapy.tools.datetools import DayCounter
28from rivapy.instruments.components import CashFlow
29from rivapy.marketdata.fixing_table import FixingTable
30from rivapy.instruments.components import *
31import numpy as np
34from rivapy.tools.enums import IrLegType
35from rivapy.tools._validators import print_member_values
38class InterestRateSwapPricer:
40 def __init__(
41 self,
42 val_date: _Union[date, datetime],
43 spec: InterestRateSwapSpecification,
44 discount_curve_pay_leg: DiscountCurve,
45 discount_curve_receive_leg: DiscountCurve,
46 fixing_curve_pay_leg: DiscountCurve,
47 fixing_curve_receive_leg: DiscountCurve,
48 fx_fwd_curve_pay_leg: DiscountCurve, # TODO FxForwardCurve ... do we need anotheer class
49 fx_fwd_curve_receive_leg: DiscountCurve,
50 pricing_request: InterestRateSwapPricingRequest,
51 pricing_param: Dict = None, # mutable argument should be defaulted to none
52 fixing_map: FixingTable = None,
53 fx_pay_leg: float = 1.0,
54 fx_receive_leg: float = 1.0,
55 ):
56 """Initializes the Interest Rate Swap Pricer with all required curves, specifications, and parameters.
58 Args:
59 val_date (date | datetime): The valuation date for pricing the swap. This is the anchor date for all time-dependent calculations.
60 spec (InterestRateSwapSpecification): The swap's structural details (legs, notionals, schedules, etc.).
61 discount_curve_pay_leg (DiscountCurve): Discount curve used to present value the pay leg.
62 discount_curve_receive_leg (DiscountCurve): Discount curve used to present value the receive leg.
63 fixing_curve_pay_leg (DiscountCurve): Curve used to forecast forward rates for the pay leg (typically for floating legs).
64 fixing_curve_receive_leg (DiscountCurve): Curve used to forecast forward rates for the receive leg.
65 fx_fwd_curve_pay_leg (DiscountCurve): FX forward curve to convert the pay leg currency to the pricing currency (if applicable).
66 fx_fwd_curve_receive_leg (DiscountCurve): FX forward curve to convert the receive leg currency to the pricing currency.
67 pricing_request (InterestRateSwapPricingRequest): Contains the pricing type, metrics requested (e.g., PV), and other flags. Not yet used properly
68 pricing_param (Dict, optional): Additional pricing parameters, such as day count conventions, compounding rules, etc.
69 fixing_map (FixingTable, optional): Historical fixings for floating legs that reference past periods.
70 fx_pay_leg (float, optional): FX rate multiplier to convert the pay leg currency to base. Default is 1.0 (i.e., same currency).
71 fx_receive_leg (float, optional): FX rate multiplier to convert the receive leg currency to base. Default is 1.0.
73 """
75 self._val_date = val_date
76 self._spec = spec
77 self._pay_leg = spec.pay_leg
78 self._receive_leg = spec.receive_leg
80 self._discount_curve_pay_leg = discount_curve_pay_leg
81 self._discount_curve_receive_leg = discount_curve_receive_leg
83 self._fixing_curve_pay_leg = fixing_curve_pay_leg # const std::shared_ptr<const DiscountCurve>& fixingCurvePayLeg,
84 self._fixing_curve_receive_leg = fixing_curve_receive_leg # const std::shared_ptr<const DiscountCurve>& fixingCurveReceiveLeg,
86 self._fx_fwd_curve_pay_leg = fx_fwd_curve_pay_leg # const std::shared_ptr<const FxForwardCurve>& fxForwardCurvePayLeg,
87 self._fx_fwd_curve_receive_leg = fx_fwd_curve_receive_leg # const std::shared_ptr<const FxForwardCurve>& fxForwardCurveReceiveLeg,
89 self._pricing_request = pricing_request # const PricingRequest& pricingRequest,
90 if pricing_param is None:
91 pricing_param = {}
92 self._pricing_param = pricing_param # std::shared_ptr<const InterestRateSwapPricingParameter> pricingParam,
93 self._fixing_map = fixing_map # std::shared_ptr<const FixingTable> fixingMap,
95 self._fx_pay_leg = fx_pay_leg # double fxPayLeg,
96 self._fx_receive_leg = fx_receive_leg # double fxReceiveLeg)
98 self._pricing_param = pricing_param
100 @staticmethod
101 def _populate_cashflows_fix(
102 val_date: _Union[date, datetime],
103 fixed_leg_spec: IrFixedLegSpecification,
104 discount_curve: DiscountCurve,
105 # forward_curve: DiscountCurve,
106 fx_forward_curve: DiscountCurve,
107 fixing_map: FixingTable,
108 set_rate: bool = False,
109 desired_rate: float = 1.0,
110 ) -> _List[CashFlow]:
111 """Generate a list of CashFlow objects, each with the cashflow amount for the given accrual period
112 and additional information added to describe the cashflow. For the fixed leg of a swap.
113 If desired_rate = 1.0, this is to calculate the annuity, for the calculation of fair swap rate.
115 Args:
116 val_date (_Union[date, datetime]): valuation date
117 fixed_leg_spec (IrFixedLegSpecification): specification of the fixed leg of the IR Swap
118 discount_curve (DiscountCurve): The discount curve used for discounting to calculated the present value
119 fx_forward_curve (DiscountCurve): fxCurve used for currency conversion for applicable swap (not yet implemented)
120 fixing_map (FixingTable): Fixing map of historial values (not yet implemented)
121 fixing_grace_period (float):
122 set_rate (bool, optional): Flag to manually set a Fixed rate. Defaults to False.
123 spread (float, optional): Desired rate value. Defaults to 1 (which then calculates annuity).
125 Returns:
126 _List[CashFlow]: list of CashFlow objects.
127 """
129 # overwrite fixed interest if desired
130 fixed_rate = fixed_leg_spec.fixed_rate
131 if set_rate:
132 fixed_rate = desired_rate # if desired_rate = 1.0, this is to calculate the annuity, for the calculation of fair swap rate.
134 # init output storage object
135 entries = []
136 dcc = DayCounter(discount_curve.daycounter)
138 # get projected notionals
139 leg_notional_structure = fixed_leg_spec.get_NotionalStructure()
140 # define number of cashflows
141 num_of_start_dates = len(fixed_leg_spec.start_dates)
143 notionals = get_projected_notionals(
144 val_date=val_date,
145 notional_structure=leg_notional_structure,
146 start_period=0,
147 end_period=num_of_start_dates,
148 fx_forward_curve=fx_forward_curve,
149 fixing_map=fixing_map,
150 ) # output is a lsit of floats
152 for i in range(len(notionals)):
154 notional_start_date = leg_notional_structure.get_pay_date_start(i)
155 notional_end_date = leg_notional_structure.get_pay_date_end(i)
157 if notional_start_date: # i.e. not None or empty
158 # add an intional notional OUTFLOW or not
159 notional_entry = CashFlow()
160 notional_entry.pay_date = notional_start_date
162 if val_date <= notional_entry.pay_date: # TODO recheck this business logic
163 notional_entry.discount_factor = discount_curve.value(val_date, notional_entry.pay_date)
164 else:
165 notional_entry.discount_factor = 0.0
167 notional_entry.pay_amount = -1 * notionals[i]
168 notional_entry.present_value = notional_entry.pay_amount * notional_entry.discount_factor
169 notional_entry.notional_cashflow = True
170 entries.append(notional_entry)
172 entry = CashFlow()
173 entry.start_date = fixed_leg_spec.start_dates[i]
174 entry.end_date = fixed_leg_spec.end_dates[i]
175 entry.pay_date = fixed_leg_spec.pay_dates[i]
176 entry.notional = notionals[i]
177 entry.rate = fixed_rate
178 entry.interest_yf = dcc.yf(entry.start_date, entry.end_date) # gives back SINGLE yearfraction
179 if val_date < entry.pay_date:
180 entry.discount_factor = discount_curve.value(val_date, entry.pay_date)
181 else:
182 entry.discount_factor = 0.0
183 entry.interest_amount = entry.notional * entry.rate * entry.interest_yf
184 entry.pay_amount = entry.interest_amount
185 entry.present_value = entry.pay_amount * entry.discount_factor
187 # setting main cashflow value
188 entry.val = entry.pay_amount
189 entry.interest_cashflow = True
190 entries.append(entry)
192 # # TEMPORARY TEST - inthe case of constant notional structure, but i want a final notional cashflow like a bond
193 # if i == len(notionals) - 1: # this checks for the last entry
194 # notional_end_date = entry.end_date
196 if notional_end_date:
197 # add an intional notional INFLOW or not
198 notional_entry = CashFlow()
199 notional_entry.pay_date = notional_end_date
201 if val_date <= notional_entry.pay_date: # TODO recheck this business logic
202 notional_entry.discount_factor = discount_curve.value(val_date, notional_entry.pay_date)
203 else:
204 notional_entry.discount_factor = 0.0
206 notional_entry.pay_amount = notionals[i] # positive
207 notional_entry.present_value = notional_entry.pay_amount * notional_entry.discount_factor
208 notional_entry.notional_cashflow = True
209 entries.append(notional_entry)
211 return entries # a LIST of ENTRY objects, where each object has the PV
213 @staticmethod
214 def _populate_cashflows_float(
215 val_date: _Union[date, datetime],
216 float_leg_spec: IrFloatLegSpecification,
217 discount_curve: DiscountCurve,
218 forward_curve: DiscountCurve,
219 fx_forward_curve: DiscountCurve,
220 fixing_map: FixingTable,
221 fixing_grace_period: float,
222 set_spread: bool = False,
223 spread: float = None,
224 ) -> _List[CashFlow]:
225 """Generate a list of CashFlow objects, each with the cashflow amount for the given accrual period
226 and additional information added to descrive the cashflow.
228 Args:
229 val_date (_Union[date, datetime]): valuation date
230 float_leg_spec (IrFloatLegSpecification): specification of the floating leg of the IR Swap
231 discount_curve (DiscountCurve): The discount curve used for discounting to calculated the present value
232 forward_curve (DiscountCurve): forward curve used to determine the forward rate to calculate the interest
233 fx_forward_curve (DiscountCurve): fxCurve used for currency conversion for applicable swap (not yet implemented)
234 fixing_map (FixingTable): Fixing map of historial values (not yet implemented)
235 fixing_grace_period (float):
236 setSpread (bool, optional): Flag to manually set a spread value. Defaults to False.
237 spread (float, optional): Desired spread value. Defaults to None.
239 Raises:
240 ValueError: No underlying index ID found in fixing table
242 Returns:
243 _List[CashFlow]: list of CashFlow objects.
244 """
246 fixing_grace_period_dt = timedelta(days=fixing_grace_period)
248 entries = []
249 udl = float_leg_spec.udl_id
251 # swap day count convention
252 dcc = DayCounter(discount_curve.daycounter)
253 # rate day count convention # note that the specification also has dcc but without the curve...
254 rate_dcc = DayCounter(float_leg_spec.rate_day_count_convention)
256 # overwrite spread if desired
257 leg_spread = float_leg_spec.spread
258 if set_spread:
259 leg_spread = spread
261 # obtain the notionals and create a list of notionals to be used for each cashflow
262 leg_notional_structure = float_leg_spec.get_NotionalStructure()
263 # define the number of cashflows
264 num_of_start_dates = len(float_leg_spec.start_dates)
266 notionals = get_projected_notionals(
267 val_date=val_date,
268 notional_structure=leg_notional_structure,
269 start_period=0,
270 end_period=num_of_start_dates,
271 fx_forward_curve=fx_forward_curve,
272 fixing_map=fixing_map,
273 ) # output is a list of floats
275 for i in range(len(notionals)):
277 notional_start_date = leg_notional_structure.get_pay_date_start(i)
278 notional_end_date = leg_notional_structure.get_pay_date_end(i)
280 if notional_start_date: # i.e. not None or empty
281 # add an initial notional OUTFLOW or not
282 notional_entry = CashFlow()
283 notional_entry.pay_date = notional_start_date
285 if val_date <= notional_entry.pay_date: # TODO recheck this business logic
286 notional_entry.discount_factor = discount_curve.value(val_date, notional_entry.pay_date)
287 else:
288 notional_entry.discount_factor = 0.0
290 notional_entry.pay_amount = -1 * notionals[i]
291 notional_entry.present_value = notional_entry.pay_amount * notional_entry.discount_factor
292 notional_entry.notional_cashflow = True
293 entries.append(notional_entry)
295 entry = CashFlow()
296 entry.start_date = float_leg_spec.start_dates[i]
297 entry.end_date = float_leg_spec.end_dates[i]
298 entry.pay_date = float_leg_spec.pay_dates[i]
299 entry.notional = notionals[i]
300 entry.interest_yf = dcc.yf(entry.start_date, entry.end_date)
301 rate_yf = rate_dcc.yf(float_leg_spec.rate_start_dates[i], float_leg_spec.rate_end_dates[i])
302 # # DEBUG TODO REMOVE
303 # print(f"notional {i}:{notionals[i]} for {entry.end_date}")
304 if val_date <= float_leg_spec.reset_dates[i]:
305 # print("swap calculating fwd_rate for floating leg") # DEBUG 11.2025 TODO REMOVE
306 # print(f"rate_start={float_leg_spec.rate_start_dates[i]}, rate_end={float_leg_spec.rate_end_dates[i]}")
307 # print(
308 # f"DF_start={forward_curve.value(val_date, float_leg_spec.rate_start_dates[i])}, DF_end={forward_curve.value(val_date, float_leg_spec.rate_end_dates[i])}"
309 # )
310 # print(f"value_fwd={forward_curve.value_fwd(val_date, float_leg_spec.rate_start_dates[i], float_leg_spec.rate_end_dates[i])}")
312 fwd_rate = forward_curve.value_fwd(val_date, float_leg_spec.rate_start_dates[i], float_leg_spec.rate_end_dates[i])
313 entry.rate = leg_spread + (1.0 / fwd_rate - 1.0) / rate_yf
314 # print(f"leg_spread: {leg_spread} rate_yf: {rate_yf} fwd_rate: {fwd_rate} calculated rate: {entry.rate} ")
315 else:
316 # print("GOT JERE INSTEAD : val_date:", val_date, " cf restet date:", float_leg_spec.reset_dates[i])
317 fixing = fixing_map.get_fixing(udl, float_leg_spec.reset_dates[i])
318 if fixing is None: # i.e. no fixing available
320 if val_date - float_leg_spec.reset_dates[i] > fixing_grace_period_dt:
321 raise ValueError(f"Missing fixing for {udl} on {float_leg_spec.reset_dates[i]}")
323 else:
324 # fix value of payment i in future based on current discount curve and a period between
325 # valDate and valDate+length of original period (workaround if fixing is not available)
326 # taken from pyvacon
327 if entry.pay_date >= val_date: # TODO understand the logic
328 time_delta = entry.end_date - entry.start_date
329 fixing = (1.0 / forward_curve.value_fwd(val_date, val_date, val_date + time_delta) - 1) / entry.interest_yf
331 entry.rate = fixing + spread
333 if val_date <= entry.pay_date:
334 entry.discount_factor = discount_curve.value(val_date, entry.pay_date)
335 else:
336 entry.discount_factor = 0.0
338 # given rate, notional, and yf, calc interest
339 entry.interest_amount = entry.notional * entry.rate * entry.interest_yf
341 # scale by forward rate #TODO # required for FX swap...
342 if val_date <= entry.end_date:
343 entry.pay_amount = entry.interest_amount / forward_curve.value_fwd(val_date, entry.end_date, entry.pay_date)
344 else:
345 if entry.pay_date >= val_date:
346 entry.pay_amount = entry.interest_amount / forward_curve.value_fwd(val_date, val_date, entry.pay_date)
347 else:
348 entry.pay_amount = 0.0
350 # given total cashflow amount - discount it
351 # #DEBUG TODO
352 # print(f"forward rate: {entry.rate}")
353 # print(f"delta_t: {entry.interest_yf}")
354 # print(f"discount_factor: {entry.discount_factor}")
355 entry.present_value = entry.pay_amount * entry.discount_factor
356 entry.interest_cashflow = True
357 entries.append(entry)
359 # # TEMPORARY TEST - inthe case of constant notional structure, but i want a final notional cashflow like a bond
360 # if i == len(notionals) - 1: # this checks for the last entry
361 # notional_end_date = entry.end_date
363 if notional_end_date:
364 # add an intional notional INFLOW or not
365 notional_entry = CashFlow()
366 notional_entry.pay_date = notional_end_date
368 if val_date <= notional_entry.pay_date: # TODO recheck this business logic
369 notional_entry.discount_factor = discount_curve.value(val_date, notional_entry.pay_date)
370 else:
371 notional_entry.discount_factor = 0.0
373 notional_entry.pay_amount = notionals[i] # positive
374 notional_entry.present_value = notional_entry.pay_amount * notional_entry.discount_factor
375 notional_entry.notional_cashflow = True
376 entries.append(notional_entry)
378 return entries
380 @staticmethod
381 def _populate_cashflows_ois(
382 val_date: _Union[date, datetime],
383 ois_leg_spec: IrOISLegSpecification,
384 discount_curve: DiscountCurve,
385 forward_curve: DiscountCurve,
386 fx_forward_curve: DiscountCurve,
387 fixing_map: FixingTable,
388 fixing_grace_period: float,
389 set_spread: bool = False,
390 spread: float = None,
391 ) -> _List[CashFlow]:
392 """Generate a list of CashFlow objects, each with the cashflow amount for the given accrual period
393 and additional information added to describe the cashflow. To be used for OIS leg specifications.
394 In this case, the daily rates are calculated and compounded to give the total interest for the accrual period.
396 Args:
397 val_date (_Union[date, datetime]): valuation date
398 float_leg_spec (IrFloatLegSpecification): specification of the floating leg of the IR Swap
399 discount_curve (DiscountCurve): The discount curve used for discounting to calculated the present value
400 forward_curve (DiscountCurve): forward curve used to determine the forward rate to calculate the interest
401 fx_forward_curve (DiscountCurve): fxCurve used for currency conversion for applicable swap (not yet implemented)
402 fixing_map (FixingTable): Fixing map of historial values (not yet implemented)
403 fixing_grace_period (float):
404 setSpread (bool, optional): Flag to manually set a spread value. Defaults to False.
405 spread (float, optional): Desired spread value. Defaults to None.
407 Raises:
408 ValueError: No underlying index ID found in fixing table
410 Returns:
411 _List[CashFlow]: list of CashFlow objects.
412 """
413 entries = [] # output container to be returned
414 udl = ois_leg_spec.udl_id # get the ID of the underlying
416 # Parameters to consider
417 # const std::vector<std::vector<boost::posix_time::ptime>>& dailyRateStartDates = oisLeg->getDailyRateStartDates();
418 # const std::vector<std::vector<boost::posix_time::ptime>>& dailyRateEndDates = oisLeg->getDailyRateEndDates();
419 # const std::vector<std::vector<boost::posix_time::ptime>>& dailyResetDates = oisLeg->getDailyResetDates();
420 # const std::vector<boost::posix_time::ptime>& startDates = oisLeg->getStartDates();
421 # const std::vector<boost::posix_time::ptime>& endDates = oisLeg->getEndDates();
422 # const std::vector<boost::posix_time::ptime>& payDates = oisLeg->getPayDates();
423 # std::shared_ptr<const NotionalStructure> notionalStructure = leg->getNotionalStructure();
424 # std::vector<double> notionals(leg->getStartDates().size());
426 # swap day count convention
427 dcc = DayCounter(discount_curve.daycounter)
428 # rate day count convention # note that the specification also has dcc but without the curve...
429 rate_dcc = DayCounter(ois_leg_spec.rate_day_count_convention)
431 # overwrite spread if desired
432 leg_spread = ois_leg_spec.spread
433 if set_spread:
434 leg_spread = spread
436 # obtain the notionals and create a list of notionals to be used for each cashflow
437 leg_notional_structure = ois_leg_spec.get_NotionalStructure()
438 # define the number of cashflows
439 num_of_start_dates = len(ois_leg_spec.start_dates)
441 notionals = get_projected_notionals(
442 val_date=val_date,
443 notional_structure=leg_notional_structure,
444 start_period=0,
445 end_period=num_of_start_dates,
446 fx_forward_curve=fx_forward_curve,
447 fixing_map=fixing_map,
448 ) # output is a list of floats
450 # obtained from ois_leg_spec # expect 2D structure
451 daily_rate_start_dates = ois_leg_spec.rate_start_dates
452 # [[0],[1]] # i:start datet fo accrual period j:rate start days within the accrual period # e.g i:accrual over 3M, j:reset daily
453 daily_rate_end_dates = ois_leg_spec.rate_end_dates
454 daily_rate_reset_dates = ois_leg_spec.rate_reset_dates
456 # for each notional, but essentially each accrual period
457 for i in range(len(notionals)):
459 notional_start_date = leg_notional_structure.get_pay_date_start(i)
460 notional_end_date = leg_notional_structure.get_pay_date_end(i)
462 if notional_start_date: # i.e. not None or empty
463 # add an initial notional OUTFLOW or not
464 notional_entry = CashFlow()
465 notional_entry.pay_date = notional_start_date
467 if val_date <= notional_entry.pay_date: # TODO recheck this business logic
468 notional_entry.discount_factor = discount_curve.value(val_date, notional_entry.pay_date)
469 else:
470 notional_entry.discount_factor = 0.0
472 notional_entry.pay_amount = -1 * notionals[i]
473 notional_entry.present_value = notional_entry.pay_amount * notional_entry.discount_factor
474 notional_entry.notional_cashflow = True
475 entries.append(notional_entry)
477 entry = CashFlow()
478 entry.start_date = ois_leg_spec.start_dates[i]
479 entry.end_date = ois_leg_spec.end_dates[i]
480 entry.pay_date = ois_leg_spec.pay_dates[i]
481 entry.notional = notionals[i]
482 entry.interest_yf = dcc.yf(entry.start_date, entry.end_date)
484 # loop over each resetting datet, i.e. daily to calculate the daily compounded interest to use to calculae coupon for this time period/entry
485 accDf = 1.0 # accrual discount factor
487 for j in range(len(daily_rate_start_dates[i])):
489 rate_yf = rate_dcc.yf(daily_rate_start_dates[i][j], daily_rate_end_dates[i][j]) # should be a day
491 if daily_rate_reset_dates[i][j] >= val_date: # rate not yet fixed
492 daily_fwd = forward_curve.value_fwd(val_date, daily_rate_start_dates[i][j], daily_rate_end_dates[i][j])
493 daily_rate = leg_spread + (1.0 / daily_fwd - 1.0) / rate_yf
495 else: # rate already fixed
497 fixing = fixing_map.get_fixing(udl, daily_rate_reset_dates[i][j])
499 if np.isnan(fixing): # math.isnan
500 if (val_date - daily_rate_reset_dates[i][j]) > fixing_grace_period:
501 raise RuntimeError(f"Fixing for udl {udl}, date {daily_rate_reset_dates[i][j]} not provided")
502 else:
503 if entry.pay_date >= val_date:
504 # fix value of payment i in future based on current discount curve and a period between valDate and valDate+length of original period (workaround if fixing is not available)
505 time_delta = entry.end_date - entry.start_date # do we need? we assume daily...
506 fixing = (
507 1.0 / forward_curve.value_fwd(val_date, daily_rate_start_dates[i][j], daily_rate_end_dates[i][j]) - 1.0
508 ) / rate_yf
510 daily_rate = leg_spread + fixing
512 accDf *= 1.0 + daily_rate * rate_yf # compounded daily, hence the multiplication
513 # accDf now calulated
515 rate_yf = rate_dcc.yf(daily_rate_start_dates[i][0], daily_rate_end_dates[i][-1]) # total accrual period Yf
516 entry.rate = (accDf - 1.0) / rate_yf # the -1 gives then just the interest portion of the compounded daily
518 if val_date <= entry.pay_date:
519 entry.discount_factor = discount_curve.value(val_date, entry.pay_date)
520 else:
521 entry.discount_factor = 0.0
523 # given rate, notional, and yf, calc interest
524 entry.interest_amount = entry.notional * entry.rate * entry.interest_yf
525 entry.pay_amount = entry.interest_amount
527 # # scale by forward rate???? #TODO
528 # if val_date <= entry.end_date:
529 # entry.pay_amount = entry.interest_amount / forward_curve.value_fwd(val_date, entry.end_date, entry.pay_date)
530 # else:
531 # if entry.pay_date >= val_date:
532 # entry.pay_amount = entry.interest_amount / forward_curve.value_fwd(val_date, val_date, entry.pay_date)
533 # else:
534 # entry.pay_amount = 0.0
536 # given total cashflow amount - discount it
537 # #DEBUG TODO
538 # print(f"forward rate: {entry.rate}")
539 # print(f"delta_t: {entry.interest_yf}")
540 # print(f"discount_factor: {entry.discount_factor}")
541 entry.present_value = entry.pay_amount * entry.discount_factor
542 entry.interest_cashflow = True
543 entries.append(entry)
545 # # TEMPORARY TEST - inthe case of constant notional structure, but i want a final notional cashflow like a bond
546 # if i == len(notionals) - 1: # this checks for the last entry
547 # notional_end_date = entry.end_date
549 if notional_end_date:
550 # add an intenional notional INFLOW or not
551 notional_entry = CashFlow()
552 notional_entry.pay_date = notional_end_date
554 if val_date <= notional_entry.pay_date: # TODO recheck this business logic
555 notional_entry.discount_factor = discount_curve.value(val_date, notional_entry.pay_date)
556 else:
557 notional_entry.discount_factor = 0.0
559 notional_entry.pay_amount = notionals[i] # positive
560 notional_entry.present_value = notional_entry.pay_amount * notional_entry.discount_factor
561 notional_entry.notional_cashflow = True
562 entries.append(notional_entry)
564 return entries
566 @staticmethod
567 def price_leg_pricing_data(val_date, pricing_data: InterestRateSwapLegPricingData_rivapy, param):
568 """Pricing a single Leg using Pricing Data architecture (not yet fully integrated, to be done once architecture clarified)
570 Args:
571 val_date (_type_): _description_
572 pricing_data (InterestRateSwapLegPricingData): float leg or base leg pricing data ...
573 param (_type_): extra parameters (not yet used)
575 Raises:
576 ValueError: _description_
578 Returns:
579 _type_: _description_
580 """
581 # get leg info from PricingData ->spec
582 leg_spec = pricing_data.spec # what kind of leg?
584 if leg_spec.leg_type == IrLegType.FIXED:
585 if pricing_data.desired_rate is not None:
586 set_rate = True
587 else:
588 set_rate = False
589 cashflow_table = InterestRateSwapPricer._populate_cashflows_fix(
590 val_date,
591 pricing_data.spec,
592 pricing_data.discount_curve,
593 pricing_data.forward_curve,
594 pricing_data.fixing_map,
595 set_rate=set_rate,
596 desired_rate=pricing_data.desired_rate,
597 )
598 elif leg_spec.leg_type == IrLegType.FLOAT:
599 if pricing_data.spread is not None:
600 set_spread = True
601 else:
602 set_spread = False
604 # REMOVE THIS TEST?
605 if isinstance(pricing_data, InterestRateSwapFloatLegPricingData_rivapy):
606 cashflow_table = InterestRateSwapPricer._populate_cashflows_float(
607 val_date,
608 pricing_data.spec,
609 pricing_data.discount_curve,
610 pricing_data.forward_curve,
611 pricing_data.fixing_curve,
612 pricing_data.fixing_map,
613 pricing_data.fixing_grace_period,
614 set_spread=set_spread,
615 spread=pricing_data.spread,
616 )
618 else:
619 raise ValueError("pricing data is not of type 'InterestRateSwapFloatLegPricingData_rivapy' ") # TODO UPDATE
621 # elif leg_spec.leg_type == IrLegType.OIS:
622 # InterestRateSwapPricer._populate_cashflows_ois
623 else:
624 raise ValueError(f"Unknown leg type {leg_spec.type}")
626 PV = 0
627 for entry in cashflow_table:
629 PV += entry.present_value
631 # return PV
632 return PV * pricing_data.fx_rate
634 @staticmethod
635 def price_leg(
636 val_date,
637 discount_curve: DiscountCurve,
638 forward_curve: DiscountCurve,
639 fxForward_curve: DiscountCurve,
640 spec: _Union[IrFixedLegSpecification, IrFloatLegSpecification],
641 fixing_map: FixingTable = None,
642 fixing_grace_period: float = 0,
643 pricing_params: dict = {"set_rate": False, "desired_rate": 1.0},
644 ):
645 """Price a leg of a IR swap making distinctions for floating and fixed legs as well as OIS legs.
647 Args:
648 val_date (_type_): Valuation date
649 discount_curve (DiscountCurve): discount curve for discounting cashflows
650 forward_curve (DiscountCurve): forward curve for the forward rate calculations
651 fxForward_curve (DiscountCurve): fx forward curve for currency conversions for applicable instruments (not yet implemented)
652 spec (_Union[IrFixedLegSpecification, IrFloatLegSpecification]): Specification object for the leg to be priced
653 fixing_map (FixingTable, optional): Historical fixing data (not yet implemented). Defaults to None.
654 fixing_grace_period (float, optional): . Defaults to 0.
655 pricing_params (_type_, optional): Additional parameters needed for swap pricing. Defaults to {"set_rate": False, "desired_rate": 1.0}.
657 Raises:
658 ValueError: if unknown leg type is passed
660 Returns:
661 float: return the aggregated Present value of the leg of the swap.
662 """
664 # get leg info from PricingData ->spec
665 leg_spec = spec # what kind of leg?
667 if leg_spec.leg_type == IrLegType.FIXED:
668 # print("generating cashflow table for FIXED leg")
669 set_rate = pricing_params["set_rate"] # if we want to set the rate
670 desired_rate = pricing_params["desired_rate"]
671 cashflow_table = InterestRateSwapPricer._populate_cashflows_fix(
672 val_date, leg_spec, discount_curve, forward_curve, fixing_map, set_rate, desired_rate
673 )
674 elif leg_spec.leg_type == IrLegType.FLOAT:
675 # print("generating cashflow table for FLOAT leg")
676 cashflow_table = InterestRateSwapPricer._populate_cashflows_float(
677 val_date, leg_spec, discount_curve, forward_curve, fxForward_curve, fixing_map, fixing_grace_period
678 ) # TODO implement spread
680 elif leg_spec.leg_type == IrLegType.OIS:
681 cashflow_table = InterestRateSwapPricer._populate_cashflows_ois(
682 val_date, leg_spec, discount_curve, forward_curve, fxForward_curve, fixing_map, fixing_grace_period
683 )
684 # TODO implement spread
685 # 2025.10.28 As of now, analytical OIS swap rate calculation does not require full cashflow table as we can calculate
686 # the fair swap rate directly from discount factors. The method is kept as a fallback for more complex OIS legs that may require full cashflow simulation.
688 else:
689 raise ValueError(f"Unknown leg type {leg_spec.type}")
691 # inside price_leg just after cashflow construction
692 # - DEBUG 11.2025
693 # print("\n=== DEBUG FLOAT LEG ===")
694 # total_pv = 0.0
695 # for i, cf in enumerate(cashflow_table):
696 # print(
697 # f"[{i}] pay_date={cf.pay_date}, "
698 # f"notional={cf.notional:.2f}, "
699 # f"rate={cf.rate:.6f}, "
700 # f"yf={cf.interest_yf:.6f}, "
701 # f"pay_amt={cf.pay_amount:.6f}, "
702 # f"DF={cf.discount_factor:.6f}, "
703 # f"PV={cf.present_value:.6f}"
704 # )
705 # total_pv += cf.present_value
706 # print(f"--- Total leg PV = {total_pv:.6f}\n")
708 PV = 0
709 # print("----------------------------------------------------------")
710 # print("DEBUG: price leg pv values") # DEBUG TODO REMOVE
711 for entry in cashflow_table:
712 # print_member_values(entry)
713 # print(entry.present_value)
714 PV += entry.present_value
716 return PV
717 # return PV* pricing_data.fx_rate
719 def price(self):
720 """price a full swap, with a pay leg and a receive leg in the context of
721 pricing container and pricer having been initlialized with required inputs."""
723 # # PricingResults& results, -> implement also
724 # val_date = self._val_date # const boost::posix_time::ptime& valDate,
725 # discount_curve_pay_leg = self. # const std::shared_ptr<const DiscountCurve>& discountCurvePayLeg,
726 # discount_curve_receive_leg= self. # const std::shared_ptr<const DiscountCurve>& discountCurveReceiveLeg,
727 # fixing_curve_pay_leg = # const std::shared_ptr<const DiscountCurve>& fixingCurvePayLeg,
728 # fixing_curve_receive_leg= # const std::shared_ptr<const DiscountCurve>& fixingCurveReceiveLeg,
729 # fx_fwd_curve_pay_leg = # const std::shared_ptr<const FxForwardCurve>& fxForwardCurvePayLeg,
730 # fx_fwd_curve_receive_leg = # const std::shared_ptr<const FxForwardCurve>& fxForwardCurveReceiveLeg,
731 # interest_rate_swap_spec = # const std::shared_ptr<const InterestRateSwapSpecification>& spec,
732 # pricing_request = # const PricingRequest& pricingRequest,
733 # pricing_param = # std::shared_ptr<const InterestRateSwapPricingParameter> pricingParam,
734 # fixing_map = # std::shared_ptr<const FixingTable> fixingMap,
735 # fx_pay_leg = # double fxPayLeg,
736 # fx_receive_leg = # double fxReceiveLeg)
738 # unit in days
739 fixing_grace_period = self._pricing_param["fixing_grace_period"]
741 # TODO check structure again....
742 # price = InterestRateSwapPricer.price_leg(valDate, discountCurveReceiveLeg, fixingCurveReceiveLeg, fxForwardCurveReceiveLeg, spec->getReceiveLeg(), fixingMap, fixingGracePeriod) * fxReceiveLeg;
743 # price -= InterestRateSwapPricer.price_leg(valDate, discountCurvePayLeg, fixingCurvePayLeg, fxForwardCurvePayLeg, spec->getPayLeg(), fixingMap, fixingGracePeriod) * fxPayLeg;
745 #
746 aggregated_price = InterestRateSwapPricer.price_leg(
747 self._val_date,
748 discount_curve=self._discount_curve_receive_leg,
749 forward_curve=self._fixing_curve_receive_leg,
750 fxForward_curve=self._fx_fwd_curve_receive_leg,
751 spec=self._receive_leg,
752 fixing_map=self._fixing_map,
753 fixing_grace_period=fixing_grace_period,
754 )
755 aggregated_price -= InterestRateSwapPricer.price_leg(
756 self._val_date,
757 discount_curve=self._discount_curve_pay_leg,
758 forward_curve=self._fixing_curve_receive_leg,
759 fxForward_curve=self._fx_fwd_curve_receive_leg,
760 spec=self._pay_leg,
761 fixing_map=self._fixing_map,
762 fixing_grace_period=fixing_grace_period,
763 )
764 # results.setPrice(price);
765 # aggregated_price is already discounted to present value inside the price_leg method
766 return aggregated_price
768 # static method also then?
769 @staticmethod
770 def compute_swap_rate(
771 ref_date: _Union[date, datetime],
772 discount_curve: DiscountCurve,
773 fixing_curve: DiscountCurve,
774 float_leg: IrFloatLegSpecification,
775 fixed_leg: IrFixedLegSpecification,
776 fixing_map: FixingTable = None,
777 pricing_params: dict = {"fixing_grace_period": 0.0},
778 ) -> float:
779 """To calculate the fair swap rate, i.e. the fixed rate(r*) such that
780 PV_fixed(r*) = PV_float
781 where
782 PV_fixed(r*) = r* * Annuity
783 where
784 Annuity = sum(notional_i * DF_i * YF_i*)
786 Fast path for OIS floating leg: use analytical OIS formula
787 so we do not simulate daily compounding each time inside the solver.
789 Args:
790 ref_date (_Union[date, datetime]): reference date
791 discount_curve (DiscountCurve): discoutn curve to determine present value
792 fixing_curve (DiscountCurve): curve used for fwd rates for the floating leg
793 float_leg (IrFloatLegSpecification): IR swap float leg specification
794 fixed_leg (IrFixedLegSpecification): IR swap fix leg specification
795 fixing_map (FixingTable, optional): historical fixing data (TODO). Defaults to None.
796 fixing_grace_period (int, optional): . Defaults to 0.
798 Returns:
799 float: fair rate for the swap
800 """
802 fixing_grace_period = pricing_params["fixing_grace_period"]
804 # -----------------------------
805 # Fast analytical OIS path:
806 # - If the floating leg is OIS, compute the fixed rate (par swap rate)
807 # analytically using discount factors (P) on the fixed leg payment dates:
808 # R = (1 - P(T_N)) / sum_i alpha_i * P(T_i)
809 # - where alpha_i is the year fraction for the fixed leg payment period i (accrual factor)
810 # - This is equivalent to pricing the compounded overnight floating leg.
811 # -----------------------------
812 try:
813 if hasattr(float_leg, "leg_type") and float_leg.leg_type == IrLegType.OIS:
815 return InterestRateSwapPricer.compute_swap_rate_ois_analytical(ref_date, discount_curve, fixing_curve, float_leg, fixed_leg)
817 except Exception:
818 # If anything unexpected (missing attributes) happens, fall back to generic route
819 logger.debug("Fast OIS path failed/fell through; using generic pricing path.")
821 # if fixed_leg.obj_id == "OIS_2M_fixed_leg3": # DEBUG TEST 2025
822 # logger.debug("OIS_2M_fixed_leg3 detected")
824 # -----------------------------
825 # generic path:
826 float_leg_PV = InterestRateSwapPricer.price_leg(ref_date, discount_curve, fixing_curve, None, float_leg, fixing_map, fixing_grace_period)
827 fixed_leg_annuity = InterestRateSwapPricer.price_leg(
828 ref_date, discount_curve, fixing_curve, None, fixed_leg, fixing_map, fixing_grace_period, pricing_params
829 )
831 if fixed_leg_annuity == 0:
832 logger.error("Fixed leg annuity is zero, cannot compute swap rate!")
834 return float_leg_PV / fixed_leg_annuity
836 # TODO
837 def compute_swap_spread(self):
838 # ref date
839 # discount curve pay leg
840 # forward curve pay leg
841 # fx forward curve pay leg
842 # discount curve rec leg
843 # forward curve rec leg
844 # fx forward curve rec leg
845 # pay leg spec
846 # rec leg spec
847 # fixing map
848 # extra param: InterestRateSwapPricingParameter
849 # fx pay
850 # fx rec
851 # fixing grace period comes from the extra param
853 # convert all prices into the currency of the swap
854 pv_pay = 0
855 pv_rec_s0 = 1
856 py_rec_s1 = 0
857 # pv_pay = fxPay * price_leg(refDate, discountCurvePay, forwardCurvePay, fxForwardCurvePay, floatLegPay, fixingMap, fixingGracePeriod);
858 # pv_rec_s0 = fxRec * price_leg(refDate, discountCurveRec, forwardCurveRec, fxForwardCurveRec, floatLegRec, fixingMap, fixingGracePeriod, true, 0.);set_spread=True, desired_spread = 0.0 #for float it is spread
859 # py_rec_s1 = fxRec * price_leg(refDate, discountCurveRec, forwardCurveRec, fxForwardCurveRec, floatLegRec, fixingMap, fixingGracePeriod, true, 1.);set_spread=True, desired_spread = 1.0
860 # note that the current price leg doesnt take spreads as options for the moment, it is left as a # TODO for now...
861 return (pv_pay - pv_rec_s0) / (py_rec_s1 - pv_rec_s0)
863 # # TODO
864 # double InterestRateSwapPricer::computeBasisSpread(
865 # const boost::posix_time::ptime& refDate,
866 # const std::shared_ptr<const DiscountCurve>& discountCurve,
867 # const std::shared_ptr<const DiscountCurve>& receiveLegFixingCurve,
868 # const std::shared_ptr<const DiscountCurve>& payLegFixingCurve,
869 # const std::shared_ptr<const IrFloatLegSpecification>& receiveLeg,
870 # const std::shared_ptr<const IrFloatLegSpecification>& payLeg,
871 # const std::shared_ptr<const IrFixedLegSpecification>& fixedLeg,
872 # std::shared_ptr<const FixingTable> fixingMap,
873 # std::shared_ptr<const InterestRateSwapPricingParameter> param
874 # )
875 # {
876 # const boost::posix_time::time_duration& fixingGracePeriod = param->fixingGracePeriod;
877 # double receiveLegPV = price(refDate, discountCurve, receiveLegFixingCurve, nullptr, receiveLeg, fixingMap, fixingGracePeriod);
878 # double payLegPV = price(refDate, discountCurve, payLegFixingCurve, nullptr, payLeg, fixingMap, fixingGracePeriod);
879 # double fixedLegPV01 = price(refDate, discountCurve, std::shared_ptr<const DiscountCurve>(), nullptr, fixedLeg,
880 # fixingMap, fixingGracePeriod, true, 1.); # the one there means its the annuity again, the true means use the given rate (1.0) for fixed rate
881 # return (receiveLegPV - payLegPV) / fixedLegPV01;
882 # }
883 @staticmethod
884 def compute_basis_spread(
885 ref_date: _Union[date, datetime],
886 discount_curve: DiscountCurve,
887 payLegFixingCurve: DiscountCurve,
888 receiveLegFixingCurve: DiscountCurve,
889 pay_leg: IrFloatLegSpecification,
890 receive_leg: IrFloatLegSpecification,
891 spread_leg: IrFixedLegSpecification,
892 fixing_map: FixingTable = None,
893 pricing_params: dict = {"fixing_grace_period": 0.0},
894 ) -> float:
895 # ref date
896 # discount curve
897 # receiveLegFixingCurve
898 # payLegFixingCurve
899 # receiveLeg spec #floatIRspec
900 # payLeg spec #floatIRspec
901 # fixed leg spec # fixedIRspec
902 # fixing grace period comes from the extra param
904 # noFxFowardCruve, set to null
905 fixing_grace_period = pricing_params["fixing_grace_period"]
907 # price_leg(refDate, discountCurve, receiveLegFixingCurve, nullptr, receiveLeg, fixingMap, fixingGracePeriod)
908 receive_leg_PV = InterestRateSwapPricer.price_leg(
909 ref_date, discount_curve, receiveLegFixingCurve, None, receive_leg, fixing_map, fixing_grace_period
910 )
911 # price_leg(refDate, discountCurve, payLegFixingCurve, nullptr, payLeg, fixingMap, fixingGracePeriod);
912 pay_leg_PV = InterestRateSwapPricer.price_leg(ref_date, discount_curve, payLegFixingCurve, None, pay_leg, fixing_map, fixing_grace_period)
913 # price_leg(refDate, discountCurve, std::shared_ptr<const DiscountCurve>(), nullptr, fixedLeg, fixingMap, fixingGracePeriod, true, 1.);
914 fixed_leg_PV01 = InterestRateSwapPricer.price_leg(
915 ref_date, discount_curve, payLegFixingCurve, None, spread_leg, fixing_map, fixing_grace_period, pricing_params
916 ) # should not need a fixing curve since its a fixed leg, we default to the pay leg
918 # # for fixed, we are setting the rate to 1
920 # # - DEBUG 11.2025
921 # print("INSIDE COMPUTE BASIS SPREAD")
922 # print("receive_leg_PV:", receive_leg_PV)
923 # print("pay_leg_PV:", pay_leg_PV)
924 # print("fixed_leg_PV01:", fixed_leg_PV01)
925 # print("INSIDE COMPUTE BASIS SPREAD: receive_leg_PV:", receive_leg_PV, "pay_leg_PV:", pay_leg_PV, "fixed_leg_PV01:", fixed_leg_PV01)
927 if abs(fixed_leg_PV01) < 1e-12:
928 raise Exception(f"fixed_leg_PV01 too small ({fixed_leg_PV01}), cannot divide — check fixed leg annuity / conventions")
929 # -
931 return (receive_leg_PV - pay_leg_PV) / fixed_leg_PV01
933 @staticmethod
934 def compute_swap_rate_ois_analytical(
935 ref_date: datetime,
936 discount_curve: DiscountCurve,
937 forward_curve: DiscountCurve,
938 float_leg: IrOISLegSpecification,
939 fixed_leg: IrFixedLegSpecification,
940 ) -> float:
941 """
942 Computes the fair (par) fixed rate for an Overnight Indexed Swap (OIS)
943 using an analytical shortcut based on discount factors.
945 This method assumes that the floating leg is a compounded overnight leg
946 and that, under standard OIS discounting, the present value of the floating
947 leg can be derived directly from the discount curve without simulating
948 daily compounding. Much faster than simulating daily compounding for each.
949 [https://btrm.org/wp-content/uploads/2024/03/BTRM-WP15_SOFR-OIS-Curve-Construction_Dec-2020.pdf]
951 The par OIS rate is computed as:
953 R = (sum_i N_i * [P(T_i) - P(T_{i+1})]) / (sum_i N_i * alpha_i * P(T_{i+1}))
955 where:
956 - P(T_i): discount factor at period start/end,
957 - alpha_i: accrual year fraction on the fixed leg,
958 - N_i: notional applicable for that period.
960 Assumptions:
961 - The floating leg is fully collateralized and discounted on the
962 same OIS curve.
963 - The compounded overnight rate is implied by the discount factors.
964 - No spread, lag, or convexity correction is applied.
965 - Notionals and accrual conventions are consistent with the given curves.
966 - Forward curve is used only for projected notionals or FX conversions,
967 not for rate projection.
968 - The fixed leg is currently not explictily used as it assumes that it has
969 the same notional structure and payment dates as the floating leg, which
970 usually the case.
973 Raises:
974 ValueError: If the computed annuity (denominator) is zero,
975 indicating invalid leg setup or inconsistent inputs.
977 Returns:
978 float: The par fixed rate that equates PV_fixed = PV_float (analytical) (as a decimal, e.g. 0.025 for 2.5%).
979 """
981 dcc = DayCounter(discount_curve.daycounter)
982 num_periods = len(float_leg.start_dates)
984 # get notionals using the same logic as your existing daily compounding path
985 leg_notional_structure = float_leg.get_NotionalStructure()
986 notionals = get_projected_notionals(
987 val_date=ref_date,
988 notional_structure=leg_notional_structure,
989 start_period=0,
990 end_period=num_periods,
991 fx_forward_curve=forward_curve,
992 fixing_map=FixingTable(),
993 )
995 start_dates = float_leg.start_dates
996 end_dates = float_leg.end_dates
997 # pay_dates = float_leg.pay_dates
999 pv_float = 0.0
1000 annuity = 0.0
1002 # use analytical compounding shortcut:
1003 # for each coupon period, the compounded OIS rate ≈ (DF_start / DF_end - 1) / YF
1004 for i in range(num_periods):
1005 yf = dcc.yf(start_dates[i], end_dates[i])
1006 df_start = discount_curve.value(ref_date, start_dates[i])
1007 df_end = discount_curve.value(ref_date, end_dates[i])
1009 # period return implied by discount factors
1010 # period_rate = (df_start / df_end - 1.0) / yf # for DEBUG
1012 # PV of floating leg cashflow = notional * (DF_start - DF_end)
1013 pv_float += notionals[i] * (df_start - df_end)
1015 # annuity = sum of DF_end * yf * notional (denominator in swap rate)
1016 annuity += notionals[i] * yf * df_end
1018 if annuity == 0:
1019 raise ValueError("Zero annuity in OIS analytical pricing")
1021 # fair fixed rate = PV_float / Annuity
1022 return pv_float / annuity
1025#########################################################################
1026# FUNCTIONS
1027def get_projected_notionals(
1028 val_date: _Union[date, datetime],
1029 notional_structure: NotionalStructure,
1030 start_period: int,
1031 end_period: int,
1032 fx_forward_curve: DiscountCurve,
1033 fixing_map: FixingTable = None,
1034) -> _List[float]:
1035 """
1036 Generate a list with projected notionals, using FX forward curve if applicable, or fixing table(not yet implemented).
1038 Args:
1039 val_date (datetime): The valuation date.
1040 notional_structure (NotionalStructure): The notional structure class object.
1041 start_period (int): Start index of the period range.
1042 end_period (int): End index of the period range (exclusive).
1043 fx_forward_curve (FxForwardCurve): Required for resetting notionals.
1044 fixing_map (FixingTable): Not used (yet).
1045 """
1047 result = []
1049 # Check if this is a resetting notional structure
1050 if isinstance(notional_structure, ResettingNotionalStructure):
1051 if fx_forward_curve is None:
1052 raise ValueError("No FX forward curve provided for resetting leg!")
1054 for i in range(start_period, end_period):
1055 fixing_date = notional_structure.get_fixing_date(i)
1056 fx = fx_forward_curve.value(val_date, fixing_date)
1057 result.append(notional_structure.get_amount(i) * fx)
1058 elif isinstance(notional_structure, ConstNotionalStructure):
1059 for i in range(start_period, end_period):
1060 # print(i)
1061 # print(notional_structure.get_amount(i))
1062 result.append(notional_structure.get_amount(0))
1063 else:
1064 for i in range(start_period, end_period):
1065 # print(i)
1066 # print(notional_structure.get_amount(i))
1067 result.append(notional_structure.get_amount(i))
1069 return result
1072# getPricingData
1075# difference between func price and priceImpl???
1078# computeSwapSpread
1081if __name__ == "__main__":
1082 pass
1083 # InterestRateSwapPricer