Coverage for rivapy / pricing / deposit_pricing.py: 72%

43 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-11-27 14:36 +0000

1from datetime import datetime, date 

2from rivapy.marketdata import DiscountCurve 

3from rivapy.pricing.pricing_request import PricingRequest 

4from rivapy.pricing._logger import logger 

5from rivapy.instruments.deposit_specifications import DepositSpecification 

6from rivapy.tools.datetools import DayCounter 

7from rivapy.pricing.bond_pricing import DeterministicCashflowPricer 

8from typing import Tuple, Union as _Union, List as _List 

9from rivapy.tools._validators import _check_start_at_or_before_end 

10 

11 

12class DepositPricer(DeterministicCashflowPricer): 

13 

14 def __init__( 

15 self, 

16 val_date: _Union[date, datetime], 

17 deposit_spec: DepositSpecification, 

18 discount_curve: DiscountCurve, 

19 ): 

20 """Create a pricer for a deposit instrument. 

21 

22 Args: 

23 val_date (date | datetime): Valuation date used for pricing. 

24 deposit_spec (DepositSpecification): Deposit specification containing schedule 

25 and contractual parameters. 

26 discount_curve (DiscountCurve): Discount curve used for discounting cashflows. 

27 """ 

28 

29 self._val_date = val_date 

30 self._spec = deposit_spec 

31 self._discount_curve = discount_curve 

32 self._validate_pricer_dates() 

33 

34 def _validate_pricer_dates(self): 

35 """Validate that the pricer valuation date is consistent with the discount curve refdate. 

36 

37 This updates ``self._discount_curve.refdate`` and ``self._val_date`` to a 

38 canonical ordering using the project's date validators. 

39 """ 

40 self._discount_curve.refdate, self._val_date = _check_start_at_or_before_end(self._discount_curve.refdate, self._val_date) 

41 

42 def expected_cashflows(self) -> _List[Tuple[datetime, float]]: 

43 """Return expected cashflows for the configured deposit and valuation date. 

44 

45 Returns: 

46 List[Tuple[datetime, float]]: List of (pay_date, amount) tuples. 

47 """ 

48 return DeterministicCashflowPricer.expected_cashflows(self._spec, self._val_date) 

49 

50 @staticmethod 

51 def get_expected_cashflows( 

52 specification: DepositSpecification, val_date: _Union[datetime.date, datetime, None] = None 

53 ) -> _List[Tuple[datetime, float]]: 

54 """Static helper: get expected cashflows for a deposit specification. 

55 

56 Args: 

57 specification (DepositSpecification): The deposit specification. 

58 val_date (date | datetime, optional): Valuation date to use. If ``None``, 

59 the specification's default behavior is used. 

60 

61 Returns: 

62 List[Tuple[datetime, float]]: List of (pay_date, amount) tuples. 

63 """ 

64 if val_date is None: 

65 return DeterministicCashflowPricer.get_expected_cashflows(specification) 

66 else: 

67 return DeterministicCashflowPricer.get_expected_cashflows(specification, val_date) 

68 

69 def price(self) -> float: 

70 """Return the present value of the configured deposit using the provided curves. 

71 

72 Returns: 

73 float: Present value. 

74 """ 

75 return self.get_price(self._val_date, self._spec, self._discount_curve) 

76 

77 @staticmethod 

78 def get_price(val_date: datetime, specification: DepositSpecification, discount_curve: DiscountCurve) -> float: 

79 """Calculate present value of a deposit using the provided discount curve. 

80 

81 Args: 

82 val_date (date | datetime): Valuation date. 

83 specification (DepositSpecification): Deposit contract specification. 

84 discount_curve (DiscountCurve): Discount curve used for discounting. 

85 

86 Returns: 

87 float: Present value (PV) computed from discounted expected cashflows. 

88 """ 

89 

90 return DeterministicCashflowPricer.get_pv_cashflows(val_date, specification, discount_curve) 

91 

92 def implied_simply_compounded_rate(self) -> float: 

93 """Return the implied simply compounded rate for the configured deposit. 

94 

95 The implied simply compounded rate is the rate that makes the deposit's PV 

96 equal to zero under a simple-compounding assumption. 

97 

98 Returns: 

99 float: Implied simply compounded rate. 

100 """ 

101 return DepositPricer.get_implied_simply_compounded_rate(self._val_date, self._spec, self._discount_curve) 

102 

103 @staticmethod 

104 def get_implied_simply_compounded_rate(val_date: datetime, specification: DepositSpecification, discount_curve: DiscountCurve) -> float: 

105 """Compute the implied simply compounded rate that sets deposit PV to zero. 

106 

107 The implementation assumes simple compounding: D(t) = 1 / (1 + rate * dt). 

108 

109 Args: 

110 val_date (date | datetime): Valuation date. 

111 specification (DepositSpecification): Deposit specification with start/end dates 

112 and day count convention. 

113 discount_curve (DiscountCurve): Discount curve used to obtain forward discount 

114 factor between start and end. 

115 

116 Returns: 

117 float: The implied simply compounded rate. 

118 

119 Raises: 

120 ValueError: If ``discount_curve`` is not an instance of DiscountCurve. 

121 """ 

122 

123 start_date = specification.start_date 

124 # maturity_date = specification.maturity_date # date of legal end of the deposit and when payment needs to be made 

125 end_date = ( 

126 specification.end_date 

127 ) # period over which interest is calculated # this is the period we need to use as it is the actual accrual period, regardless of when payment is due 

128 daycountconvention = specification.day_count_convention 

129 

130 if isinstance(discount_curve, DiscountCurve): 

131 cont_df = discount_curve.value_fwd(val_date, start_date, end_date) 

132 else: 

133 raise ValueError("Discount curve must be of type DiscountCurve") 

134 

135 dcc = DayCounter(daycountconvention) 

136 dt = dcc.yf(start_date, end_date) 

137 simple_rate = ((1 / cont_df) - 1) / dt 

138 return simple_rate