nautilus_model/python/
mod.rs1#![expect(
19 clippy::missing_errors_doc,
20 reason = "errors documented on underlying Rust methods"
21)]
22
23use nautilus_core::python::to_pyvalue_err;
24use pyo3::{PyErr, prelude::*};
25
26pub mod account;
27pub mod common;
28pub mod data;
29pub mod enums;
30pub mod events;
31pub mod identifiers;
32pub mod instruments;
33pub mod macros;
34pub mod orderbook;
35pub mod orders;
36pub mod position;
37pub mod reports;
38pub mod types;
39
40#[cfg(feature = "defi")]
41pub mod defi;
42
43use crate::{
44 identifiers::{InstrumentIdError, OptionSeriesIdError},
45 types::CurrencyLookupError,
46};
47
48#[must_use]
50#[allow(
51 clippy::needless_pass_by_value,
52 reason = "Result::map_err passes owned errors to conversion functions"
53)]
54pub fn instrument_id_error_to_pyvalue_err(e: InstrumentIdError) -> PyErr {
55 to_pyvalue_err(e)
56}
57
58#[must_use]
60#[allow(
61 clippy::needless_pass_by_value,
62 reason = "Result::map_err passes owned errors to conversion functions"
63)]
64pub fn option_series_id_error_to_pyvalue_err(e: OptionSeriesIdError) -> PyErr {
65 to_pyvalue_err(e)
66}
67
68#[must_use]
70#[allow(
71 clippy::needless_pass_by_value,
72 reason = "Result::map_err passes owned errors to conversion functions"
73)]
74pub fn currency_lookup_error_to_pyvalue_err(e: CurrencyLookupError) -> PyErr {
75 to_pyvalue_err(e)
76}
77
78#[pymodule]
85pub fn model(_: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
86 m.add("HIGH_PRECISION", crate::types::fixed::HIGH_PRECISION_MODE)?;
88 m.add("FIXED_SCALAR", crate::types::fixed::FIXED_SCALAR)?;
89 m.add("FIXED_PRECISION", crate::types::fixed::FIXED_PRECISION)?;
90 m.add("PRECISION_BYTES", crate::types::fixed::PRECISION_BYTES)?;
91 m.add_class::<crate::types::currency::Currency>()?;
92 m.add_class::<crate::types::money::Money>()?;
93 m.add_class::<crate::types::price::Price>()?;
94 m.add_class::<crate::types::quantity::Quantity>()?;
95 m.add_class::<crate::types::balance::AccountBalance>()?;
96 m.add_class::<crate::types::balance::MarginBalance>()?;
97 m.add_function(wrap_pyfunction!(data::drop_cvec_pycapsule, m)?)?;
99 m.add_class::<crate::data::DataType>()?;
100 m.add_class::<crate::data::CustomData>()?;
101 m.add_function(pyo3::wrap_pyfunction!(
102 crate::python::data::deserialize_custom_from_json,
103 m
104 )?)?;
105 m.add_function(pyo3::wrap_pyfunction!(
106 crate::python::data::register_custom_data_class,
107 m
108 )?)?;
109 m.add_function(pyo3::wrap_pyfunction!(
110 crate::python::data::custom::custom_data_backend_kind,
111 m
112 )?)?;
113 m.add_class::<crate::data::bar::BarSpecification>()?;
114 m.add_class::<crate::data::bar::BarType>()?;
115 m.add_class::<crate::data::bar::Bar>()?;
116 m.add_class::<crate::data::bet::Bet>()?;
117 m.add_class::<crate::data::bet::BetPosition>()?;
118 m.add_class::<crate::data::order::BookOrder>()?;
119 m.add_class::<crate::data::prices::MarkPriceUpdate>()?;
120 m.add_class::<crate::data::prices::IndexPriceUpdate>()?;
121 m.add_class::<crate::data::delta::OrderBookDelta>()?;
122 m.add_class::<crate::data::deltas::OrderBookDeltas>()?;
123 m.add_class::<crate::data::depth::OrderBookDepth10>()?;
124 m.add_class::<crate::data::quote::QuoteTick>()?;
125 m.add_class::<crate::data::status::InstrumentStatus>()?;
126 m.add_class::<crate::data::trade::TradeTick>()?;
127 m.add_class::<crate::data::close::InstrumentClose>()?;
128 m.add_class::<crate::data::funding::FundingRateUpdate>()?;
129 m.add_class::<crate::data::greeks::OptionGreekValues>()?;
130 m.add_class::<crate::data::greeks::BlackScholesGreeksResult>()?;
131 m.add_class::<crate::data::greeks::GreeksData>()?;
132 m.add_class::<crate::data::greeks::PortfolioGreeks>()?;
133 m.add_class::<crate::data::option_chain::OptionGreeks>()?;
134 m.add_class::<crate::data::option_chain::OptionChainSlice>()?;
135 m.add_class::<crate::data::option_chain::OptionStrikeData>()?;
136 m.add_class::<crate::python::data::option_chain::PyStrikeRange>()?;
137 m.add_class::<crate::data::forward::ForwardPrice>()?;
138 m.add_function(wrap_pyfunction!(
139 crate::python::data::greeks::py_black_scholes_greeks,
140 m
141 )?)?;
142 m.add_function(wrap_pyfunction!(
143 crate::python::data::greeks::py_imply_vol,
144 m
145 )?)?;
146 m.add_function(wrap_pyfunction!(
147 crate::python::data::greeks::py_imply_vol_and_greeks,
148 m
149 )?)?;
150 m.add_function(wrap_pyfunction!(
151 crate::python::data::greeks::py_refine_vol_and_greeks,
152 m
153 )?)?;
154 m.add_class::<crate::enums::AccountType>()?;
156 m.add_class::<crate::enums::AggregationSource>()?;
157 m.add_class::<crate::enums::AggressorSide>()?;
158 m.add_class::<crate::enums::AssetClass>()?;
159 m.add_class::<crate::enums::BarAggregation>()?;
160 m.add_class::<crate::enums::BarIntervalType>()?;
161 m.add_class::<crate::enums::BetSide>()?;
162 m.add_class::<crate::enums::BookAction>()?;
163 m.add_class::<crate::enums::BookType>()?;
164 m.add_class::<crate::enums::ContingencyType>()?;
165 m.add_class::<crate::enums::ContinuousFutureAdjustmentType>()?;
166 m.add_class::<crate::enums::CurrencyType>()?;
167 m.add_class::<crate::enums::GreeksConvention>()?;
168 m.add_class::<crate::enums::InstrumentClass>()?;
169 m.add_class::<crate::enums::InstrumentCloseType>()?;
170 m.add_class::<crate::enums::LiquiditySide>()?;
171 m.add_class::<crate::enums::MarketStatus>()?;
172 m.add_class::<crate::enums::MarketStatusAction>()?;
173 m.add_class::<crate::enums::OmsType>()?;
174 m.add_class::<crate::enums::OptionKind>()?;
175 m.add_class::<crate::enums::OtoTriggerMode>()?;
176 m.add_class::<crate::enums::OrderSide>()?;
177 m.add_class::<crate::enums::OrderStatus>()?;
178 m.add_class::<crate::enums::OrderType>()?;
179 m.add_class::<crate::enums::PositionAdjustmentType>()?;
180 m.add_class::<crate::enums::PositionSide>()?;
181 m.add_class::<crate::enums::PriceType>()?;
182 m.add_class::<crate::enums::TimeInForce>()?;
183 m.add_class::<crate::enums::TradingState>()?;
184 m.add_class::<crate::enums::TrailingOffsetType>()?;
185 m.add_class::<crate::enums::TriggerType>()?;
186 m.add_class::<crate::identifiers::AccountId>()?;
188 m.add_class::<crate::identifiers::ActorId>()?;
189 m.add_class::<crate::identifiers::ClientId>()?;
190 m.add_class::<crate::identifiers::ClientOrderId>()?;
191 m.add_class::<crate::identifiers::ComponentId>()?;
192 m.add_class::<crate::identifiers::ExecAlgorithmId>()?;
193 m.add_class::<crate::identifiers::InstrumentId>()?;
194 m.add_class::<crate::identifiers::OrderListId>()?;
195 m.add_class::<crate::identifiers::PositionId>()?;
196 m.add_class::<crate::identifiers::StrategyId>()?;
197 m.add_class::<crate::identifiers::Symbol>()?;
198 m.add_class::<crate::identifiers::TradeId>()?;
199 m.add_class::<crate::identifiers::TraderId>()?;
200 m.add_class::<crate::identifiers::Venue>()?;
201 m.add_class::<crate::identifiers::VenueOrderId>()?;
202 m.add_class::<crate::identifiers::OptionSeriesId>()?;
203 m.add_class::<crate::orders::LimitOrder>()?;
205 m.add_class::<crate::orders::LimitIfTouchedOrder>()?;
206 m.add_class::<crate::orders::MarketIfTouchedOrder>()?;
207 m.add_class::<crate::orders::MarketOrder>()?;
208 m.add_class::<crate::orders::MarketToLimitOrder>()?;
209 m.add_class::<crate::orders::StopLimitOrder>()?;
210 m.add_class::<crate::orders::StopMarketOrder>()?;
211 m.add_class::<crate::orders::TrailingStopLimitOrder>()?;
212 m.add_class::<crate::orders::TrailingStopMarketOrder>()?;
213 m.add_class::<crate::reports::fill::FillReport>()?;
215 m.add_class::<crate::reports::order::OrderStatusReport>()?;
216 m.add_class::<crate::reports::position::PositionStatusReport>()?;
217 m.add_class::<crate::reports::mass_status::ExecutionMassStatus>()?;
218 m.add_class::<crate::position::Position>()?;
220 m.add_function(wrap_pyfunction!(
221 crate::python::position::py_fold_net_position,
222 m
223 )?)?;
224 m.add_class::<crate::instruments::BettingInstrument>()?;
226 m.add_class::<crate::instruments::BinaryOption>()?;
227 m.add_class::<crate::instruments::Cfd>()?;
228 m.add_class::<crate::instruments::Commodity>()?;
229 m.add_class::<crate::instruments::CryptoFuture>()?;
230 m.add_class::<crate::instruments::CryptoFuturesSpread>()?;
231 m.add_class::<crate::instruments::CryptoOption>()?;
232 m.add_class::<crate::instruments::CryptoOptionSpread>()?;
233 m.add_class::<crate::instruments::CryptoPerpetual>()?;
234 m.add_class::<crate::instruments::CurrencyPair>()?;
235 m.add_class::<crate::instruments::Equity>()?;
236 m.add_class::<crate::instruments::FuturesContract>()?;
237 m.add_class::<crate::instruments::FuturesSpread>()?;
238 m.add_class::<crate::instruments::IndexInstrument>()?;
239 m.add_class::<crate::instruments::OptionContract>()?;
240 m.add_class::<crate::instruments::OptionSpread>()?;
241 m.add_class::<crate::instruments::PerpetualContract>()?;
242 m.add_class::<crate::instruments::SyntheticInstrument>()?;
243 m.add_class::<crate::instruments::TokenizedAsset>()?;
244 m.add_class::<crate::orderbook::book::OrderBook>()?;
246 m.add_class::<crate::orderbook::level::BookLevel>()?;
247 m.add_function(wrap_pyfunction!(
248 crate::python::orderbook::book::py_update_book_with_quote_tick,
249 m
250 )?)?;
251 m.add_function(wrap_pyfunction!(
252 crate::python::orderbook::book::py_update_book_with_trade_tick,
253 m
254 )?)?;
255 m.add_class::<crate::orderbook::own::OwnOrderBook>()?;
256 m.add_class::<crate::orderbook::own::OwnBookOrder>()?;
257 m.add_class::<crate::events::AccountState>()?;
259 m.add_class::<crate::events::OrderDenied>()?;
260 m.add_class::<crate::events::OrderFilled>()?;
261 m.add_class::<crate::events::OrderInitialized>()?;
262 m.add_class::<crate::events::OrderRejected>()?;
263 m.add_class::<crate::events::OrderTriggered>()?;
264 m.add_class::<crate::events::OrderSubmitted>()?;
265 m.add_class::<crate::events::OrderEmulated>()?;
266 m.add_class::<crate::events::OrderReleased>()?;
267 m.add_class::<crate::events::OrderUpdated>()?;
268 m.add_class::<crate::events::OrderPendingUpdate>()?;
269 m.add_class::<crate::events::OrderPendingCancel>()?;
270 m.add_class::<crate::events::OrderModifyRejected>()?;
271 m.add_class::<crate::events::OrderAccepted>()?;
272 m.add_class::<crate::events::OrderCancelRejected>()?;
273 m.add_class::<crate::events::OrderCanceled>()?;
274 m.add_class::<crate::events::OrderExpired>()?;
275 m.add_class::<crate::events::OrderSnapshot>()?;
276 m.add_class::<crate::events::PositionOpened>()?;
277 m.add_class::<crate::events::PositionChanged>()?;
278 m.add_class::<crate::events::PositionClosed>()?;
279 m.add_class::<crate::events::PositionAdjusted>()?;
280 m.add_class::<crate::events::PositionSnapshot>()?;
281 m.add_class::<crate::events::PortfolioSnapshot>()?;
282 m.add_class::<crate::accounts::BettingAccount>()?;
284 m.add_class::<crate::accounts::CashAccount>()?;
285 m.add_class::<crate::accounts::MarginAccount>()?;
286 m.add_class::<crate::accounts::margin_model::StandardMarginModel>()?;
287 m.add_class::<crate::accounts::margin_model::LeveragedMarginModel>()?;
288 m.add_function(wrap_pyfunction!(
289 crate::python::account::transformer::betting_account_from_account_events,
290 m
291 )?)?;
292 m.add_function(wrap_pyfunction!(
293 crate::python::account::transformer::cash_account_from_account_events,
294 m
295 )?)?;
296 m.add_function(wrap_pyfunction!(
297 crate::python::account::transformer::margin_account_from_account_events,
298 m
299 )?)?;
300 m.add_function(wrap_pyfunction!(
301 crate::python::data::bet::py_calc_bets_pnl,
302 m
303 )?)?;
304 m.add_function(wrap_pyfunction!(
305 crate::python::data::bet::py_probability_to_bet,
306 m
307 )?)?;
308 m.add_function(wrap_pyfunction!(
309 crate::python::data::bet::py_inverse_probability_to_bet,
310 m
311 )?)?;
312 #[cfg(feature = "defi")]
314 {
315 m.add_class::<crate::defi::chain::Blockchain>()?;
316 m.add_class::<crate::defi::chain::Chain>()?;
317 m.add_class::<crate::defi::token::Token>()?;
318 m.add_class::<crate::defi::dex::AmmType>()?;
319 m.add_class::<crate::defi::dex::Dex>()?;
320 m.add_class::<crate::defi::amm::Pool>()?;
321 m.add_class::<crate::defi::data::PoolSwap>()?;
322 m.add_class::<crate::defi::data::PoolLiquidityUpdateType>()?;
323 m.add_class::<crate::defi::data::PoolLiquidityUpdate>()?;
324 m.add_class::<crate::defi::data::PoolFeeCollect>()?;
325 m.add_class::<crate::defi::data::PoolFeeProtocolUpdate>()?;
326 m.add_class::<crate::defi::data::PoolFeeProtocolCollect>()?;
327 m.add_class::<crate::defi::data::PoolFlash>()?;
328 m.add_class::<crate::defi::data::Transaction>()?;
329 m.add_class::<crate::defi::data::Block>()?;
330 m.add_class::<crate::defi::data::DefiData>()?;
331 m.add_class::<crate::defi::dex::DexType>()?;
332 m.add_class::<crate::defi::pool_analysis::PoolSnapshot>()?;
333 m.add_class::<crate::defi::pool_analysis::PoolProfiler>()?;
334 }
335 Ok(())
336}
337
338#[cfg(test)]
339mod tests {
340 use std::sync::Once;
341
342 use pyo3::{Python, exceptions::PyValueError};
343 use rstest::rstest;
344
345 use super::*;
346
347 fn ensure_python_initialized() {
348 static INIT: Once = Once::new();
349 INIT.call_once(|| {
350 Python::initialize();
351 });
352 }
353
354 #[rstest]
355 fn test_instrument_id_error_to_pyvalue_err_preserves_display_text() {
356 ensure_python_initialized();
357
358 let error = "BTCUSDT"
359 .parse::<crate::identifiers::InstrumentId>()
360 .unwrap_err();
361
362 Python::attach(|py| {
363 let py_err = instrument_id_error_to_pyvalue_err(error);
364
365 assert!(py_err.is_instance_of::<PyValueError>(py));
366 assert_eq!(
367 py_err.value(py).to_string(),
368 "invalid `InstrumentId` value 'BTCUSDT': missing '.' separator between symbol and venue components"
369 );
370 });
371 }
372
373 #[rstest]
374 fn test_option_series_id_error_to_pyvalue_err_preserves_display_text() {
375 ensure_python_initialized();
376
377 let error = "DERIBIT:BTC:USD"
378 .parse::<crate::identifiers::OptionSeriesId>()
379 .unwrap_err();
380
381 Python::attach(|py| {
382 let py_err = option_series_id_error_to_pyvalue_err(error);
383
384 assert!(py_err.is_instance_of::<PyValueError>(py));
385 assert_eq!(
386 py_err.value(py).to_string(),
387 "invalid `OptionSeriesId` value 'DERIBIT:BTC:USD': expected format 'VENUE:UNDERLYING:SETTLEMENT:EXPIRY'"
388 );
389 });
390 }
391
392 #[rstest]
393 fn test_currency_lookup_error_to_pyvalue_err_preserves_display_text() {
394 ensure_python_initialized();
395
396 let error = "UNKNOWN_CURRENCY"
397 .parse::<crate::types::Currency>()
398 .unwrap_err();
399
400 Python::attach(|py| {
401 let py_err = currency_lookup_error_to_pyvalue_err(error);
402
403 assert!(py_err.is_instance_of::<PyValueError>(py));
404 assert_eq!(
405 py_err.value(py).to_string(),
406 "Unknown currency: UNKNOWN_CURRENCY"
407 );
408 });
409 }
410}