nautilus_model/instruments/
equity.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use std::hash::{Hash, Hasher};
17
18use nautilus_core::{
19    correctness::{check_equal_u8, check_valid_string_optional, 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::Quantity,
35    },
36};
37
38/// Represents a generic equity instrument.
39#[repr(C)]
40#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
41#[cfg_attr(
42    feature = "python",
43    pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
44)]
45pub struct Equity {
46    /// The instrument ID.
47    pub id: InstrumentId,
48    /// The raw/local/native symbol for the instrument, assigned by the venue.
49    pub raw_symbol: Symbol,
50    /// The instruments International Securities Identification Number (ISIN).
51    pub isin: Option<Ustr>,
52    /// The futures contract currency.
53    pub currency: Currency,
54    /// The price decimal precision.
55    pub price_precision: u8,
56    /// The minimum price increment (tick size).
57    pub price_increment: Price,
58    /// The initial (order) margin requirement in percentage of order value.
59    pub margin_init: Decimal,
60    /// The maintenance (position) margin in percentage of position value.
61    pub margin_maint: Decimal,
62    /// The fee rate for liquidity makers as a percentage of order value.
63    pub maker_fee: Decimal,
64    /// The fee rate for liquidity takers as a percentage of order value.
65    pub taker_fee: Decimal,
66    /// The rounded lot unit size (standard/board).
67    pub lot_size: Option<Quantity>,
68    /// The maximum allowable order quantity.
69    pub max_quantity: Option<Quantity>,
70    /// The minimum allowable order quantity.
71    pub min_quantity: Option<Quantity>,
72    /// The maximum allowable quoted price.
73    pub max_price: Option<Price>,
74    /// The minimum allowable quoted price.
75    pub min_price: Option<Price>,
76    /// UNIX timestamp (nanoseconds) when the data event occurred.
77    pub ts_event: UnixNanos,
78    /// UNIX timestamp (nanoseconds) when the data object was initialized.
79    pub ts_init: UnixNanos,
80}
81
82impl Equity {
83    /// Creates a new [`Equity`] instance with correctness checking.
84    ///
85    /// # Notes
86    ///
87    /// PyO3 requires a `Result` type for proper error handling and stacktrace printing in Python.
88    #[allow(clippy::too_many_arguments)]
89    pub fn new_checked(
90        id: InstrumentId,
91        raw_symbol: Symbol,
92        isin: Option<Ustr>,
93        currency: Currency,
94        price_precision: u8,
95        price_increment: Price,
96        lot_size: Option<Quantity>,
97        max_quantity: Option<Quantity>,
98        min_quantity: Option<Quantity>,
99        max_price: Option<Price>,
100        min_price: Option<Price>,
101        margin_init: Option<Decimal>,
102        margin_maint: Option<Decimal>,
103        maker_fee: Option<Decimal>,
104        taker_fee: Option<Decimal>,
105        ts_event: UnixNanos,
106        ts_init: UnixNanos,
107    ) -> anyhow::Result<Self> {
108        check_valid_string_optional(isin.map(|u| u.as_str()), stringify!(isin))?;
109        check_equal_u8(
110            price_precision,
111            price_increment.precision,
112            stringify!(price_precision),
113            stringify!(price_increment.precision),
114        )?;
115        check_positive_price(price_increment.raw, stringify!(price_increment.raw))?;
116
117        Ok(Self {
118            id,
119            raw_symbol,
120            isin,
121            currency,
122            price_precision,
123            price_increment,
124            lot_size,
125            max_quantity,
126            min_quantity,
127            max_price,
128            min_price,
129            margin_init: margin_init.unwrap_or_default(),
130            margin_maint: margin_maint.unwrap_or_default(),
131            maker_fee: maker_fee.unwrap_or_default(),
132            taker_fee: taker_fee.unwrap_or_default(),
133            ts_event,
134            ts_init,
135        })
136    }
137
138    /// Creates a new [`Equity`] instance.
139    #[allow(clippy::too_many_arguments)]
140    pub fn new(
141        id: InstrumentId,
142        raw_symbol: Symbol,
143        isin: Option<Ustr>,
144        currency: Currency,
145        price_precision: u8,
146        price_increment: Price,
147        lot_size: Option<Quantity>,
148        max_quantity: Option<Quantity>,
149        min_quantity: Option<Quantity>,
150        max_price: Option<Price>,
151        min_price: Option<Price>,
152        margin_init: Option<Decimal>,
153        margin_maint: Option<Decimal>,
154        maker_fee: Option<Decimal>,
155        taker_fee: Option<Decimal>,
156        ts_event: UnixNanos,
157        ts_init: UnixNanos,
158    ) -> Self {
159        Self::new_checked(
160            id,
161            raw_symbol,
162            isin,
163            currency,
164            price_precision,
165            price_increment,
166            lot_size,
167            max_quantity,
168            min_quantity,
169            max_price,
170            min_price,
171            margin_init,
172            margin_maint,
173            maker_fee,
174            taker_fee,
175            ts_event,
176            ts_init,
177        )
178        .expect(FAILED)
179    }
180}
181
182impl PartialEq<Self> for Equity {
183    fn eq(&self, other: &Self) -> bool {
184        self.id == other.id
185    }
186}
187
188impl Eq for Equity {}
189
190impl Hash for Equity {
191    fn hash<H: Hasher>(&self, state: &mut H) {
192        self.id.hash(state);
193    }
194}
195
196impl Instrument for Equity {
197    fn into_any(self) -> InstrumentAny {
198        InstrumentAny::Equity(self)
199    }
200
201    fn id(&self) -> InstrumentId {
202        self.id
203    }
204
205    fn raw_symbol(&self) -> Symbol {
206        self.raw_symbol
207    }
208
209    fn asset_class(&self) -> AssetClass {
210        AssetClass::Equity
211    }
212
213    fn instrument_class(&self) -> InstrumentClass {
214        InstrumentClass::Spot
215    }
216    fn underlying(&self) -> Option<Ustr> {
217        None
218    }
219
220    fn base_currency(&self) -> Option<Currency> {
221        None
222    }
223
224    fn quote_currency(&self) -> Currency {
225        self.currency
226    }
227
228    fn settlement_currency(&self) -> Currency {
229        self.currency
230    }
231
232    fn isin(&self) -> Option<Ustr> {
233        self.isin
234    }
235
236    fn option_kind(&self) -> Option<OptionKind> {
237        None
238    }
239
240    fn exchange(&self) -> Option<Ustr> {
241        None
242    }
243
244    fn strike_price(&self) -> Option<Price> {
245        None
246    }
247
248    fn activation_ns(&self) -> Option<UnixNanos> {
249        None
250    }
251
252    fn expiration_ns(&self) -> Option<UnixNanos> {
253        None
254    }
255
256    fn is_inverse(&self) -> bool {
257        false
258    }
259
260    fn price_precision(&self) -> u8 {
261        self.price_precision
262    }
263
264    fn size_precision(&self) -> u8 {
265        0
266    }
267
268    fn price_increment(&self) -> Price {
269        self.price_increment
270    }
271
272    fn size_increment(&self) -> Quantity {
273        Quantity::from(1)
274    }
275
276    fn multiplier(&self) -> Quantity {
277        Quantity::from(1)
278    }
279
280    fn lot_size(&self) -> Option<Quantity> {
281        self.lot_size
282    }
283
284    fn max_quantity(&self) -> Option<Quantity> {
285        self.max_quantity
286    }
287
288    fn min_quantity(&self) -> Option<Quantity> {
289        self.min_quantity
290    }
291
292    fn max_notional(&self) -> Option<Money> {
293        None
294    }
295
296    fn min_notional(&self) -> Option<Money> {
297        None
298    }
299
300    fn max_price(&self) -> Option<Price> {
301        self.max_price
302    }
303
304    fn min_price(&self) -> Option<Price> {
305        self.min_price
306    }
307
308    fn ts_event(&self) -> UnixNanos {
309        self.ts_event
310    }
311
312    fn ts_init(&self) -> UnixNanos {
313        self.ts_init
314    }
315}
316
317////////////////////////////////////////////////////////////////////////////////
318// Tests
319////////////////////////////////////////////////////////////////////////////////
320#[cfg(test)]
321mod tests {
322    use rstest::rstest;
323
324    use crate::instruments::{stubs::*, Equity};
325
326    #[rstest]
327    fn test_equality(equity_aapl: Equity) {
328        let cloned = equity_aapl;
329        assert_eq!(equity_aapl, cloned);
330    }
331}