nautilus_model/python/data/
bet.rs1use std::{
17 collections::hash_map::DefaultHasher,
18 hash::{Hash, Hasher},
19};
20
21use nautilus_core::python::{IntoPyObjectNautilusExt, to_pyvalue_err};
22use pyo3::{basic::CompareOp, prelude::*};
23use rust_decimal::Decimal;
24
25use crate::{
26 data::bet::{
27 Bet, BetPosition, calc_bets_pnl_checked, inverse_probability_to_bet, probability_to_bet,
28 },
29 enums::{BetSide, OrderSide},
30};
31
32#[pymethods]
33#[pyo3_stub_gen::derive::gen_stub_pymethods]
34impl Bet {
35 #[new]
37 fn py_new(price: Decimal, stake: Decimal, side: BetSide) -> Self {
38 Self::new(price, stake, side)
39 }
40
41 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
42 match op {
43 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
44 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
45 _ => py.NotImplemented(),
46 }
47 }
48
49 fn __hash__(&self) -> isize {
50 let mut h = DefaultHasher::new();
51 self.hash(&mut h);
52 h.finish() as isize
53 }
54
55 fn __repr__(&self) -> String {
56 format!("{self:?}")
57 }
58
59 fn __str__(&self) -> String {
60 self.to_string()
61 }
62
63 #[staticmethod]
68 #[pyo3(name = "from_stake_or_liability")]
69 fn py_from_stake_or_liability(
70 price: Decimal,
71 volume: Decimal,
72 side: BetSide,
73 ) -> PyResult<Self> {
74 Self::from_stake_or_liability_checked(price, volume, side).map_err(to_pyvalue_err)
75 }
76
77 #[staticmethod]
79 #[pyo3(name = "from_stake")]
80 fn py_from_stake(price: Decimal, stake: Decimal, side: BetSide) -> Self {
81 Self::from_stake(price, stake, side)
82 }
83
84 #[staticmethod]
86 #[pyo3(name = "from_liability")]
87 fn py_from_liability(price: Decimal, liability: Decimal, side: BetSide) -> PyResult<Self> {
88 Self::from_liability_checked(price, liability, side).map_err(to_pyvalue_err)
89 }
90
91 #[getter]
93 #[pyo3(name = "price")]
94 fn py_price(&self) -> Decimal {
95 self.price()
96 }
97
98 #[getter]
100 #[pyo3(name = "stake")]
101 fn py_stake(&self) -> Decimal {
102 self.stake()
103 }
104
105 #[getter]
107 #[pyo3(name = "side")]
108 fn py_side(&self) -> BetSide {
109 self.side()
110 }
111
112 #[pyo3(name = "exposure")]
116 fn py_exposure(&self) -> PyResult<Decimal> {
117 self.exposure_checked().map_err(to_pyvalue_err)
118 }
119
120 #[pyo3(name = "liability")]
125 fn py_liability(&self) -> PyResult<Decimal> {
126 self.liability_checked().map_err(to_pyvalue_err)
127 }
128
129 #[pyo3(name = "profit")]
133 fn py_profit(&self) -> PyResult<Decimal> {
134 self.profit_checked().map_err(to_pyvalue_err)
135 }
136
137 #[pyo3(name = "outcome_win_payoff")]
141 fn py_outcome_win_payoff(&self) -> PyResult<Decimal> {
142 self.outcome_win_payoff_checked().map_err(to_pyvalue_err)
143 }
144
145 #[pyo3(name = "outcome_lose_payoff")]
149 fn py_outcome_lose_payoff(&self) -> PyResult<Decimal> {
150 self.outcome_lose_payoff_checked().map_err(to_pyvalue_err)
151 }
152
153 #[pyo3(name = "hedging_stake")]
155 fn py_hedging_stake(&self, price: Decimal) -> PyResult<Decimal> {
156 self.hedging_stake_checked(price).map_err(to_pyvalue_err)
157 }
158
159 #[pyo3(name = "hedging_bet")]
161 fn py_hedging_bet(&self, price: Decimal) -> PyResult<Self> {
162 self.hedging_bet_checked(price).map_err(to_pyvalue_err)
163 }
164}
165
166#[pymethods]
167#[pyo3_stub_gen::derive::gen_stub_pymethods]
168impl BetPosition {
169 #[new]
171 fn py_new() -> Self {
172 Self::default()
173 }
174
175 fn __repr__(&self) -> String {
176 format!("{self:?}")
177 }
178
179 fn __str__(&self) -> String {
180 self.to_string()
181 }
182
183 #[getter]
185 #[pyo3(name = "price")]
186 fn py_price(&self) -> Decimal {
187 self.price()
188 }
189
190 #[getter]
194 #[pyo3(name = "side")]
195 fn py_side(&self) -> Option<BetSide> {
196 self.side()
197 }
198
199 #[getter]
201 #[pyo3(name = "exposure")]
202 fn py_exposure(&self) -> Decimal {
203 self.exposure()
204 }
205
206 #[getter]
208 #[pyo3(name = "realized_pnl")]
209 fn py_realized_pnl(&self) -> Decimal {
210 self.realized_pnl()
211 }
212
213 #[pyo3(name = "add_bet")]
215 fn py_add_bet(&mut self, bet: &Bet) -> PyResult<()> {
216 self.add_bet_checked(bet.clone()).map_err(to_pyvalue_err)
217 }
218
219 #[pyo3(name = "as_bet")]
221 fn py_as_bet(&self) -> PyResult<Option<Bet>> {
222 self.as_bet_checked().map_err(to_pyvalue_err)
223 }
224
225 #[pyo3(name = "unrealized_pnl")]
227 fn py_unrealized_pnl(&self, price: Decimal) -> PyResult<Decimal> {
228 self.unrealized_pnl_checked(price).map_err(to_pyvalue_err)
229 }
230
231 #[pyo3(name = "total_pnl")]
233 fn py_total_pnl(&self, price: Decimal) -> PyResult<Decimal> {
234 self.total_pnl_checked(price).map_err(to_pyvalue_err)
235 }
236
237 #[pyo3(name = "flattening_bet")]
239 fn py_flattening_bet(&self, price: Decimal) -> PyResult<Option<Bet>> {
240 self.flattening_bet_checked(price).map_err(to_pyvalue_err)
241 }
242
243 #[pyo3(name = "reset")]
245 fn py_reset(&mut self) {
246 self.reset();
247 }
248}
249
250#[pyfunction]
252#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.model")]
253#[pyo3(name = "calc_bets_pnl")]
254#[expect(clippy::needless_pass_by_value)]
255pub fn py_calc_bets_pnl(bets: Vec<Bet>) -> PyResult<Decimal> {
256 calc_bets_pnl_checked(&bets).map_err(to_pyvalue_err)
257}
258
259#[pyfunction]
267#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.model")]
268#[pyo3(name = "probability_to_bet")]
269pub fn py_probability_to_bet(
270 probability: Decimal,
271 volume: Decimal,
272 side: OrderSide,
273) -> PyResult<Bet> {
274 probability_to_bet(probability, volume, side).map_err(to_pyvalue_err)
275}
276
277#[pyfunction]
285#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.model")]
286#[pyo3(name = "inverse_probability_to_bet")]
287pub fn py_inverse_probability_to_bet(
288 probability: Decimal,
289 volume: Decimal,
290 side: OrderSide,
291) -> PyResult<Bet> {
292 inverse_probability_to_bet(probability, volume, side).map_err(to_pyvalue_err)
293}