11from __future__ import annotations
22
33import sys
4- from typing import Any , Callable , NamedTuple
4+ from typing import Any , Callable , NamedTuple , Self
5+
6+ from pydantic import BaseModel , Field , computed_field
57
68from .data import make_ccys
79from .daycounter import DayCounter
810
911usd_order = 5
1012
13+ _CCY_FIELDS = (
14+ "code" ,
15+ "isonumber" ,
16+ "twoletterscode" ,
17+ "order" ,
18+ "name" ,
19+ "rounding" ,
20+ "default_country" ,
21+ "fixeddc" ,
22+ "floatdc" ,
23+ "fixedfreq" ,
24+ "floatfreq" ,
25+ "future" ,
26+ "symbol_raw" ,
27+ "html" ,
28+ )
29+
1130
1231def to_string (v : Any ) -> str :
1332 if isinstance (v , bytes ):
@@ -24,31 +43,43 @@ def overusdfuni(v1: float) -> float:
2443 return 1.0 / v1
2544
2645
27- class ccy (NamedTuple ):
28- code : str
29- isonumber : str
30- twoletterscode : str
31- order : int
32- name : str
33- rounding : int
34- default_country : str
35- fixeddc : DayCounter = DayCounter .ACT365
36- floatdc : DayCounter = DayCounter .ACT365
37- fixedfreq : str = ""
38- floatfreq : str = ""
39- future : str = ""
40- symbol_raw : str = r"\00a4"
41- html : str = ""
42-
46+ class CCY (BaseModel , frozen = True ):
47+ code : str = Field (description = "ISO 4217 three-letter currency code" )
48+ isonumber : str = Field (description = "ISO 4217 numeric code" )
49+ twoletterscode : str = Field (description = "Internal two-letter code" )
50+ order : int = Field (description = "Default ordering in currency pairs" )
51+ name : str = Field (description = "Currency name" )
52+ rounding : int = Field (description = "Number of decimal places" )
53+ default_country : str = Field (description = "Default ISO 3166-1 alpha-2 country code" )
54+ fixeddc : DayCounter = Field (
55+ default = DayCounter .ACT365 , description = "Fixed leg day count convention"
56+ )
57+ floatdc : DayCounter = Field (
58+ default = DayCounter .ACT365 , description = "Float leg day count convention"
59+ )
60+ fixedfreq : str = Field (default = "" , description = "Fixed leg payment frequency" )
61+ floatfreq : str = Field (default = "" , description = "Float leg payment frequency" )
62+ future : str = Field (default = "" , description = "Futures contract ticker" )
63+ symbol_raw : str = Field (
64+ default = r"\u00a4" ,
65+ description = "Raw unicode escape string for the currency symbol" ,
66+ )
67+ html : str = Field (default = "" , description = "HTML entity for the currency symbol" )
68+
69+ @computed_field # type: ignore[prop-decorator]
4370 @property
4471 def symbol (self ) -> str :
72+ """Currency symbol decoded from the unicode escape string"""
4573 return self .symbol_raw .encode ("utf-8" ).decode ("unicode_escape" )
4674
4775 def __eq__ (self , other : Any ) -> bool :
48- if isinstance (other , ccy ):
76+ if isinstance (other , CCY ):
4977 return other .code == self .code
5078 return False
5179
80+ def __hash__ (self ) -> int :
81+ return hash (self .code )
82+
5283 def description (self ) -> str :
5384 if self .order > usd_order :
5485 v = "USD / %s" % self .code
@@ -60,9 +91,7 @@ def description(self) -> str:
6091 return "Dollar"
6192
6293 def info (self ) -> dict [str , Any ]:
63- data = self ._asdict ()
64- data ["symbol" ] = self .symbol
65- return data
94+ return self .model_dump ()
6695
6796 def printinfo (self , stream : Any | None = None ) -> None :
6897 info = self .info ()
@@ -73,7 +102,7 @@ def printinfo(self, stream: Any | None = None) -> None:
73102 def __str__ (self ) -> str :
74103 return self .code
75104
76- def swap (self , c2 : ccy ) -> tuple [bool , ccy , ccy ]:
105+ def swap (self , c2 : Self ) -> tuple [bool , Self , Self ]:
77106 """
78107 put the order of currencies as market standard
79108 """
@@ -108,7 +137,7 @@ def as_cross(self, delimiter: str = "") -> str:
108137 else :
109138 return "%s%sUSD" % (self .code , delimiter )
110139
111- def spot (self , c2 : ccy , v1 : float , v2 : float ) -> float :
140+ def spot (self , c2 : Self , v1 : float , v2 : float ) -> float :
112141 if self .order > c2 .order :
113142 vt = v1
114143 v1 = v2
@@ -125,8 +154,8 @@ class ccy_pair(NamedTuple):
125154 XXXYYY means 1 unit of of XXX cost XXXYYY units of YYY
126155 """
127156
128- ccy1 : ccy
129- ccy2 : ccy
157+ ccy1 : CCY
158+ ccy2 : CCY
130159
131160 @property
132161 def code (self ) -> str :
@@ -154,9 +183,11 @@ def over(self, name: str = "usd") -> ccy_pair:
154183 return self
155184
156185
157- class ccydb (dict [str , ccy ]):
186+ class ccydb (dict [str , CCY ]):
158187 def insert (self , * args : Any , ** kwargs : Any ) -> None :
159- c = ccy (* args , ** kwargs )
188+ kw = dict (zip (_CCY_FIELDS , args ))
189+ kw .update (kwargs )
190+ c = CCY (** kw )
160191 self [c .code ] = c
161192
162193
@@ -175,7 +206,7 @@ def ccypairsdb() -> dict[str, ccy_pair]:
175206 return _ccypairs
176207
177208
178- def currency (code : str | ccy ) -> ccy :
209+ def currency (code : str | CCY ) -> CCY :
179210 c = currencydb ()
180211 return c [str (code ).upper ()]
181212
0 commit comments