Coverage for rivapy / tools / enums.py: 94%

414 statements  

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

1# -*- coding: utf-8 -*- 

2from enum import Enum as _Enum, unique as _unique 

3from dataclasses import dataclass 

4from typing import List 

5 

6 

7""" 

8 

9The following Enum sub-classes replace to corresponding former classes one-on-one. The main reason for this replacement 

10is the more comfortable iterations over the enumeration class members. Moreover, the Enum class provides potentially 

11useful functionalities like comparisons, pickling, ... Finally, the decorator @unique ensures unique enumeration values. 

12""" 

13 

14 

15class _MyEnum(_Enum): 

16 @classmethod 

17 def has_value(cls, value): 

18 return value in cls._value2member_map_ 

19 

20 @classmethod 

21 def to_string(cls, value) -> str: 

22 """Checks if given enum class contains the value and raises exception if not. If value is str 

23 

24 Args: 

25 enum (_type_): _description_ 

26 value (_type_): _description_ 

27 

28 Returns: 

29 str: _description_ 

30 """ 

31 

32 # Accept either the enum's stored value (e.g. 'Act360') or its name/key (e.g. 'ACT360') 

33 # Matching is case-insensitive for robustness. 

34 if isinstance(value, str): 

35 v = value.strip() 

36 # direct match against stored values (exact) 

37 for member in cls: 

38 if v == member.value: 

39 return member.value 

40 # exact name/key match 

41 if v in cls.__members__: 

42 return cls[v].value 

43 # case-insensitive match against names or values 

44 uv = v.upper() 

45 for member in cls: 

46 if uv == member.name.upper() or uv == str(member.value).upper(): 

47 return member.value 

48 raise Exception("Unknown " + cls.__name__ + ": " + value) 

49 if isinstance(value, cls): 

50 return value.value 

51 raise Exception("Given value " + str(value) + " does not belong to enum " + cls.__name__) 

52 

53 

54class _MyIntEnum(_Enum): 

55 @classmethod 

56 def has_value(cls, value): 

57 return value in cls._value2member_map_ 

58 

59 @classmethod 

60 def to_string(cls, value) -> str: 

61 """Checks if given enum class contains the value and raises exception if not. If value is str 

62 

63 Args: 

64 enum (_type_): _description_ 

65 value (_type_): _description_ 

66 

67 Returns: 

68 str: _description_ 

69 """ 

70 

71 # Accept either the enum name (string) or integer value. Return the enum NAME as string. 

72 if isinstance(value, str): 

73 v = value.strip() 

74 # direct name/key match 

75 if v in cls.__members__: 

76 return v 

77 # case-insensitive name match 

78 uv = v.upper() 

79 for member in cls: 

80 if uv == member.name.upper(): 

81 return member.name 

82 raise Exception("Unknown " + cls.__name__ + ": " + value) 

83 elif isinstance(value, int): 

84 try: 

85 return cls(value).name 

86 except Exception: 

87 raise Exception("Unknown " + cls.__name__ + ": " + str(value)) 

88 if isinstance(value, cls): 

89 return value.name 

90 raise Exception("Given value " + str(value) + " does not belong to enum " + cls.__name__) 

91 

92 

93@_unique 

94class InterpolationType(_MyEnum): 

95 CONSTANT = "CONSTANT" 

96 LINEAR = "LINEAR" 

97 LINEAR_LOG = "LINEAR_LOG" 

98 CONSTRAINED_SPLINE = "CONSTRAINED_SPLINE" 

99 HAGAN = "HAGAN" 

100 HAGAN_DF = "HAGAN_DF" 

101 

102 

103@_unique 

104class ExtrapolationType(_MyEnum): 

105 NONE = "NONE" 

106 CONSTANT = "CONSTANT" 

107 CONSTANT_DF = "CONSTANT_DF" 

108 LINEAR = "LINEAR" 

109 LINEAR_LOG = "LINEAR_LOG" 

110 

111 

112@_unique 

113class SecuritizationLevel(_MyEnum): 

114 NONE = "NONE" 

115 COLLATERALIZED = "COLLATERALIZED" 

116 SENIOR_SECURED = "SENIOR_SECURED" 

117 SENIOR_UNSECURED = "SENIOR_UNSECURED" 

118 SUBORDINATED = "SUBORDINATED" 

119 MEZZANINE = "MEZZANINE" 

120 EQUITY = "EQUITY" 

