nautilus_model/python/
position.rs1use nautilus_core::python::{
17 IntoPyObjectNautilusExt, correctness_error_to_pyvalue_err,
18 serialization::{from_dict_pyo3, to_dict_pyo3},
19 to_pyvalue_err,
20};
21use pyo3::{
22 basic::CompareOp,
23 prelude::*,
24 types::{PyDict, PyList},
25};
26use rust_decimal::Decimal;
27
28use crate::{
29 enums::{InstrumentClass, OrderSide, PositionSide},
30 events::{OrderFilled, PositionAdjusted},
31 identifiers::{
32 AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, Symbol, TradeId, TraderId,
33 Venue, VenueOrderId,
34 },
35 position::{self, Position},
36 python::{common::commissions_from_vec, instruments::pyobject_to_instrument_any},
37 types::{Currency, Money, Price, Quantity},
38};
39
40#[pyo3_stub_gen::derive::gen_stub_pymethods]
41#[pymethods]
42impl Position {
43 #[new]
64 fn py_new(py: Python, instrument: Py<PyAny>, fill: OrderFilled) -> PyResult<Self> {
65 let instrument_any = pyobject_to_instrument_any(py, instrument)?;
66 Self::new_checked(&instrument_any, fill).map_err(correctness_error_to_pyvalue_err)
67 }
68
69 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
70 match op {
71 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
72 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
73 _ => py.NotImplemented(),
74 }
75 }
76
77 fn __repr__(&self) -> String {
78 self.to_string()
79 }
80
81 fn __str__(&self) -> String {
82 self.to_string()
83 }
84
85 #[getter]
86 #[pyo3(name = "trader_id")]
87 fn py_trader_id(&self) -> TraderId {
88 self.trader_id
89 }
90
91 #[getter]
92 #[pyo3(name = "strategy_id")]
93 fn py_strategy_id(&self) -> StrategyId {
94 self.strategy_id
95 }
96
97 #[getter]
98 #[pyo3(name = "instrument_id")]
99 fn py_instrument_id(&self) -> InstrumentId {
100 self.instrument_id
101 }
102
103 #[getter]
104 #[pyo3(name = "id")]
105 fn py_id(&self) -> PositionId {
106 self.id
107 }
108
109 #[getter]
110 #[pyo3(name = "account_id")]
111 fn py_account_id(&self) -> AccountId {
112 self.account_id
113 }
114
115 #[getter]
117 #[pyo3(name = "symbol")]
118 fn py_symbol(&self) -> Symbol {
119 self.symbol()
120 }
121
122 #[getter]
124 #[pyo3(name = "venue")]
125 fn py_venue(&self) -> Venue {
126 self.venue()
127 }
128
129 #[getter]
130 #[pyo3(name = "opening_order_id")]
131 fn py_opening_order_id(&self) -> ClientOrderId {
132 self.opening_order_id
133 }
134
135 #[getter]
136 #[pyo3(name = "closing_order_id")]
137 fn py_closing_order_id(&self) -> Option<ClientOrderId> {
138 self.closing_order_id
139 }
140
141 #[getter]
142 #[pyo3(name = "entry")]
143 fn py_entry(&self) -> OrderSide {
144 self.entry
145 }
146
147 #[getter]
148 #[pyo3(name = "side")]
149 fn py_side(&self) -> PositionSide {
150 self.side
151 }
152
153 #[getter]
154 #[pyo3(name = "signed_qty")]
155 fn py_signed_qty(&self) -> Decimal {
156 self.signed_qty
157 }
158
159 #[getter]
160 #[pyo3(name = "quantity")]
161 fn py_quantity(&self) -> Quantity {
162 self.quantity
163 }
164
165 #[getter]
166 #[pyo3(name = "peak_qty")]
167 fn py_peak_qty(&self) -> Quantity {
168 self.peak_qty
169 }
170
171 #[getter]
172 #[pyo3(name = "price_precision")]
173 fn py_price_precision(&self) -> u8 {
174 self.price_precision
175 }
176
177 #[getter]
178 #[pyo3(name = "size_precision")]
179 fn py_size_precision(&self) -> u8 {
180 self.size_precision
181 }
182
183 #[getter]
184 #[pyo3(name = "multiplier")]
185 fn py_multiplier(&self) -> Quantity {
186 self.multiplier
187 }
188
189 #[getter]
190 #[pyo3(name = "is_inverse")]
191 fn py_is_inverse(&self) -> bool {
192 self.is_inverse
193 }
194
195 #[getter]
196 #[pyo3(name = "instrument_class")]
197 fn py_instrument_class(&self) -> InstrumentClass {
198 self.instrument_class
199 }
200
201 #[getter]
202 #[pyo3(name = "is_spot_currency")]
203 fn py_is_spot_currency(&self) -> bool {
204 self.is_currency_pair
205 }
206
207 #[getter]
208 #[pyo3(name = "base_currency")]
209 fn py_base_currency(&self) -> Option<Currency> {
210 self.base_currency
211 }
212
213 #[getter]
214 #[pyo3(name = "quote_currency")]
215 fn py_quote_currency(&self) -> Currency {
216 self.quote_currency
217 }
218
219 #[getter]
220 #[pyo3(name = "settlement_currency")]
221 fn py_settlement_currency(&self) -> Currency {
222 self.settlement_currency
223 }
224
225 #[getter]
226 #[pyo3(name = "ts_init")]
227 fn py_ts_init(&self) -> u64 {
228 self.ts_init.as_u64()
229 }
230
231 #[getter]
232 #[pyo3(name = "ts_opened")]
233 fn py_ts_opened(&self) -> u64 {
234 self.ts_opened.as_u64()
235 }
236
237 #[getter]
238 #[pyo3(name = "ts_last")]
239 fn py_ts_last(&self) -> u64 {
240 self.ts_last.as_u64()
241 }
242
243 #[getter]
244 #[pyo3(name = "ts_closed")]
245 fn py_ts_closed(&self) -> Option<u64> {
246 self.ts_closed.map(std::convert::Into::into)
247 }
248
249 #[getter]
250 #[pyo3(name = "duration_ns")]
251 fn py_duration_ns(&self) -> u64 {
252 self.duration_ns.as_u64()
253 }
254
255 #[getter]
256 #[pyo3(name = "avg_px_open")]
257 fn py_avg_px_open(&self) -> Decimal {
258 self.avg_px_open
259 }
260
261 #[getter]
262 #[pyo3(name = "avg_px_close")]
263 fn py_avg_px_close(&self) -> Option<Decimal> {
264 self.avg_px_close
265 }
266
267 #[getter]
268 #[pyo3(name = "realized_return")]
269 fn py_realized_return(&self) -> f64 {
270 self.realized_return
271 }
272
273 #[getter]
274 #[pyo3(name = "realized_pnl")]
275 fn py_realized_pnl(&self) -> Option<Money> {
276 self.realized_pnl
277 }
278
279 #[pyo3(name = "events")]
280 fn py_events(&self) -> Vec<OrderFilled> {
281 self.events.clone()
282 }
283
284 #[pyo3(name = "adjustments")]
285 fn py_adjustments(&self) -> Vec<PositionAdjusted> {
286 self.adjustments.clone()
287 }
288
289 #[pyo3(name = "client_order_ids")]
291 fn py_client_order_ids(&self) -> Vec<ClientOrderId> {
292 self.client_order_ids()
293 }
294
295 #[pyo3(name = "venue_order_ids")]
297 fn py_venue_order_ids(&self) -> Vec<VenueOrderId> {
298 self.venue_order_ids()
299 }
300
301 #[pyo3(name = "trade_ids")]
303 fn py_trade_ids(&self) -> Vec<TradeId> {
304 self.trade_ids()
305 }
306
307 #[getter]
309 #[pyo3(name = "last_event")]
310 fn py_last_event(&self) -> Option<OrderFilled> {
311 self.last_event()
312 }
313
314 #[getter]
316 #[pyo3(name = "last_trade_id")]
317 fn py_last_trade_id(&self) -> Option<TradeId> {
318 self.last_trade_id()
319 }
320
321 #[getter]
323 #[pyo3(name = "event_count")]
324 fn py_event_count(&self) -> usize {
325 self.events.len()
326 }
327
328 #[getter]
330 #[pyo3(name = "is_open")]
331 fn py_is_open(&self) -> bool {
332 self.is_open()
333 }
334
335 #[getter]
337 #[pyo3(name = "is_closed")]
338 fn py_is_closed(&self) -> bool {
339 self.is_closed()
340 }
341
342 #[getter]
344 #[pyo3(name = "is_long")]
345 fn py_is_long(&self) -> bool {
346 self.is_long()
347 }
348
349 #[getter]
351 #[pyo3(name = "is_short")]
352 fn py_is_short(&self) -> bool {
353 self.is_short()
354 }
355
356 #[pyo3(name = "unrealized_pnl")]
362 fn py_unrealized_pnl(&self, last: Price) -> PyResult<Money> {
363 self.try_unrealized_pnl(last)
364 .map_err(nautilus_core::python::to_pyvalue_err)
365 }
366
367 #[pyo3(name = "total_pnl")]
373 fn py_total_pnl(&self, last: Price) -> PyResult<Money> {
374 self.try_total_pnl(last)
375 .map_err(nautilus_core::python::to_pyvalue_err)
376 }
377
378 #[pyo3(name = "commissions")]
380 fn py_commissions(&self) -> Vec<Money> {
381 self.commissions()
382 }
383
384 #[pyo3(name = "apply")]
393 fn py_apply(&mut self, fill: &OrderFilled) -> PyResult<()> {
394 self.try_apply(fill)
395 .map_err(correctness_error_to_pyvalue_err)
396 }
397
398 #[pyo3(name = "apply_adjustment")]
413 fn py_apply_adjustment(&mut self, adjustment: PositionAdjusted) -> PyResult<()> {
414 self.apply_adjustment(adjustment)
415 .map_err(correctness_error_to_pyvalue_err)
416 }
417
418 #[pyo3(name = "purge_events_for_order")]
430 fn py_purge_events_for_order(&mut self, client_order_id: ClientOrderId) -> PyResult<()> {
431 self.purge_events_for_order(client_order_id)
432 .map_err(correctness_error_to_pyvalue_err)
433 }
434
435 #[pyo3(name = "is_opposite_side")]
437 fn py_is_opposite_side(&self, side: OrderSide) -> bool {
438 self.is_opposite_side(side)
439 }
440
441 #[pyo3(name = "calculate_pnl")]
447 fn py_calculate_pnl(
448 &self,
449 avg_px_open: Decimal,
450 avg_px_close: Decimal,
451 quantity: Quantity,
452 ) -> PyResult<Money> {
453 self.try_calculate_pnl(avg_px_open, avg_px_close, quantity)
454 .map_err(nautilus_core::python::to_pyvalue_err)
455 }
456
457 #[pyo3(name = "notional_value")]
459 fn py_notional_value(&self, price: Price) -> PyResult<Money> {
460 self.try_notional_value(price)
461 .map_err(nautilus_core::python::to_pyvalue_err)
462 }
463
464 #[staticmethod]
470 #[pyo3(name = "from_dict")]
471 pub fn py_from_dict(py: Python<'_>, values: Py<PyDict>) -> PyResult<Self> {
472 let values = values.into_bound(py).copy()?;
473 let position_id = values.call_method1("pop", ("position_id",))?;
474 values.set_item("id", position_id)?;
475 let commissions = values
476 .call_method1("pop", ("commissions",))?
477 .extract::<Option<Vec<String>>>()?
478 .unwrap_or_default();
479 let amounts = PyDict::new(py);
480
481 for commission in commissions {
482 let money = Money::from_str_exact(&commission).map_err(to_pyvalue_err)?;
483 amounts.set_item(money.currency.to_string(), money.to_string())?;
484 }
485
486 values.set_item("commissions", amounts)?;
487 let position: Self = from_dict_pyo3(py, values.unbind())?;
488 position
489 .check_accounting()
490 .map_err(correctness_error_to_pyvalue_err)?;
491 Ok(position)
492 }
493
494 #[pyo3(name = "to_dict")]
500 fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
501 let dict = to_dict_pyo3(py, self)?;
502 let values = dict.bind(py);
503 values.set_item("type", stringify!(Position))?;
504 values.set_item("position_id", self.id.to_string())?;
505 values.del_item("id")?;
506 let events: PyResult<Vec<_>> = self
507 .events
508 .iter()
509 .map(|event| event.py_to_dict(py))
510 .collect();
511 values.set_item("events", events?)?;
512 let adjustments: PyResult<Vec<_>> = self
513 .adjustments
514 .iter()
515 .map(|event| event.py_to_dict(py))
516 .collect();
517 values.set_item("adjustments", adjustments?)?;
518 let venue_order_ids =
519 PyList::new(py, self.venue_order_ids().iter().map(ToString::to_string))?;
520 values.set_item("venue_order_ids", venue_order_ids)?;
521 values.set_item("commissions", commissions_from_vec(py, self.commissions())?)?;
522 Ok(dict.into_any())
523 }
524}
525
526#[must_use]
538#[pyfunction]
539#[pyo3(name = "fold_net_position")]
540#[allow(
541 clippy::needless_pass_by_value,
542 reason = "PyO3 cannot extract Python list into &[T]; Vec<T> ownership is required"
543)]
544pub fn py_fold_net_position(legs: Vec<(Decimal, Decimal, u64)>) -> (Decimal, Decimal) {
545 position::fold_net_position(&legs)
546}