1use crate::dual::{ADOrder, Number, NumberArray2};
4use crate::fx::rates::{Ccy, FXRate, FXRates};
5use bincode::config::legacy;
6use bincode::serde::{decode_from_slice, encode_to_vec};
7use chrono::prelude::*;
8use ndarray::Axis;
9use pyo3::prelude::*;
10use pyo3::exceptions::PyValueError;
12use crate::json::json_py::DeserializedObj;
14use crate::json::JSON;
15use pyo3::types::PyBytes;
16
17#[pymethods]
18impl Ccy {
19 #[new]
20 fn new_py(name: &str) -> PyResult<Self> {
21 Ccy::try_new(name)
22 }
23
24 #[getter]
25 #[pyo3(name = "name")]
26 fn name_py(&self) -> PyResult<String> {
27 Ok(self.name.to_string())
28 }
29
30 fn __repr__(&self) -> PyResult<String> {
31 Ok(format!("<Ccy: '{}'>", self.name))
32 }
33
34 fn __eq__(&self, other: &Self) -> bool {
35 self.name == other.name
36 }
37
38 pub fn __setstate__(&mut self, state: Bound<'_, PyBytes>) -> PyResult<()> {
40 *self = decode_from_slice(state.as_bytes(), legacy()).unwrap().0;
41 Ok(())
42 }
43 pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
44 Ok(PyBytes::new(py, &encode_to_vec(&self, legacy()).unwrap()))
45 }
46 pub fn __getnewargs__<'py>(&self) -> PyResult<(String,)> {
47 Ok(((*(self.name)).clone(),))
48 }
49}
50
51#[pymethods]
52impl FXRate {
53 #[new]
54 #[pyo3(signature = (lhs, rhs, rate, settlement=None))]
55 fn new_py(
56 lhs: &str,
57 rhs: &str,
58 rate: Number,
59 settlement: Option<NaiveDateTime>,
60 ) -> PyResult<Self> {
61 FXRate::try_new(lhs, rhs, rate, settlement)
62 }
63
64 #[getter]
65 #[pyo3(name = "rate")]
66 fn rate_py(&self) -> PyResult<Number> {
67 Ok(self.rate.clone())
68 }
69
70 #[getter]
71 #[pyo3(name = "ad")]
72 fn ad_py(&self) -> u8 {
73 match self.rate {
74 Number::F64(_) => 0,
75 Number::Dual(_) => 1,
76 Number::Dual2(_) => 2,
77 }
78 }
79
80 #[getter]
81 #[pyo3(name = "settlement")]
82 fn settlement_py(&self) -> PyResult<Option<NaiveDateTime>> {
83 Ok(self.settlement)
84 }
85
86 #[getter]
87 #[pyo3(name = "pair")]
88 fn pair_py(&self) -> PyResult<String> {
89 Ok(format!("{}", self.pair))
90 }
91
92 fn __repr__(&self) -> PyResult<String> {
93 match &self.rate {
94 Number::F64(f) => Ok(format!("<FXRate: '{}' {}>", self.pair, f)),
95 Number::Dual(d) => Ok(format!(
96 "<FXRate: '{}' <Dual: {}, ..>>",
97 self.pair,
98 d.real()
99 )),
100 Number::Dual2(d) => Ok(format!(
101 "<FXRate: '{}' <Dual2: {}, ..>>",
102 self.pair,
103 d.real()
104 )),
105 }
106 }
107
108 fn __eq__(&self, other: &Self) -> bool {
109 self == other
110 }
111
112 pub fn __setstate__(&mut self, state: Bound<'_, PyBytes>) -> PyResult<()> {
114 *self = decode_from_slice(state.as_bytes(), legacy()).unwrap().0;
115 Ok(())
116 }
117 pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
118 Ok(PyBytes::new(py, &encode_to_vec(&self, legacy()).unwrap()))
119 }
120 pub fn __getnewargs__<'py>(&self) -> PyResult<(String, String, Number, Option<NaiveDateTime>)> {
121 Ok((
122 (*(self.pair.0.name)).clone(),
123 (*(self.pair.1.name)).clone(),
124 self.rate.clone(),
125 self.settlement,
126 ))
127 }
128}
129
130#[pymethods]
131impl FXRates {
132 #[new]
149 #[pyo3(signature = (fx_rates, base=None))]
150 fn new_py(fx_rates: Vec<FXRate>, base: Option<Ccy>) -> PyResult<Self> {
151 FXRates::try_new(fx_rates, base)
152 }
153
154 #[getter]
155 #[pyo3(name = "fx_rates")]
156 fn fx_rates_py(&self) -> PyResult<Vec<FXRate>> {
157 Ok(self.fx_rates.clone())
158 }
159
160 #[getter]
161 #[pyo3(name = "currencies")]
162 fn currencies_py(&self) -> PyResult<Vec<Ccy>> {
163 Ok(Vec::from_iter(self.currencies.iter().cloned()))
164 }
165
166 #[getter]
167 #[pyo3(name = "ad")]
168 fn ad_py(&self) -> PyResult<u8> {
169 match &self.fx_array {
170 NumberArray2::F64(_) => Ok(0),
171 NumberArray2::Dual(_) => Ok(1),
172 NumberArray2::Dual2(_) => Ok(2),
173 }
174 }
175
176 #[getter]
177 #[pyo3(name = "base")]
178 fn base_py(&self) -> PyResult<Ccy> {
179 Ok(self.currencies[0])
180 }
181
182 #[getter]
183 #[pyo3(name = "fx_vector")]
184 fn fx_vector_py(&self) -> PyResult<Vec<Number>> {
185 match &self.fx_array {
186 NumberArray2::F64(arr) => Ok(arr.row(0).iter().map(|x| Number::F64(*x)).collect()),
187 NumberArray2::Dual(arr) => {
188 Ok(arr.row(0).iter().map(|x| Number::Dual(x.clone())).collect())
189 }
190 NumberArray2::Dual2(arr) => Ok(arr
191 .row(0)
192 .iter()
193 .map(|x| Number::Dual2(x.clone()))
194 .collect()),
195 }
196 }
197
198 #[getter]
199 #[pyo3(name = "fx_array")]
200 fn fx_array_py(&self) -> PyResult<Vec<Vec<Number>>> {
201 match &self.fx_array {
202 NumberArray2::F64(arr) => Ok(arr
203 .lanes(Axis(1))
204 .into_iter()
205 .map(|row| row.iter().map(|d| Number::F64(*d)).collect())
206 .collect()),
207 NumberArray2::Dual(arr) => Ok(arr
208 .lanes(Axis(1))
209 .into_iter()
210 .map(|row| row.iter().map(|d| Number::Dual(d.clone())).collect())
211 .collect()),
212 NumberArray2::Dual2(arr) => Ok(arr
213 .lanes(Axis(1))
214 .into_iter()
215 .map(|row| row.iter().map(|d| Number::Dual2(d.clone())).collect())
216 .collect()),
217 }
218 }
219
220 #[pyo3(name = "get_ccy_index")]
221 fn get_ccy_index_py(&self, currency: Ccy) -> Option<usize> {
222 self.get_ccy_index(¤cy)
223 }
224
225 #[pyo3(name = "rate")]
226 fn rate_py(&self, lhs: &Ccy, rhs: &Ccy) -> PyResult<Option<Number>> {
227 Ok(self.rate(lhs, rhs))
228 }
229
230 #[pyo3(name = "update")]
231 fn update_py(&mut self, fx_rates: Vec<FXRate>) -> PyResult<()> {
232 self.update(fx_rates)
233 }
234
235 #[pyo3(name = "set_ad_order")]
236 fn set_ad_order_py(&mut self, ad: ADOrder) -> PyResult<()> {
237 self.set_ad_order(ad)?;
238 Ok(())
239 }
240
241 #[pyo3(name = "to_json")]
243 fn to_json_py(&self) -> PyResult<String> {
244 match DeserializedObj::FXRates(self.clone()).to_json() {
245 Ok(v) => Ok(v),
246 Err(_) => Err(PyValueError::new_err(
247 "Failed to serialize `UnionCal` to JSON.",
248 )),
249 }
250 }
251
252 pub fn __setstate__(&mut self, state: Bound<'_, PyBytes>) -> PyResult<()> {
254 *self = decode_from_slice(state.as_bytes(), legacy()).unwrap().0;
255 Ok(())
256 }
257 pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
258 Ok(PyBytes::new(py, &encode_to_vec(&self, legacy()).unwrap()))
259 }
260 pub fn __getnewargs__<'py>(&self) -> PyResult<(Vec<FXRate>, Option<Ccy>)> {
261 Ok((self.fx_rates.clone(), Some(self.currencies[0])))
262 }
263
264 fn __eq__(&self, other: FXRates) -> bool {
266 self.eq(&other)
267 }
268
269 fn __copy__(&self) -> Self {
270 self.clone()
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277 use crate::scheduling::ndt;
278
279 #[test]
280 fn fxrates_eq() {
281 let fxr = FXRates::try_new(
282 vec![
283 FXRate::try_new("eur", "usd", Number::F64(1.08), Some(ndt(2004, 1, 1))).unwrap(),
284 FXRate::try_new("usd", "jpy", Number::F64(110.0), Some(ndt(2004, 1, 1))).unwrap(),
285 ],
286 None,
287 )
288 .unwrap();
289
290 let fxr2 = FXRates::try_new(
291 vec![
292 FXRate::try_new("eur", "usd", Number::F64(1.08), Some(ndt(2004, 1, 1))).unwrap(),
293 FXRate::try_new("usd", "jpy", Number::F64(110.0), Some(ndt(2004, 1, 1))).unwrap(),
294 ],
295 None,
296 )
297 .unwrap();
298
299 assert!(fxr.__eq__(fxr2))
300 }
301}