121 PREFERRED_SENIOR = "PREFERRED_SENIOR" 

122 NON_PREFERRED_SENIOR = "NON_PREFERRED_SENIOR" 

123 

124 

125# class SecuritizationLevel: 

126# NONE = 'NONE' 

127# COLLATERALIZED = 'COLLATERALIZED' #,,,'','SUBORDINATED','MEZZANINE','EQUITY'] 

128# SENIOR_SECURED = 'SENIOR_SECURED' 

129# SENIOR_UNSECURED = 'SENIOR_UNSECURED' 

130# SUBORDINATED = 'SUBORDINATED' 

131# MEZZANINE = 'MEZZANINE' 

132# EQUITY = 'EQUITY' 

133 

134# @_unique 

135# class ProductType(_MyEnum): 

136# BOND = 'BOND' 

137# CALLABLE_BOND = 'CALLABLE_BOND' 

138 

139 

140# @_unique 

141# class PricerType(_MyEnum): 

142# ANALYTIC = 'ANALYTIC' 

143# PDE = 'PDE' 

144# MONTE_CARLO = 'MONTE_CARLO' 

145# COMBO = 'COMBO' 

146 

147 

148@_unique 

149class EnergyTimeGridStructure(_MyEnum): 

150 BASE = "BASE" 

151 PEAK = "PEAK" 

152 OFFPEAK = "OFFPEAK" 

153 

154 

155@_unique 

156class Model(_MyEnum): 

157 BLACK76 = "BLACK76" 

158 CIR = "CIR" 

159 HULL_WHITE = "HULL_WHITE" 

160 HESTON = "HESTON" 

161 LV = "LV" 

162 GBM = "GBM" 

163 G2PP = "G2PP" 

164 VASICEK = "VASICEK" 

165 

166 

167# class Model: 

168# BLACK76 = 'BLACK76' 

169# CIR ='CIR' 

170# HULL_WHITE = 'HULL_WHITE' 

171# HESTON = 'HESTON' 

172# LV = 'LV' 

173# GBM = 'GBM' 

174# G2PP = 'G2PP' 

175# VASICEK = 'VASICEK' 

176 

177 

178@_unique 

179class Period(_MyEnum): 

180 A = "A" 

181 SA = "SA" 

182 Q = "Q" 

183 M = "M" 

184 D = "D" 

185 

186 

187# class Period: 

188# A = 'A' 

189# SA = 'SA' 

190# Q = 'Q' 

191# M = 'M' 

192# D = 'D' 

193 

194 

195@_unique 

196class RollConvention(_MyEnum): 

197 FOLLOWING = "Following" 

198 MODIFIED_FOLLOWING = "ModifiedFollowing" 

199 MODIFIED_FOLLOWING_EOM = "ModifiedFollowingEOM" 

200 MODIFIED_FOLLOWING_BIMONTHLY = "ModifiedFollowingBimonthly" 

201 PRECEDING = "Preceding" 

202 MODIFIED_PRECEDING = "ModifiedPreceding" 

203 NEAREST = "Nearest" 

204 UNADJUSTED = "Unadjusted" 

205 

206 

207@_unique 

208class RollRule(_MyEnum): 

209 """Roll Rules are used for calculating daten when building a schedule and, therefore, rolling forward (or backward) dates by periods or frequencies""" 

210 

211 NONE = "NONE" # no roll rule applied, day of a month drifts if adjustments are made acc. to bdc, i.e. the anchor date changes 

212 EOM = "EOM" # rolls from month end to month end, ambiguous days are adjusted to the end of the month, i.e. Mar 30, 

213 DOM = "DOM" # rolls from a specific day of month to the same day of month, ambiguous days are adjusted to the same day of month, i.e. Mar 30, 

214 IMM = "IMM" # rolls to the third Wednesday of the month, i.e. Mar 30, rolls to Mar 20, if Mar 20 is a weekend, it rolls to Mar 22 

215 

216 

217@_unique 

218class DayCounterType(_MyEnum): 

219 ACT_ACT = "ActAct" 

220 Act365Fixed = "Act365Fixed" 

221 ACT360 = "Act360" 

222 ThirtyU360 = "30U360" 

223 ThirtyE360 = "30E360" 

224 ACT252 = "Act252" 

225 Thirty360ISDA = "30360ISDA" 

226 ActActICMA = "ActActICMA" 

227 

228 

229@_unique 

230class InflationInterpolation(_MyEnum): 

