1use std::hash::{Hash, Hasher};
17
18use nautilus_core::{
19 correctness::{check_equal_u8, FAILED},
20 UnixNanos,
21};
22use rust_decimal::Decimal;
23use rust_decimal_macros::dec;
24use serde::{Deserialize, Serialize};
25use ustr::Ustr;
26
27use super::{any::InstrumentAny, Instrument};
28use crate::{
29 enums::{AssetClass, InstrumentClass, OptionKind},
30 identifiers::{InstrumentId, Symbol},
31 types::{
32 currency::Currency,
33 money::Money,
34 price::{check_positive_price, Price},
35 quantity::{check_positive_quantity, Quantity},
36 },
37};
38
39#[repr(C)]
41#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
42#[cfg_attr(
43 feature = "python",
44 pyo3::pyclass(module = "nautilus_trader.core.nautilus_pyo3.model")
45)]
46pub struct BettingInstrument {
47 pub id: InstrumentId,
49 pub raw_symbol: Symbol,
51 pub event_type_id: u64,
53 pub event_type_name: Ustr,
55 pub competition_id: u64,
57 pub competition_name: Ustr,
59 pub event_id: u64,
61 pub event_name: Ustr,
63 pub event_country_code: Ustr,
65 pub event_open_date: UnixNanos,
67 pub betting_type: Ustr,
69 pub market_id: Ustr,
71 pub market_name: Ustr,
73 pub market_type: Ustr,
75 pub market_start_time: UnixNanos,
77 pub selection_id: u64,
79 pub selection_name: Ustr,
81 pub selection_handicap: f64,
83 pub currency: Currency,
85 pub price_precision: u8,
87 pub size_precision: u8,
89 pub price_increment: Price,
91 pub size_increment: Quantity,
93 pub margin_init: Decimal,
95 pub margin_maint: Decimal,
97 pub maker_fee: Decimal,
99 pub taker_fee: Decimal,
101 pub max_quantity: Option<Quantity>,
103 pub min_quantity: Option<Quantity>,
105 pub max_notional: Option<Money>,
107 pub min_notional: Option<Money>,
109 pub max_price: Option<Price>,
111 pub min_price: Option<Price>,
113 pub ts_event: UnixNanos,
115 pub ts_init: UnixNanos,
117}
118
119impl BettingInstrument {
120 #[allow(clippy::too_many_arguments)]
126 pub fn new_checked(
127 id: InstrumentId,
128 raw_symbol: Symbol,
129 event_type_id: u64,
130 event_type_name: Ustr,
131 competition_id: u64,
132 competition_name: Ustr,
133 event_id: u64,
134 event_name: Ustr,
135 event_country_code: Ustr,
136 event_open_date: UnixNanos,
137 betting_type: Ustr,
138 market_id: Ustr,
139 market_name: Ustr,
140 market_type: Ustr,
141 market_start_time: UnixNanos,
142 selection_id: u64,
143 selection_name: Ustr,
144 selection_handicap: f64,
145 currency: Currency,
146 price_precision: u8,
147 size_precision: u8,
148 price_increment: Price,
149 size_increment: Quantity,
150 max_quantity: Option<Quantity>,
151 min_quantity: Option<Quantity>,
152 max_notional: Option<Money>,
153 min_notional: Option<Money>,
154 max_price: Option<Price>,
155 min_price: Option<Price>,
156 margin_init: Option<Decimal>,
157 margin_maint: Option<Decimal>,
158 maker_fee: Option<Decimal>,
159 taker_fee: Option<Decimal>,
160 ts_event: UnixNanos,
161 ts_init: UnixNanos,
162 ) -> anyhow::Result<Self> {
163 check_equal_u8(
164 price_precision,
165 price_increment.precision,
166 stringify!(price_precision),
167 stringify!(price_increment.precision),
168 )?;
169 check_equal_u8(
170 size_precision,
171 size_increment.precision,
172 stringify!(size_precision),
173 stringify!(size_increment.precision),
174 )?;
175 check_positive_price(price_increment.raw, stringify!(price_increment.raw))?;
176 check_positive_quantity(size_increment.raw, stringify!(size_increment.raw))?;
177
178 Ok(Self {
179 id,
180 raw_symbol,
181 event_type_id,
182 event_type_name,
183 competition_id,
184 competition_name,
185 event_id,
186 event_name,
187 event_country_code,
188 event_open_date,
189 betting_type,
190 market_id,
191 market_name,
192 market_type,
193 market_start_time,
194 selection_id,
195 selection_name,
196 selection_handicap,
197 currency,
198 price_precision,
199 size_precision,
200 price_increment,
201 size_increment,
202 max_quantity,
203 min_quantity,
204 max_notional,
205 min_notional,
206 max_price,
207 min_price,
208 margin_init: margin_init.unwrap_or(dec!(1)),
209 margin_maint: margin_maint.unwrap_or(dec!(1)),
210 maker_fee: maker_fee.unwrap_or_default(),
211 taker_fee: taker_fee.unwrap_or_default(),
212 ts_event,
213 ts_init,
214 })
215 }
216
217 #[allow(clippy::too_many_arguments)]
219 pub fn new(
220 id: InstrumentId,
221 raw_symbol: Symbol,
222 event_type_id: u64,
223 event_type_name: Ustr,
224 competition_id: u64,
225 competition_name: Ustr,
226 event_id: u64,
227 event_name: Ustr,
228 event_country_code: Ustr,
229 event_open_date: UnixNanos,
230 betting_type: Ustr,
231 market_id: Ustr,
232 market_name: Ustr,
233 market_type: Ustr,
234 market_start_time: UnixNanos,
235 selection_id: u64,
236 selection_name: Ustr,
237 selection_handicap: f64,
238 currency: Currency,
239 price_precision: u8,
240 size_precision: u8,
241 price_increment: Price,
242 size_increment: Quantity,
243 max_quantity: Option<Quantity>,
244 min_quantity: Option<Quantity>,
245 max_notional: Option<Money>,
246 min_notional: Option<Money>,
247 max_price: Option<Price>,
248 min_price: Option<Price>,
249 margin_init: Option<Decimal>,
250 margin_maint: Option<Decimal>,
251 maker_fee: Option<Decimal>,
252 taker_fee: Option<Decimal>,
253 ts_event: UnixNanos,
254 ts_init: UnixNanos,
255 ) -> Self {
256 Self::new_checked(
257 id,
258 raw_symbol,
259 event_type_id,
260 event_type_name,
261 competition_id,
262 competition_name,
263 event_id,
264 event_name,
265 event_country_code,
266 event_open_date,
267 betting_type,
268 market_id,
269 market_name,
270 market_type,
271 market_start_time,
272 selection_id,
273 selection_name,
274 selection_handicap,
275 currency,
276 price_precision,
277 size_precision,
278 price_increment,
279 size_increment,
280 max_quantity,
281 min_quantity,
282 max_notional,
283 min_notional,
284 max_price,
285 min_price,
286 margin_init,
287 margin_maint,
288 maker_fee,
289 taker_fee,
290 ts_event,
291 ts_init,
292 )
293 .expect(FAILED)
294 }
295}
296
297impl PartialEq<Self> for BettingInstrument {
298 fn eq(&self, other: &Self) -> bool {
299 self.id == other.id
300 }
301}
302
303impl Eq for BettingInstrument {}
304
305impl Hash for BettingInstrument {
306 fn hash<H: Hasher>(&self, state: &mut H) {
307 self.id.hash(state);
308 }
309}
310
311impl Instrument for BettingInstrument {
312 fn into_any(self) -> InstrumentAny {
313 InstrumentAny::Betting(self)
314 }
315
316 fn id(&self) -> InstrumentId {
317 self.id
318 }
319
320 fn raw_symbol(&self) -> Symbol {
321 self.raw_symbol
322 }
323
324 fn asset_class(&self) -> AssetClass {
325 AssetClass::Alternative
326 }
327
328 fn instrument_class(&self) -> InstrumentClass {
329 InstrumentClass::SportsBetting
330 }
331
332 fn underlying(&self) -> Option<Ustr> {
333 None
334 }
335
336 fn quote_currency(&self) -> Currency {
337 self.currency
338 }
339
340 fn base_currency(&self) -> Option<Currency> {
341 None
342 }
343
344 fn settlement_currency(&self) -> Currency {
345 self.currency
346 }
347
348 fn isin(&self) -> Option<Ustr> {
349 None
350 }
351
352 fn exchange(&self) -> Option<Ustr> {
353 None
354 }
355
356 fn option_kind(&self) -> Option<OptionKind> {
357 None
358 }
359
360 fn is_inverse(&self) -> bool {
361 false
362 }
363
364 fn price_precision(&self) -> u8 {
365 self.price_precision
366 }
367
368 fn size_precision(&self) -> u8 {
369 self.size_precision
370 }
371
372 fn price_increment(&self) -> Price {
373 self.price_increment
374 }
375
376 fn size_increment(&self) -> Quantity {
377 self.size_increment
378 }
379
380 fn multiplier(&self) -> Quantity {
381 Quantity::from(1)
382 }
383
384 fn lot_size(&self) -> Option<Quantity> {
385 Some(Quantity::from(1))
386 }
387
388 fn max_quantity(&self) -> Option<Quantity> {
389 self.max_quantity
390 }
391
392 fn min_quantity(&self) -> Option<Quantity> {
393 self.min_quantity
394 }
395
396 fn max_price(&self) -> Option<Price> {
397 self.max_price
398 }
399
400 fn min_price(&self) -> Option<Price> {
401 self.min_price
402 }
403
404 fn ts_event(&self) -> UnixNanos {
405 self.ts_event
406 }
407
408 fn ts_init(&self) -> UnixNanos {
409 self.ts_init
410 }
411
412 fn strike_price(&self) -> Option<Price> {
413 None
414 }
415
416 fn activation_ns(&self) -> Option<UnixNanos> {
417 Some(self.market_start_time)
418 }
419
420 fn expiration_ns(&self) -> Option<UnixNanos> {
421 None
422 }
423
424 fn max_notional(&self) -> Option<Money> {
425 self.max_notional
426 }
427
428 fn min_notional(&self) -> Option<Money> {
429 self.min_notional
430 }
431}
432
433#[cfg(test)]
437mod tests {
438 use rstest::rstest;
439
440 use crate::instruments::{stubs::*, BettingInstrument};
441
442 #[rstest]
443 fn test_equality(betting: BettingInstrument) {
444 let cloned = betting;
445 assert_eq!(betting, cloned);
446 }
447}