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