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]
84pub fn model(_: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
85 m.add("HIGH_PRECISION", crate::types::fixed::HIGH_PRECISION_MODE)?;
87 m.add("FIXED_SCALAR", crate::types::fixed::FIXED_SCALAR)?;
88 m.add("FIXED_PRECISION", crate::types::fixed::FIXED_PRECISION)?;
89 m.add("PRECISION_BYTES", crate::types::fixed::PRECISION_BYTES)?;
90 m.add_class::<crate::types::currency::Currency>()?;
91 m.add_class::<crate::types::money::Money>()?;
92 m.add_class::<crate::types::price::Price>()?;
93 m.add_class::<crate::types::quantity::Quantity>()?;
94 m.add_class::<crate::types::balance::AccountBalance>()?;
95 m.add_class::<crate::types::balance::MarginBalance>()?;
96 m.add_class::<crate::python::common::EnumIterator>()?;
97 m.add_class::<data::PyNautilusDataType>()?;
99 m.add_class::<data::PyNautilusRecordType>()?;
100 m.add_class::<instruments::PyNautilusInstrumentType>()?;
101 for record_type in <crate::data::NautilusRecordType as strum::IntoEnumIterator>::iter() {
102 m.getattr("NautilusRecordType")?.setattr(
103 record_type.to_string(),
104 Py::new(m.py(), data::PyNautilusRecordType::new(record_type))?,
105 )?;
106 }
107
108 for instrument_type in
109 <crate::instruments::NautilusInstrumentType as strum::IntoEnumIterator>::iter()
110 {
111 m.getattr("NautilusInstrumentType")?.setattr(
112 instrument_type.to_string(),
113 Py::new(
114 m.py(),
115 instruments::PyNautilusInstrumentType::new(instrument_type),
116 )?,
117 )?;
118 }
119 m.add_class::<crate::data::DataType>()?;
120 m.add_class::<crate::data::CustomData>()?;
121 m.add_function(pyo3::wrap_pyfunction!(
122 crate::python::data::deserialize_custom_from_json,
123 m
124 )?)?;
125 m.add_function(pyo3::wrap_pyfunction!(
126 crate::python::data::register_custom_data_class,
127 m
128 )?)?;
129 m.add_function(pyo3::wrap_pyfunction!(
130 crate::python::data::custom::custom_data_backend_kind,
131 m
132 )?)?;
133 m.add_class::<crate::data::bar::BarSpecification>()?;
134 m.add_class::<crate::data::bar::BarType>()?;
135 m.add_class::<crate::data::bar::Bar>()?;
136 m.add_class::<crate::data::bet::Bet>()?;
137 m.add_class::<crate::data::bet::BetPosition>()?;
138 m.add_class::<crate::data::order::BookOrder>()?;
139 m.add_class::<crate::data::prices::MarkPriceUpdate>()?;
140 m.add_class::<crate::data::prices::IndexPriceUpdate>()?;
141 m.add_class::<crate::data::delta::OrderBookDelta>()?;
142 m.add_class::<crate::data::deltas::OrderBookDeltas>()?;
143 m.add_class::<crate::data::depth::OrderBookDepth>()?;
144 m.add_class::<crate::data::quote::QuoteTick>()?;
145 m.add_class::<crate::data::status::InstrumentStatus>()?;
146 m.add_class::<crate::data::trade::TradeTick>()?;
147 m.add_class::<crate::data::close::InstrumentClose>()?;
148 m.add_class::<crate::data::funding::FundingRateUpdate>()?;
149 m.add_class::<crate::data::greeks::OptionGreekValues>()?;
150 m.add_class::<crate::data::greeks::BlackScholesGreeksResult>()?;
151 m.add_class::<crate::data::greeks::GreeksData>()?;
152 m.add_class::<crate::data::greeks::PortfolioGreeks>()?;
153 m.add_class::<crate::data::option_chain::OptionGreeks>()?;
154 m.add_class::<crate::data::option_chain::OptionChainSlice>()?;
155 m.add_class::<crate::data::option_chain::OptionStrikeData>()?;
156 m.add_class::<crate::python::data::option_chain::PyStrikeRange>()?;
157 m.add_function(wrap_pyfunction!(
158 crate::python::data::greeks::py_black_scholes_greeks,
159 m
160 )?)?;
161 m.add_function(wrap_pyfunction!(
162 crate::python::data::greeks::py_imply_vol,
163 m
164 )?)?;
165 m.add_function(wrap_pyfunction!(
166 crate::python::data::greeks::py_imply_vol_and_greeks,
167 m
168 )?)?;
169 m.add_function(wrap_pyfunction!(
170 crate::python::data::greeks::py_refine_vol_and_greeks,
171 m
172 )?)?;
173 m.add_class::<crate::enums::AccountType>()?;
175 m.add_class::<crate::enums::AggregationSource>()?;
176 m.add_class::<crate::enums::AggressorSide>()?;
177 m.add_class::<crate::enums::AssetClass>()?;
178 m.add_class::<crate::enums::BarAggregation>()?;
179 m.add_class::<crate::enums::BarIntervalType>()?;
180 m.add_class::<crate::enums::BetSide>()?;
181 m.add_class::<crate::enums::BookAction>()?;
182 m.add_class::<crate::enums::BookType>()?;
183 m.add_class::<crate::enums::ContingencyType>()?;
184 m.add_class::<crate::enums::ContinuousFutureAdjustmentType>()?;
185 m.add_class::<crate::enums::CurrencyType>()?;
186 m.add_class::<crate::enums::GreeksConvention>()?;
187 m.add_class::<crate::enums::InstrumentClass>()?;
188 m.add_class::<crate::enums::InstrumentCloseType>()?;
189 m.add_class::<crate::enums::LiquiditySide>()?;
190 m.add_class::<crate::enums::MarketStatus>()?;
191 m.add_class::<crate::enums::MarketStatusAction>()?;
192 m.add_class::<crate::enums::OmsType>()?;
193 m.add_class::<crate::enums::OptionKind>()?;
194 m.add_class::<crate::enums::OtoTriggerMode>()?;
195 m.add_class::<crate::enums::OrderSide>()?;
196 m.add_class::<crate::enums::OrderStatus>()?;
197 m.add_class::<crate::enums::OrderType>()?;
198 m.add_class::<crate::enums::PositionAdjustmentType>()?;
199 m.add_class::<crate::enums::PositionSide>()?;
200 m.add_class::<crate::enums::PriceType>()?;
201 m.add_class::<crate::enums::RecordFlag>()?;
202 m.add_class::<crate::enums::TimeInForce>()?;
203 m.add_class::<crate::enums::TradingState>()?;
204 m.add_class::<crate::enums::TrailingOffsetType>()?;
205 m.add_class::<crate::enums::TriggerType>()?;
206 m.add_class::<crate::identifiers::AccountId>()?;
208 m.add_class::<crate::identifiers::ActorId>()?;
209 m.add_class::<crate::identifiers::ClientId>()?;
210 m.add_class::<crate::identifiers::ClientOrderId>()?;
211 m.add_class::<crate::identifiers::ComponentId>()?;
212 m.add_class::<crate::identifiers::ExecAlgorithmId>()?;
213 m.add_class::<crate::identifiers::InstrumentId>()?;
214 m.add_class::<crate::identifiers::OrderListId>()?;
215 m.add_class::<crate::identifiers::PositionId>()?;
216 m.add_class::<crate::identifiers::StrategyId>()?;
217 m.add_class::<crate::identifiers::Symbol>()?;
218 m.add_class::<crate::identifiers::TradeId>()?;
219 m.add_class::<crate::identifiers::TraderId>()?;
220 m.add_class::<crate::identifiers::Venue>()?;
221 m.add_class::<crate::identifiers::VenueOrderId>()?;
222 m.add_class::<crate::identifiers::OptionSeriesId>()?;
223 m.add_class::<crate::orders::LimitOrder>()?;
225 m.add_class::<crate::orders::LimitIfTouchedOrder>()?;
226 m.add_class::<crate::orders::MarketIfTouchedOrder>()?;
227 m.add_class::<crate::orders::MarketOrder>()?;
228 m.add_class::<crate::orders::MarketToLimitOrder>()?;
229 m.add_class::<crate::orders::OrderList>()?;
230 m.add_class::<crate::orders::StopLimitOrder>()?;
231 m.add_class::<crate::orders::StopMarketOrder>()?;
232 m.add_class::<crate::orders::TrailingStopLimitOrder>()?;
233 m.add_class::<crate::orders::TrailingStopMarketOrder>()?;
234 m.add_class::<crate::reports::fill::FillReport>()?;
236 m.add_class::<crate::reports::order::OrderStatusReport>()?;
237 m.add_class::<crate::reports::position::PositionStatusReport>()?;
238 m.add_class::<crate::reports::mass_status::ExecutionMassStatus>()?;
239 m.add_class::<crate::position::Position>()?;
241 m.add_function(wrap_pyfunction!(
242 crate::python::position::py_fold_net_position,
243 m
244 )?)?;
245 m.add_class::<crate::instruments::BettingInstrument>()?;
247 m.add_class::<crate::instruments::BinaryOption>()?;
248 m.add_class::<crate::instruments::Cfd>()?;
249 m.add_class::<crate::instruments::Commodity>()?;
250 m.add_class::<crate::instruments::CryptoFuture>()?;
251 m.add_class::<crate::instruments::CryptoFuturesSpread>()?;
252 m.add_class::<crate::instruments::CryptoOption>()?;
253 m.add_class::<crate::instruments::CryptoOptionSpread>()?;
254 m.add_class::<crate::instruments::CryptoPerpetual>()?;
255 m.add_class::<crate::instruments::CurrencyPair>()?;
256 m.add_class::<crate::instruments::Equity>()?;
257 m.add_class::<crate::instruments::FuturesContract>()?;
258 m.add_class::<crate::instruments::FuturesSpread>()?;
259 m.add_class::<crate::instruments::IndexInstrument>()?;
260 m.add_class::<crate::instruments::OptionContract>()?;
261 m.add_class::<crate::instruments::OptionSpread>()?;
262 m.add_class::<crate::instruments::PerpetualContract>()?;
263 m.add_class::<crate::instruments::SyntheticInstrument>()?;
264 m.add_class::<crate::instruments::TokenizedAsset>()?;
265 m.add_class::<crate::orderbook::book::OrderBook>()?;
267 m.add_class::<crate::orderbook::level::BookLevel>()?;
268 m.add_function(wrap_pyfunction!(
269 crate::python::orderbook::book::py_update_book_with_quote_tick,
270 m
271 )?)?;
272 m.add_function(wrap_pyfunction!(
273 crate::python::orderbook::book::py_update_book_with_trade_tick,
274 m
275 )?)?;
276 m.add_class::<crate::orderbook::own::OwnOrderBook>()?;
277 m.add_class::<crate::orderbook::own::OwnBookOrder>()?;
278 m.add_class::<crate::events::AccountState>()?;
280 m.add_class::<crate::events::OrderDenied>()?;
281 m.add_class::<crate::events::OrderFilled>()?;
282 m.add_class::<crate::events::OrderFillVoided>()?;
283 m.add_class::<crate::events::OrderInitialized>()?;
284 m.add_class::<crate::events::OrderRejected>()?;
285 m.add_class::<crate::events::OrderTriggered>()?;
286 m.add_class::<crate::events::OrderSubmitted>()?;
287 m.add_class::<crate::events::OrderEmulated>()?;
288 m.add_class::<crate::events::OrderReleased>()?;
289 m.add_class::<crate::events::OrderUpdated>()?;
290 m.add_class::<crate::events::OrderPendingUpdate>()?;
291 m.add_class::<crate::events::OrderPendingCancel>()?;
292 m.add_class::<crate::events::OrderModifyRejected>()?;
293 m.add_class::<crate::events::OrderAccepted>()?;
294 m.add_class::<crate::events::OrderCancelRejected>()?;
295 m.add_class::<crate::events::OrderCanceled>()?;
296 m.add_class::<crate::events::OrderExpired>()?;
297 m.add_class::<crate::events::OrderSnapshot>()?;
298 m.add_class::<crate::events::PositionOpened>()?;
299 m.add_class::<crate::events::PositionChanged>()?;
300 m.add_class::<crate::events::PositionClosed>()?;
301 m.add_class::<crate::events::PositionAdjusted>()?;
302 m.add_class::<crate::events::PositionSnapshot>()?;
303 m.add_class::<crate::events::PortfolioSnapshot>()?;
304 m.add_class::<crate::accounts::BettingAccount>()?;
306 m.add_class::<crate::accounts::CashAccount>()?;
307 m.add_class::<crate::accounts::MarginAccount>()?;
308 m.add_class::<crate::accounts::WalletAccount>()?;
309 m.add_class::<crate::accounts::margin_model::StandardMarginModel>()?;
310 m.add_class::<crate::accounts::margin_model::LeveragedMarginModel>()?;
311 m.add_function(wrap_pyfunction!(
312 crate::python::account::transformer::betting_account_from_account_events,
313 m
314 )?)?;
315 m.add_function(wrap_pyfunction!(
316 crate::python::account::transformer::cash_account_from_account_events,
317 m
318 )?)?;
319 m.add_function(wrap_pyfunction!(
320 crate::python::account::transformer::margin_account_from_account_events,
321 m
322 )?)?;
323 m.add_function(wrap_pyfunction!(
324 crate::python::account::transformer::wallet_account_from_account_events,
325 m
326 )?)?;
327 m.add_function(wrap_pyfunction!(
328 crate::python::data::bet::py_calc_bets_pnl,
329 m
330 )?)?;
331 m.add_function(wrap_pyfunction!(
332 crate::python::data::bet::py_probability_to_bet,
333 m
334 )?)?;
335 m.add_function(wrap_pyfunction!(
336 crate::python::data::bet::py_inverse_probability_to_bet,
337 m
338 )?)?;
339 #[cfg(feature = "defi")]
341 {
342 m.add_class::<crate::defi::chain::Blockchain>()?;
343 m.add_class::<crate::defi::chain::Chain>()?;
344 m.add_class::<crate::defi::token::Token>()?;
345 m.add_class::<crate::defi::dex::AmmType>()?;
346 m.add_class::<crate::defi::dex::Dex>()?;
347 m.add_class::<crate::defi::amm::Pool>()?;
348 m.add_class::<crate::defi::data::PoolSwap>()?;
349 m.add_class::<crate::defi::data::PoolLiquidityUpdateType>()?;
350 m.add_class::<crate::defi::data::PoolLiquidityUpdate>()?;
351 m.add_class::<crate::defi::data::PoolFeeCollect>()?;
352 m.add_class::<crate::defi::data::PoolFeeProtocolUpdate>()?;
353 m.add_class::<crate::defi::data::PoolFeeProtocolCollect>()?;
354 m.add_class::<crate::defi::data::PoolFlash>()?;
355 m.add_class::<crate::defi::data::Transaction>()?;
356 m.add_class::<crate::defi::data::Block>()?;
357 m.add_class::<crate::defi::data::DefiData>()?;
358 m.add_class::<crate::defi::dex::DexType>()?;
359 m.add_class::<crate::defi::pool_analysis::PoolSnapshot>()?;
360 m.add_class::<crate::defi::pool_analysis::PoolProfiler>()?;
361 m.add_class::<crate::defi::pool_analysis::position::PoolPosition>()?;
362 m.add_class::<crate::defi::pool_analysis::quote::SwapQuote>()?;
363 m.add_class::<crate::defi::pool_analysis::size_estimator::SizeForImpactResult>()?;
364 m.add_class::<crate::defi::pool_analysis::snapshot::PoolAnalytics>()?;
365 m.add_class::<crate::defi::pool_analysis::snapshot::PoolState>()?;
366 m.add_class::<crate::defi::tick_map::tick::PoolTick>()?;
367 }
368 Ok(())
369}
370
371#[cfg(test)]
372mod tests {
373 use std::sync::Once;
374
375 use pyo3::{Python, exceptions::PyValueError};
376 use rstest::rstest;
377
378 use super::*;
379
380 fn ensure_python_initialized() {
381 static INIT: Once = Once::new();
382 INIT.call_once(|| {
383 Python::initialize();
384 });
385 }
386
387 #[rstest]
388 fn test_instrument_id_error_to_pyvalue_err_preserves_display_text() {
389 ensure_python_initialized();
390
391 let error = "BTCUSDT"
392 .parse::<crate::identifiers::InstrumentId>()
393 .unwrap_err();
394
395 Python::attach(|py| {
396 let py_err = instrument_id_error_to_pyvalue_err(error);
397
398 assert!(py_err.is_instance_of::<PyValueError>(py));
399 assert_eq!(
400 py_err.value(py).to_string(),
401 "invalid `InstrumentId` value 'BTCUSDT': missing '.' separator between symbol and venue components"
402 );
403 });
404 }
405
406 #[rstest]
407 fn test_option_series_id_error_to_pyvalue_err_preserves_display_text() {
408 ensure_python_initialized();
409
410 let error = "DERIBIT:BTC:USD"
411 .parse::<crate::identifiers::OptionSeriesId>()
412 .unwrap_err();
413
414 Python::attach(|py| {
415 let py_err = option_series_id_error_to_pyvalue_err(error);
416
417 assert!(py_err.is_instance_of::<PyValueError>(py));
418 assert_eq!(
419 py_err.value(py).to_string(),
420 "invalid `OptionSeriesId` value 'DERIBIT:BTC:USD': expected format 'VENUE:UNDERLYING:SETTLEMENT:EXPIRY'"
421 );
422 });
423 }
424
425 #[rstest]
426 fn test_currency_lookup_error_to_pyvalue_err_preserves_display_text() {
427 ensure_python_initialized();
428
429 let error = "UNKNOWN_CURRENCY"
430 .parse::<crate::types::Currency>()
431 .unwrap_err();
432
433 Python::attach(|py| {
434 let py_err = currency_lookup_error_to_pyvalue_err(error);
435
436 assert!(py_err.is_instance_of::<PyValueError>(py));
437 assert_eq!(
438 py_err.value(py).to_string(),
439 "Unknown currency: UNKNOWN_CURRENCY"
440 );
441 });
442 }
443}