nautilus_model/instruments/
binary_option.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, 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/// Represents a generic binary option 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 BinaryOption {
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 binary option asset class.
51    pub asset_class: AssetClass,
52    /// The binary option contract currency.
53    pub currency: Currency,
54    /// UNIX timestamp (nanoseconds) for contract activation.
55    pub activation_ns: UnixNanos,
56    /// UNIX timestamp (nanoseconds) for contract expiration.
57    pub expiration_ns: UnixNanos,
58    /// The price decimal precision.
59    pub price_precision: u8,
60    /// The trading size decimal precision.
61    pub size_precision: u8,
62    /// The minimum price increment (tick size).
63    pub price_increment: Price,
64    /// The minimum size increment.
65    pub size_increment: Quantity,
66    /// The initial (order) margin requirement in percentage of order value.
67    pub margin_init: Decimal,
68    /// The maintenance (position) margin in percentage of position value.
69    pub margin_maint: Decimal,
70    /// The fee rate for liquidity makers as a percentage of order value.
71    pub maker_fee: Decimal,
72    /// The fee rate for liquidity takers as a percentage of order value.
73    pub taker_fee: Decimal,
74    /// The binary outcome of the market.
75    pub outcome: Option<Ustr>,
76    /// The market description.
77    pub description: Option<Ustr>,
78    /// The maximum allowable order quantity.
79    pub max_quantity: Option<Quantity>,
80    /// The minimum allowable order quantity.
81    pub min_quantity: Option<Quantity>,
82    /// The maximum allowable order notional value.
83    pub max_notional: Option<Money>,
84    /// The minimum allowable order notional value.
85    pub min_notional: Option<Money>,
86    /// The maximum allowable quoted price.
87    pub max_price: Option<Price>,
88    /// The minimum allowable quoted price.
89    pub min_price: Option<Price>,
90    /// UNIX timestamp (nanoseconds) when the data event occurred.
91    pub ts_event: UnixNanos,
92    /// UNIX timestamp (nanoseconds) when the data object was initialized.
93    pub ts_init: UnixNanos,
94}
95
96impl BinaryOption {
97    /// Creates a new [`BinaryOption`] instance with correctness checking.
98    ///
99    /// # Notes
100    ///
101    /// PyO3 requires a `Result` type for proper error handling and stacktrace printing in Python.
102    #[allow(clippy::too_many_arguments)]
103    pub fn new_checked(
104        id: InstrumentId,
105        raw_symbol: Symbol,
106        asset_class: AssetClass,
107        currency: Currency,
108        activation_ns: UnixNanos,
109        expiration_ns: UnixNanos,
110        price_precision: u8,
111        size_precision: u8,
112        price_increment: Price,
113        size_increment: Quantity,
114        outcome: Option<Ustr>,
115        description: Option<Ustr>,
116        max_quantity: Option<Quantity>,
117        min_quantity: Option<Quantity>,
118        max_notional: Option<Money>,
119        min_notional: Option<Money>,
120        max_price: Option<Price>,
121        min_price: Option<Price>,
122        margin_init: Option<Decimal>,
123        margin_maint: Option<Decimal>,
124        maker_fee: Option<Decimal>,
125        taker_fee: Option<Decimal>,
126        ts_event: UnixNanos,
127        ts_init: UnixNanos,
128    ) -> anyhow::Result<Self> {
129        check_equal_u8(
130            price_precision,
131            price_increment.precision,
132            stringify!(price_precision),
133            stringify!(price_increment.precision),
134        )?;
135        check_equal_u8(
136            size_precision,
137            size_increment.precision,
138            stringify!(size_precision),
139            stringify!(size_increment.precision),
140        )?;
141        check_positive_price(price_increment.raw, stringify!(price_increment.raw))?;
142        check_positive_quantity(size_increment.raw, stringify!(size_increment.raw))?;
143
144        Ok(Self {
145            id,
146            raw_symbol,
147            asset_class,
148            currency,
149            activation_ns,
150            expiration_ns,
151            price_precision,
152            size_precision,
153            price_increment,
154            size_increment,
155            margin_init: margin_init.unwrap_or_default(),
156            margin_maint: margin_maint.unwrap_or_default(),
157            maker_fee: maker_fee.unwrap_or_default(),
158            taker_fee: taker_fee.unwrap_or_default(),
159            outcome,
160            description,
161            max_quantity,
162            min_quantity,
163            max_notional,
164            min_notional,
165            max_price,
166            min_price,
167            ts_event,
168            ts_init,
169        })
170    }
171
172    /// Creates a new [`BinaryOption`] instance.
173    #[allow(clippy::too_many_arguments)]
174    pub fn new(
175        id: InstrumentId,
176        raw_symbol: Symbol,
177        asset_class: AssetClass,
178        currency: Currency,
179        activation_ns: UnixNanos,
180        expiration_ns: UnixNanos,
181        price_precision: u8,
182        size_precision: u8,
183        price_increment: Price,
184        size_increment: Quantity,
185        outcome: Option<Ustr>,
186        description: Option<Ustr>,
187        max_quantity: Option<Quantity>,
188        min_quantity: Option<Quantity>,
189        max_notional: Option<Money>,
190        min_notional: Option<Money>,
191        max_price: Option<Price>,
192        min_price: Option<Price>,
193        margin_init: Option<Decimal>,
194        margin_maint: Option<Decimal>,
195        maker_fee: Option<Decimal>,
196        taker_fee: Option<Decimal>,
197        ts_event: UnixNanos,
198        ts_init: UnixNanos,
199    ) -> Self {
200        Self::new_checked(
201            id,
202            raw_symbol,
203            asset_class,
204            currency,
205            activation_ns,
206            expiration_ns,
207            price_precision,
208            size_precision,
209            price_increment,
210            size_increment,
211            outcome,
212            description,
213            max_quantity,
214            min_quantity,
215            max_notional,
216            min_notional,
217            max_price,
218            min_price,
219            margin_init,
220            margin_maint,
221            maker_fee,
222            taker_fee,
223            ts_event,
224            ts_init,
225        )
226        .expect(FAILED)
227    }
228}
229
230impl PartialEq<Self> for BinaryOption {
231    fn eq(&self, other: &Self) -> bool {
232        self.id == other.id
233    }
234}
235
236impl Eq for BinaryOption {}
237
238impl Hash for BinaryOption {
239    fn hash<H: Hasher>(&self, state: &mut H) {
240        self.id.hash(state);
241    }
242}
243
244impl Instrument for BinaryOption {
245    fn into_any(self) -> InstrumentAny {
246        InstrumentAny::BinaryOption(self)
247    }
248
249    fn id(&self) -> InstrumentId {
250        self.id
251    }
252
253    fn raw_symbol(&self) -> Symbol {
254        self.raw_symbol
255    }
256
257    fn asset_class(&self) -> AssetClass {
258        self.asset_class
259    }
260
261    fn instrument_class(&self) -> InstrumentClass {
262        InstrumentClass::BinaryOption
263    }
264
265    fn underlying(&self) -> Option<Ustr> {
266        None
267    }
268
269    fn base_currency(&self) -> Option<Currency> {
270        None
271    }
272
273    fn quote_currency(&self) -> Currency {
274        self.currency
275    }
276
277    fn settlement_currency(&self) -> Currency {
278        self.currency
279    }
280
281    fn isin(&self) -> Option<Ustr> {
282        None
283    }
284
285    fn exchange(&self) -> Option<Ustr> {
286        None
287    }
288
289    fn option_kind(&self) -> Option<OptionKind> {
290        None
291    }
292
293    fn is_inverse(&self) -> bool {
294        false
295    }
296
297    fn price_precision(&self) -> u8 {
298        self.price_precision
299    }
300
301    fn size_precision(&self) -> u8 {
302        self.size_precision
303    }
304
305    fn price_increment(&self) -> Price {
306        self.price_increment
307    }
308
309    fn size_increment(&self) -> Quantity {
310        self.size_increment
311    }
312
313    fn multiplier(&self) -> Quantity {
314        Quantity::from(1)
315    }
316
317    fn lot_size(&self) -> Option<Quantity> {
318        Some(Quantity::from(1))
319    }
320
321    fn max_quantity(&self) -> Option<Quantity> {
322        self.max_quantity
323    }
324
325    fn min_quantity(&self) -> Option<Quantity> {
326        self.min_quantity
327    }
328
329    fn max_price(&self) -> Option<Price> {
330        self.max_price
331    }
332
333    fn min_price(&self) -> Option<Price> {
334        self.min_price
335    }
336
337    fn ts_event(&self) -> UnixNanos {
338        self.ts_event
339    }
340
341    fn ts_init(&self) -> UnixNanos {
342        self.ts_init
343    }
344
345    fn strike_price(&self) -> Option<Price> {
346        None
347    }
348
349    fn activation_ns(&self) -> Option<UnixNanos> {
350        Some(self.activation_ns)
351    }
352
353    fn expiration_ns(&self) -> Option<UnixNanos> {
354        Some(self.expiration_ns)
355    }
356
357    fn max_notional(&self) -> Option<Money> {
358        self.max_notional
359    }
360
361    fn min_notional(&self) -> Option<Money> {
362        self.min_notional
363    }
364}
365
366////////////////////////////////////////////////////////////////////////////////
367// Tests
368////////////////////////////////////////////////////////////////////////////////
369#[cfg(test)]
370mod tests {
371    use rstest::rstest;
372
373    use crate::instruments::{stubs::*, BinaryOption};
374
375    #[rstest]
376    fn test_equality(binary_option: BinaryOption) {
377        let cloned = binary_option;
378        assert_eq!(binary_option, cloned);
379    }
380}