231 UNDEFINED = "UNDEFINED" 

232 GERMAN = "GERMAN" 

233 JAPAN = "JAPAN" 

234 CONSTANT = "CONSTANT" 

235 

236 

237@_unique 

238class Sector(_MyEnum): 

239 UNDEFINED = "UNDEFINED" 

240 # BASIC_MATERIALS = 'BasicMaterials' 

241 CONGLOMERATES = "Conglomerates" 

242 CONSUMER_GOODS = "ConsumerGoods" 

243 # FINANCIAL = 'Financial' 

244 # HEALTHCARE = 'Healthcare' 

245 # INDUSTRIAL_GOODS = 'IndustrialGoods' 

246 SERVICES = "Services" 

247 # TECHNOLOGY = 'Technology' 

248 # UTILITIES = 'Utilities' 

249 

250 COMMUNICATION_SERVICES = "CommunicationServices" 

251 CONSUMER_STAPLES = "ConsumerStaples" 

252 CONSUMER_DISCRETIONARY = "ConsumerDiscretionary" 

253 ENERGY = "Energy" 

254 FINANCIAL = "Financial" 

255 HEALTH_CARE = "HealthCare" 

256 INDUSTRIALS = "Industrials" 

257 INFORMATION_TECHNOLOGY = "InformationTechnology" 

258 MATERIALS = "Materials" 

259 REAL_ESTATE = "RealEstate" 

260 UTILITIES = "Utilities" 

261 

262 

263@_unique 

264class ESGRating(_MyEnum): # see MSCI ESG ratings 

265 AAA = "AAA" 

266 AA = "AA" 

267 A = "A" 

268 BBB = "BBB" 

269 BB = "BB" 

270 B = "B" 

271 CCC = "CCC" 

272 

273 

274@_unique 

275class Rating(_MyEnum): 

276 # cf. https://www.moneyland.ch/de/vergleich-rating-agenturen 

277 AAA = "AAA" 

278 AA_PLUS = "AA+" 

279 AA = "AA" 

280 AA_MINUS = "AA-" 

281 A_PLUS = "A+" 

282 A = "A" 

283 A_MINUS = "A-" 

284 BBB_PLUS = "BBB+" 

285 BBB = "BBB" 

286 BBB_MINUS = "BBB-" 

287 BB_PLUS = "BB+" 

288 BB = "BB" 

289 BB_MINUS = "BB-" 

290 B_PLUS = "B+" 

291 B = "B" 

292 B_MINUS = "B-" 

293 CCC_PLUS = "CCC+" 

294 CCC = "CCC" 

295 CCC_MINUS = "CCC-" 

296 CC = "CC" 

297 C = "C" 

298 D = "D" 

299 NONE = "NONE" # not rated 

300 

301 

302# class ProductType: 

303# BOND = 'BOND' 

304# CALLABLE_BOND = 'CALLABLE_BOND' 

305 

306 

307class PricerType: 

308 ANALYTIC = "ANALYTIC" 

309 PDE = "PDE" 

310 MONTE_CARLO = "MONTE_CARLO" 

311 COMBO = "COMBO" 

312 

313 

314@_unique 

315class VolatilityStickyness(_MyEnum): 

316 NONE = "NONE" 

317 StickyStrike = "StickyStrike" 

318 StickyXStrike = "StickyXStrike" 

319 StickyFwdMoneyness = "StickyFwdMoneyness" 

320 

321 

322@_unique 

323class Currency(_MyEnum): 

324 AED = "AED" 

325 AFN = "AFN" 

326 ALL = "ALL" 

327 AMD = "AMD" 

328 ANG = "ANG" 

329 AOA = "AOA" 

330 ARS = "ARS" 

331 AUD = "AUD" 

332 AWG = "AWG" 

333 AZN = "AZN" 

334 BAM = "BAM" 

335 BBD = "BBD" 

336 BDT = "BDT" 

337 BGN = "BGN" 

338 BHD = "BHD" 

339 BIF = "BIF" 

340 BMD = "BMD" 

341 BND = "BND" 

342 BOB = "BOB" 

343 BRL = "BRL" 

344 BSD = "BSD" 

345 BTN = "BTN" 

346 BWP = "BWP" 

347 BYR = "BYR" 

348 BZD = "BZD" 

349 CAD = "CAD" 

350 CDF = "CDF" 

351 CHF = "CHF" 

352 CLP = "CLP" 

353 CNH = "CNH" 

