Skip to content

Commit e0921ff

Browse files
committed
CCY
1 parent abd3277 commit e0921ff

2 files changed

Lines changed: 61 additions & 28 deletions

File tree

ccy/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
set_new_country,
1515
)
1616
from .core.currency import (
17+
CCY,
1718
ccypair,
1819
currency,
1920
currency_pair,
@@ -36,6 +37,7 @@
3637
from .dates.period import Period, period
3738

3839
__all__ = [
40+
"CCY",
3941
"currency",
4042
"currencydb",
4143
"ccypair",

ccy/core/currency.py

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,32 @@
11
from __future__ import annotations
22

33
import 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

68
from .data import make_ccys
79
from .daycounter import DayCounter
810

911
usd_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

1231
def 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

Comments
 (0)