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