1use nautilus_core::python::{
17 IntoPyObjectNautilusExt, correctness_error_to_pyvalue_err, serialization::from_dict_pyo3,
18};
19use pyo3::{
20 basic::CompareOp,
21 prelude::*,
22 types::{PyDict, PyList},
23};
24use rust_decimal::{Decimal, prelude::ToPrimitive};
25
26use super::common::commissions_from_vec;
27use crate::{
28 enums::{InstrumentClass, OrderSide, PositionSide},
29 events::{OrderFilled, PositionAdjusted},
30 identifiers::{
31 AccountId, ClientOrderId, InstrumentId, PositionId, StrategyId, Symbol, TradeId, TraderId,
32 Venue, VenueOrderId,
33 },
34 position::{self, Position},
35 python::instruments::pyobject_to_instrument_any,
36 types::{Currency, Money, Price, Quantity},
37};
38
39#[pyo3_stub_gen::derive::gen_stub_pymethods]
40#[pymethods]
41impl Position {
42 #[new]
49 fn py_new(py: Python, instrument: Py<PyAny>, fill: OrderFilled) -> PyResult<Self> {
50 let instrument_any = pyobject_to_instrument_any(py, instrument)?;
51 Self::new_checked(&instrument_any, fill).map_err(correctness_error_to_pyvalue_err)
52 }
53
54 fn __richcmp__(&self, other: &Self, op: CompareOp, py: Python<'_>) -> Py<PyAny> {
55 match op {
56 CompareOp::Eq => self.eq(other).into_py_any_unwrap(py),
57 CompareOp::Ne => self.ne(other).into_py_any_unwrap(py),
58 _ => py.NotImplemented(),
59 }
60 }
61
62 fn __repr__(&self) -> String {
63 self.to_string()
64 }
65
66 fn __str__(&self) -> String {
67 self.to_string()
68 }
69
70 #[getter]
71 #[pyo3(name = "trader_id")]
72 fn py_trader_id(&self) -> TraderId {
73 self.trader_id
74 }
75
76 #[getter]
77 #[pyo3(name = "strategy_id")]
78 fn py_strategy_id(&self) -> StrategyId {
79 self.strategy_id
80 }
81
82 #[getter]
83 #[pyo3(name = "instrument_id")]
84 fn py_instrument_id(&self) -> InstrumentId {
85 self.instrument_id
86 }
87
88 #[getter]
89 #[pyo3(name = "id")]
90 fn py_id(&self) -> PositionId {
91 self.id
92 }
93
94 #[getter]
95 #[pyo3(name = "account_id")]
96 fn py_account_id(&self) -> AccountId {
97 self.account_id
98 }
99
100 #[getter]
102 #[pyo3(name = "symbol")]
103 fn py_symbol(&self) -> Symbol {
104 self.symbol()
105 }
106
107 #[getter]
109 #[pyo3(name = "venue")]
110 fn py_venue(&self) -> Venue {
111 self.venue()
112 }
113
114 #[getter]
115 #[pyo3(name = "opening_order_id")]
116 fn py_opening_order_id(&self) -> ClientOrderId {
117 self.opening_order_id
118 }
119
120 #[getter]
121 #[pyo3(name = "closing_order_id")]
122 fn py_closing_order_id(&self) -> Option<ClientOrderId> {
123 self.closing_order_id
124 }
125
126 #[getter]
127 #[pyo3(name = "entry")]
128 fn py_entry(&self) -> OrderSide {
129 self.entry
130 }
131
132 #[getter]
133 #[pyo3(name = "side")]
134 fn py_side(&self) -> PositionSide {
135 self.side
136 }
137
138 #[getter]
139 #[pyo3(name = "signed_qty")]
140 fn py_signed_qty(&self) -> f64 {
141 self.signed_qty
142 }
143
144 #[getter]
145 #[pyo3(name = "quantity")]
146 fn py_quantity(&self) -> Quantity {
147 self.quantity
148 }
149
150 #[getter]
151 #[pyo3(name = "peak_qty")]
152 fn py_peak_qty(&self) -> Quantity {
153 self.peak_qty
154 }
155
156 #[getter]
157 #[pyo3(name = "price_precision")]
158 fn py_price_precision(&self) -> u8 {
159 self.price_precision
160 }
161
162 #[getter]
163 #[pyo3(name = "size_precision")]
164 fn py_size_precision(&self) -> u8 {
165 self.size_precision
166 }
167
168 #[getter]
169 #[pyo3(name = "multiplier")]
170 fn py_multiplier(&self) -> Quantity {
171 self.multiplier
172 }
173
174 #[getter]
175 #[pyo3(name = "is_inverse")]
176 fn py_is_inverse(&self) -> bool {
177 self.is_inverse
178 }
179
180 #[getter]
181 #[pyo3(name = "instrument_class")]
182 fn py_instrument_class(&self) -> InstrumentClass {
183 self.instrument_class
184 }
185
186 #[getter]
187 #[pyo3(name = "is_spot_currency")]
188 fn py_is_spot_currency(&self) -> bool {
189 self.is_currency_pair
190 }
191
192 #[getter]
193 #[pyo3(name = "base_currency")]
194 fn py_base_currency(&self) -> Option<Currency> {
195 self.base_currency
196 }
197
198 #[getter]
199 #[pyo3(name = "quote_currency")]
200 fn py_quote_currency(&self) -> Currency {
201 self.quote_currency
202 }
203
204 #[getter]
205 #[pyo3(name = "settlement_currency")]
206 fn py_settlement_currency(&self) -> Currency {
207 self.settlement_currency
208 }
209
210 #[getter]
211 #[pyo3(name = "ts_init")]
212 fn py_ts_init(&self) -> u64 {
213 self.ts_init.as_u64()
214 }
215
216 #[getter]
217 #[pyo3(name = "ts_opened")]
218 fn py_ts_opened(&self) -> u64 {
219 self.ts_opened.as_u64()
220 }
221
222 #[getter]
223 #[pyo3(name = "ts_last")]
224 fn py_ts_last(&self) -> u64 {
225 self.ts_last.as_u64()
226 }
227
228 #[getter]
229 #[pyo3(name = "ts_closed")]
230 fn py_ts_closed(&self) -> Option<u64> {
231 self.ts_closed.map(std::convert::Into::into)
232 }
233
234 #[getter]
235 #[pyo3(name = "duration_ns")]
236 fn py_duration_ns(&self) -> u64 {
237 self.duration_ns
238 }
239
240 #[getter]
241 #[pyo3(name = "avg_px_open")]
242 fn py_avg_px_open(&self) -> f64 {
243 self.avg_px_open
244 }
245
246 #[getter]
247 #[pyo3(name = "avg_px_close")]
248 fn py_avg_px_close(&self) -> Option<f64> {
249 self.avg_px_close
250 }
251
252 #[getter]
253 #[pyo3(name = "realized_return")]
254 fn py_realized_return(&self) -> f64 {
255 self.realized_return
256 }
257
258 #[getter]
259 #[pyo3(name = "realized_pnl")]
260 fn py_realized_pnl(&self) -> Option<Money> {
261 self.realized_pnl
262 }
263
264 #[pyo3(name = "events")]
265 fn py_events(&self) -> Vec<OrderFilled> {
266 self.events.clone()
267 }
268
269 #[pyo3(name = "adjustments")]
270 fn py_adjustments(&self) -> Vec<PositionAdjusted> {
271 self.adjustments.clone()
272 }
273
274 #[pyo3(name = "client_order_ids")]
276 fn py_client_order_ids(&self) -> Vec<ClientOrderId> {
277 self.client_order_ids()
278 }
279
280 #[pyo3(name = "venue_order_ids")]
282 fn py_venue_order_ids(&self) -> Vec<VenueOrderId> {
283 self.venue_order_ids()
284 }
285
286 #[pyo3(name = "trade_ids")]
288 fn py_trade_ids(&self) -> Vec<TradeId> {
289 self.trade_ids()
290 }
291
292 #[getter]
294 #[pyo3(name = "last_event")]
295 fn py_last_event(&self) -> Option<OrderFilled> {
296 self.last_event()
297 }
298
299 #[getter]
301 #[pyo3(name = "last_trade_id")]
302 fn py_last_trade_id(&self) -> Option<TradeId> {
303 self.last_trade_id()
304 }
305
306 #[getter]
308 #[pyo3(name = "event_count")]
309 fn py_event_count(&self) -> usize {
310 self.events.len()
311 }
312
313 #[getter]
315 #[pyo3(name = "is_open")]
316 fn py_is_open(&self) -> bool {
317 self.is_open()
318 }
319
320 #[getter]
322 #[pyo3(name = "is_closed")]
323 fn py_is_closed(&self) -> bool {
324 self.is_closed()
325 }
326
327 #[getter]
329 #[pyo3(name = "is_long")]
330 fn py_is_long(&self) -> bool {
331 self.is_long()
332 }
333
334 #[getter]
336 #[pyo3(name = "is_short")]
337 fn py_is_short(&self) -> bool {
338 self.is_short()
339 }
340
341 #[pyo3(name = "unrealized_pnl")]
343 fn py_unrealized_pnl(&self, last: Price) -> PyResult<Money> {
344 self.try_unrealized_pnl(last)
345 .map_err(nautilus_core::python::to_pyvalue_err)
346 }
347
348 #[pyo3(name = "total_pnl")]
350 fn py_total_pnl(&self, last: Price) -> PyResult<Money> {
351 self.try_total_pnl(last)
352 .map_err(nautilus_core::python::to_pyvalue_err)
353 }
354
355 #[pyo3(name = "commissions")]
357 fn py_commissions(&self) -> Vec<Money> {
358 self.commissions()
359 }
360
361 #[pyo3(name = "apply")]
363 fn py_apply(&mut self, fill: &OrderFilled) -> PyResult<()> {
364 self.try_apply(fill)
365 .map_err(correctness_error_to_pyvalue_err)
366 }
367
368 #[pyo3(name = "apply_adjustment")]
377 fn py_apply_adjustment(&mut self, adjustment: PositionAdjusted) {
378 self.apply_adjustment(adjustment);
379 }
380
381 #[pyo3(name = "purge_events_for_order")]
388 fn py_purge_events_for_order(&mut self, client_order_id: ClientOrderId) {
389 self.purge_events_for_order(client_order_id);
390 }
391
392 #[pyo3(name = "is_opposite_side")]
394 fn py_is_opposite_side(&self, side: OrderSide) -> bool {
395 self.is_opposite_side(side)
396 }
397
398 #[pyo3(name = "calculate_pnl")]
400 fn py_calculate_pnl(
401 &self,
402 avg_px_open: f64,
403 avg_px_close: f64,
404 quantity: Quantity,
405 ) -> PyResult<Money> {
406 self.try_calculate_pnl(avg_px_open, avg_px_close, quantity)
407 .map_err(nautilus_core::python::to_pyvalue_err)
408 }
409
410 #[pyo3(name = "notional_value")]
412 fn py_notional_value(&self, price: Price) -> PyResult<Money> {
413 self.try_notional_value(price)
414 .map_err(nautilus_core::python::to_pyvalue_err)
415 }
416
417 #[staticmethod]
423 #[pyo3(name = "from_dict")]
424 pub fn py_from_dict(py: Python<'_>, values: Py<PyDict>) -> PyResult<Self> {
425 from_dict_pyo3(py, values)
426 }
427
428 #[pyo3(name = "to_dict")]
434 fn py_to_dict(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
435 let dict = PyDict::new(py);
436 dict.set_item("type", stringify!(Position))?;
437 let events_dict: PyResult<Vec<_>> = self.events.iter().map(|e| e.py_to_dict(py)).collect();
438 dict.set_item("events", events_dict?)?;
439 let adjustments_dict: PyResult<Vec<_>> =
440 self.adjustments.iter().map(|a| a.py_to_dict(py)).collect();
441 dict.set_item("adjustments", adjustments_dict?)?;
442 dict.set_item("trader_id", self.trader_id.to_string())?;
443 dict.set_item("strategy_id", self.strategy_id.to_string())?;
444 dict.set_item("instrument_id", self.instrument_id.to_string())?;
445 dict.set_item("position_id", self.id.to_string())?;
446 dict.set_item("account_id", self.account_id.to_string())?;
447 dict.set_item("opening_order_id", self.opening_order_id.to_string())?;
448
449 match self.closing_order_id {
450 Some(closing_order_id) => {
451 dict.set_item("closing_order_id", closing_order_id.to_string())?;
452 }
453 None => dict.set_item("closing_order_id", py.None())?,
454 }
455 dict.set_item("entry", self.entry.to_string())?;
456 dict.set_item("side", self.side.to_string())?;
457 dict.set_item("signed_qty", self.signed_qty.to_f64())?;
458 dict.set_item("quantity", self.quantity.to_string())?;
459 dict.set_item("peak_qty", self.peak_qty.to_string())?;
460 dict.set_item("price_precision", self.price_precision.to_u8())?;
461 dict.set_item("size_precision", self.size_precision.to_u8())?;
462 dict.set_item("multiplier", self.multiplier.to_string())?;
463 dict.set_item("is_inverse", self.is_inverse)?;
464
465 match self.base_currency {
466 Some(base_currency) => {
467 dict.set_item("base_currency", base_currency.code.to_string())?;
468 }
469 None => dict.set_item("base_currency", py.None())?,
470 }
471 dict.set_item("quote_currency", self.quote_currency.code.to_string())?;
472 dict.set_item(
473 "settlement_currency",
474 self.settlement_currency.code.to_string(),
475 )?;
476 dict.set_item("ts_init", self.ts_init.as_u64())?;
477 dict.set_item("ts_opened", self.ts_opened.as_u64())?;
478 dict.set_item("ts_last", self.ts_last.as_u64())?;
479 match self.ts_closed {
480 Some(ts_closed) => dict.set_item("ts_closed", ts_closed.as_u64())?,
481 None => dict.set_item("ts_closed", py.None())?,
482 }
483 dict.set_item("duration_ns", self.duration_ns.to_u64())?;
484 dict.set_item("avg_px_open", self.avg_px_open)?;
485 match self.avg_px_close {
486 Some(avg_px_close) => dict.set_item("avg_px_close", avg_px_close)?,
487 None => dict.set_item("avg_px_close", py.None())?,
488 }
489 dict.set_item("realized_return", self.realized_return)?;
490 match self.realized_pnl {
491 Some(realized_pnl) => dict.set_item("realized_pnl", realized_pnl.to_string())?,
492 None => dict.set_item("realized_pnl", py.None())?,
493 }
494 let venue_order_ids_list =
495 PyList::new(py, self.venue_order_ids().iter().map(ToString::to_string))?;
496 dict.set_item("venue_order_ids", venue_order_ids_list)?;
497 let trade_ids_list = PyList::new(py, self.trade_ids().iter().map(ToString::to_string))?;
498 dict.set_item("trade_ids", trade_ids_list)?;
499 dict.set_item("buy_qty", self.buy_qty.to_string())?;
500 dict.set_item("sell_qty", self.sell_qty.to_string())?;
501 dict.set_item("commissions", commissions_from_vec(py, self.commissions())?)?;
502 Ok(dict.into())
503 }
504}
505
506#[must_use]
518#[pyfunction]
519#[pyo3(name = "fold_net_position")]
520#[allow(
521 clippy::needless_pass_by_value,
522 reason = "PyO3 cannot extract Python list into &[T]; Vec<T> ownership is required"
523)]
524pub fn py_fold_net_position(legs: Vec<(Decimal, Decimal, u64)>) -> (Decimal, Decimal) {
525 position::fold_net_position(&legs)
526}