nautilus_execution/python/reconciliation.rs
1// -------------------------------------------------------------------------------------------------
2// Copyright (C) 2015-2026 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
16//! Python bindings for reconciliation functions.
17
18use nautilus_core::python::to_pyvalue_err;
19use nautilus_model::{
20 python::instruments::pyobject_to_instrument_any, reports::ExecutionMassStatus,
21};
22use pyo3::{
23 IntoPyObjectExt,
24 prelude::*,
25 types::{PyDict, PyTuple},
26};
27use rust_decimal::Decimal;
28
29use crate::reconciliation::{
30 calculate_reconciliation_price, process_mass_status_for_reconciliation,
31};
32
33/// Process mass status for position reconciliation.
34///
35/// Takes ExecutionMassStatus and Instrument, performs all reconciliation logic in Rust,
36/// and returns tuple of (order_reports, fill_reports) ready for processing.
37///
38/// # Returns
39///
40/// Tuple of `(Dict[str, OrderStatusReport], Dict[str, List[FillReport]])`
41///
42/// # Errors
43///
44/// Returns an error if instrument conversion or reconciliation fails.
45#[pyfunction(name = "adjust_fills_for_partial_window")]
46#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.execution")]
47#[pyo3(signature = (mass_status, instrument, tolerance=None))]
48pub fn py_adjust_fills_for_partial_window(
49 py: Python<'_>,
50 mass_status: &Bound<'_, PyAny>,
51 instrument: Py<PyAny>,
52 tolerance: Option<String>,
53) -> PyResult<Py<PyTuple>> {
54 let instrument_any = pyobject_to_instrument_any(py, instrument)?;
55 let mass_status_obj: ExecutionMassStatus = mass_status.extract()?;
56
57 let tol = tolerance
58 .map(|s| Decimal::from_str_exact(&s).map_err(to_pyvalue_err))
59 .transpose()?;
60
61 let result = process_mass_status_for_reconciliation(&mass_status_obj, &instrument_any, tol)
62 .map_err(to_pyvalue_err)?;
63
64 let orders_dict = PyDict::new(py);
65 for (id, order) in result.orders {
66 orders_dict.set_item(id.to_string(), order.into_py_any(py)?)?;
67 }
68
69 let fills_dict = PyDict::new(py);
70 for (id, fills) in result.fills {
71 let fills_list: Result<Vec<_>, _> = fills.into_iter().map(|f| f.into_py_any(py)).collect();
72 fills_dict.set_item(id.to_string(), fills_list?)?;
73 }
74
75 Ok(PyTuple::new(
76 py,
77 [orders_dict.into_py_any(py)?, fills_dict.into_py_any(py)?],
78 )?
79 .into())
80}
81
82/// Calculate the price needed for a reconciliation order to achieve target position.
83///
84/// This is a pure function that calculates what price a fill would need to have
85/// to move from the current position state to the target position state with the
86/// correct average price, accounting for the netting simulation logic.
87///
88/// # Returns
89///
90/// Returns `Some(Decimal)` if a valid reconciliation price can be calculated, `None` otherwise.
91///
92/// # Notes
93///
94/// The function handles four scenarios:
95/// 1. Position to flat: reconciliation_px = current_avg_px (close at current average)
96/// 2. Flat to position: reconciliation_px = target_avg_px
97/// 3. Position flip (sign change): reconciliation_px = target_avg_px (due to value reset in simulation)
98/// 4. Accumulation/reduction: weighted average formula
99#[pyfunction(name = "calculate_reconciliation_price")]
100#[pyo3_stub_gen::derive::gen_stub_pyfunction(module = "nautilus_trader.execution")]
101#[pyo3(signature = (current_position_qty, current_position_avg_px, target_position_qty, target_position_avg_px))]
102pub fn py_calculate_reconciliation_price(
103 current_position_qty: Decimal,
104 current_position_avg_px: Option<Decimal>,
105 target_position_qty: Decimal,
106 target_position_avg_px: Option<Decimal>,
107) -> Option<Decimal> {
108 calculate_reconciliation_price(
109 current_position_qty,
110 current_position_avg_px,
111 target_position_qty,
112 target_position_avg_px,
113 )
114}