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