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