nautilus_model/python/
position.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use nautilus_core::python::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,
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: PyObject, 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(py),
48            CompareOp::Ne => self.ne(other).into_py(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 = "client_order_ids")]
237    fn py_client_order_ids(&self) -> Vec<ClientOrderId> {
238        self.client_order_ids()
239    }
240
241    #[getter]
242    #[pyo3(name = "venue_order_ids")]
243    fn py_venue_order_ids(&self) -> Vec<VenueOrderId> {
244        self.venue_order_ids()
245    }
246
247    #[getter]
248    #[pyo3(name = "trade_ids")]
249    fn py_trade_ids(&self) -> Vec<TradeId> {
250        self.trade_ids()
251    }
252
253    #[getter]
254    #[pyo3(name = "last_event")]
255    fn py_last_event(&self) -> OrderFilled {
256        self.last_event()
257    }
258
259    #[getter]
260    #[pyo3(name = "last_trade_id")]
261    fn py_last_trade_id(&self) -> Option<TradeId> {
262        self.last_trade_id()
263    }
264
265    #[getter]
266    #[pyo3(name = "event_count")]
267    fn py_event_count(&self) -> usize {
268        self.events.len()
269    }
270
271    #[getter]
272    #[pyo3(name = "is_open")]
273    fn py_is_open(&self) -> bool {
274        self.is_open()
275    }
276
277    #[getter]
278    #[pyo3(name = "is_closed")]
279    fn py_is_closed(&self) -> bool {
280        self.is_closed()
281    }
282
283    #[getter]
284    #[pyo3(name = "is_long")]
285    fn py_is_long(&self) -> bool {
286        self.is_long()
287    }
288
289    #[getter]
290    #[pyo3(name = "is_short")]
291    fn py_is_short(&self) -> bool {
292        self.is_short()
293    }
294
295    #[pyo3(name = "unrealized_pnl")]
296    fn py_unrealized_pnl(&self, last: Price) -> Money {
297        self.unrealized_pnl(last)
298    }
299
300    #[pyo3(name = "total_pnl")]
301    fn py_total_pnl(&self, last: Price) -> Money {
302        self.total_pnl(last)
303    }
304
305    #[pyo3(name = "commissions")]
306    fn py_commissions(&self) -> Vec<Money> {
307        self.commissions()
308    }
309
310    #[pyo3(name = "apply")]
311    fn py_apply(&mut self, fill: &OrderFilled) {
312        self.apply(fill);
313    }
314
315    #[pyo3(name = "is_opposite_side")]
316    fn py_is_opposite_side(&self, side: OrderSide) -> bool {
317        self.is_opposite_side(side)
318    }
319
320    #[pyo3(name = "calculate_pnl")]
321    fn py_calculate_pnl(&self, avg_px_open: f64, avg_px_close: f64, quantity: Quantity) -> Money {
322        self.calculate_pnl(avg_px_open, avg_px_close, quantity)
323    }
324
325    #[pyo3(name = "notional_value")]
326    fn py_notional_value(&self, price: Price) -> Money {
327        self.notional_value(price)
328    }
329
330    #[staticmethod]
331    #[pyo3(name = "from_dict")]
332    pub fn py_from_dict(py: Python<'_>, values: Py<PyDict>) -> PyResult<Self> {
333        from_dict_pyo3(py, values)
334    }
335
336    #[pyo3(name = "to_dict")]
337    fn py_to_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
338        let dict = PyDict::new(py);
339        dict.set_item("type", stringify!(Position))?;
340        let events_dict: PyResult<Vec<_>> = self.events.iter().map(|e| e.py_to_dict(py)).collect();
341        dict.set_item("events", events_dict?)?;
342        dict.set_item("trader_id", self.trader_id.to_string())?;
343        dict.set_item("strategy_id", self.strategy_id.to_string())?;
344        dict.set_item("instrument_id", self.instrument_id.to_string())?;
345        dict.set_item("position_id", self.id.to_string())?;
346        dict.set_item("account_id", self.account_id.to_string())?;
347        dict.set_item("opening_order_id", self.opening_order_id.to_string())?;
348        match self.closing_order_id {
349            Some(closing_order_id) => {
350                dict.set_item("closing_order_id", closing_order_id.to_string())?;
351            }
352            None => dict.set_item("closing_order_id", py.None())?,
353        }
354        dict.set_item("entry", self.entry.to_string())?;
355        dict.set_item("side", self.side.to_string())?;
356        dict.set_item("signed_qty", self.signed_qty.to_f64())?;
357        dict.set_item("quantity", self.quantity.to_string())?;
358        dict.set_item("peak_qty", self.peak_qty.to_string())?;
359        dict.set_item("price_precision", self.price_precision.to_u8())?;
360        dict.set_item("size_precision", self.size_precision.to_u8())?;
361        dict.set_item("multiplier", self.multiplier.to_string())?;
362        dict.set_item("is_inverse", self.is_inverse)?;
363        match self.base_currency {
364            Some(base_currency) => {
365                dict.set_item("base_currency", base_currency.code.to_string())?;
366            }
367            None => dict.set_item("base_currency", py.None())?,
368        }
369        dict.set_item("quote_currency", self.quote_currency.code.to_string())?;
370        dict.set_item(
371            "settlement_currency",
372            self.settlement_currency.code.to_string(),
373        )?;
374        dict.set_item("ts_init", self.ts_init.as_u64())?;
375        dict.set_item("ts_opened", self.ts_opened.as_u64())?;
376        dict.set_item("ts_last", self.ts_last.as_u64())?;
377        match self.ts_closed {
378            Some(ts_closed) => dict.set_item("ts_closed", ts_closed.as_u64())?,
379            None => dict.set_item("ts_closed", py.None())?,
380        }
381        dict.set_item("duration_ns", self.duration_ns.to_u64())?;
382        dict.set_item("avg_px_open", self.avg_px_open)?;
383        match self.avg_px_close {
384            Some(avg_px_close) => dict.set_item("avg_px_close", avg_px_close)?,
385            None => dict.set_item("avg_px_close", py.None())?,
386        }
387        dict.set_item("realized_return", self.realized_return)?;
388        match self.realized_pnl {
389            Some(realized_pnl) => dict.set_item("realized_pnl", realized_pnl.to_string())?,
390            None => dict.set_item("realized_pnl", py.None())?,
391        }
392        let venue_order_ids_list = PyList::new(
393            py,
394            self.venue_order_ids()
395                .iter()
396                .map(std::string::ToString::to_string),
397        )
398        .expect("Invalid `ExactSizeIterator`");
399        dict.set_item("venue_order_ids", venue_order_ids_list)?;
400        let trade_ids_list = PyList::new(
401            py,
402            self.trade_ids.iter().map(std::string::ToString::to_string),
403        )
404        .expect("Invalid `ExactSizeIterator`");
405        dict.set_item("trade_ids", trade_ids_list)?;
406        dict.set_item("buy_qty", self.buy_qty.to_string())?;
407        dict.set_item("sell_qty", self.sell_qty.to_string())?;
408        dict.set_item("commissions", commissions_from_vec(py, self.commissions())?)?;
409        Ok(dict.into())
410    }
411}