RustQuant_instruments/fx/exchange.rs
1// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2// RustQuant: A Rust library for quantitative finance tools.
3// Copyright (C) 2023 https://github.com/avhz
4// Dual licensed under Apache 2.0 and MIT.
5// See:
6// - LICENSE-APACHE.md
7// - LICENSE-MIT.md
8// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9
10//! FX exchange module.
11
12use super::CurrencyPair;
13use crate::fx::currency::Currency;
14use crate::fx::money::Money;
15use std::collections::HashMap;
16
17// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
18// STRUCTS, ENUMS, AND TRAITS
19// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
20
21/// Exchange struct to hold exchange rates.
22#[derive(Debug, Clone, Default)]
23pub struct Exchange {
24 /// Exchange rates hashmap.
25 /// The key is a string of the form e.g. "USD_EUR",
26 /// and the value is an ExchangeRate struct.
27 /// The key is generated from the from_currency and to_currency of the ExchangeRate.
28 pub rates: HashMap<CurrencyPair, ExchangeRate>,
29}
30
31/// `ExchangeRate` struct to hold exchange rate information.
32#[allow(clippy::module_name_repetitions)]
33#[derive(Debug, Clone, Copy)]
34pub struct ExchangeRate {
35 /// From currency
36 pub from_currency: Currency,
37
38 /// To currency
39 pub to_currency: Currency,
40
41 /// The actual exchange rate as a ratio from_currency/to_currency
42 pub rate: f64,
43}
44
45// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
46// IMPLEMENTATIONS
47// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
48
49impl Exchange {
50 /// Create a new empty Exchange.
51 ///
52 /// # Example
53 /// ```
54 /// use RustQuant::instruments::fx::exchange::Exchange;
55 ///
56 /// let exchange = Exchange::new();
57 /// ```
58 ///
59 #[must_use]
60 pub fn new() -> Self {
61 Self {
62 rates: HashMap::new(),
63 }
64 }
65
66 /// Adds a new `ExchangeRate` to the Exchange.
67 ///
68 /// # Example
69 /// ```
70 /// use RustQuant::instruments::*;
71 /// use RustQuant::iso::*;
72 ///
73 /// let mut exchange = Exchange::new();
74 ///
75 /// let usd_to_eur = ExchangeRate::new(USD, EUR, 0.85); // USD to EUR
76 /// let eur_to_usd = ExchangeRate::new(EUR, USD, 1.18); // EUR to USD
77 ///
78 /// exchange.add_rate(usd_to_eur);
79 /// exchange.add_rate(eur_to_usd);
80 /// ```
81 ///
82 pub fn add_rate(&mut self, rate: ExchangeRate) {
83 // let key = format!(
84 // "{}/{}",
85 // rate.from_currency.code.alphabetic, rate.to_currency.code.alphabetic
86 // );
87 let key = CurrencyPair::new(rate.from_currency, rate.to_currency);
88 self.rates.insert(key, rate);
89 }
90
91 /// Retrieves an `ExchangeRate` from the Exchange.
92 ///
93 /// # Example
94 /// ```
95 /// use RustQuant::instruments::*;
96 ///
97 /// let mut exchange = Exchange::new();
98 ///
99 /// let usd_to_eur = ExchangeRate::new(USD, EUR, 0.85); // USD to EUR
100 /// let eur_to_usd = ExchangeRate::new(EUR, USD, 1.18); // EUR to USD
101 ///
102 /// exchange.add_rate(usd_to_eur);
103 /// exchange.add_rate(eur_to_usd);
104 ///
105 /// let retrieved_usd_to_eur = exchange.get_rate(&USD, &EUR).expect("Rate not found");
106 /// assert_eq!(retrieved_usd_to_eur.rate, 0.85);
107 ///
108 /// let retrieved_eur_to_usd = exchange.get_rate(&EUR, &USD).expect("Rate not found");
109 /// assert_eq!(retrieved_eur_to_usd.rate, 1.18);
110 /// ```
111 ///
112 #[must_use]
113 pub fn get_rate(
114 &self,
115 from_currency: &Currency,
116 to_currency: &Currency,
117 ) -> Option<&ExchangeRate> {
118 // let key = format!(
119 // "{}/{}",
120 // from_currency.code.alphabetic, to_currency.code.alphabetic
121 // );
122 let key = CurrencyPair::new(*from_currency, *to_currency);
123 self.rates.get(&key)
124 }
125
126 /// Convert money from one currency to another using the exchange rate in the Exchange.
127 /// It panics if the conversion rate is not found or if the money's currency doesn't match with `from_currency`.
128 ///
129 /// # Example
130 /// ```
131 /// use RustQuant::instruments::*;
132 ///
133 /// let mut exchange = Exchange::new();
134 ///
135 /// let usd_to_eur = ExchangeRate::new(USD, EUR, 0.85); // USD to EUR
136 /// let eur_to_usd = ExchangeRate::new(EUR, USD, 1.18); // EUR to USD
137 ///
138 /// exchange.add_rate(usd_to_eur);
139 /// exchange.add_rate(eur_to_usd);
140 ///
141 /// let usd_100 = Money::new(USD, 100.0); // 100 USD
142 /// let eur_85 = exchange.convert(usd_100, EUR); // Should be 85 EUR
143 ///
144 /// assert_eq!(eur_85.currency, EUR);
145 /// assert_eq!(eur_85.amount, 85.0);
146 /// ```
147 #[must_use]
148 pub fn convert(&self, money: Money, to_currency: Currency) -> Money {
149 let rate = self
150 .get_rate(&money.currency, &to_currency)
151 .unwrap_or_else(|| {
152 panic!(
153 "Exchange rate for converting {} to {} not found.",
154 money.currency.code.alphabetic, to_currency.code.alphabetic
155 )
156 });
157 rate.convert(money)
158 }
159}
160
161impl ExchangeRate {
162 /// Create a new exchange rate.
163 #[must_use]
164 pub fn new(from_currency: Currency, to_currency: Currency, rate: f64) -> Self {
165 Self {
166 from_currency,
167 to_currency,
168 rate,
169 }
170 }
171
172 /// Convert money from one currency to another using this exchange rate.
173 /// It panics if the money's currency doesn't match with `from_currency`.
174 ///
175 /// # Example
176 /// ```
177 /// use RustQuant::instruments::*;
178 /// use RustQuant::utils::assert_approx_equal;
179 ///
180 /// // Use USD and EUR currency constants from the money module.
181 /// let usd = Money::new(USD, 100.0);
182 /// let eur_usd = ExchangeRate::new(USD, EUR, 0.9186955); // 1 USD = 0.9186955 EUR
183 /// let eur = eur_usd.convert(usd);
184 ///
185 /// assert_approx_equal!(eur.amount, 91.86955, 1e-5);
186 /// assert_eq!(eur.currency, EUR);
187 /// ```
188 ///
189 /// It panics if the money's currency doesn't match with `from_currency`.
190 ///
191 /// ```should_panic
192 /// use RustQuant::instruments::*;
193 ///
194 /// let usd = Money::new(EUR, 100.0); // Notice the wrong currency
195 /// let eur_usd = ExchangeRate::new(USD, EUR, 0.9186955); // 1 USD = 0.9186955 EUR
196 ///
197 /// eur_usd.convert(usd); // This will panic
198 /// ```
199 #[must_use]
200 pub fn convert(&self, money: Money) -> Money {
201 if money.currency == self.from_currency {
202 let new_amount = money.amount * self.rate;
203 Money::new(self.to_currency, new_amount)
204 } else {
205 panic!(
206 "The currency of the money doesn't match with from_currency of the exchange rate."
207 )
208 }
209 }
210}
211
212// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
213// UNIT TESTS
214// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
215
216// You can now add additional unit tests
217#[cfg(test)]
218mod test_exchange_rate {
219 use super::*;
220 use crate::fx::*;
221 use crate::fx::{EUR, USD};
222 use std::f64::EPSILON as EPS;
223 use RustQuant_utils::assert_approx_equal;
224
225 #[test]
226 fn test_conversion() {
227 // Create Money instance
228 let usd_100 = Money::new(USD, 100.0);
229
230 // Create ExchangeRate instance
231 let usd_to_eur = ExchangeRate::new(USD, EUR, 0.85); // 1 USD = 0.85 EUR as an example
232
233 // Convert USD to EUR
234 let eur_85 = usd_to_eur.convert(usd_100);
235
236 // Verify the conversion
237 assert_eq!(eur_85.currency, EUR);
238 assert_approx_equal!(eur_85.amount, 85.0, EPS);
239 }
240
241 #[test]
242 fn test_add_and_get_rate() {
243 let mut exchange = Exchange::new();
244
245 let usd_to_eur = ExchangeRate::new(USD, EUR, 0.85); // USD to EUR
246 let eur_to_usd = ExchangeRate::new(EUR, USD, 1.18); // EUR to USD
247
248 exchange.add_rate(usd_to_eur);
249 exchange.add_rate(eur_to_usd);
250
251 let retrieved_usd_to_eur = exchange.get_rate(&USD, &EUR).expect("Rate not found");
252 assert_approx_equal!(retrieved_usd_to_eur.rate, 0.85, EPS);
253
254 let retrieved_eur_to_usd = exchange.get_rate(&EUR, &USD).expect("Rate not found");
255 assert_approx_equal!(retrieved_eur_to_usd.rate, 1.18, EPS);
256 }
257
258 #[test]
259 fn test_conversion_with_exchange() {
260 let mut exchange = Exchange::new();
261
262 let usd_to_eur = ExchangeRate::new(USD, EUR, 0.85); // USD to EUR
263 let eur_to_usd = ExchangeRate::new(EUR, USD, 1.18); // EUR to USD
264
265 exchange.add_rate(usd_to_eur);
266 exchange.add_rate(eur_to_usd);
267
268 let usd_100 = Money::new(USD, 100.0); // 100 USD
269 let eur_85 = exchange.convert(usd_100, EUR); // Should be 85 EUR
270
271 assert_eq!(eur_85.currency, EUR);
272 assert_approx_equal!(eur_85.amount, 85.0, EPS);
273 }
274}