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