1
1
import numpy as np
2
2
from datetime import datetime as dt , timedelta
3
3
from datetime import date
4
- from enum import IntEnum
5
4
from dataclasses import dataclass
6
5
from dateutil .relativedelta import relativedelta
7
6
from typing import List , Dict , Any
7
+ from FrequencyClass import Frequency
8
8
9
9
10
- class Frequency (IntEnum ):
11
- ANNUAL = 1
12
- BIANNUAL = 2
13
- TRIANNUAL = 3
14
- QUARTERLY = 4
15
- MONTHLY = 12
16
10
17
-
18
- @dataclass
11
+ @dataclass (frozen = True )
19
12
class CorpBond :
20
13
asset_id : int
21
14
nace : str
@@ -29,6 +22,32 @@ class CorpBond:
29
22
default_probability : float
30
23
market_price : float
31
24
25
+ def __post_init__ (self ) -> None :
26
+ if self .asset_id <= 0 :
27
+ raise ValueError ("Asset ID must be greater than 0" )
28
+ if self .coupon_rate < 0 :
29
+ raise ValueError ("Coupon rate cannot be negative" )
30
+ if self .coupon_rate > 1 :
31
+ raise ValueError ("Coupon rate cannot be greater than 1" )
32
+ if self .recovery_rate < 0 :
33
+ raise ValueError ("Recovery rate cannot be negative" )
34
+ if self .recovery_rate > 1 :
35
+ raise ValueError ("Recovery rate cannot be greater than 1" )
36
+ if self .default_probability < 0 :
37
+ raise ValueError ("Default probability cannot be negative" )
38
+ if self .default_probability > 1 :
39
+ raise ValueError ("Default probability cannot be greater than 1" )
40
+ if self .market_price < 0 :
41
+ raise ValueError ("Market price cannot be negative" )
42
+ if self .frequency not in [Frequency .MONTHLY , Frequency .QUARTERLY ,Frequency .TRIANNUAL , Frequency .BIANNUAL , Frequency .ANNUAL ]:
43
+ raise ValueError ("Frequency must be either Monthly, Quarterly,Triannual, SemiAnnual or Annual" )
44
+ if self .notional_amount <= 0 :
45
+ raise ValueError ("Notional amount must be greater than 0" )
46
+ if self .maturity_date <= self .issue_date :
47
+ raise ValueError ("Maturity date cannot be before issue date" )
48
+
49
+
50
+
32
51
@property
33
52
def dividend_amount (self ) -> float :
34
53
return self .coupon_rate * self .notional_amount
@@ -69,7 +88,7 @@ def __init__(self, corporate_bonds: dict[int,CorpBond] = None):
69
88
self .corporate_bonds = corporate_bonds
70
89
71
90
def IsEmpty (self )-> bool :
72
- if self .corporate_bonds == None :
91
+ if self .corporate_bonds is None :
73
92
return True
74
93
if len (self .corporate_bonds ) == 0 :
75
94
return True
@@ -80,14 +99,12 @@ def add(self,corp_bond: CorpBond) :
80
99
81
100
:type corp_bond: CorpBond
82
101
"""
83
- if self .corporate_bonds == None :
84
- self .corporate_bonds = {corp_bond .asset_id : corp_bond }
85
- else :
102
+ if self .corporate_bonds is not None :
86
103
self .corporate_bonds .update ({corp_bond .asset_id : corp_bond })
104
+ else :
105
+ self .corporate_bonds = {corp_bond .asset_id : corp_bond }
87
106
88
-
89
-
90
- def create_coupon_dates (self , modelling_date )-> dict :
107
+ def create_aggregate_coupon_dates (self , modelling_date )-> dict :
91
108
"""
92
109
Create the vector of dates at which the coupons are paid out and the total amounts for
93
110
all corporate bonds in the portfolio, for dates on or after the modelling date
@@ -109,13 +126,18 @@ def create_coupon_dates(self, modelling_date)->dict:
109
126
coupon_date : date
110
127
for asset_id in self .corporate_bonds :
111
128
corp_bond = self .corporate_bonds [asset_id ]
112
- dividend_amount = corp_bond .dividend_amount
113
129
for coupon_date in corp_bond .generate_coupon_dates (modelling_date ):
114
130
if coupon_date in coupons :
115
- coupons [coupon_date ] = dividend_amount + coupons [ coupon_date ]
131
+ coupons [coupon_date ] += corp_bond . dividend_amount
116
132
else :
117
- coupons .update ({coupon_date :dividend_amount })
133
+ coupons .update ({coupon_date :corp_bond . dividend_amount })
118
134
return coupons
135
+
136
+ """
137
+ def create_coupon_dates(self, modelling_date: date):
138
+ for corp_bond in self.corporate_bonds.values() :
139
+ corp_bond.generate_coupon_dates(modelling_date)
140
+ """
119
141
120
142
def create_maturity_cashflow (self , modelling_date : date ) -> dict :
121
143
"""
@@ -128,12 +150,10 @@ def create_maturity_cashflow(self, modelling_date: date) -> dict:
128
150
129
151
for asset_id in self .corporate_bonds :
130
152
corp_bond = self .corporate_bonds [asset_id ]
131
- maturity_amount = corp_bond .notional_amount
132
- maturity_date = corp_bond .maturity_date
133
153
if maturity_date in maturities :
134
- maturities [maturity_date ] = maturity_amount + maturities [ maturity_date ]
154
+ maturities [maturity_date ] += corp_bond . notional_amount
135
155
else :
136
- maturities .update ({maturity_date :maturity_amount })
156
+ maturities .update ({corp_bond . maturity_date :corp_bond . notional_amount })
137
157
return maturities
138
158
139
159
class CorporateBond :
0 commit comments