Coverage for src / qsmile / data / meta.py: 100%

28 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-01 22:47 +0000

1"""Smile metadata for coordinate transforms.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass 

6 

7import pandas as pd 

8 

9from qsmile.core.daycount import DayCount 

10 

11 

12@dataclass(frozen=True) 

13class SmileMetadata: 

14 """Parameters needed by coordinate transforms. 

15 

16 Parameters 

17 ---------- 

18 date : pd.Timestamp 

19 Valuation / pricing date. 

20 expiry : pd.Timestamp 

21 Expiry date. Must be strictly after ``date``. 

22 daycount : DayCount 

23 Day-count convention for computing the year fraction. 

24 Defaults to ``DayCount.ACT365``. 

25 forward : float | None 

26 Forward price. Must be positive when provided. 

27 discount_factor : float | None 

28 Discount factor. Must be positive when provided. 

29 sigma_atm : float | None 

30 ATM implied volatility. Must be positive when provided. 

31 Required for StandardisedStrike transforms. 

32 """ 

33 

34 date: pd.Timestamp 

35 expiry: pd.Timestamp 

36 daycount: DayCount = DayCount.ACT365 

37 forward: float | None = None 

38 discount_factor: float | None = None 

39 sigma_atm: float | None = None 

40 

41 @property 

42 def texpiry(self) -> float: 

43 """Year fraction derived from (date, expiry, daycount).""" 

44 return self.daycount.year_fraction(self.date, self.expiry) 

45 

46 def __post_init__(self) -> None: 

47 """Validate inputs.""" 

48 if self.expiry <= self.date: 

49 msg = f"expiry must be after date, got date={self.date}, expiry={self.expiry}" 

50 raise ValueError(msg) 

51 if self.forward is not None and self.forward <= 0: 

52 msg = f"forward must be positive, got {self.forward}" 

53 raise ValueError(msg) 

54 if self.discount_factor is not None and self.discount_factor <= 0: 

55 msg = f"discount_factor must be positive, got {self.discount_factor}" 

56 raise ValueError(msg) 

57 if self.sigma_atm is not None and self.sigma_atm <= 0: 

58 msg = f"sigma_atm must be positive, got {self.sigma_atm}" 

59 raise ValueError(msg)