354 CNY = "CNY" 

355 COP = "COP" 

356 CRC = "CRC" 

357 CUC = "CUC" 

358 CUP = "CUP" 

359 CVE = "CVE" 

360 CZK = "CZK" 

361 DJF = "DJF" 

362 DKK = "DKK" 

363 DOP = "DOP" 

364 DZD = "DZD" 

365 EGP = "EGP" 

366 ERN = "ERN" 

367 ETB = "ETB" 

368 EUR = "EUR" 

369 FJD = "FJD" 

370 FKP = "FKP" 

371 GBP = "GBP" 

372 GEL = "GEL" 

373 GGP = "GGP" 

374 GHS = "GHS" 

375 GIP = "GIP" 

376 GMD = "GMD" 

377 GNF = "GNF" 

378 GTQ = "GTQ" 

379 GYD = "GYD" 

380 HKD = "HKD" 

381 HNL = "HNL" 

382 HRK = "HRK" 

383 HTG = "HTG" 

384 HUF = "HUF" 

385 IDR = "IDR" 

386 ILS = "ILS" 

387 IMP = "IMP" 

388 INR = "INR" 

389 IQD = "IQD" 

390 IRR = "IRR" 

391 ISK = "ISK" 

392 JEP = "JEP" 

393 JMD = "JMD" 

394 JOD = "JOD" 

395 JPY = "JPY" 

396 KES = "KES" 

397 KGS = "KGS" 

398 KHR = "KHR" 

399 KMF = "KMF" 

400 KPW = "KPW" 

401 KRW = "KRW" 

402 KWD = "KWD" 

403 KYD = "KYD" 

404 KZT = "KZT" 

405 LAK = "LAK" 

406 LBP = "LBP" 

407 LKR = "LKR" 

408 LRD = "LRD" 

409 LSL = "LSL" 

410 LTL = "LTL" 

411 LVL = "LVL" 

412 LYD = "LYD" 

413 MAD = "MAD" 

414 MDL = "MDL" 

415 MGA = "MGA" 

416 MKD = "MKD" 

417 MMK = "MMK" 

418 MNT = "MNT" 

419 MOP = "MOP" 

420 MRO = "MRO" 

421 MUR = "MUR" 

422 MVR = "MVR" 

423 MWK = "MWK" 

424 MXN = "MXN" 

425 MYR = "MYR" 

426 MZN = "MZN" 

427 NAD = "NAD" 

428 NGN = "NGN" 

429 NIO = "NIO" 

430 NOK = "NOK" 

431 NPR = "NPR" 

432 NZD = "NZD" 

433 OMR = "OMR" 

434 PAB = "PAB" 

435 PEN = "PEN" 

436 PGK = "PGK" 

437 PHP = "PHP" 

438 PKR = "PKR" 

439 PLN = "PLN" 

440 PYG = "PYG" 

441 QAR = "QAR" 

442 RON = "RON" 

443 RSD = "RSD" 

444 RUB = "RUB" 

445 RWF = "RWF" 

446 SAR = "SAR" 

447 SBD = "SBD" 

448 SCR = "SCR" 

449 SDG = "SDG" 

450 SEK = "SEK" 

451 SGD = "SGD" 

452 SHP = "SHP" 

453 SLL = "SLL" 

454 SOS = "SOS" 

455 SPL = "SPL" 

456 SRD = "SRD" 

457 STD = "STD" 

458 SVC = "SVC" 

459 SYP = "SYP" 

460 SZL = "SZL" 

461 THB = "THB" 

462 TJS = "TJS" 

463 TMT = "TMT" 

464 TND = "TND" 

465 TOP = "TOP" 

466 TRY = "TRY" 

467 TTD = "TTD" 

468 TVD = "TVD" 

469 TWD = "TWD" 

470 TZS = "TZS" 

471 UAH = "UAH" 

472 UGX = "UGX" 

473 USD = "USD" 

474 UYU = "UYU" 

475 UZS = "UZS" 

476 VEF = "VEF" 

477 VND = "VND" 

478 VUV = "VUV" 

479 WST = "WST" 

480 XAF = "XAF" 

481 XAG = "XAG" 

482 XAU = "XAU" 

483 XPD = "XPD" 

484 XPT = "XPT" 

485 XCD = "XCD" 

486 XDR = "XDR" 

487 XOF = "XOF" 

488 XPF = "XPF" 

489 YER = "YER" 

490 ZAR = "ZAR" 

