nautilus_model/instruments/
currency_pair.rs1use std::hash::{Hash, Hasher};
17
18use nautilus_core::{
19 correctness::{check_equal_u8, FAILED},
20 UnixNanos,
21};
22use rust_decimal::Decimal;
23use serde::{Deserialize, Serialize};
24use ustr::Ustr;
25
26use super::{any::InstrumentAny, Instrument};
27use crate::{
28 enums::{AssetClass, InstrumentClass, OptionKind},
29 identifiers::{InstrumentId, Symbol},
30 types::{
31 currency::Currency,
32 money::Money,
33 price::{check_positive_price, Price},
34 quantity::{check_positive_quantity, Quantity},
35 },
36};
37
38#[repr(C)]
42#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
43#[cfg_attr(
44 feature = "python",
45 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
46)]
47pub struct CurrencyPair {
48 pub id: InstrumentId,
50 pub raw_symbol: Symbol,
52 pub base_currency: Currency,
54 pub quote_currency: Currency,
56 pub price_precision: u8,
58 pub size_precision: u8,
60 pub price_increment: Price,
62 pub size_increment: Quantity,
64 pub margin_init: Decimal,
66 pub margin_maint: Decimal,
68 pub maker_fee: Decimal,
70 pub taker_fee: Decimal,
72 pub lot_size: Option<Quantity>,
74 pub max_quantity: Option<Quantity>,
76 pub min_quantity: Option<Quantity>,
78 pub max_notional: Option<Money>,
80 pub min_notional: Option<Money>,
82 pub max_price: Option<Price>,
84 pub min_price: Option<Price>,
86 pub ts_event: UnixNanos,
88 pub ts_init: UnixNanos,
90}
91
92impl CurrencyPair {
93 #[allow(clippy::too_many_arguments)]
99 pub fn new_checked(
100 id: InstrumentId,
101 raw_symbol: Symbol,
102 base_currency: Currency,
103 quote_currency: Currency,
104 price_precision: u8,
105 size_precision: u8,
106 price_increment: Price,
107 size_increment: Quantity,
108 lot_size: Option<Quantity>,
109 max_quantity: Option<Quantity>,
110 min_quantity: Option<Quantity>,
111 max_notional: Option<Money>,
112 min_notional: Option<Money>,
113 max_price: Option<Price>,
114 min_price: Option<Price>,
115 margin_init: Option<Decimal>,
116 margin_maint: Option<Decimal>,
117 maker_fee: Option<Decimal>,
118 taker_fee: Option<Decimal>,
119 ts_event: UnixNanos,
120 ts_init: UnixNanos,
121 ) -> anyhow::Result<Self> {
122 check_equal_u8(
123 price_precision,
124 price_increment.precision,
125 stringify!(price_precision),
126 stringify!(price_increment.precision),
127 )?;
128 check_equal_u8(
129 size_precision,
130 size_increment.precision,
131 stringify!(size_precision),
132 stringify!(size_increment.precision),
133 )?;
134 check_positive_price(price_increment.raw, stringify!(price_increment.raw))?;
135 check_positive_quantity(size_increment.raw, stringify!(size_increment.raw))?;
136
137 Ok(Self {
138 id,
139 raw_symbol,
140 base_currency,
141 quote_currency,
142 price_precision,
143 size_precision,
144 price_increment,
145 size_increment,
146 lot_size,
147 max_quantity,
148 min_quantity,
149 max_notional,
150 min_notional,
151 max_price,
152 min_price,
153 margin_init: margin_init.unwrap_or_default(),
154 margin_maint: margin_maint.unwrap_or_default(),
155 maker_fee: maker_fee.unwrap_or_default(),
156 taker_fee: taker_fee.unwrap_or_default(),
157 ts_event,
158 ts_init,
159 })
160 }
161
162 #[allow(clippy::too_many_arguments)]
164 pub fn new(
165 id: InstrumentId,
166 raw_symbol: Symbol,
167 base_currency: Currency,
168 quote_currency: Currency,
169 price_precision: u8,
170 size_precision: u8,
171 price_increment: Price,
172 size_increment: Quantity,
173 lot_size: Option<Quantity>,
174 max_quantity: Option<Quantity>,
175 min_quantity: Option<Quantity>,
176 max_notional: Option<Money>,
177 min_notional: Option<Money>,
178 max_price: Option<Price>,
179 min_price: Option<Price>,
180 margin_init: Option<Decimal>,
181 margin_maint: Option<Decimal>,
182 maker_fee: Option<Decimal>,
183 taker_fee: Option<Decimal>,
184 ts_event: UnixNanos,
185 ts_init: UnixNanos,
186 ) -> Self {
187 Self::new_checked(
188 id,
189 raw_symbol,
190 base_currency,
191 quote_currency,
192 price_precision,
193 size_precision,
194 price_increment,
195 size_increment,
196 lot_size,
197 max_quantity,
198 min_quantity,
199 max_notional,
200 min_notional,
201 max_price,
202 min_price,
203 margin_init,
204 margin_maint,
205 maker_fee,
206 taker_fee,
207 ts_event,
208 ts_init,
209 )
210 .expect(FAILED)
211 }
212}
213
214impl PartialEq<Self> for CurrencyPair {
215 fn eq(&self, other: &Self) -> bool {
216 self.id == other.id
217 }
218}
219
220impl Eq for CurrencyPair {}
221
222impl Hash for CurrencyPair {
223 fn hash<H: Hasher>(&self, state: &mut H) {
224 self.id.hash(state);
225 }
226}
227
228impl Instrument for CurrencyPair {
229 fn into_any(self) -> InstrumentAny {
230 InstrumentAny::CurrencyPair(self)
231 }
232
233 fn id(&self) -> InstrumentId {
234 self.id
235 }
236
237 fn raw_symbol(&self) -> Symbol {
238 self.raw_symbol
239 }
240
241 fn asset_class(&self) -> AssetClass {
242 AssetClass::FX
243 }
244
245 fn instrument_class(&self) -> InstrumentClass {
246 InstrumentClass::Spot
247 }
248
249 fn underlying(&self) -> Option<Ustr> {
250 None
251 }
252
253 fn base_currency(&self) -> Option<Currency> {
254 Some(self.base_currency)
255 }
256
257 fn quote_currency(&self) -> Currency {
258 self.quote_currency
259 }
260
261 fn settlement_currency(&self) -> Currency {
262 self.quote_currency
263 }
264 fn isin(&self) -> Option<Ustr> {
265 None
266 }
267
268 fn is_inverse(&self) -> bool {
269 false
270 }
271
272 fn price_precision(&self) -> u8 {
273 self.price_precision
274 }
275
276 fn size_precision(&self) -> u8 {
277 self.size_precision
278 }
279
280 fn price_increment(&self) -> Price {
281 self.price_increment
282 }
283
284 fn size_increment(&self) -> Quantity {
285 self.size_increment
286 }
287
288 fn multiplier(&self) -> Quantity {
289 Quantity::from(1)
290 }
291
292 fn lot_size(&self) -> Option<Quantity> {
293 self.lot_size
294 }
295
296 fn max_quantity(&self) -> Option<Quantity> {
297 self.max_quantity
298 }
299
300 fn min_quantity(&self) -> Option<Quantity> {
301 self.min_quantity
302 }
303
304 fn max_price(&self) -> Option<Price> {
305 self.max_price
306 }
307
308 fn min_price(&self) -> Option<Price> {
309 self.min_price
310 }
311
312 fn ts_event(&self) -> UnixNanos {
313 self.ts_event
314 }
315
316 fn ts_init(&self) -> UnixNanos {
317 self.ts_init
318 }
319
320 fn margin_init(&self) -> Decimal {
321 self.margin_init
322 }
323
324 fn margin_maint(&self) -> Decimal {
325 self.margin_maint
326 }
327
328 fn taker_fee(&self) -> Decimal {
329 self.taker_fee
330 }
331
332 fn maker_fee(&self) -> Decimal {
333 self.maker_fee
334 }
335
336 fn option_kind(&self) -> Option<OptionKind> {
337 None
338 }
339
340 fn exchange(&self) -> Option<Ustr> {
341 None
342 }
343
344 fn strike_price(&self) -> Option<Price> {
345 None
346 }
347
348 fn activation_ns(&self) -> Option<UnixNanos> {
349 None
350 }
351
352 fn expiration_ns(&self) -> Option<UnixNanos> {
353 None
354 }
355
356 fn max_notional(&self) -> Option<Money> {
357 self.max_notional
358 }
359
360 fn min_notional(&self) -> Option<Money> {
361 self.min_notional
362 }
363}
364
365#[cfg(test)]
369mod tests {
370 use rstest::rstest;
371
372 use crate::instruments::{stubs::*, CurrencyPair};
373
374 #[rstest]
375 fn test_equality(currency_pair_btcusdt: CurrencyPair) {
376 let cloned = currency_pair_btcusdt;
377 assert_eq!(currency_pair_btcusdt, cloned);
378 }
379}