491 ZMW = "ZMW" 

492 ZWD = "ZWD" 

493 

494 

495class Country(_MyEnum): 

496 DE = "DE" 

497 FR = "FR" 

498 CA = "CA" 

499 US = "US" 

500 GB = "GB" 

501 JP = "JP" 

502 CN = "CN" 

503 

504 

505class IrLegType(_MyEnum): 

506 """Enums object for the type of interest rate swap legs.""" 

507 

508 FIXED = "FIXED" 

509 FLOAT = "FLOAT" 

510 OIS = "OIS" 

511 

512 @staticmethod 

513 def from_string(s: str) -> str: 

514 s = s.upper() 

515 if s in (IrLegType.FIXED, IrLegType.FLOAT, IrLegType.OIS): 

516 return s 

517 raise ValueError(f"Unknown leg type '{s}'") 

518 

519 # @staticmethod 

520 # def to_string(cls, value: str) -> str: 

521 # return value.upper() 

522 

523 

524class Instrument(_MyEnum): 

525 """Enums object for the type of instrument.""" 

526 

527 IRS = "IRS" 

528 TBS = "TBS" 

529 BS = "BS" 

530 DEPOSIT = "DEPOSIT" 

531 OIS = "OIS" 

532 FRA = "FRA" 

533 FXF = "FXF" 

534 

535 

536@dataclass(frozen=True) 

537class IRIndexMetadata: 

538 name: str 

539 currency: str 

540 tenor: str 

541 spot_days: int 

542 business_day_convention: str 

543 day_count_convention: str 

544 roll_convention: str 

545 calendar: str 

546 aliases: List[str] 

547 

548 

549class InterestRateIndex(_MyEnum): 

550 EUR1M = IRIndexMetadata( 

551 name="EURIBOR 1M", 

552 currency="EUR", 

553 tenor="1M", 

554 spot_days=2, 

555 business_day_convention="ModifiedFollowing", 

556 day_count_convention="ACT360", 

557 roll_convention="EOM", 

558 calendar="TARGET", 

559 aliases=["EUR1M", " EUR 1M", "EURIBOR 1M"], 

560 ) 

561 EUR3M = IRIndexMetadata( 

562 name="EURIBOR 3M", 

563 currency="EUR", 

564 tenor="3M", 

565 spot_days=2, 

566 business_day_convention="ModifiedFollowing", 

567 day_count_convention="ACT360", 

568 roll_convention="EOM", 

569 calendar="TARGET", 

570 aliases=["EUR3M", " EUR 3M", "EURIBOR 3M", "EURIBOR_3M"], 

571 ) 

572 EUR6M = IRIndexMetadata( 

573 name="EURIBOR 6M", 

574 currency="EUR", 

575 tenor="6M", 

576 spot_days=2, 

577 business_day_convention="ModifiedFollowing", 

578 day_count_convention="ACT360", 

579 roll_convention="EOM", 

580 calendar="TARGET", 

581 aliases=["EUR6M", "EUR 6M", "EURIBOR 6M"], 

582 ) 

583 EUR12M = IRIndexMetadata( 

584 name="EURIBOR 12M", 

585 currency="EUR", 

586 tenor="12M", 

587 spot_days=2, 

588 business_day_convention="ModifiedFollowing", 

589 day_count_convention="ACT360", 

590 roll_convention="EOM", 

591 calendar="TARGET", 

592 aliases=["EUR12M", "EUR 12M", "EURIBOR 12M", "EUR1Y", "EUR_1Y", "EURIBOR_1Y"], 

593 ) 

594 ESTR = IRIndexMetadata( 

595 name="€STR", 

596 currency="EUR", 

597 tenor="O/N", 

598 spot_days=0, 

599 business_day_convention="Following", 

600 day_count_convention="ACT360", 

601 roll_convention="None", 

602 calendar="TARGET", 

603 aliases=["EURSTR", "EUR STR", "€STR", "EUR1D", "EUR O/N"], 

604 ) 

605 

606 

607def get_index_by_alias(alias: str) -> InterestRateIndex: 

608 alias = alias.strip().upper() 

609 for index in InterestRateIndex: 

610 value = index.value 

611 aliases = [a.upper() for a in value.aliases] 

612 if alias in aliases or alias == index.name.upper(): 

613 print("erfolgreich") 

614 str = index.value.name 

615 return index 

616 raise ValueError(f"Unknown index alias: {alias}")